From 085295d5dbda84ff02221cc65bd472fff69e636e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 8 Jul 2017 19:21:13 -0400 Subject: Dev 1.2.1 (#43) - Revamp notifications * Test proguard * Test proguard without enums * Allow notifications from only current account * Prettify notifications * Clean up layouts * Test proguard log * Update rxkotlin * Update remaining dependencies --- app/proguard-rules.pro | 19 +-- .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 4 +- .../kotlin/com/pitchedapps/frost/LoginActivity.kt | 10 +- .../kotlin/com/pitchedapps/frost/MainActivity.kt | 11 ++ .../frost/services/FrostNotifications.kt | 149 +++++++++++++++++++++ .../frost/services/NotificationService.kt | 117 +++++++--------- .../pitchedapps/frost/services/UpdateReceiver.kt | 1 - .../pitchedapps/frost/settings/Notifications.kt | 18 ++- .../kotlin/com/pitchedapps/frost/utils/Prefs.kt | 2 + .../kotlin/com/pitchedapps/frost/utils/Utils.kt | 40 +----- .../com/pitchedapps/frost/views/AccountItem.kt | 4 +- app/src/main/res/values/strings_preferences | 6 + app/src/main/res/xml/changelog.xml | 15 +++ docs/Changelog.md | 8 ++ gradle.properties | 12 +- 15 files changed, 283 insertions(+), 133 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6df89e1c..9df5ace7 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -3,11 +3,11 @@ -keep class * extends com.raizlabs.android.dbflow.config.DatabaseHolder { *; } -keepattributes *Annotation* # Enums --keepclassmembers class * extends java.lang.Enum { - public *; - public static **[] values(); - public static ** valueOf(java.lang.String); -} +#-keepclassmembers class * extends java.lang.Enum { +# public *; +# public static **[] values(); +# public static ** valueOf(java.lang.String); +#} # Crashlytics -keepattributes SourceFile,LineNumberTable -keep public class * extends java.lang.Exception @@ -23,9 +23,12 @@ # IAB -keep class com.android.vending.billing.** # About libs --keep class .R --keep class **.R$* { - ; +#-keep class .R +#-keep class **.R$* { +# ; +#} +-keepclasseswithmembers class **.R$* { + public static final int define_*; } # Glide -keep public class * implements com.bumptech.glide.module.GlideModule diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index 85a49730..4b62d244 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -4,7 +4,6 @@ import android.app.Application import android.graphics.drawable.Drawable import android.net.Uri import android.widget.ImageView -import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.signature.ApplicationVersionSignature import com.crashlytics.android.Crashlytics @@ -20,7 +19,6 @@ import com.raizlabs.android.dbflow.config.FlowConfig import com.raizlabs.android.dbflow.config.FlowManager import io.fabric.sdk.android.Fabric import timber.log.Timber -import timber.log.Timber.DebugTree import java.util.* @@ -42,7 +40,7 @@ class FrostApp : Application() { // if (LeakCanary.isInAnalyzerProcess(this)) return // refWatcher = LeakCanary.install(this) if (BuildConfig.DEBUG) { - Timber.plant(DebugTree()) + Timber.plant(Timber.DebugTree()) // LeakCanary.enableDisplayLeakActivity(this) } else { Fabric.with(this, Crashlytics(), Answers()) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt index 9b79b996..a27a1ee2 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/LoginActivity.kt @@ -13,19 +13,14 @@ import ca.allanwang.kau.utils.fadeOut import com.bumptech.glide.Glide import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.Target import com.crashlytics.android.answers.LoginEvent import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.fetchUsername import com.pitchedapps.frost.dbflow.loadFbCookiesAsync import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL -import com.pitchedapps.frost.utils.L -import com.pitchedapps.frost.utils.frostAnswers -import com.pitchedapps.frost.utils.launchNewTask -import com.pitchedapps.frost.utils.setFrostColors +import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.web.LoginWebView import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers @@ -110,8 +105,7 @@ class LoginActivity : BaseActivity() { fun loadProfile(id: Long) { - val options = RequestOptions().transform(CircleCrop()) - Glide.with(this@LoginActivity).load(PROFILE_PICTURE_URL(id)).apply(options).listener(object : RequestListener { + Glide.with(this@LoginActivity).load(PROFILE_PICTURE_URL(id)).withRoundIcon().listener(object : RequestListener { override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { profileObservable.onSuccess(true) return false diff --git a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt index f9a597db..ec7bb838 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/MainActivity.kt @@ -49,6 +49,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.NotificationContent import com.pitchedapps.frost.utils.* import com.pitchedapps.frost.utils.iab.validatePro import com.pitchedapps.frost.views.BadgedIcon @@ -58,6 +59,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject +import org.jetbrains.anko.doAsync import org.jsoup.Jsoup import java.util.concurrent.TimeUnit @@ -145,6 +147,15 @@ class MainActivity : BaseActivity(), FrostWebViewSearch.SearchContract, } setFrostColors(toolbar, themeWindow = false, headers = arrayOf(tabs, appBar), backgrounds = arrayOf(viewPager)) validatePro() + + doAsync { + val debugIcon = "https://scontent-sea1-1.xx.fbcdn.net/v/t1.0-1/cp0/e15/q65/p120x120/12994387_243040309382307_4586627375882013710_n.jpg?efg=eyJpIjoidCJ9&oh=4f99b56bb3dab33d1312bd502ff91974&oe=59C8F9AB" + NotificationContent(loadFbCookie(Prefs.userId)!!, 1234, + "https://www.google.ca/", + this@MainActivity.string(R.string.kau_lorem_ipsum), + System.currentTimeMillis(), debugIcon) + .createNotification(this@MainActivity, true) + } } fun tabsForEachView(action: (position: Int, view: BadgedIcon) -> Unit) { 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(40.dpToPx, 40.dpToPx) { + + override fun onResourceReady(resource: Bitmap, transition: Transition) { + 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? = 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. 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 03ed517b..5986a998 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -2,11 +2,13 @@ package com.pitchedapps.frost.settings import ca.allanwang.kau.kpref.KPrefAdapterBuilder import ca.allanwang.kau.utils.minuteToText +import ca.allanwang.kau.utils.snackbar import com.pitchedapps.frost.R import com.pitchedapps.frost.SettingsActivity +import com.pitchedapps.frost.services.fetchNotifications +import com.pitchedapps.frost.services.scheduleNotifications import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.materialDialogThemed -import com.pitchedapps.frost.utils.scheduleNotifications import com.pitchedapps.frost.views.Keywords /** @@ -49,4 +51,18 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { } } + checkbox(R.string.notification_all_accounts, { Prefs.notificationAllAccounts }, { Prefs.notificationAllAccounts = it }) { + descRes = R.string.notification_all_accounts_desc + } + + plainText(R.string.notification_fetch_now) { + descRes = R.string.notification_fetch_now_desc + onClick = { + _, _, _ -> + snackbar(if (fetchNotifications()) R.string.notification_fetch_success else R.string.notification_fetch_fail) + true + } + } + + } \ 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 95642a7a..7ce8ca10 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -85,6 +85,8 @@ object Prefs : KPref() { var notificationKeywords: StringSet by kpref("notification_keywords", mutableSetOf()) + var notificationAllAccounts: Boolean by kpref("notification_all_accounts", 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/Utils.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt index 6f3459d7..3d3025cf 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Utils.kt @@ -1,31 +1,29 @@ 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.graphics.Color import android.graphics.drawable.ColorDrawable import android.support.annotation.StringRes import android.support.design.internal.SnackbarContentLayout import android.support.design.widget.Snackbar -import android.support.v4.app.NotificationCompat import android.support.v7.widget.Toolbar import android.view.View import android.widget.FrameLayout import android.widget.TextView import ca.allanwang.kau.utils.* import com.afollestad.materialdialogs.MaterialDialog +import com.bumptech.glide.RequestBuilder import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.module.AppGlideModule +import com.bumptech.glide.request.RequestOptions import com.crashlytics.android.answers.Answers import com.crashlytics.android.answers.CustomEvent import com.pitchedapps.frost.* import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.FbTab import com.pitchedapps.frost.facebook.formattedFbUrl -import com.pitchedapps.frost.services.NotificationService /** * Created by Allan Wang on 2017-06-03. @@ -64,14 +62,6 @@ fun WebOverlayActivity.url(): String { return intent.extras?.getString(ARG_URL) ?: FbTab.FEED.url } -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) - } - - fun Context.materialDialogThemed(action: MaterialDialog.Builder.() -> Unit): MaterialDialog { val builder = MaterialDialog.Builder(this).theme() builder.action() @@ -111,29 +101,6 @@ fun Activity.setFrostColors(toolbar: Toolbar? = null, themeWindow: Boolean = tru 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.eThrow("Notification scheduler failed") - return false - } - return true -} - fun frostAnswers(action: Answers.() -> Unit) { if (BuildConfig.DEBUG || !Prefs.analytics) return Answers.getInstance().action() @@ -163,3 +130,4 @@ fun Activity.frostNavigationBar() { navigationBarColor = if (Prefs.tintNavBar) Prefs.headerColor else Color.BLACK } +fun RequestBuilder.withRoundIcon() = apply(RequestOptions().transform(CircleCrop())) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt index 0fb9dbbb..5186b46c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/views/AccountItem.kt @@ -21,6 +21,7 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.facebook.PROFILE_PICTURE_URL import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.withRoundIcon /** * Created by Allan Wang on 2017-06-05. @@ -35,8 +36,7 @@ class AccountItem(val cookie: CookieModel?) : KauIItem { + Glide.with(itemView).load(PROFILE_PICTURE_URL(cookie.id)).withRoundIcon().listener(object : RequestListener { override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { text.fadeIn() return false diff --git a/app/src/main/res/values/strings_preferences b/app/src/main/res/values/strings_preferences index e9cc643c..f70ffe59 100644 --- a/app/src/main/res/values/strings_preferences +++ b/app/src/main/res/values/strings_preferences @@ -44,6 +44,12 @@ Add Keyword Type keyword and press + Empty Keyword + Notify from all accounts + Get notifications for every account that is logged in. Disabling this will only fetch notifications form the currently selected account. + Fetch Notifications Now + Trigger the notification fetcher one time. + Fetching Notifications… + Couldn\'t fetch notifications Fancy Animations Reveal webviews using ripples and animate transitions diff --git a/app/src/main/res/xml/changelog.xml b/app/src/main/res/xml/changelog.xml index b1bf7a5c..1a332ea4 100644 --- a/app/src/main/res/xml/changelog.xml +++ b/app/src/main/res/xml/changelog.xml @@ -2,10 +2,25 @@ + + + + + + + + + + + + + diff --git a/docs/Changelog.md b/docs/Changelog.md index b21ae2dd..e7c3b7ab 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,13 @@ # Changelog +## Beta Updates +* Create toggle for notifications only from primary account +* Micro string optimizations +* Add profile icons to notifications +* Make notifications expandable +* Add notification trigger in settings +* Fix bug where only single latest notification is showing + ## v1.2 * Scale browser on keyboard pop up * Clean up web overlay diff --git a/gradle.properties b/gradle.properties index 758a2007..630f03f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,16 +17,16 @@ MIN_SDK=21 TARGET_SDK=26 BUILD_TOOLS=26.0.0 -KAU=bb8ea41755 +KAU=880d433e47 KOTLIN=1.1.3 -MATERIAL_DRAWER=5.9.3 -MATERIAL_DRAWER_KT=1.0.4 -IICON_MATERIAL=2.2.0.2 -IICON_COMMUNITY=1.9.32.1 +MATERIAL_DRAWER=5.9.4 +MATERIAL_DRAWER_KT=1.0.5 +IICON_MATERIAL=2.2.0.3 +IICON_COMMUNITY=1.9.32.2 JSOUP=1.10.3 GLIDE=4.0.0-RC1 DBFLOW=4.0.4 PAPER_PARCEL=2.0.1 CRASHLYTICS=2.6.8 LEAK_CANARY=1.5.1 -ROBOELECTRIC=3.3.2 \ No newline at end of file +ROBOELECTRIC=3.4-rc3 \ No newline at end of file -- cgit v1.2.3