aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle1
-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
-rw-r--r--app/src/main/res/values-de-rDE/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-es-rES/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-fr-rFR/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-gl-rES/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-it-rIT/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-ko-rKR/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-pl-rPL/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-pt-rBR/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-vi-rVN/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values-zh-rCN/strings_pref_notifications.xml4
-rw-r--r--app/src/main/res/values/strings_pref_debug.xml3
-rw-r--r--app/src/main/res/values/strings_pref_notifications.xml8
-rw-r--r--app/src/main/res/xml/frost_changelog.xml4
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt8
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt27
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt61
43 files changed, 530 insertions, 196 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 54284b09..9bd6c298 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -177,6 +177,7 @@ dependencies {
implementation"com.mikepenz:fastadapter-extensions:${FAST_ADAPTER_EXTENSIONS}@aar"
implementation "com.github.bumptech.glide:okhttp3-integration:${GLIDE}"
+ kapt "com.github.bumptech.glide:compiler:${GLIDE}"
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.3"
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.*
diff --git a/app/src/main/res/values-de-rDE/strings_pref_notifications.xml b/app/src/main/res/values-de-rDE/strings_pref_notifications.xml
index d2280858..c2ab299a 100644
--- a/app/src/main/res/values-de-rDE/strings_pref_notifications.xml
+++ b/app/src/main/res/values-de-rDE/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Schlagwort hinzufügen</string>
<string name="hint_keyword">Gebe das Schlagwort ein und drücke +</string>
<string name="empty_keyword">Leeres Schlagwort</string>
- <string name="notification_all_accounts">Benachrichtigungen von allen Accounts</string>
- <string name="notification_all_accounts_desc">Bekomme Benachrichtigungen von allen Accounts die eingeloggt sind. Deaktiviere dies, um nur noch Benachrichtigungen von dem aktuellen Account zu bekommen.</string>
+ <string name="notification_general_all_accounts">Benachrichtigungen von allen Accounts</string>
+ <string name="notification_general_all_accounts_desc">Bekomme Benachrichtigungen von allen Accounts die eingeloggt sind. Deaktiviere dies, um nur noch Benachrichtigungen von dem aktuellen Account zu bekommen.</string>
<string name="notification_messages">Aktiviere Nachrichten Benachrichtigungen</string>
<string name="notification_messages_desc">Bekomme sofortige Benachrichtigungen für deine Nachrichten für den aktuellen Account.</string>
<string name="notification_messages_all_accounts">Benachrichtigungen für Nachrichten von allen Accounts</string>
diff --git a/app/src/main/res/values-es-rES/strings_pref_notifications.xml b/app/src/main/res/values-es-rES/strings_pref_notifications.xml
index 63192d10..69aa9b60 100644
--- a/app/src/main/res/values-es-rES/strings_pref_notifications.xml
+++ b/app/src/main/res/values-es-rES/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Añadir palabra clave</string>
<string name="hint_keyword">Escriba la palabra clave y pulse +</string>
<string name="empty_keyword">Palabra clave vacía</string>
- <string name="notification_all_accounts">Notificar de todas las cuentas</string>
- <string name="notification_all_accounts_desc">Obten notificaciones de todas las cuentas en las que estes logueado. Desactivar esto hará que se muestren solo las notificaciones de la cuenta en la que estés.</string>
+ <string name="notification_general_all_accounts">Notificar de todas las cuentas</string>
+ <string name="notification_general_all_accounts_desc">Obten notificaciones de todas las cuentas en las que estes logueado. Desactivar esto hará que se muestren solo las notificaciones de la cuenta en la que estés.</string>
<string name="notification_messages">Activar notificaciones de mensajes</string>
<string name="notification_messages_desc">Obten notificaciones instantáneas de mensajes para tu cuenta actual.</string>
<string name="notification_messages_all_accounts">Notificar mensajes de todas las cuentas</string>
diff --git a/app/src/main/res/values-fr-rFR/strings_pref_notifications.xml b/app/src/main/res/values-fr-rFR/strings_pref_notifications.xml
index 89687082..fb519054 100644
--- a/app/src/main/res/values-fr-rFR/strings_pref_notifications.xml
+++ b/app/src/main/res/values-fr-rFR/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Ajouter un mot-clé</string>
<string name="hint_keyword">Tapez le mot-clé et appuyez sur +</string>
<string name="empty_keyword">Mot-clé vide</string>
- <string name="notification_all_accounts">Notifier de tous les comptes</string>
- <string name="notification_all_accounts_desc">Recevoir des notifications pour chaque compte connecté. Désactiver cette option fera une récupération uniquement pour le compte sélectionné.</string>
+ <string name="notification_general_all_accounts">Notifier de tous les comptes</string>
+ <string name="notification_general_all_accounts_desc">Recevoir des notifications pour chaque compte connecté. Désactiver cette option fera une récupération uniquement pour le compte sélectionné.</string>
<string name="notification_messages">Activer les notifications de message</string>
<string name="notification_messages_desc">Recevoir des notifications de messagerie instantanée pour le compte actuel.</string>
<string name="notification_messages_all_accounts">Notifier les messages de tous les comptes</string>
diff --git a/app/src/main/res/values-gl-rES/strings_pref_notifications.xml b/app/src/main/res/values-gl-rES/strings_pref_notifications.xml
index aea99fb3..f3ce204d 100644
--- a/app/src/main/res/values-gl-rES/strings_pref_notifications.xml
+++ b/app/src/main/res/values-gl-rES/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Engadir palabra chave</string>
<string name="hint_keyword">Escribe a palabra e toca en +</string>
<string name="empty_keyword">Palabra chave baleira</string>
- <string name="notification_all_accounts">Notificar de todas as contas</string>
- <string name="notification_all_accounts_desc">Obtén notificacións de todas as contas con que iniciaras sesión. Ao desactivar isto, só obterás notificacións da conta actualmente seleccionada.</string>
+ <string name="notification_general_all_accounts">Notificar de todas as contas</string>
+ <string name="notification_general_all_accounts_desc">Obtén notificacións de todas as contas con que iniciaras sesión. Ao desactivar isto, só obterás notificacións da conta actualmente seleccionada.</string>
<string name="notification_messages">Activar notificacións de mensaxes</string>
<string name="notification_messages_desc">Obtén notificacións instantáneas de mensaxes para a túa conta actual.</string>
<string name="notification_messages_all_accounts">Notificar mensaxes de todas as contas</string>
diff --git a/app/src/main/res/values-it-rIT/strings_pref_notifications.xml b/app/src/main/res/values-it-rIT/strings_pref_notifications.xml
index 9e3fb335..5f0b530c 100644
--- a/app/src/main/res/values-it-rIT/strings_pref_notifications.xml
+++ b/app/src/main/res/values-it-rIT/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Aggiungi parole chiave</string>
<string name="hint_keyword">Scrivi la parola e premi +</string>
<string name="empty_keyword">Parola Chiave Vuota</string>
- <string name="notification_all_accounts">Notifica da tutti gli accounts</string>
- <string name="notification_all_accounts_desc">Ricevi notifiche per ogni account registrato. Disattivando questa opzione riceverai notifiche solo dall\'account selezionato.</string>
+ <string name="notification_general_all_accounts">Notifica da tutti gli accounts</string>
+ <string name="notification_general_all_accounts_desc">Ricevi notifiche per ogni account registrato. Disattivando questa opzione riceverai notifiche solo dall\'account selezionato.</string>
<string name="notification_messages">Attiva notifiche messaggi</string>
<string name="notification_messages_desc">Ricevi notifiche istantanee di messaggi per il tuo account.</string>
<string name="notification_messages_all_accounts">Notifica messaggi da tutti gli account</string>
diff --git a/app/src/main/res/values-ko-rKR/strings_pref_notifications.xml b/app/src/main/res/values-ko-rKR/strings_pref_notifications.xml
index beba9ee0..c6777c41 100644
--- a/app/src/main/res/values-ko-rKR/strings_pref_notifications.xml
+++ b/app/src/main/res/values-ko-rKR/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">키워드 추가</string>
<string name="hint_keyword">키워드를 작성하고 + 를 누르세요.</string>
<string name="empty_keyword">키워드 비어있음</string>
- <string name="notification_all_accounts">모든 계정에서 알림</string>
- <string name="notification_all_accounts_desc">로그인 되어 있는 모든 계정에서 알림을 받습니다. 이 옺션을 해제하면 현재 로그인된 계정의 알림만 받게 됩니다.</string>
+ <string name="notification_general_all_accounts">모든 계정에서 알림</string>
+ <string name="notification_general_all_accounts_desc">로그인 되어 있는 모든 계정에서 알림을 받습니다. 이 옺션을 해제하면 현재 로그인된 계정의 알림만 받게 됩니다.</string>
<string name="notification_messages">메시지 알림 활성화</string>
<string name="notification_messages_desc">현재 계정의 메시지 알림을 받습니다.</string>
<string name="notification_messages_all_accounts">모든 계정의 메시지 받기</string>
diff --git a/app/src/main/res/values-pl-rPL/strings_pref_notifications.xml b/app/src/main/res/values-pl-rPL/strings_pref_notifications.xml
index 0f2a4cf8..12de118d 100644
--- a/app/src/main/res/values-pl-rPL/strings_pref_notifications.xml
+++ b/app/src/main/res/values-pl-rPL/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Dodaj słowo kluczowe</string>
<string name="hint_keyword">Wpisz słowo kluczowe i naciśnij +</string>
<string name="empty_keyword">Puste słowo kluczowe</string>
- <string name="notification_all_accounts">Powiadamiaj ze wszystkich kont</string>
- <string name="notification_all_accounts_desc">Otrzymuj powiadomienia dla każdego zalogowanego konta. Wyłączenie spowoduje sprawdzanie powiadomień, tylko z obecnie wybranego konta.</string>
+ <string name="notification_general_all_accounts">Powiadamiaj ze wszystkich kont</string>
+ <string name="notification_general_all_accounts_desc">Otrzymuj powiadomienia dla każdego zalogowanego konta. Wyłączenie spowoduje sprawdzanie powiadomień, tylko z obecnie wybranego konta.</string>
<string name="notification_messages">Włącz powiadomienia o wiadomościach</string>
<string name="notification_messages_desc">Otrzymuj natychmiastowe powiadomienia dla obecnego konta.</string>
<string name="notification_messages_all_accounts">Powiadamiaj ze wszystkich kont</string>
diff --git a/app/src/main/res/values-pt-rBR/strings_pref_notifications.xml b/app/src/main/res/values-pt-rBR/strings_pref_notifications.xml
index cd8a8a25..1e128f7d 100644
--- a/app/src/main/res/values-pt-rBR/strings_pref_notifications.xml
+++ b/app/src/main/res/values-pt-rBR/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Adicionar Palavra-Chave</string>
<string name="hint_keyword">Escreva a palavra-chave e pressione +</string>
<string name="empty_keyword">Palavra-chave Vazia</string>
- <string name="notification_all_accounts">Notificações de todas as contas</string>
- <string name="notification_all_accounts_desc">Receber notificações para qualquer conta que esteja logada. Desativar esta opção vai apenas buscar as notificações da conta atualmente selecionada.</string>
+ <string name="notification_general_all_accounts">Notificações de todas as contas</string>
+ <string name="notification_general_all_accounts_desc">Receber notificações para qualquer conta que esteja logada. Desativar esta opção vai apenas buscar as notificações da conta atualmente selecionada.</string>
<string name="notification_messages">Ativar as notificações de mensagem</string>
<string name="notification_messages_desc">Receber notificações de conversas de sua conta atual.</string>
<string name="notification_messages_all_accounts">Notificar mensagens de todas as contas</string>
diff --git a/app/src/main/res/values-vi-rVN/strings_pref_notifications.xml b/app/src/main/res/values-vi-rVN/strings_pref_notifications.xml
index 92889571..d077173c 100644
--- a/app/src/main/res/values-vi-rVN/strings_pref_notifications.xml
+++ b/app/src/main/res/values-vi-rVN/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">Thêm từ khoá</string>
<string name="hint_keyword">Nhập từ khoá rồi bấm +</string>
<string name="empty_keyword">Từ khoá trống</string>
- <string name="notification_all_accounts">Thông báo từ tất cả các tài khoản</string>
- <string name="notification_all_accounts_desc">Hiện thông báo từ tất cả các tài khoản đăng đăng nhập. Khi tắt, sẽ chỉ hiện thông báo từ tài khoản hiện tại.</string>
+ <string name="notification_general_all_accounts">Thông báo từ tất cả các tài khoản</string>
+ <string name="notification_general_all_accounts_desc">Hiện thông báo từ tất cả các tài khoản đăng đăng nhập. Khi tắt, sẽ chỉ hiện thông báo từ tài khoản hiện tại.</string>
<string name="notification_messages">Bật thông báo tin nhắn</string>
<string name="notification_messages_desc">Nhận thông báo tin nhắn cho tài khoản hiện tại.</string>
<string name="notification_messages_all_accounts">Thông báo tin nhắn từ tất cả các tài khoản</string>
diff --git a/app/src/main/res/values-zh-rCN/strings_pref_notifications.xml b/app/src/main/res/values-zh-rCN/strings_pref_notifications.xml
index 66ae53a0..38a2722d 100644
--- a/app/src/main/res/values-zh-rCN/strings_pref_notifications.xml
+++ b/app/src/main/res/values-zh-rCN/strings_pref_notifications.xml
@@ -8,8 +8,8 @@
<string name="add_keyword">添加关键字</string>
<string name="hint_keyword">输入关键字并按 +</string>
<string name="empty_keyword">空关键字</string>
- <string name="notification_all_accounts">所有帐户通知</string>
- <string name="notification_all_accounts_desc">从每个登录帐户中获取通知。如果禁用则只从当前选择帐户中获取通知。</string>
+ <string name="notification_general_all_accounts">所有帐户通知</string>
+ <string name="notification_general_all_accounts_desc">从每个登录帐户中获取通知。如果禁用则只从当前选择帐户中获取通知。</string>
<string name="notification_messages">启用通知</string>
<string name="notification_messages_desc">获得当前帐户即时消息通知。</string>
<string name="notification_messages_all_accounts">所有帐户消息通知</string>
diff --git a/app/src/main/res/values/strings_pref_debug.xml b/app/src/main/res/values/strings_pref_debug.xml
index 8e8c051c..771e5130 100644
--- a/app/src/main/res/values/strings_pref_debug.xml
+++ b/app/src/main/res/values/strings_pref_debug.xml
@@ -16,4 +16,7 @@
<string name="debug_web_desc">Navigate to the page with an issue and send the resources for debugging.</string>
<string name="parsing_data">Parsing Data</string>
+
+ <string name="debug_parsers">Debug Parsers</string>
+ <string name="debug_parsers_desc">Launch one of the available parsers to debug its response data</string>
</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/strings_pref_notifications.xml b/app/src/main/res/values/strings_pref_notifications.xml
index 49665f8e..8db493b8 100644
--- a/app/src/main/res/values/strings_pref_notifications.xml
+++ b/app/src/main/res/values/strings_pref_notifications.xml
@@ -8,14 +8,16 @@
<string name="add_keyword">Add Keyword</string>
<string name="hint_keyword">Type keyword and press +</string>
<string name="empty_keyword">Empty Keyword</string>
- <string name="notification_all_accounts">Notify from all accounts</string>
- <string name="notification_all_accounts_desc">Get notifications for every account that is logged in. Disabling this will only fetch notifications form the currently selected account.</string>
+ <string name="notification_general">Enable general notifications</string>
+ <string name="notification_general_desc">Get general notifications for your current account.</string>
+ <string name="notification_general_all_accounts">Notify from all accounts</string>
+ <string name="notification_general_all_accounts_desc">Get general notifications for every account that is logged in.</string>
<string name="notification_messages">Enable message notifications</string>
<string name="notification_messages_desc">Get instant message notifications for your current account.</string>
<string name="notification_messages_all_accounts">Notify messages from all accounts</string>
<string name="notification_messages_all_accounts_desc">Get instant message notifications from all accounts</string>
<string name="notification_fetch_now">Fetch Notifications Now</string>
- <string name="notification_fetch_now_desc">Trigger the notification fetcher once. Note that fetching instant messages takes time.</string>
+ <string name="notification_fetch_now_desc">Trigger the notification fetcher once.</string>
<string name="notification_fetch_success">Fetching Notifications…</string>
<string name="notification_fetch_fail">Couldn\'t fetch notifications</string>
<string name="notification_sound">Notification sound</string>
diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml
index 28978193..0ddc28f3 100644
--- a/app/src/main/res/xml/frost_changelog.xml
+++ b/app/src/main/res/xml/frost_changelog.xml
@@ -9,8 +9,8 @@
<version title="v1.7.7" />
<item text="Fix overlay loading" />
<item text="Improve image loading" />
- <item text="" />
- <item text="" />
+ <item text="Launch image viewer when opening full sized image" />
+ <item text="Improve filtering system" />
<item text="" />
<item text="" />
<item text="" />
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt
index a79ccf3f..08853466 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbRegexTest.kt
@@ -49,4 +49,12 @@ class FbRegexTest {
val data = "\"uri\":\"$url\"}"
assertEquals(url, FB_JSON_URL_MATCHER.find(data)[1])
}
+
+ @Test
+ fun imageIdRegex() {
+ val id = 123456L
+ val img = "https://scontent-yyz1-1.xx.fbcdn.net/v/t31.0-8/fr/cp0/e15/q65/89056_${id}_98239_o.jpg"
+ assertEquals(id, FB_IMAGE_ID_MATCHER.find(img)[1]?.toLongOrNull())
+ }
+
} \ No newline at end of file
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
index 62b7cac2..79d5ea64 100644
--- a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
+++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
@@ -1,7 +1,10 @@
package com.pitchedapps.frost.facebook
+import com.pitchedapps.frost.utils.isImageUrl
import org.junit.Test
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
/**
@@ -57,4 +60,28 @@ class FbUrlTest {
assertFbFormat(expected, url)
}
+
+ @Test
+ fun imageRegex() {
+ arrayOf(
+ "https://scontent-yyz1-1.xx.fbcdn.net/v/t1.0-9/fr/cp0/e15/q65/229_546131_836546862_n.jpg?efg=e343J9&oh=d4245b1&oe=5453",
+ "/photo/view_full_size/?fbid=1523&ref_component=mbasic_photo_permalink&ref_page=%2Fwap%2Fphoto.php&refid=153&_ft_=...",
+ "#!/photo/view_full_size/?fbid=1523&ref_component=mbasic_photo_permalink&ref_page=%2Fwap%2Fphoto.php&refid=153&_ft_=..."
+ ).forEach {
+ assertTrue(it.isImageUrl, "Failed to match image for $it")
+ }
+ }
+
+ @Test
+ fun antiImageRegex() {
+ arrayOf(
+ "http...fbcdn.net...mp4",
+ "/photo/...png",
+ "https://www.google.ca"
+ ).forEach {
+ assertFalse(it.isImageUrl, "Should not have matched image for $it")
+ }
+
+ }
+
} \ No newline at end of file
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt
new file mode 100644
index 00000000..ec92b059
--- /dev/null
+++ b/app/src/test/kotlin/com/pitchedapps/frost/rx/ResettableFlyweightTest.kt
@@ -0,0 +1,61 @@
+package com.pitchedapps.frost.rx
+
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+/**
+ * Created by Allan Wang on 07/01/18.
+ */
+private inline val threadId
+ get() = Thread.currentThread().id
+
+class ResettableFlyweightTest {
+
+ class IntFlyweight : RxFlyweight<Int, Long, Long>() {
+ override fun call(input: Int): Long {
+ println("Call for $input on thread $threadId")
+ Thread.sleep(20)
+ return System.currentTimeMillis()
+ }
+
+ override fun validate(input: Int, cond: Long) = System.currentTimeMillis() - cond < 500
+
+ override fun cache(input: Int): Long = System.currentTimeMillis()
+ }
+
+ private lateinit var flyweight: IntFlyweight
+ private lateinit var latch: CountDownLatch
+
+ @Before
+ fun init() {
+ flyweight = IntFlyweight()
+ latch = CountDownLatch(1)
+ }
+
+ @Test
+ fun testCache() {
+ flyweight(1).subscribe { i ->
+ flyweight(1).subscribe { j ->
+ assertEquals(i, j, "Did not use cache during calls")
+ latch.countDown()
+ }
+ }
+ latch.await()
+ }
+
+ @Test
+ fun testNoCache() {
+ flyweight(1).subscribe { i ->
+ flyweight(2).subscribe { j ->
+ assertNotEquals(i, j, "Should not use cache for calls with different keys")
+ latch.countDown()
+ }
+ }
+ latch.await()
+ }
+
+
+} \ No newline at end of file