From 94716835011f7899949205f07b3d0d45e38521bb Mon Sep 17 00:00:00 2001 From: rebelonion <87634197+rebelonion@users.noreply.github.com> Date: Mon, 18 Mar 2024 23:51:00 -0500 Subject: [PATCH] feat: AlarmManager option for notifications --- app/src/main/AndroidManifest.xml | 17 + app/src/main/java/ani/dantotsu/App.kt | 54 +-- .../notifications/AlarmManagerScheduler.kt | 77 +++++ .../notifications/BootCompletedReceiver.kt | 64 ++++ .../dantotsu/notifications/TaskScheduler.kt | 48 +++ .../notifications/WorkManagerScheduler.kt | 62 ++++ .../anilist/AnilistNotificationReceiver.kt | 25 ++ .../anilist/AnilistNotificationTask.kt | 109 ++++++ .../anilist/AnilistNotificationWorker.kt | 98 +----- .../comment/CommentNotificationReceiver.kt | 18 + .../comment/CommentNotificationTask.kt | 316 ++++++++++++++++++ .../comment/CommentNotificationWorker.kt | 306 +---------------- .../ani/dantotsu/settings/SettingsActivity.kt | 39 +++ .../dantotsu/settings/saving/PrefManager.kt | 1 + .../dantotsu/settings/saving/Preferences.kt | 1 + app/src/main/res/layout/activity_settings.xml | 18 + app/src/main/res/values/strings.xml | 1 + 17 files changed, 822 insertions(+), 432 deletions(-) create mode 100644 app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationTask.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt create mode 100644 app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 52ad1196..11800322 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + @@ -314,6 +315,22 @@ + + + + + + + + + + + + + + (PrefName.UseAlarmManager) - // CommentNotificationWorker - val commentInterval = CommentNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.CommentNotificationInterval)] - if (commentInterval.toInt() != 0) { - val recurringWork = PeriodicWorkRequest.Builder( - CommentNotificationWorker::class.java, - commentInterval, java.util.concurrent.TimeUnit.MINUTES) - .setConstraints(constraints) - .build() - androidx.work.WorkManager.getInstance(this).enqueueUniquePeriodicWork( - CommentNotificationWorker.WORK_NAME, - androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - recurringWork - ) - } else { - androidx.work.WorkManager.getInstance(this).cancelUniqueWork(CommentNotificationWorker.WORK_NAME) - //run once - androidx.work.WorkManager.getInstance(this).enqueue(OneTimeWorkRequest.Companion.from( - CommentNotificationWorker::class.java)) - } + TaskScheduler.create(this, useAlarmManager).scheduleAllTasks(this) + + androidx.work.WorkManager.getInstance(this) + .enqueue(OneTimeWorkRequest.Companion.from(CommentNotificationWorker::class.java)) + + androidx.work.WorkManager.getInstance(this) + .enqueue(OneTimeWorkRequest.Companion.from(AnilistNotificationWorker::class.java)) - // AnilistNotificationWorker - val anilistInterval = AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)] - if (anilistInterval.toInt() != 0) { - val recurringWork = PeriodicWorkRequest.Builder( - AnilistNotificationWorker::class.java, - anilistInterval, java.util.concurrent.TimeUnit.MINUTES) - .setConstraints(constraints) - .build() - androidx.work.WorkManager.getInstance(this).enqueueUniquePeriodicWork( - AnilistNotificationWorker.WORK_NAME, - androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - recurringWork - ) - } else { - androidx.work.WorkManager.getInstance(this).cancelUniqueWork(AnilistNotificationWorker.WORK_NAME) - //run once - androidx.work.WorkManager.getInstance(this).enqueue(OneTimeWorkRequest.Companion.from(AnilistNotificationWorker::class.java)) - } - androidx.work.WorkManager.getInstance(this).cancelUniqueWork("ani.dantotsu.notifications.NotificationWorker") //legacy worker } diff --git a/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt b/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt new file mode 100644 index 00000000..63dc757c --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt @@ -0,0 +1,77 @@ +package ani.dantotsu.notifications + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import ani.dantotsu.notifications.anilist.AnilistNotificationReceiver +import ani.dantotsu.notifications.comment.CommentNotificationReceiver +import ani.dantotsu.notifications.TaskScheduler.TaskType +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import java.util.concurrent.TimeUnit + +class AlarmManagerScheduler(private val context: Context) : TaskScheduler { + override fun scheduleRepeatingTask(taskType: TaskType, interval: Long) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val intent = when (taskType) { + TaskType.COMMENT_NOTIFICATION -> Intent( + context, + CommentNotificationReceiver::class.java + ) + + TaskType.ANILIST_NOTIFICATION -> Intent( + context, + AnilistNotificationReceiver::class.java + ) + } + + val pendingIntent = PendingIntent.getBroadcast( + context, + taskType.ordinal, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val triggerAtMillis = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(interval) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + triggerAtMillis, + pendingIntent + ) + } else { + alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent) + } + } catch (e: SecurityException) { + PrefManager.setVal(PrefName.UseAlarmManager, false) + TaskScheduler.create(context, true).cancelAllTasks() + TaskScheduler.create(context, false).scheduleAllTasks(context) + } + } + + override fun cancelTask(taskType: TaskType) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val intent = when (taskType) { + TaskType.COMMENT_NOTIFICATION -> Intent( + context, + CommentNotificationReceiver::class.java + ) + + TaskType.ANILIST_NOTIFICATION -> Intent( + context, + AnilistNotificationReceiver::class.java + ) + } + + val pendingIntent = PendingIntent.getBroadcast( + context, + taskType.ordinal, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + alarmManager.cancel(pendingIntent) + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt new file mode 100644 index 00000000..3ebc9412 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt @@ -0,0 +1,64 @@ +package ani.dantotsu.notifications + +import android.app.AlarmManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import ani.dantotsu.notifications.anilist.AnilistNotificationWorker +import ani.dantotsu.notifications.comment.CommentNotificationWorker +import ani.dantotsu.notifications.TaskScheduler.TaskType +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.util.Logger + +class BootCompletedReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { + if (context != null) { + val scheduler = AlarmManagerScheduler(context) + PrefManager.init(context) + Logger.init(context) + Logger.log("Starting Dantotsu Subscription Service on Boot") + if (PrefManager.getVal(PrefName.UseAlarmManager)) { + val commentInterval = + CommentNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.CommentNotificationInterval)] + val anilistInterval = + AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)] + scheduler.scheduleRepeatingTask( + TaskType.COMMENT_NOTIFICATION, + commentInterval + ) + scheduler.scheduleRepeatingTask( + TaskType.ANILIST_NOTIFICATION, + anilistInterval + ) + } + } + } + } +} + +class AlarmPermissionStateReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) { + if (context != null) { + PrefManager.init(context) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val canScheduleExactAlarms = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + alarmManager.canScheduleExactAlarms() + } else { + true + } + if(canScheduleExactAlarms) { + TaskScheduler.create(context, false).cancelAllTasks() + TaskScheduler.create(context, true).scheduleAllTasks(context) + } else { + TaskScheduler.create(context, true).cancelAllTasks() + TaskScheduler.create(context, false).scheduleAllTasks(context) + } + PrefManager.setVal(PrefName.UseAlarmManager, canScheduleExactAlarms) + } + } + } +} diff --git a/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt b/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt new file mode 100644 index 00000000..1a75422c --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt @@ -0,0 +1,48 @@ +package ani.dantotsu.notifications + +import android.content.Context +import ani.dantotsu.notifications.anilist.AnilistNotificationWorker +import ani.dantotsu.notifications.comment.CommentNotificationWorker +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName + +interface TaskScheduler { + fun scheduleRepeatingTask(taskType: TaskType, interval: Long) + fun cancelTask(taskType: TaskType) + + fun cancelAllTasks() { + for (taskType in TaskType.entries) { + cancelTask(taskType) + } + } + + fun scheduleAllTasks(context: Context) { + for (taskType in TaskType.entries) { + val interval = when (taskType) { + TaskType.COMMENT_NOTIFICATION -> CommentNotificationWorker.checkIntervals[PrefManager.getVal( + PrefName.CommentNotificationInterval)] + TaskType.ANILIST_NOTIFICATION -> AnilistNotificationWorker.checkIntervals[PrefManager.getVal( + PrefName.AnilistNotificationInterval)] + } + scheduleRepeatingTask(taskType, interval) + } + } + + companion object { + fun create(context: Context, useAlarmManager: Boolean): TaskScheduler { + return if (useAlarmManager) { + AlarmManagerScheduler(context) + } else { + WorkManagerScheduler(context) + } + } + } + enum class TaskType { + COMMENT_NOTIFICATION, + ANILIST_NOTIFICATION + } +} + +interface Task { + suspend fun execute(context: Context): Boolean +} diff --git a/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt b/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt new file mode 100644 index 00000000..aa8c9439 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt @@ -0,0 +1,62 @@ +package ani.dantotsu.notifications + +import android.content.Context +import androidx.work.Constraints +import androidx.work.PeriodicWorkRequest +import ani.dantotsu.notifications.anilist.AnilistNotificationWorker +import ani.dantotsu.notifications.comment.CommentNotificationWorker +import ani.dantotsu.notifications.TaskScheduler.TaskType + +class WorkManagerScheduler(private val context: Context) : TaskScheduler { + override fun scheduleRepeatingTask(taskType: TaskType, interval: Long) { + val constraints = Constraints.Builder() + .setRequiredNetworkType(androidx.work.NetworkType.CONNECTED) + .build() + + when (taskType) { + TaskType.COMMENT_NOTIFICATION -> { + val recurringWork = PeriodicWorkRequest.Builder( + CommentNotificationWorker::class.java, + interval, java.util.concurrent.TimeUnit.MINUTES, + PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, java.util.concurrent.TimeUnit.MINUTES + ) + .setConstraints(constraints) + .build() + androidx.work.WorkManager.getInstance(context).enqueueUniquePeriodicWork( + CommentNotificationWorker.WORK_NAME, + androidx.work.ExistingPeriodicWorkPolicy.UPDATE, + recurringWork + ) + } + + TaskType.ANILIST_NOTIFICATION -> { + val recurringWork = PeriodicWorkRequest.Builder( + AnilistNotificationWorker::class.java, + interval, java.util.concurrent.TimeUnit.MINUTES, + PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, java.util.concurrent.TimeUnit.MINUTES + ) + .setConstraints(constraints) + .build() + androidx.work.WorkManager.getInstance(context).enqueueUniquePeriodicWork( + AnilistNotificationWorker.WORK_NAME, + androidx.work.ExistingPeriodicWorkPolicy.UPDATE, + recurringWork + ) + } + } + } + + override fun cancelTask(taskType: TaskType) { + when (taskType) { + TaskType.COMMENT_NOTIFICATION -> { + androidx.work.WorkManager.getInstance(context) + .cancelUniqueWork(CommentNotificationWorker.WORK_NAME) + } + + TaskType.ANILIST_NOTIFICATION -> { + androidx.work.WorkManager.getInstance(context) + .cancelUniqueWork(AnilistNotificationWorker.WORK_NAME) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt new file mode 100644 index 00000000..ace8029c --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt @@ -0,0 +1,25 @@ +package ani.dantotsu.notifications.anilist + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import ani.dantotsu.notifications.AlarmManagerScheduler +import ani.dantotsu.notifications.TaskScheduler +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.util.Logger +import kotlinx.coroutines.runBlocking + +class AnilistNotificationReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + Logger.log("AnilistNotificationReceiver: onReceive") + if (context != null) { + runBlocking { + AnilistNotificationTask().execute(context) + } + val anilistInterval = + AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)] + AlarmManagerScheduler(context).scheduleRepeatingTask(TaskScheduler.TaskType.ANILIST_NOTIFICATION, anilistInterval) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationTask.kt b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationTask.kt new file mode 100644 index 00000000..56be25ad --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationTask.kt @@ -0,0 +1,109 @@ +package ani.dantotsu.notifications.anilist + +import android.Manifest +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import ani.dantotsu.MainActivity +import ani.dantotsu.R +import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.notifications.Task +import ani.dantotsu.profile.activity.ActivityItemBuilder +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.util.Logger +import eu.kanade.tachiyomi.data.notification.Notifications +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class AnilistNotificationTask : Task { + override suspend fun execute(context: Context): Boolean { + try { + withContext(Dispatchers.IO) { + PrefManager.init(context) //make sure prefs are initialized + val userId = PrefManager.getVal(PrefName.AnilistUserId) + if (userId.isNotEmpty()) { + Anilist.getSavedToken() + val res = Anilist.query.getNotifications( + userId.toInt(), + resetNotification = false + ) + val unreadNotificationCount = res?.data?.user?.unreadNotificationCount ?: 0 + if (unreadNotificationCount > 0) { + val unreadNotifications = + res?.data?.page?.notifications?.sortedBy { it.id } + ?.takeLast(unreadNotificationCount) + val lastId = PrefManager.getVal(PrefName.LastAnilistNotificationId) + val newNotifications = unreadNotifications?.filter { it.id > lastId } + val filteredTypes = + PrefManager.getVal>(PrefName.AnilistFilteredTypes) + newNotifications?.forEach { + if (!filteredTypes.contains(it.notificationType)) { + val content = ActivityItemBuilder.getContent(it) + val notification = createNotification(context, content, it.id) + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + ) { + NotificationManagerCompat.from(context) + .notify( + Notifications.CHANNEL_ANILIST, + System.currentTimeMillis().toInt(), + notification + ) + } + } + } + if (newNotifications?.isNotEmpty() == true) { + PrefManager.setVal( + PrefName.LastAnilistNotificationId, + newNotifications.last().id + ) + } + } + } + } + return true + } catch (e: Exception) { + Logger.log("AnilistNotificationTask: ${e.message}") + Logger.log(e) + return false + } + } + + private fun createNotification( + context: Context, + content: String, + notificationId: Int? = null + ): android.app.Notification { + val title = "New Anilist Notification" + val intent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS") + if (notificationId != null) { + Logger.log("notificationId: $notificationId") + putExtra("activityId", notificationId) + } + } + val pendingIntent = PendingIntent.getActivity( + context, + notificationId ?: 0, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + return NotificationCompat.Builder(context, Notifications.CHANNEL_ANILIST) + .setSmallIcon(R.drawable.notification_icon) + .setContentTitle(title) + .setContentText(content) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .build() + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt index 50a9befb..645b9733 100644 --- a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt +++ b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt @@ -1,102 +1,20 @@ package ani.dantotsu.notifications.anilist -import android.Manifest -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.work.Worker +import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import ani.dantotsu.MainActivity -import ani.dantotsu.R -import ani.dantotsu.connections.anilist.Anilist -import ani.dantotsu.profile.activity.ActivityItemBuilder -import ani.dantotsu.settings.saving.PrefManager -import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.util.Logger -import eu.kanade.tachiyomi.data.notification.Notifications -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch class AnilistNotificationWorker(appContext: Context, workerParams: WorkerParameters) : - Worker(appContext, workerParams) { + CoroutineWorker(appContext, workerParams) { - override fun doWork(): Result { - val scope = CoroutineScope(Dispatchers.IO) - scope.launch { - PrefManager.init(applicationContext) //make sure prefs are initialized - val userId = PrefManager.getVal(PrefName.AnilistUserId) - if (userId.isNotEmpty()) { - Anilist.getSavedToken() - val res = Anilist.query.getNotifications(userId.toInt(), resetNotification = false) - val unreadNotificationCount = res?.data?.user?.unreadNotificationCount ?: 0 - if (unreadNotificationCount > 0) { - val unreadNotifications = res?.data?.page?.notifications?.sortedBy { it.id } - ?.takeLast(unreadNotificationCount) - val lastId = PrefManager.getVal(PrefName.LastAnilistNotificationId) - val newNotifications = unreadNotifications?.filter { it.id > lastId } - val filteredTypes = - PrefManager.getVal>(PrefName.AnilistFilteredTypes) - newNotifications?.forEach { - if (!filteredTypes.contains(it.notificationType)) { - val content = ActivityItemBuilder.getContent(it) - val notification = createNotification(applicationContext, content, it.id) - if (ActivityCompat.checkSelfPermission( - applicationContext, - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - ) { - NotificationManagerCompat.from(applicationContext) - .notify( - Notifications.CHANNEL_ANILIST, - System.currentTimeMillis().toInt(), - notification - ) - } - } - } - if (newNotifications?.isNotEmpty() == true) { - PrefManager.setVal(PrefName.LastAnilistNotificationId, newNotifications.last().id) - } - } - } + override suspend fun doWork(): Result { + Logger.log("AnilistNotificationWorker: doWork") + return if (AnilistNotificationTask().execute(applicationContext)) { + Result.success() + } else { + Result.retry() } - return Result.success() - } - - - private fun createNotification( - context: Context, - content: String, - notificationId: Int? = null - ): android.app.Notification { - val title = "New Anilist Notification" - val intent = Intent(applicationContext, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS") - if (notificationId != null) { - Logger.log("notificationId: $notificationId") - putExtra("activityId", notificationId) - } - } - val pendingIntent = PendingIntent.getActivity( - applicationContext, - notificationId ?: 0, - intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - return NotificationCompat.Builder(context, Notifications.CHANNEL_ANILIST) - .setSmallIcon(R.drawable.notification_icon) - .setContentTitle(title) - .setContentText(content) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - .build() } companion object { diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt new file mode 100644 index 00000000..65c78e98 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt @@ -0,0 +1,18 @@ +package ani.dantotsu.notifications.comment + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import ani.dantotsu.util.Logger +import kotlinx.coroutines.runBlocking + +class CommentNotificationReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + Logger.log("CommentNotificationReceiver: onReceive") + if (context != null) { + runBlocking { + CommentNotificationTask().execute(context) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt new file mode 100644 index 00000000..ec6b2361 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt @@ -0,0 +1,316 @@ +package ani.dantotsu.notifications.comment + +import android.Manifest +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Color +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import ani.dantotsu.MainActivity +import ani.dantotsu.R +import ani.dantotsu.connections.comments.CommentsAPI +import ani.dantotsu.notifications.Task +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.util.Logger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient + +class CommentNotificationTask : Task { + override suspend fun execute(context: Context): Boolean { + try { + withContext(Dispatchers.IO) { + PrefManager.init(context) //make sure prefs are initialized + val client = OkHttpClient() + CommentsAPI.fetchAuthToken(client) + val notificationResponse = CommentsAPI.getNotifications(client) + var notifications = notificationResponse?.notifications?.toMutableList() + //if we have at least one reply notification, we need to fetch the media titles + var names = emptyMap() + if (notifications?.any { it.type == 1 || it.type == null } == true) { + val mediaIds = + notifications.filter { it.type == 1 || it.type == null }.map { it.mediaId } + names = MediaNameFetch.fetchMediaTitles(mediaIds) + } + + val recentGlobal = PrefManager.getVal( + PrefName.RecentGlobalNotification + ) + + notifications = + notifications?.filter { it.type != 3 || it.notificationId > recentGlobal } + ?.toMutableList() + + val newRecentGlobal = + notifications?.filter { it.type == 3 }?.maxOfOrNull { it.notificationId } + if (newRecentGlobal != null) { + PrefManager.setVal(PrefName.RecentGlobalNotification, newRecentGlobal) + } + if (notifications.isNullOrEmpty()) return@withContext + PrefManager.setVal( + PrefName.UnreadCommentNotifications, + PrefManager.getVal(PrefName.UnreadCommentNotifications) + (notifications.size + ?: 0) + ) + + notifications.forEach { + val type: CommentNotificationWorker.NotificationType = when (it.type) { + 1 -> CommentNotificationWorker.NotificationType.COMMENT_REPLY + 2 -> CommentNotificationWorker.NotificationType.COMMENT_WARNING + 3 -> CommentNotificationWorker.NotificationType.APP_GLOBAL + 420 -> CommentNotificationWorker.NotificationType.NO_NOTIFICATION + else -> CommentNotificationWorker.NotificationType.UNKNOWN + } + val notification = when (type) { + CommentNotificationWorker.NotificationType.COMMENT_WARNING -> { + val title = "You received a warning" + val message = it.content ?: "Be more thoughtful with your comments" + + val commentStore = CommentStore( + title, + message, + it.mediaId, + it.commentId + ) + addNotificationToStore(commentStore) + + createNotification( + context, + CommentNotificationWorker.NotificationType.COMMENT_WARNING, + message, + title, + it.mediaId, + it.commentId, + "", + "" + ) + } + + CommentNotificationWorker.NotificationType.COMMENT_REPLY -> { + val title = "New Comment Reply" + val mediaName = names[it.mediaId]?.title ?: "Unknown" + val message = "${it.username} replied to your comment in $mediaName" + + val commentStore = CommentStore( + title, + message, + it.mediaId, + it.commentId + ) + addNotificationToStore(commentStore) + + createNotification( + context, + CommentNotificationWorker.NotificationType.COMMENT_REPLY, + message, + title, + it.mediaId, + it.commentId, + names[it.mediaId]?.color ?: "#222222", + names[it.mediaId]?.coverImage ?: "" + ) + } + + CommentNotificationWorker.NotificationType.APP_GLOBAL -> { + val title = "Update from Dantotsu" + val message = it.content ?: "New feature available" + + val commentStore = CommentStore( + title, + message, + null, + null + ) + addNotificationToStore(commentStore) + + createNotification( + context, + CommentNotificationWorker.NotificationType.APP_GLOBAL, + message, + title, + 0, + 0, + "", + "" + ) + } + + CommentNotificationWorker.NotificationType.NO_NOTIFICATION -> { + PrefManager.removeCustomVal("genre_thumb") + PrefManager.removeCustomVal("banner_ANIME_time") + PrefManager.removeCustomVal("banner_MANGA_time") + PrefManager.setVal(PrefName.ImageUrl, it.content ?: "") + null + } + + CommentNotificationWorker.NotificationType.UNKNOWN -> { + null + } + } + + if (ActivityCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + ) { + if (notification != null) { + NotificationManagerCompat.from(context) + .notify( + type.id, + System.currentTimeMillis().toInt(), + notification + ) + } + } + } + } + return true + } catch (e: Exception) { + Logger.log("CommentNotificationTask: ${e.message}") + Logger.log(e) + return false + } + } + + private fun addNotificationToStore(notification: CommentStore) { + val notificationStore = PrefManager.getNullableVal>( + PrefName.CommentNotificationStore, + null + ) ?: listOf() + val newStore = notificationStore.toMutableList() + if (newStore.size > 10) { + newStore.remove(newStore.minByOrNull { it.time }) + } + if (newStore.any { it.content == notification.content }) { + return + } + newStore.add(notification) + PrefManager.setVal(PrefName.CommentNotificationStore, newStore) + } + + private fun createNotification( + context: Context, + notificationType: CommentNotificationWorker.NotificationType, + message: String, + title: String, + mediaId: Int, + commentId: Int, + color: String, + imageUrl: String + ): android.app.Notification? { + Logger.log( + "Creating notification of type $notificationType" + + ", message: $message, title: $title, mediaId: $mediaId, commentId: $commentId" + ) + val notification = when (notificationType) { + CommentNotificationWorker.NotificationType.COMMENT_WARNING -> { + val intent = Intent(context, MainActivity::class.java).apply { + putExtra("FRAGMENT_TO_LOAD", "COMMENTS") + putExtra("mediaId", mediaId) + putExtra("commentId", commentId) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val pendingIntent = PendingIntent.getActivity( + context, + commentId, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + val builder = NotificationCompat.Builder(context, notificationType.id) + .setContentTitle(title) + .setContentText(message) + .setSmallIcon(R.drawable.notification_icon) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + builder.build() + } + + CommentNotificationWorker.NotificationType.COMMENT_REPLY -> { + val intent = Intent(context, MainActivity::class.java).apply { + putExtra("FRAGMENT_TO_LOAD", "COMMENTS") + putExtra("mediaId", mediaId) + putExtra("commentId", commentId) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val pendingIntent = PendingIntent.getActivity( + context, + commentId, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + val builder = NotificationCompat.Builder(context, notificationType.id) + .setContentTitle(title) + .setContentText(message) + .setSmallIcon(R.drawable.notification_icon) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + if (imageUrl.isNotEmpty()) { + val bitmap = getBitmapFromUrl(imageUrl) + if (bitmap != null) { + builder.setLargeIcon(bitmap) + } + } + if (color.isNotEmpty()) { + builder.color = Color.parseColor(color) + } + builder.build() + } + + CommentNotificationWorker.NotificationType.APP_GLOBAL -> { + val intent = Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + val pendingIntent = PendingIntent.getActivity( + context, + System.currentTimeMillis().toInt(), + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + val builder = NotificationCompat.Builder(context, notificationType.id) + .setContentTitle(title) + .setContentText(message) + .setSmallIcon(R.drawable.notification_icon) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + builder.build() + } + + else -> { + null + } + } + return notification + } + + private fun getBitmapFromVectorDrawable(context: Context, drawableId: Int): Bitmap? { + val drawable = ContextCompat.getDrawable(context, drawableId) ?: return null + val bitmap = Bitmap.createBitmap( + drawable.intrinsicWidth, + drawable.intrinsicHeight, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + return bitmap + } + + private fun getBitmapFromUrl(url: String): Bitmap? { + return try { + val inputStream = java.net.URL(url).openStream() + BitmapFactory.decodeStream(inputStream) + } catch (e: Exception) { + null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt index 5ecc65db..5b75b481 100644 --- a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt @@ -1,310 +1,20 @@ package ani.dantotsu.notifications.comment -import android.Manifest -import android.app.PendingIntent import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Color -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat -import androidx.work.Worker +import androidx.work.CoroutineWorker import androidx.work.WorkerParameters -import ani.dantotsu.MainActivity -import ani.dantotsu.R -import ani.dantotsu.connections.comments.CommentsAPI -import ani.dantotsu.settings.saving.PrefManager -import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.data.notification.Notifications -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import okhttp3.OkHttpClient class CommentNotificationWorker(appContext: Context, workerParams: WorkerParameters) : - Worker(appContext, workerParams) { - override fun doWork(): Result { - val scope = CoroutineScope(Dispatchers.IO) - scope.launch { - PrefManager.init(applicationContext) //make sure prefs are initialized - val client = OkHttpClient() - CommentsAPI.fetchAuthToken(client) - val notificationResponse = CommentsAPI.getNotifications(client) - var notifications = notificationResponse?.notifications?.toMutableList() - //if we have at least one reply notification, we need to fetch the media titles - var names = emptyMap() - if (notifications?.any { it.type == 1 || it.type == null } == true) { - val mediaIds = - notifications.filter { it.type == 1 || it.type == null }.map { it.mediaId } - names = MediaNameFetch.fetchMediaTitles(mediaIds) - } - - val recentGlobal = PrefManager.getVal( - PrefName.RecentGlobalNotification - ) - - notifications = - notifications?.filter { it.type != 3 || it.notificationId > recentGlobal } - ?.toMutableList() - - val newRecentGlobal = - notifications?.filter { it.type == 3 }?.maxOfOrNull { it.notificationId } - if (newRecentGlobal != null) { - PrefManager.setVal(PrefName.RecentGlobalNotification, newRecentGlobal) - } - if (notifications.isNullOrEmpty()) return@launch - PrefManager.setVal(PrefName.UnreadCommentNotifications, - PrefManager.getVal(PrefName.UnreadCommentNotifications) + (notifications?.size ?: 0) - ) - - notifications.forEach { - val type: NotificationType = when (it.type) { - 1 -> NotificationType.COMMENT_REPLY - 2 -> NotificationType.COMMENT_WARNING - 3 -> NotificationType.APP_GLOBAL - 420 -> NotificationType.NO_NOTIFICATION - else -> NotificationType.UNKNOWN - } - val notification = when (type) { - NotificationType.COMMENT_WARNING -> { - val title = "You received a warning" - val message = it.content ?: "Be more thoughtful with your comments" - - val commentStore = CommentStore( - title, - message, - it.mediaId, - it.commentId - ) - addNotificationToStore(commentStore) - - createNotification( - NotificationType.COMMENT_WARNING, - message, - title, - it.mediaId, - it.commentId, - "", - "" - ) - } - - NotificationType.COMMENT_REPLY -> { - val title = "New Comment Reply" - val mediaName = names[it.mediaId]?.title ?: "Unknown" - val message = "${it.username} replied to your comment in $mediaName" - - val commentStore = CommentStore( - title, - message, - it.mediaId, - it.commentId - ) - addNotificationToStore(commentStore) - - createNotification( - NotificationType.COMMENT_REPLY, - message, - title, - it.mediaId, - it.commentId, - names[it.mediaId]?.color ?: "#222222", - names[it.mediaId]?.coverImage ?: "" - ) - } - - NotificationType.APP_GLOBAL -> { - val title = "Update from Dantotsu" - val message = it.content ?: "New feature available" - - val commentStore = CommentStore( - title, - message, - null, - null - ) - addNotificationToStore(commentStore) - - createNotification( - NotificationType.APP_GLOBAL, - message, - title, - 0, - 0, - "", - "" - ) - } - - NotificationType.NO_NOTIFICATION -> { - PrefManager.removeCustomVal("genre_thumb") - PrefManager.removeCustomVal("banner_ANIME_time") - PrefManager.removeCustomVal("banner_MANGA_time") - PrefManager.setVal(PrefName.ImageUrl, it.content ?: "") - null - } - - NotificationType.UNKNOWN -> { - null - } - } - - if (ActivityCompat.checkSelfPermission( - applicationContext, - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - ) { - if (notification != null) { - NotificationManagerCompat.from(applicationContext) - .notify( - type.id, - System.currentTimeMillis().toInt(), - notification - ) - } - } - } - } - return Result.success() - } - - private fun addNotificationToStore(notification: CommentStore) { - val notificationStore = PrefManager.getNullableVal>( - PrefName.CommentNotificationStore, - null - ) ?: listOf() - val newStore = notificationStore.toMutableList() - if (newStore.size > 10) { - newStore.remove(newStore.minByOrNull { it.time }) - } - if (newStore.any { it.content == notification.content }) { - return - } - newStore.add(notification) - PrefManager.setVal(PrefName.CommentNotificationStore, newStore) - } - - private fun createNotification( - notificationType: NotificationType, - message: String, - title: String, - mediaId: Int, - commentId: Int, - color: String, - imageUrl: String - ): android.app.Notification? { - Logger.log( - "Creating notification of type $notificationType" + - ", message: $message, title: $title, mediaId: $mediaId, commentId: $commentId" - ) - val notification = when (notificationType) { - NotificationType.COMMENT_WARNING -> { - val intent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra("FRAGMENT_TO_LOAD", "COMMENTS") - putExtra("mediaId", mediaId) - putExtra("commentId", commentId) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - val pendingIntent = PendingIntent.getActivity( - applicationContext, - commentId, - intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - val builder = NotificationCompat.Builder(applicationContext, notificationType.id) - .setContentTitle(title) - .setContentText(message) - .setSmallIcon(R.drawable.notification_icon) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - builder.build() - } - - NotificationType.COMMENT_REPLY -> { - val intent = Intent(applicationContext, MainActivity::class.java).apply { - putExtra("FRAGMENT_TO_LOAD", "COMMENTS") - putExtra("mediaId", mediaId) - putExtra("commentId", commentId) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - val pendingIntent = PendingIntent.getActivity( - applicationContext, - commentId, - intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - val builder = NotificationCompat.Builder(applicationContext, notificationType.id) - .setContentTitle(title) - .setContentText(message) - .setSmallIcon(R.drawable.notification_icon) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - if (imageUrl.isNotEmpty()) { - val bitmap = getBitmapFromUrl(imageUrl) - if (bitmap != null) { - builder.setLargeIcon(bitmap) - } - } - if (color.isNotEmpty()) { - builder.color = Color.parseColor(color) - } - builder.build() - } - - NotificationType.APP_GLOBAL -> { - val intent = Intent(applicationContext, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - val pendingIntent = PendingIntent.getActivity( - applicationContext, - System.currentTimeMillis().toInt(), - intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - val builder = NotificationCompat.Builder(applicationContext, notificationType.id) - .setContentTitle(title) - .setContentText(message) - .setSmallIcon(R.drawable.notification_icon) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - builder.build() - } - - else -> { - null - } - } - return notification - } - - private fun getBitmapFromVectorDrawable(context: Context, drawableId: Int): Bitmap? { - val drawable = ContextCompat.getDrawable(context, drawableId) ?: return null - val bitmap = Bitmap.createBitmap( - drawable.intrinsicWidth, - drawable.intrinsicHeight, Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, canvas.width, canvas.height) - drawable.draw(canvas) - return bitmap - } - - private fun getBitmapFromUrl(url: String): Bitmap? { - return try { - val inputStream = java.net.URL(url).openStream() - BitmapFactory.decodeStream(inputStream) - } catch (e: Exception) { - null + CoroutineWorker(appContext, workerParams) { + override suspend fun doWork(): Result { + Logger.log("CommentNotificationWorker: doWork") + return if (CommentNotificationTask().execute(applicationContext)) { + Result.success() + } else { + Result.retry() } } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 907ba068..80c99b4a 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -1,9 +1,13 @@ package ani.dantotsu.settings import android.annotation.SuppressLint +import android.app.AlarmManager import android.app.AlertDialog +import android.content.Context import android.content.Intent import android.graphics.drawable.Animatable +import android.net.Uri +import android.os.Build import android.os.Build.BRAND import android.os.Build.DEVICE import android.os.Build.SUPPORTED_ABIS @@ -47,6 +51,7 @@ import ani.dantotsu.initActivity import ani.dantotsu.loadImage import ani.dantotsu.util.Logger import ani.dantotsu.navBarHeight +import ani.dantotsu.notifications.TaskScheduler import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.openLinkInBrowser @@ -764,6 +769,40 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene openSettings(this, null) } + binding.settingsNotificationsUseAlarmManager.isChecked = + PrefManager.getVal(PrefName.UseAlarmManager) + + binding.settingsNotificationsUseAlarmManager.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + val alertDialog = AlertDialog.Builder(this, R.style.MyPopup) + .setTitle("Use Alarm Manager") + .setMessage("Using Alarm Manger can help fight against battery optimization, but may consume more battery. It also requires the Alarm Manager permission.") + .setPositiveButton("Use") { dialog, _ -> + PrefManager.setVal(PrefName.UseAlarmManager, true) + if (SDK_INT >= Build.VERSION_CODES.S) { + if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) { + val intent = Intent("android.settings.REQUEST_SCHEDULE_EXACT_ALARM") + startActivity(intent) + binding.settingsNotificationsCheckingSubscriptions.isChecked = true + } + } + dialog.dismiss() + } + .setNegativeButton("Cancel") { dialog, _ -> + binding.settingsNotificationsCheckingSubscriptions.isChecked = false + PrefManager.setVal(PrefName.UseAlarmManager, false) + dialog.dismiss() + } + .create() + alertDialog.window?.setDimAmount(0.8f) + alertDialog.show() + } else { + PrefManager.setVal(PrefName.UseAlarmManager, false) + TaskScheduler.create(this, true).cancelAllTasks() + TaskScheduler.create(this, false).scheduleAllTasks(this) + } + } + if (!BuildConfig.FLAVOR.contains("fdroid")) { binding.settingsLogo.setOnLongClickListener { lifecycleScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt index d277b011..3a13cb88 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt @@ -24,6 +24,7 @@ object PrefManager { private var protectedPreferences: SharedPreferences? = null fun init(context: Context) { //must be called in Application class or will crash + if (generalPreferences != null) return generalPreferences = context.getSharedPreferences(Location.General.location, Context.MODE_PRIVATE) uiPreferences = diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 9b2beb3c..13486d4d 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -38,6 +38,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files AnilistNotificationInterval(Pref(Location.General, Int::class, 3)), LastAnilistNotificationId(Pref(Location.General, Int::class, 0)), AnilistFilteredTypes(Pref(Location.General, Set::class, setOf())), + UseAlarmManager(Pref(Location.General, Boolean::class, false)), //User Interface UseOLED(Pref(Location.UI, Boolean::class, false)), diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 42fe4cf0..2a1eabbc 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -1469,6 +1469,24 @@ app:showText="false" app:thumbTint="@color/button_switch_track" /> + + Show notification for Checking Subscriptions + Use Alarm Manager for reliable Notifications Notification for Checking Subscriptions Subscriptions Update Frequency : %1$s Subscriptions Update Frequency