aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps/frost/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps/frost/services')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt149
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt117
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt1
3 files changed, 198 insertions, 69 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
new file mode 100644
index 00000000..6af6b1db
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -0,0 +1,149 @@
+package com.pitchedapps.frost.services
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.job.JobInfo
+import android.app.job.JobScheduler
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.support.v4.app.NotificationCompat
+import android.support.v4.app.NotificationManagerCompat
+import ca.allanwang.kau.utils.color
+import ca.allanwang.kau.utils.dpToPx
+import ca.allanwang.kau.utils.string
+import com.bumptech.glide.request.target.SimpleTarget
+import com.bumptech.glide.request.transition.Transition
+import com.pitchedapps.frost.BuildConfig
+import com.pitchedapps.frost.FrostWebActivity
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.WebOverlayActivity
+import com.pitchedapps.frost.dbflow.CookieModel
+import com.pitchedapps.frost.dbflow.fetchUsername
+import com.pitchedapps.frost.facebook.FB_URL_BASE
+import com.pitchedapps.frost.utils.GlideApp
+import com.pitchedapps.frost.utils.L
+import com.pitchedapps.frost.utils.withRoundIcon
+import org.jetbrains.anko.runOnUiThread
+
+/**
+ * Created by Allan Wang on 2017-07-08.
+ */
+val Context.frostNotification: NotificationCompat.Builder
+ get() = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply {
+ setSmallIcon(R.drawable.frost_f_24)
+ setAutoCancel(true)
+ color = color(R.color.frost_notification_accent)
+ }
+
+val NotificationCompat.Builder.withBigText: NotificationCompat.BigTextStyle
+ get() = NotificationCompat.BigTextStyle(this)
+
+/**
+ * Created by Allan Wang on 2017-07-08.
+ *
+ * Custom target to set the content view and update a given notification
+ */
+class FrostNotificationTarget(val context: Context,
+ val notifId: Int,
+ val notifTag: String,
+ val builder: NotificationCompat.Builder
+) : SimpleTarget<Bitmap>(40.dpToPx, 40.dpToPx) {
+
+ override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>) {
+ builder.setLargeIcon(resource)
+ NotificationManagerCompat.from(context).notify(notifTag, notifId, builder.withBigText.build())
+ }
+}
+
+/**
+ * Notification data holder
+ */
+data class NotificationContent(val data: CookieModel,
+ val notifId: Int,
+ val href: String,
+ 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("${FB_URL_BASE}$href")
+ intent.putExtra(WebOverlayActivity.ARG_USER_ID, data.id)
+ val group = "frost_${data.id}"
+ val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
+ val notifBuilder = context.frostNotification
+ .setContentTitle(context.string(R.string.app_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())
+
+ if (profileUrl.isNotBlank()) {
+ context.runOnUiThread {
+ GlideApp.with(context)
+ .asBitmap()
+ .load(profileUrl)
+ .withRoundIcon()
+ .into(FrostNotificationTarget(context, notifId, group, notifBuilder))
+ }
+ }
+ }
+ }
+}
+
+const val NOTIFICATION_PERIODIC_JOB = 7
+
+/**
+ * [interval] is # of min, which must be at least 15
+ * returns false if an error occurs; true otherwise
+ */
+fun Context.scheduleNotifications(minutes: Long): Boolean {
+ val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
+ scheduler.cancel(NOTIFICATION_PERIODIC_JOB)
+ if (minutes < 0L) return true
+ val serviceComponent = ComponentName(this, NotificationService::class.java)
+ val builder = JobInfo.Builder(NOTIFICATION_PERIODIC_JOB, serviceComponent)
+ .setPeriodic(minutes * 60000)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //TODO add options
+ val result = scheduler.schedule(builder.build())
+ if (result <= 0) {
+ L.eThrow("Notification scheduler failed")
+ return false
+ }
+ return true
+}
+
+const val NOTIFICATION_JOB_NOW = 6
+
+/**
+ * Run notification job right now
+ */
+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)
+ .setMinimumLatency(0L)
+ .setOverrideDeadline(2000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ val result = scheduler.schedule(builder.build())
+ if (result <= 0) {
+ L.eThrow("Notification scheduler failed")
+ return false
+ }
+ return true
+} \ No newline at end of file
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 d00ca8f0..4c03f056 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,18 @@
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.FrostWebActivity
import com.pitchedapps.frost.R
-import com.pitchedapps.frost.WebOverlayActivity
import com.pitchedapps.frost.dbflow.*
import com.pitchedapps.frost.facebook.FACEBOOK_COM
-import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
import com.pitchedapps.frost.utils.frostAnswersCustom
-import com.pitchedapps.frost.utils.frostNotification
import org.jetbrains.anko.doAsync
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
@@ -36,6 +28,12 @@ class NotificationService : JobService() {
var future: Future<Unit>? = null
+ companion object {
+ val epochMatcher: Regex by lazy { Regex(":([0-9]*?),") }
+ val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*?),") }
+ val profMatcher: Regex by lazy { Regex("url\\(\"(.*?)\"\\)") }
+ }
+
override fun onStopJob(params: JobParameters?): Boolean {
future?.cancel(true)
future = null
@@ -44,27 +42,15 @@ class NotificationService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
future = doAsync {
- loadFbCookiesSync().forEach {
- data ->
- L.i("Handle notifications for $data")
- val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).get()
- val unreadNotifications = doc.getElementById("notifications_list").getElementsByClass("aclb")
- var notifCount = 0
- var latestEpoch = lastNotificationTime(data.id)
- L.v("Latest Epoch $latestEpoch")
- unreadNotifications.forEach unread@ {
- elem ->
- val notif = parseNotification(data, elem)
- if (notif != null) {
- if (notif.timestamp <= latestEpoch) return@unread
- notif.createNotification(this@NotificationService)
- latestEpoch = notif.timestamp
- notifCount++
- }
+ if (Prefs.notificationAllAccounts) {
+ loadFbCookiesSync().forEach {
+ data ->
+ fetchNotifications(data)
}
- if (notifCount > 0) saveNotificationTime(NotificationModel(data.id, latestEpoch))
- frostAnswersCustom("Notifications") { putCustomAttribute("Count", notifCount) }
- summaryNotification(data.id, notifCount)
+ } else {
+ val currentCookie = loadFbCookie(Prefs.userId)
+ if (currentCookie != null)
+ fetchNotifications(currentCookie)
}
L.d("Finished notifications")
jobFinished(params, false)
@@ -73,13 +59,35 @@ class NotificationService : JobService() {
return true
}
- companion object {
- val epochMatcher: Regex by lazy { Regex(":([0-9]*),") }
- val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*),") }
+ fun fetchNotifications(data: CookieModel) {
+ L.i("Notif fetch for $data")
+ val doc = Jsoup.connect(FbTab.NOTIFICATIONS.url).cookie(FACEBOOK_COM, data.cookie).get()
+ //aclb for unread, acw for read
+ val unreadNotifications = doc.getElementById("notifications_list").getElementsByClass("aclb")
+ var notifCount = 0
+// val prevLatestEpoch = 1498931565L // for testing
+ val prevLatestEpoch = lastNotificationTime(data.id)
+ L.v("Notif Prev Latest Epoch $prevLatestEpoch")
+ var newLatestEpoch = prevLatestEpoch
+ unreadNotifications.forEach unread@ {
+ elem ->
+ val notif = parseNotification(data, elem) ?: return@unread
+ L.v("Notif timestamp ${notif.timestamp}")
+ if (notif.timestamp <= prevLatestEpoch) return@unread
+ notif.createNotification(this@NotificationService)
+ if (notif.timestamp > newLatestEpoch)
+ newLatestEpoch = notif.timestamp
+ notifCount++
+ }
+ if (newLatestEpoch != prevLatestEpoch) saveNotificationTime(NotificationModel(data.id, newLatestEpoch))
+ frostAnswersCustom("Notifications") { 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()
@@ -89,48 +97,21 @@ class NotificationService : JobService() {
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
- return NotificationContent(data, notifId.toInt(), a.attr("href"), text, epoch)
+ //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)
}
private fun Context.debugNotification(text: String) {
- if (BuildConfig.DEBUG) {
- val notifBuilder = frostNotification
- .setContentTitle(string(R.string.app_name))
- .setContentText(text)
-
- NotificationManagerCompat.from(this).notify(999, notifBuilder.build())
- }
- }
-
- data class NotificationContent(val data: CookieModel, val notifId: Int, val href: String, val text: String, val timestamp: Long) {
- 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("$FB_URL_BASE$href")
- intent.putExtra(WebOverlayActivity.ARG_USER_ID, data.id)
- val group = "frost_${data.id}"
- val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
- val notifBuilder = context.frostNotification
- .setContentTitle(context.string(R.string.app_name))
- .setContentText(text)
- .setContentIntent(pendingIntent)
- .setCategory(Notification.CATEGORY_SOCIAL)
- .setSubText(data.name)
- .setGroup(group)
-
- if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
-
- NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.build())
- }
- }
+ if (!BuildConfig.DEBUG) return
+ val notifBuilder = frostNotification
+ .setContentTitle(string(R.string.app_name))
+ .setContentText(text)
+ NotificationManagerCompat.from(this).notify(999, notifBuilder.build())
}
fun summaryNotification(userId: Long, count: Int) {
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 a239e58f..52f7412f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
@@ -5,7 +5,6 @@ import android.content.Context
import android.content.Intent
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
-import com.pitchedapps.frost.utils.scheduleNotifications
/**
* Created by Allan Wang on 2017-05-31.