From 91119de328bf5f4e8c945f8fb470453319b9f0ed Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 13 Jul 2017 13:50:00 -0700 Subject: 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 --- app/build.gradle | 13 +-- app/proguard-rules.pro | 8 -- .../pitchedapps/frost/ExampleInstrumentedTest.java | 26 ------ app/src/main/assets/js/menu.js | 1 - app/src/main/assets/js/menu.min.js | 3 +- .../kotlin/com/pitchedapps/frost/AboutActivity.kt | 2 +- .../com/pitchedapps/frost/SettingsActivity.kt | 10 +- .../com/pitchedapps/frost/WebOverlayActivity.kt | 9 ++ .../com/pitchedapps/frost/dbflow/NotificationDb.kt | 22 +++-- .../pitchedapps/frost/facebook/FbUrlFormatter.kt | 6 +- .../com/pitchedapps/frost/injectors/CssHider.kt | 3 +- .../frost/services/FrostNotifications.kt | 5 +- .../frost/services/NotificationService.kt | 103 +++++++++++++++++---- .../com/pitchedapps/frost/settings/Appearance.kt | 8 +- .../com/pitchedapps/frost/settings/Behaviour.kt | 2 +- .../com/pitchedapps/frost/settings/Experimental.kt | 2 +- .../kotlin/com/pitchedapps/frost/settings/Feed.kt | 2 +- .../pitchedapps/frost/settings/Notifications.kt | 2 +- .../kotlin/com/pitchedapps/frost/utils/Prefs.kt | 2 + .../com/pitchedapps/frost/utils/iab/IABDialogs.kt | 2 +- .../pitchedapps/frost/views/KPrefTextSeekbar.kt | 2 +- .../com/pitchedapps/frost/views/RippleCanvas.kt | 83 ----------------- .../pitchedapps/frost/web/FrostWebViewClient.kt | 22 +++-- .../frost/web/FrostWebViewClientMenu.kt | 45 ++------- app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/strings_errors.xml | 4 + app/src/main/res/xml/changelog.xml | 13 ++- .../com/pitchedapps/frost/facebook/FbUrlTest.kt | 39 ++++++++ .../kotlin/com/pitchedapps/frost/utils/UrlTest.kt | 22 ----- 29 files changed, 216 insertions(+), 246 deletions(-) delete mode 100644 app/src/androidTest/java/com/pitchedapps/frost/ExampleInstrumentedTest.java delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/views/RippleCanvas.kt create mode 100644 app/src/main/res/values/strings_errors.xml create mode 100644 app/src/test/kotlin/com/pitchedapps/frost/facebook/FbUrlTest.kt delete mode 100644 app/src/test/kotlin/com/pitchedapps/frost/utils/UrlTest.kt (limited to 'app') 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$* { -# ; -#} --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 Testing documentation - */ -@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) : AlterTableMigration(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   + 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   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 = 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 = 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 @@ mit Login failed; id not found IAB query is still in progress + New Message 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 @@ + + + Empty url given to overlay; exiting + \ 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 @@ --> + + + - + + + + + + - - - 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 -- cgit v1.2.3