aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2018-01-10 22:13:28 -0500
committerGitHub <noreply@github.com>2018-01-10 22:13:28 -0500
commitfd5f2a82eb968b5d50f586925ebb705249062446 (patch)
tree7e2cb3edad1e2398d74eb2780a912ed05188db41 /app/src/main/kotlin
parentad97b4ff946b4ba3a3f7ac880575eed9de810166 (diff)
downloadfrost-fd5f2a82eb968b5d50f586925ebb705249062446.tar.gz
frost-fd5f2a82eb968b5d50f586925ebb705249062446.tar.bz2
frost-fd5f2a82eb968b5d50f586925ebb705249062446.zip
Misc (#614)
* Add locale log * Add flyweight design for authenticator * Add option to have instant messages only * Update interceptor * Add hd image model loader (#613) * Launch image view for view full image * Update changelog * Greatly improve ImageActivity loading * Update hashes * Add back keyword filter * Clean up
Diffstat (limited to 'app/src/main/kotlin')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt7
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt153
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt1
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt31
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt67
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt32
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt13
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/iitems/MenuIItem.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/parsers/NotifParser.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/parsers/SearchParser.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/rx/RxFlyweight.kt86
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt10
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt21
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt51
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt12
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt30
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt11
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt4
26 files changed, 403 insertions, 171 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
index 8897e804..67785a60 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -7,7 +7,6 @@ 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
import com.crashlytics.android.Crashlytics
@@ -18,6 +17,7 @@ import com.pitchedapps.frost.dbflow.CookiesDb
import com.pitchedapps.frost.dbflow.FbTabsDb
import com.pitchedapps.frost.dbflow.NotificationDb
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.services.setupNotificationChannels
import com.pitchedapps.frost.utils.FrostPglAdBlock
@@ -86,8 +86,9 @@ class FrostApp : Application() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String) {
val c = imageView.context
- val old = Glide.with(c).load(uri).apply(RequestOptions().placeholder(placeholder))
- Glide.with(c).load(uri).apply(RequestOptions()
+ val request = GlideApp.with(c)
+ val old = request.load(uri).apply(RequestOptions().placeholder(placeholder))
+ request.load(uri).apply(RequestOptions()
.signature(ApplicationVersionSignature.obtain(c)))
.thumbnail(old).into(imageView)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
index ffcbadab..3a01e05b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt
@@ -119,10 +119,6 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
viewPager.offscreenPageLimit = TAB_COUNT
setupDrawer(savedInstanceState)
-// fab.setOnClickListener { view ->
-// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
-// .setAction("Action", null).show()
-// }
setFrostColors {
toolbar(toolbar)
themeWindow = false
@@ -198,7 +194,7 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract,
}
}
-3L -> launchNewTask<LoginActivity>(clearStack = false)
- -4L -> launchNewTask<SelectorActivity>( cookies(), false)
+ -4L -> launchNewTask<SelectorActivity>(cookies(), false)
else -> {
FbCookie.switchUser(profile.identifier, this@BaseMainActivity::refreshAll)
tabsForEachView { _, view -> view.badgeText = null }
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 99fa6eee..cdde8311 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -1,12 +1,8 @@
package com.pitchedapps.frost.activities
-import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
-import android.graphics.Bitmap
import android.graphics.Color
-import android.graphics.drawable.Drawable
-import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.support.design.widget.FloatingActionButton
@@ -20,24 +16,22 @@ import ca.allanwang.kau.mediapicker.scanMedia
import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.utils.*
-import com.bumptech.glide.Glide
-import com.bumptech.glide.RequestManager
-import com.bumptech.glide.request.target.BaseTarget
-import com.bumptech.glide.request.target.SizeReadyCallback
-import com.bumptech.glide.request.target.Target
-import com.bumptech.glide.request.transition.Transition
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.typeface.IIcon
import com.pitchedapps.frost.R
+import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER
+import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.facebook.requests.call
import com.pitchedapps.frost.utils.*
import com.sothree.slidinguppanel.SlidingUpPanelLayout
import okhttp3.Request
import org.jetbrains.anko.activityUiThreadWithContext
import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.uiThread
import java.io.File
+import java.io.FileFilter
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
@@ -70,12 +64,13 @@ class ImageActivity : KauBaseActivity() {
internal var savedFile: File? = null
/**
* Indicator for fab's click result
+ * Can be called from any thread
*/
internal var fabAction: FabStates = FabStates.NOTHING
set(value) {
if (field == value) return
field = value
- value.update(fab)
+ runOnUiThread { value.update(fab) }
}
companion object {
@@ -87,25 +82,27 @@ class ImageActivity : KauBaseActivity() {
private const val TIME_FORMAT = "yyyyMMdd_HHmmss"
private const val IMG_TAG = "Frost"
private const val IMG_EXTENSION = ".png"
+ private const val PURGE_TIME: Long = 10 * 60 * 1000 // 10 min block
private val L = KauLoggerExtension("Image", com.pitchedapps.frost.utils.L)
}
- val imageUrl: String by lazy { intent.getStringExtra(ARG_IMAGE_URL).trim('"') }
+ val IMAGE_URL: String by lazy { intent.getStringExtra(ARG_IMAGE_URL).trim('"') }
- val text: String? by lazy { intent.getStringExtra(ARG_TEXT) }
+ val TEXT: String? by lazy { intent.getStringExtra(ARG_TEXT) }
- private val glide: RequestManager by lazy { Glide.with(this) }
+ // a unique image identifier based on the id (if it exists), and its hash
+ val IMAGE_HASH: String by lazy { "${Math.abs(FB_IMAGE_ID_MATCHER.find(IMAGE_URL)[1]?.hashCode() ?: 0)}_${Math.abs(IMAGE_URL.hashCode())}" }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent?.extras ?: return finish()
L.i { "Displaying image" }
- val layout = if (!text.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless
+ val layout = if (!TEXT.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless
setContentView(layout)
container.setBackgroundColor(Prefs.bgColor.withMinAlpha(222))
caption?.setTextColor(Prefs.textColor)
caption?.setBackgroundColor(Prefs.bgColor.colorToForeground(0.2f).withAlpha(255))
- caption?.text = text
+ caption?.text = TEXT
progress.tint(Prefs.accentColor)
panel?.addPanelSlideListener(object : SlidingUpPanelLayout.SimplePanelSlideListener() {
override fun onPanelSlide(panel: View, slideOffset: Float) {
@@ -119,90 +116,76 @@ class ImageActivity : KauBaseActivity() {
override fun onImageLoadError(e: Exception?) {
errorRef = e
e.logFrostAnswers("Image load error")
- imageCallback(null, false)
+ fabAction = FabStates.ERROR
}
})
- glide.asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback))
setFrostColors {
themeWindow = false
}
- }
-
- /**
- * Callback to add image to view
- * [resource] is guaranteed to be nonnull when [success] is true
- * and null when it is false
- */
- private fun imageCallback(resource: Bitmap?, success: Boolean) {
- if (progress.isVisible) progress.fadeOut()
- if (success) {
- saveTempImage(resource!!, {
- if (it == null) {
- imageCallback(null, false)
- } else {
- photo.setImage(ImageSource.uri(it))
+ doAsync({
+ L.e(it) { "Failed to load image $IMAGE_HASH" }
+ errorRef = it
+ fabAction = FabStates.ERROR
+ }) {
+ loadImage { file ->
+ if (file == null) {
+ fabAction = FabStates.ERROR
+ return@loadImage
+ }
+ tempFile = file
+ L.d { "Temp image path ${file.absolutePath}" }
+ uiThread {
+ photo.setImage(ImageSource.uri(frostUriFromFile(file)))
fabAction = FabStates.DOWNLOAD
- photo.animate().alpha(1f).scaleXY(1f).withEndAction(fab::show).start()
+ photo.animate().alpha(1f).scaleXY(1f).start()
}
- })
- } else {
- fabAction = FabStates.ERROR
- fab.show()
+ }
}
}
/**
- * Bitmap load handler
+ * Returns a file pointing to the image, or null if something goes wrong
*/
- class PhotoTarget(val callback: (resource: Bitmap?, success: Boolean) -> Unit) : BaseTarget<Bitmap>() {
-
- override fun removeCallback(cb: SizeReadyCallback?) {}
-
- override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) =
- callback(resource, true)
+ private inline fun loadImage(callback: (file: File?) -> Unit) {
+ val local = File(tempDir, IMAGE_HASH)
+ if (local.exists() && local.length() > 1) {
+ local.setLastModified(System.currentTimeMillis())
+ L.d { "Loading from local cache ${local.absolutePath}" }
+ return callback(local)
+ }
+ val response = Request.Builder()
+ .url(IMAGE_URL)
+ .get()
+ .call()
+ .execute()
- override fun onLoadFailed(errorDrawable: Drawable?) =
- callback(null, false)
+ if (!response.isSuccessful) {
+ L.e { "Unsuccessful response for image" }
+ errorRef = Throwable("Unsuccessful response for image")
+ return callback(null)
+ }
- override fun getSize(cb: SizeReadyCallback) =
- cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ if (!local.createFreshFile()) {
+ L.e { "Could not create temp file" }
+ return callback(null)
+ }
- }
+ var valid = false
- private fun saveTempImage(resource: Bitmap, callback: (uri: Uri?) -> Unit) {
- var photoFile: File? = null
- try {
- photoFile = createPrivateMediaFile()
- } catch (e: IOException) {
- errorRef = e
- logImage(e)
- } finally {
- if (photoFile == null) {
- callback(null)
- } else {
- tempFile = photoFile
- L.d { "Temp image path ${tempFile?.absolutePath}" }
- // File created; proceed with request
- val photoURI = frostUriFromFile(photoFile)
- photoFile.outputStream().use { resource.compress(Bitmap.CompressFormat.PNG, 100, it) }
- callback(photoURI)
+ response.body()?.byteStream()?.use { input ->
+ local.outputStream().use { output ->
+ input.copyTo(output)
+ valid = true
}
}
- }
- private fun logImage(e: Exception?) {
- if (!Prefs.analytics) return
- val error = e ?: IOException("$imageUrl failed to load")
- L.e(error) { "$imageUrl failed to load" }
- }
+ if (!valid) {
+ L.e { "Failed to copy file" }
+ local.delete()
+ return callback(null)
+ }
- @Throws(IOException::class)
- private fun createPrivateMediaFile(): File {
- val timeStamp = SimpleDateFormat(TIME_FORMAT, Locale.getDefault()).format(Date())
- val imageFileName = "${IMG_TAG}_${timeStamp}_"
- if (!tempDir.exists())
- tempDir.mkdirs()
- return File.createTempFile(imageFileName, IMG_EXTENSION, tempDir)
+ callback(local)
}
@Throws(IOException::class)
@@ -218,7 +201,7 @@ class ImageActivity : KauBaseActivity() {
@Throws(IOException::class)
private fun downloadImageTo(file: File) {
val body = Request.Builder()
- .url(imageUrl)
+ .url(IMAGE_URL)
.get()
.call()
.execute()
@@ -270,8 +253,10 @@ class ImageActivity : KauBaseActivity() {
override fun onDestroy() {
tempFile = null
- tempDir.deleteRecursively()
- L.d { "Closing $localClassName" }
+ val purge = System.currentTimeMillis() - PURGE_TIME
+ tempDir.listFiles(FileFilter { it.isFile && it.lastModified() < purge }).forEach {
+ it.delete()
+ }
super.onDestroy()
}
}
@@ -287,7 +272,7 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC
if (activity.errorRef != null)
L.e(activity.errorRef) { "ImageActivity error report" }
activity.sendFrostEmail(R.string.debug_image_link_subject) {
- addItem("Url", activity.imageUrl)
+ addItem("Url", activity.IMAGE_URL)
addItem("Message", activity.errorRef?.message ?: "Null")
}
}
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 2434c8c2..3b320cce 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/LoginActivity.kt
@@ -10,7 +10,6 @@ import android.widget.ImageView
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
-import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
@@ -24,6 +23,7 @@ import com.pitchedapps.frost.dbflow.loadFbCookiesAsync
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
import com.pitchedapps.frost.glide.FrostGlide
+import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.glide.transform
import com.pitchedapps.frost.utils.*
import com.pitchedapps.frost.web.LoginWebView
@@ -62,7 +62,7 @@ class LoginActivity : BaseActivity() {
setContentView(R.layout.activity_login)
setSupportActionBar(toolbar)
setTitle(R.string.kau_login)
- setFrostColors{
+ setFrostColors {
toolbar(toolbar)
}
web.loadLogin({ refresh = it != 100 }) { cookie ->
@@ -73,7 +73,7 @@ class LoginActivity : BaseActivity() {
loadInfo(cookie)
})
}
- profileLoader = Glide.with(profile)
+ profileLoader = GlideApp.with(profile)
}
private fun loadInfo(cookie: CookieModel) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt
index acc23cad..4ba3c80d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbRegex.kt
@@ -25,6 +25,7 @@ val FB_NOTIF_ID_MATCHER: Regex by lazy { Regex("notif_([0-9]+)") }
val FB_MESSAGE_NOTIF_ID_MATCHER: Regex by lazy { Regex("[thread|user]_fbid_([0-9]+)") }
val FB_CSS_URL_MATCHER: Regex by lazy { Regex("url\\([\"|']?(.*?)[\"|']?\\)") }
val FB_JSON_URL_MATCHER: Regex by lazy { Regex("\"(http.*?)\"") }
+val FB_IMAGE_ID_MATCHER: Regex by lazy { Regex("fbcdn.*?/[0-9]+_([0-9]+)_") }
operator fun MatchResult?.get(groupIndex: Int) = this?.groupValues?.get(groupIndex)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt
index ae8652e6..3ca37bb4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/FbRequest.kt
@@ -2,6 +2,7 @@ package com.pitchedapps.frost.facebook.requests
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.facebook.*
+import com.pitchedapps.frost.rx.RxFlyweight
import com.pitchedapps.frost.utils.L
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
@@ -12,7 +13,17 @@ import org.apache.commons.text.StringEscapeUtils
/**
* Created by Allan Wang on 21/12/17.
*/
-private val authMap: MutableMap<String, RequestAuth> = mutableMapOf()
+private class RxAuth : RxFlyweight<String, Long, RequestAuth>() {
+
+ override fun call(input: String) = input.getAuth()
+
+ override fun validate(input: String, cond: Long) =
+ System.currentTimeMillis() - cond < 3600000 // valid for an hour
+
+ override fun cache(input: String) = System.currentTimeMillis()
+}
+
+private val auth = RxAuth()
/**
* Synchronously fetch [RequestAuth] from cookie
@@ -21,18 +32,13 @@ private val authMap: MutableMap<String, RequestAuth> = mutableMapOf()
*/
fun String?.fbRequest(fail: () -> Unit = {}, action: RequestAuth.() -> Unit) {
if (this == null) return fail()
- val savedAuth = authMap[this]
- if (savedAuth != null) {
- savedAuth.action()
- } else {
- val auth = getAuth()
- if (!auth.isValid) {
- L.e { "Attempted fbrequest with invalid auth" }
- return fail()
+ auth(this).subscribe { a: RequestAuth?, _ ->
+ if (a?.isValid == true)
+ a.action()
+ else {
+ L.e { "Failed auth for ${hashCode()}" }
+ fail()
}
- authMap.put(this, auth)
- L._i { "Found auth $auth" }
- auth.action()
}
}
@@ -94,6 +100,7 @@ private fun String.requestBuilder() = Request.Builder()
fun Request.Builder.call() = client.newCall(build())!!
fun String.getAuth(): RequestAuth {
+ L.v { "Getting auth for ${hashCode()}" }
var auth = RequestAuth(cookie = this)
val id = FB_USER_MATCHER.find(this)[1]?.toLong() ?: return auth
auth = auth.copy(userId = id)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt
index 61a94ac5..fa78bbfa 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt
@@ -1,9 +1,19 @@
package com.pitchedapps.frost.facebook.requests
import com.bumptech.glide.Priority
+import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.DataSource
+import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher
+import com.bumptech.glide.load.model.ModelLoader
+import com.bumptech.glide.load.model.ModelLoaderFactory
+import com.bumptech.glide.load.model.MultiModelLoaderFactory
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.signature.ObjectKey
+import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER
import com.pitchedapps.frost.facebook.FB_URL_BASE
+import com.pitchedapps.frost.facebook.get
import okhttp3.Call
import okhttp3.Request
import java.io.IOException
@@ -17,8 +27,54 @@ fun RequestAuth.getFullSizedImage(fbid: Long) = frostRequest(::getJsonUrl) {
get()
}
-class ImageFbidFetcher(private val fbid: Long,
- private val cookie: String) : DataFetcher<InputStream> {
+/**
+ * Request loader for a potentially hd version of a url
+ * In this case, each url may potentially return an id,
+ * which may potentially be used to fetch a higher res image url
+ * The following aims to allow such loading while adhering to Glide's lifecycle
+ */
+data class HdImageMaybe(val url: String, val cookie: String) {
+
+ val id: Long by lazy { FB_IMAGE_ID_MATCHER.find(url)[1]?.toLongOrNull() ?: -1 }
+
+ val isValid: Boolean by lazy {
+ id != -1L && cookie.isNotBlank()
+ }
+
+}
+
+/*
+ * The following was a test to see if hd image loading would work
+ *
+ * It's working and tested, though the improvements aren't really worth the extra data use
+ * and reload
+ */
+
+class HdImageLoadingFactory : ModelLoaderFactory<HdImageMaybe, InputStream> {
+
+ override fun build(multiFactory: MultiModelLoaderFactory) = HdImageLoading()
+
+ override fun teardown() = Unit
+}
+
+fun <T> RequestBuilder<T>.loadWithPotentialHd(model: HdImageMaybe) =
+ thumbnail(clone().load(model.url))
+ .load(model)
+ .apply(RequestOptions().override(Target.SIZE_ORIGINAL))
+
+class HdImageLoading : ModelLoader<HdImageMaybe, InputStream> {
+
+ override fun buildLoadData(model: HdImageMaybe,
+ width: Int,
+ height: Int,
+ options: Options?): ModelLoader.LoadData<InputStream>? =
+ if (!model.isValid) null
+ else ModelLoader.LoadData(ObjectKey(model), HdImageFetcher(model))
+
+ override fun handles(model: HdImageMaybe) = model.isValid
+}
+
+class HdImageFetcher(private val model: HdImageMaybe) : DataFetcher<InputStream> {
@Volatile private var cancelled: Boolean = false
private var urlCall: Call? = null
@@ -33,10 +89,12 @@ class ImageFbidFetcher(private val fbid: Long,
override fun getDataSource(): DataSource = DataSource.REMOTE
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
- cookie.fbRequest(fail = { callback.fail("Invalid auth") }) {
+ if (!model.isValid) return callback.fail("Model is invalid")
+ model.cookie.fbRequest(fail = { callback.fail("Invalid auth") }) {
if (cancelled) return@fbRequest callback.fail("Cancelled")
- val url = getFullSizedImage(fbid).invoke() ?: return@fbRequest callback.fail("Null url")
+ val url = getFullSizedImage(model.id).invoke() ?: return@fbRequest callback.fail("Null url")
if (cancelled) return@fbRequest callback.fail("Cancelled")
+ if (!url.contains("png") && !url.contains("jpg")) return@fbRequest callback.fail("Invalid format")
urlCall = Request.Builder().url(url).get().call()
inputStream = try {
@@ -44,7 +102,6 @@ class ImageFbidFetcher(private val fbid: Long,
} catch (e: IOException) {
null
}
-
callback.onDataReady(inputStream)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt
new file mode 100644
index 00000000..0e37a61e
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Messages.kt
@@ -0,0 +1,32 @@
+package com.pitchedapps.frost.facebook.requests
+
+import com.pitchedapps.frost.facebook.FB_URL_BASE
+import okhttp3.Call
+
+/**
+ * Created by Allan Wang on 07/01/18.
+ */
+fun RequestAuth.sendMessage(group: String, content: String): FrostRequest<Boolean> {
+
+ // todo test more; only tested against tids=cid...
+ val body = listOf(
+ "tids" to group,
+ "body" to content,
+ "fb_dtsg" to fb_dtsg,
+ "__user" to userId
+ ).withEmptyData("m_sess", "__dyn", "__req", "__ajax__")
+
+ return frostRequest(::validateMessage) {
+ url("${FB_URL_BASE}messages/send")
+ post(body.toForm())
+ }
+}
+
+/**
+ * Messages are a bit weird with their responses
+ */
+private fun validateMessage(call: Call): Boolean {
+ val body = call.execute().body() ?: return false
+ // todo
+ return true
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt
index 6d2b3cda..651e57d8 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/glide/GlideUtils.kt
@@ -1,9 +1,14 @@
package com.pitchedapps.frost.glide
+import android.content.Context
+import com.bumptech.glide.Glide
+import com.bumptech.glide.Registry
import com.bumptech.glide.RequestBuilder
+import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.MultiTransformation
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.bumptech.glide.load.resource.bitmap.CircleCrop
+import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions
/**
@@ -25,3 +30,11 @@ fun <T> RequestBuilder<T>.transform(vararg transformation: BitmapTransformation)
1 -> apply(RequestOptions.bitmapTransform(transformation[0]))
else -> apply(RequestOptions.bitmapTransform(MultiTransformation(*transformation)))
}
+
+@GlideModule
+class FrostGlideModule : AppGlideModule() {
+
+ override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
+// registry.prepend(HdImageMaybe::class.java, InputStream::class.java, HdImageLoadingFactory())
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/iitems/MenuIItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/iitems/MenuIItem.kt
index 690d1be8..f5b1df17 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/iitems/MenuIItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/iitems/MenuIItem.kt
@@ -9,14 +9,13 @@ import ca.allanwang.kau.ui.createSimpleRippleDrawable
import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.gone
import ca.allanwang.kau.utils.visible
-import com.bumptech.glide.Glide
import com.mikepenz.fastadapter.FastAdapter
import com.pitchedapps.frost.R
import com.pitchedapps.frost.facebook.requests.MenuFooterItem
import com.pitchedapps.frost.facebook.requests.MenuHeader
import com.pitchedapps.frost.facebook.requests.MenuItem
import com.pitchedapps.frost.glide.FrostGlide
-import com.pitchedapps.frost.glide.transform
+import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.utils.Prefs
/**
@@ -42,7 +41,8 @@ class MenuContentIItem(val data: MenuItem)
badge.setTextColor(Prefs.textColor)
val iconUrl = item.data.pic
if (iconUrl != null)
- Glide.with(itemView).load(iconUrl)
+ GlideApp.with(itemView)
+ .load(iconUrl)
.transform(FrostGlide.roundCorner)
.into(icon.visible())
else
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt
index 2e16f386..8bc2c1fe 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/iitems/NotificationIItem.kt
@@ -10,12 +10,11 @@ import ca.allanwang.kau.utils.bindView
import ca.allanwang.kau.utils.gone
import ca.allanwang.kau.utils.visible
import ca.allanwang.kau.utils.withAlpha
-import com.bumptech.glide.Glide
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import com.pitchedapps.frost.R
import com.pitchedapps.frost.glide.FrostGlide
-import com.pitchedapps.frost.glide.transform
+import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.parsers.FrostNotif
import com.pitchedapps.frost.services.FrostRunnable
import com.pitchedapps.frost.utils.Prefs
@@ -52,7 +51,7 @@ class NotificationIItem(val notification: FrostNotif, val cookie: String) : KauI
val thumbnail: ImageView by bindView(R.id.item_thumbnail)
private val glide
- get() = Glide.with(itemView)
+ get() = GlideApp.with(itemView)
override fun bindView(item: NotificationIItem, payloads: MutableList<Any>) {
val notif = item.notification
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt
index f0938eca..d5730e16 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/parsers/FrostParser.kt
@@ -25,6 +25,12 @@ import org.jsoup.select.Elements
interface FrostParser<out T : Any> {
/**
+ * Name associated to parser
+ * Purely for display
+ */
+ var nameRes: Int
+
+ /**
* Url to request from
*/
val url: String
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt
index 24ddd601..697cbbe8 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/parsers/MessageParser.kt
@@ -69,6 +69,8 @@ data class FrostThread(val id: Long,
private class MessageParserImpl : FrostParserBase<FrostMessages>(true) {
+ override var nameRes = FbItem.MESSAGES.titleId
+
override val url = FbItem.MESSAGES.url
override fun textToDoc(text: String): Document? {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/parsers/NotifParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/parsers/NotifParser.kt
index 23852852..812f12e3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/parsers/NotifParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/parsers/NotifParser.kt
@@ -60,6 +60,8 @@ data class FrostNotif(val id: Long,
private class NotifParserImpl : FrostParserBase<FrostNotifs>(false) {
+ override var nameRes = FbItem.NOTIFICATIONS.titleId
+
override val url = FbItem.NOTIFICATIONS.url
override fun parseImpl(doc: Document): FrostNotifs? {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/parsers/SearchParser.kt b/app/src/main/kotlin/com/pitchedapps/frost/parsers/SearchParser.kt
index 7fbc0f08..5300bf11 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/parsers/SearchParser.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/parsers/SearchParser.kt
@@ -55,6 +55,8 @@ data class FrostSearch(val href: String, val title: String, val description: Str
private class SearchParserImpl : FrostParserBase<FrostSearches>(false) {
+ override var nameRes = FbItem._SEARCH.titleId
+
override val url = "${FbItem._SEARCH.url}?q=a"
override fun parseImpl(doc: Document): FrostSearches? {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/rx/RxFlyweight.kt b/app/src/main/kotlin/com/pitchedapps/frost/rx/RxFlyweight.kt
new file mode 100644
index 00000000..159a9bf2
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/rx/RxFlyweight.kt
@@ -0,0 +1,86 @@
+package com.pitchedapps.frost.rx
+
+import io.reactivex.Single
+import io.reactivex.schedulers.Schedulers
+import java.util.concurrent.TimeUnit
+
+/**
+ * Created by Allan Wang on 07/01/18.
+ *
+ * Reactive flyweight to help deal with prolonged executions
+ * Each call will output a [Single], which may be new if none exist or the old one is invalidated,
+ * or reused if an old one is still valid
+ *
+ * Types:
+ * T input argument for caller
+ * C condition condition to check against for validity
+ * R response response within reactive output
+ */
+abstract class RxFlyweight<in T : Any, C : Any, R : Any> {
+
+ /**
+ * Given an input emit the desired response
+ * This will be executed in a separate thread
+ */
+ protected abstract fun call(input: T): R
+
+ /**
+ * Given an input and condition, check if
+ * we may used cache data or if we need to make a new request
+ * Return [true] to use cache, [false] otherwise
+ */
+ protected abstract fun validate(input: T, cond: C): Boolean
+
+ /**
+ * Given an input, create a new condition to be used
+ * for future requests
+ */
+ protected abstract fun cache(input: T): C
+
+ private val conditionals = mutableMapOf<T, C>()
+ private val sources = mutableMapOf<T, Single<R>>()
+
+ private val lock = Any()
+
+ /**
+ * Entry point to give an input a receive a [Single]
+ * Note that the observer is not bound to any particular thread,
+ * as it is dependent on [createNewSource]
+ */
+ operator fun invoke(input: T): Single<R> {
+ synchronized(lock) {
+ val source = sources[input]
+
+ // update condition and retrieve old one
+ val condition = conditionals.put(input, cache(input))
+
+ // check to reuse observable
+ if (source != null && condition != null && validate(input, condition))
+ return source
+
+ val newSource = createNewSource(input).cache().doOnError { sources.remove(input) }
+
+ sources.put(input, newSource)
+ return newSource
+ }
+ }
+
+ /**
+ * Open source creator
+ * Result will then be created with [Single.cache]
+ * If you don't have a need for cache,
+ * you likely won't have a need for flyweights
+ */
+ open protected fun createNewSource(input: T): Single<R> =
+ Single.fromCallable { call(input) }
+ .timeout(20, TimeUnit.SECONDS)
+ .subscribeOn(Schedulers.io())
+
+ fun reset() {
+ synchronized(lock) {
+ sources.clear()
+ conditionals.clear()
+ }
+ }
+
+} \ No newline at end of file
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 50392dea..e8c5e7c1 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -19,7 +19,6 @@ import android.support.v4.app.NotificationManagerCompat
import ca.allanwang.kau.utils.color
import ca.allanwang.kau.utils.dpToPx
import ca.allanwang.kau.utils.string
-import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.pitchedapps.frost.BuildConfig
@@ -31,7 +30,7 @@ import com.pitchedapps.frost.dbflow.lastNotificationTime
import com.pitchedapps.frost.enums.OverlayContext
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.glide.FrostGlide
-import com.pitchedapps.frost.glide.transform
+import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.parsers.FrostParser
import com.pitchedapps.frost.parsers.MessageParser
import com.pitchedapps.frost.parsers.NotifParser
@@ -146,7 +145,10 @@ enum class NotificationType(
fun fetch(context: Context, data: CookieModel) {
val response = parser.parse(data.cookie)
?: return L.v { "$name notification data not found" }
- val notifs = response.data.getUnreadNotifications(data)
+ val notifs = response.data.getUnreadNotifications(data).filter {
+ val text = it.text
+ Prefs.notificationKeywords.any { text.contains(it, true) }
+ }
if (notifs.isEmpty()) return
var notifCount = 0
val userId = data.id
@@ -201,7 +203,7 @@ enum class NotificationType(
if (profileUrl != null) {
context.runOnUiThread {
//todo verify if context is valid?
- Glide.with(context)
+ GlideApp.with(context)
.asBitmap()
.load(profileUrl)
.transform(FrostGlide.circleCrop)
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 3d19606b..fc946772 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -61,7 +61,8 @@ class NotificationService : JobService() {
val cookies = loadFbCookiesSync()
cookies.forEach {
val current = it.id == currentId
- if (current || Prefs.notificationAllAccounts)
+ if (Prefs.notificationsGeneral
+ && (current || Prefs.notificationAllAccounts))
NotificationType.GENERAL.fetch(context, it)
if (Prefs.notificationsInstantMessages
&& (current || Prefs.notificationsImAllAccounts))
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
index 3e795e80..3b37d1c3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Debug.kt
@@ -3,6 +3,7 @@ package com.pitchedapps.frost.settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.utils.materialDialog
import ca.allanwang.kau.utils.startActivityForResult
+import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.DebugActivity
import com.pitchedapps.frost.activities.SettingsActivity
@@ -10,6 +11,9 @@ import com.pitchedapps.frost.activities.SettingsActivity.Companion.ACTIVITY_REQU
import com.pitchedapps.frost.debugger.OfflineWebsite
import com.pitchedapps.frost.facebook.FbCookie
import com.pitchedapps.frost.facebook.FbItem
+import com.pitchedapps.frost.parsers.MessageParser
+import com.pitchedapps.frost.parsers.NotifParser
+import com.pitchedapps.frost.parsers.SearchParser
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.frostUriFromFile
import com.pitchedapps.frost.utils.sendFrostEmail
@@ -34,6 +38,23 @@ fun SettingsActivity.getDebugPrefs(): KPrefAdapterBuilder.() -> Unit = {
descRes = R.string.debug_web_desc
onClick = { this@getDebugPrefs.startActivityForResult<DebugActivity>(ACTIVITY_REQUEST_DEBUG) }
}
+
+ plainText(R.string.debug_parsers) {
+ descRes = R.string.debug_parsers_desc
+ onClick = {
+
+ val parsers = arrayOf(NotifParser, MessageParser, SearchParser)
+
+ materialDialog {
+ items(parsers.map { string(it.nameRes) })
+ itemsCallback { dialog, _, position, _ ->
+ dialog.dismiss()
+ // todo add debugging
+ }
+ }
+
+ }
+ }
}
private const val ZIP_NAME = "debug"
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 1108f5d4..2ee086c0 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
@@ -23,7 +23,7 @@ import com.pitchedapps.frost.views.Keywords
fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
text(R.string.notification_frequency, Prefs::notificationFreq, { Prefs.notificationFreq = it }) {
- val options = longArrayOf(-1, 15, 30, 60, 120, 180, 300, 1440, 2880)
+ val options = longArrayOf(15, 30, 60, 120, 180, 300, 1440, 2880)
val texts = options.map { if (it <= 0) string(R.string.no_notifications) else minuteToText(it) }
onClick = {
materialDialogThemed {
@@ -36,6 +36,12 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
})
}
}
+ enabler = {
+ val enabled = Prefs.notificationsGeneral || Prefs.notificationsInstantMessages
+ if (!enabled)
+ scheduleNotifications(-1)
+ enabled
+ }
textGetter = { minuteToText(it) }
}
@@ -52,15 +58,34 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
}
}
- checkbox(R.string.notification_all_accounts, Prefs::notificationAllAccounts, { Prefs.notificationAllAccounts = it }) {
- descRes = R.string.notification_all_accounts_desc
+ checkbox(R.string.notification_general, Prefs::notificationsGeneral,
+ {
+ Prefs.notificationsGeneral = it
+ reloadByTitle(R.string.notification_general_all_accounts)
+ if (!Prefs.notificationsInstantMessages)
+ reloadByTitle(R.string.notification_frequency)
+ }) {
+ descRes = R.string.notification_general_desc
+ }
+
+ checkbox(R.string.notification_general_all_accounts, Prefs::notificationAllAccounts,
+ { Prefs.notificationAllAccounts = it }) {
+ descRes = R.string.notification_general_all_accounts_desc
+ enabler = Prefs::notificationsGeneral
}
- checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages, { Prefs.notificationsInstantMessages = it; reloadByTitle(R.string.notification_messages_all_accounts) }) {
+ checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages,
+ {
+ Prefs.notificationsInstantMessages = it
+ reloadByTitle(R.string.notification_messages_all_accounts)
+ if (!Prefs.notificationsGeneral)
+ reloadByTitle(R.string.notification_frequency)
+ }) {
descRes = R.string.notification_messages_desc
}
- checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts, { Prefs.notificationsImAllAccounts = it }) {
+ checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts,
+ { Prefs.notificationsImAllAccounts = it }) {
descRes = R.string.notification_messages_all_accounts_desc
enabler = Prefs::notificationsInstantMessages
}
@@ -91,22 +116,28 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
}
}
- text(R.string.notification_ringtone, Prefs::notificationRingtone, { Prefs.notificationRingtone = it }) {
+ text(R.string.notification_ringtone, Prefs::notificationRingtone,
+ { Prefs.notificationRingtone = it }) {
ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE)
}
- text(R.string.message_ringtone, Prefs::messageRingtone, { Prefs.messageRingtone = it }) {
+ text(R.string.message_ringtone, Prefs::messageRingtone,
+ { Prefs.messageRingtone = it }) {
ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE)
}
- checkbox(R.string.notification_vibrate, Prefs::notificationVibrate, { Prefs.notificationVibrate = it })
+ checkbox(R.string.notification_vibrate, Prefs::notificationVibrate,
+ { Prefs.notificationVibrate = it })
- checkbox(R.string.notification_lights, Prefs::notificationLights, { Prefs.notificationLights = it })
+ checkbox(R.string.notification_lights, Prefs::notificationLights,
+ { Prefs.notificationLights = it })
plainText(R.string.notification_fetch_now) {
descRes = R.string.notification_fetch_now_desc
onClick = {
- val text = if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail
+ val text =
+ if (fetchNotifications()) R.string.notification_fetch_success
+ else R.string.notification_fetch_fail
frostSnackbar(text)
}
}
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 7bec8ce0..7422fb36 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -113,6 +113,8 @@ object Prefs : KPref() {
var notificationKeywords: StringSet by kpref("notification_keywords", mutableSetOf())
+ var notificationsGeneral: Boolean by kpref("notification_general", true)
+
var notificationAllAccounts: Boolean by kpref("notification_all_accounts", true)
var notificationsInstantMessages: Boolean by kpref("notification_im", true)
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 1fb41dca..486fbae1 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -250,8 +250,15 @@ inline val String?.isFacebookUrl
/**
* [true] if url is a video and can be accepted by VideoViewer
*/
-inline val String?.isVideoUrl
- get() = this != null && (startsWith(VIDEO_REDIRECT) || startsWith("https://video-"))
+inline val String.isVideoUrl
+ get() = startsWith(VIDEO_REDIRECT) || startsWith("https://video-")
+
+/**
+ * [true] if url is or redirects to an explicit facebook image
+ */
+inline val String.isImageUrl
+ get() = (contains("fbcdn.net") && (contains(".png") || contains(".jpg")))
+ || contains("/photo/view_full_size")
/**
* [true] if url can be displayed in a different webview
@@ -308,6 +315,7 @@ fun EmailBuilder.addFrostDetails() {
addItem("Prev version", Prefs.prevVersionCode.toString())
val proTag = if (IS_FROST_PRO) "TY" else "FP"
addItem("Random Frost ID", "${Prefs.frostId}-$proTag")
+ addItem("Locale", Locale.getDefault().displayName)
}
fun frostJsoup(url: String)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
index 64cf34a1..78847df4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt
@@ -7,7 +7,6 @@ import android.view.View
import android.widget.ImageView
import ca.allanwang.kau.iitems.KauIItem
import ca.allanwang.kau.utils.*
-import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
@@ -17,7 +16,7 @@ import com.pitchedapps.frost.R
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
import com.pitchedapps.frost.glide.FrostGlide
-import com.pitchedapps.frost.glide.transform
+import com.pitchedapps.frost.glide.GlideApp
import com.pitchedapps.frost.utils.Prefs
/**
@@ -33,7 +32,7 @@ class AccountItem(val cookie: CookieModel?) : KauIItem<AccountItem, AccountItem.
text.setTextColor(Prefs.textColor)
if (cookie != null) {
text.text = cookie.name
- Glide.with(itemView).load(PROFILE_PICTURE_URL(cookie.id))
+ GlideApp.with(itemView).load(PROFILE_PICTURE_URL(cookie.id))
.transform(FrostGlide.roundCorner).listener(object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
text.fadeIn()
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 454e2a4b..0501e2e6 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostRequestInterceptor.kt
@@ -17,35 +17,13 @@ import java.io.ByteArrayInputStream
*/
private val blankResource: WebResourceResponse by lazy { WebResourceResponse("text/plain", "utf-8", ByteArrayInputStream("".toByteArray())) }
-//these hosts will redirect to a blank resource
-private val blacklistHost: Set<String> =
- setOf(
- // "edge-chat.facebook.com" //todo make more specific? This is required for message responses
- )
-
-//these hosts will return null and skip logging
-private val whitelistHost: Set<String> =
- setOf(
- "static.xx.fbcdn.net",
- "m.facebook.com",
- "touch.facebook.com"
- )
-
-//these hosts will skip ad inspection
-//this list does not have to include anything from the two above
-private val adWhitelistHost: Set<String> =
- setOf(
- "scontent-sea1-1.xx.fbcdn.net"
- )
-
fun WebView.shouldFrostInterceptRequest(request: WebResourceRequest): WebResourceResponse? {
- request.url ?: return null
- val httpUrl = HttpUrl.parse(request.url.toString()) ?: return null
+ val requestUrl = request.url?.toString() ?: return null
+ val httpUrl = HttpUrl.parse(requestUrl) ?: return null
val host = httpUrl.host()
val url = httpUrl.toString()
-// if (blacklistHost.contains(host)) return blankResource
- if (whitelistHost.contains(host)) return null
- if (!adWhitelistHost.contains(host) && FrostPglAdBlock.isAdHost(host)) return blankResource
+ if (host.contains("facebook") || host.contains("fbcdn")) return null
+ if (FrostPglAdBlock.isAdHost(host)) return blankResource
// if (!shouldLoadImages && !Prefs.loadMediaOnMeteredNetwork && request.isMedia) return blankResource
L.v { "Intercept Request: $host $url" }
return null
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
index 9a3dc331..6c09de7c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt
@@ -3,7 +3,6 @@ package com.pitchedapps.frost.web
import com.pitchedapps.frost.activities.WebOverlayActivity
import com.pitchedapps.frost.activities.WebOverlayActivityBase
import com.pitchedapps.frost.activities.WebOverlayBasicActivity
-
import com.pitchedapps.frost.contracts.VideoViewHolder
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
@@ -36,6 +35,11 @@ fun FrostWebView.requestWebOverlay(url: String): Boolean {
context.runOnUiThread { context.showVideo(url) }
return true
}
+ if (url.isImageUrl) {
+ L.d { "Found fb image" }
+ context.launchImageActivity(url.formattedFbUrl, null)
+ return true
+ }
if (!url.isIndependent) {
L.d { "Forbid overlay switch" }
return false
@@ -43,13 +47,14 @@ fun FrostWebView.requestWebOverlay(url: String): Boolean {
if (!Prefs.overlayEnabled) return false
if (context is WebOverlayActivityBase) {
L.v { "Check web request from overlay" }
+ val shouldUseBasic = url.formattedFbUrl.shouldUseBasicAgent
//already overlay; manage user agent
- if (userAgentString != USER_AGENT_BASIC && url.formattedFbUrl.shouldUseBasicAgent) {
+ if (userAgentString != USER_AGENT_BASIC && shouldUseBasic) {
L.i { "Switch to basic agent overlay" }
context.launchWebOverlayBasic(url)
return true
}
- if (context is WebOverlayBasicActivity && !url.formattedFbUrl.shouldUseBasicAgent) {
+ if (context is WebOverlayBasicActivity && !shouldUseBasic) {
L.i { "Switch from basic agent" }
context.launchWebOverlay(url)
return 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 e23ec0f8..d0f7d490 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt
@@ -1,15 +1,11 @@
package com.pitchedapps.frost.web
-import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
-import com.pitchedapps.frost.activities.LoginActivity
-import com.pitchedapps.frost.activities.MainActivity
-import com.pitchedapps.frost.activities.SelectorActivity
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbItem
import com.pitchedapps.frost.injectors.*