feat: AlarmManager option for notifications
This commit is contained in:
parent
deeefb8e35
commit
9471683501
17 changed files with 822 additions and 432 deletions
|
@ -16,6 +16,7 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
|
@ -314,6 +315,22 @@
|
|||
<action android:name="Aani.dantotsu.ACTION_ALARM" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".notifications.AlarmPermissionStateReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".notifications.BootCompletedReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".notifications.anilist.AnilistNotificationReceiver"/>
|
||||
<receiver android:name=".notifications.comment.CommentNotificationReceiver"/>
|
||||
|
||||
|
||||
<meta-data
|
||||
android:name="preloaded_fonts"
|
||||
|
|
|
@ -6,15 +6,14 @@ import android.content.Context
|
|||
import android.os.Bundle
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import ani.dantotsu.aniyomi.anime.custom.AppModule
|
||||
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
|
||||
import ani.dantotsu.connections.comments.CommentsAPI
|
||||
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
|
||||
import ani.dantotsu.notifications.comment.CommentNotificationWorker
|
||||
import ani.dantotsu.notifications.TaskScheduler
|
||||
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
|
||||
import ani.dantotsu.notifications.comment.CommentNotificationWorker
|
||||
import ani.dantotsu.others.DisabledReports
|
||||
import ani.dantotsu.parsers.AnimeSources
|
||||
import ani.dantotsu.parsers.MangaSources
|
||||
|
@ -127,49 +126,16 @@ class App : MultiDexApplication() {
|
|||
}
|
||||
|
||||
private fun startWorkers() {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(androidx.work.NetworkType.CONNECTED)
|
||||
.build()
|
||||
val useAlarmManager = PrefManager.getVal<Boolean>(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
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String>(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<Int>(PrefName.LastAnilistNotificationId)
|
||||
val newNotifications = unreadNotifications?.filter { it.id > lastId }
|
||||
val filteredTypes =
|
||||
PrefManager.getVal<Set<String>>(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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String>(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<Int>(PrefName.LastAnilistNotificationId)
|
||||
val newNotifications = unreadNotifications?.filter { it.id > lastId }
|
||||
val filteredTypes =
|
||||
PrefManager.getVal<Set<String>>(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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Int, MediaNameFetch.Companion.ReturnedData>()
|
||||
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<Int>(
|
||||
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<Int>(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<List<CommentStore>>(
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Int, MediaNameFetch.Companion.ReturnedData>()
|
||||
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<Int>(
|
||||
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<Int>(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<List<CommentStore>>(
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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<String>())),
|
||||
UseAlarmManager(Pref(Location.General, Boolean::class, false)),
|
||||
|
||||
//User Interface
|
||||
UseOLED(Pref(Location.UI, Boolean::class, false)),
|
||||
|
|
|
@ -1469,6 +1469,24 @@
|
|||
app:showText="false"
|
||||
app:thumbTint="@color/button_switch_track" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/settingsNotificationsUseAlarmManager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:drawableStart="@drawable/ic_round_new_releases_24"
|
||||
android:drawablePadding="16dp"
|
||||
android:elegantTextHeight="true"
|
||||
android:fontFamily="@font/poppins_bold"
|
||||
android:minHeight="64dp"
|
||||
android:text="@string/use_alarm_manager"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?attr/colorOnBackground"
|
||||
app:cornerRadius="0dp"
|
||||
app:drawableTint="?attr/colorPrimary"
|
||||
app:showText="false"
|
||||
app:thumbTint="@color/button_switch_track" />
|
||||
|
||||
</ani.dantotsu.others.Xpandable>
|
||||
|
||||
<ani.dantotsu.others.Xpandable
|
||||
|
|
|
@ -383,6 +383,7 @@
|
|||
\n\n_It is not required to sync both MAL and Anilist accounts._
|
||||
</string>
|
||||
<string name="notification_for_checking_subscriptions">Show notification for Checking Subscriptions</string>
|
||||
<string name="use_alarm_manager">Use Alarm Manager for reliable Notifications</string>
|
||||
<string name="checking_subscriptions">Notification for Checking Subscriptions</string>
|
||||
<string name="subscriptions_checking_time_s">Subscriptions Update Frequency : %1$s</string>
|
||||
<string name="subscriptions_checking_time">Subscriptions Update Frequency</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue