aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-08-14 20:48:39 -0700
committerGitHub <noreply@github.com>2017-08-14 20:48:39 -0700
commit5d9a3fd7fb8f2f9d0f592c89446824980c9841c6 (patch)
treea770b2564b67280fcc9fcc65144bd0b8bd8e2881 /app/src/main/kotlin
parentab7ec131b62ac1567e983c846c921bd3ada11dd4 (diff)
downloadfrost-5d9a3fd7fb8f2f9d0f592c89446824980c9841c6.tar.gz
frost-5d9a3fd7fb8f2f9d0f592c89446824980c9841c6.tar.bz2
frost-5d9a3fd7fb8f2f9d0f592c89446824980c9841c6.zip
v1.4.5 (#174)v1.4.5
* Update/kau (#125) * Update logger * Clean imports and bring back reactive libs * Update dependencies and make billing async * Misc (#128) * Update null * Attempt to improve transparent theme backgrounds * Update menu * Move injections to visible method and reduce offset * Update searchview and logging * Clean temp strings and add network states * Move console blacklist to web state * Change some logs to info * Move glide loader to onCreate (#135) * Remove commit number increments (#139) * Fix/misc (#140) * Add canadian locale to toLowerCase * Add try catch to JsAssets * Disable error throwing for bad search subject * Log more throwables quietly * Check internet connection before fetching username * Remove name check in frost notifications * Add activity lifecycle logger * Add rxjava to lib showcase * Move network checker to io thread (#150) * Update dependency * Blank * Feature/jsoup debugger (#152) * Create debugger * Update debugger content * Create debugging logic * Finalize and test debugger * Add reload listener * Fix/pro crash without play store (#155) * Update changelog * Check if iab service exists * Add checker before launching play store request * Separate strings * Enhancement/message notifications (#157) * Map message notifs to the headless html extractor * Update strings * Bring im notifs out of alpha * Update changelog * Remove confirmation dialog (#159) * Separate message notifications and add click intents (#171) * Separate message notifications and add click intent for group notifications * Add comments and finalize * Feature/scroll down on message thread (#172) * Add hook for scroll * Update changelog * Add custom navdrawer layout (#173) * Add faq for auto play * Update changelog * Fix page banner bg (#163)
Diffstat (limited to 'app/src/main/kotlin')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt34
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt32
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt43
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt19
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt65
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt23
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt16
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt8
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt (renamed from app/src/main/kotlin/com/pitchedapps/frost/facebook/FbTab.kt)4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt50
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt14
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt76
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt105
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt151
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt14
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt17
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt34
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt31
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt17
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt102
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt29
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt82
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt88
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt67
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt33
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt11
42 files changed, 862 insertions, 398 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
index 371f9c33..4fabf8b8 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -1,9 +1,12 @@
package com.pitchedapps.frost
+import android.app.Activity
import android.app.Application
import android.graphics.drawable.Drawable
import android.net.Uri
+import android.os.Bundle
import android.widget.ImageView
+import ca.allanwang.kau.logging.KL
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ApplicationVersionSignature
@@ -12,13 +15,12 @@ import com.crashlytics.android.answers.Answers
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.pitchedapps.frost.facebook.FbCookie
-import com.pitchedapps.frost.utils.CrashReportingTree
+import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.Showcase
import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager
import io.fabric.sdk.android.Fabric
-import timber.log.Timber
import java.util.*
@@ -39,22 +41,18 @@ class FrostApp : Application() {
Prefs.initialize(this, "${BuildConfig.APPLICATION_ID}.prefs")
// if (LeakCanary.isInAnalyzerProcess(this)) return
// refWatcher = LeakCanary.install(this)
- if (BuildConfig.DEBUG) {
- Timber.plant(Timber.DebugTree())
-// LeakCanary.enableDisplayLeakActivity(this)
- } else {
+ if (!BuildConfig.DEBUG) {
Fabric.with(this, Crashlytics(), Answers())
Crashlytics.setUserIdentifier(Prefs.frostId)
- Timber.plant(CrashReportingTree())
}
+ KL.debug(BuildConfig.DEBUG)
+ L.debug(BuildConfig.DEBUG)
Prefs.verboseLogging = false
FbCookie()
if (Prefs.installDate == -1L) Prefs.installDate = System.currentTimeMillis()
if (Prefs.identifier == -1) Prefs.identifier = Random().nextInt(Int.MAX_VALUE)
Prefs.lastLaunch = System.currentTimeMillis()
-
-
super.onCreate()
/**
@@ -69,6 +67,24 @@ class FrostApp : Application() {
.thumbnail(old).into(imageView)
}
})
+ if (BuildConfig.DEBUG)
+ registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
+ override fun onActivityPaused(activity: Activity) {}
+ override fun onActivityResumed(activity: Activity) {}
+ override fun onActivityStarted(activity: Activity) {}
+
+ override fun onActivityDestroyed(activity: Activity) {
+ L.d("Activity ${activity.localClassName} destroyed")
+ }
+
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {}
+
+ override fun onActivityStopped(activity: Activity) {}
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ L.d("Activity ${activity.localClassName} created")
+ }
+ })
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
index fbcd12cc..670e8669 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/AboutActivity.kt
@@ -1,10 +1,8 @@
package com.pitchedapps.frost.activities
-import android.os.Bundle
import android.support.constraint.ConstraintLayout
import android.support.constraint.ConstraintSet
import android.support.v7.widget.RecyclerView
-import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@@ -13,8 +11,6 @@ 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.animators.FadeScaleAnimatorAdd
-import ca.allanwang.kau.animators.KauAnimator
import ca.allanwang.kau.utils.*
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library
@@ -26,10 +22,8 @@ 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.L
import com.pitchedapps.frost.utils.Prefs
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
-import java.security.InvalidParameterException
/**
@@ -41,6 +35,7 @@ class AboutActivity : AboutActivityBase(null, {
backgroundColor = Prefs.bgColor.withMinAlpha(200)
cutoutForeground = if (0xff3b5998.toInt().isColorVisibleOn(Prefs.bgColor)) 0xff3b5998.toInt() else Prefs.accentColor
cutoutDrawableRes = R.drawable.frost_f_256
+ faqPageTitleRes = R.string.faq_title
faqXmlRes = R.xml.frost_faq
faqParseNewLine = false
}) {
@@ -60,6 +55,7 @@ class AboutActivity : AboutActivityBase(null, {
"kotterknife",
"materialdialogs",
"materialdrawer",
+ "rxjava",
"subsamplingscaleimageview"
)
@@ -68,6 +64,9 @@ class AboutActivity : AboutActivityBase(null, {
return l
}
+ var lastClick = -1L
+ var clickCount = 0
+
override fun postInflateMainPage(adapter: FastItemThemedAdapter<IItem<*, *>>) {
/**
* Frost may not be a library but we're conveying the same info
@@ -85,7 +84,22 @@ class AboutActivity : AboutActivityBase(null, {
}
}
adapter.add(LibraryIItem(frost)).add(AboutLinks())
-
+ adapter.withOnClickListener { _, _, item, _ ->
+ if (item is LibraryIItem) {
+ val now = System.currentTimeMillis()
+ if (now - lastClick > 500)
+ clickCount = 0
+ else
+ clickCount++
+ lastClick = now
+ if (clickCount == 7 && !Prefs.debugSettings) {
+ Prefs.debugSettings = true
+ L.d("Debugging section enabled")
+ toast(R.string.debug_toast_enabled)
+ }
+ }
+ false
+ }
}
class AboutLinks : AbstractItem<AboutLinks, AboutLinks.ViewHolder>(), ThemableIItem by ThemableIItemDelegate() {
@@ -128,7 +142,7 @@ class AboutActivity : AboutActivityBase(null, {
setImageDrawable(icon.toDrawable(context, 32))
scaleType = ImageView.ScaleType.CENTER
background = context.resolveDrawable(android.R.attr.selectableItemBackgroundBorderless)
- setOnClickListener({ onClick() })
+ setOnClickListener { onClick() }
container.addView(this)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
index 77a20d04..c7ca5ec7 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseActivity.kt
@@ -2,10 +2,16 @@ package com.pitchedapps.frost.activities
import android.os.Bundle
import ca.allanwang.kau.internal.KauBaseActivity
+import com.github.pwittchen.reactivenetwork.library.rx2.Connectivity
+import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.pitchedapps.frost.R
+import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.materialDialogThemed
import com.pitchedapps.frost.utils.setFrostTheme
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
/**
* Created by Allan Wang on 2017-06-12.
@@ -29,4 +35,41 @@ abstract class BaseActivity : KauBaseActivity() {
setFrostTheme()
}
+ private var networkDisposable: Disposable? = null
+ private var networkConsumer: ((Connectivity) -> Unit)? = null
+
+ fun setNetworkObserver(consumer: (connectivity: Connectivity) -> Unit) {
+ this.networkConsumer = consumer
+ }
+
+ fun observeNetworkConnectivity() {
+ val consumer = networkConsumer ?: return
+ networkDisposable = ReactiveNetwork.observeNetworkConnectivity(applicationContext)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ connectivity: Connectivity ->
+ connectivity.apply {
+ L.d("Network connectivity changed: isAvailable: $isAvailable isRoaming: $isRoaming")
+ consumer(connectivity)
+ }
+ }
+ }
+
+ fun disposeNetworkConnectivity() {
+ if (!(networkDisposable?.isDisposed ?: true))
+ networkDisposable?.dispose()
+ networkDisposable = null
+ }
+
+ override fun onResume() {
+ super.onResume()
+ disposeNetworkConnectivity()
+ observeNetworkConnectivity()
+ }
+
+ override fun onPause() {
+ super.onPause()
+ disposeNetworkConnectivity()
+ }
} \ 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
index 6a39b269..31479d54 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -7,7 +7,6 @@ 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.view.View
@@ -34,11 +33,8 @@ 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.
@@ -99,8 +95,8 @@ class ImageActivity : KauBaseActivity() {
})
fab.setOnClickListener { fabAction.onClick(this) }
photo.setOnImageEventListener(object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
- override fun onImageLoadError(e: Exception) {
- L.e(e, "Image load error")
+ override fun onImageLoadError(e: Exception?) {
+ e.logFrostAnswers("Image load error")
imageCallback(null, false)
}
})
@@ -155,7 +151,7 @@ class ImageActivity : KauBaseActivity() {
callback(null)
} else {
tempFilePath = photoFile.absolutePath
- Timber.d("Temp image path $tempFilePath")
+ L.d("Temp image path $tempFilePath")
// File created; proceed with request
val photoURI = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".provider",
@@ -252,7 +248,7 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
}
activity.startActivity(intent)
} catch (e: Exception) {
- L.e(e, "Image share failed");
+ e.logFrostAnswers("Image share failed")
activity.snackbar(R.string.image_share_failed)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
index 47c286fa..67f07635 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -12,6 +12,7 @@ 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.RequestManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
@@ -44,6 +45,7 @@ class LoginActivity : BaseActivity() {
val profileObservable = SingleSubject.create<Boolean>()
val usernameObservable = SingleSubject.create<String>()
+ lateinit var profileLoader: RequestManager
// Helper to set and enable swipeRefresh
var refresh: Boolean
@@ -68,6 +70,7 @@ class LoginActivity : BaseActivity() {
loadInfo(cookie)
})
}
+ profileLoader = Glide.with(profile)
}
fun loadInfo(cookie: CookieModel) {
@@ -78,8 +81,8 @@ class LoginActivity : BaseActivity() {
(foundImage, name) ->
refresh = false
if (!foundImage) {
- L.eThrow("Could not get profile photo; Invalid userId?")
- L.i("-\t$cookie")
+ L.e("Could not get profile photo; Invalid userId?")
+ L.i(null, cookie.toString())
}
textview.text = String.format(getString(R.string.welcome), name)
textview.fadeIn()
@@ -102,14 +105,14 @@ class LoginActivity : BaseActivity() {
fun loadProfile(id: Long) {
- Glide.with(profile).load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener<Drawable> {
+ profileLoader.load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
profileObservable.onSuccess(true)
return false
}
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
- if (e != null) L.e(e, "Profile loading exception")
+ e.logFrostAnswers( "Profile loading exception")
profileObservable.onSuccess(false)
return false
}
@@ -119,12 +122,4 @@ class LoginActivity : BaseActivity() {
fun loadUsername(cookie: CookieModel) {
cookie.fetchUsername { usernameObservable.onSuccess(it) }
}
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == 999) {
- L.d("Result found for activity with result $resultCode")
- L.d("Intent data ${data?.extras.toString()}")
- } else
- super.onActivityResult(requestCode, resultCode, data)
- }
} \ 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
index e8148b55..759be983 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/MainActivity.kt
@@ -50,7 +50,7 @@ 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.FbItem
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.utils.*
@@ -60,6 +60,7 @@ import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
import com.pitchedapps.frost.views.BadgedIcon
import com.pitchedapps.frost.views.FrostViewPager
import com.pitchedapps.frost.web.SearchWebView
+import com.pitchedapps.frost.web.shouldLoadImages
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
@@ -160,14 +161,10 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
// }
setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager))
onCreateBilling()
- if (Prefs.installDate < 1501454310304 && Showcase.intro)
- materialDialogThemed {
- title(R.string.intro_title)
- content(R.string.intro_desc)
- positiveText(R.string.kau_yes)
- negativeText(R.string.kau_no)
- onPositive { _, _ -> launchIntroActivity(cookies()) }
- }
+ setNetworkObserver {
+ connectivity ->
+ shouldLoadImages = !connectivity.isRoaming
+ }
}
fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) {
@@ -206,10 +203,10 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
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
+ FbItem.FEED.icon -> view.badgeText = feed
+ FbItem.FRIENDS.icon -> view.badgeText = requests
+ FbItem.MESSAGES.icon -> view.badgeText = messages
+ FbItem.NOTIFICATIONS.icon -> view.badgeText = notifications
}
}
}
@@ -230,10 +227,10 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
translucentStatusBar = false
sliderBackgroundColor = navBg
drawerHeader = accountHeader {
+ customViewRes = R.layout.material_drawer_header
textColor = Prefs.iconColor.toLong()
backgroundDrawable = ColorDrawable(navHeader)
selectionSecondLineShown = false
- paddingBelow = false
cookies().forEach { (id, name) ->
profile(name = name ?: "") {
iconUrl = PROFILE_PICTURE_URL(id)
@@ -261,7 +258,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
identifier = -4L
}
onProfileChanged { _, profile, current ->
- if (current) launchWebOverlay(FbTab.PROFILE.url)
+ if (current) launchWebOverlay(FbItem.PROFILE.url)
else when (profile.identifier) {
-2L -> {
val currentCookie = loadFbCookie(Prefs.userId)
@@ -295,25 +292,25 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
}
}
drawerHeader.setActiveProfile(Prefs.userId)
- primaryFrostItem(FbTab.FEED_MOST_RECENT)
- primaryFrostItem(FbTab.FEED_TOP_STORIES)
- primaryFrostItem(FbTab.ACTIVITY_LOG)
+ primaryFrostItem(FbItem.FEED_MOST_RECENT)
+ primaryFrostItem(FbItem.FEED_TOP_STORIES)
+ primaryFrostItem(FbItem.ACTIVITY_LOG)
divider()
- primaryFrostItem(FbTab.PHOTOS)
- primaryFrostItem(FbTab.GROUPS)
- primaryFrostItem(FbTab.FRIENDS)
- primaryFrostItem(FbTab.PAGES)
+ primaryFrostItem(FbItem.PHOTOS)
+ primaryFrostItem(FbItem.GROUPS)
+ primaryFrostItem(FbItem.FRIENDS)
+ primaryFrostItem(FbItem.PAGES)
divider()
- primaryFrostItem(FbTab.EVENTS)
- primaryFrostItem(FbTab.BIRTHDAYS)
- primaryFrostItem(FbTab.ON_THIS_DAY)
+ primaryFrostItem(FbItem.EVENTS)
+ primaryFrostItem(FbItem.BIRTHDAYS)
+ primaryFrostItem(FbItem.ON_THIS_DAY)
divider()
- primaryFrostItem(FbTab.NOTES)
- primaryFrostItem(FbTab.SAVED)
+ primaryFrostItem(FbItem.NOTES)
+ primaryFrostItem(FbItem.SAVED)
}
}
- fun Builder.primaryFrostItem(item: FbTab) = this.primaryItem(item.titleId) {
+ fun Builder.primaryFrostItem(item: FbItem) = this.primaryItem(item.titleId) {
iicon = item.icon
iconColor = Prefs.textColor.toLong()
textColor = Prefs.textColor.toLong()
@@ -349,6 +346,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
override fun searchOverlayDispose() {
hiddenSearchView?.dispose()
hiddenSearchView = null
+ searchView?.unBind { launchWebOverlay(FbItem.SEARCH.url); true }
searchView = null
}
@@ -369,10 +367,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
if (Prefs.searchBar) {
if (firstLoadFinished && hiddenSearchView == null) hiddenSearchView = SearchWebView(this, this)
if (searchView == null) searchView = bindSearchView(menu, R.id.action_search, Prefs.iconColor) {
- textObserver = {
- observable, _ ->
- observable.observeOn(AndroidSchedulers.mainThread()).subscribe { hiddenSearchView?.query(it) }
- }
+ textCallback = { query, _ -> runOnUiThread { hiddenSearchView?.query(query) } }
foregroundColor = Prefs.textColor
backgroundColor = Prefs.bgColor.withMinAlpha(200)
openListener = { hiddenSearchView?.pauseLoad = false }
@@ -380,8 +375,8 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
onItemClick = { _, key, _, _ -> launchWebOverlay(key) }
}
} else {
- searchOverlayDispose()
- menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbTab.SEARCH.url); true }
+ if (searchView != null) searchOverlayDispose()
+ else menu.findItem(R.id.action_search).setOnMenuItemClickListener { _ -> launchWebOverlay(FbItem.SEARCH.url); true }
}
return true
}
@@ -461,7 +456,7 @@ class MainActivity : BaseActivity(), SearchWebView.SearchContract,
val currentFragment
get() = supportFragmentManager.findFragmentByTag("android:switcher:${R.id.container}:${viewPager.currentItem}") as WebFragment
- inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbTab>) : FragmentPagerAdapter(fm) {
+ inner class SectionsPagerAdapter(fm: FragmentManager, val pages: List<FbItem>) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
val fragment = WebFragment(pages[position], position)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
index 7cbbe4df..196aa461 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/SettingsActivity.kt
@@ -6,16 +6,12 @@ import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
-import ca.allanwang.kau.about.kauLaunchAbout
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.finishSlideOut
-import ca.allanwang.kau.utils.setMenuIcons
-import ca.allanwang.kau.utils.string
-import ca.allanwang.kau.utils.tint
+import ca.allanwang.kau.utils.*
import ca.allanwang.kau.xml.showChangelog
import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial
@@ -38,7 +34,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!onActivityResultBilling(requestCode, resultCode, data))
super.onActivityResult(requestCode, resultCode, data)
- adapter.notifyDataSetChanged()
+ reloadList()
}
override fun kPrefCoreAttributes(): CoreAttributeContract.() -> Unit = {
@@ -67,6 +63,11 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
iicon = GoogleMaterial.Icon.gmd_notifications
}
+ subItems(R.string.network, getNetworkPrefs()) {
+ descRes = R.string.network_desc
+ iicon = GoogleMaterial.Icon.gmd_network_cell
+ }
+
subItems(R.string.experimental, getExperimentalPrefs()) {
descRes = R.string.experimental_desc
iicon = CommunityMaterial.Icon.cmd_flask_outline
@@ -81,7 +82,7 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
plainText(R.string.about_frost) {
descRes = R.string.about_frost_desc
iicon = GoogleMaterial.Icon.gmd_info
- onClick = { _, _, _ -> kauLaunchAbout(AboutActivity::class.java); true }
+ onClick = { _, _, _ -> startActivityForResult(AboutActivity::class.java, 9, true); true }
}
plainText(R.string.replay_intro) {
@@ -89,6 +90,12 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
onClick = { _, _, _ -> launchIntroActivity(cookies()); true }
}
+ subItems(R.string.debug_frost, getDebugPrefs()) {
+ descRes = R.string.debug_frost_desc
+ iicon = CommunityMaterial.Icon.cmd_android_debug_bridge
+ visible = { Prefs.debugSettings }
+ }
+
if (BuildConfig.DEBUG) {
checkbox(R.string.custom_pro, { Prefs.debugPro }, { Prefs.debugPro = it })
}
@@ -130,8 +137,6 @@ class SettingsActivity : KPrefActivity(), FrostBilling by IABSettings() {
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)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt
index fd8a3677..f3d90bcc 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/contracts/FileChooser.kt
@@ -5,8 +5,6 @@ import android.content.Intent
import android.net.Uri
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
-import ca.allanwang.kau.mediapicker.MediaPickerActivityOverlayBase
-import ca.allanwang.kau.mediapicker.MediaType
import ca.allanwang.kau.mediapicker.kauLaunchMediaPicker
import ca.allanwang.kau.mediapicker.kauOnMediaPickerResult
import com.pitchedapps.frost.activities.ImagePickerActivity
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
index 901ba02d..92cdf503 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
@@ -2,16 +2,18 @@ package com.pitchedapps.frost.dbflow
import android.os.Parcel
import android.os.Parcelable
+import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.pitchedapps.frost.facebook.FACEBOOK_COM
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.logFrostAnswers
import com.raizlabs.android.dbflow.annotation.ConflictAction
import com.raizlabs.android.dbflow.annotation.Database
import com.raizlabs.android.dbflow.annotation.PrimaryKey
import com.raizlabs.android.dbflow.annotation.Table
import com.raizlabs.android.dbflow.kotlinextensions.*
import com.raizlabs.android.dbflow.structure.BaseModel
-import org.jetbrains.anko.doAsync
+import io.reactivex.schedulers.Schedulers
import org.jsoup.Jsoup
import paperparcel.PaperParcel
import java.net.UnknownHostException
@@ -64,20 +66,22 @@ fun removeCookie(id: Long) {
}
fun CookieModel.fetchUsername(callback: (String) -> Unit) {
- doAsync {
+ ReactiveNetwork.checkInternetConnectivity().subscribeOn(Schedulers.io()).subscribe {
+ yes, _ ->
+ if (!yes) return@subscribe callback("")
var result = ""
try {
- result = Jsoup.connect(FbTab.PROFILE.url)
+ result = Jsoup.connect(FbItem.PROFILE.url)
.cookie(FACEBOOK_COM, cookie)
.get().title()
L.d("Fetch username found", result)
} catch (e: Exception) {
if (e !is UnknownHostException)
- L.e(e, "Fetch username failed")
+ e.logFrostAnswers("Fetch username failed")
} finally {
if (result.isBlank() && (name?.isNotBlank() ?: false)) {
callback(name!!)
- return@doAsync
+ return@subscribe
}
if (name != result) {
name = result
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt
index 69c7f3d5..4b2d3403 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/FbTabsDb.kt
@@ -1,7 +1,7 @@
package com.pitchedapps.frost.dbflow
import android.content.Context
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.defaultTabs
import com.pitchedapps.frost.utils.L
import com.raizlabs.android.dbflow.annotation.Database
@@ -22,15 +22,15 @@ object FbTabsDb {
}
@Table(database = FbTabsDb::class, allFields = true)
-data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbTab = FbTab.FEED) : BaseModel()
+data class FbTabModel(@PrimaryKey var position: Int = -1, var tab: FbItem = FbItem.FEED) : BaseModel()
-fun loadFbTabs(): List<FbTab> {
+fun loadFbTabs(): List<FbItem> {
val tabs: List<FbTabModel>? = SQLite.select().from(FbTabModel::class).orderBy(FbTabModel_Table.position, true).queryList()
if (tabs?.isNotEmpty() ?: false) return tabs!!.map { it.tab }
L.d("No tabs; loading default")
return defaultTabs()
}
-fun List<FbTab>.saveAsync(c: Context) {
+fun List<FbItem>.saveAsync(c: Context) {
mapIndexed { index, fbTab -> FbTabModel(index, fbTab) }.replace(c, FbTabsDb.NAME)
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbTab.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt
index d1f0b046..a4736091 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbTab.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbItem.kt
@@ -10,7 +10,7 @@ import com.pitchedapps.frost.web.FrostWebViewClient
import com.pitchedapps.frost.web.FrostWebViewClientMenu
import com.pitchedapps.frost.web.FrostWebViewCore
-enum class FbTab(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: String, val webClient: ((webCore: FrostWebViewCore) -> FrostWebViewClient)? = null) {
+enum class FbItem(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: String, val webClient: ((webCore: FrostWebViewCore) -> FrostWebViewClient)? = null) {
ACTIVITY_LOG(R.string.activity_log, GoogleMaterial.Icon.gmd_list, "me/allactivity"),
BIRTHDAYS(R.string.birthdays, GoogleMaterial.Icon.gmd_cake, "events/birthdays"),
CHAT(R.string.chat, GoogleMaterial.Icon.gmd_chat, "buddylist"),
@@ -36,4 +36,4 @@ enum class FbTab(@StringRes val titleId: Int, val icon: IIcon, relativeUrl: Stri
val url = "$FB_URL_BASE$relativeUrl"
}
-fun defaultTabs(): List<FbTab> = listOf(FbTab.FEED, FbTab.MESSAGES, FbTab.NOTIFICATIONS, FbTab.MENU)
+fun defaultTabs(): List<FbItem> = listOf(FbItem.FEED, FbItem.MESSAGES, FbItem.NOTIFICATIONS, FbItem.MENU)
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 7cd93d14..69b2ba41 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
@@ -33,7 +33,7 @@ class FbUrlFormatter(url: String) {
if (cleanedUrl.startsWith("#!/")) cleanedUrl = cleanedUrl.substring(2)
if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1)
cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url
- L.v("Formatted url from $url to $cleanedUrl")
+ L.v(null, "Formatted url from $url to $cleanedUrl")
cleaned = cleanedUrl
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt
index dfdfa027..f2bcc328 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/UsernameFetcher.kt
@@ -3,6 +3,7 @@ package com.pitchedapps.frost.facebook
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.dbflow.saveFbCookie
import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.logFrostAnswers
import io.reactivex.subjects.SingleSubject
import org.jsoup.Jsoup
import kotlin.concurrent.thread
@@ -16,12 +17,12 @@ object UsernameFetcher {
thread {
var name = ""
try {
- name = Jsoup.connect(FbTab.PROFILE.url)
+ name = Jsoup.connect(FbItem.PROFILE.url)
.cookie(FACEBOOK_COM, data.cookie)
.get().title()
L.d("User name found", name)
} catch (e: Exception) {
- L.e(e, "User name fetching failed")
+ e.logFrostAnswers("User name fetching failed")
} finally {
data.name = name
saveFbCookie(data)
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 239f5842..920052f9 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragment.kt
@@ -8,7 +8,7 @@ import android.view.View
import android.view.ViewGroup
import ca.allanwang.kau.utils.withArguments
import com.pitchedapps.frost.activities.MainActivity
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.FeedSort
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.web.FrostWebView
@@ -30,15 +30,15 @@ class WebFragment : Fragment() {
const val REQUEST_TEXT_ZOOM = 17
const val REQUEST_REFRESH = 99
- operator fun invoke(data: FbTab, position: Int) = WebFragment().withArguments(
+ operator fun invoke(data: FbItem, position: Int) = WebFragment().withArguments(
ARG_URL to data.url,
ARG_POSITION to position,
ARG_URL_ENUM to when (data) {
//If is feed, check if sorting method is specified
- FbTab.FEED -> when (FeedSort(Prefs.feedSort)) {
+ FbItem.FEED -> when (FeedSort(Prefs.feedSort)) {
FeedSort.DEFAULT -> data
- FeedSort.MOST_RECENT -> FbTab.FEED_MOST_RECENT
- FeedSort.TOP -> FbTab.FEED_TOP_STORIES
+ FeedSort.MOST_RECENT -> FbItem.FEED_MOST_RECENT
+ FeedSort.TOP -> FbItem.FEED_TOP_STORIES
}
else -> data
})
@@ -47,7 +47,7 @@ class WebFragment : Fragment() {
// val refresh: SwipeRefreshLayout by lazy { frostWebView.refresh }
val web: FrostWebViewCore by lazy { frostWebView.web }
val url: String by lazy { arguments.getString(ARG_URL) }
- val urlEnum: FbTab by lazy { arguments.getSerializable(ARG_URL_ENUM) as FbTab }
+ val urlEnum: FbItem by lazy { arguments.getSerializable(ARG_URL_ENUM) as FbItem }
val position: Int by lazy { arguments.getInt(ARG_POSITION) }
lateinit var frostWebView: FrostWebView
private var firstLoad = true
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
index 0992a9cb..733bc151 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssAssets.kt
@@ -3,7 +3,10 @@ package com.pitchedapps.frost.injectors
import android.graphics.Color
import android.webkit.WebView
import ca.allanwang.kau.utils.*
+import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
+import java.io.FileNotFoundException
+import java.util.*
/**
* Created by Allan Wang on 2017-05-31.
@@ -14,33 +17,38 @@ enum class CssAssets(val folder: String = "themes") : InjectorContract {
MATERIAL_LIGHT, MATERIAL_DARK, MATERIAL_AMOLED, MATERIAL_GLASS, CUSTOM, ROUND_ICONS("components")
;
- var file = "${name.toLowerCase()}.compact.css"
+ var file = "${name.toLowerCase(Locale.CANADA)}.compact.css"
var injector: JsInjector? = null
override fun inject(webView: WebView, callback: ((String) -> Unit)?) {
if (injector == null) {
- var content = webView.context.assets.open("css/$folder/$file").bufferedReader().use { it.readText() }
- if (this == CUSTOM) {
- var bbt = Prefs.bgColor
- val bt: String
- if (Color.alpha(bbt) == 255) {
- bbt = bbt.adjustAlpha(0.2f).colorToForeground(0.35f)
- bt = Prefs.bgColor.toRgbaString()
- } else {
- bbt = bbt.adjustAlpha(0.05f).colorToForeground(0.5f)
- bt = "transparent"
+ try {
+ var content = webView.context.assets.open("css/$folder/$file").bufferedReader().use { it.readText() }
+ if (this == CUSTOM) {
+ var bbt = Prefs.bgColor
+ val bt: String
+ if (Color.alpha(bbt) == 255) {
+ bbt = bbt.adjustAlpha(0.2f).colorToForeground(0.35f)
+ bt = Prefs.bgColor.toRgbaString()
+ } else {
+ bbt = bbt.adjustAlpha(0.05f).colorToForeground(0.5f)
+ bt = "transparent"
+ }
+ content = content
+ .replace("\$T\$", Prefs.textColor.toRgbaString())
+ .replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString())
+ .replace("\$B\$", Prefs.bgColor.toRgbaString())
+ .replace("\$BT\$", bt)
+ .replace("\$BBT\$", bbt.toRgbaString())
+ .replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
+ .replace("\$OO\$", Prefs.bgColor.colorToForeground(0.35f).withAlpha(255).toRgbaString())
+ .replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
}
- content = content
- .replace("\$T\$", Prefs.textColor.toRgbaString())
- .replace("\$TT\$", Prefs.textColor.colorToBackground(0.05f).toRgbaString())
- .replace("\$B\$", Prefs.bgColor.toRgbaString())
- .replace("\$BT\$", bt)
- .replace("\$BBT\$", bbt.toRgbaString())
- .replace("\$O\$", Prefs.bgColor.withAlpha(255).toRgbaString())
- .replace("\$OO\$", Prefs.bgColor.colorToForeground(0.35f).withAlpha(255).toRgbaString())
- .replace("\$D\$", Prefs.textColor.adjustAlpha(0.3f).toRgbaString())
+ injector = JsBuilder().css(content).build()
+ } catch (e: FileNotFoundException) {
+ L.e(e, "CssAssets file not found")
+ injector = JsInjector(JsActions.EMPTY.function)
}
- injector = JsBuilder().css(content).build()
}
injector!!.inject(webView, callback)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt
index fae1846b..3fa03bcc 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsActions.kt
@@ -14,7 +14,8 @@ enum class JsActions(body: String) : InjectorContract {
* see [com.pitchedapps.frost.web.FrostJSI.loadLogin]
*/
LOGIN_CHECK("document.getElementById('signup-button')&&Frost.loadLogin();"),
- BASE_HREF("document.write(\"<base href='$FB_URL_BASE'/>\");"),
+ BASE_HREF("""document.write("<base href='$FB_URL_BASE'/>");"""),
+ FETCH_BODY("""setTimeout(function(){var e=document.querySelector("main");e||(e=document.querySelector("body")),Frost.handleHtml(e.outerHTML)},1e2);"""),
/**
* Used as a pseudoinjector for maybe functions
*/
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
index d2201c52..27b0f92a 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
@@ -1,6 +1,9 @@
package com.pitchedapps.frost.injectors
import android.webkit.WebView
+import com.pitchedapps.frost.utils.L
+import java.io.FileNotFoundException
+import java.util.*
/**
* Created by Allan Wang on 2017-05-31.
@@ -11,13 +14,18 @@ enum class JsAssets : InjectorContract {
MENU, CLICK_A, CONTEXT_A, HEADER_BADGES, SEARCH, TEXTAREA_LISTENER, NOTIF_MSG
;
- var file = "${name.toLowerCase()}.min.js"
+ var file = "${name.toLowerCase(Locale.CANADA)}.min.js"
var injector: JsInjector? = null
override fun inject(webView: WebView, callback: ((String) -> Unit)?) {
if (injector == null) {
- val content = webView.context.assets.open("js/$file").bufferedReader().use { it.readText() }
- injector = JsBuilder().js(content).build()
+ try {
+ val content = webView.context.assets.open("js/$file").bufferedReader().use { it.readText() }
+ injector = JsBuilder().js(content).build()
+ } catch (e: FileNotFoundException) {
+ L.e(e, "JsAssets file not found")
+ injector = JsInjector(JsActions.EMPTY.function)
+ }
}
injector!!.inject(webView, callback)
}
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 2453d3b0..d3dfe79c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -21,7 +21,6 @@ import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.FrostWebActivity
import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.dbflow.fetchUsername
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.utils.ARG_USER_ID
import com.pitchedapps.frost.utils.L
@@ -31,6 +30,12 @@ import org.jetbrains.anko.runOnUiThread
/**
* Created by Allan Wang on 2017-07-08.
+ *
+ * Logic for build notifications, scheduling notifications, and showing notifications
+ */
+
+/**
+ * Wrap the default builder with our icon and accent color
*/
val Context.frostNotification: NotificationCompat.Builder
get() = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply {
@@ -39,6 +44,9 @@ val Context.frostNotification: NotificationCompat.Builder
color = color(R.color.frost_notification_accent)
}
+/**
+ * Assign global changes to the notification after it is built
+ */
@Suppress("DEPRECATION")
//The update feature is for Android O and seems to still be in beta
fun Notification.frostConfig() = apply {
@@ -54,6 +62,7 @@ val NotificationCompat.Builder.withBigText: NotificationCompat.BigTextStyle
* Created by Allan Wang on 2017-07-08.
*
* Custom target to set the content view and update a given notification
+ * 40dp is the size of the right avatar
*/
class FrostNotificationTarget(val context: Context,
val notifId: Int,
@@ -67,6 +76,9 @@ class FrostNotificationTarget(val context: Context,
}
}
+internal const val FROST_NOTIFICATION_GROUP = "frost"
+internal const val FROST_MESSAGE_NOTIFICATION_GROUP = "frost_im"
+
/**
* Notification data holder
*/
@@ -77,39 +89,35 @@ data class NotificationContent(val data: CookieModel,
val text: String,
val timestamp: Long,
val profileUrl: String) {
- fun createNotification(context: Context, verifiedUser: Boolean = false) {
- //in case we haven't found the name, we will try one more time before passing the notification
- if (!verifiedUser && data.name?.isBlank() ?: true) {
- data.fetchUsername {
- data.name = it
- createNotification(context, true)
- }
- } else {
- val intent = Intent(context, FrostWebActivity::class.java)
- intent.data = Uri.parse(href.formattedFbUrl)
- intent.putExtra(ARG_USER_ID, data.id)
- val group = "frost_${data.id}"
- val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
- val notifBuilder = context.frostNotification
- .setContentTitle(title ?: context.string(R.string.frost_name))
- .setContentText(text)
- .setContentIntent(pendingIntent)
- .setCategory(Notification.CATEGORY_SOCIAL)
- .setSubText(data.name)
- .setGroup(group)
-
- if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
- L.v("Notif load $this")
- NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build().frostConfig())
-
- if (profileUrl.isNotBlank()) {
- context.runOnUiThread {
- Glide.with(context)
- .asBitmap()
- .load(profileUrl)
- .withRoundIcon()
- .into(FrostNotificationTarget(context, notifId, group, notifBuilder))
- }
+ fun createNotification(context: Context) = createNotification(context, FROST_NOTIFICATION_GROUP)
+
+ fun createMessageNotification(context: Context) = createNotification(context, FROST_MESSAGE_NOTIFICATION_GROUP)
+
+ private fun createNotification(context: Context, groupPrefix: String) {
+ val intent = Intent(context, FrostWebActivity::class.java)
+ intent.data = Uri.parse(href.formattedFbUrl)
+ intent.putExtra(ARG_USER_ID, data.id)
+ val group = "${groupPrefix}_${data.id}"
+ val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
+ val notifBuilder = context.frostNotification
+ .setContentTitle(title ?: context.string(R.string.frost_name))
+ .setContentText(text)
+ .setContentIntent(pendingIntent)
+ .setCategory(Notification.CATEGORY_SOCIAL)
+ .setSubText(data.name)
+ .setGroup(group)
+
+ if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
+ L.v("Notif load", this.toString())
+ NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build().frostConfig())
+
+ if (profileUrl.isNotBlank()) {
+ context.runOnUiThread {
+ Glide.with(context)
+ .asBitmap()
+ .load(profileUrl)
+ .withRoundIcon()
+ .into(FrostNotificationTarget(context, notifId, group, notifBuilder))
}
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
index fe7758cc..5859a306 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -1,26 +1,33 @@
package com.pitchedapps.frost.services
+import android.app.Notification
+import android.app.PendingIntent
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
+import android.content.Intent
+import android.net.Uri
import android.support.v4.app.NotificationManagerCompat
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.FrostWebActivity
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.dbflow.lastNotificationTime
import com.pitchedapps.frost.dbflow.loadFbCookie
import com.pitchedapps.frost.dbflow.loadFbCookiesSync
import com.pitchedapps.frost.facebook.FACEBOOK_COM
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.facebook.formattedFbUrl
+import com.pitchedapps.frost.injectors.JsAssets
+import com.pitchedapps.frost.utils.ARG_USER_ID
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostAnswersCustom
-import com.pitchedapps.frost.web.MessageWebView
+import com.pitchedapps.frost.web.launchHeadlessHtmlExtractor
+import io.reactivex.schedulers.Schedulers
import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.uiThread
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import java.util.concurrent.Future
@@ -30,6 +37,9 @@ import java.util.concurrent.Future
*
* Service to manage notifications
* Will periodically check through all accounts in the db and send notifications when appropriate
+ *
+ * Note that general notifications are parsed directly with Jsoup,
+ * but instant messages are done so with a headless webview as it is generated from JS
*/
class NotificationService : JobService() {
@@ -58,7 +68,7 @@ class NotificationService : JobService() {
fun finish(params: JobParameters?) {
val time = System.currentTimeMillis() - startTime
- L.d("Notification service has finished in $time ms")
+ L.i("Notification service has finished in $time ms")
frostAnswersCustom("NotificationTime",
"Type" to "Service",
"IM Included" to Prefs.notificationsInstantMessages,
@@ -68,8 +78,8 @@ class NotificationService : JobService() {
future = null
}
-
override fun onStartJob(params: JobParameters?): Boolean {
+ L.i("Fetching notifications")
future = doAsync {
if (Prefs.notificationAllAccounts) {
val cookies = loadFbCookiesSync()
@@ -83,9 +93,15 @@ class NotificationService : JobService() {
L.d("Finished main notifications")
if (Prefs.notificationsInstantMessages) {
val currentCookie = loadFbCookie(Prefs.userId)
- if (currentCookie != null)
- uiThread { MessageWebView(this@NotificationService, params, currentCookie) }
- } else finish(params)
+ if (currentCookie != null) {
+ fetchMessageNotifications(currentCookie) {
+ L.i("Notif IM fetching finished ${if (it) "succesfully" else "unsuccessfully"}")
+ finish(params)
+ }
+ return@doAsync
+ }
+ }
+ finish(params)
}
return true
}
@@ -95,13 +111,21 @@ class NotificationService : JobService() {
return null
}
+ /*
+ * ----------------------------------------------------------------
+ * General notification logic.
+ * Fetch notifications -> Filter new ones -> Parse notifications ->
+ * Show notifications -> Show group notification
+ * ----------------------------------------------------------------
+ */
+
fun fetchGeneralNotifications(data: CookieModel) {
- L.i("Notif fetch for $data")
- val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get()
+ L.d("Notif fetch", data.toString())
+ val doc = Jsoup.connect(FbItem.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get()
//aclb for unread, acw for read
val unreadNotifications = (doc.getElementById("notifications_list") ?: return L.eThrow("Notification list not found")).getElementsByClass("aclb")
var notifCount = 0
-// val prevLatestEpoch = 1498931565L // for testing
+ //val prevLatestEpoch = 1498931565L // for testing
val prevNotifTime = lastNotificationTime(data.id)
val prevLatestEpoch = prevNotifTime.epoch
L.v("Notif Prev Latest Epoch $prevLatestEpoch")
@@ -122,7 +146,6 @@ class NotificationService : JobService() {
summaryNotification(data.id, notifCount)
}
-
fun parseNotification(data: CookieModel, element: Element): NotificationContent? {
val a = element.getElementsByTag("a").first() ?: return logNotif("IM No a tag")
val abbr = element.getElementsByTag("abbr")
@@ -134,13 +157,36 @@ class NotificationService : JobService() {
if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
//fetch profpic
val p = element.select("i.img[style*=url]")
- val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: ""
+ val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: ""
return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl)
}
- fun fetchMessageNotifications(data: CookieModel, content: String) {
- L.i("Notif IM fetch for $data")
- val doc = Jsoup.parseBodyFragment(content)
+ fun summaryNotification(userId: Long, count: Int)
+ = summaryNotification(userId, count, R.string.notifications, FbItem.NOTIFICATIONS.url, FROST_NOTIFICATION_GROUP)
+
+ /*
+ * ----------------------------------------------------------------
+ * Instant message notification logic.
+ * Fetch notifications -> Filter new ones -> Parse notifications ->
+ * Show notifications -> Show group notification
+ * ----------------------------------------------------------------
+ */
+
+ inline fun fetchMessageNotifications(data: CookieModel, crossinline callback: (success: Boolean) -> Unit) {
+ launchHeadlessHtmlExtractor(FbItem.MESSAGES.url, JsAssets.NOTIF_MSG) {
+ it.observeOn(Schedulers.newThread()).subscribe {
+ (html, errorRes) ->
+ L.d("Notf IM html received")
+ if (errorRes != -1) return@subscribe callback(false)
+ fetchMessageNotifications(data, html)
+ callback(true)
+ }
+ }
+ }
+
+ fun fetchMessageNotifications(data: CookieModel, html: String) {
+ L.d("Notif IM fetch", data.toString())
+ val doc = Jsoup.parseBodyFragment(html)
val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb")
var notifCount = 0
val prevNotifTime = lastNotificationTime(data.id)
@@ -152,7 +198,7 @@ class NotificationService : JobService() {
val notif = parseMessageNotification(data, elem) ?: return@unread
L.v("Notif im timestamp ${notif.timestamp}")
if (notif.timestamp <= prevLatestEpoch) return@unread
- notif.createNotification(this@NotificationService)
+ notif.createMessageNotification(this@NotificationService)
if (notif.timestamp > newLatestEpoch)
newLatestEpoch = notif.timestamp
notifCount++
@@ -160,7 +206,7 @@ class NotificationService : JobService() {
if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save()
L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}")
frostAnswersCustom("Notifications", "Type" to "Message", "Count" to notifCount)
- summaryNotification(data.id, notifCount)
+ summaryMessageNotification(data.id, notifCount)
}
fun parseMessageNotification(data: CookieModel, element: Element): NotificationContent? {
@@ -174,11 +220,14 @@ class NotificationService : JobService() {
if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
//fetch convo pic
val p = element.select("i.img[style*=url]")
- val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: ""
- L.v("url ${a.attr("href")}")
- return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl.formattedFbUrl)
+ val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: ""
+ L.v("url", a.attr("href"))
+ return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl)
}
+ fun summaryMessageNotification(userId: Long, count: Int)
+ = summaryNotification(userId, count, R.string.messages, FbItem.MESSAGES.url, FROST_MESSAGE_NOTIFICATION_GROUP)
+
private fun Context.debugNotification(text: String) {
if (!BuildConfig.DEBUG) return
val notifBuilder = frostNotification
@@ -187,15 +236,21 @@ class NotificationService : JobService() {
NotificationManagerCompat.from(this).notify(999, notifBuilder.build().frostConfig())
}
- fun summaryNotification(userId: Long, count: Int) {
+ private fun summaryNotification(userId: Long, count: Int, contentRes: Int, pendingUrl: String, groupPrefix: String) {
if (count <= 1) return
+ val intent = Intent(this, FrostWebActivity::class.java)
+ intent.data = Uri.parse(pendingUrl)
+ intent.putExtra(ARG_USER_ID, userId)
+ val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notifBuilder = frostNotification
.setContentTitle(string(R.string.frost_name))
- .setContentText("$count notifications")
- .setGroup("frost_$userId")
+ .setContentText("$count ${string(contentRes)}")
+ .setGroup("${groupPrefix}_$userId")
.setGroupSummary(true)
+ .setContentIntent(pendingIntent)
+ .setCategory(Notification.CATEGORY_SOCIAL)
- NotificationManagerCompat.from(this).notify("frost_$userId", userId.toInt(), notifBuilder.build().frostConfig())
+ NotificationManagerCompat.from(this).notify("${groupPrefix}_$userId", userId.toInt(), notifBuilder.build().frostConfig())
}
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
index 52f7412f..9e53889e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
@@ -13,6 +13,7 @@ import com.pitchedapps.frost.utils.Prefs
*/
class UpdateReceiver : BroadcastReceiver() {
+ //todo check action warning
override fun onReceive(context: Context, intent: Intent) {
L.d("Frost has updated")
context.scheduleNotifications(Prefs.notificationFreq) //Update notifications
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 2af67602..bf524835 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt
@@ -31,6 +31,10 @@ fun SettingsActivity.getBehaviourPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.search_bar_desc
}
+ checkbox(R.string.force_message_bottom, { Prefs.messageScrollToBottom }, { Prefs.messageScrollToBottom = it }) {
+ descRes = R.string.force_message_bottom_desc
+ }
+
checkbox(R.string.exit_confirmation, { Prefs.exitConfirmation }, { Prefs.exitConfirmation = it }) {
descRes = R.string.exit_confirmation_desc
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
new file mode 100644
index 00000000..f8dc81d1
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
@@ -0,0 +1,151 @@
+package com.pitchedapps.frost.settings
+
+import android.content.Context
+import android.support.annotation.UiThread
+import ca.allanwang.kau.email.sendEmail
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
+import ca.allanwang.kau.utils.string
+import com.afollestad.materialdialogs.MaterialDialog
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.SettingsActivity
+import com.pitchedapps.frost.facebook.FACEBOOK_COM
+import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
+import com.pitchedapps.frost.injectors.InjectorContract
+import com.pitchedapps.frost.injectors.JsActions
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.cleanHtml
+import com.pitchedapps.frost.utils.materialDialogThemed
+import com.pitchedapps.frost.web.launchHeadlessHtmlExtractor
+import com.pitchedapps.frost.web.query
+import io.reactivex.disposables.Disposable
+import org.jetbrains.anko.AnkoAsyncContext
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.runOnUiThread
+import org.jetbrains.anko.uiThread
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+
+/**
+ * Created by Allan Wang on 2017-06-30.
+ *
+ * A sub pref section that is enabled through a hidden preference
+ * Each category will load a page, extract the contents, remove private info, and create a report
+ */
+fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = {
+
+ plainText(R.string.experimental_disclaimer) {
+ descRes = R.string.debug_disclaimer_info
+ }
+
+ Debugger.values().forEach {
+ plainText(it.data.titleId) {
+ iicon = it.data.icon
+ onClick = { itemView, _, _ -> it.debug(itemView.context); true }
+ }
+ }
+
+}
+
+private enum class Debugger(val data: FbItem, val injector: InjectorContract?, vararg query: String) {
+ NOTIFICATIONS(FbItem.NOTIFICATIONS, null, "#notifications_list"),
+ SEARCH(FbItem.SEARCH, JsActions.FETCH_BODY);
+
+ val query = if (query.isNotEmpty()) arrayOf(*query, "#root", "main", "body") else emptyArray()
+
+ fun debug(context: Context) {
+ val dialog = context.materialDialogThemed {
+ title("Debugging")
+ progress(true, 0)
+ canceledOnTouchOutside(false)
+ positiveText(R.string.kau_cancel)
+ onPositive { dialog, _ -> dialog.cancel() }
+ }
+ if (injector != null) dialog.extractHtml(injector)
+ else dialog.debugAsync {
+ loadJsoup()
+ }
+ }
+
+ fun MaterialDialog.debugAsync(task: AnkoAsyncContext<MaterialDialog>.() -> Unit) {
+ doAsync({ t: Throwable ->
+ val msg = t.message
+ L.e("Debugger failed: $msg")
+ context.runOnUiThread {
+ cancel()
+ context.materialDialogThemed {
+ title(R.string.debug_incomplete)
+ if (msg != null) content(msg)
+ }
+ }
+ }, task)
+ }
+
+ /**
+ * Wait for html to be returned from headless webview
+ *
+ * from [debug] to [simplifyJsoup] if [query] is not empty, or [createReport] otherwise
+ */
+ @UiThread
+ private fun MaterialDialog.extractHtml(injector: InjectorContract) {
+ setContent("Fetching webpage")
+ var disposable: Disposable? = null
+ setOnCancelListener { disposable?.dispose() }
+ context.launchHeadlessHtmlExtractor(data.url, injector) {
+ disposable = it.subscribe {
+ (html, errorRes) ->
+ debugAsync {
+ if (errorRes == -1) {
+ L.i("Debug report successful", html)
+ if (query.isNotEmpty()) simplifyJsoup(Jsoup.parseBodyFragment(html))
+ else createReport(html)
+ } else {
+ throw Throwable(context.string(errorRes))
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get data directly from the link and search for our queries, returning the outerHTML
+ * of the first query found
+ *
+ * from [debug] to [simplifyJsoup]
+ */
+ private fun AnkoAsyncContext<MaterialDialog>.loadJsoup() {
+ uiThread {
+ it.setContent("Load Jsoup")
+ it.setOnCancelListener(null)
+ it.debugAsync {
+ val connection = Jsoup.connect(data.url).cookie(FACEBOOK_COM, FbCookie.webCookie).userAgent(USER_AGENT_BASIC)
+ val doc = connection.get()
+ simplifyJsoup(doc)
+ }
+ }
+ }
+
+ /**
+ * Takes snippet of given document that matches the first query in the [query] items
+ * before sending it to [createReport]
+ */
+ private fun AnkoAsyncContext<MaterialDialog>.simplifyJsoup(doc: Document) {
+ weakRef.get() ?: return
+ val q = query.first { doc.select(it).isNotEmpty() }
+ createReport(doc.select(q).outerHtml())
+ }
+
+ private fun AnkoAsyncContext<MaterialDialog>.createReport(html: String) {
+ val cleanHtml = html.cleanHtml()
+ uiThread {
+ val c = it.context
+ it.dismiss()
+ c.sendEmail(c.string(R.string.dev_email),
+ "${c.string(R.string.debug_report_email_title)} $name") {
+ addItem("Query List", query.contentToString())
+ footer = cleanHtml
+ }
+ }
+ }
+} \ No newline at end of file
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 594cbe01..a1b459fb 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,11 @@
package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
+import ca.allanwang.kau.logging.KL
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.MainActivity
import com.pitchedapps.frost.activities.SettingsActivity
+import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.Showcase
@@ -22,13 +24,17 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = {
// Experimental content starts here ------------------
- checkbox(R.string.notification_messages, { Prefs.notificationsInstantMessages }, { Prefs.notificationsInstantMessages = it }) {
- descRes = R.string.notification_messages_desc
- }
+
// Experimental content ends here --------------------
- checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, { Prefs.verboseLogging = it }) {
+ checkbox(R.string.verbose_logging, { Prefs.verboseLogging }, {
+ Prefs.verboseLogging = it
+ KL.debug(it)
+ KL.showPrivateText = false
+ L.debug(it)
+ KL.showPrivateText = false
+ }) {
descRes = R.string.verbose_logging_desc
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt
new file mode 100644
index 00000000..30ab2579
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Network.kt
@@ -0,0 +1,17 @@
+package com.pitchedapps.frost.settings
+
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.activities.SettingsActivity
+import com.pitchedapps.frost.utils.Prefs
+
+/**
+ * Created by Allan Wang on 2017-08-08.
+ */
+fun SettingsActivity.getNetworkPrefs(): KPrefAdapterBuilder.() -> Unit = {
+
+ checkbox(R.string.network_media_on_metered, { Prefs.loadMediaOnMeteredNetwork }, { Prefs.loadMediaOnMeteredNetwork = it }) {
+ descRes = R.string.network_media_on_metered_desc
+ }
+
+} \ No newline at end of file
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 a5aa84d3..b1e5015f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
@@ -55,6 +55,10 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.notification_all_accounts_desc
}
+ checkbox(R.string.notification_messages, { Prefs.notificationsInstantMessages }, { Prefs.notificationsInstantMessages = it }) {
+ descRes = R.string.notification_messages_desc
+ }
+
checkbox(R.string.notification_sound, { Prefs.notificationSound }, { Prefs.notificationSound = it })
checkbox(R.string.notification_vibrate, { Prefs.notificationVibrate }, { Prefs.notificationVibrate = it })
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt
new file mode 100644
index 00000000..da8672f4
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/JsoupCleaner.kt
@@ -0,0 +1,34 @@
+package com.pitchedapps.frost.utils
+
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Attribute
+import org.jsoup.nodes.Element
+import org.jsoup.safety.Whitelist
+
+/**
+ * Created by Allan Wang on 2017-08-10.
+ *
+ * Parses html with Jsoup and cleans the data, emitting just the frame containing debugging info
+ *
+ * Removes text, removes unnecessary nodes
+ */
+fun String.cleanHtml() = cleanText().cleanJsoup()
+
+internal fun String.cleanText(): String = replace(Regex(">(?s).+?<"), "><")
+
+internal fun String.cleanJsoup(): String = Jsoup.clean(this, PrivacyWhitelist())
+
+class PrivacyWhitelist : Whitelist() {
+
+ val blacklistAttrs = arrayOf("style", "aria-label", "rel")
+ val blacklistTags = arrayOf("body", "html", "head", "i", "b", "u", "style", "script",
+ "br", "p", "span", "ul", "ol", "li")
+
+ override fun isSafeAttribute(tagName: String, el: Element, attr: Attribute): Boolean {
+ val key = attr.key
+ if (key == "href") attr.setValue("-")
+ return key !in blacklistAttrs
+ }
+
+ override fun isSafeTag(tag: String) = tag !in blacklistTags
+}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
index 16a3d2ae..d5c1a6fb 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/L.kt
@@ -1,9 +1,8 @@
package com.pitchedapps.frost.utils
-import android.util.Log
-import ca.allanwang.kau.logging.TimberLogger
+import ca.allanwang.kau.logging.KauLogger
import com.crashlytics.android.Crashlytics
-import timber.log.Timber
+import com.pitchedapps.frost.BuildConfig
/**
@@ -16,25 +15,15 @@ import timber.log.Timber
* Debug and Error logs must not reveal person info
* Person info logs can be marked as info or verbose
*/
-object L : TimberLogger("Frost") {
+object L : KauLogger("Frost") {
- /**
- * Helper function to separate private info
- */
- fun d(tag: String, personal: String?) {
- L.d(tag)
- L.i("-\t$personal")
- }
-}
-
-internal class CrashReportingTree : Timber.Tree() {
- override fun log(priority: Int, tag: String?, message: String?, t: Throwable?) {
- when (priority) {
- Log.VERBOSE, Log.INFO -> return
- Log.DEBUG -> if (!Prefs.verboseLogging) return
+ override fun logImpl(priority: Int, message: String?, privateMessage: String?, t: Throwable?) {
+ if (BuildConfig.DEBUG) {
+ super.logImpl(priority, message, privateMessage, t)
+ } else {
+ if (message != null)
+ Crashlytics.log(priority, "Frost", message)
+ if (t != null) Crashlytics.logException(t)
}
- if (message != null)
- Crashlytics.log(priority, "Frost", message)
- if (t != null) Crashlytics.logException(t)
}
} \ No newline at end of file
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 b053b9dd..9b8064a4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -97,8 +97,7 @@ object Prefs : KPref() {
var notificationAllAccounts: Boolean by kpref("notification_all_accounts", true)
- //todo remove from experimental once stabilized
- var notificationsInstantMessages: Boolean by kpref("notification_im", Showcase.experimentalDefault)
+ var notificationsInstantMessages: Boolean by kpref("notification_im", false)
var notificationVibrate: Boolean by kpref("notification_vibrate", true)
@@ -106,6 +105,8 @@ object Prefs : KPref() {
var notificationLights: Boolean by kpref("notification_lights", true)
+ var messageScrollToBottom: Boolean by kpref("message_scroll_to_bottom", false)
+
/**
* Cache like value to determine if user has or had pro
* In most cases, [com.pitchedapps.frost.utils.iab.IS_FROST_PRO] should be looked at instead
@@ -128,4 +129,8 @@ object Prefs : KPref() {
var viewpagerSwipe: Boolean by kpref("viewpager_swipe", true)
+ var loadMediaOnMeteredNetwork: Boolean by kpref("media_on_metered_network", true)
+
+ var debugSettings: Boolean by kpref("debug_settings", false)
+
}
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 496a6b5b..e79816f3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -25,7 +25,7 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.*
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FACEBOOK_COM
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.formattedFbUrl
import java.io.IOException
import java.util.*
@@ -56,8 +56,8 @@ fun Activity.cookies(): ArrayList<CookieModel> {
fun Context.launchWebOverlay(url: String) {
val argUrl = url.formattedFbUrl
- L.v("Launch received $url")
- L.i("Launch web overlay: $argUrl")
+ L.v("Launch received", url)
+ L.i("Launch web overlay", argUrl)
startActivity(WebOverlayActivity::class.java, false, intentBuilder = {
putExtra(ARG_URL, argUrl)
})
@@ -74,7 +74,7 @@ fun Activity.launchIntroActivity(cookieList: ArrayList<CookieModel>)
= launchNewTask(IntroActivity::class.java, cookieList, true)
fun WebOverlayActivity.url(): String {
- return intent.extras?.getString(ARG_URL) ?: FbTab.FEED.url
+ return intent.extras?.getString(ARG_URL) ?: FbItem.FEED.url
}
fun Context.materialDialogThemed(action: MaterialDialog.Builder.() -> Unit): MaterialDialog {
@@ -132,6 +132,15 @@ fun frostAnswersCustom(name: String, vararg events: Pair<String, Any>) {
}
}
+/**
+ * Helper method to quietly keep track of throwable issues
+ */
+fun Throwable?.logFrostAnswers(text: String) {
+ val msg = if (this == null) text else "$text: $message"
+ L.e(msg)
+ frostAnswersCustom("Errors", "text" to text, "message" to (this?.message ?: "NA"))
+}
+
fun View.frostSnackbar(@StringRes text: Int, builder: Snackbar.() -> Unit = {}) {
Snackbar.make(this, text, Snackbar.LENGTH_LONG).apply {
builder()
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
index bad7f8fd..7f6e8a6d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABBinder.kt
@@ -9,6 +9,13 @@ import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostAnswers
+import com.pitchedapps.frost.utils.logFrostAnswers
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.onComplete
+import org.jetbrains.anko.uiThread
+import java.lang.ref.WeakReference
+import java.math.BigDecimal
+import java.util.*
/**
* Created by Allan Wang on 2017-07-22.
@@ -33,31 +40,48 @@ interface FrostBilling : BillingProcessor.IBillingHandler {
abstract class IABBinder : FrostBilling {
var bp: BillingProcessor? = null
- var activity: Activity? = null
-
- override fun Activity.onCreateBilling() {
- activity = this
- bp = BillingProcessor.newBillingProcessor(this, PUBLIC_BILLING_KEY, this@IABBinder)
- bp?.initialize()
+ lateinit var activityRef: WeakReference<Activity>
+ val activity
+ get() = activityRef.get()
+
+ override final fun Activity.onCreateBilling() {
+ activityRef = WeakReference(this)
+ doAsync {
+ bp = BillingProcessor.newBillingProcessor(this@onCreateBilling, PUBLIC_BILLING_KEY, this@IABBinder)
+ bp?.initialize()
+ }
}
override fun onDestroyBilling() {
+ activityRef.clear()
bp?.release()
bp = null
- activity = null
}
- override fun onBillingInitialized() = L.d("IAB initialized")
+ override fun onBillingInitialized() = L.i("IAB initialized")
override fun onPurchaseHistoryRestored() = L.d("IAB restored")
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
- L.d("IAB $productId purchased")
- frostAnswers {
- logPurchase(PurchaseEvent()
- .putItemId(productId)
- .putSuccess(true)
- )
+ bp.doAsync {
+ L.i("IAB $productId purchased")
+ val listing = weakRef.get()?.getPurchaseListingDetails(productId) ?: return@doAsync
+ val currency = try {
+ Currency.getInstance(listing.currency)
+ } catch (e: Exception) {
+ null
+ }
+ frostAnswers {
+ logPurchase(PurchaseEvent().apply {
+ putItemId(productId)
+ putSuccess(true)
+ if (currency != null) {
+ putCurrency(Currency.getInstance(Locale.getDefault()))
+ putItemType(productId)
+ putItemPrice(BigDecimal.valueOf(listing.priceValue))
+ }
+ })
+ }
}
}
@@ -67,13 +91,14 @@ abstract class IABBinder : FrostBilling {
.putCustomAttribute("result", errorCode.toString())
.putSuccess(false))
}
- L.e(error, "IAB error $errorCode")
+ error.logFrostAnswers("IAB error $errorCode")
}
override fun onActivityResultBilling(requestCode: Int, resultCode: Int, data: Intent?): Boolean
= bp?.handleActivityResult(requestCode, resultCode, data) ?: false
override fun purchasePro() {
+ val bp = this.bp
if (bp == null) {
frostAnswers {
logPurchase(PurchaseEvent()
@@ -83,10 +108,12 @@ abstract class IABBinder : FrostBilling {
L.eThrow("IAB null bp on purchase attempt")
return
}
- if (!(bp?.isOneTimePurchaseSupported ?: false))
- activity?.playStorePurchaseUnsupported()
+ val a = activity ?: return
+
+ if (!BillingProcessor.isIabServiceAvailable(a) || !bp.isOneTimePurchaseSupported)
+ a.playStorePurchaseUnsupported()
else
- bp?.purchase(activity, FROST_PRO)
+ bp.purchase(a, FROST_PRO)
}
}
@@ -107,15 +134,18 @@ class IABSettings : IABBinder() {
* Attempts to get pro, or launch purchase flow if user doesn't have it
*/
override fun restorePurchases() {
- if (bp == null) return
- val load = bp?.loadOwnedPurchasesFromGoogle() ?: return
- L.d("IAB settings load from google $load")
- if (!(bp?.isPurchased(FROST_PRO) ?: return)) {
- if (Prefs.pro) activity.playStoreNoLongerPro()
- else purchasePro()
- } else {
- if (!Prefs.pro) activity.playStoreFoundPro()
- else activity?.purchaseRestored()
+ bp.doAsync {
+ val load = weakRef.get()?.loadOwnedPurchasesFromGoogle() ?: return@doAsync
+ L.d("IAB settings load from google $load")
+ uiThread {
+ if (!(weakRef.get()?.isPurchased(FROST_PRO) ?: return@uiThread)) {
+ if (Prefs.pro) activity.playStoreNoLongerPro()
+ else purchasePro()
+ } else {
+ if (!Prefs.pro) activity.playStoreFoundPro()
+ else activity?.purchaseRestored()
+ }
+ }
}
}
}
@@ -142,13 +172,17 @@ class IABMain : IABBinder() {
override fun restorePurchases() {
if (restored || bp == null) return
restored = true
- val load = bp?.loadOwnedPurchasesFromGoogle() ?: false
- L.d("IAB main load from google $load")
- if (!(bp?.isPurchased(FROST_PRO) ?: false)) {
- if (Prefs.pro) activity.playStoreNoLongerPro()
- } else {
- if (!Prefs.pro) activity.playStoreFoundPro()
+ bp.doAsync {
+ val load = weakRef.get()?.loadOwnedPurchasesFromGoogle() ?: false
+ L.d("IAB main load from google $load")
+ onComplete {
+ if (!(weakRef.get()?.isPurchased(FROST_PRO) ?: false)) {
+ if (Prefs.pro) activity.playStoreNoLongerPro()
+ } else {
+ if (!Prefs.pro) activity.playStoreFoundPro()
+ }
+ onDestroyBilling()
+ }
}
- onDestroyBilling()
}
} \ No newline at end of file
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 df0f04fd..e997731b 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
@@ -2,6 +2,7 @@ package com.pitchedapps.frost.utils.iab
import android.app.Activity
import ca.allanwang.kau.utils.restart
+import ca.allanwang.kau.utils.startLink
import ca.allanwang.kau.utils.startPlayStoreLink
import ca.allanwang.kau.utils.string
import com.crashlytics.android.answers.PurchaseEvent
@@ -69,9 +70,11 @@ fun Activity.playStorePurchaseUnsupported() {
materialDialogThemed {
title(R.string.uh_oh)
content(R.string.play_store_unsupported)
- positiveText(R.string.kau_ok)
- neutralText(R.string.kau_play_store)
- onNeutral { _, _ -> startPlayStoreLink(R.string.play_store_package_id) }
+ negativeText(R.string.kau_close)
+ positiveText(R.string.kau_play_store)
+ neutralText(R.string.paypal)
+ onPositive { _, _ -> startPlayStoreLink(R.string.play_store_package_id) }
+ onNeutral { _, _ -> startLink(string(R.string.dev_paypal)) }
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
index 6bc27256..61711092 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostChromeClients.kt
@@ -34,15 +34,9 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() {
val activityContract = (webCore.context as? ActivityWebContract)
val context = webCore.context!!
- 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()}")
+ L.d("Chrome Console ${consoleMessage.lineNumber()}: ${consoleMessage.message()}")
return true
}
@@ -63,10 +57,10 @@ class FrostChromeClient(webCore: FrostWebViewCore) : WebChromeClient() {
}
override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) {
- L.d("Requesting geolocation")
+ L.i("Requesting geolocation")
context.kauRequestPermissions(PERMISSION_ACCESS_FINE_LOCATION) {
granted, _ ->
- L.d("Geolocation response received; ${if (granted) "granted" else "denied"}")
+ L.i("Geolocation response received; ${if (granted) "granted" else "denied"}")
callback(origin, granted, true)
}
}
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 018ad737..f24a7a51 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -81,12 +81,14 @@ class FrostJSI(val webView: FrostWebViewCore) {
}
@JavascriptInterface
- fun handleHtml(html: String) {
+ fun handleHtml(html: String?) {
+ html ?: return
webView.post { webView.frostWebClient.handleHtml(html) }
}
@JavascriptInterface
- fun handleHeader(html: String) {
+ fun handleHeader(html: String?) {
+ html ?: return
headerObservable?.onNext(html)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
index 3f2891d0..1a907f7f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
@@ -5,6 +5,7 @@ import android.webkit.WebResourceResponse
import android.webkit.WebView
import ca.allanwang.kau.utils.use
import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.Prefs
import okhttp3.HttpUrl
import java.io.ByteArrayInputStream
@@ -15,17 +16,17 @@ import java.io.ByteArrayInputStream
* 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())) }
+private val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) }
//these hosts will redirect to a blank resource
-val blacklistHost: Set<String> by lazy {
+private val blacklistHost: Set<String> by lazy {
setOf(
"edge-chat.facebook.com"
)
}
//these hosts will return null and skip logging
-val whitelistHost: Set<String> by lazy {
+private val whitelistHost: Set<String> by lazy {
setOf(
"static.xx.fbcdn.net",
"m.facebook.com",
@@ -35,13 +36,13 @@ val whitelistHost: Set<String> by lazy {
//these hosts will skip ad inspection
//this list does not have to include anything from the two above
-val adWhitelistHost: Set<String> by lazy {
+private val adWhitelistHost: Set<String> by lazy {
setOf(
"scontent-sea1-1.xx.fbcdn.net"
)
}
-var adblock: Set<String>? = null
+private var adblock: Set<String>? = null
fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
val httpUrl = HttpUrl.parse(request.url?.toString() ?: return null) ?: return null
@@ -53,7 +54,8 @@ fun shouldFrostInterceptRequest(view: WebView, request: WebResourceRequest): Web
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}")
+ if (!shouldLoadImages && !Prefs.loadMediaOnMeteredNetwork && request.isMedia) return blankResource
+ L.v("Intercept Request", "$host $url")
return null
}
@@ -64,16 +66,25 @@ fun WebResourceRequest.query(action: (url: String) -> Boolean): Boolean {
return action(url?.path ?: return false)
}
+val WebResourceRequest.isImage: Boolean
+ get() = query { it.contains(".jpg") || it.contains(".png") }
+
+val WebResourceRequest.isMedia: Boolean
+ get() = query { it.contains(".jpg") || it.contains(".png") || it.contains("video") }
+
/**
* Generic filter passthrough
* If Resource is already nonnull, pass it, otherwise check if filter is met and override the response accordingly
*/
-fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean): WebResourceResponse?
- = this ?: if (request.query { filter(it) }) blankResource else null
+fun WebResourceResponse?.filter(request: WebResourceRequest, filter: (url: String) -> Boolean)
+ = filter(request.query { filter(it) })
+
+fun WebResourceResponse?.filter(filter: Boolean): WebResourceResponse?
+ = this ?: if (filter) blankResource else null
fun WebResourceResponse?.filterCss(request: WebResourceRequest): WebResourceResponse?
= filter(request) { it.endsWith(".css") }
fun WebResourceResponse?.filterImage(request: WebResourceRequest): WebResourceResponse?
- = filter(request) { it.contains(".jpg") || it.contains(".png") }
+ = filter(request.isImage)
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 79ca1fdf..89ad766d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt
@@ -11,7 +11,7 @@ import android.widget.FrameLayout
import android.widget.ProgressBar
import ca.allanwang.kau.utils.*
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostDownload
@@ -53,7 +53,7 @@ class FrostWebView @JvmOverloads constructor(
}
@SuppressLint("SetJavaScriptEnabled")
- fun setupWebview(url: String, enum: FbTab? = null) {
+ fun setupWebview(url: String, enum: FbItem? = null) {
with(web) {
baseUrl = url
baseEnum = enum
@@ -77,7 +77,7 @@ class FrostWebView @JvmOverloads constructor(
//Some urls have postJavascript injections so make sure we load the base url
override fun onRefresh() {
when (web.baseUrl) {
- FbTab.MENU.url -> web.loadBaseUrl(true)
+ FbItem.MENU.url -> web.loadBaseUrl(true)
else -> web.reload(true)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
index 94bff3c3..5f679c65 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -1,8 +1,8 @@
package com.pitchedapps.frost.web
import android.content.Context
-import android.content.Intent
import android.graphics.Bitmap
+import android.graphics.Color
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
@@ -14,10 +14,12 @@ import com.pitchedapps.frost.activities.WebOverlayActivity
import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.injectors.*
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.utils.iab.IS_FROST_PRO
import io.reactivex.subjects.Subject
+import org.jetbrains.anko.withAlpha
/**
* Created by Allan Wang on 2017-05-31.
@@ -42,18 +44,19 @@ open class BaseWebViewClient : WebViewClient() {
open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient() {
val refreshObservable: Subject<Boolean> = webCore.refreshObservable
+ val isMain = webCore.baseEnum != null
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
if (url == null) return
- L.i("FWV Loading $url")
-// L.v("Cookies ${CookieManager.getInstance().getCookie(url)}")
+ L.i("FWV Loading", url)
refreshObservable.onNext(true)
if (!url.contains(FACEBOOK_COM)) return
if (url.contains("logout.php")) FbCookie.logout(Prefs.userId, { launchLogin(view.context) })
else if (url.contains("login.php")) FbCookie.reset({ launchLogin(view.context) })
}
+
fun launchLogin(c: Context) {
if (c is MainActivity && c.cookies().isNotEmpty())
c.launchNewTask(SelectorActivity::class.java, c.cookies())
@@ -61,44 +64,52 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
c.launchNewTask(LoginActivity::class.java)
}
+ fun injectBackgroundColor()
+ = webCore.setBackgroundColor(if (isMain) Color.TRANSPARENT else Prefs.bgColor.withAlpha(255))
+
+
+ override fun onPageCommitVisible(view: WebView, url: String?) {
+ super.onPageCommitVisible(view, url)
+ injectBackgroundColor()
+ view.jsInject(
+ CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons),
+ CssHider.HEADER,
+ CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO),
+ Prefs.themeInjector,
+ CssHider.NON_RECENT.maybe(webCore.url?.contains("?sk=h_chr") ?: false))
+ }
+
override fun onPageFinished(view: WebView, url: String?) {
- super.onPageFinished(view, url)
- if (url == null) return
- L.i("Page finished $url")
+ url ?: return
+ L.i("Page finished", url)
if (!url.contains(FACEBOOK_COM)) {
refreshObservable.onNext(false)
return
}
- view.jsInject(
- CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons),
- CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && IS_FROST_PRO),
- CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO)
- )
onPageFinishedActions(url)
}
open internal fun onPageFinishedActions(url: String) {
+ if (url.startsWith("${FbItem.MESSAGES.url}/read/") && Prefs.messageScrollToBottom)
+ webCore.pageDown(true)
injectAndFinish()
}
internal fun injectAndFinish() {
L.d("Page finished reveal")
- webCore.jsInject(CssHider.HEADER,
- CssHider.NON_RECENT.maybe(webCore.url.contains("?sk=h_chr")),
- Prefs.themeInjector,
- callback = {
- refreshObservable.onNext(false)
- webCore.jsInject(
- JsActions.LOGIN_CHECK,
- JsAssets.CLICK_A.maybe(webCore.baseEnum != null && Prefs.overlayEnabled),
- JsAssets.TEXTAREA_LISTENER,
- JsAssets.CONTEXT_A,
- JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null)
- )
- })
- }
-
- open fun handleHtml(html: String) {
+ refreshObservable.onNext(false)
+ injectBackgroundColor()
+ webCore.jsInject(
+ JsActions.LOGIN_CHECK,
+ JsAssets.CLICK_A.maybe(webCore.baseEnum != null && Prefs.overlayEnabled),
+ JsAssets.TEXTAREA_LISTENER,
+ CssHider.ADS.maybe(!Prefs.showFacebookAds && IS_FROST_PRO),
+ JsAssets.CONTEXT_A,
+ JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null)
+ )
+ }
+
+ open fun handleHtml(html: String?) {
L.d("Handle Html")
}
@@ -112,26 +123,26 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : BaseWebViewClient
* returns false if we are already in an overlaying activity
*/
private fun launchRequest(request: WebResourceRequest): Boolean {
- L.d("Launching Url", request.url.toString())
+ L.d("Launching Url", request.url?.toString() ?: "null")
if (webCore.context is WebOverlayActivity) return false
webCore.context.launchWebOverlay(request.url.toString())
return true
}
- private fun launchImage(request: WebResourceRequest, text: String? = null): Boolean {
- L.d("Launching Image", request.url.toString())
- webCore.context.launchImageActivity(request.url.toString(), text)
+ private fun launchImage(url: String, text: String? = null): Boolean {
+ L.d("Launching Image", url)
+ webCore.context.launchImageActivity(url, text)
if (webCore.canGoBack()) webCore.goBack()
return true
}
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
- L.i("Url Loading ${request.url}")
- val path = request.url.path ?: return super.shouldOverrideUrlLoading(view, request)
- L.v("Url Loading Path $path")
+ L.i("Url Loading", request.url?.toString())
+ val path = request.url?.path ?: return super.shouldOverrideUrlLoading(view, request)
+ L.v("Url Loading Path", path)
if (path.startsWith("/composer/")) return launchRequest(request)
if (request.url.toString().contains("scontent-sea1-1.xx.fbcdn.net") && (path.endsWith(".jpg") || path.endsWith(".png")))
- return launchImage(request)
+ return launchImage(request.url.toString())
if (view.context.resolveActivityForUri(request.url)) return true
return super.shouldOverrideUrlLoading(view, request)
}
@@ -163,6 +174,7 @@ class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(web
}
override fun onPageFinishedActions(url: String) {
+ L.d("Should inject ${url.shouldInjectMenu}")
if (!url.shouldInjectMenu) injectAndFinish()
}
}
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 d8edc15c..6dbc7c8d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
@@ -14,7 +14,7 @@ import ca.allanwang.kau.utils.circularReveal
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
import ca.allanwang.kau.utils.isVisible
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.utils.Prefs
import io.reactivex.Scheduler
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -41,7 +41,7 @@ class FrostWebViewCore @JvmOverloads constructor(
var baseUrl: String? = null
- var baseEnum: FbTab? = null //only viewpager items should pass the base enum
+ var baseEnum: FbItem? = null //only viewpager items should pass the base enum
internal lateinit var frostWebClient: FrostWebViewClient
init {
@@ -76,7 +76,7 @@ class FrostWebViewCore @JvmOverloads constructor(
if (isVisible) fadeOut(duration = 200L)
} else if (loading) {
dispose?.dispose()
- if (animate && Prefs.animate) circularReveal(offset = 150L)
+ if (animate && Prefs.animate) circularReveal(offset = WEB_LOAD_DELAY)
else fadeIn(duration = 100L)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt
new file mode 100644
index 00000000..50f2f6bc
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/HeadlessHtmlExtractor.kt
@@ -0,0 +1,88 @@
+package com.pitchedapps.frost.web
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.webkit.JavascriptInterface
+import android.webkit.WebView
+import ca.allanwang.kau.utils.gone
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
+import com.pitchedapps.frost.injectors.InjectorContract
+import com.pitchedapps.frost.utils.L
+import io.reactivex.Single
+import io.reactivex.SingleEmitter
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import org.jetbrains.anko.runOnUiThread
+import java.util.concurrent.TimeUnit
+
+/**
+ * Created by Allan Wang on 2017-08-12.
+ *
+ * Launches a headless html request and returns a result pair
+ * When successful, the pair will contain the html content and -1
+ * When unsuccessful, the pair will contain an empty string and a StringRes for the given error
+ *
+ * All errors are rerouted to success calls, so no exceptions should occur.
+ * The headless extractor will also destroy itself on cancellation or when the request is finished
+ */
+fun Context.launchHeadlessHtmlExtractor(url: String, injector: InjectorContract, action: (Single<Pair<String, Int>>) -> Unit) {
+ val single = Single.create<Pair<String, Int>> { e: SingleEmitter<Pair<String, Int>> ->
+ val extractor = HeadlessHtmlExtractor(this, url, injector, e)
+ e.setCancellable {
+ runOnUiThread { extractor.destroy() }
+ e.onSuccess("" to R.string.html_extraction_cancelled)
+ }
+ }.subscribeOn(AndroidSchedulers.mainThread())
+ .timeout(20, TimeUnit.SECONDS, Schedulers.io(), { it.onSuccess("" to R.string.html_extraction_timeout) })
+ .onErrorReturn { "" to R.string.html_extraction_error }
+ action(single)
+}
+
+/**
+ * Given a link and some javascript, will load the link and load the JS on completion
+ * The JS is expected to call [HeadlessHtmlExtractor.HtmlJSI.handleHtml], which will be sent
+ * to the [emitter]
+ */
+@SuppressLint("ViewConstructor")
+private class HeadlessHtmlExtractor(
+ context: Context, url: String, val injector: InjectorContract, val emitter: SingleEmitter<Pair<String, Int>>
+) : WebView(context) {
+
+ val startTime = System.currentTimeMillis()
+
+ init {
+ L.v("Created HeadlessHtmlExtractor for $url")
+ gone()
+ setupWebview(url)
+ }
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private fun setupWebview(url: String) {
+ settings.javaScriptEnabled = true
+ settings.userAgentString = USER_AGENT_BASIC
+ webViewClient = HeadlessWebViewClient(url, injector) // basic client that loads our JS once the page has loaded
+ webChromeClient = QuietChromeClient() // basic client that disables logging
+ addJavascriptInterface(HtmlJSI(), "Frost")
+ loadUrl(url)
+ }
+
+ inner class HtmlJSI {
+ @JavascriptInterface
+ fun handleHtml(html: String?) {
+ val time = System.currentTimeMillis() - startTime
+ emitter.onSuccess((html ?: "") to -1)
+ post {
+ L.d("HeadlessHtmlExtractor fetched $url in $time ms")
+ settings.javaScriptEnabled = false
+ settings.blockNetworkLoads = true
+ destroy()
+ }
+ }
+ }
+
+ override fun destroy() {
+ super.destroy()
+ L.d("HeadlessHtmlExtractor destroyed")
+ }
+} \ No newline at end of file
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 31be4450..aea25337 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
@@ -60,7 +60,7 @@ class LoginWebView @JvmOverloads constructor(
view.jsInject(CssHider.HEADER.maybe(containsFacebook),
CssHider.CORE.maybe(containsFacebook),
Prefs.themeInjector.maybe(containsFacebook),
- callback = { if (!view.isVisible) view.fadeIn(offset = 150L) })
+ callback = { if (!view.isVisible) view.fadeIn(offset = WEB_LOAD_DELAY) })
}
fun checkForLogin(url: String?, onFound: (id: Long, cookie: String) -> Unit) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt
deleted file mode 100644
index 53fa0657..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/MessageWebView.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.pitchedapps.frost.web
-
-import android.annotation.SuppressLint
-import android.app.job.JobParameters
-import android.webkit.JavascriptInterface
-import android.webkit.WebView
-import ca.allanwang.kau.utils.gone
-import com.pitchedapps.frost.dbflow.CookieModel
-import com.pitchedapps.frost.facebook.FbTab
-import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
-import com.pitchedapps.frost.injectors.JsAssets
-import com.pitchedapps.frost.services.NotificationService
-import com.pitchedapps.frost.utils.L
-import com.pitchedapps.frost.utils.frostAnswersCustom
-import org.jetbrains.anko.doAsync
-
-/**
- * Created by Allan Wang on 2017-07-17.
- *
- * Bare boned headless view made solely to extract conversation info
- */
-@SuppressLint("ViewConstructor")
-class MessageWebView(val service: NotificationService, val params: JobParameters?, val cookie: CookieModel) : WebView(service) {
-
- private val startTime = System.currentTimeMillis()
- private var isCancelled = false
-
- init {
- gone()
- setupWebview()
- }
-
- @SuppressLint("SetJavaScriptEnabled")
- private fun setupWebview() {
- settings.javaScriptEnabled = true
- settings.userAgentString = USER_AGENT_BASIC
- webViewClient = HeadlessWebViewClient("MessageNotifs", JsAssets.NOTIF_MSG)
- webChromeClient = QuietChromeClient()
- addJavascriptInterface(MessageJSI(), "Frost")
- loadUrl(FbTab.MESSAGES.url)
- }
-
- fun finish() {
- if (isCancelled) return
- isCancelled = true
- post { destroy() }
- service.finish(params)
- }
-
- override fun destroy() {
- L.d("MessageWebView destroyed")
- super.destroy()
- }
-
- inner class MessageJSI {
- @JavascriptInterface
- fun handleHtml(html: String) {
- if (isCancelled) return
- if (html.length < 10) return finish()
- val time = System.currentTimeMillis() - startTime
- L.d("Notif messages fetched in $time ms")
- frostAnswersCustom("NotificationTime", "Type" to "IM Headless", "Duration" to time)
- doAsync { service.fetchMessageNotifications(cookie, html); finish() }
- }
- }
-
-} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt
index 05d56f92..da6d8ad3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/SearchWebView.kt
@@ -6,7 +6,7 @@ import android.webkit.JavascriptInterface
import android.webkit.WebView
import ca.allanwang.kau.searchview.SearchItem
import ca.allanwang.kau.utils.gone
-import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.injectors.JsAssets
import com.pitchedapps.frost.injectors.JsBuilder
@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit
*/
class SearchWebView(context: Context, val contract: SearchContract) : WebView(context) {
- val searchSubject = PublishSubject.create<String>()
+ val searchSubject = PublishSubject.create<String>()!!
init {
gone()
@@ -39,11 +39,11 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co
* Contains the last item's href (search more) as well as the number of items found
* This holder is synchronized
*/
- var previousResult: Pair<String?, Int> = Pair(null, 0)
+ var previousResult: Pair<String, Int> = Pair("", 0)
fun saveResultFrame(result: List<Pair<List<String>, String>>) {
synchronized(previousResult) {
- previousResult = Pair(result.lastOrNull()?.second, result.size)
+ previousResult = Pair(result.last().second, result.size)
}
}
@@ -56,17 +56,22 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co
addJavascriptInterface(SearchJSI(), "Frost")
searchSubject.debounce(300, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread())
.map {
- Jsoup.parse(it).select("a:not([rel*='keywords(']):not([href=#])[rel]").map {
+ val doc = Jsoup.parse(it)
+ L.d(doc.getElementById("main-search_input")?.html())
+ val searchQuery = doc.getElementById("main-search-input")?.text() ?: "Null input"
+ L.d("Search query", searchQuery)
+ doc.select("a:not([rel*='keywords(']):not([href=#])[rel]").map {
element ->
//split text into separate items
- L.v("Search element ${element.attr("href")}")
- val texts = element.select("div").map { (it.text()) }.filter { it.isNotBlank() }
+ L.v("Search element", element.attr("href"))
+ val texts = element.select("div").map { it.text() }.filter { !it.isNullOrBlank() }
val pair = Pair(texts, element.attr("href"))
- L.v("Search element potential $pair")
+ L.v("Search element potential", pair.toString())
pair
}.filter { it.first.isNotEmpty() }
}
- .filter { content -> Pair(content.lastOrNull()?.second, content.size) != previousResult }
+ .filter { it.isNotEmpty() }
+ .filter { Pair(it.last().second, it.size) != previousResult }
.subscribe {
content: List<Pair<List<String>, String>> ->
saveResultFrame(content)
@@ -90,7 +95,7 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co
}
override fun reload() {
- super.loadUrl(FbTab.SEARCH.url)
+ super.loadUrl(FbItem.SEARCH.url)
}
/**
@@ -104,7 +109,8 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co
inner class SearchJSI {
@JavascriptInterface
- fun handleHtml(html: String) {
+ fun handleHtml(html: String?) {
+ html ?: return
L.d("Search received response ${contract.isSearchOpened}")
if (!contract.isSearchOpened) pauseLoad = true
searchSubject.onNext(html)
@@ -117,11 +123,14 @@ class SearchWebView(context: Context, val contract: SearchContract) : WebView(co
L.d("Search loaded successfully")
}
1 -> { //something is not found in the search view; this is effectively useless
- L.eThrow("Search subject error; reverting to full overlay")
+ L.e("Search subject error; reverting to full overlay")
Prefs.searchBar = false
searchSubject.onComplete()
contract.searchOverlayDispose()
}
+ 2 -> {
+ L.v("Search emission received")
+ }
}
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt
new file mode 100644
index 00000000..ad1fe467
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/WebStates.kt
@@ -0,0 +1,11 @@
+package com.pitchedapps.frost.web
+
+/**
+ * Created by Allan Wang on 2017-08-08.
+ *
+ * Global variables that are define states or constants for web contents
+ */
+const val WEB_LOAD_DELAY = 50L
+var shouldLoadImages = false
+
+val consoleBlacklist = setOf("edge-chat") \ No newline at end of file