From d90cb9b61cd2e033b46f4780ad1340c5f35b7751 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 16 Jul 2017 17:26:58 -0700 Subject: Add image viewing and downloading (#63) * Commence aggressive image caching * Add glide toggle and css url parsing * Add image hook and refractor activities * Update version analytics * Implemented imageactivity but glide will not load * Create working image loader * Finalize image view * Finalize image view logic * Remove custom cache experiment --- app/build.gradle | 6 + app/src/debug/res/xml/file_paths.xml | 9 + app/src/main/AndroidManifest.xml | 30 +- app/src/main/assets/adblock.txt | 2344 ++++++++++++++++++++ app/src/main/assets/js/click_a.js | 2 +- app/src/main/assets/js/click_a.min.js | 4 +- app/src/main/assets/js/context_a.js | 20 +- app/src/main/assets/js/context_a.min.js | 32 +- .../kotlin/com/pitchedapps/frost/AboutActivity.kt | 143 -- .../kotlin/com/pitchedapps/frost/BaseActivity.kt | 31 - .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 1 + .../com/pitchedapps/frost/FrostWebActivity.kt | 20 - .../kotlin/com/pitchedapps/frost/LoginActivity.kt | 125 -- .../kotlin/com/pitchedapps/frost/MainActivity.kt | 462 ---- .../com/pitchedapps/frost/SelectorActivity.kt | 46 - .../com/pitchedapps/frost/SettingsActivity.kt | 143 -- .../kotlin/com/pitchedapps/frost/StartActivity.kt | 3 + .../com/pitchedapps/frost/WebOverlayActivity.kt | 157 -- .../pitchedapps/frost/activities/AboutActivity.kt | 145 ++ .../pitchedapps/frost/activities/BaseActivity.kt | 32 + .../frost/activities/FrostWebActivity.kt | 20 + .../pitchedapps/frost/activities/ImageActivity.kt | 297 +++ .../pitchedapps/frost/activities/LoginActivity.kt | 126 ++ .../pitchedapps/frost/activities/MainActivity.kt | 472 ++++ .../frost/activities/SelectorActivity.kt | 47 + .../frost/activities/SettingsActivity.kt | 145 ++ .../frost/activities/WebOverlayActivity.kt | 158 ++ .../com/pitchedapps/frost/facebook/FbConst.kt | 2 +- .../pitchedapps/frost/facebook/FbUrlFormatter.kt | 16 +- .../com/pitchedapps/frost/fragments/WebFragment.kt | 2 +- .../frost/services/FrostNotifications.kt | 2 +- .../com/pitchedapps/frost/settings/Appearance.kt | 4 +- .../com/pitchedapps/frost/settings/Behaviour.kt | 4 +- .../com/pitchedapps/frost/settings/Experimental.kt | 8 +- .../kotlin/com/pitchedapps/frost/settings/Feed.kt | 4 +- .../pitchedapps/frost/settings/Notifications.kt | 2 +- .../kotlin/com/pitchedapps/frost/utils/Prefs.kt | 16 + .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 21 +- .../com/pitchedapps/frost/utils/WebContextMenu.kt | 12 +- .../kotlin/com/pitchedapps/frost/utils/iab/IAB.kt | 2 +- .../com/pitchedapps/frost/utils/iab/IABDialogs.kt | 4 +- .../com/pitchedapps/frost/web/BaseWebViewClient.kt | 16 + .../com/pitchedapps/frost/web/FrostChromeClient.kt | 16 +- .../kotlin/com/pitchedapps/frost/web/FrostJSI.kt | 31 +- .../frost/web/FrostRequestInterceptor.kt | 67 + .../com/pitchedapps/frost/web/FrostWebView.kt | 2 +- .../pitchedapps/frost/web/FrostWebViewClient.kt | 21 +- .../com/pitchedapps/frost/web/FrostWebViewCore.kt | 13 +- .../pitchedapps/frost/web/FrostWebViewSearch.kt | 18 +- .../com/pitchedapps/frost/web/LoginWebView.kt | 4 +- app/src/main/res/drawable/frost_f_24.xml | 2 +- app/src/main/res/drawable/frost_f_256.xml | 2 +- app/src/main/res/layout/activity_image.xml | 48 + .../main/res/layout/activity_image_textless.xml | 30 + app/src/main/res/layout/activity_login.xml | 2 +- app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/layout/activity_web_overlay.xml | 2 +- app/src/main/res/values/strings.xml | 27 +- app/src/main/res/values/strings_libs.xml | 26 + .../main/res/values/strings_pref_experimental.xml | 1 - app/src/main/res/values/styles.xml | 7 + app/src/main/res/xml/changelog.xml | 19 +- app/src/main/res/xml/file_paths.xml | 9 + app/src/releaseTest/res/xml/file_paths.xml | 9 + .../com/pitchedapps/frost/facebook/FbUrlTest.kt | 14 +- docs/Changelog.md | 14 +- gradle.properties | 21 +- 67 files changed, 4266 insertions(+), 1276 deletions(-) create mode 100644 app/src/debug/res/xml/file_paths.xml create mode 100644 app/src/main/assets/adblock.txt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/BaseActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/FrostWebActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/SelectorActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt create mode 100644 app/src/main/res/layout/activity_image.xml create mode 100644 app/src/main/res/layout/activity_image_textless.xml create mode 100644 app/src/main/res/values/strings_libs.xml create mode 100644 app/src/main/res/xml/file_paths.xml create mode 100644 app/src/releaseTest/res/xml/file_paths.xml diff --git a/app/build.gradle b/app/build.gradle index 9e492f1a..2bd90315 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,6 +143,8 @@ dependencies { compile "org.jsoup:jsoup:${JSOUP}" + compile "com.squareup.okhttp3:okhttp:${OKHTTP}" + compile "com.github.bumptech.glide:glide:${GLIDE}" kapt "com.github.bumptech.glide:compiler:${GLIDE}" @@ -158,4 +160,8 @@ dependencies { compile("com.crashlytics.sdk.android:crashlytics:${CRASHLYTICS}@aar") { transitive = true; } + + compile "com.davemorrissey.labs:subsampling-scale-image-view:${SCALE_IMAGE_VIEW}" + + compile "com.sothree.slidinguppanel:library:${SLIDING_PANEL}" } \ No newline at end of file diff --git a/app/src/debug/res/xml/file_paths.xml b/app/src/debug/res/xml/file_paths.xml new file mode 100644 index 00000000..087f23c9 --- /dev/null +++ b/app/src/debug/res/xml/file_paths.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 90f2d3b3..1ae569ed 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,26 +33,26 @@ + android:theme="@style/FrostTheme.Overlay.Slide" /> + android:theme="@style/FrostTheme.Overlay.Slide"> @@ -107,17 +107,20 @@ + + + + diff --git a/app/src/main/assets/adblock.txt b/app/src/main/assets/adblock.txt new file mode 100644 index 00000000..1d561271 --- /dev/null +++ b/app/src/main/assets/adblock.txt @@ -0,0 +1,2344 @@ +# Ad server list for use with hosts files to block ads +# +# For more information about this list, see: https://pgl.yoyo.org/adservers/ +# ---- +# last updated: Wed, 12 Jul 2017 13:54:58 GMT +# entries: 2331 +# format: hosts (hosts -- in hosts file format) +# credits: Peter Lowe - pgl@yoyo.org - https://pgl.yoyo.org/ - https://twitter.com/pgl +# this URL: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext&useip=0.0.0.0 +# other formats: https://pgl.yoyo.org/adservers/formats.php +# policy: https://pgl.yoyo.org/adservers/policy.php +# +# start date: Wed, 12 Jul 2017 00:00:00 +101com.com +101order.com +123found.com +180hits.de +180searchassistant.com +207.net +247media.com +24log.com +24log.de +24pm-affiliation.com +2mdn.net +2o7.net +360yield.com +4affiliate.net +4d5.net +50websads.com +518ad.com +51yes.com +600z.com +777partner.com +77tracking.com +7bpeople.com +7search.com +99count.com +a-ads.com +a-counter.kiev.ua +a.0day.kiev.ua +a.aproductmsg.com +a.collective-media.net +a.consumer.net +a.mktw.net +a.sakh.com +a.ucoz.net +a.ucoz.ru +a.xanga.com +a32.g.a.yimg.com +aaddzz.com +abacho.net +abc-ads.com +absoluteclickscom.com +abz.com +ac.rnm.ca +accounts.pkr.com.invalid +acsseo.com +actionsplash.com +actualdeals.com +acuityads.com +ad-balancer.at +ad-balancer.net +ad-center.com +ad-pay.de +ad-rotator.com +ad-server.gulasidorna.se +ad-serverparc.nl +ad-souk.com +ad-space.net +ad-tech.com +ad-up.com +ad.100.tbn.ru +ad.71i.de +ad.a8.net +ad.abcnews.com +ad.abctv.com +ad.aboutwebservices.com +ad.abum.com +ad.afy11.net +ad.allstar.cz +ad.altervista.org +ad.amgdgt.com +ad.anuntis.com +ad.auditude.com +ad.bizo.com +ad.bnmla.com +ad.bondage.com +ad.caradisiac.com +ad.centrum.cz +ad.cgi.cz +ad.choiceradio.com +ad.clix.pt +ad.cooks.com +ad.crwdcntrl.net +ad.digitallook.com +ad.directrev.com +ad.doctissimo.fr +ad.domainfactory.de +ad.e-kolay.net +ad.eurosport.com +ad.f1cd.ru +ad.flurry.com +ad.foxnetworks.com +ad.freecity.de +ad.gate24.ch +ad.globe7.com +ad.grafika.cz +ad.hbv.de +ad.hodomobile.com +ad.httpool.com +ad.hyena.cz +ad.iinfo.cz +ad.ilove.ch +ad.infoseek.com +ad.jamba.net +ad.jamster.co.uk +ad.jetsoftware.com +ad.keenspace.com +ad.leadbolt.net +ad.liveinternet.ru +ad.lupa.cz +ad.media-servers.net +ad.mediastorm.hu +ad.mgd.de +ad.musicmatch.com +ad.nachtagenten.de +ad.nozonedata.com +ad.nttnavi.co.jp +ad.nwt.cz +ad.onad.eu +ad.pandora.tv +ad.preferances.com +ad.profiwin.de +ad.prv.pl +ad.rambler.ru +ad.reunion.com +ad.scanmedios.com +ad.sensismediasmart.com.au +ad.seznam.cz +ad.simgames.net +ad.slutload.com +ad.smartclip.net +ad.tbn.ru +ad.technoratimedia.com +ad.thewheelof.com +ad.turn.com +ad.tv2.no +ad.twitchguru.com +ad.usatoday.com +ad.virtual-nights.com +ad.wavu.hu +ad.way.cz +ad.weatherbug.com +ad.wsod.com +ad.wz.cz +ad.yadro.ru +ad.yourmedia.com +ad.zanox.com +ad0.bigmir.net +ad01.mediacorpsingapore.com +ad1.emediate.dk +ad1.emule-project.org +ad1.kde.cz +ad1.pamedia.com.au +ad2.iinfo.cz +ad2.linxcz.cz +ad2.lupa.cz +ad2flash.com +ad2games.com +ad3.iinfo.cz +ad3.pamedia.com.au +ad4game.com +adaction.de +adadvisor.net +adap.tv +adapt.tv +adbanner.ro +adbard.net +adbers.com +adblade.com +adblockanalytics.com +adboost.de.vu +adboost.net +adbooth.net +adbot.com +adbrite.com +adbroker.de +adbunker.com +adbutler.com +adbutler.de +adbuyer.com +adbuyer3.lycos.com +adcash.com +adcast.deviantart.com +adcell.de +adcenter.mdf.se +adcenter.net +adcentriconline.com +adcept.net +adclick.com +adclient.uimserv.net +adclient1.tucows.com +adcomplete.com +adconion.com +adcontent.gamespy.com +adcycle.com +add.newmedia.cz +addealing.com +addfreestats.com +addme.com +adecn.com +ademails.com +adengage.com +adexchangeprediction.com +adexpose.com +adext.inkclub.com +adf.ly +adfactor.nl +adfarm.mediaplex.com +adflight.com +adforce.com +adform.com +adgardener.com +adgoto.com +adgridwork.com +adhese.be +adhese.com +adimage.asiaone.com.sg +adimage.guardian.co.uk +adimages.been.com +adimages.carsoup.com +adimages.go.com +adimages.homestore.com +adimages.omroepzeeland.nl +adimages.sanomawsoy.fi +adimg.cnet.com +adimg.com.com +adimg.uimserv.net +adimg1.chosun.com +adimgs.sapo.pt +adimpact.com +adinjector.net +adinterax.com +adisfy.com +adition.com +adition.de +adition.net +adizio.com +adjix.com +adjug.com +adjuggler.com +adjuggler.yourdictionary.com +adjustnetwork.com +adk2.com +adk2ads.tictacti.com +adland.ru +adlantic.nl +adledge.com +adlegend.com +adlog.com.com +adloox.com +adlooxtracking.com +adlure.net +admagnet.net +admailtiser.com +adman.gr +adman.in.gr +adman.otenet.gr +admanagement.ch +admanager.btopenworld.com +admanager.carsoup.com +admarketplace.net +admarvel.com +admax.nexage.com +admedia.com +admedia.ro +admeld.com +admerize.be +admeta.com +admex.com +adminder.com +adminshop.com +admized.com +admob.com +admonitor.com +admotion.com.ar +adnet-media.net +adnet.asahi.com +adnet.biz +adnet.de +adnet.ru +adnet.worldreviewer.com +adnetinteractive.com +adnetwork.net +adnetworkperformance.com +adnews.maddog2000.de +adnotch.com +adnxs.com +adocean.pl +adonspot.com +adoperator.com +adorigin.com +adpepper.dk +adpepper.nl +adperium.com +adpia.vn +adplus.co.id +adplxmd.com +adprofile.net +adprojekt.pl +adq.nextag.com +adrazzi.com +adreactor.com +adrecreate.com +adremedy.com +adreporting.com +adres.internet.com +adrevolver.com +adriver.ru +adrolays.de +adrotate.de +adrotator.se +adrta.com +ads-click.com +ads.4tube.com +ads.5ci.lt +ads.abovetopsecret.com +ads.aceweb.net +ads.activestate.com +ads.adfox.ru +ads.administrator.de +ads.adshareware.net +ads.adultfriendfinder.com +ads.adultswim.com +ads.advance.net +ads.adverline.com +ads.affiliates.match.com +ads.ak.facebook.com.edgesuite.net +ads.allvatar.com +ads.alt.com +ads.amdmb.com +ads.amigos.com +ads.aol.co.uk +ads.aol.com +ads.apn.co.nz +ads.appsgeyser.com +ads.as4x.tmcs.net +ads.as4x.tmcs.ticketmaster.com +ads.asia1.com.sg +ads.asiafriendfinder.com +ads.ask.com +ads.aspalliance.com +ads.avazu.net +ads.batpmturner.com +ads.beenetworks.net +ads.belointeractive.com +ads.berlinonline.de +ads.betanews.com +ads.betfair.com +ads.betfair.com.au +ads.bigchurch.com +ads.bigfoot.com +ads.bing.com +ads.bittorrent.com +ads.blog.com +ads.bloomberg.com +ads.bluelithium.com +ads.bluemountain.com +ads.bluesq.com +ads.bonniercorp.com +ads.boylesports.com +ads.brabys.com +ads.brazzers.com +ads.bumq.com +ads.businessweek.com +ads.canalblog.com +ads.canoe.ca +ads.casinocity.com +ads.cbc.ca +ads.cc +ads.cc-dt.com +ads.centraliprom.com +ads.cgnetworks.com +ads.channel4.com +ads.clearchannel.com +ads.co.com +ads.com.com +ads.contactmusic.com +ads.contentabc.com +ads.contextweb.com +ads.crakmedia.com +ads.creative-serving.com +ads.creativematch.com +ads.cricbuzz.com +ads.cybersales.cz +ads.dada.it +ads.datinggold.com +ads.datingyes.com +ads.dazoot.ro +ads.deltha.hu +ads.dennisnet.co.uk +ads.desmoinesregister.com +ads.detelefoongids.nl +ads.deviantart.com +ads.digital-digest.com +ads.digitalmedianet.com +ads.digitalpoint.com +ads.directionsmag.com +ads.domain.com +ads.domeus.com +ads.eagletribune.com +ads.easy-forex.com +ads.eatinparis.com +ads.economist.com +ads.edbindex.dk +ads.egrana.com.br +ads.einmedia.com +ads.electrocelt.com +ads.elitetrader.com +ads.emirates.net.ae +ads.epltalk.com +ads.eu.msn.com +ads.exactdrive.com +ads.expat-blog.biz +ads.expedia.com +ads.ezboard.com +ads.factorymedia.com +ads.fairfax.com.au +ads.faxo.com +ads.ferianc.com +ads.filmup.com +ads.financialcontent.com +ads.flooble.com +ads.fool.com +ads.footymad.net +ads.forbes.com +ads.forbes.net +ads.forium.de +ads.fortunecity.com +ads.fotosidan.se +ads.foxkidseurope.net +ads.foxnetworks.com +ads.foxnews.com +ads.freecity.de +ads.friendfinder.com +ads.ft.com +ads.futurenet.com +ads.gamecity.net +ads.gamershell.com +ads.gamespyid.com +ads.gamigo.de +ads.gaming-universe.de +ads.gawker.com +ads.geekswithblogs.net +ads.glispa.com +ads.gmodules.com +ads.godlikeproductions.com +ads.goyk.com +ads.gplusmedia.com +ads.gradfinder.com +ads.grindinggears.com +ads.groundspeak.com +ads.gsm-exchange.com +ads.gsmexchange.com +ads.guardian.co.uk +ads.guardianunlimited.co.uk +ads.guru3d.com +ads.hardwaresecrets.com +ads.harpers.org +ads.hbv.de +ads.hearstmags.com +ads.heartlight.org +ads.heias.com +ads.hideyourarms.com +ads.hollywood.com +ads.horsehero.com +ads.horyzon-media.com +ads.iafrica.com +ads.ibest.com.br +ads.ibryte.com +ads.icq.com +ads.ign.com +ads.img.co.za +ads.imgur.com +ads.indiatimes.com +ads.infi.net +ads.internic.co.il +ads.ipowerweb.com +ads.isoftmarketing.com +ads.itv.com +ads.iwon.com +ads.jewishfriendfinder.com +ads.jiwire.com +ads.jobsite.co.uk +ads.jpost.com +ads.jubii.dk +ads.justhungry.com +ads.kaktuz.net +ads.kelbymediagroup.com +ads.kinobox.cz +ads.kinxxx.com +ads.kompass.com +ads.krawall.de +ads.lesbianpersonals.com +ads.linuxfoundation.org +ads.linuxjournal.com +ads.linuxsecurity.com +ads.livenation.com +ads.mariuana.it +ads.massinfra.nl +ads.mcafee.com +ads.mediaodyssey.com +ads.medienhaus.de +ads.mgnetwork.com +ads.mmania.com +ads.moceanads.com +ads.motor-forum.nl +ads.motormedia.nl +ads.msn.com +ads.multimania.lycos.fr +ads.nationalgeographic.com +ads.ncm.com +ads.netmechanic.com +ads.networksolutions.com +ads.newdream.net +ads.newgrounds.com +ads.newmedia.cz +ads.newsint.co.uk +ads.newsquest.co.uk +ads.ninemsn.com.au +ads.nj.com +ads.nola.com +ads.nordichardware.com +ads.nordichardware.se +ads.nwsource.com +ads.nyi.net +ads.nytimes.com +ads.nyx.cz +ads.nzcity.co.nz +ads.o2.pl +ads.oddschecker.com +ads.okcimg.com +ads.ole.com +ads.olivebrandresponse.com +ads.oneplace.com +ads.ookla.com +ads.optusnet.com.au +ads.outpersonals.com +ads.passion.com +ads.pennet.com +ads.penny-arcade.com +ads.pheedo.com +ads.phpclasses.org +ads.pickmeup-ltd.com +ads.planet.nl +ads.pni.com +ads.pof.com +ads.powweb.com +ads.primissima.it +ads.printscr.com +ads.prisacom.com +ads.program3.com +ads.psd2html.com +ads.pushplay.com +ads.quoka.de +ads.rcs.it +ads.recoletos.es +ads.rediff.com +ads.redlightcenter.com +ads.redtube.com +ads.resoom.de +ads.returnpath.net +ads.s3.sitepoint.com +ads.satyamonline.com +ads.savannahnow.com +ads.saymedia.com +ads.scifi.com +ads.seniorfriendfinder.com +ads.servebom.com +ads.sexinyourcity.com +ads.shizmoo.com +ads.shopstyle.com +ads.sift.co.uk +ads.silverdisc.co.uk +ads.slim.com +ads.smartclick.com +ads.soft32.com +ads.space.com +ads.sptimes.com +ads.stackoverflow.com +ads.sun.com +ads.supplyframe.com +ads.t-online.de +ads.tahono.com +ads.techtv.com +ads.telegraph.co.uk +ads.themovienation.com +ads.thestar.com +ads.tmcs.net +ads.totallyfreestuff.com +ads.townhall.com +ads.trinitymirror.co.uk +ads.tripod.com +ads.tripod.lycos.co.uk +ads.tripod.lycos.de +ads.tripod.lycos.es +ads.tripod.lycos.it +ads.tripod.lycos.nl +ads.tripod.spray.se +ads.tso.dennisnet.co.uk +ads.uknetguide.co.uk +ads.ultimate-guitar.com +ads.uncrate.com +ads.undertone.com +ads.usatoday.com +ads.v3.com +ads.verticalresponse.com +ads.vgchartz.com +ads.videosz.com +ads.virtual-nights.com +ads.virtualcountries.com +ads.vnumedia.com +ads.waps.cn +ads.wapx.cn +ads.weather.ca +ads.web.aol.com +ads.web.cs.com +ads.web.de +ads.webmasterpoint.org +ads.websiteservices.com +ads.whi.co.nz +ads.whoishostingthis.com +ads.wiezoekje.nl +ads.wikia.nocookie.net +ads.wineenthusiast.com +ads.wwe.biz +ads.xhamster.com +ads.xtra.co.nz +ads.y-0.net +ads.yahoo.com +ads.yimg.com +ads.yldmgrimg.net +ads.yourfreedvds.com +ads.youtube.com +ads.zdnet.com +ads.ztod.com +ads03.redtube.com +ads1.canoe.ca +ads1.mediacapital.pt +ads1.msn.com +ads1.rne.com +ads1.virtual-nights.com +ads10.speedbit.com +ads180.com +ads2.brazzers.com +ads2.clearchannel.com +ads2.contentabc.com +ads2.gamecity.net +ads2.jubii.dk +ads2.net-communities.co.uk +ads2.oneplace.com +ads2.rne.com +ads2.virtual-nights.com +ads2.xnet.cz +ads2004.treiberupdate.de +ads3.contentabc.com +ads3.gamecity.net +ads3.virtual-nights.com +ads4.clearchannel.com +ads4.gamecity.net +ads4.virtual-nights.com +ads4homes.com +ads5.canoe.ca +ads5.virtual-nights.com +ads6.gamecity.net +ads7.gamecity.net +ads8.com +adsatt.abc.starwave.com +Adsatt.ABCNews.starwave.com +adsatt.espn.go.com +adsatt.espn.starwave.com +Adsatt.go.starwave.com +adsby.bidtheatre.com +adscale.de +adscholar.com +adscience.nl +adscpm.com +adsdaq.com +adsdk.com +adsend.de +adserv.evo-x.de +adserv.gamezone.de +adserv.iafrica.com +adserv.qconline.com +adserve.ams.rhythmxchange.com +adserver-live.yoc.mobi +adserver.43plc.com +adserver.71i.de +adserver.adultfriendfinder.com +adserver.aidameter.com +adserver.aol.fr +adserver.beggarspromo.com +adserver.betandwin.de +adserver.bing.com +adserver.bizhat.com +adserver.break-even.it +adserver.cams.com +adserver.com +adserver.digitoday.com +adserver.dotcommedia.de +adserver.finditquick.com +adserver.flossiemediagroup.com +adserver.freecity.de +adserver.freenet.de +adserver.friendfinder.com +adserver.hardsextube.com +adserver.hardwareanalysis.com +adserver.html.it +adserver.irishwebmasterforum.com +adserver.janes.com +adserver.libero.it +adserver.news.com.au +adserver.ngz-network.de +adserver.nydailynews.com +adserver.o2.pl +adserver.oddschecker.com +adserver.omroepzeeland.nl +adserver.pl +adserver.portalofevil.com +adserver.portugalmail.net +adserver.portugalmail.pt +adserver.sanomawsoy.fi +adserver.sciflicks.com +adserver.sharewareonline.com +adserver.spankaway.com +adserver.startnow.com +adserver.theonering.net +adserver.twitpic.com +adserver.viagogo.com +adserver.virginmedia.com +adserver.yahoo.com +adserver01.de +adserver1-images.backbeatmedia.com +adserver1.backbeatmedia.com +adserver1.mindshare.de +adserver1.ogilvy-interactive.de +adserver2.mindshare.de +adserverplus.com +adserversolutions.com +adservinginternational.com +adsfac.eu +adsfac.net +adsfac.us +adshost1.com +adside.com +adsk2.co +adskape.ru +adsklick.de +adsmarket.com +adsmart.co.uk +adsmart.com +adsmart.net +adsmogo.com +adsnative.com +adsoftware.com +adsoldier.com +adsonar.com +adspace.ro +adspeed.net +adspirit.de +adsponse.de +adsremote.scrippsnetworks.com +adsrevenue.net +adsrv.deviantart.com +adsrv.eacdn.com +adsrv.iol.co.za +adsrvr.org +adsstat.com +adstat.4u.pl +adstest.weather.com +adsupply.com +adsymptotic.com +adsynergy.com +adsys.townnews.com +adsystem.simplemachines.org +adtech.de +adtechus.com +adtegrity.net +adthis.com +adtiger.de +adtoll.com +adtology.com +adtoma.com +adtrace.org +adtrade.net +adtrading.de +adtrak.net +adtriplex.com +adultadvertising.com +adv-adserver.com +adv-banner.libero.it +adv.cooperhosting.net +adv.freeonline.it +adv.hwupgrade.it +adv.livedoor.com +adv.webmd.com +adv.wp.pl +adv.yo.cz +advariant.com +adventory.com +advert.bayarea.com +advert.dyna.ultraweb.hu +adverticum.com +adverticum.net +adverticus.de +advertise.com +advertiseireland.com +advertisespace.com +advertising.com +advertising.guildlaunch.net +advertisingbanners.com +advertisingbox.com +advertisingtag.net +advertmarket.com +advertmedia.de +advertpro.sitepoint.com +advertpro.ya.com +adverts.carltononline.com +advertserve.com +advertstream.com +advertwizard.com +advideo.uimserv.net +adview.ppro.de +advisormedia.cz +adviva.net +advnt.com +adwareremovergold.com +adwhirl.com +adwitserver.com +adworldnetwork.com +adworx.at +adworx.be +adworx.nl +adx.allstar.cz +adx.atnext.com +adxpansion.com +adxpose.com +adxvalue.com +adyea.com +adzerk.net +adzerk.s3.amazonaws.com +adzones.com +af-ad.co.uk +affbuzzads.com +affili.net +affiliate.1800flowers.com +affiliate.doubleyourdating.com +affiliate.dtiserv.com +affiliate.gamestop.com +affiliate.mercola.com +affiliate.mogs.com +affiliate.offgamers.com +affiliate.travelnow.com +affiliate.viator.com +affiliatefuel.com +affiliatefuture.com +affiliates.allposters.com +affiliates.babylon.com +affiliates.digitalriver.com +affiliates.globat.com +affiliates.internationaljock.com +affiliates.streamray.com +affiliates.thinkhost.net +affiliates.thrixxx.com +affiliates.ultrahosting.com +affiliatetracking.com +affiliatetracking.net +affiliatewindow.com +affiliation-france.com +afftracking.justanswer.com +ah-ha.com +ahalogy.com +aidu-ads.de +aim4media.com +aistat.net +aktrack.pubmatic.com +alclick.com +alenty.com +alexa-sitestats.s3.amazonaws.com +alipromo.com +all4spy.com +alladvantage.com +allosponsor.com +amazingcounters.com +amazon-adsystem.com +americash.com +amung.us +an.tacoda.net +anahtars.com +analytics.adpost.org +analytics.google.com +analytics.live.com +analytics.yahoo.com +anm.intelli-direct.com +annonser.dagbladet.no +api.intensifier.de +apture.com +arc1.msn.com +arcadebanners.com +ard.xxxblackbook.com +are-ter.com +as.webmd.com +as1.advfn.com +assets1.exgfnetwork.com +assoc-amazon.com +at-adserver.alltop.com +atdmt.com +athena-ads.wikia.com +atwola.com +auctionads.com +auctionads.net +audience2media.com +audit.median.hu +audit.webinform.hu +auto-bannertausch.de +autohits.dk +avenuea.com +avpa.javalobby.org +avres.net +avsads.com +awempire.com +awin1.com +azfront.com +b-1st.com +b.aol.com +b.engadget.com +ba.afl.rakuten.co.jp +babs.tv2.dk +backbeatmedia.com +banik.redigy.cz +banner-exchange-24.de +banner.ad.nu +banner.ambercoastcasino.com +banner.blogranking.net +banner.buempliz-online.ch +banner.casino.net +banner.casinodelrio.com +banner.cotedazurpalace.com +banner.coza.com +banner.cz +banner.easyspace.com +banner.elisa.net +banner.eurogrand.com +banner.featuredusers.com +banner.getgo.de +banner.goldenpalace.com +banner.img.co.za +banner.inyourpocket.com +banner.kiev.ua +banner.linux.se +banner.media-system.de +banner.mindshare.de +banner.nixnet.cz +banner.noblepoker.com +banner.northsky.com +banner.orb.net +banner.penguin.cz +banner.rbc.ru +banner.relcom.ru +banner.tanto.de +banner.titan-dsl.de +banner.vadian.net +banner.webmersion.com +banner.wirenode.com +bannerads.de +bannerboxes.com +bannercommunity.de +bannerconnect.com +bannerconnect.net +bannerexchange.cjb.net +bannerflow.com +bannergrabber.internet.gr +bannerhost.com +bannerimage.com +bannerlandia.com.ar +bannermall.com +bannermarkt.nl +bannerpower.com +banners.adultfriendfinder.com +banners.amigos.com +banners.asiafriendfinder.com +banners.audioholics.com +banners.babylon-x.com +banners.bol.com.br +banners.cams.com +banners.clubseventeen.com +banners.czi.cz +banners.dine.com +banners.direction-x.com +banners.directnic.com +banners.easydns.com +banners.freett.com +banners.friendfinder.com +banners.getiton.com +banners.iq.pl +banners.isoftmarketing.com +banners.lifeserv.com +banners.linkbuddies.com +banners.passion.com +banners.resultonline.com +banners.sexsearch.com +banners.sys-con.com +banners.thomsonlocal.com +banners.videosz.com +banners.virtuagirlhd.com +banners.wunderground.com +bannerserver.com +bannersgomlm.com +bannershotlink.perfectgonzo.com +bannersng.yell.com +bannerspace.com +bannerswap.com +bannertesting.com +bannery.cz +bannieres.acces-contenu.com +bans.adserver.co.il +bans.bride.ru +barnesandnoble.bfast.com +basebanner.com +baypops.com +bbelements.com +bbn.img.com.ua +begun.ru +belstat.com +belstat.nl +berp.com +best-pr.info +best-top.ro +bestsearch.net +bhclicks.com +bidclix.com +bidclix.net +bidswitch.net +bidtrk.com +bidvertiser.com +bigbangmedia.com +bigclicks.com +billboard.cz +bitads.net +bitmedianetwork.com +bizad.nikkeibp.co.jp +bizrate.com +blast4traffic.com +blingbucks.com +blogads.com +blogcounter.de +blogherads.com +blogrush.com +blogtoplist.se +blogtopsites.com +blueadvertise.com +bluekai.com +bluelithium.com +bluewhaleweb.com +bm.annonce.cz +bn.bfast.com +boersego-ads.de +boldchat.com +boom.ro +boomads.com +boost-my-pr.de +box.anchorfree.net +bpath.com +braincash.com +brandreachsys.com +bravenet.com.invalid +bridgetrack.com +brightinfo.com +british-banners.com +bs.yandex.ru +bttrack.com +budsinc.com +bullseye.backbeatmedia.com +buyhitscheap.com +buysellads.com +buzzonclick.com +bvalphaserver.com +bwp.download.com +c.bigmir.net +c1.nowlinux.com +c1exchange.com +campaign.bharatmatrimony.com +caniamedia.com +carbonads.com +carbonads.net +casalemedia.com +casalmedia.com +cash4members.com +cash4popup.de +cashcrate.com +cashengines.com +cashfiesta.com +cashlayer.com +cashpartner.com +casinogames.com +casinopays.com +casinorewards.com +casinotraffic.com +casinotreasure.com +cbanners.virtuagirlhd.com +cbmall.com +cdn.freefacti.com +cdn.freefarcy.com +cecash.com +centerpointmedia.com +ceskydomov.alias.ngs.modry.cz +cetrk.com +cgicounter.puretec.de +ch.questionmarket.com +chameleon.ad +channelintelligence.com +chart.dk +chartbeat.com +chartbeat.net +checkm8.com +checkstat.nl +chestionar.ro +chitika.net +cibleclick.com.invalid +cityads.telus.net +cj.com +cjbmanagement.com +cjlog.com +claria.com +class-act-clicks.com +click.absoluteagency.com +click.fool.com +click.kmindex.ru +click2freemoney.com +click2paid.com +clickability.com +clickadz.com +clickagents.com +clickbank.com +clickbank.net +clickbooth.com +clickboothlnk.com +clickbrokers.com +clickcompare.co.uk +clickdensity.com +clickedyclick.com +clickhereforcellphones.com +clickhouse.com +clickhype.com +clicklink.jp +clickmedia.ro +clickonometrics.pl +clicks.equantum.com +clicks.mods.de +clickserve.cc-dt.com +clicksor.com +clicktag.de +clickthruserver.com +clickthrutraffic.com +clicktrace.info +clicktrack.ziyu.net +clicktracks.com +clicktrade.com +clickxchange.com +clickz.com +clickzxc.com +clicmanager.fr +clientmetrics-pa.googleapis.com +clients.tbo.com +clixgalore.com +clk.konflab.com +clkads.com +clkrev.com +cluster.adultworld.com +clustrmaps.com +cmpstar.com +cnomy.com +cnt.spbland.ru +cnt1.pocitadlo.cz +code-server.biz +colonize.com +comclick.com +commindo-media-ressourcen.de +commissionmonster.com +compactbanner.com +comprabanner.it +confirmed-profits.com +connextra.com +contaxe.de +content.acc-hd.de +content.ad +contextweb.com +conversantmedia.com +conversionruler.com +cookies.cmpnet.com +coremetrics.com +count.rbc.ru +count.rin.ru +count.west263.com +counted.com +counter.bloke.com +counter.cnw.cz +counter.cz +counter.dreamhost.com +counter.fateback.com +counter.mirohost.net +counter.mojgorod.ru +counter.nowlinux.com +counter.rambler.ru +counter.search.bg +counter.sparklit.com +counter.yadro.ru +counters.honesty.com +counting.kmindex.ru +counts.tucows.com +coupling-media.de +cpalead.com +cpays.com +cpmaffiliation.com +cpmstar.com +cpxinteractive.com +cqcounter.com +crakmedia.com +craktraffic.com +crawlability.com +crazypopups.com +creafi-online-media.com +creative.whi.co.nz +creatives.as4x.tmcs.net +creatives.livejasmin.com +crispads.com +criteo.com +crowdgravity.com +crtv.mate1.com +crwdcntrl.net +ctnetwork.hu +cubics.com +customad.cnn.com +cyberbounty.com +cybermonitor.com +d.adroll.com +dakic-ia-300.com +danban.com +dapper.net +datashreddergold.com +dbbsrv.com +dc-storm.com +de17a.com +dealdotcom.com +debtbusterloans.com +decknetwork.net +deloo.de +demandbase.com +demdex.net +di1.shopping.com +dialerporn.com +didtheyreadit.com +direct-xxx-access.com +directaclick.com +directivepub.com +directleads.com +directorym.com +directtrack.com +discountclick.com +displayadsmedia.com +dist.belnk.com +dmtracker.com +dmtracking.alibaba.com +dmtracking2.alibaba.com +dnads.directnic.com +domaining.in +domainsponsor.com +domainsteam.de +domdex.com +doubleclick.com +doubleclick.de +doubleclick.net +doublepimp.com +drumcash.com +dynamic.fmpub.net +e-adimages.scrippsnetworks.com +e-bannerx.com +e-debtconsolidation.com +e-m.fr +e-n-t-e-r-n-e-x.com +e-planning.net +e.kde.cz +eadexchange.com +eas.almamedia.fi +easyhits4u.com +ebayadvertising.com +ebocornac.com +ebuzzing.com +ecircle-ag.com +eclick.vn +ecoupons.com +edgeio.com +effectivemeasure.com +effectivemeasure.net +eiv.baidu.com +elitedollars.com +elitetoplist.com +emarketer.com +emediate.dk +emediate.eu +engine.espace.netavenir.com +enginenetwork.com +enoratraffic.com +enquisite.com +entercasino.com +entrecard.s3.amazonaws.com +eqads.com +ero-advertising.com +esellerate.net +estat.com +etahub.com +etargetnet.com +etracker.de +eu-adcenter.net +eu1.madsone.com +eur.a1.yimg.com +eurekster.com +euroclick.com +euros4click.de +eusta.de +evergage.com +evidencecleanergold.com +ewebcounter.com +exchange-it.com +exchange.bg +exchangead.com +exchangeclicksonline.com +exelator.com +exit76.com +exitexchange.com +exitfuel.com +exoclick.com +exogripper.com +experteerads.com +exponential.com +express-submit.de +extractorandburner.com +extreme-dm.com +extremetracking.com +eyeblaster.com +eyereturn.com +eyeviewads.com +eyewonder.com +ezula.com +f5biz.com +fast-adv.it +fastclick.com +fastclick.com.edgesuite.net +fastclick.net +fb-promotions.com +fc.webmasterpro.de +feedbackresearch.com +feedjit.com +ffxcam.fairfax.com.au +fimc.net +fimserve.com +findcommerce.com +findyourcasino.com +fineclicks.com +first.nova.cz +firstlightera.com +flashtalking.com +fleshlightcash.com +flexbanner.com +flowgo.com +flurry.com +fonecta.leiki.com +foo.cosmocode.de +forex-affiliate.net +fpctraffic.com +fpctraffic2.com +fragmentserv.iac-online.de +free-banners.com +freebanner.com +freelogs.com +freeonlineusers.com +freepay.com +freestats.com +freestats.tv +freewebcounter.com +funklicks.com +funpageexchange.com +fusionads.net +fusionquest.com +fxstyle.net +galaxien.com +game-advertising-online.com +gamehouse.com +gamesites100.net +gamesites200.com +gamesitestop100.com +gator.com +gbanners.hornymatches.com +gemius.pl +geo.digitalpoint.com +geobanner.adultfriendfinder.com +geovisite.com +getclicky.com +globalismedia.com +globaltakeoff.net +globaltrack.com.invalid +globe7.com +globus-inter.com +gmads.net +go-rank.de +goingplatinum.com +goldstats.com +google-analytics.com +googleadservices.com +googlesyndication.com +gostats.com +gp.dejanews.com +gpr.hu +grafstat.ro +grapeshot.co.uk +greystripe.com +gtop100.com +gunggo.com +harrenmedia.com +harrenmedianetwork.com +havamedia.net +heias.com +hentaicounter.com +herbalaffiliateprogram.com +hexusads.fluent.ltd.uk +heyos.com +hgads.com +hidden.gogoceleb.com +hightrafficads.com +histats.com +hit-parade.com +hit.bg +hit.ua +hit.webcentre.lycos.co.uk +hitbox.com +hitcents.com +hitfarm.com +hitiz.com +hitlist.ru +hitlounge.com +hitometer.com +hits.europuls.eu +hits.informer.com +hits.puls.lv +hits.theguardian.com +hits4me.com +hits4pay.com +hitslink.com +hittail.com +hollandbusinessadvertising.nl +homepageking.de +hostedads.realitykings.com +hotjar.com +hotkeys.com +hotlog.ru +hotrank.com.tw +hs-analytics.net +htmlhubing.xyz +httpool.com +hurricanedigitalmedia.com +hydramedia.com +hyperbanner.net +hypertracker.com +i-clicks.net +i.xx.openx.com +i1img.com +i1media.no +ia.iinfo.cz +iad.anm.co.uk +iadnet.com +iasds01.com +iconadserver.com +icptrack.com +idcounter.com +identads.com +idregie.com +idtargeting.com +ientrymail.com +iesnare.com +ifa.tube8live.com +ilbanner.com +ilead.itrack.it +ilovecheating.com +imageads.canoe.ca +imagecash.net +images-pw.secureserver.net +images.v3.com +imarketservices.com +img.prohardver.hu +imgpromo.easyrencontre.com +imonitor.nethost.cz +imprese.cz +impressionmedia.cz +impressionz.co.uk +imrworldwide.com +inboxdollars.com +incentaclick.com +indexstats.com +indieclick.com +industrybrains.com +inetlog.ru +infinite-ads.com +infinityads.com +infolinks.com +information.com +inringtone.com +insightexpress.com +insightexpressai.com +instantmadness.com +intelliads.com +intellitxt.com +interactive.forthnet.gr +intergi.com +internetfuel.com +interreklame.de +interstat.hu +ip.ro +ip193.cn +iperceptions.com +ipro.com +ireklama.cz +itfarm.com +itop.cz +its-that-easy.com +itsptp.com +jcount.com +jinkads.de +joetec.net +js.users.51.la +juicyads.com +jumptap.com +justrelevant.com +justwebads.com +k.iinfo.cz +kanoodle.com +keymedia.hu +kindads.com +kissmetrics.com +kliks.nl +kniverto.com +komoona.com +kompasads.com +kontera.com +kt-g.de +ktu.sv2.biz +lakequincy.com +launchbit.com +layer-ad.de +layer-ads.de +lbn.ru +lct.salesforce.com +lead-analytics.nl +leadboltads.net +leadclick.com +leadingedgecash.com +leadzupc.com +levelrate.de +lfstmedia.com +liftdna.com +ligatus.com +ligatus.de +lightningcast.net +lightspeedcash.com +link-booster.de +link4ads.com +linkadd.de +linkbuddies.com +linkexchange.com +linkprice.com +linkrain.com +linkreferral.com +links-ranking.de +linkshighway.com +linkstorms.com +linkswaper.com +linktarget.com +liquidad.narrowcastmedia.com +liveintent.com +liverail.com +loading321.com +log.btopenworld.com +logua.com +lop.com +lucidmedia.com +lzjl.com +m.webtrends.com +m1.webstats4u.com +m4n.nl +mackeeperapp.mackeeper.com +madclient.uimserv.net +madisonavenue.com +mads.cnet.com +madvertise.de +marchex.com +market-buster.com +marketing.888.com +marketing.hearstmagazines.nl +marketing.nyi.net +marketing.osijek031.com +marketingsolutions.yahoo.com +maroonspider.com +mas.sector.sk +mastermind.com +matchcraft.com +mathtag.com +max.i12.de +maximumcash.com +mbn.com.ua +mbs.megaroticlive.com +mbuyu.nl +mdotm.com +measuremap.com +media-adrunner.mycomputer.com +media-servers.net +media.ftv-publicite.fr +media.funpic.de +media6degrees.com +mediaarea.eu +mediacharger.com +mediadvertising.ro +mediageneral.com +mediamath.com +mediamgr.ugo.com +mediaplazza.com +mediaplex.com +mediascale.de +mediatext.com +mediax.angloinfo.com +mediaz.angloinfo.com +medleyads.com +medyanetads.com +megacash.de +megago.com +megastats.com +megawerbung.de +metaffiliation.com +metanetwork.com +methodcash.com +metrics.cnn.com +metrics.windowsitpro.com +mgid.com +miarroba.com +microstatic.pl +microticker.com +midnightclicking.com +misstrends.com +mixpanel.com +mixtraffic.com +mjxads.internet.com +mlm.de +mmismm.com +mmtro.com +moatads.com +mobclix.com +mocean.mobi +moneyexpert.com +monsterpops.com +mopub.com +mouseflow.com +mpstat.us +mr-rank.de +mrskincash.com +mtree.com +musiccounter.ru +muwmedia.com +myaffiliateprogram.com +mybloglog.com +mycounter.ua +mymoneymakingapp.com +mypagerank.net +mypagerank.ru +mypowermall.com +mystat-in.net +mystat.pl +mytop-in.net +n69.com +naiadsystems.com.invalid +naj.sk +namimedia.com +nastydollars.com +navigator.io +navrcholu.cz +nbjmp.com +ndparking.com +nedstat.com +nedstat.nl +nedstatbasic.net +nedstatpro.net +nend.net +neocounter.neoworx-blog-tools.net +neoffic.com +net-filter.com +netaffiliation.com +netagent.cz +netclickstats.com +netcommunities.com +netdirect.nl +netincap.com +netpool.netbookia.net +netshelter.net +neudesicmediagroup.com +newads.bangbros.com +newbie.com +newnet.qsrch.com +newnudecash.com +newopenx.detik.com +newt1.adultadworld.com +newt1.adultworld.com +newtopsites.com +ng3.ads.warnerbros.com +ngs.impress.co.jp +nitroclicks.com +novem.pl +nuggad.net +numax.nu-1.com +nuseek.com +oas.benchmark.fr +oas.foxnews.com +oas.repubblica.it +oas.roanoke.com +oas.salon.com +oas.toronto.com +oas.uniontrib.com +oas.villagevoice.com +oascentral.businessweek.com +oascentral.chicagobusiness.com +oascentral.fortunecity.com +oascentral.register.com +oewa.at +oewabox.at +offerforge.com +offermatica.com +olivebrandresponse.com +omniture.com +onclasrv.com +onclickads.net +oneandonlynetwork.com +onenetworkdirect.com +onestat.com +onestatfree.com +online-metrix.net +onlinecash.com +onlinecashmethod.com +onlinerewardcenter.com +openad.tf1.fr +openad.travelnow.com +openads.friendfinder.com +openads.org +openx.angelsgroup.org.uk +openx.blindferret.com +opienetwork.com +optimost.com +optmd.com +ordingly.com +ota.cartrawler.com +otto-images.developershed.com +outbrain.com +overture.com +owebmoney.ru +oxado.com +oxcash.com +oxen.hillcountrytexas.com +p.adpdx.com +pagead.l.google.com +pagefair.com +pagerank-ranking.de +pagerank-submitter.de +pagerank-united.de +pagerank4you.com +pageranktop.com +parse.ly.invalid +parsely.com +partage-facile.com +partner-ads.com +partner.pelikan.cz +partner.topcities.com +partnerad.l.google.com +partnercash.de +partners.priceline.com +passion-4.net +pay-ads.com +paycounter.com +paypopup.com +payserve.com +pbnet.ru +pcash.imlive.com +peep-auktion.de +peer39.com +pennyweb.com +pepperjamnetwork.com +percentmobile.com +perfectaudience.com +perfiliate.com +performancerevenue.com +performancerevenues.com +performancing.com +pgmediaserve.com +pgpartner.com +pheedo.com +phoenix-adrunner.mycomputer.com +phpadsnew.new.natuurpark.nl +phpmyvisites.net +picadmedia.com +pillscash.com +pimproll.com +pixel.adsafeprotected.com +pixel.jumptap.com +pixel.redditmedia.com +play4traffic.com +playhaven.com +plista.com +plugrush.com +pointroll.com +pop-under.ru +popads.net +popub.com +popunder.ru +popup.msn.com +popupmoney.com +popupnation.com +popups.infostart.com +popuptraffic.com +porngraph.com +porntrack.com +postrelease.com +potenza.cz +pr-ten.de +praddpro.de +prchecker.info +precisioncounter.com +predictad.com +primaryads.com +primetime.net +privatecash.com +pro-advertising.com +pro.i-doctor.co.kr +proext.com +profero.com +projectwonderful.com +promo.badoink.com +promo.ulust.com +promo1.webcams.nl +promobenef.com +promos.fling.com +promote.pair.com +promotion-campaigns.com +pronetadvertising.com +propellerads.com +proranktracker.com +proton-tm.com +protraffic.com +provexia.com +prsitecheck.com +psstt.com +pub.chez.com +pub.club-internet.fr +pub.hardware.fr +pub.realmedia.fr +pubdirecte.com +publicidad.elmundo.es +pubmatic.com +pubs.lemonde.fr +pulse360.com +q.azcentral.com +qctop.com +qnsr.com +quantcast.com +quantserve.com +quarterserver.de +questaffiliates.net +quigo.com +quinst.com +quisma.com +rad.msn.com +radar.cedexis.com +radarurl.com +radiate.com +rampidads.com +rank-master.com +rank-master.de +rankchamp.de +ranking-charts.de +ranking-hits.de +ranking-id.de +ranking-links.de +ranking-liste.de +ranking-street.de +rankingchart.de +rankingscout.com +rankyou.com +rapidcounter.com +rate.ru +ratings.lycos.com +rb1.design.ru +re-directme.com +reachjunction.com +reactx.com +readserver.net +realcastmedia.com +realclix.com +realmedia-a800.d4p.net +realtechnetwork.com +realtracker.com +reduxmedia.com +reedbusiness.com.invalid +referralware.com +regnow.com +reinvigorate.net +reklam.rfsl.se +reklama.mironet.cz +reklama.reflektor.cz +reklamcsere.hu +reklame.unwired-i.net +reklamer.com.ua +relevanz10.de +relmaxtop.com +remotead.cnet.com +republika.onet.pl +retargeter.com +revenue.net +revenuedirect.com +revsci.net +revstats.com +richmails.com +richmedia.yimg.com +richwebmaster.com +rightstats.com +rlcdn.com +rle.ru +rmads.msn.com +rmedia.boston.com +roar.com +robotreplay.com +roia.biz +rok.com.com +rose.ixbt.com +rotabanner.com +roxr.net +rtbpop.com +rtbpopd.com +ru-traffic.com +ru4.com +rubiconproject.com +s.adroll.com +s2d6.com +sageanalyst.net +sail-horizon.com +samsungacr.com +samsungads.com +sbx.pagesjaunes.fr +scambiobanner.aruba.it +scanscout.com +scopelight.com +scorecardresearch.com +scratch2cash.com +scripte-monster.de +searchfeast.com +searchmarketing.com +searchramp.com +secure.webconnect.net +sedoparking.com +sedotracker.com +seeq.com.invalid +sensismediasmart.com.au +seo4india.com +serv0.com +servedby-buysellads.com +servedbyadbutler.com +servedbyopenx.com +servethis.com +services.hearstmags.com +serving-sys.com +sexaddpro.de +sexadvertentiesite.nl +sexcounter.com +sexinyourcity.com +sexlist.com +sextracker.com +sexystat.com +shareadspace.com +shareasale.com +sharepointads.com +sher.index.hu +shinystat.com +shinystat.it +shoppingads.com +siccash.com +sidebar.angelfire.com +sinoa.com +sitemeter.com +sitestat.com +sixsigmatraffic.com +skimresources.com +skylink.vn +slickaffiliate.com +slopeaota.com +smart4ads.com +smartadserver.com +smowtion.com +snapads.com +snoobi.com +socialspark.com +softclick.com.br +spacash.com +sparkstudios.com +specificmedia.co.uk +specificpop.com +spezialreporte.de +spinbox.techtracker.com +spinbox.versiontracker.com +sponsorads.de +sponsorpro.de +sponsors.thoughtsmedia.com +spot.fitness.com +spotxchange.com +spylog.com +spywarelabs.com +spywarenuker.com +spywords.com +srwww1.com +starffa.com +start.freeze.com +stat.cliche.se +stat.dealtime.com +stat.dyna.ultraweb.hu +stat.pl +stat.webmedia.pl +stat.zenon.net +stat24.com +stat24.meta.ua +statcounter.com +static.fmpub.net +static.itrack.it +staticads.btopenworld.com +statistik-gallup.net +statm.the-adult-company.com +stats.blogger.com +stats.cts-bv.nl +stats.directnic.com +stats.hyperinzerce.cz +stats.mirrorfootball.co.uk +stats.multiup.org +stats.olark.com +stats.self.com +stats.townnews.com +stats.unwired-i.net +stats.wordpress.com +stats.x14.eu +stats2.self.com +stats4all.com +statsie.com +statxpress.com +steelhouse.com +steelhousemedia.com +stickyadstv.com +suavalds.com +subscribe.hearstmags.com +sugoicounter.com +sumo.com +sumome.com +superclix.de +superstats.com +supertop.ru +supertop100.com +suptullog.com +surfmusik-adserver.de +swan-swan-goose.com +swissadsolutions.com +swordfishdc.com +sx.trhnt.com +t.insigit.com +t.pusk.ru +taboola.com +tacoda.net +tagular.com +tailsweep.co.uk +tailsweep.com +tailsweep.se +takru.com +tangerinenet.biz +tapad.com +targad.de +targetingnow.com +targetnet.com +targetpoint.com +tatsumi-sys.jp +tcads.net +teads.tv +techclicks.net +teenrevenue.com +teliad.de +text-link-ads.com +textad.sexsearch.com +textads.biz +textads.opera.com +textlinks.com +tfag.de +theadhost.com +theads.me +thebugs.ws +thecounter.com +therapistla.com +therichkids.com +thrnt.com +thruport.com +tinybar.com +tizers.net +tlvmedia.com +tntclix.co.uk +top-casting-termine.de +top-site-list.com +top.list.ru +top.mail.ru +top.proext.com +top100-images.rambler.ru +top100.mafia.ru +top123.ro +top20.com.invalid +top20free.com +top90.ro +topbarh.box.sk +topbucks.com +topforall.com +topgamesites.net +toplist.cz +toplist.pornhost.com +toplista.mw.hu +toplistcity.com +topmmorpgsites.com.invalid +topping.com.ua +toprebates.com +topsafelist.net +topsearcher.com +topsir.com +topsite.lv +topsites.com.br +topstats.com +totemcash.com +touchclarity.com +touchclarity.natwest.com +tour.brazzers.com +tpnads.com +track.adform.net +track.anchorfree.com +track.gawker.com +trackalyzer.com +tracker.icerocket.com +tracker.marinsm.com +tracking.crunchiemedia.com +tracking.gajmp.com +tracking.internetstores.de +tracking.yourfilehost.com +tracking101.com +trackingsoft.com +trackmysales.com +tradeadexchange.com +tradedoubler.com +traffic-exchange.com +traffic.liveuniversenetwork.com +trafficadept.com +trafficbalancerouting.com +trafficcdn.liveuniversenetwork.com +trafficfactory.biz +trafficholder.com +traffichunt.com +trafficjunky.net +trafficleader.com +trafficsecrets.com +trafficspaces.net +trafficstrategies.com +trafficswarm.com +traffictrader.net +trafficz.com +trafficz.net +traffiq.com +trafic.ro +travis.bosscasinos.com +trekblue.com +trekdata.com +trendcounter.com +trendmd.com +trhunt.com +tribalfusion.com +trix.net +truehits.net +truehits1.gits.net.th +truehits2.gits.net.th +tsms-ad.tsms.com +tubemogul.com +turn.com +tvmtracker.com +twittad.com +tyroo.com +uarating.com +ukbanners.com +ultramercial.com +unanimis.co.uk +untd.com +updated.com +urlcash.net +us.a1.yimg.com +usapromotravel.com +usmsad.tom.com +utarget.co.uk +utils.mediageneral.net +v1.cnzz.com +validclick.com +valuead.com +valueclick.com +valueclickmedia.com +valuecommerce.com +valuesponsor.com +veille-referencement.com +ventivmedia.com +vericlick.com +vertadnet.com +veruta.com +vervewireless.com +vibrantmedia.com +video-stats.video.google.com +videoegg.com +view4cash.de +viewpoint.com +visistat.com +visit.webhosting.yahoo.com +visitbox.de +visual-pagerank.fr +visualrevenue.com +voicefive.com +vpon.com +vrs.cz +vs.tucows.com +vungle.com +warlog.ru +wdads.sx.atl.publicus.com +web-stat.com +web.informer.com +web2.deja.com +webads.co.nz +webads.nl +webangel.ru +webcash.nl +webcounter.cz +webcounter.goweb.de +webgains.com +webmaster-partnerprogramme24.de +webmasterplan.com +webmasterplan.de +weborama.fr +webpower.com +webreseau.com +webseoanalytics.com +websponsors.com +webstat.channel4.com +webstat.com +webstat.net +webstats4u.com +webtrackerplus.com +webtraffic.se +webtraxx.de +webtrendslive.com +werbung.meteoxpress.com +wetrack.it +whaleads.com +whenu.com +whispa.com +whoisonline.net +wholesaletraffic.info +widespace.com +widgetbucks.com +wikia-ads.wikia.com +window.nixnet.cz +wintricksbanner.googlepages.com +witch-counter.de +wlmarketing.com +wmirk.ru +wonderlandads.com +wondoads.de +woopra.com +worldwide-cash.net +wtlive.com +www-banner.chat.ru +www-google-analytics.l.google.com +www.banner-link.com.br +www.dnps.com +www.kaplanindex.com +www.money4exit.de +www.photo-ads.co.uk +www1.gto-media.com +www8.glam.com +wwwpromoter.com +x-traceur.com +x6.yakiuchi.com +xchange.ro +xclicks.net +xertive.com +xg4ken.com +xiti.com +xplusone.com +xponsor.com +xq1.net +xrea.com +xtendmedia.com +xtremetop100.com +xxxcounter.com +xxxmyself.com +y.ibsys.com +yab-adimages.s3.amazonaws.com +yabuka.com +yadro.ru +yesads.com +yesadvertising.com +yieldads.com +yieldlab.net +yieldmanager.com +yieldmanager.net +yieldmo.com +yieldtraffic.com +yoc.mobi +yoggrt.com +z5x.net +zangocash.com +zanox-affiliate.de +zanox.com +zantracker.com +zedo.com +zencudo.co.uk +zenkreka.com +zenzuu.com +zeus.developershed.com +zeusclicks.com +zintext.com +zmedia.com +zv1.november-lax.com \ No newline at end of file diff --git a/app/src/main/assets/js/click_a.js b/app/src/main/assets/js/click_a.js index 81b12fd7..9f4ddc38 100644 --- a/app/src/main/assets/js/click_a.js +++ b/app/src/main/assets/js/click_a.js @@ -22,7 +22,7 @@ if (!window.hasOwnProperty('frost_click_a')) { if (!prevented) { console.log('Click Intercept', url); - Frost.loadUrl(url); + if (typeof Frost !== 'undefined') Frost.loadUrl(url); } e.stopPropagation(); e.preventDefault(); diff --git a/app/src/main/assets/js/click_a.min.js b/app/src/main/assets/js/click_a.min.js index 059c1183..c7870e2f 100644 --- a/app/src/main/assets/js/click_a.min.js +++ b/app/src/main/assets/js/click_a.min.js @@ -8,8 +8,8 @@ var t=e.target||e.srcElement var n=t.getAttribute("href") ;if(n.includes("photoset_token"))return ;prevented||(console.log("Click Intercept",n), -Frost.loadUrl(n)),e.stopPropagation(), -e.preventDefault() +"undefined"!=typeof Frost&&Frost.loadUrl(n)), +e.stopPropagation(),e.preventDefault() } },_frostPreventClick=function(){ console.log("Click prevented"),prevented=!0 diff --git a/app/src/main/assets/js/context_a.js b/app/src/main/assets/js/context_a.js index 395dfca1..c7601764 100644 --- a/app/src/main/assets/js/context_a.js +++ b/app/src/main/assets/js/context_a.js @@ -9,7 +9,7 @@ if (!window.hasOwnProperty('frost_context_a')) { var longClick = false; var _frostAContext = function(e) { - Frost.longClick(true); + if (typeof Frost !== 'undefined') Frost.longClick(true); longClick = true; /* @@ -23,13 +23,19 @@ if (!window.hasOwnProperty('frost_context_a')) { if (element.tagName === 'A' && element.getAttribute('href') !== '#') { var url = element.getAttribute('href'); if (!url) return; - if (url.includes('photoset_token')) return; - var text = element.parentNode.innerText; - // console.log('Context Intercept', element.tagName, element.id, element.className) - console.log('Context Content', url, text); - Frost.contextMenu(url, text); + //check if image item exists + var image = element.parentNode.querySelector('[style*="background-image: url("]'); + if (image) { + var imageUrl = window.getComputedStyle(image, null).backgroundImage.slice(5, -2); + console.log('Context image', imageUrl); + if (typeof Frost !== 'undefined') Frost.loadImage(imageUrl, text); + } else { + if (url.includes('photoset_token')) return; + console.log('Context Content', url, text); + if (typeof Frost !== 'undefined') Frost.contextMenu(url, text); + } e.stopPropagation(); e.preventDefault(); } @@ -39,7 +45,7 @@ if (!window.hasOwnProperty('frost_context_a')) { document.addEventListener('touchend', function _frostEnd(e) { if (longClick) { - Frost.longClick(false); + if (typeof Frost !== 'undefined') Frost.longClick(false); longClick = false; } }, true); diff --git a/app/src/main/assets/js/context_a.min.js b/app/src/main/assets/js/context_a.min.js index 82f89e8b..5c5f033a 100644 --- a/app/src/main/assets/js/context_a.min.js +++ b/app/src/main/assets/js/context_a.min.js @@ -1,20 +1,28 @@ if(!window.hasOwnProperty("frost_context_a")){ console.log("frost_context_a frost_click_a"), window.frost_context_a=!0 -;var longClick=!1,_frostAContext=function(t){ -Frost.longClick(!0),longClick=!0 -;var e=t.target||t.currentTarget||t.srcElement -;if(e&&("A"!==e.tagName&&(e=e.parentNode), -"A"!==e.tagName&&(e=e.parentNode),"A"===e.tagName&&"#"!==e.getAttribute("href"))){ -var o=e.getAttribute("href") -;if(o.includes("photoset_token"))return -;var n=e.parentNode.innerText -;console.log("Context Content",o,n),Frost.contextMenu(o,n), -t.stopPropagation(),t.preventDefault() +;var longClick=!1,_frostAContext=function(e){ +"undefined"!=typeof Frost&&Frost.longClick(!0), +longClick=!0 +;var t=e.target||e.currentTarget||e.srcElement +;if(t&&("A"!==t.tagName&&(t=t.parentNode), +"A"!==t.tagName&&(t=t.parentNode),"A"===t.tagName&&"#"!==t.getAttribute("href"))){ +var o=t.getAttribute("href") +;if(!o)return +;var n=t.parentNode.innerText,r=t.parentNode.querySelector('[style*="background-image: url("]') +;if(r){ +var a=window.getComputedStyle(r,null).backgroundImage.slice(5,-2) +;console.log("Context image",a), +"undefined"!=typeof Frost&&Frost.loadImage(a,n) +}else{ +if(o.includes("photoset_token"))return +;console.log("Context Content",o,n),"undefined"!=typeof Frost&&Frost.contextMenu(o,n) } +e.stopPropagation(),e.preventDefault() } -;document.addEventListener("contextmenu",_frostAContext,!0),document.addEventListener("touchend",function(t){ -longClick&&(Frost.longClick(!1), +} +;document.addEventListener("contextmenu",_frostAContext,!0),document.addEventListener("touchend",function(e){ +longClick&&("undefined"!=typeof Frost&&Frost.longClick(!1), longClick=!1) },!0) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt deleted file mode 100644 index 6cab2a59..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.pitchedapps.frost - -import android.support.constraint.ConstraintLayout -import android.support.constraint.ConstraintSet -import android.support.v7.widget.RecyclerView -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import ca.allanwang.kau.about.AboutActivityBase -import ca.allanwang.kau.about.LibraryIItem -import ca.allanwang.kau.adapters.FastItemThemedAdapter -import ca.allanwang.kau.adapters.ThemableIItem -import ca.allanwang.kau.adapters.ThemableIItemDelegate -import ca.allanwang.kau.utils.* -import com.mikepenz.aboutlibraries.Libs -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.aboutlibraries.entity.License -import com.mikepenz.community_material_typeface_library.CommunityMaterial -import com.mikepenz.fastadapter.IItem -import com.mikepenz.fastadapter.items.AbstractItem -import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.mikepenz.iconics.typeface.IIcon -import com.pitchedapps.frost.utils.Prefs - - -/** - * Created by Allan Wang on 2017-06-26. - */ -class AboutActivity : AboutActivityBase(null, { - textColor = Prefs.textColor - accentColor = Prefs.accentColor - backgroundColor = Prefs.bgColor.withMinAlpha(200) - cutoutForeground = if (0xff3b5998.toInt().isColorVisibleOn(Prefs.bgColor)) 0xff3b5998.toInt() else Prefs.accentColor - cutoutDrawableRes = R.drawable.frost_f_256 -}) { - - override fun getLibraries(libs: Libs): List { - val include = arrayOf( - "AboutLibraries", - "AndroidIconics", - "dbflow", - "fastadapter", - "glide", - "Jsoup", - "kau", - "kotterknife", - "materialdialogs", - "materialdrawer" - ) - - /* - * These are great libraries, but either aren't used directly or are too common to be listed - * Give more emphasis on the unique libs! - */ - val exclude = arrayOf( - "GoogleMaterialDesignIcons", - "intellijannotations", - "MaterialDesignIconicIcons", - "MaterialDesignIcons", - "materialize", - "appcompat_v7", - "design", - "recyclerview_v7", - "support_v4" - ) - val l = libs.prepareLibraries(this, include, null, false, true) -// l.forEach { KL.d("Lib ${it.definedName}") } - return l - } - - override fun postInflateMainPage(adapter: FastItemThemedAdapter>) { - /** - * Frost may not be a library but we're conveying the same info - */ - val frost = Library().apply { - libraryName = string(R.string.app_name) - author = "Pitched Apps" - libraryWebsite = "https://github.com/AllanWang/Frost-for-Facebook" - isOpenSource = true - libraryDescription = string(R.string.frost_description) - libraryVersion = BuildConfig.VERSION_NAME - license = License().apply { - licenseName = "GNU GPL v3" - licenseWebsite = "https://www.gnu.org/licenses/gpl-3.0.en.html" - } - } - adapter.add(LibraryIItem(frost)).add(AboutLinks()) - - } - - class AboutLinks : AbstractItem(), ThemableIItem by ThemableIItemDelegate() { - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - - override fun getType(): Int = R.id.item_about_links - - override fun getLayoutRes(): Int = R.layout.item_about_links - - override fun bindView(holder: ViewHolder, payloads: MutableList?) { - super.bindView(holder, payloads) - with(holder) { - bindIconColor(*images.toTypedArray()) - bindBackgroundColor(container) - } - } - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - - val container: ConstraintLayout by bindView(R.id.about_icons_container) - val images: List - - /** - * There are a lot of constraints to be added to each item just to have them chained properly - * My as well do it programmatically - * Initializing the viewholder will setup the icons, scale type and background of all icons, - * link their click listeners and chain them together via a horizontal spread - */ - init { - val c = itemView.context - val size = c.dimenPixelSize(R.dimen.kau_avatar_bounds) - images = arrayOf Unit>>( - GoogleMaterial.Icon.gmd_star to { c.startPlayStoreLink(R.string.play_store_package_id) }, - CommunityMaterial.Icon.cmd_reddit to { c.startLink("https://www.reddit.com/r/FrostForFacebook/") }, - CommunityMaterial.Icon.cmd_github_circle to { c.startLink("https://github.com/AllanWang/Frost-for-Facebook") } - ).mapIndexed { i, (icon, onClick) -> - ImageView(c).apply { - layoutParams = ViewGroup.LayoutParams(size, size) - id = 109389 + i - setImageDrawable(icon.toDrawable(context, 32)) - scaleType = ImageView.ScaleType.CENTER - background = context.resolveDrawable(android.R.attr.selectableItemBackgroundBorderless) - setOnClickListener({ onClick() }) - container.addView(this) - } - } - val set = ConstraintSet() - set.clone(container) - set.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, - images.map { it.id }.toIntArray(), null, ConstraintSet.CHAIN_SPREAD_INSIDE) - set.applyTo(container) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/BaseActivity.kt deleted file mode 100644 index c2551125..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/BaseActivity.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.pitchedapps.frost - -import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import com.pitchedapps.frost.utils.Prefs -import com.pitchedapps.frost.utils.materialDialogThemed -import com.pitchedapps.frost.utils.setFrostTheme - -/** - * Created by Allan Wang on 2017-06-12. - */ -open class BaseActivity : AppCompatActivity() { - override fun onBackPressed() { - if (isTaskRoot && Prefs.exitConfirmation) { - materialDialogThemed { - title(R.string.kau_exit) - content(R.string.kau_exit_confirmation) - positiveText(R.string.kau_yes) - negativeText(R.string.kau_no) - onPositive { _, _ -> super.onBackPressed() } - checkBoxPromptRes(R.string.kau_do_not_show_again, false, { _, b -> Prefs.exitConfirmation = !b }) - } - } else super.onBackPressed() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setFrostTheme() - } - -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 4b62d244..3f5bdeda 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -10,6 +10,7 @@ import com.crashlytics.android.Crashlytics import com.crashlytics.android.answers.Answers import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader +import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.CrashReportingTree import com.pitchedapps.frost.utils.GlideApp diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostWebActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostWebActivity.kt deleted file mode 100644 index 3e337813..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostWebActivity.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.pitchedapps.frost - -import android.os.Bundle -import com.pitchedapps.frost.utils.Prefs - - -/** - * Created by Allan Wang on 2017-06-19. - * - * Replica of [WebOverlayActivity] with a different base url - * Didn't use activity-alias because it causes issues when only one activity has the singleInstance mode - */ -class FrostWebActivity : WebOverlayActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - Prefs.prevId = Prefs.userId - super.onCreate(savedInstanceState) - } - -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt deleted file mode 100644 index a27a1ee2..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.pitchedapps.frost - -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.os.Handler -import android.support.v4.widget.SwipeRefreshLayout -import android.support.v7.widget.AppCompatTextView -import android.support.v7.widget.Toolbar -import android.widget.ImageView -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.fadeIn -import ca.allanwang.kau.utils.fadeOut -import com.bumptech.glide.Glide -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target -import com.crashlytics.android.answers.LoginEvent -import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.dbflow.fetchUsername -import com.pitchedapps.frost.dbflow.loadFbCookiesAsync -import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL -import com.pitchedapps.frost.utils.* -import com.pitchedapps.frost.web.LoginWebView -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.functions.BiFunction -import io.reactivex.internal.operators.single.SingleToObservable -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.SingleSubject - - -/** - * Created by Allan Wang on 2017-06-01. - */ -class LoginActivity : BaseActivity() { - - val toolbar: Toolbar by bindView(R.id.toolbar) - val web: LoginWebView by bindView(R.id.login_webview) - val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) - val textview: AppCompatTextView by bindView(R.id.textview) - val profile: ImageView by bindView(R.id.profile) - - val loginObservable = SingleSubject.create() - val progressObservable = BehaviorSubject.create()!! - val profileObservable = SingleSubject.create() - val usernameObservable = SingleSubject.create() - - // Helper to set and enable swipeRefresh - var refresh: Boolean - get() = swipeRefresh.isRefreshing - set(value) { - if (value) swipeRefresh.isEnabled = true - swipeRefresh.isRefreshing = value - if (!value) swipeRefresh.isEnabled = false - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_login) - setSupportActionBar(toolbar) - setTitle(R.string.kau_login) - setFrostColors(toolbar) - web.loginObservable = loginObservable - web.progressObservable = progressObservable - loginObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { - cookie -> - web.fadeOut(onFinish = { - profile.fadeIn() - loadInfo(cookie) - }) - } - progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { refresh = it != 100 } - web.loadLogin() - } - - fun loadInfo(cookie: CookieModel) { - refresh = true - Observable.zip(SingleToObservable(profileObservable), SingleToObservable(usernameObservable), - BiFunction> { foundImage, name -> Pair(foundImage, name) }) - .observeOn(AndroidSchedulers.mainThread()).subscribe { - (foundImage, name) -> - refresh = false - if (!foundImage) { - L.eThrow("Could not get profile photo; Invalid userId?") - L.i("-\t$cookie") - } - textview.text = String.format(getString(R.string.welcome), name) - textview.fadeIn() - frostAnswers { logLogin(LoginEvent().putMethod("frost_browser").putSuccess(true)) } - /* - * The user may have logged into an account that is already in the database - * We will let the db handle duplicates and load it now after the new account has been saved - */ - loadFbCookiesAsync { - cookies -> - Handler().postDelayed({ - launchNewTask(MainActivity::class.java, ArrayList(cookies), clearStack = true) - }, 1000) - } - } - loadProfile(cookie.id) - loadUsername(cookie) - } - - - fun loadProfile(id: Long) { - Glide.with(this@LoginActivity).load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener { - override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { - profileObservable.onSuccess(true) - return false - } - - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { - if (e != null) L.e(e, "Profile loading exception") - profileObservable.onSuccess(false) - return false - } - }).into(profile) - } - - fun loadUsername(cookie: CookieModel) { - cookie.fetchUsername { usernameObservable.onSuccess(it) } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt deleted file mode 100644 index f9a597db..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt +++ /dev/null @@ -1,462 +0,0 @@ -package com.pitchedapps.frost - -import android.app.AlarmManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.graphics.drawable.ColorDrawable -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.support.annotation.StringRes -import android.support.design.widget.* -import android.support.v4.app.ActivityOptionsCompat -import android.support.v4.app.Fragment -import android.support.v4.app.FragmentManager -import android.support.v4.app.FragmentPagerAdapter -import android.support.v4.view.ViewPager -import android.support.v7.widget.Toolbar -import android.view.Menu -import android.view.MenuItem -import android.webkit.ValueCallback -import android.webkit.WebChromeClient -import ca.allanwang.kau.changelog.showChangelog -import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult -import ca.allanwang.kau.searchview.SearchItem -import ca.allanwang.kau.searchview.SearchView -import ca.allanwang.kau.searchview.bindSearchView -import ca.allanwang.kau.utils.* -import co.zsmb.materialdrawerkt.builders.Builder -import co.zsmb.materialdrawerkt.builders.accountHeader -import co.zsmb.materialdrawerkt.builders.drawer -import co.zsmb.materialdrawerkt.draweritems.badgeable.primaryItem -import co.zsmb.materialdrawerkt.draweritems.badgeable.secondaryItem -import co.zsmb.materialdrawerkt.draweritems.divider -import co.zsmb.materialdrawerkt.draweritems.profile.profile -import co.zsmb.materialdrawerkt.draweritems.profile.profileSetting -import com.crashlytics.android.answers.ContentViewEvent -import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.materialdrawer.AccountHeader -import com.mikepenz.materialdrawer.Drawer -import com.pitchedapps.frost.contracts.ActivityWebContract -import com.pitchedapps.frost.contracts.FileChooserContract -import com.pitchedapps.frost.contracts.FileChooserDelegate -import com.pitchedapps.frost.dbflow.loadFbCookie -import com.pitchedapps.frost.dbflow.loadFbTabs -import com.pitchedapps.frost.facebook.FbCookie -import com.pitchedapps.frost.facebook.FbCookie.switchUser -import com.pitchedapps.frost.facebook.FbTab -import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL -import com.pitchedapps.frost.fragments.WebFragment -import com.pitchedapps.frost.utils.* -import com.pitchedapps.frost.utils.iab.validatePro -import com.pitchedapps.frost.views.BadgedIcon -import com.pitchedapps.frost.views.FrostViewPager -import com.pitchedapps.frost.web.FrostWebViewSearch -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import io.reactivex.subjects.PublishSubject -import org.jsoup.Jsoup -import java.util.concurrent.TimeUnit - -class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, - ActivityWebContract, FileChooserContract by FileChooserDelegate() { - - lateinit var adapter: SectionsPagerAdapter - val toolbar: Toolbar by bindView(R.id.toolbar) - val viewPager: FrostViewPager by bindView(R.id.container) - val fab: FloatingActionButton by bindView(R.id.fab) - val tabs: TabLayout by bindView(R.id.tabs) - val appBar: AppBarLayout by bindView(R.id.appbar) - val coordinator: CoordinatorLayout by bindView(R.id.main_content) - lateinit var drawer: Drawer - lateinit var drawerHeader: AccountHeader - var webFragmentObservable = PublishSubject.create()!! - var lastPosition = -1 - val headerBadgeObservable = PublishSubject.create() - var hiddenSearchView: FrostWebViewSearch? = null - var firstLoadFinished = false - set(value) { - L.d("First fragment load has finished") - field = value - if (value && hiddenSearchView == null) { - hiddenSearchView = FrostWebViewSearch(this, this) - } - } - var searchView: SearchView? = null - override val isSearchOpened: Boolean - get() = searchView?.isOpen ?: false - - companion object { - const val ACTIVITY_SETTINGS = 97 - /* - * Possible responses from the SettingsActivity - * after the configurations have changed - */ - const val REQUEST_RESTART = 90909 - const val REQUEST_REFRESH = 80808 - const val REQUEST_WEB_ZOOM = 50505 - const val REQUEST_NAV = 10101 - const val REQUEST_SEARCH = 70707 - const val REQUEST_RESTART_APPLICATION = 60606 - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (BuildConfig.VERSION_CODE > Prefs.versionCode) { - Prefs.versionCode = BuildConfig.VERSION_CODE - if (!BuildConfig.DEBUG) showChangelog(R.xml.changelog, Prefs.textColor) { theme() } - } - setContentView(R.layout.activity_main) - setSupportActionBar(toolbar) - adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs()) - viewPager.adapter = adapter - viewPager.offscreenPageLimit = 5 - viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - if (lastPosition == position) return - if (lastPosition != -1) webFragmentObservable.onNext(-(lastPosition + 1)) - webFragmentObservable.onNext(position) - lastPosition = position - } - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - super.onPageScrolled(position, positionOffset, positionOffsetPixels) - val delta: Float by lazy { positionOffset * (255 - 128).toFloat() } - tabsForEachView { - tabPosition, view -> - view.setAllAlpha(when (tabPosition) { - position -> 255.0f - delta - position + 1 -> 128.0f + delta - else -> 128f - }) - } - } - }) - viewPager.post { webFragmentObservable.onNext(0); lastPosition = 0 } //trigger hook so title is set - setupDrawer(savedInstanceState) - setupTabs() - fab.setOnClickListener { view -> - Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) - .setAction("Action", null).show() - } - setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager)) - validatePro() - } - - fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) { - (0 until tabs.tabCount).asSequence().forEach { - i -> - action(i, tabs.getTabAt(i)!!.customView as BadgedIcon) - } - } - - fun setupTabs() { - viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) - tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) { - override fun onTabReselected(tab: TabLayout.Tab) { - super.onTabReselected(tab) - currentFragment.web.scrollOrRefresh() - } - - override fun onTabSelected(tab: TabLayout.Tab) { - super.onTabSelected(tab) - (tab.customView as BadgedIcon).badgeText = null - } - }) - headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread()) - .map { Jsoup.parse(it) } - .filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist - .map { - val feed = it.select("[data-sigil*=feed] [data-sigil=count]") - val requests = it.select("[data-sigil*=requests] [data-sigil=count]") - val messages = it.select("[data-sigil*=messages] [data-sigil=count]") - val notifications = it.select("[data-sigil*=notifications] [data-sigil=count]") - return@map arrayOf(feed, requests, messages, notifications).map { it?.getOrNull(0)?.ownText() } - } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - (feed, requests, messages, notifications) -> - tabsForEachView { - _, view -> - when (view.iicon) { - FbTab.FEED.icon -> view.badgeText = feed - FbTab.FRIENDS.icon -> view.badgeText = requests - FbTab.MESSAGES.icon -> view.badgeText = messages - FbTab.NOTIFICATIONS.icon -> view.badgeText = notifications - } - } - } - adapter.pages.forEach { - tabs.addTab(tabs.newTab() - .setCustomView(BadgedIcon(this).apply { - iicon = it.icon - })) - } - } - - fun setupDrawer(savedInstanceState: Bundle?) { - val navBg = Prefs.bgColor.withMinAlpha(200).toLong() - val navHeader = Prefs.headerColor.withMinAlpha(200) - drawer = drawer { - toolbar = this@MainActivity.toolbar - savedInstance = savedInstanceState - translucentStatusBar = false - sliderBackgroundColor = navBg - drawerHeader = accountHeader { - textColor = Prefs.iconColor.toLong() - backgroundDrawable = ColorDrawable(navHeader) - selectionSecondLineShown = false - paddingBelow = false - cookies().forEach { (id, name) -> - profile(name = name ?: "") { - iconUrl = PROFILE_PICTURE_URL(id) - textColor = Prefs.textColor.toLong() - selectedTextColor = Prefs.textColor.toLong() - selectedColor = 0x00000001.toLong() - identifier = id - } - } - profileSetting(nameRes = R.string.kau_logout) { - iicon = GoogleMaterial.Icon.gmd_exit_to_app - iconColor = Prefs.textColor.toLong() - textColor = Prefs.textColor.toLong() - identifier = -2L - } - profileSetting(nameRes = R.string.kau_add_account) { - iconDrawable = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_add).actionBar().paddingDp(5).color(Prefs.textColor) - textColor = Prefs.textColor.toLong() - identifier = -3L - } - profileSetting(nameRes = R.string.kau_manage_account) { - iicon = GoogleMaterial.Icon.gmd_settings - iconColor = Prefs.textColor.toLong() - textColor = Prefs.textColor.toLong() - identifier = -4L - } - onProfileChanged { _, profile, current -> - if (current) launchWebOverlay(FbTab.PROFILE.url) - else when (profile.identifier) { - -2L -> { - val currentCookie = loadFbCookie(Prefs.userId) - if (currentCookie == null) { - toast(R.string.account_not_found) - FbCookie.reset { launchLogin(cookies(), true) } - } else { - materialDialogThemed { - title(R.string.kau_logout) - content(String.format(string(R.string.kau_logout_confirm_as_x), currentCookie.name ?: Prefs.userId.toString())) - positiveText(R.string.kau_yes) - negativeText(R.string.kau_no) - onPositive { _, _ -> - FbCookie.logout(Prefs.userId) { - val allCookies = cookies() - allCookies.remove(currentCookie) - launchLogin(allCookies, true) - } - } - } - } - } - -3L -> launchNewTask(LoginActivity::class.java, clearStack = false) - -4L -> launchNewTask(SelectorActivity::class.java, cookies(), false) - else -> { - switchUser(profile.identifier, { refreshAll() }) - tabsForEachView { _, view -> view.badgeText = null } - } - } - false - } - } - drawerHeader.setActiveProfile(Prefs.userId) - primaryFrostItem(FbTab.FEED_MOST_RECENT) - primaryFrostItem(FbTab.FEED_TOP_STORIES) - primaryFrostItem(FbTab.ACTIVITY_LOG) - divider() - primaryFrostItem(FbTab.PHOTOS) - primaryFrostItem(FbTab.GROUPS) - primaryFrostItem(FbTab.PAGES) - divider() - primaryFrostItem(FbTab.EVENTS) - primaryFrostItem(FbTab.BIRTHDAYS) - primaryFrostItem(FbTab.ON_THIS_DAY) - divider() - primaryFrostItem(FbTab.NOTES) - primaryFrostItem(FbTab.SAVED) - } - } - - fun Builder.primaryFrostItem(item: FbTab) = this.primaryItem(item.titleId) { - iicon = item.icon - iconColor = Prefs.textColor.toLong() - textColor = Prefs.textColor.toLong() - selectedIconColor = Prefs.textColor.toLong() - selectedTextColor = Prefs.textColor.toLong() - selectedColor = 0x00000001.toLong() - identifier = item.titleId.toLong() - onClick { _ -> - frostAnswers { - logContentView(ContentViewEvent() - .putContentName(item.name) - .putContentType("drawer_item")) - } - launchWebOverlay(item.url) - false - } - } - - fun Builder.secondaryFrostItem(@StringRes title: Int, onClick: () -> Unit) = this.secondaryItem(title) { - textColor = Prefs.textColor.toLong() - selectedIconColor = Prefs.textColor.toLong() - selectedTextColor = Prefs.textColor.toLong() - selectedColor = 0x00000001.toLong() - identifier = title.toLong() - onClick { _ -> onClick(); false } - } - - - /** - * Something happened where the normal search function won't work - * Fallback to overlay style - */ - override fun searchOverlayDispose() { - hiddenSearchView?.dispose() - hiddenSearchView = null - searchView = null - //todo remove true searchview and add contract - } - - override fun emitSearchResponse(items: List) { - searchView?.results = items - } - - fun refreshAll() { - webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_main, menu) - toolbar.tint(Prefs.iconColor) - setMenuIcons(menu, Prefs.iconColor, - R.id.action_settings to GoogleMaterial.Icon.gmd_settings, - R.id.action_search to GoogleMaterial.Icon.gmd_search) - if (Prefs.searchBar) { - if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = FrostWebViewSearch(this, this) - if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { - textObserver = { - observable, _ -> - observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) } - } - foregroundColor = Prefs.textColor - backgroundColor = Prefs.bgColor.withMinAlpha(200) - openListener = { hiddenSearchView?.pauseLoad = false } - closeListener = { hiddenSearchView?.pauseLoad = true } - onItemClick = { _, key, _, _ -> launchWebOverlay(key) } - } - } else { - searchOverlayDispose() - menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbTab.SEARCH.url); true } - } - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_settings -> { - val intent = Intent(this, SettingsActivity::class.java) - val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle() - startActivityForResult(intent, ACTIVITY_SETTINGS, bundle) - } - else -> return super.onOptionsItemSelected(item) - } - return true - } - - override fun openFileChooser(filePathCallback: ValueCallback>, fileChooserParams: WebChromeClient.FileChooserParams) { - openFileChooser(this, filePathCallback, fileChooserParams) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (onActivityResultWeb(requestCode, resultCode, data)) return - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == ACTIVITY_SETTINGS) { - when (resultCode) { - REQUEST_RESTART -> restart() - REQUEST_REFRESH -> webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH) - REQUEST_NAV -> frostNavigationBar() - REQUEST_WEB_ZOOM -> webFragmentObservable.onNext(WebFragment.REQUEST_TEXT_ZOOM) - REQUEST_SEARCH -> invalidateOptionsMenu() - REQUEST_RESTART_APPLICATION -> { //completely restart application - L.d("Restart Application Requested") - val intent = packageManager.getLaunchIntentForPackage(packageName) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) - val pending = PendingIntent.getActivity(this, 666, intent, PendingIntent.FLAG_CANCEL_CURRENT) - val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - alarm.setExactAndAllowWhileIdle(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) - else - alarm.setExact(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) - finish() - System.exit(0) - } - } - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - kauOnRequestPermissionsResult(permissions, grantResults) - } - - override fun onResume() { - super.onResume() - FbCookie.switchBackUser { } - } - - override fun onStart() { - //validate some pro features - if (!Prefs.pro) { - if (Prefs.theme == Theme.CUSTOM.ordinal) Prefs.theme = Theme.DEFAULT.ordinal - } - super.onStart() - } - - override fun onBackPressed() { - if (searchView?.onBackPressed() ?: false) return - if (currentFragment.onBackPressed()) return - super.onBackPressed() - } - - val currentFragment - get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment - - inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List) : FragmentPagerAdapter(fm) { - - override fun getItem(position: Int): Fragment { - val fragment = WebFragment(pages[position], position) - //If first load hasn't occurred, add a listener - if (!firstLoadFinished) { - var disposable: Disposable? = null - fragment.post { - disposable = it.web.refreshObservable.subscribe { - if (!it) { - //Ensure first load finisher only happens once - if (!firstLoadFinished) firstLoadFinished = true - disposable?.dispose() - disposable = null - } - } - } - } - return fragment - } - - override fun getCount() = pages.size - - override fun getPageTitle(position: Int): CharSequence = getString(pages[position].titleId) - } - -} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/SelectorActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/SelectorActivity.kt deleted file mode 100644 index 103ae227..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/SelectorActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.pitchedapps.frost - -import android.os.Bundle -import android.support.constraint.ConstraintLayout -import android.support.v7.widget.AppCompatTextView -import android.support.v7.widget.GridLayoutManager -import android.support.v7.widget.RecyclerView -import android.view.View -import ca.allanwang.kau.utils.bindView -import com.mikepenz.fastadapter.FastAdapter -import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter -import com.mikepenz.fastadapter.listeners.ClickEventHook -import com.pitchedapps.frost.facebook.FbCookie -import com.pitchedapps.frost.utils.cookies -import com.pitchedapps.frost.utils.launchNewTask -import com.pitchedapps.frost.utils.setFrostColors -import com.pitchedapps.frost.views.AccountItem - -/** - * Created by Allan Wang on 2017-06-04. - */ -class SelectorActivity : BaseActivity() { - - val recycler: RecyclerView by bindView(R.id.selector_recycler) - val adapter = FastItemAdapter() - val text: AppCompatTextView by bindView(R.id.text_select_account) - val container: ConstraintLayout by bindView(R.id.container) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_selector) - recycler.layoutManager = GridLayoutManager(this, 2) - recycler.adapter = adapter - adapter.add(cookies().map { AccountItem(it) }) - adapter.add(AccountItem(null)) // add account - adapter.withEventHook(object : ClickEventHook() { - override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? AccountItem.ViewHolder)?.v - - override fun onClick(v: View, position: Int, fastAdapter: FastAdapter, item: AccountItem) { - if (item.cookie == null) this@SelectorActivity.launchNewTask(LoginActivity::class.java) - else FbCookie.switchUser(item.cookie, { launchNewTask(MainActivity::class.java, cookies()) }) - } - }) - setFrostColors(texts = arrayOf(text), backgrounds = arrayOf(container)) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt deleted file mode 100644 index e0be330d..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.pitchedapps.frost - -import android.content.Intent -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import ca.allanwang.kau.changelog.showChangelog -import ca.allanwang.kau.kpref.activity.CoreAttributeContract -import ca.allanwang.kau.kpref.activity.KPrefActivity -import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder -import ca.allanwang.kau.kpref.activity.items.KPrefItemBase -import ca.allanwang.kau.ui.views.RippleCanvas -import ca.allanwang.kau.utils.* -import com.mikepenz.community_material_typeface_library.CommunityMaterial -import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.pitchedapps.frost.settings.* -import com.pitchedapps.frost.utils.* -import com.pitchedapps.frost.utils.iab.* - - -/** - * Created by Allan Wang on 2017-06-06. - */ -class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListener { - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (!IAB.handleActivityResult(requestCode, resultCode, data)) { - super.onActivityResult(requestCode, resultCode, data) - adapter.notifyDataSetChanged() - } - } - - - override fun receivedBroadcast() { - L.d("IAB broadcast") - adapter.notifyDataSetChanged() - } - - override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = { - textColor = { Prefs.textColor } - accentColor = { Prefs.accentColor } - } - - override fun onCreateKPrefs(savedInstanceState: android.os.Bundle?): KPrefAdapterBuilder.() -> Unit = { - subItems(R.string.appearance, getAppearancePrefs()) { - descRes = R.string.appearance_desc - iicon = GoogleMaterial.Icon.gmd_palette - } - - subItems(R.string.behaviour, getBehaviourPrefs()) { - descRes = R.string.behaviour_desc - iicon = GoogleMaterial.Icon.gmd_trending_up - } - - subItems(R.string.newsfeed, getFeedPrefs()) { - descRes = R.string.newsfeed_desc - iicon = CommunityMaterial.Icon.cmd_newspaper - } - - subItems(R.string.notifications, getNotificationPrefs()) { - descRes = R.string.notifications_desc - iicon = GoogleMaterial.Icon.gmd_notifications - } - - subItems(R.string.experimental, getExperimentalPrefs()) { - descRes = R.string.experimental_desc - iicon = CommunityMaterial.Icon.cmd_flask_outline - } - - plainText(R.string.restore_purchases) { - descRes = R.string.restore_purchases_desc - iicon = GoogleMaterial.Icon.gmd_refresh - onClick = { _, _, _ -> this@SettingsActivity.restorePurchases(); true } - } - - plainText(R.string.about_frost) { - iicon = GoogleMaterial.Icon.gmd_info - onClick = { _, _, _ -> startActivity(AboutActivity::class.java, transition = true); true } - } - - if (BuildConfig.DEBUG) { - checkbox(R.string.custom_pro, { Prefs.debugPro }, { Prefs.debugPro = it }) - } - } - - fun KPrefItemBase.BaseContract<*>.dependsOnPro() { - onDisabledClick = { _, _, _ -> openPlayProPurchase(0); true } - enabler = { IS_FROST_PRO } - } - - fun shouldRestartMain() { - setResult(MainActivity.REQUEST_RESTART) - } - - override fun onCreate(savedInstanceState: Bundle?) { - setFrostTheme(true) - super.onCreate(savedInstanceState) - animate = Prefs.animate - themeExterior(false) - } - - fun themeExterior(animate: Boolean = true) { - if (animate) bgCanvas.fade(Prefs.bgColor) - else bgCanvas.set(Prefs.bgColor) - if (animate) toolbarCanvas.ripple(Prefs.headerColor, RippleCanvas.MIDDLE, RippleCanvas.END) - else toolbarCanvas.set(Prefs.headerColor) - frostNavigationBar() - } - - override fun onBackPressed() { - if (!super.backPress()) - finishSlideOut() - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_settings, menu) - toolbar.tint(Prefs.iconColor) - toolbarTitle.textColor = Prefs.iconColor - toolbarTitle.invalidate() - setMenuIcons(menu, Prefs.iconColor, - R.id.action_email to GoogleMaterial.Icon.gmd_email, - R.id.action_changelog to GoogleMaterial.Icon.gmd_info) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_email -> materialDialogThemed { - title(R.string.subject) - items(Support.values().map { string(it.title) }) - itemsCallback { _, _, which, _ -> Support.values()[which].sendEmail(this@SettingsActivity) } - } - R.id.action_changelog -> showChangelog(R.xml.changelog, Prefs.textColor) { theme() } - else -> return super.onOptionsItemSelected(item) - } - return true - } - - override fun onDestroy() { - IAB.dispose() - super.onDestroy() - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt index 122c571e..12cb955d 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/StartActivity.kt @@ -2,6 +2,9 @@ package com.pitchedapps.frost import android.os.Bundle import android.support.v7.app.AppCompatActivity +import com.pitchedapps.frost.activities.LoginActivity +import com.pitchedapps.frost.activities.MainActivity +import com.pitchedapps.frost.activities.SelectorActivity import com.pitchedapps.frost.dbflow.loadFbCookiesAsync import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.utils.L diff --git a/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt deleted file mode 100644 index 6c1fb5bd..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.pitchedapps.frost - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.support.design.widget.CoordinatorLayout -import android.support.design.widget.Snackbar -import android.support.v7.app.AppCompatActivity -import android.support.v7.widget.Toolbar -import android.view.Menu -import android.view.MenuItem -import android.webkit.ValueCallback -import android.webkit.WebChromeClient -import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult -import ca.allanwang.kau.swipe.kauSwipeOnCreate -import ca.allanwang.kau.swipe.kauSwipeOnDestroy -import ca.allanwang.kau.swipe.kauSwipeOnPostCreate -import ca.allanwang.kau.utils.* -import com.mikepenz.community_material_typeface_library.CommunityMaterial -import com.mikepenz.google_material_typeface_library.GoogleMaterial -import com.pitchedapps.frost.contracts.ActivityWebContract -import com.pitchedapps.frost.contracts.FileChooserContract -import com.pitchedapps.frost.contracts.FileChooserDelegate -import com.pitchedapps.frost.facebook.FbCookie -import com.pitchedapps.frost.facebook.formattedFbUrl -import com.pitchedapps.frost.utils.* -import com.pitchedapps.frost.web.FrostWebView - - -/** - * Created by Allan Wang on 2017-06-01. - */ -open class WebOverlayActivity : AppCompatActivity(), - ActivityWebContract, FileChooserContract by FileChooserDelegate() { - - val toolbar: Toolbar by bindView(R.id.overlay_toolbar) - val frostWeb: FrostWebView by bindView(R.id.overlay_frost_webview) - val coordinator: CoordinatorLayout by bindView(R.id.overlay_main_content) - - val urlTest: String? - get() = intent.extras?.getString(ARG_URL) ?: intent.dataString - - open val url: String - get() = (intent.extras?.getString(ARG_URL) ?: intent.dataString).formattedFbUrl - - val userId: Long - get() = intent.extras?.getLong(ARG_USER_ID, Prefs.userId) ?: Prefs.userId - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (urlTest == null) { - L.eThrow("Empty link on web overlay") - toast(R.string.null_url_overlay) - finish() - return - } - setContentView(R.layout.activity_web_overlay) - setSupportActionBar(toolbar) - supportActionBar?.setDisplayShowHomeEnabled(true) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - toolbar.navigationIcon = GoogleMaterial.Icon.gmd_close.toDrawable(this, 16, Prefs.iconColor) - toolbar.setNavigationOnClickListener { finishSlideOut() } - kauSwipeOnCreate { - if (!Prefs.overlayFullScreenSwipe) edgeSize = 20.dpToPx - transitionSystemBars = false - } - setFrostColors(toolbar, themeWindow = false) - coordinator.setBackgroundColor(Prefs.bgColor.withAlpha(255)) - - frostWeb.setupWebview(url) - frostWeb.web.addTitleListener({ toolbar.title = it }) - if (userId != Prefs.userId) FbCookie.switchUser(userId) { frostWeb.web.loadBaseUrl() } - else frostWeb.web.loadBaseUrl() - if (Showcase.firstWebOverlay) { - coordinator.frostSnackbar(R.string.web_overlay_swipe_hint) { - duration = Snackbar.LENGTH_INDEFINITE - setAction(R.string.kau_got_it) { _ -> this.dismiss() } - } - } - } - - /** - * Manage url loadings - * This is usually only called when multiple listeners are added and inject the same url - * We will avoid reloading if the url is the same - */ - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - val newUrl = (intent.extras?.getString(ARG_URL) ?: intent.dataString ?: return).formattedFbUrl - L.d("New intent") - if (url != newUrl) { - this.intent = intent - frostWeb.web.baseUrl = newUrl - frostWeb.web.loadBaseUrl() - } - } - - /** - * Our theme for the overlay should be fully opaque - */ - fun theme() { - val opaqueAccent = Prefs.headerColor.withAlpha(255) - statusBarColor = opaqueAccent.darken() - navigationBarColor = opaqueAccent - toolbar.setBackgroundColor(opaqueAccent) - toolbar.setTitleTextColor(Prefs.iconColor) - coordinator.setBackgroundColor(Prefs.bgColor.withAlpha(255)) - toolbar.overflowIcon?.setTint(Prefs.iconColor) - } - - override fun onPostCreate(savedInstanceState: Bundle?) { - super.onPostCreate(savedInstanceState) - kauSwipeOnPostCreate() - } - - override fun onDestroy() { - super.onDestroy() - kauSwipeOnDestroy() - } - - override fun onBackPressed() { - if (!frostWeb.onBackPressed()) { - finishSlideOut() - } - } - - override fun openFileChooser(filePathCallback: ValueCallback>, fileChooserParams: WebChromeClient.FileChooserParams) { - openFileChooser(this, filePathCallback, fileChooserParams) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (onActivityResultWeb(requestCode, resultCode, data)) return - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - kauOnRequestPermissionsResult(permissions, grantResults) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_web, menu) - toolbar.tint(Prefs.iconColor) - setMenuIcons(menu, Prefs.iconColor, - R.id.action_share to CommunityMaterial.Icon.cmd_share, - R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_copy_link -> copyToClipboard(frostWeb.web.url) - R.id.action_share -> shareText(frostWeb.web.url) - else -> return super.onOptionsItemSelected(item) - } - return true - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt new file mode 100644 index 00000000..63ad8bae --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt @@ -0,0 +1,145 @@ +package com.pitchedapps.frost.activities + +import android.support.constraint.ConstraintLayout +import android.support.constraint.ConstraintSet +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import ca.allanwang.kau.about.AboutActivityBase +import ca.allanwang.kau.about.LibraryIItem +import ca.allanwang.kau.adapters.FastItemThemedAdapter +import ca.allanwang.kau.adapters.ThemableIItem +import ca.allanwang.kau.adapters.ThemableIItemDelegate +import ca.allanwang.kau.utils.* +import com.mikepenz.aboutlibraries.Libs +import com.mikepenz.aboutlibraries.entity.Library +import com.mikepenz.aboutlibraries.entity.License +import com.mikepenz.community_material_typeface_library.CommunityMaterial +import com.mikepenz.fastadapter.IItem +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.typeface.IIcon +import com.pitchedapps.frost.BuildConfig +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.Prefs + + +/** + * Created by Allan Wang on 2017-06-26. + */ +class AboutActivity : AboutActivityBase(null, { + textColor = Prefs.textColor + accentColor = Prefs.accentColor + backgroundColor = Prefs.bgColor.withMinAlpha(200) + cutoutForeground = if (0xff3b5998.toInt().isColorVisibleOn(Prefs.bgColor)) 0xff3b5998.toInt() else Prefs.accentColor + cutoutDrawableRes = R.drawable.frost_f_256 +}) { + + override fun getLibraries(libs: Libs): List { + val include = arrayOf( + "AboutLibraries", + "AndroidIconics", + "dbflow", + "fastadapter", + "glide", + "Jsoup", + "kau", + "kotterknife", + "materialdialogs", + "materialdrawer" + ) + + /* + * These are great libraries, but either aren't used directly or are too common to be listed + * Give more emphasis on the unique libs! + */ + val exclude = arrayOf( + "GoogleMaterialDesignIcons", + "intellijannotations", + "MaterialDesignIconicIcons", + "MaterialDesignIcons", + "materialize", + "appcompat_v7", + "design", + "recyclerview_v7", + "support_v4" + ) + val l = libs.prepareLibraries(this, include, null, false, true) +// l.forEach { KL.d("Lib ${it.definedName}") } + return l + } + + override fun postInflateMainPage(adapter: FastItemThemedAdapter>) { + /** + * Frost may not be a library but we're conveying the same info + */ + val frost = Library().apply { + libraryName = string(R.string.app_name) + author = "Pitched Apps" + libraryWebsite = "https://github.com/AllanWang/Frost-for-Facebook" + isOpenSource = true + libraryDescription = string(R.string.frost_description) + libraryVersion = BuildConfig.VERSION_NAME + license = License().apply { + licenseName = "GNU GPL v3" + licenseWebsite = "https://www.gnu.org/licenses/gpl-3.0.en.html" + } + } + adapter.add(LibraryIItem(frost)).add(AboutLinks()) + + } + + class AboutLinks : AbstractItem(), ThemableIItem by ThemableIItemDelegate() { + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) + + override fun getType(): Int = R.id.item_about_links + + override fun getLayoutRes(): Int = R.layout.item_about_links + + override fun bindView(holder: ViewHolder, payloads: MutableList?) { + super.bindView(holder, payloads) + with(holder) { + bindIconColor(*images.toTypedArray()) + bindBackgroundColor(container) + } + } + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + + val container: ConstraintLayout by bindView(R.id.about_icons_container) + val images: List + + /** + * There are a lot of constraints to be added to each item just to have them chained properly + * My as well do it programmatically + * Initializing the viewholder will setup the icons, scale type and background of all icons, + * link their click listeners and chain them together via a horizontal spread + */ + init { + val c = itemView.context + val size = c.dimenPixelSize(R.dimen.kau_avatar_bounds) + images = arrayOf Unit>>( + GoogleMaterial.Icon.gmd_star to { c.startPlayStoreLink(R.string.play_store_package_id) }, + CommunityMaterial.Icon.cmd_reddit to { c.startLink("https://www.reddit.com/r/FrostForFacebook/") }, + CommunityMaterial.Icon.cmd_github_circle to { c.startLink("https://github.com/AllanWang/Frost-for-Facebook") } + ).mapIndexed { i, (icon, onClick) -> + ImageView(c).apply { + layoutParams = ViewGroup.LayoutParams(size, size) + id = 109389 + i + setImageDrawable(icon.toDrawable(context, 32)) + scaleType = ImageView.ScaleType.CENTER + background = context.resolveDrawable(android.R.attr.selectableItemBackgroundBorderless) + setOnClickListener({ onClick() }) + container.addView(this) + } + } + val set = ConstraintSet() + set.clone(container) + set.createHorizontalChain(ConstraintSet.PARENT_ID, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.RIGHT, + images.map { it.id }.toIntArray(), null, ConstraintSet.CHAIN_SPREAD_INSIDE) + set.applyTo(container) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt new file mode 100644 index 00000000..fd020af1 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt @@ -0,0 +1,32 @@ +package com.pitchedapps.frost.activities + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.materialDialogThemed +import com.pitchedapps.frost.utils.setFrostTheme + +/** + * Created by Allan Wang on 2017-06-12. + */ +open class BaseActivity : AppCompatActivity() { + override fun onBackPressed() { + if (isTaskRoot && Prefs.exitConfirmation) { + materialDialogThemed { + title(R.string.kau_exit) + content(R.string.kau_exit_confirmation) + positiveText(R.string.kau_yes) + negativeText(R.string.kau_no) + onPositive { _, _ -> super.onBackPressed() } + checkBoxPromptRes(R.string.kau_do_not_show_again, false, { _, b -> Prefs.exitConfirmation = !b }) + } + } else super.onBackPressed() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setFrostTheme() + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt new file mode 100644 index 00000000..1773471f --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/FrostWebActivity.kt @@ -0,0 +1,20 @@ +package com.pitchedapps.frost.activities + +import android.os.Bundle +import com.pitchedapps.frost.utils.Prefs + + +/** + * Created by Allan Wang on 2017-06-19. + * + * Replica of [WebOverlayActivity] with a different base url + * Didn't use activity-alias because it causes issues when only one activity has the singleInstance mode + */ +class FrostWebActivity : WebOverlayActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + Prefs.prevId = Prefs.userId + super.onCreate(savedInstanceState) + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt new file mode 100644 index 00000000..509ac2cb --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -0,0 +1,297 @@ +package com.pitchedapps.frost.activities + +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.content.Intent +import android.content.res.ColorStateList +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.support.design.widget.FloatingActionButton +import android.support.v4.content.FileProvider +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.view.ViewGroup +import android.widget.ProgressBar +import android.widget.TextView +import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE +import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult +import ca.allanwang.kau.permissions.kauRequestPermissions +import ca.allanwang.kau.utils.* +import com.bumptech.glide.request.target.BaseTarget +import com.bumptech.glide.request.target.SizeReadyCallback +import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition +import com.davemorrissey.labs.subscaleview.ImageSource +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.typeface.IIcon +import com.pitchedapps.frost.BuildConfig +import com.pitchedapps.frost.R +import com.pitchedapps.frost.utils.* +import com.sothree.slidinguppanel.SlidingUpPanelLayout +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.uiThread +import timber.log.Timber +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.* + +/** + * Created by Allan Wang on 2017-07-15. + */ +class ImageActivity : AppCompatActivity() { + + val progress: ProgressBar by bindView(R.id.image_progress) + val container: ViewGroup by bindView(R.id.image_container) + val panel: SlidingUpPanelLayout? by bindOptionalView(R.id.image_panel) + val photo: SubsamplingScaleImageView by bindView(R.id.image_photo) + val caption: TextView? by bindOptionalView(R.id.image_text) + val fab: FloatingActionButton by bindView(R.id.image_fab) + + /** + * Reference to the temporary file path + * Should be nonnull if the image is successfully loaded + * As this is temporary, the image is deleted upon exit + */ + internal var tempFilePath: String? = null + /** + * Reference to path for downloaded image + * Nonnull once the image is downloaded by the user + */ + internal var downloadPath: String? = null + /** + * Indicator for fab's click result + */ + internal var fabAction: FabStates = FabStates.NOTHING + set(value) { + if (field == value) return + field = value + value.update(fab) + } + + val imageUrl: String + get() = intent.extras.getString(ARG_IMAGE_URL) + + val text: String? + get() = intent.extras.getString(ARG_TEXT) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(if (!text.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless) + container.setBackgroundColor(Prefs.bgColor.withMinAlpha(222)) + caption?.setTextColor(Prefs.textColor) + caption?.setBackgroundColor(Prefs.bgColor.colorToForeground(0.1f).withAlpha(255)) + caption?.text = text + progress.tint(Prefs.accentColor) + panel?.addPanelSlideListener(object : SlidingUpPanelLayout.SimplePanelSlideListener() { + + override fun onPanelSlide(panel: View, slideOffset: Float) { + if (slideOffset == 0f && !fab.isShown) fab.show() + else if (slideOffset != 0f && fab.isShown) fab.hide() + caption?.alpha = slideOffset / 2 + 0.5f + } + + }) + fab.setOnClickListener { fabAction.onClick(this) } + photo.setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() { + override fun onImageLoadError(e: Exception) { + L.e(e, "Image load error") + imageCallback(null, false) + } + }) + GlideApp.with(this).asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback)) + } + + /** + * Callback to add image to view + * [resource] is guaranteed to be nonnull when [success] is true + * and null when it is false + */ + private fun imageCallback(resource: Bitmap?, success: Boolean) { + if (progress.isVisible) progress.fadeOut() + if (success) { + saveTempImage(resource!!, { + if (it == null) { + imageCallback(null, false) + } else { + photo.setImage(ImageSource.uri(it)) + fabAction = FabStates.DOWNLOAD + photo.animate().alpha(1f).scaleX(1f).scaleY(1f).withEndAction { fab.show() }.start() + } + }) + } else { + fabAction = FabStates.ERROR + fab.show() + } + } + + /** + * Bitmap load handler + */ + class PhotoTarget(val callback: (resource: Bitmap?, success: Boolean) -> Unit) : BaseTarget() { + + override fun removeCallback(cb: SizeReadyCallback?) {} + + override fun onResourceReady(resource: Bitmap, transition: Transition?) = callback(resource, true) + + override fun onLoadFailed(errorDrawable: Drawable?) = callback(null, false) + + override fun getSize(cb: SizeReadyCallback) = cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + + } + + private fun saveTempImage(resource: Bitmap, callback: (uri: Uri?) -> Unit) { + var photoFile: File? = null + try { + photoFile = createImageFile() + } catch (ignored: IOException) { + } finally { + if (photoFile == null) { + callback(null) + } else { + tempFilePath = photoFile.absolutePath + Timber.d("Temp image path $tempFilePath") + // File created; proceed with request + val photoURI = FileProvider.getUriForFile(this, + BuildConfig.APPLICATION_ID + ".provider", + photoFile) + photoFile.outputStream().use { resource.compress(Bitmap.CompressFormat.PNG, 100, it) } + callback(photoURI) + } + } + } + + @Suppress("SIMPLE_DATE_FORMAT") + @Throws(IOException::class) + private fun createImageFile(): File { + // Create an image file name + @SuppressLint("SimpleDateFormat") + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) + val imageFileName = "Frost_" + timeStamp + "_" + val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) + return File.createTempFile( + imageFileName, /* prefix */ + ".png", /* suffix */ + storageDir /* directory */ + ) + } + + internal fun downloadImage() { + kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { + granted, _ -> + if (granted) { + doAsync { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date()) + val imageFileName = "Frost_" + timeStamp + "_" + val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + val frostDir = File(storageDir, "Frost") + if (!frostDir.exists()) frostDir.mkdirs() + val destination = File.createTempFile(imageFileName, ".png", frostDir) + downloadPath = destination.absolutePath + var success = true + try { + File(tempFilePath).copyTo(destination, true) + } catch (e: Exception) { + success = false + } finally { + uiThread { + snackbar(if (success) R.string.image_download_success else R.string.image_download_fail) + if (success) { + deleteTempFile() + fabAction = FabStates.SHARE + } + } + } + } + } + } + } + + internal fun deleteTempFile() { + if (tempFilePath != null) { + File(tempFilePath!!).delete() + tempFilePath = null + } + } + + override fun onDestroy() { + deleteTempFile() + super.onDestroy() + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + kauOnRequestPermissionsResult(permissions, grantResults) + } +} + +internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.textColor, val backgroundTint: Int = Prefs.accentBackgroundColor.withAlpha(255)) { + ERROR(GoogleMaterial.Icon.gmd_error, Color.WHITE, Color.RED) { + override fun onClick(activity: ImageActivity) { + //todo add something + } + }, + NOTHING(GoogleMaterial.Icon.gmd_adjust) { + override fun onClick(activity: ImageActivity) {} + }, + DOWNLOAD(GoogleMaterial.Icon.gmd_file_download) { + override fun onClick(activity: ImageActivity) { + activity.downloadImage() + } + }, + SHARE(GoogleMaterial.Icon.gmd_share) { + override fun onClick(activity: ImageActivity) { + try { + val photoURI = FileProvider.getUriForFile(activity, + BuildConfig.APPLICATION_ID + ".provider", + File(activity.downloadPath)) + val intent = Intent(Intent.ACTION_SEND).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + putExtra(Intent.EXTRA_STREAM, photoURI) + type = "image/png" + } + activity.startActivity(intent) + } catch (e: Exception) { + L.e(e, "Image share failed"); + activity.snackbar(R.string.image_share_failed) + } + } + }; + + /** + * Change the fab look + * If it's in view, give it some animations + */ + fun update(fab: FloatingActionButton) { + if (!fab.isShown) { + fab.setIcon(iicon, color = iconColor) + fab.backgroundTintList = ColorStateList.valueOf(backgroundTint) + } else { + var switched = false + ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f).apply { + duration = 500L + addUpdateListener { + val x = it.animatedValue as Float + val scale = x * 0.3f + 0.7f + fab.scaleX = scale + fab.scaleY = scale + fab.imageAlpha = (x * 255).toInt() + if (it.animatedFraction > 0.5f && !switched) { + switched = true + fab.setIcon(iicon, color = iconColor) + fab.backgroundTintList = ColorStateList.valueOf(backgroundTint) + } + } + start() + } + } + } + + abstract fun onClick(activity: ImageActivity) + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt new file mode 100644 index 00000000..e4897be5 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt @@ -0,0 +1,126 @@ +package com.pitchedapps.frost.activities + +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.Handler +import android.support.v4.widget.SwipeRefreshLayout +import android.support.v7.widget.AppCompatTextView +import android.support.v7.widget.Toolbar +import android.widget.ImageView +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.fadeIn +import ca.allanwang.kau.utils.fadeOut +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import com.crashlytics.android.answers.LoginEvent +import com.pitchedapps.frost.R +import com.pitchedapps.frost.dbflow.CookieModel +import com.pitchedapps.frost.dbflow.fetchUsername +import com.pitchedapps.frost.dbflow.loadFbCookiesAsync +import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL +import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.web.LoginWebView +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.functions.BiFunction +import io.reactivex.internal.operators.single.SingleToObservable +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.SingleSubject + + +/** + * Created by Allan Wang on 2017-06-01. + */ +class LoginActivity : BaseActivity() { + + val toolbar: Toolbar by bindView(R.id.toolbar) + val web: LoginWebView by bindView(R.id.login_webview) + val swipeRefresh: SwipeRefreshLayout by bindView(R.id.swipe_refresh) + val textview: AppCompatTextView by bindView(R.id.textview) + val profile: ImageView by bindView(R.id.profile) + + val loginObservable = SingleSubject.create() + val progressObservable = BehaviorSubject.create()!! + val profileObservable = SingleSubject.create() + val usernameObservable = SingleSubject.create() + + // Helper to set and enable swipeRefresh + var refresh: Boolean + get() = swipeRefresh.isRefreshing + set(value) { + if (value) swipeRefresh.isEnabled = true + swipeRefresh.isRefreshing = value + if (!value) swipeRefresh.isEnabled = false + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_login) + setSupportActionBar(toolbar) + setTitle(R.string.kau_login) + setFrostColors(toolbar) + web.loginObservable = loginObservable + web.progressObservable = progressObservable + loginObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { + cookie -> + web.fadeOut(onFinish = { + profile.fadeIn() + loadInfo(cookie) + }) + } + progressObservable.observeOn(AndroidSchedulers.mainThread()).subscribe { refresh = it != 100 } + web.loadLogin() + } + + fun loadInfo(cookie: CookieModel) { + refresh = true + Observable.zip(SingleToObservable(profileObservable), SingleToObservable(usernameObservable), + BiFunction> { foundImage, name -> Pair(foundImage, name) }) + .observeOn(AndroidSchedulers.mainThread()).subscribe { + (foundImage, name) -> + refresh = false + if (!foundImage) { + L.eThrow("Could not get profile photo; Invalid userId?") + L.i("-\t$cookie") + } + textview.text = String.format(getString(R.string.welcome), name) + textview.fadeIn() + frostAnswers { logLogin(LoginEvent().putMethod("frost_browser").putSuccess(true)) } + /* + * The user may have logged into an account that is already in the database + * We will let the db handle duplicates and load it now after the new account has been saved + */ + loadFbCookiesAsync { + cookies -> + Handler().postDelayed({ + launchNewTask(MainActivity::class.java, ArrayList(cookies), clearStack = true) + }, 1000) + } + } + loadProfile(cookie.id) + loadUsername(cookie) + } + + + fun loadProfile(id: Long) { + Glide.with(this@LoginActivity).load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener { + override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + profileObservable.onSuccess(true) + return false + } + + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + if (e != null) L.e(e, "Profile loading exception") + profileObservable.onSuccess(false) + return false + } + }).into(profile) + } + + fun loadUsername(cookie: CookieModel) { + cookie.fetchUsername { usernameObservable.onSuccess(it) } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt new file mode 100644 index 00000000..ba76e594 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt @@ -0,0 +1,472 @@ +package com.pitchedapps.frost.activities + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.drawable.ColorDrawable +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.support.annotation.StringRes +import android.support.design.widget.* +import android.support.v4.app.ActivityOptionsCompat +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v4.app.FragmentPagerAdapter +import android.support.v4.view.ViewPager +import android.support.v7.widget.Toolbar +import android.view.Menu +import android.view.MenuItem +import android.webkit.ValueCallback +import android.webkit.WebChromeClient +import ca.allanwang.kau.changelog.showChangelog +import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult +import ca.allanwang.kau.searchview.SearchItem +import ca.allanwang.kau.searchview.SearchView +import ca.allanwang.kau.searchview.bindSearchView +import ca.allanwang.kau.utils.* +import co.zsmb.materialdrawerkt.builders.Builder +import co.zsmb.materialdrawerkt.builders.accountHeader +import co.zsmb.materialdrawerkt.builders.drawer +import co.zsmb.materialdrawerkt.draweritems.badgeable.primaryItem +import co.zsmb.materialdrawerkt.draweritems.badgeable.secondaryItem +import co.zsmb.materialdrawerkt.draweritems.divider +import co.zsmb.materialdrawerkt.draweritems.profile.profile +import co.zsmb.materialdrawerkt.draweritems.profile.profileSetting +import com.crashlytics.android.answers.ContentViewEvent +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.materialdrawer.AccountHeader +import com.mikepenz.materialdrawer.Drawer +import com.pitchedapps.frost.BuildConfig +import com.pitchedapps.frost.R +import com.pitchedapps.frost.contracts.ActivityWebContract +import com.pitchedapps.frost.contracts.FileChooserContract +import com.pitchedapps.frost.contracts.FileChooserDelegate +import com.pitchedapps.frost.dbflow.loadFbCookie +import com.pitchedapps.frost.dbflow.loadFbTabs +import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.FbCookie.switchUser +import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL +import com.pitchedapps.frost.fragments.WebFragment +import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.utils.iab.validatePro +import com.pitchedapps.frost.views.BadgedIcon +import com.pitchedapps.frost.views.FrostViewPager +import com.pitchedapps.frost.web.FrostWebViewSearch +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.PublishSubject +import org.jsoup.Jsoup +import java.util.concurrent.TimeUnit + +class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, + ActivityWebContract, FileChooserContract by FileChooserDelegate() { + + lateinit var adapter: SectionsPagerAdapter + val toolbar: Toolbar by bindView(R.id.toolbar) + val viewPager: FrostViewPager by bindView(R.id.container) + val fab: FloatingActionButton by bindView(R.id.fab) + val tabs: TabLayout by bindView(R.id.tabs) + val appBar: AppBarLayout by bindView(R.id.appbar) + val coordinator: CoordinatorLayout by bindView(R.id.main_content) + lateinit var drawer: Drawer + lateinit var drawerHeader: AccountHeader + var webFragmentObservable = PublishSubject.create()!! + var lastPosition = -1 + val headerBadgeObservable = PublishSubject.create() + var hiddenSearchView: FrostWebViewSearch? = null + var firstLoadFinished = false + set(value) { + L.d("First fragment load has finished") + field = value + if (value && hiddenSearchView == null) { + hiddenSearchView = FrostWebViewSearch(this, this) + } + } + var searchView: SearchView? = null + override val isSearchOpened: Boolean + get() = searchView?.isOpen ?: false + + companion object { + const val ACTIVITY_SETTINGS = 97 + /* + * Possible responses from the SettingsActivity + * after the configurations have changed + */ + const val REQUEST_RESTART = 90909 + const val REQUEST_REFRESH = 80808 + const val REQUEST_WEB_ZOOM = 50505 + const val REQUEST_NAV = 10101 + const val REQUEST_SEARCH = 70707 + const val REQUEST_RESTART_APPLICATION = 60606 + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (BuildConfig.VERSION_CODE > Prefs.versionCode) { + Prefs.versionCode = BuildConfig.VERSION_CODE + if (!BuildConfig.DEBUG) { + showChangelog(R.xml.changelog, Prefs.textColor) { theme() } + frostAnswersCustom("Version") { + putCustomAttribute("Version code", BuildConfig.VERSION_CODE) + putCustomAttribute("Version name", BuildConfig.VERSION_NAME) + putCustomAttribute("Build type", BuildConfig.BUILD_TYPE) + putCustomAttribute("Frost id", Prefs.frostId) + } + } + } + setContentView(R.layout.activity_main) + setSupportActionBar(toolbar) + adapter = SectionsPagerAdapter(supportFragmentManager, loadFbTabs()) + viewPager.adapter = adapter + viewPager.offscreenPageLimit = 5 + viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + if (lastPosition == position) return + if (lastPosition != -1) webFragmentObservable.onNext(-(lastPosition + 1)) + webFragmentObservable.onNext(position) + lastPosition = position + } + + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + super.onPageScrolled(position, positionOffset, positionOffsetPixels) + val delta: Float by lazy { positionOffset * (255 - 128).toFloat() } + tabsForEachView { + tabPosition, view -> + view.setAllAlpha(when (tabPosition) { + position -> 255.0f - delta + position + 1 -> 128.0f + delta + else -> 128f + }) + } + } + }) + viewPager.post { webFragmentObservable.onNext(0); lastPosition = 0 } //trigger hook so title is set + setupDrawer(savedInstanceState) + setupTabs() + fab.setOnClickListener { view -> + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show() + } + setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager)) + validatePro() + } + + fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) { + (0 until tabs.tabCount).asSequence().forEach { + i -> + action(i, tabs.getTabAt(i)!!.customView as BadgedIcon) + } + } + + fun setupTabs() { + viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs)) + tabs.addOnTabSelectedListener(object : TabLayout.ViewPagerOnTabSelectedListener(viewPager) { + override fun onTabReselected(tab: TabLayout.Tab) { + super.onTabReselected(tab) + currentFragment.web.scrollOrRefresh() + } + + override fun onTabSelected(tab: TabLayout.Tab) { + super.onTabSelected(tab) + (tab.customView as BadgedIcon).badgeText = null + } + }) + headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread()) + .map { Jsoup.parse(it) } + .filter { it.select("[data-sigil=count]").size >= 0 } //ensure headers exist + .map { + val feed = it.select("[data-sigil*=feed] [data-sigil=count]") + val requests = it.select("[data-sigil*=requests] [data-sigil=count]") + val messages = it.select("[data-sigil*=messages] [data-sigil=count]") + val notifications = it.select("[data-sigil*=notifications] [data-sigil=count]") + return@map arrayOf(feed, requests, messages, notifications).map { it?.getOrNull(0)?.ownText() } + } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + (feed, requests, messages, notifications) -> + tabsForEachView { + _, view -> + when (view.iicon) { + FbTab.FEED.icon -> view.badgeText = feed + FbTab.FRIENDS.icon -> view.badgeText = requests + FbTab.MESSAGES.icon -> view.badgeText = messages + FbTab.NOTIFICATIONS.icon -> view.badgeText = notifications + } + } + } + adapter.pages.forEach { + tabs.addTab(tabs.newTab() + .setCustomView(BadgedIcon(this).apply { + iicon = it.icon + })) + } + } + + fun setupDrawer(savedInstanceState: Bundle?) { + val navBg = Prefs.bgColor.withMinAlpha(200).toLong() + val navHeader = Prefs.headerColor.withMinAlpha(200) + drawer = drawer { + toolbar = this@MainActivity.toolbar + savedInstance = savedInstanceState + translucentStatusBar = false + sliderBackgroundColor = navBg + drawerHeader = accountHeader { + textColor = Prefs.iconColor.toLong() + backgroundDrawable = ColorDrawable(navHeader) + selectionSecondLineShown = false + paddingBelow = false + cookies().forEach { (id, name) -> + profile(name = name ?: "") { + iconUrl = PROFILE_PICTURE_URL(id) + textColor = Prefs.textColor.toLong() + selectedTextColor = Prefs.textColor.toLong() + selectedColor = 0x00000001.toLong() + identifier = id + } + } + profileSetting(nameRes = R.string.kau_logout) { + iicon = GoogleMaterial.Icon.gmd_exit_to_app + iconColor = Prefs.textColor.toLong() + textColor = Prefs.textColor.toLong() + identifier = -2L + } + profileSetting(nameRes = R.string.kau_add_account) { + iconDrawable = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_add).actionBar().paddingDp(5).color(Prefs.textColor) + textColor = Prefs.textColor.toLong() + identifier = -3L + } + profileSetting(nameRes = R.string.kau_manage_account) { + iicon = GoogleMaterial.Icon.gmd_settings + iconColor = Prefs.textColor.toLong() + textColor = Prefs.textColor.toLong() + identifier = -4L + } + onProfileChanged { _, profile, current -> + if (current) launchWebOverlay(FbTab.PROFILE.url) + else when (profile.identifier) { + -2L -> { + val currentCookie = loadFbCookie(Prefs.userId) + if (currentCookie == null) { + toast(R.string.account_not_found) + FbCookie.reset { launchLogin(cookies(), true) } + } else { + materialDialogThemed { + title(R.string.kau_logout) + content(String.format(string(R.string.kau_logout_confirm_as_x), currentCookie.name ?: Prefs.userId.toString())) + positiveText(R.string.kau_yes) + negativeText(R.string.kau_no) + onPositive { _, _ -> + FbCookie.logout(Prefs.userId) { + val allCookies = cookies() + allCookies.remove(currentCookie) + launchLogin(allCookies, true) + } + } + } + } + } + -3L -> launchNewTask(LoginActivity::class.java, clearStack = false) + -4L -> launchNewTask(SelectorActivity::class.java, cookies(), false) + else -> { + switchUser(profile.identifier, { refreshAll() }) + tabsForEachView { _, view -> view.badgeText = null } + } + } + false + } + } + drawerHeader.setActiveProfile(Prefs.userId) + primaryFrostItem(FbTab.FEED_MOST_RECENT) + primaryFrostItem(FbTab.FEED_TOP_STORIES) + primaryFrostItem(FbTab.ACTIVITY_LOG) + divider() + primaryFrostItem(FbTab.PHOTOS) + primaryFrostItem(FbTab.GROUPS) + primaryFrostItem(FbTab.PAGES) + divider() + primaryFrostItem(FbTab.EVENTS) + primaryFrostItem(FbTab.BIRTHDAYS) + primaryFrostItem(FbTab.ON_THIS_DAY) + divider() + primaryFrostItem(FbTab.NOTES) + primaryFrostItem(FbTab.SAVED) + } + } + + fun Builder.primaryFrostItem(item: FbTab) = this.primaryItem(item.titleId) { + iicon = item.icon + iconColor = Prefs.textColor.toLong() + textColor = Prefs.textColor.toLong() + selectedIconColor = Prefs.textColor.toLong() + selectedTextColor = Prefs.textColor.toLong() + selectedColor = 0x00000001.toLong() + identifier = item.titleId.toLong() + onClick { _ -> + frostAnswers { + logContentView(ContentViewEvent() + .putContentName(item.name) + .putContentType("drawer_item")) + } + launchWebOverlay(item.url) + false + } + } + + fun Builder.secondaryFrostItem(@StringRes title: Int, onClick: () -> Unit) = this.secondaryItem(title) { + textColor = Prefs.textColor.toLong() + selectedIconColor = Prefs.textColor.toLong() + selectedTextColor = Prefs.textColor.toLong() + selectedColor = 0x00000001.toLong() + identifier = title.toLong() + onClick { _ -> onClick(); false } + } + + + /** + * Something happened where the normal search function won't work + * Fallback to overlay style + */ + override fun searchOverlayDispose() { + hiddenSearchView?.dispose() + hiddenSearchView = null + searchView = null + //todo remove true searchview and add contract + } + + override fun emitSearchResponse(items: List) { + searchView?.results = items + } + + fun refreshAll() { + webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + toolbar.tint(Prefs.iconColor) + setMenuIcons(menu, Prefs.iconColor, + R.id.action_settings to GoogleMaterial.Icon.gmd_settings, + R.id.action_search to GoogleMaterial.Icon.gmd_search) + if (Prefs.searchBar) { + if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = FrostWebViewSearch(this, this) + if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) { + textObserver = { + observable, _ -> + observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) } + } + foregroundColor = Prefs.textColor + backgroundColor = Prefs.bgColor.withMinAlpha(200) + openListener = { hiddenSearchView?.pauseLoad = false } + closeListener = { hiddenSearchView?.pauseLoad = true } + onItemClick = { _, key, _, _ -> launchWebOverlay(key) } + } + } else { + searchOverlayDispose() + menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbTab.SEARCH.url); true } + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_settings -> { + val intent = Intent(this, SettingsActivity::class.java) + val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.kau_slide_in_right, R.anim.kau_fade_out).toBundle() + startActivityForResult(intent, ACTIVITY_SETTINGS, bundle) + } + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun openFileChooser(filePathCallback: ValueCallback>, fileChooserParams: WebChromeClient.FileChooserParams) { + openFileChooser(this, filePathCallback, fileChooserParams) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (onActivityResultWeb(requestCode, resultCode, data)) return + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == ACTIVITY_SETTINGS) { + when (resultCode) { + REQUEST_RESTART -> restart() + REQUEST_REFRESH -> webFragmentObservable.onNext(WebFragment.REQUEST_REFRESH) + REQUEST_NAV -> frostNavigationBar() + REQUEST_WEB_ZOOM -> webFragmentObservable.onNext(WebFragment.REQUEST_TEXT_ZOOM) + REQUEST_SEARCH -> invalidateOptionsMenu() + REQUEST_RESTART_APPLICATION -> { //completely restart application + L.d("Restart Application Requested") + val intent = packageManager.getLaunchIntentForPackage(packageName) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) + val pending = PendingIntent.getActivity(this, 666, intent, PendingIntent.FLAG_CANCEL_CURRENT) + val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + alarm.setExactAndAllowWhileIdle(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) + else + alarm.setExact(AlarmManager.RTC, System.currentTimeMillis() + 100, pending) + finish() + System.exit(0) + } + } + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + kauOnRequestPermissionsResult(permissions, grantResults) + } + + override fun onResume() { + super.onResume() + FbCookie.switchBackUser { } + } + + override fun onStart() { + //validate some pro features + if (!Prefs.pro) { + if (Prefs.theme == Theme.CUSTOM.ordinal) Prefs.theme = Theme.DEFAULT.ordinal + } + super.onStart() + } + + override fun onBackPressed() { + if (searchView?.onBackPressed() ?: false) return + if (currentFragment.onBackPressed()) return + super.onBackPressed() + } + + val currentFragment + get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment + + inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List) : FragmentPagerAdapter(fm) { + + override fun getItem(position: Int): Fragment { + val fragment = WebFragment(pages[position], position) + //If first load hasn't occurred, add a listener + if (!firstLoadFinished) { + var disposable: Disposable? = null + fragment.post { + disposable = it.web.refreshObservable.subscribe { + if (!it) { + //Ensure first load finisher only happens once + if (!firstLoadFinished) firstLoadFinished = true + disposable?.dispose() + disposable = null + } + } + } + } + return fragment + } + + override fun getCount() = pages.size + + override fun getPageTitle(position: Int): CharSequence = getString(pages[position].titleId) + } + +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt new file mode 100644 index 00000000..ff87f448 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SelectorActivity.kt @@ -0,0 +1,47 @@ +package com.pitchedapps.frost.activities + +import android.os.Bundle +import android.support.constraint.ConstraintLayout +import android.support.v7.widget.AppCompatTextView +import android.support.v7.widget.GridLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.View +import ca.allanwang.kau.utils.bindView +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter +import com.mikepenz.fastadapter.listeners.ClickEventHook +import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.utils.cookies +import com.pitchedapps.frost.utils.launchNewTask +import com.pitchedapps.frost.utils.setFrostColors +import com.pitchedapps.frost.views.AccountItem + +/** + * Created by Allan Wang on 2017-06-04. + */ +class SelectorActivity : BaseActivity() { + + val recycler: RecyclerView by bindView(R.id.selector_recycler) + val adapter = FastItemAdapter() + val text: AppCompatTextView by bindView(R.id.text_select_account) + val container: ConstraintLayout by bindView(R.id.container) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_selector) + recycler.layoutManager = GridLayoutManager(this, 2) + recycler.adapter = adapter + adapter.add(cookies().map { AccountItem(it) }) + adapter.add(AccountItem(null)) // add account + adapter.withEventHook(object : ClickEventHook() { + override fun onBind(viewHolder: RecyclerView.ViewHolder): View? = (viewHolder as? AccountItem.ViewHolder)?.v + + override fun onClick(v: View, position: Int, fastAdapter: FastAdapter, item: AccountItem) { + if (item.cookie == null) this@SelectorActivity.launchNewTask(LoginActivity::class.java) + else FbCookie.switchUser(item.cookie, { launchNewTask(MainActivity::class.java, cookies()) }) + } + }) + setFrostColors(texts = arrayOf(text), backgrounds = arrayOf(container)) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt new file mode 100644 index 00000000..b3b3bd7c --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt @@ -0,0 +1,145 @@ +package com.pitchedapps.frost.activities + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import ca.allanwang.kau.changelog.showChangelog +import ca.allanwang.kau.kpref.activity.CoreAttributeContract +import ca.allanwang.kau.kpref.activity.KPrefActivity +import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder +import ca.allanwang.kau.kpref.activity.items.KPrefItemBase +import ca.allanwang.kau.ui.views.RippleCanvas +import ca.allanwang.kau.utils.* +import com.mikepenz.community_material_typeface_library.CommunityMaterial +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.pitchedapps.frost.BuildConfig +import com.pitchedapps.frost.R +import com.pitchedapps.frost.settings.* +import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.utils.iab.* + + +/** + * Created by Allan Wang on 2017-06-06. + */ +class SettingsActivity : KPrefActivity(), IabBroadcastReceiver.IabBroadcastListener { + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (!IAB.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data) + adapter.notifyDataSetChanged() + } + } + + + override fun receivedBroadcast() { + L.d("IAB broadcast") + adapter.notifyDataSetChanged() + } + + override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = { + textColor = { Prefs.textColor } + accentColor = { Prefs.accentColor } + } + + override fun onCreateKPrefs(savedInstanceState: android.os.Bundle?): KPrefAdapterBuilder.() -> Unit = { + subItems(R.string.appearance, getAppearancePrefs()) { + descRes = R.string.appearance_desc + iicon = GoogleMaterial.Icon.gmd_palette + } + + subItems(R.string.behaviour, getBehaviourPrefs()) { + descRes = R.string.behaviour_desc + iicon = GoogleMaterial.Icon.gmd_trending_up + } + + subItems(R.string.newsfeed, getFeedPrefs()) { + descRes = R.string.newsfeed_desc + iicon = CommunityMaterial.Icon.cmd_newspaper + } + + subItems(R.string.notifications, getNotificationPrefs()) { + descRes = R.string.notifications_desc + iicon = GoogleMaterial.Icon.gmd_notifications + } + + subItems(R.string.experimental, getExperimentalPrefs()) { + descRes = R.string.experimental_desc + iicon = CommunityMaterial.Icon.cmd_flask_outline + } + + plainText(R.string.restore_purchases) { + descRes = R.string.restore_purchases_desc + iicon = GoogleMaterial.Icon.gmd_refresh + onClick = { _, _, _ -> this@SettingsActivity.restorePurchases(); true } + } + + plainText(R.string.about_frost) { + iicon = GoogleMaterial.Icon.gmd_info + onClick = { _, _, _ -> startActivity(AboutActivity::class.java, transition = true); true } + } + + if (BuildConfig.DEBUG) { + checkbox(R.string.custom_pro, { Prefs.debugPro }, { Prefs.debugPro = it }) + } + } + + fun KPrefItemBase.BaseContract<*>.dependsOnPro() { + onDisabledClick = { _, _, _ -> openPlayProPurchase(0); true } + enabler = { IS_FROST_PRO } + } + + fun shouldRestartMain() { + setResult(MainActivity.REQUEST_RESTART) + } + + override fun onCreate(savedInstanceState: Bundle?) { + setFrostTheme(true) + super.onCreate(savedInstanceState) + animate = Prefs.animate + themeExterior(false) + } + + fun themeExterior(animate: Boolean = true) { + if (animate) bgCanvas.fade(Prefs.bgColor) + else bgCanvas.set(Prefs.bgColor) + if (animate) toolbarCanvas.ripple(Prefs.headerColor, RippleCanvas.MIDDLE, RippleCanvas.END) + else toolbarCanvas.set(Prefs.headerColor) + frostNavigationBar() + } + + override fun onBackPressed() { + if (!super.backPress()) + finishSlideOut() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_settings, menu) + toolbar.tint(Prefs.iconColor) + toolbarTitle.textColor = Prefs.iconColor + toolbarTitle.invalidate() + setMenuIcons(menu, Prefs.iconColor, + R.id.action_email to GoogleMaterial.Icon.gmd_email, + R.id.action_changelog to GoogleMaterial.Icon.gmd_info) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_email -> materialDialogThemed { + title(R.string.subject) + items(Support.values().map { string(it.title) }) + itemsCallback { _, _, which, _ -> Support.values()[which].sendEmail(this@SettingsActivity) } + } + R.id.action_changelog -> showChangelog(R.xml.changelog, Prefs.textColor) { theme() } + else -> return super.onOptionsItemSelected(item) + } + return true + } + + override fun onDestroy() { + IAB.dispose() + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt new file mode 100644 index 00000000..f03c653c --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/WebOverlayActivity.kt @@ -0,0 +1,158 @@ +package com.pitchedapps.frost.activities + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.support.design.widget.CoordinatorLayout +import android.support.design.widget.Snackbar +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar +import android.view.Menu +import android.view.MenuItem +import android.webkit.ValueCallback +import android.webkit.WebChromeClient +import ca.allanwang.kau.permissions.kauOnRequestPermissionsResult +import ca.allanwang.kau.swipe.kauSwipeOnCreate +import ca.allanwang.kau.swipe.kauSwipeOnDestroy +import ca.allanwang.kau.swipe.kauSwipeOnPostCreate +import ca.allanwang.kau.utils.* +import com.mikepenz.community_material_typeface_library.CommunityMaterial +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import com.pitchedapps.frost.R +import com.pitchedapps.frost.contracts.ActivityWebContract +import com.pitchedapps.frost.contracts.FileChooserContract +import com.pitchedapps.frost.contracts.FileChooserDelegate +import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.utils.* +import com.pitchedapps.frost.web.FrostWebView + + +/** + * Created by Allan Wang on 2017-06-01. + */ +open class WebOverlayActivity : AppCompatActivity(), + ActivityWebContract, FileChooserContract by FileChooserDelegate() { + + val toolbar: Toolbar by bindView(R.id.overlay_toolbar) + val frostWeb: FrostWebView by bindView(R.id.overlay_frost_webview) + val coordinator: CoordinatorLayout by bindView(R.id.overlay_main_content) + + val urlTest: String? + get() = intent.extras?.getString(ARG_URL) ?: intent.dataString + + open val url: String + get() = (intent.extras?.getString(ARG_URL) ?: intent.dataString).formattedFbUrl + + val userId: Long + get() = intent.extras?.getLong(ARG_USER_ID, Prefs.userId) ?: Prefs.userId + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (urlTest == null) { + L.eThrow("Empty link on web overlay") + toast(R.string.null_url_overlay) + finish() + return + } + setContentView(R.layout.activity_web_overlay) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayShowHomeEnabled(true) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + toolbar.navigationIcon = GoogleMaterial.Icon.gmd_close.toDrawable(this, 16, Prefs.iconColor) + toolbar.setNavigationOnClickListener { finishSlideOut() } + kauSwipeOnCreate { + if (!Prefs.overlayFullScreenSwipe) edgeSize = 20.dpToPx + transitionSystemBars = false + } + setFrostColors(toolbar, themeWindow = false) + coordinator.setBackgroundColor(Prefs.bgColor.withAlpha(255)) + + frostWeb.setupWebview(url) + frostWeb.web.addTitleListener({ toolbar.title = it }) + if (userId != Prefs.userId) FbCookie.switchUser(userId) { frostWeb.web.loadBaseUrl() } + else frostWeb.web.loadBaseUrl() + if (Showcase.firstWebOverlay) { + coordinator.frostSnackbar(R.string.web_overlay_swipe_hint) { + duration = Snackbar.LENGTH_INDEFINITE + setAction(R.string.kau_got_it) { _ -> this.dismiss() } + } + } + } + + /** + * Manage url loadings + * This is usually only called when multiple listeners are added and inject the same url + * We will avoid reloading if the url is the same + */ + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + val newUrl = (intent.extras?.getString(ARG_URL) ?: intent.dataString ?: return).formattedFbUrl + L.d("New intent") + if (url != newUrl) { + this.intent = intent + frostWeb.web.baseUrl = newUrl + frostWeb.web.loadBaseUrl() + } + } + + /** + * Our theme for the overlay should be fully opaque + */ + fun theme() { + val opaqueAccent = Prefs.headerColor.withAlpha(255) + statusBarColor = opaqueAccent.darken() + navigationBarColor = opaqueAccent + toolbar.setBackgroundColor(opaqueAccent) + toolbar.setTitleTextColor(Prefs.iconColor) + coordinator.setBackgroundColor(Prefs.bgColor.withAlpha(255)) + toolbar.overflowIcon?.setTint(Prefs.iconColor) + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + kauSwipeOnPostCreate() + } + + override fun onDestroy() { + super.onDestroy() + kauSwipeOnDestroy() + } + + override fun onBackPressed() { + if (!frostWeb.onBackPressed()) { + finishSlideOut() + } + } + + override fun openFileChooser(filePathCallback: ValueCallback>, fileChooserParams: WebChromeClient.FileChooserParams) { + openFileChooser(this, filePathCallback, fileChooserParams) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (onActivityResultWeb(requestCode, resultCode, data)) return + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + kauOnRequestPermissionsResult(permissions, grantResults) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_web, menu) + toolbar.tint(Prefs.iconColor) + setMenuIcons(menu, Prefs.iconColor, + R.id.action_share to CommunityMaterial.Icon.cmd_share, + R.id.action_copy_link to GoogleMaterial.Icon.gmd_content_copy) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_copy_link -> copyToClipboard(frostWeb.web.url) + R.id.action_share -> shareText(frostWeb.web.url) + else -> return super.onOptionsItemSelected(item) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt index e5216881..4d5127c5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt @@ -8,6 +8,6 @@ const val FACEBOOK_COM = "facebook.com" const val FB_URL_BASE = "https://m.facebook.com/" fun PROFILE_PICTURE_URL(id: Long) = "https://graph.facebook.com/$id/picture?type=large" -const val USER_AGENT_BASIC_FULL = "Mozilla/5.0 (Linux; Android 4.4.2; en-us; SAMSUNG SM-G900T Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36" +const val USER_AGENT_FULL = "Mozilla/5.0 (Linux; Android 4.4.2; en-us; SAMSUNG SM-G900T Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36" const val USER_AGENT_BASIC = "Mozilla/5.0 (Linux; U; Android 2.3.3; en-gb; Nexus S Build/GRI20) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" const val USER_AGENT_MESSENGER = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36" \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt index e53bb202..622be067 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt @@ -14,10 +14,10 @@ class FbUrlFormatter(url: String) { init { var cleanedUrl = url - discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "") } + discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "", true) } val changed = cleanedUrl != url //note that discardables strip away the first ? - decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v) } - val qm = cleanedUrl.indexOf(if (changed)"&" else "?") + decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v, true) } + val qm = cleanedUrl.indexOf(if (changed) "&" else "?") if (qm > -1) { cleanedUrl.substring(qm + 1).split("&").forEach { val p = it.split("=") @@ -72,7 +72,15 @@ class FbUrlFormatter(url: String) { "%60" to "`", "%3B" to ";", "%2F" to "/", "%3F" to "?", "%3A" to ":", "%40" to "@", "%3D" to "=", "%26" to "&", "%24" to "$", "%2B" to "+", "%22" to "\"", "%2C" to ",", - "%20" to " " + "%20" to " ", + //css + "\\3C " to "<", "\\3E " to ">", "\\23 " to "#", "\\25 " to "%", + "\\7B " to "{", "\\7D " to "}", "\\7C " to "|", "\\5C " to "\\", + "\\5E " to "^", "\\7E " to "~", "\\5B " to "[", "\\5D " to "]", + "\\60 " to "`", "\\3B " to ";", "\\2F " to "/", "\\3F " to "?", + "\\3A " to ":", "\\40 " to "@", "\\3D " to "=", "\\26 " to "&", + "\\24 " to "$", "\\2B " to "+", "\\22 " to "\"", "\\2C " to ",", + "\\20 " to " " ) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt index 87afc434..239f5842 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt @@ -7,7 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import ca.allanwang.kau.utils.withArguments -import com.pitchedapps.frost.MainActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.FeedSort import com.pitchedapps.frost.utils.Prefs diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index 7b60a718..737b86ab 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -17,7 +17,7 @@ import ca.allanwang.kau.utils.string import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.pitchedapps.frost.BuildConfig -import com.pitchedapps.frost.FrostWebActivity +import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.R import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.fetchUsername diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt index e05d0dd3..e814e1ac 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt @@ -5,9 +5,9 @@ import ca.allanwang.kau.kpref.activity.items.KPrefColorPicker import ca.allanwang.kau.kpref.activity.items.KPrefSeekbar import ca.allanwang.kau.ui.views.RippleCanvas import ca.allanwang.kau.utils.string -import com.pitchedapps.frost.MainActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.injectors.CssAssets import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.IS_FROST_PRO diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt index 7ce546b3..e7cf3598 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt @@ -1,9 +1,9 @@ package com.pitchedapps.frost.settings import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder -import com.pitchedapps.frost.MainActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.utils.Prefs /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index 86bd356b..236d19f9 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -1,9 +1,9 @@ package com.pitchedapps.frost.settings import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder -import com.pitchedapps.frost.MainActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.Showcase @@ -20,10 +20,14 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { descRes = R.string.experimental_by_default_desc } + // Experimental content starts here ------------------ + checkbox(R.string.search_bar, { Prefs.searchBar }, { Prefs.searchBar = it; setResult(MainActivity.REQUEST_SEARCH) }) { descRes = R.string.search_bar_desc } + // Experimental content ends here -------------------- + checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { Prefs.verboseLogging = it }) { descRes = R.string.verbose_logging_desc } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt index 29256950..689e6bdb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt @@ -2,9 +2,9 @@ package com.pitchedapps.frost.settings import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.utils.string -import com.pitchedapps.frost.MainActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.facebook.FeedSort import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.materialDialogThemed diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt index b6105054..59ebf700 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -4,7 +4,7 @@ import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder import ca.allanwang.kau.utils.minuteToText import ca.allanwang.kau.utils.snackbar import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.services.fetchNotifications import com.pitchedapps.frost.services.scheduleNotifications import com.pitchedapps.frost.utils.Prefs diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt index d2e04898..f8c7af56 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -4,6 +4,7 @@ import ca.allanwang.kau.kotlin.lazyResettable import ca.allanwang.kau.kpref.KPref import ca.allanwang.kau.kpref.StringSet import ca.allanwang.kau.kpref.kpref +import ca.allanwang.kau.utils.colorToForeground import ca.allanwang.kau.utils.isColorVisibleOn import com.pitchedapps.frost.facebook.FeedSort import com.pitchedapps.frost.injectors.InjectorContract @@ -57,9 +58,24 @@ object Prefs : KPref() { val iconColor: Int get() = t.iconColor + /** + * Ensures that the color is visible against the background + */ val accentColor: Int get() = if (headerColor.isColorVisibleOn(bgColor, 100)) headerColor else textColor + /** + * Ensures that the color is visible against both the foreground and background + */ + val accentBackgroundColor: Int + get() { + if (headerColor.isColorVisibleOn(textColor, 100)) { + if (headerColor.isColorVisibleOn(bgColor, 100)) return headerColor + else return headerColor.colorToForeground(0.2f) + } + return bgColor.colorToForeground(0.2f) + } + val themeInjector: InjectorContract get() = t.injector diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 9a599945..c0db8308 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -8,19 +8,27 @@ import android.support.annotation.StringRes import android.support.design.internal.SnackbarContentLayout import android.support.design.widget.Snackbar import android.support.v7.widget.Toolbar +import android.util.Log import android.view.View import android.widget.FrameLayout import android.widget.TextView import ca.allanwang.kau.utils.* import com.afollestad.materialdialogs.MaterialDialog +import com.bumptech.glide.GlideBuilder import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.annotation.GlideExtension import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.request.RequestOptions import com.crashlytics.android.answers.Answers import com.crashlytics.android.answers.CustomEvent -import com.pitchedapps.frost.* +import com.pitchedapps.frost.BuildConfig +import com.pitchedapps.frost.R +import com.pitchedapps.frost.activities.ImageActivity +import com.pitchedapps.frost.activities.LoginActivity +import com.pitchedapps.frost.activities.SelectorActivity +import com.pitchedapps.frost.activities.WebOverlayActivity import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.formattedFbUrl @@ -31,6 +39,8 @@ import com.pitchedapps.frost.facebook.formattedFbUrl const val EXTRA_COOKIES = "extra_cookies" const val ARG_URL = "arg_url" const val ARG_USER_ID = "arg_user_id" +const val ARG_IMAGE_URL = "arg_image_url" +const val ARG_TEXT = "arg_text" @GlideModule class FrostGlideModule : AppGlideModule() @@ -59,6 +69,13 @@ fun Context.launchWebOverlay(url: String) { }) } +fun Context.launchImageActivity(imageUrl: String, text: String?) { + startActivity(ImageActivity::class.java, intentBuilder = { + putExtra(ARG_IMAGE_URL, imageUrl) + putExtra(ARG_TEXT, text) + }) +} + fun WebOverlayActivity.url(): String { return intent.extras?.getString(ARG_URL) ?: FbTab.FEED.url } @@ -131,4 +148,4 @@ fun Activity.frostNavigationBar() { navigationBarColor = if (Prefs.tintNavBar) Prefs.headerColor else Color.BLACK } -fun RequestBuilder.withRoundIcon() = apply(RequestOptions().transform(CircleCrop())) +fun RequestBuilder.withRoundIcon() = apply(RequestOptions().transform(CircleCrop())) \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt index 67c20a5a..17ea46a3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/WebContextMenu.kt @@ -4,7 +4,8 @@ import android.content.Context import ca.allanwang.kau.utils.copyToClipboard import ca.allanwang.kau.utils.shareText import ca.allanwang.kau.utils.string -import com.pitchedapps.frost.MainActivity +import ca.allanwang.kau.utils.toast +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.R /** @@ -18,7 +19,10 @@ fun Context.showWebContextMenu(wc: WebContext) { materialDialogThemed { title(title) - items(WebContextType.values.map { this@showWebContextMenu.string(it.textId) }) + items(WebContextType.values.map { + if (it == WebContextType.COPY_TEXT && wc.text == null) return@map null + this@showWebContextMenu.string(it.textId) + }.filterNotNull()) itemsCallback { _, _, position, _ -> WebContextType[position].onClick(this@showWebContextMenu, wc) @@ -30,11 +34,11 @@ fun Context.showWebContextMenu(wc: WebContext) { } } -class WebContext(val url: String, val text: String) +class WebContext(val url: String, val text: String?) enum class WebContextType(val textId: Int, val onClick: (c: Context, wc: WebContext) -> Unit) { COPY_LINK(R.string.copy_link, { c, wc -> c.copyToClipboard(wc.url) }), - COPY_TEXT(R.string.copy_text, { c, wc -> c.copyToClipboard(wc.text) }), + COPY_TEXT(R.string.copy_text, { c, wc -> if (wc.text != null) c.copyToClipboard(wc.text) else c.toast(R.string.no_text) }), SHARE_LINK(R.string.share_link, { c, wc -> c.shareText(wc.url) }) ; diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt index 58c748c2..964e771c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IAB.kt @@ -9,7 +9,7 @@ import ca.allanwang.kau.utils.snackbar import com.crashlytics.android.answers.PurchaseEvent import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.utils.* /** diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt index 4f65b7f8..fae2f6bb 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt @@ -4,9 +4,9 @@ import android.app.Activity import ca.allanwang.kau.utils.restart import ca.allanwang.kau.utils.startPlayStoreLink import ca.allanwang.kau.utils.string -import com.pitchedapps.frost.MainActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.R -import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.materialDialogThemed diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt new file mode 100644 index 00000000..09241254 --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/BaseWebViewClient.kt @@ -0,0 +1,16 @@ +package com.pitchedapps.frost.web + +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient + +/** + * Created by Allan Wang on 2017-07-13. + */ +open class BaseWebViewClient : WebViewClient() { + + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? + = shouldFrostInterceptRequest(view, request) + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt index aab3a165..4df6d6a7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClient.kt @@ -1,9 +1,10 @@ package com.pitchedapps.frost.web import android.net.Uri -import android.os.Message -import android.view.View -import android.webkit.* +import android.webkit.ConsoleMessage +import android.webkit.ValueCallback +import android.webkit.WebChromeClient +import android.webkit.WebView import ca.allanwang.kau.utils.snackbar import com.pitchedapps.frost.contracts.ActivityWebContract import com.pitchedapps.frost.utils.L @@ -20,9 +21,16 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() { val titleObservable: BehaviorSubject = webCore.titleObservable val activityContract = (webCore.context as? ActivityWebContract) + companion object { + val consoleBlacklist = setOf( + "edge-chat" + ) + } + override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { + if (consoleBlacklist.any { consoleMessage.message().contains(it) }) return true L.i("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") - return super.onConsoleMessage(consoleMessage) + return true } override fun onReceivedTitle(view: WebView, title: String) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt index 3340e7d2..3f976fb8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt @@ -2,8 +2,9 @@ package com.pitchedapps.frost.web import android.content.Context import android.webkit.JavascriptInterface -import ca.allanwang.kau.logging.KL -import com.pitchedapps.frost.MainActivity +import ca.allanwang.kau.utils.startActivity +import com.pitchedapps.frost.activities.ImageActivity +import com.pitchedapps.frost.activities.MainActivity import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.utils.* @@ -13,12 +14,18 @@ import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-06-01. */ -class FrostJSI(val context: Context, val webView: FrostWebViewCore) { +class FrostJSI(val webView: FrostWebViewCore) { - val headerObservable: Subject? = (context as? MainActivity)?.headerBadgeObservable + val context: Context + get() = webView.context + + val activity: MainActivity? + get() = (context as? MainActivity) + + val headerObservable: Subject? = activity?.headerBadgeObservable val cookies: ArrayList - get() = (context as? MainActivity)?.cookies() ?: arrayListOf() + get() = activity?.cookies() ?: arrayListOf() @JavascriptInterface fun loadUrl(url: String) { @@ -35,8 +42,8 @@ class FrostJSI(val context: Context, val webView: FrostWebViewCore) { } @JavascriptInterface - fun contextMenu(url: String, text: String) { - webView.post { webView.context.showWebContextMenu(WebContext(url.formattedFbUrl, text)) } + fun contextMenu(url: String, text: String?) { + webView.post { context.showWebContextMenu(WebContext(url.formattedFbUrl, text)) } } /** @@ -45,7 +52,7 @@ class FrostJSI(val context: Context, val webView: FrostWebViewCore) { */ @JavascriptInterface fun longClick(start: Boolean) { - (webView.context as? MainActivity)?.viewPager?.enableSwipe = !start + activity?.viewPager?.enableSwipe = !start } @JavascriptInterface @@ -53,6 +60,14 @@ class FrostJSI(val context: Context, val webView: FrostWebViewCore) { context.launchLogin(cookies, true) } + /** + * Launch image overlay + */ + @JavascriptInterface + fun loadImage(imageUrl: String, text: String?) { + context.launchImageActivity(imageUrl, text) + } + @JavascriptInterface fun emit(flag: Int) { webView.post { webView.frostWebClient.emit(flag) } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt new file mode 100644 index 00000000..45dc83aa --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt @@ -0,0 +1,67 @@ +package com.pitchedapps.frost.web + +import android.graphics.Bitmap.CompressFormat +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import ca.allanwang.kau.utils.use +import com.pitchedapps.frost.utils.GlideApp +import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs +import okhttp3.HttpUrl +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream + + +/** + * Created by Allan Wang on 2017-07-13. + * + * Handler to decide when a request should be done by us + * This is the crux of Frost's optimizations for the web browser + */ +val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) } + +//these hosts will redirect to a blank resource +val blacklistHost: Set by lazy { + setOf( + "edge-chat.facebook.com" + ) +} + +//these hosts will return null and skip logging +val whitelistHost: Set by lazy { + setOf( + "static.xx.fbcdn.net", + "m.facebook.com", + "touch.facebook.com" + ) +} + +//these hosts will skip ad inspection +//this list does not have to include anything from the two above +val adWhitelistHost: Set by lazy { + setOf( + "scontent-sea1-1.xx.fbcdn.net" + ) +} + +var adblock: Set? = null + +fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + val httpUrl = HttpUrl.parse(request.url?.toString() ?: return null) ?: return null + val host = httpUrl.host() + val url = httpUrl.toString() + if (blacklistHost.contains(host)) return blankResource + if (whitelistHost.contains(host)) return null + if (!adWhitelistHost.contains(host)) { + if (adblock == null) adblock = view.context.assets.open("adblock.txt").bufferedReader().use { it.readLines().toSet() } + if (adblock?.any { url.contains(it) } ?: false) return blankResource + } + L.v("Intercept Request ${host} ${url}") + return null +} + +fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse? + = this ?: if (request.url.path.endsWith(".css")) blankResource else null + diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt index 7c0a6597..e7dae22a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt @@ -69,7 +69,7 @@ class FrostWebView @JvmOverloads constructor( frostWebClient = baseEnum?.webClient?.invoke(this) ?: FrostWebViewClient(this) webViewClient = frostWebClient webChromeClient = FrostChromeClient(this) - addJavascriptInterface(FrostJSI(context, this), "Frost") + addJavascriptInterface(FrostJSI(this), "Frost") setBackgroundColor(Color.TRANSPARENT) } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt index 5e5dc597..5b2b4bfd 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt @@ -4,10 +4,9 @@ import android.content.Context import android.graphics.Bitmap import android.webkit.WebResourceRequest import android.webkit.WebView -import android.webkit.WebViewClient -import com.pitchedapps.frost.LoginActivity -import com.pitchedapps.frost.MainActivity -import com.pitchedapps.frost.SelectorActivity +import com.pitchedapps.frost.activities.LoginActivity +import com.pitchedapps.frost.activities.MainActivity +import com.pitchedapps.frost.activities.SelectorActivity import com.pitchedapps.frost.facebook.FACEBOOK_COM import com.pitchedapps.frost.facebook.FbCookie import com.pitchedapps.frost.injectors.* @@ -17,7 +16,7 @@ import io.reactivex.subjects.Subject /** * Created by Allan Wang on 2017-05-31. */ -open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() { +open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() { val refreshObservable: Subject = webCore.refreshObservable @@ -97,15 +96,9 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() { return super.shouldOverrideUrlLoading(view, request) } - override fun onPageCommitVisible(view: WebView?, url: String?) { - L.d("ASDF PCV") - super.onPageCommitVisible(view, url) - } - -// override fun onLoadResource(view: WebView, url: String) { -// L.v("Load resource $url") -// super.onLoadResource(view, url) +// override fun onPageCommitVisible(view: WebView?, url: String?) { +// L.d("ASDF PCV") +// super.onPageCommitVisible(view, url) // } - } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt index 1e023dca..d96fba55 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt @@ -72,7 +72,7 @@ class FrostWebViewCore @JvmOverloads constructor( dispose = refreshObservable.subscribeOn(AndroidSchedulers.mainThread()).subscribe { if (it) { loading = true - if (isVisible()) fadeOut(duration = 200L) + if (isVisible) fadeOut(duration = 200L) } else if (loading) { dispose?.dispose() if (animate && Prefs.animate) circularReveal(offset = 150L) @@ -148,11 +148,12 @@ class FrostWebViewCore @JvmOverloads constructor( if (scrollY > 10000) { scrollTo(0, 0) } else { - val animator = ValueAnimator.ofInt(scrollY, 0) - animator.duration = Math.min(scrollY, 500).toLong() - animator.interpolator = DecelerateInterpolator() - animator.addUpdateListener { scrollY = it.animatedValue as Int } - animator.start() + ValueAnimator.ofInt(scrollY, 0).apply { + duration = Math.min(scrollY, 500).toLong() + interpolator = DecelerateInterpolator() + addUpdateListener { scrollY = it.animatedValue as Int } + start() + } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt index b3b1cfe5..bcadf32a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewSearch.kt @@ -3,9 +3,7 @@ package com.pitchedapps.frost.web import android.annotation.SuppressLint import android.content.Context import android.view.View -import android.webkit.JavascriptInterface -import android.webkit.WebView -import android.webkit.WebViewClient +import android.webkit.* import ca.allanwang.kau.searchview.SearchItem import ca.allanwang.kau.utils.gone import com.pitchedapps.frost.facebook.FbTab @@ -56,7 +54,8 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi settings.javaScriptEnabled = true settings.userAgentString = USER_AGENT_BASIC setLayerType(View.LAYER_TYPE_HARDWARE, null) - webViewClient = FrostWebViewClientSearch() + webViewClient = SearchWebViewClient() + webChromeClient = SearchChromeClient() addJavascriptInterface(SearchJSI(), "Frost") searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread()) .map { @@ -111,13 +110,22 @@ class FrostWebViewSearch(context: Context, val contract: SearchContract) : WebVi * * Barebones client that does what [FrostWebViewSearch] needs */ - inner class FrostWebViewClientSearch : WebViewClient() { + inner class SearchWebViewClient : BaseWebViewClient() { override fun onPageFinished(view: WebView, url: String) { super.onPageFinished(view, url) L.i("Search Page finished $url") view.jsInject(JsAssets.SEARCH) } + + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? + = super.shouldInterceptRequest(view, request).filterCss(request) + } + + class SearchChromeClient : WebChromeClient() { + + //mute console + override fun onConsoleMessage(consoleMessage: ConsoleMessage) = true } inner class SearchJSI { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index 38d4ad8c..d7a2db0a 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -66,7 +66,7 @@ class LoginWebView @JvmOverloads constructor( } - inner class LoginClient : WebViewClient() { + inner class LoginClient : BaseWebViewClient() { override fun onPageFinished(view: WebView, url: String) { super.onPageFinished(view, url) @@ -88,7 +88,7 @@ class LoginWebView @JvmOverloads constructor( inner class LoginChromeClient : WebChromeClient() { override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { L.d("Login Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}") - return super.onConsoleMessage(consoleMessage) + return true } override fun onProgressChanged(view: WebView, newProgress: Int) { diff --git a/app/src/main/res/drawable/frost_f_24.xml b/app/src/main/res/drawable/frost_f_24.xml index b435fa03..18271849 100644 --- a/app/src/main/res/drawable/frost_f_24.xml +++ b/app/src/main/res/drawable/frost_f_24.xml @@ -8,7 +8,7 @@ \ No newline at end of file diff --git a/app/src/main/res/drawable/frost_f_256.xml b/app/src/main/res/drawable/frost_f_256.xml index fce1b5ca..220dee69 100644 --- a/app/src/main/res/drawable/frost_f_256.xml +++ b/app/src/main/res/drawable/frost_f_256.xml @@ -8,7 +8,7 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml new file mode 100644 index 00000000..a2264b25 --- /dev/null +++ b/app/src/main/res/layout/activity_image.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_image_textless.xml b/app/src/main/res/layout/activity_image_textless.xml new file mode 100644 index 00000000..3c0cc685 --- /dev/null +++ b/app/src/main/res/layout/activity_image_textless.xml @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index eef0e434..cd5eef08 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -6,7 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" - tools:context=".LoginActivity"> + tools:context=".activities.LoginActivity"> + tools:context=".activities.MainActivity"> + tools:context=".activities.WebOverlayActivity"> Frost Pro has been restored. Enjoy the features! It seems like you don\'t have pro. If this is a persistent issue, contact me and attach your purchase receipt. - - - Raizlabs - https://www.raizlabs.com/ - - DbFlow - - annotation processing. -

- The library is built on speed, performance, and approachability. It not only eliminates most boiler-plate code for dealing with databases, but also provides a powerful and simple API to manage interactions. - Let DBFlow make SQL code flow like a steady stream so you can focus on writing amazing apps. - ]]> -
- https://github.com/Raizlabs/DBFlow - 4.0.4 - - true - https://github.com/Raizlabs/DBFlow - - com.raizlabs.android.dbflow - - mit Login failed; id not found IAB query is still in progress New Message + No text + Image downloaded + Image failed to download + Failed to share image diff --git a/app/src/main/res/values/strings_libs.xml b/app/src/main/res/values/strings_libs.xml new file mode 100644 index 00000000..9cbb77d7 --- /dev/null +++ b/app/src/main/res/values/strings_libs.xml @@ -0,0 +1,26 @@ + + + + + Raizlabs + https://www.raizlabs.com/ + + DbFlow + + annotation processing. +

