aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-13 13:50:00 -0700
committerGitHub <noreply@github.com>2017-07-13 13:50:00 -0700
commit91119de328bf5f4e8c945f8fb470453319b9f0ed (patch)
tree9ba1786f9cd8488a0cc0dfb247e1b387a4161cfb /app
parentde34d09f975079d5c044eae6da7ed92605009faf (diff)
downloadfrost-91119de328bf5f4e8c945f8fb470453319b9f0ed.tar.gz
frost-91119de328bf5f4e8c945f8fb470453319b9f0ed.tar.bz2
frost-91119de328bf5f4e8c945f8fb470453319b9f0ed.zip
Dev 1.2.2 - Add framework for messenger notifications (#47)
* Update KAU to v2.0 * Only inject theme for facebook and inject js after * Clean up menu loading logic * Add path null check * Remove .idea files * Add url formatter testers * Update tests and check url nullability - Fixes * Create instant messaging parser * Shorted notification log and remove unnecessary null checks * Make migration buildable * Test message parser * finalize messenger notifs for now
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle13
-rw-r--r--app/proguard-rules.pro8
-rw-r--r--app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java26
-rw-r--r--app/src/main/assets/js/menu.js1
-rw-r--r--app/src/main/assets/js/menu.min.js3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt10
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt22
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt5
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt103
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt8
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/KPrefTextSeekbar.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/RippleCanvas.kt83
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt22
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt45
-rw-r--r--app/src/main/res/values/strings.xml1
-rw-r--r--app/src/main/res/values/strings_errors.xml4
-rw-r--r--app/src/main/res/xml/changelog.xml13
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt39
-rw-r--r--app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTest.kt22
29 files changed, 216 insertions, 246 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 7df9c5d7..45bdaa16 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,12 +26,6 @@ android {
prefix 'v'
}
- playAccountConfigs {
- defaultAccountConfig {
- jsonFile = file('../files/gplay-keys.json')
- }
- }
-
defaultConfig {
applicationId "${project.APP_GROUP}." + project.APP_ID.toLowerCase()
minSdkVersion Integer.parseInt(project.MIN_SDK)
@@ -124,8 +118,11 @@ dependencies {
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:${ROBOELECTRIC}"
-
- compile "ca.allanwang.kau:core:${KAU}"
+ compile "ca.allanwang.kau:about:$KAU"
+ compile "ca.allanwang.kau:colorpicker:$KAU"
+// compile "ca.allanwang.kau:imagepicker:$KAU"
+ compile "ca.allanwang.kau:kpref-activity:$KAU"
+ compile "ca.allanwang.kau:searchview:$KAU"
compile "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN}"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:${KOTLIN}"
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 9df5ace7..7d6324cb 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -22,14 +22,6 @@
-keeppackagenames org.jsoup.nodes
# IAB
-keep class com.android.vending.billing.**
-# About libs
-#-keep class .R
-#-keep class **.R$* {
-# <fields>;
-#}
--keepclasseswithmembers class **.R$* {
- public static final int define_*;
-}
# Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
diff --git a/app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java
deleted file mode 100644
index 9e27deaf..00000000
--- a/app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.pitchedapps.frost;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumentation test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("com.pitchedapps.frost", appContext.getPackageName());
- }
-}
diff --git a/app/src/main/assets/js/menu.js b/app/src/main/assets/js/menu.js
index f8cd03c9..7394c824 100644
--- a/app/src/main/assets/js/menu.js
+++ b/app/src/main/assets/js/menu.js
@@ -27,7 +27,6 @@ if (!window.hasOwnProperty('frost_menu')) {
setTimeout(function() {
y.disconnect();
console.log('Unhook styler');
- Frost.handleHtml(document.documentElement.outerHTML);
}, 500);
}
});
diff --git a/app/src/main/assets/js/menu.min.js b/app/src/main/assets/js/menu.min.js
index ce878b88..5403e03a 100644
--- a/app/src/main/assets/js/menu.min.js
+++ b/app/src/main/assets/js/menu.min.js
@@ -15,8 +15,7 @@ var o=document.querySelector(".mSideMenu")
for(x.disconnect(),console.log("Found side menu");root.firstChild;)root.removeChild(root.firstChild)
;for(;o.childNodes.length;)root.appendChild(o.childNodes[0])
;Frost.emit(0),setTimeout(function(){
-y.disconnect(),console.log("Unhook styler"),
-Frost.handleHtml(document.documentElement.outerHTML)
+y.disconnect(),console.log("Unhook styler")
},500)
}
})
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt
index 36809535..6cab2a59 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/AboutActivity.kt
@@ -7,10 +7,10 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import ca.allanwang.kau.about.AboutActivityBase
+import ca.allanwang.kau.about.LibraryIItem
import ca.allanwang.kau.adapters.FastItemThemedAdapter
import ca.allanwang.kau.adapters.ThemableIItem
import ca.allanwang.kau.adapters.ThemableIItemDelegate
-import ca.allanwang.kau.iitems.LibraryIItem
import ca.allanwang.kau.utils.*
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt
index 66c541ed..e0be330d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt
@@ -5,12 +5,12 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import ca.allanwang.kau.changelog.showChangelog
-import ca.allanwang.kau.kpref.CoreAttributeContract
-import ca.allanwang.kau.kpref.KPrefActivity
-import ca.allanwang.kau.kpref.KPrefAdapterBuilder
-import ca.allanwang.kau.kpref.items.KPrefItemBase
+import ca.allanwang.kau.kpref.activity.CoreAttributeContract
+import ca.allanwang.kau.kpref.activity.KPrefActivity
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
+import ca.allanwang.kau.kpref.activity.items.KPrefItemBase
+import ca.allanwang.kau.ui.views.RippleCanvas
import ca.allanwang.kau.utils.*
-import ca.allanwang.kau.views.RippleCanvas
import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.pitchedapps.frost.settings.*
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt
index dda6c066..d197918d 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/WebOverlayActivity.kt
@@ -41,6 +41,9 @@ open class WebOverlayActivity : AppCompatActivity(),
const val ARG_USER_ID = "arg_user_id"
}
+ val urlTest: String?
+ get() = intent.extras?.getString(ARG_URL) ?: intent.dataString
+
open val url: String
get() = (intent.extras?.getString(ARG_URL) ?: intent.dataString).formattedFbUrl
@@ -49,6 +52,12 @@ open class WebOverlayActivity : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (urlTest == null) {
+ L.eThrow("Empty link on web overlay")
+ toast(R.string.null_url_overlay)
+ finish()
+ return
+ }
setContentView(R.layout.activity_web_overlay)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowHomeEnabled(true)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
index 1585e425..66f5ae64 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
@@ -1,11 +1,10 @@
package com.pitchedapps.frost.dbflow
import com.pitchedapps.frost.utils.L
-import com.raizlabs.android.dbflow.annotation.ConflictAction
-import com.raizlabs.android.dbflow.annotation.Database
-import com.raizlabs.android.dbflow.annotation.PrimaryKey
-import com.raizlabs.android.dbflow.annotation.Table
+import com.raizlabs.android.dbflow.annotation.*
import com.raizlabs.android.dbflow.kotlinextensions.*
+import com.raizlabs.android.dbflow.sql.SQLiteType
+import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration
import com.raizlabs.android.dbflow.structure.BaseModel
/**
@@ -15,13 +14,22 @@ import com.raizlabs.android.dbflow.structure.BaseModel
@Database(name = NotificationDb.NAME, version = NotificationDb.VERSION)
object NotificationDb {
const val NAME = "Notifications"
- const val VERSION = 1
+ const val VERSION = 2
+}
+
+@Migration(version = 2, database = NotificationDb::class)
+class NotificationMigration2(modelClass: Class<NotificationModel>) : AlterTableMigration<NotificationModel>(modelClass) {
+ override fun onPreMigrate() {
+ super.onPreMigrate()
+ addColumn(SQLiteType.INTEGER, "epochIm")
+ L.d("Added column")
+ }
}
@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
-data class NotificationModel(@PrimaryKey var id: Long = -1L, var epoch: Long = -1L) : BaseModel()
+data class NotificationModel(@PrimaryKey var id: Long = -1L, var epoch: Long = -1L, var epochIm: Long = -1) : BaseModel()
-fun lastNotificationTime(id: Long): Long = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()?.epoch ?: -1L
+fun lastNotificationTime(id: Long): NotificationModel = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle() ?: NotificationModel(id = id)
fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
notificationModel.async save {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
index 1090f1f3..e53bb202 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt
@@ -15,8 +15,9 @@ class FbUrlFormatter(url: String) {
init {
var cleanedUrl = url
discardable.forEach { cleanedUrl = cleanedUrl.replace(it, "") }
+ val changed = cleanedUrl != url //note that discardables strip away the first ?
decoder.forEach { (k, v) -> cleanedUrl = cleanedUrl.replace(k, v) }
- val qm = cleanedUrl.indexOf("?")
+ val qm = cleanedUrl.indexOf(if (changed)"&" else "?")
if (qm > -1) {
cleanedUrl.substring(qm + 1).split("&").forEach {
val p = it.split("=")
@@ -58,7 +59,8 @@ class FbUrlFormatter(url: String) {
"http://m.facebook.com/l.php?u=",
"https://m.facebook.com/l.php?u=",
"http://touch.facebook.com/l.php?u=",
- "https://touch.facebook.com/l.php?u="
+ "https://touch.facebook.com/l.php?u=",
+ "/video_redirect/?src="
)
@JvmStatic val discardableQueries = arrayOf("ref", "refid")
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt
index a83e87dc..cb33e527 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/CssHider.kt
@@ -15,7 +15,8 @@ enum class CssHider(vararg val items: String) : InjectorContract {
"article[data-xt*=sponsor]",
"article[data-store*=sponsor]"
),
- PEOPLE_YOU_MAY_KNOW("article._d2r")
+ PEOPLE_YOU_MAY_KNOW("article._d2r"),
+ MESSENGER("._s15", "[data-testid=info_panel]", "js_i")
;
val injector: JsInjector by lazy { JsBuilder().css("${items.joinToString(separator = ",")}{display:none!important}").build() }
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 6af6b1db..2cea55b4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -64,6 +64,7 @@ class FrostNotificationTarget(val context: Context,
data class NotificationContent(val data: CookieModel,
val notifId: Int,
val href: String,
+ val title: String? = null,
val text: String,
val timestamp: Long,
val profileUrl: String) {
@@ -81,7 +82,7 @@ data class NotificationContent(val data: CookieModel,
val group = "frost_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val notifBuilder = context.frostNotification
- .setContentTitle(context.string(R.string.app_name))
+ .setContentTitle(title ?: context.string(R.string.app_name))
.setContentText(text)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
@@ -133,7 +134,7 @@ const val NOTIFICATION_JOB_NOW = 6
/**
* Run notification job right now
*/
-fun Context.fetchNotifications():Boolean {
+fun Context.fetchNotifications(): Boolean {
val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(this, NotificationService::class.java)
val builder = JobInfo.Builder(NOTIFICATION_JOB_NOW, serviceComponent)
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 4c03f056..f9d0c63c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -7,9 +7,13 @@ import android.support.v4.app.NotificationManagerCompat
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.dbflow.*
+import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.dbflow.lastNotificationTime
+import com.pitchedapps.frost.dbflow.loadFbCookie
+import com.pitchedapps.frost.dbflow.loadFbCookiesSync
import com.pitchedapps.frost.facebook.FACEBOOK_COM
import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.facebook.USER_AGENT_BASIC
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostAnswersCustom
@@ -31,6 +35,7 @@ class NotificationService : JobService() {
companion object {
val epochMatcher: Regex by lazy { Regex(":([0-9]*?),") }
val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*?),") }
+ val messageNotifIdMatcher: Regex by lazy { Regex("thread_fbid_([0-9]+)") }
val profMatcher: Regex by lazy { Regex("url\\(\"(.*?)\"\\)") }
}
@@ -59,14 +64,26 @@ class NotificationService : JobService() {
return true
}
+ fun logNotif(text: String): NotificationContent? {
+ L.eThrow("NotificationService: $text")
+ return null
+ }
+
fun fetchNotifications(data: CookieModel) {
+ fetchGeneralNotifications(data)
+// fetchMessageNotifications(data)
+ debugNotification("Hello")
+ }
+
+ fun fetchGeneralNotifications(data: CookieModel) {
L.i("Notif fetch for $data")
- val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).get()
+ val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get()
//aclb for unread, acw for read
- val unreadNotifications = doc.getElementById("notifications_list").getElementsByClass("aclb")
+ val unreadNotifications = (doc.getElementById("notifications_list") ?: return L.eThrow("Notification list not found")).getElementsByClass("aclb")
var notifCount = 0
// val prevLatestEpoch = 1498931565L // for testing
- val prevLatestEpoch = lastNotificationTime(data.id)
+ val prevNotifTime = lastNotificationTime(data.id)
+ val prevLatestEpoch = prevNotifTime.epoch
L.v("Notif Prev Latest Epoch $prevLatestEpoch")
var newLatestEpoch = prevLatestEpoch
unreadNotifications.forEach unread@ {
@@ -79,31 +96,77 @@ class NotificationService : JobService() {
newLatestEpoch = notif.timestamp
notifCount++
}
- if (newLatestEpoch != prevLatestEpoch) saveNotificationTime(NotificationModel(data.id, newLatestEpoch))
- frostAnswersCustom("Notifications") { putCustomAttribute("Count", notifCount) }
+ if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).update()
+ frostAnswersCustom("Notifications") {
+ putCustomAttribute("Type", "General")
+ putCustomAttribute("Count", notifCount)
+ }
summaryNotification(data.id, notifCount)
}
fun parseNotification(data: CookieModel, element: Element): NotificationContent? {
- val a = element.getElementsByTag("a").first() ?: return null
- //fetch id
- val dataStore = a.attr("data-store")
- val notifId = if (dataStore == null) System.currentTimeMillis()
- else notifIdMatcher.find(dataStore)?.groups?.get(1)?.value?.toLong() ?: System.currentTimeMillis()
+ val a = element.getElementsByTag("a").first() ?: return logNotif("IM No a tag")
val abbr = element.getElementsByTag("abbr")
- val timeString = abbr?.text()
- var text = a.text().replace("\u00a0", " ") //remove &nbsp;
+ val epoch = epochMatcher.find(abbr.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: return logNotif("IM No epoch")
+ //fetch id
+ val notifId = notifIdMatcher.find(a.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: System.currentTimeMillis()
+ val timeString = abbr.text()
+ val text = a.text().replace("\u00a0", " ").removeSuffix(timeString).trim() //remove &nbsp;
if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
- if (timeString != null) text = text.removeSuffix(timeString)
- text = text.trim()
- //fetch epoch
- val abbrData = abbr?.attr("data-store")
- val epoch = if (abbrData == null) -1L else epochMatcher.find(abbrData)?.groups?.get(1)?.value?.toLong() ?: -1L
//fetch profpic
val p = element.select("i.img[style*=url]")
- val pUrl = profMatcher.find(p.getOrNull(0)?.attr("style") ?: "")?.groups?.get(1)?.value ?: ""
- return NotificationContent(data, notifId.toInt(), a.attr("href"), text, epoch, pUrl)
+ val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: ""
+ return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl)
+ }
+
+ fun fetchMessageNotifications(data: CookieModel) {
+ if (!Prefs.notificationsInstantMessages) return
+ L.i("Notif IM fetch for $data")
+ val doc = Jsoup.connect(FbTab.MESSAGES.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get()
+ val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb")
+ var notifCount = 0
+ L.d("IM notif count ${unreadNotifications.size}")
+ unreadNotifications.forEach {
+ with(it) {
+ L.d("notif ${id()} ${className()}")
+ }
+ }
+ val prevNotifTime = lastNotificationTime(data.id)
+ val prevLatestEpoch = prevNotifTime.epochIm
+ L.v("Notif Prev Latest Im Epoch $prevLatestEpoch")
+ var newLatestEpoch = prevLatestEpoch
+ unreadNotifications.forEach unread@ {
+ elem ->
+ val notif = parseMessageNotification(data, elem) ?: return@unread
+ L.v("Notif im timestamp ${notif.timestamp}")
+ if (notif.timestamp <= prevLatestEpoch) return@unread
+ notif.createNotification(this@NotificationService)
+ if (notif.timestamp > newLatestEpoch)
+ newLatestEpoch = notif.timestamp
+ notifCount++
+ }
+// if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).update()
+ frostAnswersCustom("Notifications") {
+ putCustomAttribute("Type", "Message")
+ putCustomAttribute("Count", notifCount)
+ }
+ summaryNotification(data.id, notifCount)
+ }
+
+ fun parseMessageNotification(data: CookieModel, element: Element): NotificationContent? {
+ val a = element.getElementsByTag("a").first() ?: return null
+ val abbr = element.getElementsByTag("abbr")
+ val epoch = epochMatcher.find(abbr.attr("data-store"))?.groups?.get(1)?.value?.toLong() ?: return logNotif("No epoch")
+ val thread = element.getElementsByAttributeValueContaining("id", "thread_fbid_").first() ?: return null
+ //fetch id
+ val notifId = messageNotifIdMatcher.find(thread.id())?.groups?.get(1)?.value?.toLong() ?: System.currentTimeMillis()
+ val text = element.select("span.snippet").firstOrNull()?.text()?.trim() ?: getString(R.string.new_message)
+ if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out
+ //fetch convo pic
+ val p = element.select("i.img[style*=url]")
+ val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: ""
+ return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl)
}
private fun Context.debugNotification(text: String) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt
index 68d3fbc6..e05d0dd3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Appearance.kt
@@ -1,10 +1,10 @@
package com.pitchedapps.frost.settings
-import ca.allanwang.kau.kpref.KPrefAdapterBuilder
-import ca.allanwang.kau.kpref.items.KPrefColorPicker
-import ca.allanwang.kau.kpref.items.KPrefSeekbar
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
+import ca.allanwang.kau.kpref.activity.items.KPrefColorPicker
+import ca.allanwang.kau.kpref.activity.items.KPrefSeekbar
+import ca.allanwang.kau.ui.views.RippleCanvas
import ca.allanwang.kau.utils.string
-import ca.allanwang.kau.views.RippleCanvas
import com.pitchedapps.frost.MainActivity
import com.pitchedapps.frost.R
import com.pitchedapps.frost.SettingsActivity
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt
index 2d121073..b912c103 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Behaviour.kt
@@ -1,6 +1,6 @@
package com.pitchedapps.frost.settings
-import ca.allanwang.kau.kpref.KPrefAdapterBuilder
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import com.pitchedapps.frost.R
import com.pitchedapps.frost.SettingsActivity
import com.pitchedapps.frost.utils.Prefs
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt
index 6022ee26..86bd356b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt
@@ -1,6 +1,6 @@
package com.pitchedapps.frost.settings
-import ca.allanwang.kau.kpref.KPrefAdapterBuilder
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import com.pitchedapps.frost.MainActivity
import com.pitchedapps.frost.R
import com.pitchedapps.frost.SettingsActivity
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt
index b8bfb086..29256950 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Feed.kt
@@ -1,6 +1,6 @@
package com.pitchedapps.frost.settings
-import ca.allanwang.kau.kpref.KPrefAdapterBuilder
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.utils.string
import com.pitchedapps.frost.MainActivity
import com.pitchedapps.frost.R
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 5986a998..86cfcc16 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
@@ -1,6 +1,6 @@
package com.pitchedapps.frost.settings
-import ca.allanwang.kau.kpref.KPrefAdapterBuilder
+import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.utils.minuteToText
import ca.allanwang.kau.utils.snackbar
import com.pitchedapps.frost.R
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 7ce8ca10..64ec08cd 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -87,6 +87,8 @@ object Prefs : KPref() {
var notificationAllAccounts: Boolean by kpref("notification_all_accounts", true)
+ var notificationsInstantMessages: Boolean by kpref("notification_im", true)
+
/**
* Cache like value to determine if user has or had pro
* In most cases, [com.pitchedapps.frost.utils.iab.IS_FROST_PRO] should be looked at instead
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt
index 1d5abdd6..4f65b7f8 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/iab/IABDialogs.kt
@@ -67,7 +67,7 @@ fun Activity.playStoreNotFound() {
}
fun Activity.playStoreProNotAvailable() {
- playStoreLog("Pro query; store not available")
+ L.d("Pro query; store not available")
materialDialogThemed {
title(R.string.uh_oh)
content(R.string.play_store_not_found_pro_query)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/KPrefTextSeekbar.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/KPrefTextSeekbar.kt
index 1d8f308d..4249cd09 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/KPrefTextSeekbar.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/KPrefTextSeekbar.kt
@@ -2,7 +2,7 @@ package com.pitchedapps.frost.views
import android.annotation.SuppressLint
import android.util.TypedValue
-import ca.allanwang.kau.kpref.items.KPrefSeekbar
+import ca.allanwang.kau.kpref.activity.items.KPrefSeekbar
import com.pitchedapps.frost.R
/**
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/RippleCanvas.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/RippleCanvas.kt
deleted file mode 100644
index 719a01cc..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/RippleCanvas.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.pitchedapps.frost.views
-
-import android.animation.ValueAnimator
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.util.AttributeSet
-import android.view.View
-
-/**
- * Created by Allan Wang on 2016-11-17.
- *
- *
- * Canvas drawn ripples that keep the previous color
- * Extends to view dimensions
- * Supports multiple ripples from varying locations
- */
-class RippleCanvas @JvmOverloads constructor(
- context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
-) : View(context, attrs, defStyleAttr) {
- val paint: Paint = Paint()
- var baseColor = Color.TRANSPARENT
- val ripples: MutableList<Ripple> = mutableListOf()
-
- init {
- paint.isAntiAlias = true
- paint.style = Paint.Style.FILL
- }
-
- override fun onDraw(canvas: Canvas) {
- canvas.drawColor(baseColor)
- val itr = ripples.iterator()
- while (itr.hasNext()) {
- val r = itr.next()
- paint.color = r.color
- canvas.drawCircle(r.x, r.y, r.radius, paint)
- if (r.radius == r.maxRadius) {
- itr.remove()
- baseColor = r.color
- }
- }
- }
-
- @JvmOverloads fun ripple(color: Int, startX: Float = 0f, startY: Float = 0f, duration: Int = 1000) {
- var x = startX
- var y = startY
- val w = width.toFloat()
- val h = height.toFloat()
- if (x == MIDDLE)
- x = w / 2
- else if (x > w) x = 0f
- if (y == MIDDLE)
- y = h / 2
- else if (y > h) y = 0f
- val maxRadius = Math.hypot(Math.max(x, w - x).toDouble(), Math.max(y, h - y).toDouble()).toFloat()
- val ripple = Ripple(color, x, y, 0f, maxRadius)
- ripples.add(ripple)
- val animator = ValueAnimator.ofFloat(0f, maxRadius)
- animator.duration = duration.toLong()
- animator.addUpdateListener { animation ->
- ripple.setRadius(animation.animatedValue as Float)
- invalidate()
- }
- animator.start()
- }
-
- fun set(color: Int) {
- baseColor = color
- ripples.clear()
- invalidate()
- }
-
- inner class Ripple internal constructor(val color: Int, val x: Float, val y: Float, var radius: Float, val maxRadius: Float) {
- internal fun setRadius(r: Float) {
- radius = r
- }
- }
-
- companion object {
- val MIDDLE = -1.0f
- }
-}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
index 16a4a092..0a254c50 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
@@ -45,15 +45,15 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
refreshObservable.onNext(false)
return
}
- view.jsInject(JsActions.LOGIN_CHECK,
+ view.jsInject(
CssAssets.ROUND_ICONS.maybe(Prefs.showRoundedIcons),
CssHider.PEOPLE_YOU_MAY_KNOW.maybe(!Prefs.showSuggestedFriends && Prefs.pro),
- CssHider.ADS.maybe(!Prefs.showFacebookAds && Prefs.pro),
- JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null))
+ CssHider.ADS.maybe(!Prefs.showFacebookAds && Prefs.pro)
+ )
onPageFinishedActions(url)
}
- open internal fun onPageFinishedActions(url: String?) {
+ open internal fun onPageFinishedActions(url: String) {
injectAndFinish()
}
@@ -61,9 +61,15 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
L.d("Page finished reveal")
webCore.jsInject(CssHider.HEADER,
Prefs.themeInjector,
- JsAssets.CLICK_A.maybe(webCore.baseEnum != null),
- JsAssets.CONTEXT_A,
- callback = { refreshObservable.onNext(false) })
+ callback = {
+ refreshObservable.onNext(false)
+ webCore.jsInject(
+ JsActions.LOGIN_CHECK,
+ JsAssets.CLICK_A.maybe(webCore.baseEnum != null),
+ JsAssets.CONTEXT_A,
+ JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null)
+ )
+ })
}
open fun handleHtml(html: String) {
@@ -86,7 +92,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
L.i("Url Loading ${request.url}")
- val path = request.url.path
+ val path = request.url.path ?: return super.shouldOverrideUrlLoading(view, request)
if (path.startsWith("/composer/")) return launchRequest(request)
return super.shouldOverrideUrlLoading(view, request)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt
index 0f08bcf3..10648e73 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClientMenu.kt
@@ -1,42 +1,26 @@
package com.pitchedapps.frost.web
-import android.graphics.Bitmap
import android.webkit.WebView
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.injectors.JsAssets
import com.pitchedapps.frost.injectors.jsInject
-import com.pitchedapps.frost.utils.L
-import io.reactivex.subjects.Subject
/**
* Created by Allan Wang on 2017-05-31.
*/
class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(webCore) {
- var content: String? = null
- val progressObservable: Subject<Int> = webCore.progressObservable
- private val contentBaseUrl = "${FB_URL_BASE}notifications"
-
- override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
- super.onPageStarted(view, url, favicon)
- if (content != null) {
- when (url.removePrefix(FB_URL_BASE)) {
- "settings",
- "settings#",
- "settings#!/settings?soft=bookmarks" -> {
- L.d("Load from stored $url")
- view.stopLoading()
- view.loadDataWithBaseURL(contentBaseUrl, content, "text/html", "utf-8", "https://google.ca/test")
- }
- }
- }
+ private val String.shouldInjectMenu
+ get() = when (removePrefix(FB_URL_BASE)) {
+ "settings",
+ "settings#",
+ "settings#!/settings?soft=bookmarks" -> true
+ else -> false
}
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
- if (url == webCore.baseUrl && content == null) {
- jsInject(JsAssets.MENU)
- }
+ if (url.shouldInjectMenu) jsInject(JsAssets.MENU)
}
override fun emit(flag: Int) {
@@ -44,19 +28,8 @@ class FrostWebViewClientMenu(webCore: FrostWebViewCore) : FrostWebViewClient(web
super.injectAndFinish()
}
- override fun onPageFinishedActions(url: String?) {
- when (url?.removePrefix(FB_URL_BASE)) {
- "settings",
- "settings#",
- "settings#!/settings?soft=bookmarks" -> {
- //do nothing; we will further inject before revealing
- }
- else -> injectAndFinish()
- }
+ override fun onPageFinishedActions(url: String) {
+ if (!url.shouldInjectMenu) injectAndFinish()
}
- override fun handleHtml(html: String) {
- super.handleHtml(html)
- content = html //we will not save this locally in case things change
- }
} \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 800a8ef7..4d0ed062 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -94,4 +94,5 @@
<string name="library_dbflow_licenseId">mit</string>
<string name="login_id_failed">Login failed; id not found</string>
<string name="iab_still_in_progress">IAB query is still in progress</string>
+ <string name="new_message">New Message</string>
</resources>
diff --git a/app/src/main/res/values/strings_errors.xml b/app/src/main/res/values/strings_errors.xml
new file mode 100644
index 00000000..162114f7
--- /dev/null
+++ b/app/src/main/res/values/strings_errors.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="null_url_overlay">Empty url given to overlay; exiting</string>
+</resources> \ No newline at end of file
diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml
index 1a332ea4..8b229dea 100644
--- a/app/src/main/res/xml/changelog.xml
+++ b/app/src/main/res/xml/changelog.xml
@@ -9,17 +9,22 @@
-->
<version title="Beta Updates"/>
+ <item text="Reduce Menu loading logic" />
+ <item text="Load js injectors after showing webview" />
+ <item text="Add notifications for messages" />
<item text="" />
- <!--<version title="v1.3"/>-->
+ <item text="" />
+ <item text="" />
+ <item text="" />
+ <item text="" />
+
+ <version title="v1.3"/>
<item text="Create toggle for notifications only from primary account" />
<item text="Micro string optimizations" />
<item text="Add profile icons to notifications" />
<item text="Make notifications expandable" />
<item text="Add notification trigger in settings" />
<item text="Fix bug where only single latest notification is showing" />
- <item text="" />
- <item text="" />
- <item text="" />
<version title="v1.2"/>
<item text="Scale browser on keyboard pop up" />
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
new file mode 100644
index 00000000..d7ec4b46
--- /dev/null
+++ b/app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt
@@ -0,0 +1,39 @@
+package com.pitchedapps.frost.facebook
+
+import org.junit.Test
+import kotlin.test.assertEquals
+
+
+/**
+ * Created by Allan Wang on 2017-07-07.
+ */
+class FbUrlTest {
+
+ @Test
+ fun base() {
+ val url = "https://touch.facebook.com/relative/?asdf=1234&hjkl=7890"
+ assertFbFormat(url, url)
+ }
+
+ @Test
+ fun relative() {
+ val url = "/relative/?asdf=1234&hjkl=7890"
+ assertFbFormat("$FB_URL_BASE${url.substring(1)}", url)
+ }
+
+ @Test
+ fun redirect() {
+ assertFbFormat("$FB_URL_BASE/relative/?asdf=1234&hjkl=7890", "https://touch.facebook.com/l.php?u=$FB_URL_BASE/relative/&asdf=1234&hjkl=7890")
+ }
+
+ @Test fun discard() {
+ val prefix = "$FB_URL_BASE/?test=1234"
+ val suffix = "&apple=notorange"
+ assertFbFormat("$prefix$suffix", "$prefix&ref=hello$suffix")
+ }
+
+ fun assertFbFormat(expected: String, url: String) {
+ val fbUrl = FbUrlFormatter(url)
+ assertEquals(expected, fbUrl.toString(), "FbUrl Mismatch:\n${fbUrl.toLogList().joinToString("\n\t")}")
+ }
+} \ No newline at end of file
diff --git a/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTest.kt b/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTest.kt
deleted file mode 100644
index 3590e625..00000000
--- a/app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTest.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.pitchedapps.frost.utils
-
-import com.pitchedapps.frost.facebook.FbUrlFormatter
-import org.junit.Test
-
-
-/**
- * Created by Allan Wang on 2017-07-07.
- */
-class UrlTest {
-
- @Test
- fun testParse() {
- val url = FbUrlFormatter(TEST_URL)
- url.log()
- println(url.toString())
- }
-
- private fun FbUrlFormatter.log() = toLogList().forEach { println(it) }
-}
-
-const val TEST_URL = "https://touch.facebook.com/ScienceOrientationWeek/?refid=52&_ft_=qid.6440135786032091148%3Amf_story_key.3325631938086219467%3Atop_level_post_id.1555752904487229%3Apage_id.525538540842009&__tn__=C" \ No newline at end of file