diff options
author | Allan Wang <me@allanwang.ca> | 2017-08-14 20:48:39 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-14 20:48:39 -0700 |
commit | 5d9a3fd7fb8f2f9d0f592c89446824980c9841c6 (patch) | |
tree | a770b2564b67280fcc9fcc65144bd0b8bd8e2881 /app/src/main/kotlin/com/pitchedapps/frost/services | |
parent | ab7ec131b62ac1567e983c846c921bd3ada11dd4 (diff) | |
download | frost-5d9a3fd7fb8f2f9d0f592c89446824980c9841c6.tar.gz frost-5d9a3fd7fb8f2f9d0f592c89446824980c9841c6.tar.bz2 frost-5d9a3fd7fb8f2f9d0f592c89446824980c9841c6.zip |
v1.4.5 (#174)v1.4.5
* Update/kau (#125)
* Update logger
* Clean imports and bring back reactive libs
* Update dependencies and make billing async
* Misc (#128)
* Update null
* Attempt to improve transparent theme backgrounds
* Update menu
* Move injections to visible method and reduce offset
* Update searchview and logging
* Clean temp strings and add network states
* Move console blacklist to web state
* Change some logs to info
* Move glide loader to onCreate (#135)
* Remove commit number increments (#139)
* Fix/misc (#140)
* Add canadian locale to toLowerCase
* Add try catch to JsAssets
* Disable error throwing for bad search subject
* Log more throwables quietly
* Check internet connection before fetching username
* Remove name check in frost notifications
* Add activity lifecycle logger
* Add rxjava to lib showcase
* Move network checker to io thread (#150)
* Update dependency
* Blank
* Feature/jsoup debugger (#152)
* Create debugger
* Update debugger content
* Create debugging logic
* Finalize and test debugger
* Add reload listener
* Fix/pro crash without play store (#155)
* Update changelog
* Check if iab service exists
* Add checker before launching play store request
* Separate strings
* Enhancement/message notifications (#157)
* Map message notifs to the headless html extractor
* Update strings
* Bring im notifs out of alpha
* Update changelog
* Remove confirmation dialog (#159)
* Separate message notifications and add click intents (#171)
* Separate message notifications and add click intent for group notifications
* Add comments and finalize
* Feature/scroll down on message thread (#172)
* Add hook for scroll
* Update changelog
* Add custom navdrawer layout (#173)
* Add faq for auto play
* Update changelog
* Fix page banner bg (#163)
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/services')
3 files changed, 123 insertions, 59 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index 2453d3b0..d3dfe79c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -21,7 +21,6 @@ import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.dbflow.CookieModel -import com.pitchedapps.frost.dbflow.fetchUsername import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.L @@ -31,6 +30,12 @@ import org.jetbrains.anko.runOnUiThread /** * Created by Allan Wang on 2017-07-08. + * + * Logic for build notifications, scheduling notifications, and showing notifications + */ + +/** + * Wrap the default builder with our icon and accent color */ val Context.frostNotification: NotificationCompat.Builder get() = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply { @@ -39,6 +44,9 @@ val Context.frostNotification: NotificationCompat.Builder color = color(R.color.frost_notification_accent) } +/** + * Assign global changes to the notification after it is built + */ @Suppress("DEPRECATION") //The update feature is for Android O and seems to still be in beta fun Notification.frostConfig() = apply { @@ -54,6 +62,7 @@ val NotificationCompat.Builder.withBigText: NotificationCompat.BigTextStyle * Created by Allan Wang on 2017-07-08. * * Custom target to set the content view and update a given notification + * 40dp is the size of the right avatar */ class FrostNotificationTarget(val context: Context, val notifId: Int, @@ -67,6 +76,9 @@ class FrostNotificationTarget(val context: Context, } } +internal const val FROST_NOTIFICATION_GROUP = "frost" +internal const val FROST_MESSAGE_NOTIFICATION_GROUP = "frost_im" + /** * Notification data holder */ @@ -77,39 +89,35 @@ data class NotificationContent(val data: CookieModel, val text: String, val timestamp: Long, val profileUrl: String) { - fun createNotification(context: Context, verifiedUser: Boolean = false) { - //in case we haven't found the name, we will try one more time before passing the notification - if (!verifiedUser && data.name?.isBlank() ?: true) { - data.fetchUsername { - data.name = it - createNotification(context, true) - } - } else { - val intent = Intent(context, FrostWebActivity::class.java) - intent.data = Uri.parse(href.formattedFbUrl) - intent.putExtra(ARG_USER_ID, data.id) - val group = "frost_${data.id}" - val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) - val notifBuilder = context.frostNotification - .setContentTitle(title ?: context.string(R.string.frost_name)) - .setContentText(text) - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_SOCIAL) - .setSubText(data.name) - .setGroup(group) - - if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) - L.v("Notif load $this") - NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build().frostConfig()) - - if (profileUrl.isNotBlank()) { - context.runOnUiThread { - Glide.with(context) - .asBitmap() - .load(profileUrl) - .withRoundIcon() - .into(FrostNotificationTarget(context, notifId, group, notifBuilder)) - } + fun createNotification(context: Context) = createNotification(context, FROST_NOTIFICATION_GROUP) + + fun createMessageNotification(context: Context) = createNotification(context, FROST_MESSAGE_NOTIFICATION_GROUP) + + private fun createNotification(context: Context, groupPrefix: String) { + val intent = Intent(context, FrostWebActivity::class.java) + intent.data = Uri.parse(href.formattedFbUrl) + intent.putExtra(ARG_USER_ID, data.id) + val group = "${groupPrefix}_${data.id}" + val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) + val notifBuilder = context.frostNotification + .setContentTitle(title ?: context.string(R.string.frost_name)) + .setContentText(text) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SOCIAL) + .setSubText(data.name) + .setGroup(group) + + if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) + L.v("Notif load", this.toString()) + NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build().frostConfig()) + + if (profileUrl.isNotBlank()) { + context.runOnUiThread { + Glide.with(context) + .asBitmap() + .load(profileUrl) + .withRoundIcon() + .into(FrostNotificationTarget(context, notifId, group, notifBuilder)) } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index fe7758cc..5859a306 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -1,26 +1,33 @@ package com.pitchedapps.frost.services +import android.app.Notification +import android.app.PendingIntent import android.app.job.JobParameters import android.app.job.JobService import android.content.Context +import android.content.Intent +import android.net.Uri import android.support.v4.app.NotificationManagerCompat import ca.allanwang.kau.utils.string import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R +import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.lastNotificationTime import com.pitchedapps.frost.dbflow.loadFbCookie import com.pitchedapps.frost.dbflow.loadFbCookiesSync import com.pitchedapps.frost.facebook.FACEBOOK_COM -import com.pitchedapps.frost.facebook.FbTab +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.formattedFbUrl +import com.pitchedapps.frost.injectors.JsAssets +import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswersCustom -import com.pitchedapps.frost.web.MessageWebView +import com.pitchedapps.frost.web.launchHeadlessHtmlExtractor +import io.reactivex.schedulers.Schedulers import org.jetbrains.anko.doAsync -import org.jetbrains.anko.uiThread import org.jsoup.Jsoup import org.jsoup.nodes.Element import java.util.concurrent.Future @@ -30,6 +37,9 @@ import java.util.concurrent.Future * * Service to manage notifications * Will periodically check through all accounts in the db and send notifications when appropriate + * + * Note that general notifications are parsed directly with Jsoup, + * but instant messages are done so with a headless webview as it is generated from JS */ class NotificationService : JobService() { @@ -58,7 +68,7 @@ class NotificationService : JobService() { fun finish(params: JobParameters?) { val time = System.currentTimeMillis() - startTime - L.d("Notification service has finished in $time ms") + L.i("Notification service has finished in $time ms") frostAnswersCustom("NotificationTime", "Type" to "Service", "IM Included" to Prefs.notificationsInstantMessages, @@ -68,8 +78,8 @@ class NotificationService : JobService() { future = null } - override fun onStartJob(params: JobParameters?): Boolean { + L.i("Fetching notifications") future = doAsync { if (Prefs.notificationAllAccounts) { val cookies = loadFbCookiesSync() @@ -83,9 +93,15 @@ class NotificationService : JobService() { L.d("Finished main notifications") if (Prefs.notificationsInstantMessages) { val currentCookie = loadFbCookie(Prefs.userId) - if (currentCookie != null) - uiThread { MessageWebView(this@NotificationService, params, currentCookie) } - } else finish(params) + if (currentCookie != null) { + fetchMessageNotifications(currentCookie) { + L.i("Notif IM fetching finished ${if (it) "succesfully" else "unsuccessfully"}") + finish(params) + } + return@doAsync + } + } + finish(params) } return true } @@ -95,13 +111,21 @@ class NotificationService : JobService() { return null } + /* + * ---------------------------------------------------------------- + * General notification logic. + * Fetch notifications -> Filter new ones -> Parse notifications -> + * Show notifications -> Show group notification + * ---------------------------------------------------------------- + */ + fun fetchGeneralNotifications(data: CookieModel) { - L.i("Notif fetch for $data") - val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() + L.d("Notif fetch", data.toString()) + val doc = Jsoup.connect(FbItem.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).userAgent(USER_AGENT_BASIC).get() //aclb for unread, acw for read val unreadNotifications = (doc.getElementById("notifications_list") ?: return L.eThrow("Notification list not found")).getElementsByClass("aclb") var notifCount = 0 -// val prevLatestEpoch = 1498931565L // for testing + //val prevLatestEpoch = 1498931565L // for testing val prevNotifTime = lastNotificationTime(data.id) val prevLatestEpoch = prevNotifTime.epoch L.v("Notif Prev Latest Epoch $prevLatestEpoch") @@ -122,7 +146,6 @@ class NotificationService : JobService() { summaryNotification(data.id, notifCount) } - fun parseNotification(data: CookieModel, element: Element): NotificationContent? { val a = element.getElementsByTag("a").first() ?: return logNotif("IM No a tag") val abbr = element.getElementsByTag("abbr") @@ -134,13 +157,36 @@ class NotificationService : JobService() { if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out //fetch profpic val p = element.select("i.img[style*=url]") - val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: "" + val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: "" return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl) } - fun fetchMessageNotifications(data: CookieModel, content: String) { - L.i("Notif IM fetch for $data") - val doc = Jsoup.parseBodyFragment(content) + fun summaryNotification(userId: Long, count: Int) + = summaryNotification(userId, count, R.string.notifications, FbItem.NOTIFICATIONS.url, FROST_NOTIFICATION_GROUP) + + /* + * ---------------------------------------------------------------- + * Instant message notification logic. + * Fetch notifications -> Filter new ones -> Parse notifications -> + * Show notifications -> Show group notification + * ---------------------------------------------------------------- + */ + + inline fun fetchMessageNotifications(data: CookieModel, crossinline callback: (success: Boolean) -> Unit) { + launchHeadlessHtmlExtractor(FbItem.MESSAGES.url, JsAssets.NOTIF_MSG) { + it.observeOn(Schedulers.newThread()).subscribe { + (html, errorRes) -> + L.d("Notf IM html received") + if (errorRes != -1) return@subscribe callback(false) + fetchMessageNotifications(data, html) + callback(true) + } + } + } + + fun fetchMessageNotifications(data: CookieModel, html: String) { + L.d("Notif IM fetch", data.toString()) + val doc = Jsoup.parseBodyFragment(html) val unreadNotifications = (doc.getElementById("threadlist_rows") ?: return L.eThrow("Notification messages not found")).getElementsByClass("aclb") var notifCount = 0 val prevNotifTime = lastNotificationTime(data.id) @@ -152,7 +198,7 @@ class NotificationService : JobService() { val notif = parseMessageNotification(data, elem) ?: return@unread L.v("Notif im timestamp ${notif.timestamp}") if (notif.timestamp <= prevLatestEpoch) return@unread - notif.createNotification(this@NotificationService) + notif.createMessageNotification(this@NotificationService) if (notif.timestamp > newLatestEpoch) newLatestEpoch = notif.timestamp notifCount++ @@ -160,7 +206,7 @@ class NotificationService : JobService() { if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save() L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}") frostAnswersCustom("Notifications", "Type" to "Message", "Count" to notifCount) - summaryNotification(data.id, notifCount) + summaryMessageNotification(data.id, notifCount) } fun parseMessageNotification(data: CookieModel, element: Element): NotificationContent? { @@ -174,11 +220,14 @@ class NotificationService : JobService() { if (Prefs.notificationKeywords.any { text.contains(it, ignoreCase = true) }) return null //notification filtered out //fetch convo pic val p = element.select("i.img[style*=url]") - val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value ?: "" - L.v("url ${a.attr("href")}") - return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl.formattedFbUrl) + val pUrl = profMatcher.find(p.attr("style"))?.groups?.get(1)?.value?.formattedFbUrl ?: "" + L.v("url", a.attr("href")) + return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl) } + fun summaryMessageNotification(userId: Long, count: Int) + = summaryNotification(userId, count, R.string.messages, FbItem.MESSAGES.url, FROST_MESSAGE_NOTIFICATION_GROUP) + private fun Context.debugNotification(text: String) { if (!BuildConfig.DEBUG) return val notifBuilder = frostNotification @@ -187,15 +236,21 @@ class NotificationService : JobService() { NotificationManagerCompat.from(this).notify(999, notifBuilder.build().frostConfig()) } - fun summaryNotification(userId: Long, count: Int) { + private fun summaryNotification(userId: Long, count: Int, contentRes: Int, pendingUrl: String, groupPrefix: String) { if (count <= 1) return + val intent = Intent(this, FrostWebActivity::class.java) + intent.data = Uri.parse(pendingUrl) + intent.putExtra(ARG_USER_ID, userId) + val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) val notifBuilder = frostNotification .setContentTitle(string(R.string.frost_name)) - .setContentText("$count notifications") - .setGroup("frost_$userId") + .setContentText("$count ${string(contentRes)}") + .setGroup("${groupPrefix}_$userId") .setGroupSummary(true) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SOCIAL) - NotificationManagerCompat.from(this).notify("frost_$userId", userId.toInt(), notifBuilder.build().frostConfig()) + NotificationManagerCompat.from(this).notify("${groupPrefix}_$userId", userId.toInt(), notifBuilder.build().frostConfig()) } }
\ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt index 52f7412f..9e53889e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt @@ -13,6 +13,7 @@ import com.pitchedapps.frost.utils.Prefs */ class UpdateReceiver : BroadcastReceiver() { + //todo check action warning override fun onReceive(context: Context, intent: Intent) { L.d("Frost has updated") context.scheduleNotifications(Prefs.notificationFreq) //Update notifications |