aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-06-14 23:39:05 -0700
committerAllan Wang <me@allanwang.ca>2017-06-14 23:39:05 -0700
commit610c37698ab93b8d51efcaec9f721292cacfd854 (patch)
tree2bb97be0f43aa5c5d64237e61dc628938e7350b5 /app/src/main/kotlin/com/pitchedapps
parentfbbc92e4c98a30e107fb2a63887f8b6d20bffabb (diff)
downloadfrost-610c37698ab93b8d51efcaec9f721292cacfd854.tar.gz
frost-610c37698ab93b8d51efcaec9f721292cacfd854.tar.bz2
frost-610c37698ab93b8d51efcaec9f721292cacfd854.zip
Create notification service framework
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt6
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt31
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationExtensions.kt13
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt121
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt2
8 files changed, 177 insertions, 7 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
index 85d1c8b5..09a54444 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -13,6 +13,7 @@ import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerUIUtils
import com.pitchedapps.frost.facebook.FbCookie
+import com.pitchedapps.frost.services.requestNotifications
import com.pitchedapps.frost.utils.CrashReportingTree
import com.pitchedapps.frost.utils.Prefs
import com.raizlabs.android.dbflow.config.FlowConfig
@@ -47,7 +48,7 @@ class FrostApp : Application() {
Prefs.initialize(this, "${com.pitchedapps.frost.BuildConfig.APPLICATION_ID}.prefs")
FbCookie()
super.onCreate()
-
+ requestNotifications(Prefs.userId)
//Drawer profile loading logic
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt
index 9ba83879..386bf4d3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt
@@ -87,7 +87,7 @@ class LoginActivity : BaseActivity() {
(foundImage, name) ->
refresh = false
L.d("Zip done")
- if (!foundImage) L.e("Could not get profile photo; Invalid id?\n\t$cookie")
+ if (!foundImage) L.e("Could not get profile photo; Invalid userId?\n\t$cookie")
textview.setTextWithFade(String.format(getString(R.string.welcome), name), duration = 500)
/*
* The user may have logged into an account that is already in the database
@@ -96,7 +96,7 @@ class LoginActivity : BaseActivity() {
loadFbCookiesAsync {
cookies ->
Handler().postDelayed({
- launchNewTask(MainActivity::class.java, ArrayList(cookies))
+ launchNewTask(MainActivity::class.java, ArrayList(cookies), clearStack = true)
}, 1000)
}
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
index fce3e670..17840a14 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
@@ -1,6 +1,5 @@
package com.pitchedapps.frost
-import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
@@ -30,6 +29,7 @@ import com.pitchedapps.frost.facebook.FbCookie.switchUser
import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
import com.pitchedapps.frost.fragments.WebFragment
+import com.pitchedapps.frost.services.requestNotifications
import com.pitchedapps.frost.utils.*
import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject
@@ -178,7 +178,9 @@ class MainActivity : BaseActivity() {
})
}
R.id.action_changelog -> showChangelog(R.xml.changelog)
- R.id.action_call -> launchNewTask(LoginActivity::class.java)
+ R.id.action_call -> {
+ requestNotifications(Prefs.userId)
+ }
R.id.action_db -> adapter.pages.saveAsync(this)
R.id.action_restart -> restart()
else -> return super.onOptionsItemSelected(item)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
new file mode 100644
index 00000000..e71500fc
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/NotificationDb.kt
@@ -0,0 +1,31 @@
+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.kotlinextensions.*
+import com.raizlabs.android.dbflow.structure.BaseModel
+
+/**
+ * Created by Allan Wang on 2017-05-30.
+ */
+
+@Database(name = NotificationDb.NAME, version = NotificationDb.VERSION)
+object NotificationDb {
+ const val NAME = "Notifications"
+ const val VERSION = 1
+}
+
+@Table(database = NotificationDb::class, allFields = true, primaryKeyConflict = ConflictAction.REPLACE)
+data class NotificationModel(@PrimaryKey var id: Long = -1L, var epoch: Long = -1L) : BaseModel()
+
+fun lastNotificationTime(id: Long): Long = (select from NotificationModel::class where (NotificationModel_Table.id eq id)).querySingle()?.epoch ?: -1L
+
+fun saveNotificationTime(notificationModel: NotificationModel, callback: (() -> Unit)? = null) {
+ notificationModel.async save {
+ L.d("Fb notification $notificationModel saved")
+ callback?.invoke()
+ }
+} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationExtensions.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationExtensions.kt
new file mode 100644
index 00000000..ac94b527
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationExtensions.kt
@@ -0,0 +1,13 @@
+package com.pitchedapps.frost.services
+
+import android.content.Context
+import android.content.Intent
+
+/**
+ * Created by Allan Wang on 2017-06-14.
+ */
+fun Context.requestNotifications(id: Long) {
+ val intent = Intent(this, NotificationService::class.java)
+ intent.putExtra(NotificationService.ARG_ID, id)
+ startService(intent)
+} \ 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
new file mode 100644
index 00000000..3bbecb4f
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -0,0 +1,121 @@
+package com.pitchedapps.frost.services
+
+import android.app.IntentService
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.support.v4.app.ActivityOptionsCompat
+import android.support.v4.app.NotificationCompat
+import android.support.v4.app.NotificationManagerCompat
+import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.R
+import com.pitchedapps.frost.WebOverlayActivity
+import com.pitchedapps.frost.dbflow.NotificationModel
+import com.pitchedapps.frost.dbflow.lastNotificationTime
+import com.pitchedapps.frost.dbflow.loadFbCookie
+import com.pitchedapps.frost.dbflow.saveNotificationTime
+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.ARG_URL
+import com.pitchedapps.frost.utils.L
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+
+/**
+ * Created by Allan Wang on 2017-06-14.
+ */
+class NotificationService : IntentService(NotificationService::class.java.simpleName) {
+
+ companion object {
+ const val ARG_ID = "arg_id"
+ val epochMatcher: Regex by lazy { Regex(":([0-9]*),") }
+ val notifIdMatcher: Regex by lazy { Regex("notif_id\":([0-9]*),") }
+ }
+
+ override fun onHandleIntent(intent: Intent) {
+ val id = intent.getLongExtra(ARG_ID, -1L)
+ L.i("Handling notifications for $id")
+ if (id == -1L) return
+ val data = loadFbCookie(id) ?: return
+ L.i("Using data $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.i("Latest Epoch $latestEpoch")
+ unreadNotifications.forEach {
+ elem ->
+ val notif = parseNotification(data.id, elem)
+ if (notif != null) {
+ if (notif.timestamp <= latestEpoch) return@forEach
+ notif.createNotification(this)
+ latestEpoch = notif.timestamp
+ notifCount++
+ }
+ }
+ saveNotificationTime(NotificationModel(data.id, latestEpoch))
+ summaryNotification(data.id, notifCount)
+ }
+
+ fun parseNotification(userId: Long, element: Element): NotificationContent? {
+ val a = element.getElementsByTag("a").first() ?: return null
+ 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 abbr = element.getElementsByTag("abbr")
+ val timeString = abbr?.text()
+ var text = a.text().replace("\u00a0", " ") //remove &nbsp;
+ if (timeString != null) text = text.removeSuffix(timeString)
+ text = text.trim()
+ val abbrData = abbr?.attr("data-store")
+ val epoch = if (abbrData == null) -1L else epochMatcher.find(abbrData)?.groups?.get(1)?.value?.toLong() ?: -1L
+ return NotificationContent(userId, notifId.toInt(), a.attr("href"), text, epoch)
+ }
+
+ data class NotificationContent(val userId: Long, val notifId: Int, val href: String, val text: String, val timestamp: Long) {
+ fun createNotification(context: Context) {
+ val intent = Intent(context, WebOverlayActivity::class.java)
+// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP or PendingIntent.FLAG_ONE_SHOT)
+ intent.putExtra(ARG_URL, "$FB_URL_BASE$href")
+ intent.action = System.currentTimeMillis().toString() //dummy action
+ val bundle = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.slide_in_right, R.anim.slide_out_right).toBundle()
+ val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT, bundle)
+ val notifBuilder = NotificationCompat.Builder(context)
+ .setSmallIcon(R.drawable.frost_f_24)
+ .setContentTitle(context.string(R.string.app_name))
+ .setContentText(text)
+ .setContentIntent(pendingIntent)
+ .setGroup("frost_$userId")
+ .setAutoCancel(true)
+
+ if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
+
+ NotificationManagerCompat.from(context).notify("frost_$userId", notifId, notifBuilder.build())
+ }
+ }
+
+ fun summaryNotification(userId: Long, count: Int) {
+ if (count <= 1) return
+ val notifBuilder = NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.frost_f_24)
+ .setContentTitle(string(R.string.app_name))
+ .setContentText("$count notifications")
+ .setGroup("frost_$userId")
+ .setGroupSummary(true)
+ .setAutoCancel(true)
+
+ NotificationManagerCompat.from(this).notify("frost_$userId", userId.toInt(), notifBuilder.build())
+ }
+
+ private fun log(element: Element) {
+ with(element) {
+ L.i("\n\nElement ${text()}")
+ attributes().forEach {
+ L.i("attr ${it.html()}")
+ }
+ L.i("Content ${html()}")
+ }
+ }
+
+} \ No newline at end of file
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 4845f553..1ae17cde 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
@@ -61,7 +61,9 @@ open class FrostWebViewClient(val refreshObservable: Subject<Boolean>) : WebView
internal fun onPageFinishedReveal(view: FrostWebViewCore, animate: Boolean) {
L.d("Page finished reveal")
- view.jsInject(CssHider.HEADER, CssAssets.MATERIAL_DARK, callback = {
+ view.jsInject(CssHider.HEADER,
+// CssAssets.MATERIAL_DARK,
+ callback = {
L.d("Finished ${it.contentToString()}")
refreshObservable.onNext(false)
if (animate) view.circularReveal(offset = 150L)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
index 191d9350..721a75de 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt
@@ -52,7 +52,7 @@ class LoginWebView @JvmOverloads constructor(
cookieObservable.onComplete()
loginObservable.onSuccess(CookieModel(id.toLong(), "", cookie))
} catch (e: NumberFormatException) {
- //todo send report that id has changed
+ //todo send report that userId has changed
}
}
}