aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/kotlin/com
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/kotlin/com')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt4
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt190
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt172
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt40
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt97
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt2
7 files changed, 221 insertions, 286 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
index bb9c45c0..36b51753 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt
@@ -77,10 +77,10 @@ class FrostApp : Application() {
super.onCreate()
- applicationContext.scheduleNotifications(Prefs.notificationFreq)
-
setupNotificationChannels(applicationContext)
+ applicationContext.scheduleNotifications(Prefs.notificationFreq)
+
/**
* Drawer profile loading logic
* Reload the image on every version update
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt
index 8000c106..d9edda78 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/WebFragments.kt
@@ -30,7 +30,6 @@ class WebFragment : BaseFragment() {
}
override fun updateFab(contract: MainFabContract) {
- L.e { "Update fab" }
val web = core as? WebView
if (web == null) {
L.e { "Webview not found in fragment $baseEnum" }
@@ -40,7 +39,6 @@ class WebFragment : BaseFragment() {
contract.showFab(GoogleMaterial.Icon.gmd_edit) {
JsActions.CREATE_POST.inject(web)
}
- L.e { "UPP" }
return
}
super.updateFab(contract)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt
deleted file mode 100644
index 25f10398..00000000
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-package com.pitchedapps.frost.services
-
-import android.annotation.SuppressLint
-import android.app.IntentService
-import android.app.Notification
-import android.app.PendingIntent
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.support.v4.app.NotificationCompat
-import android.support.v4.app.NotificationManagerCompat
-import ca.allanwang.kau.utils.copyFromInputStream
-import ca.allanwang.kau.utils.string
-import com.pitchedapps.frost.R
-import com.pitchedapps.frost.utils.L
-import com.pitchedapps.frost.utils.createMediaFile
-import com.pitchedapps.frost.utils.frostUriFromFile
-import okhttp3.MediaType
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.ResponseBody
-import okio.*
-import org.jetbrains.anko.toast
-import java.io.File
-
-/**
- * Created by Allan Wang on 2017-08-08.
- *
- * Not in use
- *
- * Background file downloader
- * All we are given is a link and a mime type
- *
- * With reference to the <a href="https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java">OkHttp3 sample</a>
- */
-@SuppressLint("Registered")
-class DownloadService : IntentService("FrostVideoDownloader") {
-
- companion object {
- const val EXTRA_URL = "download_url"
- private const val MAX_PROGRESS = 1000
- private const val DOWNLOAD_GROUP = "frost_downloads"
- }
-
- val client: OkHttpClient by lazy { initClient() }
-
- val start = System.currentTimeMillis()
- var totalSize = 0L
- val downloaded = mutableSetOf<String>()
-
- private lateinit var notifBuilder: NotificationCompat.Builder
- private var notifId: Int = -1
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- if (intent != null && intent.flags == PendingIntent.FLAG_CANCEL_CURRENT) {
- L.i { "Cancelling download service" }
- cancelDownload()
- return Service.START_NOT_STICKY
- }
- return super.onStartCommand(intent, flags, startId)
- }
-
- override fun onHandleIntent(intent: Intent?) {
- val url: String = intent?.getStringExtra(EXTRA_URL) ?: return
-
- if (downloaded.contains(url)) return
-
- val request: Request = Request.Builder()
- .url(url)
- .build()
-
- notifBuilder = frostNotification
- notifId = Math.abs(url.hashCode() + System.currentTimeMillis().toInt())
- val cancelIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
-
- notifBuilder.setContentTitle(string(R.string.downloading_video))
- .setCategory(Notification.CATEGORY_PROGRESS)
- .setWhen(System.currentTimeMillis())
- .setProgress(MAX_PROGRESS, 0, false)
- .setOngoing(true)
- .addAction(R.drawable.ic_action_cancel, string(R.string.kau_cancel), cancelIntent)
- .setGroup(DOWNLOAD_GROUP)
-
- client.newCall(request).execute().use { response ->
- if (!response.isSuccessful) {
- L.e { "Video download failed" }
- toast("Video download failed")
- return@use
- }
-
- val stream = response.body()?.byteStream() ?: return@use
- val extension = response.request().body()?.contentType()?.subtype()
- val destination = createMediaFile(if (extension == null) "" else ".$extension")
- destination.copyFromInputStream(stream)
-
- notifBuilder.setContentIntent(getPendingIntent(this, destination))
- notifBuilder.show()
- }
- }
-
- private fun NotificationCompat.Builder.show() {
- NotificationManagerCompat.from(this@DownloadService).notify(DOWNLOAD_GROUP, notifId, build())
- }
-
-
- private fun getPendingIntent(context: Context, file: File): PendingIntent {
- val uri = context.frostUriFromFile(file)
- val type = context.contentResolver.getType(uri)
- L.i { "DownloadType: retrieved pending intent" }
- L._i { "Contents: $uri $type" }
- val intent = Intent(Intent.ACTION_VIEW, uri)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .setDataAndType(uri, type)
- return PendingIntent.getActivity(context, 0, intent, 0)
- }
-
- /**
- * Adds url to downloaded list and modifies the notif builder for the finished state
- * Does not show the new notification
- */
- private fun finishDownload(url: String) {
- L.i { "Video download finished" }
- downloaded.add(url)
- notifBuilder.setContentTitle(string(R.string.downloaded_video))
- .setProgress(0, 0, false).setOngoing(false).setAutoCancel(true)
- .apply { mActions.clear() }
- }
-
- private fun cancelDownload() {
- client.dispatcher().cancelAll()
- NotificationManagerCompat.from(this).cancel(DOWNLOAD_GROUP, notifId)
- }
-
- private fun onProgressUpdate(url: String, type: MediaType?, percentage: Float, done: Boolean) {
- L.v { "Download request progress $percentage for $url" }
- notifBuilder.setProgress(MAX_PROGRESS, (percentage * MAX_PROGRESS).toInt(), false)
- if (done) finishDownload(url)
- notifBuilder.show()
- }
-
- override fun onTaskRemoved(rootIntent: Intent?) {
- super.onTaskRemoved(rootIntent)
- }
-
- private fun initClient(): OkHttpClient = OkHttpClient.Builder()
- .addNetworkInterceptor { chain ->
- val original = chain.proceed(chain.request())
- val body = original.body() ?: return@addNetworkInterceptor original
- if (body.contentLength() > 0L) totalSize += body.contentLength()
- return@addNetworkInterceptor original.newBuilder()
- .body(ProgressResponseBody(
- original.request().url().toString(),
- body,
- this@DownloadService::onProgressUpdate))
- .build()
- }
- .build()
-
- private class ProgressResponseBody(
- val url: String,
- val responseBody: ResponseBody,
- val listener: (url: String, type: MediaType?, percentage: Float, done: Boolean) -> Unit) : ResponseBody() {
-
- private val bufferedSource: BufferedSource by lazy { Okio.buffer(source(responseBody.source())) }
-
- override fun contentLength(): Long = responseBody.contentLength()
-
- override fun contentType(): MediaType? = responseBody.contentType()
-
- override fun source(): BufferedSource = bufferedSource
-
- private fun source(source: Source): Source = object : ForwardingSource(source) {
-
- private var totalBytesRead = 0L
-
- override fun read(sink: Buffer, byteCount: Long): Long {
- val bytesRead = super.read(sink, byteCount)
- // read() returns the number of bytes read, or -1 if this source is exhausted.
- totalBytesRead += if (bytesRead != -1L) bytesRead else 0
- listener(
- url,
- contentType(),
- totalBytesRead.toFloat() / responseBody.contentLength(),
- bytesRead == -1L
- )
- return bytesRead
- }
- }
- }
-} \ No newline at end of file
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 08cf321c..b6427e5b 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt
@@ -1,5 +1,6 @@
package com.pitchedapps.frost.services
+import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -13,12 +14,13 @@ import android.net.Uri
import android.os.BaseBundle
import android.os.Build
import android.os.Bundle
+import android.os.PersistableBundle
+import android.support.annotation.RequiresApi
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.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.FrostWebActivity
import com.pitchedapps.frost.dbflow.CookieModel
@@ -43,32 +45,66 @@ import java.util.*
*
* Logic for build notifications, scheduling notifications, and showing notifications
*/
+const val NOTIF_CHANNEL_GENERAL = "general"
+const val NOTIF_CHANNEL_MESSAGES = "messages"
+
fun setupNotificationChannels(c: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val manager = c.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- val mainChannel = NotificationChannel(BuildConfig.APPLICATION_ID, c.getString(R.string.frost_name), NotificationManager.IMPORTANCE_DEFAULT)
- mainChannel.lightColor = c.color(R.color.facebook_blue)
- mainChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
- manager.createNotificationChannel(mainChannel)
+ val appName = c.string(R.string.frost_name)
+ val msg = c.string(R.string.messages)
+ manager.notificationChannels
+ .filter { it.id != NOTIF_CHANNEL_GENERAL && it.id != NOTIF_CHANNEL_MESSAGES }
+ .forEach { manager.deleteNotificationChannel(it.id) }
+ manager.createNotificationChannel(NOTIF_CHANNEL_GENERAL, appName)
+ manager.createNotificationChannel(NOTIF_CHANNEL_MESSAGES, "$appName: $msg")
+ L.d { "Created notification channels: ${manager.notificationChannels.size} channels, ${manager.notificationChannelGroups.size} groups" }
}
-inline val Context.frostNotification: NotificationCompat.Builder
- get() = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply {
- setSmallIcon(R.drawable.frost_f_24)
- setAutoCancel(true)
- setStyle(NotificationCompat.BigTextStyle())
- color = color(R.color.frost_notification_accent)
- }
+@RequiresApi(Build.VERSION_CODES.O)
+private fun NotificationManager.createNotificationChannel(id: String, name: String): NotificationChannel {
+ val channel = NotificationChannel(id,
+ name, NotificationManager.IMPORTANCE_DEFAULT)
+ channel.enableLights(true)
+ channel.lightColor = Prefs.accentColor
+ channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+ createNotificationChannel(channel)
+ return channel
+}
-fun NotificationCompat.Builder.withDefaults(ringtone: String = Prefs.notificationRingtone) = apply {
- var defaults = 0
- if (Prefs.notificationVibrate) defaults = defaults or Notification.DEFAULT_VIBRATE
- if (Prefs.notificationSound) {
- if (ringtone.isNotBlank()) setSound(Uri.parse(ringtone))
- else defaults = defaults or Notification.DEFAULT_SOUND
+fun Context.frostNotification(id: String) =
+ NotificationCompat.Builder(this, id)
+ .apply {
+ setSmallIcon(R.drawable.frost_f_24)
+ setAutoCancel(true)
+ setOnlyAlertOnce(true)
+ setStyle(NotificationCompat.BigTextStyle())
+ color = color(R.color.frost_notification_accent)
+ }
+
+/**
+ * Dictates whether a notification should have sound/vibration/lights or not
+ * Delegates to channels if Android O and up
+ * Otherwise uses our provided preferences
+ */
+fun NotificationCompat.Builder.setFrostAlert(enable: Boolean, ringtone: String): NotificationCompat.Builder {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ setGroupAlertBehavior(
+ if (enable) Notification.GROUP_ALERT_CHILDREN
+ else Notification.GROUP_ALERT_SUMMARY)
+ } else if (!enable) {
+ setDefaults(0)
+ } else {
+ var defaults = 0
+ if (Prefs.notificationVibrate) defaults = defaults or Notification.DEFAULT_VIBRATE
+ if (Prefs.notificationSound) {
+ if (ringtone.isNotBlank()) setSound(Uri.parse(ringtone))
+ else defaults = defaults or Notification.DEFAULT_SOUND
+ }
+ if (Prefs.notificationLights) defaults = defaults or Notification.DEFAULT_LIGHTS
+ setDefaults(defaults)
}
- if (Prefs.notificationLights) defaults = defaults or Notification.DEFAULT_LIGHTS
- setDefaults(defaults)
+ return this
}
private val _40_DP = 40.dpToPx
@@ -77,6 +113,7 @@ private val _40_DP = 40.dpToPx
* Enum to handle notification creations
*/
enum class NotificationType(
+ private val channelId: String,
private val overlayContext: OverlayContext,
private val fbItem: FbItem,
private val parser: FrostParser<ParseNotification>,
@@ -84,7 +121,8 @@ enum class NotificationType(
private val putTime: (notif: NotificationModel, time: Long) -> NotificationModel,
private val ringtone: () -> String) {
- GENERAL(OverlayContext.NOTIFICATION,
+ GENERAL(NOTIF_CHANNEL_GENERAL,
+ OverlayContext.NOTIFICATION,
FbItem.NOTIFICATIONS,
NotifParser,
NotificationModel::epoch,
@@ -95,7 +133,8 @@ enum class NotificationType(
FrostRunnable.prepareMarkNotificationRead(content.id, cookie)
},
- MESSAGE(OverlayContext.MESSAGE,
+ MESSAGE(NOTIF_CHANNEL_MESSAGES,
+ OverlayContext.MESSAGE,
FbItem.MESSAGES,
MessageParser,
NotificationModel::epochIm,
@@ -121,36 +160,48 @@ enum class NotificationType(
* Get unread data from designated parser
* Display notifications for those after old epoch
* Save new epoch
+ *
+ * Returns the number of notifications generated,
+ * or -1 if an error occurred
*/
- fun fetch(context: Context, data: CookieModel) {
+ fun fetch(context: Context, data: CookieModel): Int {
val response = parser.parse(data.cookie)
- ?: return L.v { "$name notification data not found" }
- val notifs = response.data.getUnreadNotifications(data).filter {
+ if (response == null) {
+ L.v { "$name notification data not found" }
+ return -1
+ }
+ val notifContents = response.data.getUnreadNotifications(data).filter {
val text = it.text
Prefs.notificationKeywords.none { text.contains(it, true) }
}
- if (notifs.isEmpty()) return
- var notifCount = 0
+ if (notifContents.isEmpty()) return 0
val userId = data.id
val prevNotifTime = lastNotificationTime(userId)
val prevLatestEpoch = getTime(prevNotifTime)
L.v { "Notif $name prev epoch $prevLatestEpoch" }
var newLatestEpoch = prevLatestEpoch
- notifs.forEach { notif ->
+ val notifs = mutableListOf<FrostNotification>()
+ notifContents.forEach { notif ->
L.v { "Notif timestamp ${notif.timestamp}" }
if (notif.timestamp <= prevLatestEpoch) return@forEach
- createNotification(context, notif, notifCount == 0)
+ notifs.add(createNotification(context, notif))
if (notif.timestamp > newLatestEpoch)
newLatestEpoch = notif.timestamp
- notifCount++
}
if (newLatestEpoch > prevLatestEpoch)
putTime(prevNotifTime, newLatestEpoch).save()
L.d { "Notif $name new epoch ${getTime(lastNotificationTime(userId))}" }
- summaryNotification(context, userId, notifCount)
+ frostAnswersCustom("Notifications", "Type" to name, "Count" to notifs.size)
+ if (notifs.size > 1)
+ summaryNotification(context, userId, notifs.size).notify(context)
+ val ringtone = ringtone()
+ notifs.forEachIndexed { i, notif ->
+ notif.withAlert(i < 2, ringtone).notify(context)
+ }
+ return notifs.size
}
- private fun debugNotification(context: Context, data: CookieModel) {
+ fun debugNotification(context: Context, data: CookieModel) {
val content = NotificationContent(data,
System.currentTimeMillis(),
"https://github.com/AllanWang/Frost-for-Facebook",
@@ -158,15 +209,13 @@ enum class NotificationType(
"Test 123",
System.currentTimeMillis() / 1000,
"https://www.iconexperience.com/_img/v_collection_png/256x256/shadow/dog.png")
- createNotification(context, content, true)
+ createNotification(context, content).notify(context)
}
/**
* Create and submit a new notification with the given [content]
- * If [withDefaults] is set, it will also add the appropriate sound, vibration, and light
- * Note that when we have multiple notifications coming in at once, we don't want to have defaults for all of them
*/
- private fun createNotification(context: Context, content: NotificationContent, withDefaults: Boolean) {
+ private fun createNotification(context: Context, content: NotificationContent): FrostNotification {
with(content) {
val intent = Intent(context, FrostWebActivity::class.java)
intent.data = Uri.parse(href)
@@ -176,7 +225,7 @@ enum class NotificationType(
val group = "${groupPrefix}_${data.id}"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
- val notifBuilder = context.frostNotification
+ val notifBuilder = context.frostNotification(channelId)
.setContentTitle(title ?: context.string(R.string.frost_name))
.setContentText(text)
.setContentIntent(pendingIntent)
@@ -184,9 +233,6 @@ enum class NotificationType(
.setSubText(data.name)
.setGroup(group)
- if (withDefaults)
- notifBuilder.withDefaults(ringtone())
-
if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000)
L.v { "Notif load $content" }
@@ -204,32 +250,37 @@ enum class NotificationType(
}
}
- NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.build())
+ return FrostNotification(group, notifId, notifBuilder)
}
}
+
/**
* Create a summary notification to wrap the previous ones
* This will always produce sound, vibration, and lights based on preferences
* and will only show if we have at least 2 notifications
*/
- private fun summaryNotification(context: Context, userId: Long, count: Int) {
- frostAnswersCustom("Notifications", "Type" to name, "Count" to count)
- if (count <= 1) return
+ private fun summaryNotification(context: Context, userId: Long, count: Int): FrostNotification {
val intent = Intent(context, FrostWebActivity::class.java)
intent.data = Uri.parse(fbItem.url)
intent.putExtra(ARG_USER_ID, userId)
+ val group = "${groupPrefix}_$userId"
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
- val notifBuilder = context.frostNotification.withDefaults(ringtone())
+ val notifBuilder = context.frostNotification(channelId)
.setContentTitle(context.string(R.string.frost_name))
.setContentText("$count ${context.string(fbItem.titleId)}")
- .setGroup("${groupPrefix}_$userId")
+ .setGroup(group)
.setGroupSummary(true)
.setContentIntent(pendingIntent)
.setCategory(Notification.CATEGORY_SOCIAL)
- NotificationManagerCompat.from(context).notify("${groupPrefix}_$userId", userId.toInt(), notifBuilder.build())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ notifBuilder.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+ }
+
+ return FrostNotification(group, 1, notifBuilder)
}
+
}
/**
@@ -247,6 +298,31 @@ data class NotificationContent(val data: CookieModel,
}
+/**
+ * Wrapper for a complete notification builder and identifier
+ * which can be immediately notified when given a [Context]
+ */
+data class FrostNotification(private val tag: String,
+ private val id: Int,
+ val notif: NotificationCompat.Builder) {
+
+ fun withAlert(enable: Boolean, ringtone: String): FrostNotification {
+ notif.setFrostAlert(enable, ringtone)
+ return this
+ }
+
+ fun notify(context: Context) =
+ NotificationManagerCompat.from(context).notify(tag, id, notif.build())
+}
+
+const val NOTIFICATION_PARAM_ID = "notif_param_id"
+
+private fun JobInfo.Builder.setExtras(id: Int): JobInfo.Builder {
+ val bundle = PersistableBundle()
+ bundle.putInt(NOTIFICATION_PARAM_ID, id)
+ return setExtras(bundle)
+}
+
const val NOTIFICATION_PERIODIC_JOB = 7
/**
@@ -260,6 +336,7 @@ fun Context.scheduleNotifications(minutes: Long): Boolean {
val serviceComponent = ComponentName(this, NotificationService::class.java)
val builder = JobInfo.Builder(NOTIFICATION_PERIODIC_JOB, serviceComponent)
.setPeriodic(minutes * 60000)
+ .setExtras(NOTIFICATION_PERIODIC_JOB)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) //TODO add options
val result = scheduler.schedule(builder.build())
@@ -280,6 +357,7 @@ fun Context.fetchNotifications(): Boolean {
val serviceComponent = ComponentName(this, NotificationService::class.java)
val builder = JobInfo.Builder(NOTIFICATION_JOB_NOW, serviceComponent)
.setMinimumLatency(0L)
+ .setExtras(NOTIFICATION_JOB_NOW)
.setOverrideDeadline(2000L)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
val result = scheduler.schedule(builder.build())
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 fc946772..6ba968e7 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt
@@ -2,11 +2,11 @@ package com.pitchedapps.frost.services
import android.app.job.JobParameters
import android.app.job.JobService
-import android.content.Context
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.dbflow.CookieModel
import com.pitchedapps.frost.dbflow.loadFbCookiesSync
import com.pitchedapps.frost.utils.L
import com.pitchedapps.frost.utils.Prefs
@@ -24,9 +24,9 @@ import java.util.concurrent.Future
*/
class NotificationService : JobService() {
- var future: Future<Unit>? = null
+ private var future: Future<Unit>? = null
- val startTime = System.currentTimeMillis()
+ private val startTime = System.currentTimeMillis()
override fun onStopJob(params: JobParameters?): Boolean {
val time = System.currentTimeMillis() - startTime
@@ -59,31 +59,51 @@ class NotificationService : JobService() {
?: return@doAsync L.eThrow("NotificationService had null weakRef to self")
val currentId = Prefs.userId
val cookies = loadFbCookiesSync()
+ val jobId = params?.extras?.getInt(NOTIFICATION_PARAM_ID, -1) ?: -1
+ var notifCount = 0
cookies.forEach {
val current = it.id == currentId
if (Prefs.notificationsGeneral
&& (current || Prefs.notificationAllAccounts))
- NotificationType.GENERAL.fetch(context, it)
+ notifCount += fetch(jobId, NotificationType.GENERAL, it)
if (Prefs.notificationsInstantMessages
&& (current || Prefs.notificationsImAllAccounts))
- NotificationType.MESSAGE.fetch(context, it)
+ notifCount += fetch(jobId, NotificationType.MESSAGE, it)
}
+
+ if (notifCount == 0 && jobId == NOTIFICATION_JOB_NOW)
+ generalNotification(665, R.string.no_new_notifications, BuildConfig.DEBUG)
+
finish(params)
}
return true
}
+ /**
+ * Implemented fetch to also notify when an error occurs
+ * Also normalized the output to return the number of notifications received
+ */
+ private fun fetch(jobId: Int, type: NotificationType, cookie: CookieModel): Int {
+ val count = type.fetch(this, cookie)
+ if (count < 0) {
+ if (jobId == NOTIFICATION_JOB_NOW)
+ generalNotification(666, R.string.error_notification, BuildConfig.DEBUG)
+ return 0
+ }
+ return count
+ }
+
private fun logNotif(text: String): NotificationContent? {
L.eThrow("NotificationService: $text")
return null
}
- private fun Context.debugNotification(text: String = string(R.string.kau_lorem_ipsum)) {
- if (!BuildConfig.DEBUG) return
- val notifBuilder = frostNotification.withDefaults()
+ private fun generalNotification(id: Int, textRes: Int, withDefaults: Boolean) {
+ val notifBuilder = frostNotification(NOTIF_CHANNEL_GENERAL)
+ .setFrostAlert(withDefaults, Prefs.notificationRingtone)
.setContentTitle(string(R.string.frost_name))
- .setContentText(text)
- NotificationManagerCompat.from(this).notify(999, notifBuilder.build())
+ .setContentText(string(textRes))
+ NotificationManagerCompat.from(this).notify(id, notifBuilder.build())
}
} \ No newline at end of file
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 2ee086c0..962e60ae 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt
@@ -1,14 +1,20 @@
package com.pitchedapps.frost.settings
+import android.annotation.SuppressLint
import android.content.Intent
import android.media.RingtoneManager
import android.net.Uri
+import android.os.Build
+import android.provider.Settings
import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder
import ca.allanwang.kau.kpref.activity.items.KPrefText
import ca.allanwang.kau.utils.minuteToText
import ca.allanwang.kau.utils.string
+import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.activities.SettingsActivity
+import com.pitchedapps.frost.dbflow.NotificationModel
+import com.pitchedapps.frost.dbflow.loadFbCookiesAsync
import com.pitchedapps.frost.services.fetchNotifications
import com.pitchedapps.frost.services.scheduleNotifications
import com.pitchedapps.frost.utils.Prefs
@@ -20,6 +26,7 @@ import com.pitchedapps.frost.views.Keywords
/**
* Created by Allan Wang on 2017-06-29.
*/
+@SuppressLint("InlinedApi")
fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
text(R.string.notification_frequency, Prefs::notificationFreq, { Prefs.notificationFreq = it }) {
@@ -90,47 +97,69 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = {
enabler = Prefs::notificationsInstantMessages
}
- checkbox(R.string.notification_sound, Prefs::notificationSound, {
- Prefs.notificationSound = it
- reloadByTitle(R.string.notification_ringtone,
- R.string.message_ringtone)
- })
-
- fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) {
- enabler = Prefs::notificationSound
- textGetter = {
- if (it.isBlank()) string(R.string.kau_default)
- else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it))
- ?.getTitle(this@getNotificationPrefs) ?: "---" //todo figure out why this happens
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ plainText(R.string.notification_channel) {
+ descRes = R.string.notification_channel_desc
+ onClick = {
+ val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
+ startActivity(intent)
+ }
}
- onClick = {
- val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
- putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, string(R.string.select_ringtone))
- putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
- putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
- putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
- if (item.pref.isNotBlank())
- putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(item.pref))
+ } else {
+ checkbox(R.string.notification_sound, Prefs::notificationSound, {
+ Prefs.notificationSound = it
+ reloadByTitle(R.string.notification_ringtone,
+ R.string.message_ringtone)
+ })
+
+ fun KPrefText.KPrefTextContract<String>.ringtone(code: Int) {
+ enabler = Prefs::notificationSound
+ textGetter = {
+ if (it.isBlank()) string(R.string.kau_default)
+ else RingtoneManager.getRingtone(this@getNotificationPrefs, Uri.parse(it))
+ ?.getTitle(this@getNotificationPrefs)
+ ?: "---" //todo figure out why this happens
+ }
+ onClick = {
+ val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
+ putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, string(R.string.select_ringtone))
+ putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
+ putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
+ putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
+ if (item.pref.isNotBlank())
+ putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(item.pref))
+ }
+ startActivityForResult(intent, code)
}
- startActivityForResult(intent, code)
}
- }
- text(R.string.notification_ringtone, Prefs::notificationRingtone,
- { Prefs.notificationRingtone = it }) {
- ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE)
- }
+ text(R.string.notification_ringtone, Prefs::notificationRingtone,
+ { Prefs.notificationRingtone = it }) {
+ ringtone(SettingsActivity.REQUEST_NOTIFICATION_RINGTONE)
+ }
- text(R.string.message_ringtone, Prefs::messageRingtone,
- { Prefs.messageRingtone = it }) {
- ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE)
- }
+ text(R.string.message_ringtone, Prefs::messageRingtone,
+ { Prefs.messageRingtone = it }) {
+ ringtone(SettingsActivity.REQUEST_MESSAGE_RINGTONE)
+ }
+
+ checkbox(R.string.notification_vibrate, Prefs::notificationVibrate,
+ { Prefs.notificationVibrate = it })
- checkbox(R.string.notification_vibrate, Prefs::notificationVibrate,
- { Prefs.notificationVibrate = it })
+ checkbox(R.string.notification_lights, Prefs::notificationLights,
+ { Prefs.notificationLights = it })
+ }
- checkbox(R.string.notification_lights, Prefs::notificationLights,
- { Prefs.notificationLights = it })
+ if (BuildConfig.DEBUG) {
+ plainText(R.string.reset_notif_epoch) {
+ onClick = {
+ loadFbCookiesAsync {
+ it.map { NotificationModel(it.id) }.forEach { it.save() }
+ }
+ }
+ }
+ }
plainText(R.string.notification_fetch_now) {
descRes = R.string.notification_fetch_now_desc
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 f9f471df..6db3f83c 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt
@@ -41,7 +41,7 @@ object Prefs : KPref() {
var exitConfirmation: Boolean by kpref("exit_confirmation", true)
- var notificationFreq: Long by kpref("notification_freq", 60L)
+ var notificationFreq: Long by kpref("notification_freq", 15L)
var versionCode: Int by kpref("version_code", -1)