feat: move subscriptions to new notification method

This commit is contained in:
rebelonion 2024-03-19 19:30:12 -05:00
parent a39db5ea93
commit 808d4e6bf5
32 changed files with 491 additions and 620 deletions

View file

@ -69,7 +69,7 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/currently_airing_widget_info" /> android:resource="@xml/currently_airing_widget_info" />
</receiver> </receiver>
<receiver android:name=".subcriptions.NotificationClickReceiver" /> <receiver android:name=".notifications.IncognitoNotificationClickReceiver" />
<activity <activity
@ -307,14 +307,6 @@
android:exported="false" android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<receiver
android:name=".subcriptions.AlarmReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="Aani.dantotsu.ACTION_ALARM" />
</intent-filter>
</receiver>
<receiver android:name=".notifications.AlarmPermissionStateReceiver" <receiver android:name=".notifications.AlarmPermissionStateReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -330,7 +322,7 @@
</receiver> </receiver>
<receiver android:name=".notifications.anilist.AnilistNotificationReceiver"/> <receiver android:name=".notifications.anilist.AnilistNotificationReceiver"/>
<receiver android:name=".notifications.comment.CommentNotificationReceiver"/> <receiver android:name=".notifications.comment.CommentNotificationReceiver"/>
<receiver android:name=".notifications.subscription.SubscriptionNotificationReceiver"/>
<meta-data <meta-data
android:name="preloaded_fonts" android:name="preloaded_fonts"

View file

@ -87,6 +87,7 @@ class App : MultiDexApplication() {
Logger.init(this) Logger.init(this)
Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler()) Thread.setDefaultUncaughtExceptionHandler(FinalExceptionHandler())
Logger.log("App: Logging started")
initializeNetwork(baseContext) initializeNetwork(baseContext)
@ -122,22 +123,10 @@ class App : MultiDexApplication() {
CommentsAPI.fetchAuthToken() CommentsAPI.fetchAuthToken()
} }
startWorkers()
}
private fun startWorkers() {
val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager) val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager)
TaskScheduler.create(this, useAlarmManager).scheduleAllTasks(this) 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))
} }
private fun setupNotificationChannels() { private fun setupNotificationChannels() {
try { try {
Notifications.createChannels(this) Notifications.createChannels(this)

View file

@ -70,6 +70,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
@ -92,13 +93,13 @@ import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.databinding.ItemCountDownBinding import ani.dantotsu.databinding.ItemCountDownBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.notifications.IncognitoNotificationClickReceiver
import ani.dantotsu.others.SpoilerPlugin import ani.dantotsu.others.SpoilerPlugin
import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.parsers.ShowResponse
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
import ani.dantotsu.subcriptions.NotificationClickReceiver
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
@ -1172,7 +1173,7 @@ fun incognitoNotification(context: Context) {
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) val incognito: Boolean = PrefManager.getVal(PrefName.Incognito)
if (incognito) { if (incognito) {
val intent = Intent(context, NotificationClickReceiver::class.java) val intent = Intent(context, IncognitoNotificationClickReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast( val pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, context, 0, intent,
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE
@ -1190,6 +1191,28 @@ fun incognitoNotification(context: Context) {
} }
} }
fun hasNotificationPermission(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
} else {
NotificationManagerCompat.from(context).areNotificationsEnabled()
}
}
fun openSettings(context: Context, channelId: String?): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val intent = Intent(
if (channelId != null) Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
else Settings.ACTION_APP_NOTIFICATION_SETTINGS
).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
}
context.startActivity(intent)
true
} else false
}
suspend fun View.pop() { suspend fun View.pop() {
currActivity()?.runOnUiThread { currActivity()?.runOnUiThread {
ObjectAnimator.ofFloat(this@pop, "scaleX", 1f, 1.25f).setDuration(120).start() ObjectAnimator.ofFloat(this@pop, "scaleX", 1f, 1.25f).setDuration(120).start()

View file

@ -38,6 +38,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.Download import androidx.media3.exoplayer.offline.Download
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.work.OneTimeWorkRequest
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistHomeViewModel import ani.dantotsu.connections.anilist.AnilistHomeViewModel
import ani.dantotsu.databinding.ActivityMainBinding import ani.dantotsu.databinding.ActivityMainBinding
@ -49,6 +50,8 @@ import ani.dantotsu.home.LoginFragment
import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.MangaFragment
import ani.dantotsu.home.NoInternet import ani.dantotsu.home.NoInternet
import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.notifications.comment.CommentNotificationWorker
import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.FeedActivity import ani.dantotsu.profile.activity.FeedActivity
@ -59,7 +62,6 @@ import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData import ani.dantotsu.settings.saving.SharedPreferenceBooleanLiveData
import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.settings.saving.internal.PreferencePackager
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.BaseTransientBottomBar
@ -98,6 +100,13 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
androidx.work.WorkManager.getInstance(this)
.enqueue(OneTimeWorkRequest.Companion.from(CommentNotificationWorker::class.java))
androidx.work.WorkManager.getInstance(this)
.enqueue(OneTimeWorkRequest.Companion.from(AnilistNotificationWorker::class.java))
val action = intent.action val action = intent.action
val type = intent.type val type = intent.type
if (Intent.ACTION_VIEW == action && type != null) { if (Intent.ACTION_VIEW == action && type != null) {
@ -405,9 +414,6 @@ class MainActivity : AppCompatActivity() {
) )
} }
} }
delay(500)
startSubscription()
} }
load = true load = true
} }

View file

