feat: move subscriptions to new notification method
This commit is contained in:
parent
a39db5ea93
commit
808d4e6bf5
32 changed files with 491 additions and 620 deletions
|
@ -0,0 +1,157 @@
|
|||
package ani.dantotsu.notifications.subscription
|
||||
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.currContext
|
||||
import ani.dantotsu.media.Media
|
||||
import ani.dantotsu.media.Selected
|
||||
import ani.dantotsu.media.manga.MangaNameAdapter
|
||||
import ani.dantotsu.parsers.AnimeParser
|
||||
import ani.dantotsu.parsers.AnimeSources
|
||||
import ani.dantotsu.parsers.Episode
|
||||
import ani.dantotsu.parsers.HAnimeSources
|
||||
import ani.dantotsu.parsers.HMangaSources
|
||||
import ani.dantotsu.parsers.MangaChapter
|
||||
import ani.dantotsu.parsers.MangaParser
|
||||
import ani.dantotsu.parsers.MangaSources
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.tryWithSuspend
|
||||
import ani.dantotsu.util.Logger
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
class SubscriptionHelper {
|
||||
companion object {
|
||||
private fun loadSelected(
|
||||
mediaId: Int
|
||||
): Selected {
|
||||
val data =
|
||||
PrefManager.getNullableCustomVal("${mediaId}-select", null, Selected::class.java)
|
||||
?: Selected().let {
|
||||
it.sourceIndex = 0
|
||||
it.preferDub = PrefManager.getVal(PrefName.SettingsPreferDub)
|
||||
it
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private fun saveSelected( mediaId: Int, data: Selected) {
|
||||
PrefManager.setCustomVal("${mediaId}-select", data)
|
||||
}
|
||||
|
||||
fun getAnimeParser(id: Int): AnimeParser {
|
||||
val sources = AnimeSources
|
||||
Logger.log("getAnimeParser size: ${sources.list.size}")
|
||||
val selected = loadSelected(id)
|
||||
val parser = sources[selected.sourceIndex]
|
||||
parser.selectDub = selected.preferDub
|
||||
return parser
|
||||
}
|
||||
|
||||
suspend fun getEpisode(
|
||||
parser: AnimeParser,
|
||||
id: Int
|
||||
): Episode? {
|
||||
|
||||
val selected = loadSelected(id)
|
||||
val ep = withTimeoutOrNull(10 * 1000) {
|
||||
tryWithSuspend {
|
||||
val show = parser.loadSavedShowResponse(id) ?: throw Exception(
|
||||
currContext()?.getString(
|
||||
R.string.failed_to_load_data,
|
||||
id
|
||||
)
|
||||
)
|
||||
show.sAnime?.let {
|
||||
parser.getLatestEpisode(
|
||||
show.link, show.extra,
|
||||
it, selected.latest
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ep?.apply {
|
||||
selected.latest = number.toFloat()
|
||||
saveSelected(id, selected)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMangaParser(id: Int): MangaParser {
|
||||
val sources = MangaSources
|
||||
val selected = loadSelected(id)
|
||||
return sources[selected.sourceIndex]
|
||||
}
|
||||
|
||||
suspend fun getChapter(
|
||||
parser: MangaParser,
|
||||
id: Int
|
||||
): MangaChapter? {
|
||||
val selected = loadSelected(id)
|
||||
val chp = withTimeoutOrNull(10 * 1000) {
|
||||
tryWithSuspend {
|
||||
val show = parser.loadSavedShowResponse(id) ?: throw Exception(
|
||||
currContext()?.getString(
|
||||
R.string.failed_to_load_data,
|
||||
id
|
||||
)
|
||||
)
|
||||
show.sManga?.let {
|
||||
parser.getLatestChapter(
|
||||
show.link, show.extra,
|
||||
it, selected.latest
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chp?.apply {
|
||||
selected.latest = MangaNameAdapter.findChapterNumber(number) ?: 0f
|
||||
saveSelected(id, selected)
|
||||
}
|
||||
}
|
||||
|
||||
data class SubscribeMedia(
|
||||
val isAnime: Boolean,
|
||||
val isAdult: Boolean,
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val image: String?
|
||||
) : java.io.Serializable
|
||||
|
||||
private const val SUBSCRIPTIONS = "subscriptions"
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun getSubscriptions(): Map<Int, SubscribeMedia> =
|
||||
(PrefManager.getNullableCustomVal(
|
||||
SUBSCRIPTIONS,
|
||||
null,
|
||||
Map::class.java
|
||||
) as? Map<Int, SubscribeMedia>)
|
||||
?: mapOf<Int, SubscribeMedia>().also { PrefManager.setCustomVal(SUBSCRIPTIONS, it) }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun saveSubscription(media: Media, subscribed: Boolean) {
|
||||
val data = PrefManager.getNullableCustomVal(
|
||||
SUBSCRIPTIONS,
|
||||
null,
|
||||
Map::class.java
|
||||
) as? MutableMap<Int, SubscribeMedia>
|
||||
?: mutableMapOf()
|
||||
if (subscribed) {
|
||||
if (!data.containsKey(media.id)) {
|
||||
val new = SubscribeMedia(
|
||||
media.anime != null,
|
||||
media.isAdult,
|
||||
media.id,
|
||||
media.userPreferredName,
|
||||
media.cover
|
||||
)
|
||||
data[media.id] = new
|
||||
}
|
||||
} else {
|
||||
data.remove(media.id)
|
||||
}
|
||||
PrefManager.setCustomVal(SUBSCRIPTIONS, data)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package ani.dantotsu.notifications.subscription
|
||||
|
||||
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 SubscriptionNotificationReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
Logger.log("SubscriptionNotificationReceiver: onReceive")
|
||||
runBlocking {
|
||||
SubscriptionNotificationTask().execute(context)
|
||||
}
|
||||
val subscriptionInterval =
|
||||
SubscriptionNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.SubscriptionNotificationInterval)]
|
||||
AlarmManagerScheduler(context).scheduleRepeatingTask(
|
||||
TaskScheduler.TaskType.SUBSCRIPTION_NOTIFICATION,
|
||||
subscriptionInterval
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
package ani.dantotsu.notifications.subscription
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import ani.dantotsu.App
|
||||
import ani.dantotsu.FileUrl
|
||||
import ani.dantotsu.R
|
||||
import ani.dantotsu.connections.anilist.UrlMedia
|
||||
import ani.dantotsu.hasNotificationPermission
|
||||
import ani.dantotsu.notifications.Task
|
||||
import ani.dantotsu.parsers.AnimeSources
|
||||
import ani.dantotsu.parsers.Episode
|
||||
import ani.dantotsu.parsers.MangaChapter
|
||||
import ani.dantotsu.parsers.MangaSources
|
||||
import ani.dantotsu.settings.saving.PrefManager
|
||||
import ani.dantotsu.settings.saving.PrefName
|
||||
import ani.dantotsu.util.Logger
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK_PROGRESS
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications.ID_SUBSCRIPTION_CHECK_PROGRESS
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class SubscriptionNotificationTask : Task {
|
||||
private var currentlyPerforming = false
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override suspend fun execute(context: Context): Boolean {
|
||||
if (!currentlyPerforming) {
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
PrefManager.init(context)
|
||||
currentlyPerforming = true
|
||||
App.context = context
|
||||
Logger.log("SubscriptionNotificationTask: execute")
|
||||
var timeout = 15_000L
|
||||
do {
|
||||
delay(1000)
|
||||
timeout -= 1000
|
||||
} while (timeout > 0 && !AnimeSources.isInitialized && !MangaSources.isInitialized)
|
||||
Logger.log("SubscriptionNotificationTask: timeout: $timeout")
|
||||
if (timeout <= 0) {
|
||||
currentlyPerforming = false
|
||||
return@withContext
|
||||
}
|
||||
val subscriptions = SubscriptionHelper.getSubscriptions()
|
||||
var i = 0
|
||||
val index = subscriptions.map { i++; it.key to i }.toMap()
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
|
||||
val progressEnabled: Boolean =
|
||||
PrefManager.getVal(PrefName.SubscriptionCheckingNotifications)
|
||||
val progressNotification = if (progressEnabled) getProgressNotification(
|
||||
context,
|
||||
subscriptions.size
|
||||
) else null
|
||||
if (progressNotification != null && hasNotificationPermission(context)) {
|
||||
notificationManager.notify(
|
||||
ID_SUBSCRIPTION_CHECK_PROGRESS,
|
||||
progressNotification.build()
|
||||
)
|
||||
//Seems like if the parent coroutine scope gets cancelled, the notification stays
|
||||
//So adding this as a safeguard? dk if this will be useful
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
delay(5 * subscriptions.size * 1000L)
|
||||
notificationManager.cancel(ID_SUBSCRIPTION_CHECK_PROGRESS)
|
||||
}
|
||||
}
|
||||
|
||||
fun progress(progress: Int, parser: String, media: String) {
|
||||
if (progressNotification != null && hasNotificationPermission(context))
|
||||
notificationManager.notify(
|
||||
ID_SUBSCRIPTION_CHECK_PROGRESS,
|
||||
progressNotification
|
||||
.setProgress(subscriptions.size, progress, false)
|
||||
.setContentText("$media on $parser")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
subscriptions.toList().map {
|
||||
val media = it.second
|
||||
val text = if (media.isAnime) {
|
||||
val parser =
|
||||
SubscriptionHelper.getAnimeParser(media.id)
|
||||
progress(index[it.first]!!, parser.name, media.name)
|
||||
val ep: Episode? =
|
||||
SubscriptionHelper.getEpisode(
|
||||
parser,
|
||||
media.id
|
||||
)
|
||||
if (ep != null) context.getString(R.string.episode) + "${ep.number}${
|
||||
if (ep.title != null) " : ${ep.title}" else ""
|
||||
}${
|
||||
if (ep.isFiller) " [Filler]" else ""
|
||||
} " + context.getString(R.string.just_released) to ep.thumbnail
|
||||
else null
|
||||
} else {
|
||||
val parser =
|
||||
SubscriptionHelper.getMangaParser(media.id)
|
||||
progress(index[it.first]!!, parser.name, media.name)
|
||||
val ep: MangaChapter? =
|
||||
SubscriptionHelper.getChapter(
|
||||
parser,
|
||||
media.id
|
||||
)
|
||||
if (ep != null) ep.number + " " + context.getString(R.string.just_released) to null
|
||||
else null
|
||||
} ?: return@map
|
||||
val notification = createNotification(
|
||||
context.applicationContext,
|
||||
media,
|
||||
text.first,
|
||||
text.second
|
||||
)
|
||||
if (hasNotificationPermission(context)) {
|
||||
NotificationManagerCompat.from(context)
|
||||
.notify(
|
||||
CHANNEL_SUBSCRIPTION_CHECK,
|
||||
System.currentTimeMillis().toInt(),
|
||||
notification
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (progressNotification != null) notificationManager.cancel(
|
||||
ID_SUBSCRIPTION_CHECK_PROGRESS
|
||||
)
|
||||
currentlyPerforming = false
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Logger.log("SubscriptionNotificationTask: ${e.message}")
|
||||
Logger.log(e)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun createNotification(
|
||||
context: Context,
|
||||
media: SubscriptionHelper.Companion.SubscribeMedia,
|
||||
text: String,
|
||||
thumbnail: FileUrl?
|
||||
): android.app.Notification {
|
||||
val pendingIntent = getIntent(context, media.id)
|
||||
val icon =
|
||||
if (media.isAnime) R.drawable.ic_round_movie_filter_24 else R.drawable.ic_round_menu_book_24
|
||||
|
||||
val builder = NotificationCompat.Builder(context, CHANNEL_SUBSCRIPTION_CHECK)
|
||||
.setSmallIcon(icon)
|
||||
.setContentTitle(media.name)
|
||||
.setContentText(text)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
|
||||
if (thumbnail != null) {
|
||||
val bitmap = getBitmapFromUrl(thumbnail.url)
|
||||
if (bitmap != null) {
|
||||
builder.setLargeIcon(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
|
||||
}
|
||||
|
||||
private fun getProgressNotification(
|
||||
context: Context,
|
||||
size: Int
|
||||
): NotificationCompat.Builder {
|
||||
return NotificationCompat.Builder(context, CHANNEL_SUBSCRIPTION_CHECK_PROGRESS)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setContentTitle(context.getString(R.string.checking_subscriptions_title))
|
||||
.setProgress(size, 0, false)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
}
|
||||
|
||||
private fun getBitmapFromUrl(url: String): Bitmap? {
|
||||
return try {
|
||||
val inputStream = java.net.URL(url).openStream()
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getIntent(context: Context, mediaId: Int): PendingIntent {
|
||||
val notifyIntent = Intent(context, UrlMedia::class.java)
|
||||
.putExtra("media", mediaId)
|
||||
.setAction(mediaId.toString())
|
||||
.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
context, mediaId, notifyIntent,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT
|
||||
} else {
|
||||
PendingIntent.FLAG_ONE_SHOT
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package ani.dantotsu.notifications.subscription
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import ani.dantotsu.notifications.anilist.AnilistNotificationTask
|
||||
import ani.dantotsu.util.Logger
|
||||
|
||||
class SubscriptionNotificationWorker(appContext: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Logger.log("SubscriptionNotificationWorker: doWork")
|
||||
return if (AnilistNotificationTask().execute(applicationContext)) {
|
||||
Result.success()
|
||||
} else {
|
||||
Logger.log("SubscriptionNotificationWorker: doWork failed")
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val checkIntervals = arrayOf(0L, 480, 720, 1440)
|
||||
const val WORK_NAME =
|
||||
"ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker"
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue