aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com/pitchedapps
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com/pitchedapps')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt21
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt38
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt3
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt85
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt20
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt28
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt9
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt1
12 files changed, 180 insertions, 58 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
index cdded244..cdfebfac 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt
@@ -30,8 +30,12 @@ import com.pitchedapps.frost.facebook.FbTab
import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL
import com.pitchedapps.frost.fragments.WebFragment
import com.pitchedapps.frost.utils.*
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import org.jetbrains.anko.childrenSequence
+import org.jsoup.Jsoup
+import java.util.concurrent.TimeUnit
class MainActivity : BaseActivity() {
@@ -45,6 +49,7 @@ class MainActivity : BaseActivity() {
lateinit var drawerHeader: AccountHeader
var webFragmentObservable = PublishSubject.create<Int>()!!
var lastPosition = -1
+ val headerBadgeObservable = PublishSubject.create<String>()
companion object {
const val FRAGMENT_REFRESH = 99
@@ -97,6 +102,22 @@ class MainActivity : BaseActivity() {
currentFragment.web.scrollOrRefresh()
}
})
+ headerBadgeObservable.throttleFirst(15, TimeUnit.SECONDS).subscribeOn(Schedulers.newThread())
+ .map { Jsoup.parse(it) }
+ .filter { it.select("[data-sigil=\"count\"]").size >= 0 } //ensure headers exist
+ .map {
+ val feed = it.select("[data-sigil*=\"feed\"] [data-sigil=\"count\"]")
+ val requests = it.select("[data-sigil*=\"requests\"] [data-sigil=\"count\"]")
+ val messages = it.select("[data-sigil*=\"messages\"] [data-sigil=\"count\"]")
+ val notifications = it.select("[data-sigil*=\"notifications\"] [data-sigil=\"count\"]")
+ return@map arrayOf(feed, requests, messages, notifications).map { it?.getOrNull(0)?.ownText() }
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe {
+ (feed, requests, messages, notifications) ->
+ L.d("Header subscription $feed $requests $messages $notifications")
+ L.d("contained nulls ${feed == null}")
+ }
adapter.pages.forEach { tabs.addTab(tabs.newTab().setIcon(it.icon.toDrawable(this, sizeDp = 20, color = Prefs.iconColor))) }
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt
index 0a9732b4..b55b2a10 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/SettingsActivity.kt
@@ -6,16 +6,18 @@ import ca.allanwang.kau.kpref.KPrefAdapterBuilder
import ca.allanwang.kau.utils.*
import ca.allanwang.kau.views.RippleCanvas
import com.pitchedapps.frost.utils.*
+import org.jetbrains.anko.toast
/**
* Created by Allan Wang on 2017-06-06.
*/
class SettingsActivity : KPrefActivity() {
+
override fun onCreateKPrefs(savedInstanceState: android.os.Bundle?): KPrefAdapterBuilder.() -> Unit = {
textColor = { Prefs.textColor }
accentColor = { Prefs.textColor }
header(R.string.settings)
- text<Int>(R.string.theme, { Prefs.theme }, { Prefs.theme = it }) {
+ text(R.string.theme, { Prefs.theme }, { Prefs.theme = it }) {
onClick = {
_, _, item ->
this@SettingsActivity.materialDialogThemed {
@@ -65,16 +67,40 @@ class SettingsActivity : KPrefActivity() {
allowCustom = true
}
- colorPicker(R.string.icon_color, { Prefs.customIconColor }, { Prefs.customIconColor = it; toolbar.setTitleTextColor(it) }) {
- enabler = { Prefs.isCustomTheme }
- onDisabledClick = { itemView, _, _ -> itemView.snackbar(R.string.requires_custom_theme); true }
- allowCustomAlpha = false
- allowCustom = true
+ fun Long.timeToText(): String =
+ if (this == -1L) string(R.string.none)
+ else if (this == 60L) string(R.string.one_hour)
+ else if (this == 1440L) string(R.string.one_day)
+ else if (this % 1440L == 0L) String.format(string(R.string.x_days), this / 1440L)
+ else if (this % 60L == 0L) String.format(string(R.string.x_hours), this / 60L)
+ else String.format(string(R.string.x_minutes), this)
+
+ text(R.string.notifications, { Prefs.notificationFreq }, { Prefs.notificationFreq = it; reloadByTitle(R.string.notifications) }) {
+ val options = longArrayOf(-1, 15, 30, 60, 120, 180, 300, 1440, 2880)
+ val texts = options.map { it.timeToText() }
+ onClick = {
+ _, _, item ->
+ this@SettingsActivity.materialDialogThemed {
+ title(R.string.notifications)
+ items(texts)
+ itemsCallbackSingleChoice(options.indexOf(item.pref), {
+ _, _, which, text ->
+ item.pref = options[which]
+ this@SettingsActivity.scheduleNotifications(item.pref)
+ this@SettingsActivity.toast(text)
+ true
+ })
+ }
+ true
+ }
+ textGetter = { it.timeToText() }
}
+
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ setFrostTheme()
themeExterior(false)
}
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
index 46946ab9..1d1aa86e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/dbflow/CookiesDb.kt
@@ -42,6 +42,9 @@ fun loadFbCookiesAsync(callback: (cookies: List<CookieModel>) -> Unit) {
(select from CookieModel::class).orderBy(CookieModel_Table.name, true).async().queryListResultCallback { _, tResult -> callback.invoke(tResult) }.execute()
}
+fun loadFbCookiesSync(): List<CookieModel> = (select from CookieModel::class).orderBy(CookieModel_Table.name, true).queryList()
+
+
fun saveFbCookie(cookie: CookieModel, callback: (() -> Unit)? = null) {
cookie.async save {
L.d("Fb cookie $cookie saved")
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
index 864f95ea..e3db5444 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsAssets.kt
@@ -10,7 +10,7 @@ import com.pitchedapps.frost.utils.L
* //TODO add folder mapping using Prefs
*/
enum class JsAssets : InjectorContract {
- MENU, MENU_CLICK, CLICK_INTERCEPTOR
+ MENU, MENU_CLICK, CLICK_INTERCEPTOR, HEADER_BADGES
;
var file = "${name.toLowerCase()}.min.js"
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt
deleted file mode 100644
index b37ca1f8..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationReceiver.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.pitchedapps.frost.services
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-
-/**
- * Created by Allan Wang on 2017-05-31.
- */
-class NotificationReceiver : BroadcastReceiver() {
-
- companion object {
- const val ACTION = "com.pitchedapps.frost.NOTIFICATIONS"
- }
-
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action != ACTION) return
- }
-
-} \ 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 85765541..f506bd8f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -1,14 +1,18 @@
package com.pitchedapps.frost.services
-import android.app.IntentService
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.os.Looper
import android.support.v4.app.ActivityOptionsCompat
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationManagerCompat
+import ca.allanwang.kau.utils.checkThread
import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.WebOverlayActivity
import com.pitchedapps.frost.dbflow.*
@@ -17,43 +21,58 @@ 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.jetbrains.anko.doAsync
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
+import java.util.concurrent.Future
/**
* Created by Allan Wang on 2017-06-14.
*/
-class NotificationService : IntentService(NotificationService::class.java.simpleName) {
+class NotificationService : JobService() {
- 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]*),") }
+ var future: Future<Unit>? = null
+
+ override fun onStopJob(params: JobParameters?): Boolean {
+ future?.cancel(true)
+ future = null
+ return false
}
- 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.v("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.v("Latest Epoch $latestEpoch")
- unreadNotifications.forEach {
- elem ->
- val notif = parseNotification(data, elem)
- if (notif != null) {
- if (notif.timestamp <= latestEpoch) return@forEach
- notif.createNotification(this)
- latestEpoch = notif.timestamp
- notifCount++
+ override fun onStartJob(params: JobParameters?): Boolean {
+ future = doAsync {
+ loadFbCookiesSync().forEach {
+ data ->
+ L.i("Handling notifications for ${data.id}")
+ L.v("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.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 (notifCount > 0) saveNotificationTime(NotificationModel(data.id, latestEpoch))
+ summaryNotification(data.id, notifCount)
}
+ L.d("Finished notifications")
+ jobFinished(params, false)
}
- if (notifCount > 0) saveNotificationTime(NotificationModel(data.id, latestEpoch))
- summaryNotification(data.id, notifCount)
+ return true
+ }
+
+ 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]*),") }
}
fun parseNotification(data: CookieModel, element: Element): NotificationContent? {
@@ -71,6 +90,18 @@ class NotificationService : IntentService(NotificationService::class.java.simple
return NotificationContent(data, notifId.toInt(), a.attr("href"), text, epoch)
}
+ private fun Context.debugNotification(text: String) {
+ if (BuildConfig.DEBUG) {
+ val notifBuilder = NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.frost_f_24)
+ .setContentTitle(string(R.string.app_name))
+ .setContentText(text)
+ .setAutoCancel(true)
+
+ 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) {
val intent = Intent(context, WebOverlayActivity::class.java)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
new file mode 100644
index 00000000..c40750ef
--- /dev/null
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/UpdateReceiver.kt
@@ -0,0 +1,20 @@
+package com.pitchedapps.frost.services
+
+import android.content.BroadcastReceiver
+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.
+ */
+class UpdateReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ L.d("Frost has updated")
+ context.scheduleNotifications(Prefs.notificationFreq) //Update notifications
+ }
+
+} \ No newline at end of file
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 e217da46..b319fcc3 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -31,6 +31,8 @@ object Prefs : KPref() {
var exitConfirmation: Boolean by kpref("exit_confirmation", true)
+ var notificationFreq: Long by kpref("notification_freq", -1L)
+
private val loader = lazyResettable { Theme.values[Prefs.theme] }
private val t: Theme by loader
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
index abbd8366..52922822 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt
@@ -1,11 +1,13 @@
package com.pitchedapps.frost.utils
import android.app.Activity
+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.Color
import android.graphics.drawable.ColorDrawable
-import android.support.v4.app.ActivityOptionsCompat
import android.support.v4.content.ContextCompat
import android.support.v7.widget.Toolbar
import android.view.View
@@ -18,6 +20,7 @@ import com.pitchedapps.frost.WebOverlayActivity
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FB_URL_BASE
import com.pitchedapps.frost.facebook.FbTab
+import com.pitchedapps.frost.services.NotificationService
/**
* Created by Allan Wang on 2017-06-03.
@@ -99,4 +102,27 @@ fun Activity.setFrostColors(toolbar: Toolbar? = null, themeWindow: Boolean = tru
texts.forEach { it.setTextColor(Prefs.textColor) }
headers.forEach { it.setBackgroundColor(darkAccent) }
backgrounds.forEach { it.setBackgroundColor(Prefs.bgColor) }
+}
+
+
+const val NOTIFICATION_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_JOB)
+ if (minutes < 0L) return true
+ val serviceComponent = ComponentName(this, NotificationService::class.java)
+ val builder = JobInfo.Builder(NOTIFICATION_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.e("Notification scheduler failed")
+ return false
+ }
+ return true
} \ No newline at end of file
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
index 36193b8b..532b9f82 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostJSI.kt
@@ -10,12 +10,16 @@ import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.cookies
import com.pitchedapps.frost.utils.launchNewTask
import com.pitchedapps.frost.utils.launchWebOverlay
+import io.reactivex.subjects.Subject
/**
* Created by Allan Wang on 2017-06-01.
*/
class FrostJSI(val context: Context, val webView: FrostWebViewCore) {
+
+ val headerObservable: Subject<String>? = (context as? MainActivity)?.headerBadgeObservable
+
val cookies: ArrayList<CookieModel>
get() = (context as? MainActivity)?.cookies() ?: arrayListOf()
@@ -51,4 +55,9 @@ class FrostJSI(val context: Context, val webView: FrostWebViewCore) {
webView.post { webView.frostWebClient!!.handleHtml(html) }
}
+ @JavascriptInterface
+ fun handleHeader(html: String) {
+ headerObservable?.onNext(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 0fe3304a..d015e22e 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClient.kt
@@ -3,7 +3,10 @@ package com.pitchedapps.frost.web
import android.content.Context
import android.graphics.Bitmap
import android.view.KeyEvent
-import android.webkit.*
+import android.webkit.WebResourceRequest
+import android.webkit.WebResourceResponse
+import android.webkit.WebView
+import android.webkit.WebViewClient
import com.pitchedapps.frost.LoginActivity
import com.pitchedapps.frost.MainActivity
import com.pitchedapps.frost.SelectorActivity
@@ -50,7 +53,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
refreshObservable.onNext(false)
return
}
- JsActions.LOGIN_CHECK.inject(view)
+ view.jsInject(JsActions.LOGIN_CHECK, JsAssets.HEADER_BADGES.maybe(webCore.baseEnum != null))
onPageFinishedActions(url)
}
@@ -62,7 +65,7 @@ open class FrostWebViewClient(val webCore: FrostWebViewCore) : WebViewClient() {
L.d("Page finished reveal")
webCore.jsInject(CssHider.HEADER,
Prefs.themeInjector,
-// JsAssets.CLICK_INTERCEPTOR,
+ // JsAssets.CLICK_INTERCEPTOR,
callback = {
refreshObservable.onNext(false)
})
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
index b6353252..6e9e956f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewCore.kt
@@ -41,6 +41,7 @@ class FrostWebViewCore @JvmOverloads constructor(
val refreshObservable: PublishSubject<Boolean> // Only emits on page loads
val titleObservable: BehaviorSubject<String> // Only emits on different non http titles
+
var baseUrl: String? = null
var baseEnum: FbTab? = null
internal var frostWebClient: FrostWebViewClient? = null