@ -14,24 +14,22 @@ class UrlMedia : Activity() {
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
val data: Uri? = intent?.data val data: Uri? = intent?.data
val type = data?.pathSegments?.getOrNull(0) val type = data?.pathSegments?.getOrNull(0)
if (type == "anime" || type == "manga") { if (type != "user") {
var id: Int? = intent?.extras?.getInt("media", 0) ?: 0 var id: Int? = intent?.extras?.getInt("media", 0) ?: 0
var isMAL = false var isMAL = false
var continueMedia = true var continueMedia = true
if (id == 0) { if (id == 0) {
continueMedia = false continueMedia = false
isMAL = data.host != "anilist.co" isMAL = data?.host != "anilist.co"
id = data.pathSegments?.getOrNull(1)?.toIntOrNull() id = data?.pathSegments?.getOrNull(1)?.toIntOrNull()
} else loadMedia = id } else loadMedia = id
startMainActivity( startMainActivity(
this, this,
bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia) bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia)
) )
} else if (type == "user") { } else {
val username = data.pathSegments?.getOrNull(1) val username = data.pathSegments?.getOrNull(1)
startMainActivity(this, bundleOf("username" to username)) startMainActivity(this, bundleOf("username" to username))
} else {
startMainActivity(this)
} }
} }
} }

View file

@ -16,7 +16,6 @@ import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
@ -30,10 +29,9 @@ import ani.dantotsu.parsers.DynamicAnimeParser
import ani.dantotsu.parsers.WatchSources import ani.dantotsu.parsers.WatchSources
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -196,7 +194,7 @@ class AnimeWatchAdapter(
subscribeButton(false) subscribeButton(false)
binding.animeSourceSubscribe.setOnLongClickListener { binding.animeSourceSubscribe.setOnLongClickListener {
openSettings(fragment.requireContext(), getChannelId(true, media.id)) openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK)
} }
//Nested Button //Nested Button

View file

@ -43,13 +43,9 @@ import ani.dantotsu.parsers.HAnimeSources
import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.notifications.subscription.SubscriptionHelper
import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
import ani.dantotsu.subcriptions.SubscriptionHelper
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.navigationrail.NavigationRailView
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -333,16 +329,7 @@ class AnimeWatchFragment : Fragment() {
var subscribed = false var subscribed = false
fun onNotificationPressed(subscribed: Boolean, source: String) { fun onNotificationPressed(subscribed: Boolean, source: String) {
this.subscribed = subscribed this.subscribed = subscribed
saveSubscription(requireContext(), media, subscribed) saveSubscription(media, subscribed)
if (!subscribed)
Notifications.deleteChannel(requireContext(), getChannelId(true, media.id))
else
Notifications.createChannel(
requireContext(),
ANIME_GROUP,
getChannelId(true, media.id),
media.userPreferredName
)
snackString( snackString(
if (subscribed) getString(R.string.subscribed_notification, source) if (subscribed) getString(R.string.subscribed_notification, source)
else getString(R.string.unsubscribed_notification) else getString(R.string.unsubscribed_notification)

View file

@ -13,11 +13,9 @@ import android.widget.LinearLayout
import android.widget.NumberPicker import android.widget.NumberPicker
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import androidx.core.view.children
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.* import ani.dantotsu.*
import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
@ -33,9 +31,8 @@ import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.FAQActivity
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
@ -161,7 +158,7 @@ class MangaReadAdapter(
subscribeButton(false) subscribeButton(false)
binding.animeSourceSubscribe.setOnLongClickListener { binding.animeSourceSubscribe.setOnLongClickListener {
openSettings(fragment.requireContext(), getChannelId(true, media.id)) openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK)
} }
binding.animeNestedButton.setOnClickListener { binding.animeNestedButton.setOnClickListener {

View file

@ -46,13 +46,9 @@ import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.notifications.subscription.SubscriptionHelper
import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId
import ani.dantotsu.subcriptions.SubscriptionHelper
import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.navigationrail.NavigationRailView
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -347,16 +343,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
var subscribed = false var subscribed = false
fun onNotificationPressed(subscribed: Boolean, source: String) { fun onNotificationPressed(subscribed: Boolean, source: String) {
this.subscribed = subscribed this.subscribed = subscribed
saveSubscription(requireContext(), media, subscribed) saveSubscription(media, subscribed)
if (!subscribed)
Notifications.deleteChannel(requireContext(), getChannelId(true, media.id))
else
Notifications.createChannel(
requireContext(),
MANGA_GROUP,
getChannelId(true, media.id),
media.userPreferredName
)
snackString( snackString(
if (subscribed) getString(R.string.subscribed_notification, source) if (subscribed) getString(R.string.subscribed_notification, source)
else getString(R.string.unsubscribed_notification) else getString(R.string.unsubscribed_notification)

View file

@ -8,6 +8,7 @@ import android.os.Build
import ani.dantotsu.notifications.anilist.AnilistNotificationReceiver import ani.dantotsu.notifications.anilist.AnilistNotificationReceiver
import ani.dantotsu.notifications.comment.CommentNotificationReceiver import ani.dantotsu.notifications.comment.CommentNotificationReceiver
import ani.dantotsu.notifications.TaskScheduler.TaskType import ani.dantotsu.notifications.TaskScheduler.TaskType
import ani.dantotsu.notifications.subscription.SubscriptionNotificationReceiver
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -25,6 +26,11 @@ class AlarmManagerScheduler(private val context: Context) : TaskScheduler {
context, context,
AnilistNotificationReceiver::class.java AnilistNotificationReceiver::class.java
) )
TaskType.SUBSCRIPTION_NOTIFICATION -> Intent(
context,
SubscriptionNotificationReceiver::class.java
)
} }
val pendingIntent = PendingIntent.getBroadcast( val pendingIntent = PendingIntent.getBroadcast(
@ -64,6 +70,11 @@ class AlarmManagerScheduler(private val context: Context) : TaskScheduler {
context, context,
AnilistNotificationReceiver::class.java AnilistNotificationReceiver::class.java
) )
TaskType.SUBSCRIPTION_NOTIFICATION -> Intent(
context,
SubscriptionNotificationReceiver::class.java
)
} }
val pendingIntent = PendingIntent.getBroadcast( val pendingIntent = PendingIntent.getBroadcast(

View file

@ -13,52 +13,48 @@ import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
class BootCompletedReceiver : BroadcastReceiver() { class BootCompletedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
if (context != null) { val scheduler = AlarmManagerScheduler(context)
val scheduler = AlarmManagerScheduler(context) PrefManager.init(context)
PrefManager.init(context) Logger.init(context)
Logger.init(context) Logger.log("Starting Dantotsu Subscription Service on Boot")
Logger.log("Starting Dantotsu Subscription Service on Boot") if (PrefManager.getVal(PrefName.UseAlarmManager)) {
if (PrefManager.getVal(PrefName.UseAlarmManager)) { val commentInterval =
val commentInterval = CommentNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.CommentNotificationInterval)]
CommentNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.CommentNotificationInterval)] val anilistInterval =
val anilistInterval = AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)]
AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)] scheduler.scheduleRepeatingTask(
scheduler.scheduleRepeatingTask( TaskType.COMMENT_NOTIFICATION,
TaskType.COMMENT_NOTIFICATION, commentInterval
commentInterval )
) scheduler.scheduleRepeatingTask(
scheduler.scheduleRepeatingTask( TaskType.ANILIST_NOTIFICATION,
TaskType.ANILIST_NOTIFICATION, anilistInterval
anilistInterval )
)
}
} }
} }
} }
} }
class AlarmPermissionStateReceiver : BroadcastReceiver() { class AlarmPermissionStateReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action == AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) { if (intent?.action == AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED) {
if (context != null) { PrefManager.init(context)
PrefManager.init(context) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val canScheduleExactAlarms = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val canScheduleExactAlarms = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { alarmManager.canScheduleExactAlarms()
alarmManager.canScheduleExactAlarms() } else {
} else { true
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)
} }
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)
} }
} }
} }