+ The library is built on speed, performance, and approachability. It not only eliminates most boiler-plate code for dealing with databases, but also provides a powerful and simple API to manage interactions. + Let DBFlow make SQL code flow like a steady stream so you can focus on writing amazing apps. + ]]> +
+ https://github.com/Raizlabs/DBFlow + 4.0.4 + + true + https://github.com/Raizlabs/DBFlow + + com.raizlabs.android.dbflow + + mit +
\ No newline at end of file diff --git a/app/src/main/res/values/strings_pref_experimental.xml b/app/src/main/res/values/strings_pref_experimental.xml index 2d49864b..0f545b3e 100644 --- a/app/src/main/res/values/strings_pref_experimental.xml +++ b/app/src/main/res/values/strings_pref_experimental.xml @@ -11,5 +11,4 @@ Enable verbose logging to help with crash reports. Logging will only be sent once an error is encountered, so repeat the issue to notify the dev. This will automatically be disabled if the app restarts. Restart Frost Crashlytics will only submit logs when a crash occurs or if errors are found and the app is restarted. Clicking here will restart the app and flush whatever issues are currently found. - \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 217bfb28..a483b5f0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -31,9 +31,16 @@ + + + + diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index 59baeef5..cf56eaeb 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -9,12 +9,13 @@ --> - - - - - - + + + + + + + @@ -25,6 +26,12 @@ + + + + + + diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..334ea0d7 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/releaseTest/res/xml/file_paths.xml b/app/src/releaseTest/res/xml/file_paths.xml new file mode 100644 index 00000000..139a1972 --- /dev/null +++ b/app/src/releaseTest/res/xml/file_paths.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt index d7ec4b46..080132bd 100644 --- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt +++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt @@ -1,5 +1,6 @@ package com.pitchedapps.frost.facebook +import okhttp3.HttpUrl import org.junit.Test import kotlin.test.assertEquals @@ -9,6 +10,11 @@ import kotlin.test.assertEquals */ class FbUrlTest { + fun assertFbFormat(expected: String, url: String) { + val fbUrl = FbUrlFormatter(url) + assertEquals(expected, fbUrl.toString(), "FbUrl Mismatch:\n${fbUrl.toLogList().joinToString("\n\t")}") + } + @Test fun base() { val url = "https://touch.facebook.com/relative/?asdf=1234&hjkl=7890" @@ -32,8 +38,10 @@ class FbUrlTest { assertFbFormat("$prefix$suffix", "$prefix&ref=hello$suffix") } - fun assertFbFormat(expected: String, url: String) { - val fbUrl = FbUrlFormatter(url) - assertEquals(expected, fbUrl.toString(), "FbUrl Mismatch:\n${fbUrl.toLogList().joinToString("\n\t")}") + @Test fun css() { + val expected = "https://test.com?efg=hi&oh=bye&oe=apple" + val orig = "https\\3a //test.com?efg\\3d hi\\26 oh\\3d bye\\26 oe\\3d apple" + assertFbFormat(expected, orig) } + } \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index f8d3308f..79f7d692 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,12 +1,8 @@ # Changelog ## Beta Updates -* Reduce Menu loading logic -* Load js injectors after showing webview -* Add toggles for sound, vibration, and lights -* Avoid restricting facebook features (such as user tagging) -* Slightly decrease wait time before making webview visible -* Add option to disable loading in overlays +* Update IAB helper +* Implemented image viewing and downloading; long press any image! ## v1.3 * Create toggle for notifications only from primary account @@ -15,6 +11,12 @@ * Make notifications expandable * Add notification trigger in settings * Fix bug where only single latest notification is showing +* Reduce Menu loading logic +* Load js injectors after showing webview +* Add toggles for sound, vibration, and lights +* Avoid restricting facebook features (such as user tagging) +* Add option to disable loading in overlays +* Fixed experimental search result text ## v1.2 * Scale browser on keyboard pop up diff --git a/gradle.properties b/gradle.properties index 8dc22a88..7ac04b38 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,16 +17,19 @@ MIN_SDK=21 TARGET_SDK=26 BUILD_TOOLS=26.0.0 -KAU=v2.0 +KAU=e6daff7 KOTLIN=1.1.3-2 -MATERIAL_DRAWER=5.9.4 -MATERIAL_DRAWER_KT=1.0.5 -IICON_MATERIAL=2.2.0.3 +CRASHLYTICS=2.6.8 +DBFLOW=4.0.4 +GLIDE=4.0.0-RC1 IICON_COMMUNITY=1.9.32.2 +IICON_MATERIAL=2.2.0.3 JSOUP=1.10.3 -GLIDE=4.0.0-RC1 -DBFLOW=4.0.4 -PAPER_PARCEL=2.0.1 -CRASHLYTICS=2.6.8 LEAK_CANARY=1.5.1 -ROBOELECTRIC=3.4-rc3 \ No newline at end of file +MATERIAL_DRAWER=5.9.4 +MATERIAL_DRAWER_KT=1.0.5 +OKHTTP=3.8.1 +PAPER_PARCEL=2.0.1 +ROBOELECTRIC=3.4-rc3 +SCALE_IMAGE_VIEW=3.6.0 +SLIDING_PANEL=3.3.1 \ No newline at end of file -- cgit v1.2.3