View file

@ -1,4 +1,4 @@
package ani.dantotsu.subcriptions package ani.dantotsu.notifications
import android.app.NotificationManager import android.app.NotificationManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
@ -9,7 +9,7 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
class NotificationClickReceiver : BroadcastReceiver() { class IncognitoNotificationClickReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
PrefManager.setVal(PrefName.Incognito, false) PrefManager.setVal(PrefName.Incognito, false)

View file

@ -3,6 +3,7 @@ package ani.dantotsu.notifications
import android.content.Context import android.content.Context
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.comment.CommentNotificationWorker
import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
@ -23,6 +24,8 @@ interface TaskScheduler {
PrefName.CommentNotificationInterval)] PrefName.CommentNotificationInterval)]
TaskType.ANILIST_NOTIFICATION -> AnilistNotificationWorker.checkIntervals[PrefManager.getVal( TaskType.ANILIST_NOTIFICATION -> AnilistNotificationWorker.checkIntervals[PrefManager.getVal(
PrefName.AnilistNotificationInterval)] PrefName.AnilistNotificationInterval)]
TaskType.SUBSCRIPTION_NOTIFICATION -> SubscriptionNotificationWorker.checkIntervals[PrefManager.getVal(
PrefName.SubscriptionNotificationInterval)]
} }
scheduleRepeatingTask(taskType, interval) scheduleRepeatingTask(taskType, interval)
} }
@ -39,7 +42,8 @@ interface TaskScheduler {
} }
enum class TaskType { enum class TaskType {
COMMENT_NOTIFICATION, COMMENT_NOTIFICATION,
ANILIST_NOTIFICATION ANILIST_NOTIFICATION,
SUBSCRIPTION_NOTIFICATION
} }
} }

View file

@ -3,9 +3,10 @@ package ani.dantotsu.notifications
import android.content.Context import android.content.Context
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import ani.dantotsu.notifications.TaskScheduler.TaskType
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.comment.CommentNotificationWorker
import ani.dantotsu.notifications.TaskScheduler.TaskType import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker
class WorkManagerScheduler(private val context: Context) : TaskScheduler { class WorkManagerScheduler(private val context: Context) : TaskScheduler {
override fun scheduleRepeatingTask(taskType: TaskType, interval: Long) { override fun scheduleRepeatingTask(taskType: TaskType, interval: Long) {
@ -17,8 +18,10 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler {
TaskType.COMMENT_NOTIFICATION -> { TaskType.COMMENT_NOTIFICATION -> {
val recurringWork = PeriodicWorkRequest.Builder( val recurringWork = PeriodicWorkRequest.Builder(
CommentNotificationWorker::class.java, CommentNotificationWorker::class.java,
interval, java.util.concurrent.TimeUnit.MINUTES, interval,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, java.util.concurrent.TimeUnit.MINUTES java.util.concurrent.TimeUnit.MINUTES,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
java.util.concurrent.TimeUnit.MINUTES
) )
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()
@ -32,8 +35,10 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler {
TaskType.ANILIST_NOTIFICATION -> { TaskType.ANILIST_NOTIFICATION -> {
val recurringWork = PeriodicWorkRequest.Builder( val recurringWork = PeriodicWorkRequest.Builder(
AnilistNotificationWorker::class.java, AnilistNotificationWorker::class.java,
interval, java.util.concurrent.TimeUnit.MINUTES, interval,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, java.util.concurrent.TimeUnit.MINUTES java.util.concurrent.TimeUnit.MINUTES,
PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
java.util.concurrent.TimeUnit.MINUTES
) )
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()
@ -43,6 +48,23 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler {
recurringWork recurringWork
) )
} }
TaskType.SUBSCRIPTION_NOTIFICATION -> {
val recurringWork = PeriodicWorkRequest.Builder(
SubscriptionNotificationWorker::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(
SubscriptionNotificationWorker.WORK_NAME,
androidx.work.ExistingPeriodicWorkPolicy.UPDATE,
recurringWork
)
}
} }
} }
@ -57,6 +79,11 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler {
androidx.work.WorkManager.getInstance(context) androidx.work.WorkManager.getInstance(context)
.cancelUniqueWork(AnilistNotificationWorker.WORK_NAME) .cancelUniqueWork(AnilistNotificationWorker.WORK_NAME)
} }
TaskType.SUBSCRIPTION_NOTIFICATION -> {
androidx.work.WorkManager.getInstance(context)
.cancelUniqueWork(SubscriptionNotificationWorker.WORK_NAME)
}
} }
} }
} }

View file

@ -11,15 +11,16 @@ import ani.dantotsu.util.Logger
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class AnilistNotificationReceiver : BroadcastReceiver() { class AnilistNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
Logger.log("AnilistNotificationReceiver: onReceive") Logger.log("AnilistNotificationReceiver: onReceive")
if (context != null) { runBlocking {
runBlocking { AnilistNotificationTask().execute(context)
AnilistNotificationTask().execute(context)
}
val anilistInterval =
AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)]
AlarmManagerScheduler(context).scheduleRepeatingTask(TaskScheduler.TaskType.ANILIST_NOTIFICATION, anilistInterval)
} }
val anilistInterval =
AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)]
AlarmManagerScheduler(context).scheduleRepeatingTask(
TaskScheduler.TaskType.ANILIST_NOTIFICATION,
anilistInterval
)
} }
} }

View file

@ -13,6 +13,7 @@ class AnilistNotificationWorker(appContext: Context, workerParams: WorkerParamet
return if (AnilistNotificationTask().execute(applicationContext)) { return if (AnilistNotificationTask().execute(applicationContext)) {
Result.success() Result.success()
} else { } else {
Logger.log("AnilistNotificationWorker: doWork failed")
Result.retry() Result.retry()
} }
} }

View file

@ -7,12 +7,10 @@ import ani.dantotsu.util.Logger
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
class CommentNotificationReceiver : BroadcastReceiver() { class CommentNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
Logger.log("CommentNotificationReceiver: onReceive") Logger.log("CommentNotificationReceiver: onReceive")
if (context != null) { runBlocking {
runBlocking { CommentNotificationTask().execute(context)
CommentNotificationTask().execute(context)
}
} }
} }
} }

View file

@ -14,6 +14,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet
return if (CommentNotificationTask().execute(applicationContext)) { return if (CommentNotificationTask().execute(applicationContext)) {
Result.success() Result.success()
} else { } else {
Logger.log("CommentNotificationWorker: doWork failed")
Result.retry() Result.retry()
} }
} }
@ -27,7 +28,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet
} }
companion object { companion object {
val checkIntervals = arrayOf(0L, 720, 1440) val checkIntervals = arrayOf(0L, 480, 720, 1440)
const val WORK_NAME = "ani.dantotsu.notifications.comment.CommentNotificationWorker" const val WORK_NAME = "ani.dantotsu.notifications.comment.CommentNotificationWorker"
} }
} }

View file

@ -1,6 +1,5 @@
package ani.dantotsu.subcriptions package ani.dantotsu.notifications.subscription
import android.content.Context
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@ -17,15 +16,13 @@ import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.tryWithSuspend import ani.dantotsu.tryWithSuspend
import ani.dantotsu.util.Logger
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
class SubscriptionHelper { class SubscriptionHelper {
companion object { companion object {
private fun loadSelected( private fun loadSelected(
context: Context, mediaId: Int
mediaId: Int,
isAdult: Boolean,
isAnime: Boolean
): Selected { ): Selected {
val data = val data =
PrefManager.getNullableCustomVal("${mediaId}-select", null, Selected::class.java) PrefManager.getNullableCustomVal("${mediaId}-select", null, Selected::class.java)
@ -37,26 +34,25 @@ class SubscriptionHelper {
return data return data
} }
private fun saveSelected(context: Context, mediaId: Int, data: Selected) { private fun saveSelected( mediaId: Int, data: Selected) {
PrefManager.setCustomVal("${mediaId}-select", data) PrefManager.setCustomVal("${mediaId}-select", data)
} }
fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser { fun getAnimeParser(id: Int): AnimeParser {
val sources = if (isAdult) HAnimeSources else AnimeSources val sources = AnimeSources
val selected = loadSelected(context, id, isAdult, true) Logger.log("getAnimeParser size: ${sources.list.size}")
val selected = loadSelected(id)
val parser = sources[selected.sourceIndex] val parser = sources[selected.sourceIndex]
parser.selectDub = selected.preferDub parser.selectDub = selected.preferDub
return parser return parser
} }
suspend fun getEpisode( suspend fun getEpisode(
context: Context,
parser: AnimeParser, parser: AnimeParser,
id: Int, id: Int
isAdult: Boolean
): Episode? { ): Episode? {
val selected = loadSelected(context, id, isAdult, true) val selected = loadSelected(id)
val ep = withTimeoutOrNull(10 * 1000) { val ep = withTimeoutOrNull(10 * 1000) {
tryWithSuspend { tryWithSuspend {
val show = parser.loadSavedShowResponse(id) ?: throw Exception( val show = parser.loadSavedShowResponse(id) ?: throw Exception(
@ -76,23 +72,21 @@ class SubscriptionHelper {
return ep?.apply { return ep?.apply {
selected.latest = number.toFloat() selected.latest = number.toFloat()
saveSelected(context, id, selected) saveSelected(id, selected)
} }
} }
fun getMangaParser(context: Context, isAdult: Boolean, id: Int): MangaParser { fun getMangaParser(id: Int): MangaParser {
val sources = if (isAdult) HMangaSources else MangaSources val sources = MangaSources
val selected = loadSelected(context, id, isAdult, false) val selected = loadSelected(id)
return sources[selected.sourceIndex] return sources[selected.sourceIndex]
} }
suspend fun getChapter( suspend fun getChapter(
context: Context,
parser: MangaParser, parser: MangaParser,
id: Int, id: Int
isAdult: Boolean
): MangaChapter? { ): MangaChapter? {
val selected = loadSelected(context, id, isAdult, true) val selected = loadSelected(id)
val chp = withTimeoutOrNull(10 * 1000) { val chp = withTimeoutOrNull(10 * 1000) {
tryWithSuspend { tryWithSuspend {
val show = parser.loadSavedShowResponse(id) ?: throw Exception( val show = parser.loadSavedShowResponse(id) ?: throw Exception(
@ -112,7 +106,7 @@ class SubscriptionHelper {
return chp?.apply { return chp?.apply {
selected.latest = MangaNameAdapter.findChapterNumber(number) ?: 0f selected.latest = MangaNameAdapter.findChapterNumber(number) ?: 0f
saveSelected(context, id, selected) saveSelected(id, selected)
} }
} }
@ -124,21 +118,21 @@ class SubscriptionHelper {
val image: String? val image: String?
) : java.io.Serializable ) : java.io.Serializable
private const val subscriptions = "subscriptions" private const val SUBSCRIPTIONS = "subscriptions"
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun getSubscriptions(): Map<Int, SubscribeMedia> = fun getSubscriptions(): Map<Int, SubscribeMedia> =
(PrefManager.getNullableCustomVal( (PrefManager.getNullableCustomVal(
subscriptions, SUBSCRIPTIONS,
null, null,
Map::class.java Map::class.java
) as? Map<Int, SubscribeMedia>) ) as? Map<Int, SubscribeMedia>)
?: mapOf<Int, SubscribeMedia>().also { PrefManager.setCustomVal(subscriptions, it) } ?: mapOf<Int, SubscribeMedia>().also { PrefManager.setCustomVal(SUBSCRIPTIONS, it) }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun saveSubscription(context: Context, media: Media, subscribed: Boolean) { fun saveSubscription(media: Media, subscribed: Boolean) {
val data = PrefManager.getNullableCustomVal( val data = PrefManager.getNullableCustomVal(
subscriptions, SUBSCRIPTIONS,
null, null,
Map::class.java Map::class.java
) as? MutableMap<Int, SubscribeMedia> ) as? MutableMap<Int, SubscribeMedia>
@ -157,7 +151,7 @@ class SubscriptionHelper {
} else { } else {
data.remove(media.id) data.remove(media.id)
} }
PrefManager.setCustomVal(subscriptions, data) PrefManager.setCustomVal(SUBSCRIPTIONS, data)
} }
} }
} }

View file

@ -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
)
}
}

View file

@ -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
}
)
}
}

View file

@ -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"
}
}

View file

@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.first
object AnimeSources : WatchSources() { object AnimeSources : WatchSources() {
override var list: List<Lazier<BaseParser>> = emptyList() override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedAnimeSources: List<String> = emptyList() var pinnedAnimeSources: List<String> = emptyList()
var isInitialized = false
suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>) { suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>) {
pinnedAnimeSources = pinnedAnimeSources =
@ -23,6 +24,7 @@ object AnimeSources : WatchSources() {
{ OfflineAnimeParser() }, { OfflineAnimeParser() },
"Downloaded" "Downloaded"
) )
isInitialized = true
// Update as StateFlow emits new values // Update as StateFlow emits new values
fromExtensions.collect { extensions -> fromExtensions.collect { extensions ->

View file

@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.first
object MangaSources : MangaReadSources() { object MangaSources : MangaReadSources() {
override var list: List<Lazier<BaseParser>> = emptyList() override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedMangaSources: List<String> = emptyList() var pinnedMangaSources: List<String> = emptyList()
var isInitialized = false
suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) { suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
pinnedMangaSources = pinnedMangaSources =
@ -23,6 +24,7 @@ object MangaSources : MangaReadSources() {
{ OfflineMangaParser() }, { OfflineMangaParser() },
"Downloaded" "Downloaded"
) )
isInitialized = true
// Update as StateFlow emits new values // Update as StateFlow emits new values
fromExtensions.collect { extensions -> fromExtensions.collect { extensions ->

View file

@ -6,7 +6,6 @@ import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Build.BRAND import android.os.Build.BRAND
import android.os.Build.DEVICE import android.os.Build.DEVICE
@ -54,7 +53,9 @@ import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.TaskScheduler import ani.dantotsu.notifications.TaskScheduler
import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.comment.CommentNotificationWorker
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker.Companion.checkIntervals
import ani.dantotsu.openLinkInBrowser import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.openSettings
import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.AppUpdater
import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.others.CustomBottomDialog
import ani.dantotsu.pop import ani.dantotsu.pop
@ -68,11 +69,6 @@ import ani.dantotsu.settings.saving.internal.PreferencePackager
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.subcriptions.Notifications
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.toast import ani.dantotsu.toast
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -652,8 +648,8 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
} }
} }
var curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime) var curTime = PrefManager.getVal<Int>(PrefName.SubscriptionNotificationInterval)
val timeNames = timeMinutes.map { val timeNames = checkIntervals.map {
val mins = it % 60 val mins = it % 60
val hours = it / 60 val hours = it / 60
if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}" if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}"
@ -668,15 +664,19 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
curTime = i curTime = i
binding.settingsSubscriptionsTime.text = binding.settingsSubscriptionsTime.text =
getString(R.string.subscriptions_checking_time_s, timeNames[i]) getString(R.string.subscriptions_checking_time_s, timeNames[i])
PrefManager.setVal(PrefName.SubscriptionsTimeS, curTime) PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime)
dialog.dismiss() dialog.dismiss()
startSubscription(true) TaskScheduler.create(this,
PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(this)
}.show() }.show()
dialog.window?.setDimAmount(0.8f) dialog.window?.setDimAmount(0.8f)
} }
binding.settingsSubscriptionsTime.setOnLongClickListener { binding.settingsSubscriptionsTime.setOnLongClickListener {
startSubscription(true) TaskScheduler.create(this,
PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(this)
true true
} }
@ -699,6 +699,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
binding.settingsAnilistSubscriptionsTime.text = binding.settingsAnilistSubscriptionsTime.text =
getString(R.string.anilist_notifications_checking_time, aItems[i]) getString(R.string.anilist_notifications_checking_time, aItems[i])
dialog.dismiss() dialog.dismiss()
TaskScheduler.create(this,
PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(this)
} }
.create() .create()
dialog.window?.setDimAmount(0.8f) dialog.window?.setDimAmount(0.8f)
@ -743,6 +746,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
binding.settingsCommentSubscriptionsTime.text = binding.settingsCommentSubscriptionsTime.text =
getString(R.string.comment_notification_checking_time, cItems[i]) getString(R.string.comment_notification_checking_time, cItems[i])
dialog.dismiss() dialog.dismiss()
TaskScheduler.create(this,
PrefManager.getVal(PrefName.UseAlarmManager)
).scheduleAllTasks(this)
} }
.create() .create()
dialog.window?.setDimAmount(0.8f) dialog.window?.setDimAmount(0.8f)
@ -753,16 +759,6 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) PrefManager.getVal(PrefName.SubscriptionCheckingNotifications)
binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked -> binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.SubscriptionCheckingNotifications, isChecked) PrefManager.setVal(PrefName.SubscriptionCheckingNotifications, isChecked)
if (isChecked)
Notifications.createChannel(
this,
null,
"subscription_checking",
getString(R.string.checking_subscriptions),
false
)
else
Notifications.deleteChannel(this, "subscription_checking")
} }
binding.settingsNotificationsCheckingSubscriptions.setOnLongClickListener { binding.settingsNotificationsCheckingSubscriptions.setOnLongClickListener {

View file

@ -17,7 +17,6 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
ContinueMedia(Pref(Location.General, Boolean::class, true)), ContinueMedia(Pref(Location.General, Boolean::class, true)),
RecentlyListOnly(Pref(Location.General, Boolean::class, false)), RecentlyListOnly(Pref(Location.General, Boolean::class, false)),
SettingsPreferDub(Pref(Location.General, Boolean::class, false)), SettingsPreferDub(Pref(Location.General, Boolean::class, false)),
SubscriptionsTimeS(Pref(Location.General, Int::class, 0)),
SubscriptionCheckingNotifications(Pref(Location.General, Boolean::class, true)), SubscriptionCheckingNotifications(Pref(Location.General, Boolean::class, true)),
CheckUpdate(Pref(Location.General, Boolean::class, true)), CheckUpdate(Pref(Location.General, Boolean::class, true)),
VerboseLogging(Pref(Location.General, Boolean::class, false)), VerboseLogging(Pref(Location.General, Boolean::class, false)),
@ -36,6 +35,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
NovelSourcesOrder(Pref(Location.General, List::class, listOf<String>())), NovelSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
CommentNotificationInterval(Pref(Location.General, Int::class, 0)), CommentNotificationInterval(Pref(Location.General, Int::class, 0)),
AnilistNotificationInterval(Pref(Location.General, Int::class, 3)), AnilistNotificationInterval(Pref(Location.General, Int::class, 3)),
SubscriptionNotificationInterval(Pref(Location.General, Int::class, 2)),
LastAnilistNotificationId(Pref(Location.General, Int::class, 0)), LastAnilistNotificationId(Pref(Location.General, Int::class, 0)),
AnilistFilteredTypes(Pref(Location.General, Set::class, setOf<String>())), AnilistFilteredTypes(Pref(Location.General, Set::class, setOf<String>())),
UseAlarmManager(Pref(Location.General, Boolean::class, false)), UseAlarmManager(Pref(Location.General, Boolean::class, false)),

View file

@ -1,60 +0,0 @@
package ani.dantotsu.subcriptions
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import ani.dantotsu.currContext
import ani.dantotsu.isOnline
import ani.dantotsu.util.Logger
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription
import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes
import ani.dantotsu.tryWith
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class AlarmReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_BOOT_COMPLETED -> tryWith(true) {
Logger.log("Starting Dantotsu Subscription Service on Boot")
context?.startSubscription()
}
}
CoroutineScope(Dispatchers.IO).launch {
val con = context ?: currContext() ?: return@launch
if (isOnline(con)) Subscription.perform(con)
}
}
companion object {
fun alarm(context: Context) {
val alarmIntent = Intent(context, AlarmReceiver::class.java)
alarmIntent.action = "ani.dantotsu.ACTION_ALARM"
val pendingIntent = PendingIntent.getBroadcast(
context, 0, alarmIntent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime)
if (timeMinutes[curTime] > 0)
alarmManager.setRepeating(
AlarmManager.RTC,
System.currentTimeMillis(),
(timeMinutes[curTime] * 60 * 1000),
pendingIntent
)
else alarmManager.cancel(pendingIntent)
}
}
}

View file

@ -1,176 +0,0 @@
package ani.dantotsu.subcriptions
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Context.NOTIFICATION_SERVICE
import android.content.Intent
import android.os.Build
import android.provider.Settings
import androidx.core.app.NotificationCompat
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.UrlMedia
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Suppress("MemberVisibilityCanBePrivate", "unused")
class Notifications {
enum class Group(val title: String, val icon: Int) {
ANIME_GROUP("New Episodes", R.drawable.ic_round_movie_filter_24),
MANGA_GROUP("New Chapters", R.drawable.ic_round_menu_book_24)
}
companion object {
fun openSettings(context: Context, channelId: String?): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val intent = Intent(
if (channelId != null) Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
else Settings.ACTION_APP_NOTIFICATION_SETTINGS
).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
}
context.startActivity(intent)
true
} else false
}
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, 0, notifyIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT
} else {
PendingIntent.FLAG_ONE_SHOT
}
)
}
fun createChannel(
context: Context,
group: Group?,
id: String,
name: String,
silent: Boolean = false
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance =
if (!silent) NotificationManager.IMPORTANCE_HIGH else NotificationManager.IMPORTANCE_LOW
val mChannel = NotificationChannel(id, name, importance)
val notificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (group != null) {
notificationManager.createNotificationChannelGroup(
NotificationChannelGroup(
group.name,
group.title
)
)
mChannel.group = group.name
}
notificationManager.createNotificationChannel(mChannel)
}
}
fun deleteChannel(context: Context, id: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.deleteNotificationChannel(id)
}
}
fun getNotification(
context: Context,
group: Group?,
channelId: String,
title: String,
text: String?,
silent: Boolean = false
): NotificationCompat.Builder {
createChannel(context, group, channelId, title, silent)
return NotificationCompat.Builder(context, channelId)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(group?.icon ?: R.drawable.monochrome)
.setContentTitle(title)
.setContentText(text)
.setAutoCancel(true)
}
suspend fun getNotification(
context: Context,
group: Group?,
channelId: String,
title: String,
text: String,
img: FileUrl?,
silent: Boolean = false,
largeImg: FileUrl?
): NotificationCompat.Builder {
val builder = getNotification(context, group, channelId, title, text, silent)
return if (img != null) {
val bitmap = withContext(Dispatchers.IO) {
Glide.with(context)
.asBitmap()
.load(GlideUrl(img.url) { img.headers })
.submit()
.get()
}
@Suppress("BlockingMethodInNonBlockingContext")
val largeBitmap = if (largeImg != null) Glide.with(context)
.asBitmap()
.load(GlideUrl(largeImg.url) { largeImg.headers })
.submit()
.get()
else null
if (largeBitmap != null) builder.setStyle(
NotificationCompat
.BigPictureStyle()
.bigPicture(largeBitmap)
.bigLargeIcon(bitmap)
)
builder.setLargeIcon(bitmap)
} else builder
}
suspend fun getNotification(
context: Context,
group: Group?,
channelId: String,
title: String,
text: String,
img: String? = null,
silent: Boolean = false,
largeImg: FileUrl? = null
): NotificationCompat.Builder {
return getNotification(
context,
group,
channelId,
title,
text,
if (img != null) FileUrl(img) else null,
silent,
largeImg
)
}
}
}

View file

@ -1,147 +0,0 @@
package ani.dantotsu.subcriptions
import android.annotation.SuppressLint
import android.app.Notification
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import ani.dantotsu.*
import ani.dantotsu.parsers.Episode
import ani.dantotsu.parsers.MangaChapter
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@SuppressLint("MissingPermission")
class Subscription {
companion object {
const val defaultTime = 1
val timeMinutes = arrayOf(0L, 720, 1440)
private var alreadyStarted = false
fun Context.startSubscription(force: Boolean = false) {
if (!alreadyStarted || force) {
alreadyStarted = true
SubscriptionWorker.enqueue(this)
AlarmReceiver.alarm(this)
} else Logger.log("Already Subscribed")
}
private var currentlyPerforming = false
suspend fun perform(context: Context) {
if (!currentlyPerforming) tryWithSuspend {
currentlyPerforming = true
App.context = context
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) {
notificationManager.notify(progressNotificationId, 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(progressNotificationId)
}
}
fun progress(progress: Int, parser: String, media: String) {
if (progressNotification != null)
notificationManager.notify(
progressNotificationId,
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(context, media.isAdult, media.id)
progress(index[it.first]!!, parser.name, media.name)
val ep: Episode? =
SubscriptionHelper.getEpisode(context, parser, media.id, media.isAdult)
if (ep != null) currActivity()!!.getString(R.string.episode) + "${ep.number}${
if (ep.title != null) " : ${ep.title}" else ""
}${
if (ep.isFiller) " [Filler]" else ""
} " + currActivity()!!.getString(R.string.just_released) to ep.thumbnail
else null
} else {
val parser =
SubscriptionHelper.getMangaParser(context, media.isAdult, media.id)
progress(index[it.first]!!, parser.name, media.name)
val ep: MangaChapter? =
SubscriptionHelper.getChapter(context, parser, media.id, media.isAdult)
if (ep != null) ep.number + " " + currActivity()!!.getString(R.string.just_released) to null
else null
} ?: return@map
createNotification(context.applicationContext, media, text.first, text.second)
}
if (progressNotification != null) notificationManager.cancel(progressNotificationId)
currentlyPerforming = false
}
}
fun getChannelId(isAnime: Boolean, mediaId: Int) =
"${if (isAnime) "anime" else "manga"}_${mediaId}"
private suspend fun createNotification(
context: Context,
media: SubscriptionHelper.Companion.SubscribeMedia,
text: String,
thumbnail: FileUrl?
) {
val notificationManager = NotificationManagerCompat.from(context)
val notification = Notifications.getNotification(
context,
if (media.isAnime) Notifications.Group.ANIME_GROUP else Notifications.Group.MANGA_GROUP,
getChannelId(media.isAnime, media.id),
media.name,
text,
media.image,
false,
thumbnail
).setContentIntent(Notifications.getIntent(context, media.id)).build()
notification.flags = Notification.FLAG_AUTO_CANCEL
//+100 to have extra ids for other notifications?
notificationManager.notify(100 + media.id, notification)
}
private const val progressNotificationId = 100
private fun getProgressNotification(
context: Context,
size: Int
): NotificationCompat.Builder {
return Notifications.getNotification(
context,
null,
"subscription_checking",
currContext()!!.getString(R.string.checking_subscriptions_title),
null,
true
).setOngoing(true).setProgress(size, 0, false).setAutoCancel(false)
}
}
}

View file

@ -1,51 +0,0 @@
package ani.dantotsu.subcriptions
import android.content.Context
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Subscription.Companion.defaultTime
import ani.dantotsu.subcriptions.Subscription.Companion.timeMinutes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
class SubscriptionWorker(val context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
Subscription.perform(context)
}
return Result.success()
}
companion object {
private const val SUBSCRIPTION_WORK_NAME = "work_subscription"
fun enqueue(context: Context) {
val curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime)
if (timeMinutes[curTime] > 0L) {
val constraints =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val periodicSyncDataWork = PeriodicWorkRequest.Builder(
SubscriptionWorker::class.java, 6, TimeUnit.HOURS
).apply {
addTag(SUBSCRIPTION_WORK_NAME)
setConstraints(constraints)
}.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
SUBSCRIPTION_WORK_NAME,
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
periodicSyncDataWork
)
}
}
}
}

View file

@ -86,7 +86,7 @@ object Logger {
fun log(message: String) { fun log(message: String) {
val trace = Thread.currentThread().stackTrace[3] val trace = Thread.currentThread().stackTrace[3]
loggerExecutor.execute { loggerExecutor.execute {
if (file == null) Log.d("Internal Logger", "$message)") if (file == null) Log.d("Internal Logger", message)
else { else {
val className = trace.className val className = trace.className
val methodName = trace.methodName val methodName = trace.methodName

View file

@ -63,6 +63,15 @@ object Notifications {
const val CHANNEL_ANILIST = "anilist_channel" const val CHANNEL_ANILIST = "anilist_channel"
const val ID_ANILIST = -901 const val ID_ANILIST = -901
/**
* Notification channel and ids used subscription checks.
*/
const val GROUP_SUBSCRIPTION_CHECK = "group_subscription_check"
const val CHANNEL_SUBSCRIPTION_CHECK = "subscription_check_channel"
const val CHANNEL_SUBSCRIPTION_CHECK_PROGRESS = "subscription_check_progress_channel"
const val ID_SUBSCRIPTION_CHECK = -1001
const val ID_SUBSCRIPTION_CHECK_PROGRESS = -1002
/** /**
* Notification channel and ids used for app and extension updates. * Notification channel and ids used for app and extension updates.
@ -118,6 +127,9 @@ object Notifications {
buildNotificationChannelGroup(GROUP_ANILIST) { buildNotificationChannelGroup(GROUP_ANILIST) {
setName("Anilist") setName("Anilist")
}, },
buildNotificationChannelGroup(GROUP_SUBSCRIPTION_CHECK) {
setName("Subscription Checks")
},
), ),
) )
@ -154,6 +166,14 @@ object Notifications {
setName("Anilist") setName("Anilist")
setGroup(GROUP_ANILIST) setGroup(GROUP_ANILIST)
}, },
buildNotificationChannel(CHANNEL_SUBSCRIPTION_CHECK, IMPORTANCE_LOW) {
setName("Subscription Checks")
setGroup(GROUP_SUBSCRIPTION_CHECK)
},
buildNotificationChannel(CHANNEL_SUBSCRIPTION_CHECK_PROGRESS, IMPORTANCE_LOW) {
setName("Subscription Checks Progress")
setGroup(GROUP_SUBSCRIPTION_CHECK)
},
buildNotificationChannel(CHANNEL_APP_GLOBAL, IMPORTANCE_HIGH) { buildNotificationChannel(CHANNEL_APP_GLOBAL, IMPORTANCE_HIGH) {
setName("Global Updates") setName("Global Updates")
}, },