diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 11800322..cf650323 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,7 +69,7 @@ android:name="android.appwidget.provider" android:resource="@xml/currently_airing_widget_info" /> - + - - - - - - @@ -330,7 +322,7 @@ - + (PrefName.UseAlarmManager) - 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() { try { Notifications.createChannels(this) diff --git a/app/src/main/java/ani/dantotsu/Functions.kt b/app/src/main/java/ani/dantotsu/Functions.kt index 13b06758..f51b28ff 100644 --- a/app/src/main/java/ani/dantotsu/Functions.kt +++ b/app/src/main/java/ani/dantotsu/Functions.kt @@ -70,6 +70,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService 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.databinding.ItemCountDownBinding import ani.dantotsu.media.Media +import ani.dantotsu.notifications.IncognitoNotificationClickReceiver import ani.dantotsu.others.SpoilerPlugin import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt -import ani.dantotsu.subcriptions.NotificationClickReceiver import ani.dantotsu.util.Logger import com.bumptech.glide.Glide import com.bumptech.glide.RequestBuilder @@ -1172,7 +1173,7 @@ fun incognitoNotification(context: Context) { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val incognito: Boolean = PrefManager.getVal(PrefName.Incognito) if (incognito) { - val intent = Intent(context, NotificationClickReceiver::class.java) + val intent = Intent(context, IncognitoNotificationClickReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, 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() { currActivity()?.runOnUiThread { ObjectAnimator.ofFloat(this@pop, "scaleX", 1f, 1.25f).setDuration(120).start() diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index d692f9b7..23d8e508 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -38,6 +38,7 @@ import androidx.lifecycle.lifecycleScope import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.Download import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.work.OneTimeWorkRequest import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.AnilistHomeViewModel import ani.dantotsu.databinding.ActivityMainBinding @@ -49,6 +50,8 @@ import ani.dantotsu.home.LoginFragment import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.NoInternet 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.profile.ProfileActivity 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.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferencePackager -import ani.dantotsu.subcriptions.Subscription.Companion.startSubscription import ani.dantotsu.themes.ThemeManager import ani.dantotsu.util.Logger import com.google.android.material.snackbar.BaseTransientBottomBar @@ -98,6 +100,13 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) 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 type = intent.type if (Intent.ACTION_VIEW == action && type != null) { @@ -405,9 +414,6 @@ class MainActivity : AppCompatActivity() { ) } } - - delay(500) - startSubscription() } load = true } diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt b/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt index 7df91d75..318bbc29 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt @@ -14,24 +14,22 @@ class UrlMedia : Activity() { ThemeManager(this).applyTheme() val data: Uri? = intent?.data val type = data?.pathSegments?.getOrNull(0) - if (type == "anime" || type == "manga") { + if (type != "user") { var id: Int? = intent?.extras?.getInt("media", 0) ?: 0 var isMAL = false var continueMedia = true if (id == 0) { continueMedia = false - isMAL = data.host != "anilist.co" - id = data.pathSegments?.getOrNull(1)?.toIntOrNull() + isMAL = data?.host != "anilist.co" + id = data?.pathSegments?.getOrNull(1)?.toIntOrNull() } else loadMedia = id startMainActivity( this, bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia) ) - } else if (type == "user") { + } else { val username = data.pathSegments?.getOrNull(1) startMainActivity(this, bundleOf("username" to username)) - } else { - startMainActivity(this) } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index 07550739..4b53fe42 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -16,7 +16,6 @@ import androidx.core.content.ContextCompat.startActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* -import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemChipBinding @@ -30,10 +29,9 @@ import ani.dantotsu.parsers.DynamicAnimeParser import ani.dantotsu.parsers.WatchSources import ani.dantotsu.settings.saving.PrefManager 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 eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK import eu.kanade.tachiyomi.util.system.WebViewUtil import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch @@ -196,7 +194,7 @@ class AnimeWatchAdapter( subscribeButton(false) binding.animeSourceSubscribe.setOnLongClickListener { - openSettings(fragment.requireContext(), getChannelId(true, media.id)) + openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK) } //Nested Button diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index fc8141e2..50eef688 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -43,13 +43,9 @@ import ani.dantotsu.parsers.HAnimeSources import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName -import ani.dantotsu.subcriptions.Notifications -import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP -import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId -import ani.dantotsu.subcriptions.SubscriptionHelper -import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription +import ani.dantotsu.notifications.subscription.SubscriptionHelper +import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription import com.google.android.material.appbar.AppBarLayout -import com.google.android.material.navigationrail.NavigationRailView import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import kotlinx.coroutines.Dispatchers @@ -333,16 +329,7 @@ class AnimeWatchFragment : Fragment() { var subscribed = false fun onNotificationPressed(subscribed: Boolean, source: String) { this.subscribed = subscribed - saveSubscription(requireContext(), media, subscribed) - if (!subscribed) - Notifications.deleteChannel(requireContext(), getChannelId(true, media.id)) - else - Notifications.createChannel( - requireContext(), - ANIME_GROUP, - getChannelId(true, media.id), - media.userPreferredName - ) + saveSubscription(media, subscribed) snackString( if (subscribed) getString(R.string.subscribed_notification, source) else getString(R.string.unsubscribed_notification) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index 5c11ae35..9f8ca4da 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -13,11 +13,9 @@ import android.widget.LinearLayout import android.widget.NumberPicker import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.startActivity -import androidx.core.view.children import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* -import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemChipBinding @@ -33,9 +31,8 @@ import ani.dantotsu.parsers.MangaSources import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.saving.PrefManager 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 eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.system.WebViewUtil import kotlinx.coroutines.MainScope @@ -161,7 +158,7 @@ class MangaReadAdapter( subscribeButton(false) binding.animeSourceSubscribe.setOnLongClickListener { - openSettings(fragment.requireContext(), getChannelId(true, media.id)) + openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK) } binding.animeNestedButton.setOnClickListener { diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 9fda920f..621e07fb 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -46,13 +46,9 @@ import ani.dantotsu.parsers.MangaSources import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName -import ani.dantotsu.subcriptions.Notifications -import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP -import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId -import ani.dantotsu.subcriptions.SubscriptionHelper -import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription +import ani.dantotsu.notifications.subscription.SubscriptionHelper +import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription 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.source.ConfigurableSource import kotlinx.coroutines.CoroutineScope @@ -347,16 +343,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { var subscribed = false fun onNotificationPressed(subscribed: Boolean, source: String) { this.subscribed = subscribed - saveSubscription(requireContext(), media, subscribed) - if (!subscribed) - Notifications.deleteChannel(requireContext(), getChannelId(true, media.id)) - else - Notifications.createChannel( - requireContext(), - MANGA_GROUP, - getChannelId(true, media.id), - media.userPreferredName - ) + saveSubscription(media, subscribed) snackString( if (subscribed) getString(R.string.subscribed_notification, source) else getString(R.string.unsubscribed_notification) diff --git a/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt b/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt index 63dc757c..9c89a582 100644 --- a/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt +++ b/app/src/main/java/ani/dantotsu/notifications/AlarmManagerScheduler.kt @@ -8,6 +8,7 @@ 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.notifications.subscription.SubscriptionNotificationReceiver import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import java.util.concurrent.TimeUnit @@ -25,6 +26,11 @@ class AlarmManagerScheduler(private val context: Context) : TaskScheduler { context, AnilistNotificationReceiver::class.java ) + + TaskType.SUBSCRIPTION_NOTIFICATION -> Intent( + context, + SubscriptionNotificationReceiver::class.java + ) } val pendingIntent = PendingIntent.getBroadcast( @@ -64,6 +70,11 @@ class AlarmManagerScheduler(private val context: Context) : TaskScheduler { context, AnilistNotificationReceiver::class.java ) + + TaskType.SUBSCRIPTION_NOTIFICATION -> Intent( + context, + SubscriptionNotificationReceiver::class.java + ) } val pendingIntent = PendingIntent.getBroadcast( diff --git a/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt index 3ebc9412..8cfc0a1c 100644 --- a/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt +++ b/app/src/main/java/ani/dantotsu/notifications/BootCompletedReceiver.kt @@ -13,52 +13,48 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.util.Logger 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 (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 - ) - } + 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?) { + 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) + PrefManager.init(context) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val canScheduleExactAlarms = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + alarmManager.canScheduleExactAlarms() + } else { + true } + if (canScheduleExactAlarms) { + TaskScheduler.create(context, false).cancelAllTasks() + TaskScheduler.create(context, true).scheduleAllTasks(context) + } else { + TaskScheduler.create(context, true).cancelAllTasks() + TaskScheduler.create(context, false).scheduleAllTasks(context) + } + PrefManager.setVal(PrefName.UseAlarmManager, canScheduleExactAlarms) } } } diff --git a/app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/IncognitoNotificationClickReceiver.kt similarity index 85% rename from app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt rename to app/src/main/java/ani/dantotsu/notifications/IncognitoNotificationClickReceiver.kt index 410d9b23..814441e7 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/NotificationClickReceiver.kt +++ b/app/src/main/java/ani/dantotsu/notifications/IncognitoNotificationClickReceiver.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.subcriptions +package ani.dantotsu.notifications import android.app.NotificationManager import android.content.BroadcastReceiver @@ -9,7 +9,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName -class NotificationClickReceiver : BroadcastReceiver() { +class IncognitoNotificationClickReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { PrefManager.setVal(PrefName.Incognito, false) diff --git a/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt b/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt index 1a75422c..fa0304ed 100644 --- a/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt +++ b/app/src/main/java/ani/dantotsu/notifications/TaskScheduler.kt @@ -3,6 +3,7 @@ package ani.dantotsu.notifications import android.content.Context import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.notifications.comment.CommentNotificationWorker +import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName @@ -23,6 +24,8 @@ interface TaskScheduler { PrefName.CommentNotificationInterval)] TaskType.ANILIST_NOTIFICATION -> AnilistNotificationWorker.checkIntervals[PrefManager.getVal( PrefName.AnilistNotificationInterval)] + TaskType.SUBSCRIPTION_NOTIFICATION -> SubscriptionNotificationWorker.checkIntervals[PrefManager.getVal( + PrefName.SubscriptionNotificationInterval)] } scheduleRepeatingTask(taskType, interval) } @@ -39,7 +42,8 @@ interface TaskScheduler { } enum class TaskType { COMMENT_NOTIFICATION, - ANILIST_NOTIFICATION + ANILIST_NOTIFICATION, + SUBSCRIPTION_NOTIFICATION } } diff --git a/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt b/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt index aa8c9439..0a26213e 100644 --- a/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt +++ b/app/src/main/java/ani/dantotsu/notifications/WorkManagerScheduler.kt @@ -3,9 +3,10 @@ package ani.dantotsu.notifications import android.content.Context import androidx.work.Constraints import androidx.work.PeriodicWorkRequest +import ani.dantotsu.notifications.TaskScheduler.TaskType import ani.dantotsu.notifications.anilist.AnilistNotificationWorker 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 { override fun scheduleRepeatingTask(taskType: TaskType, interval: Long) { @@ -17,8 +18,10 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler { 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 + interval, + java.util.concurrent.TimeUnit.MINUTES, + PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, + java.util.concurrent.TimeUnit.MINUTES ) .setConstraints(constraints) .build() @@ -32,8 +35,10 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler { 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 + interval, + java.util.concurrent.TimeUnit.MINUTES, + PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, + java.util.concurrent.TimeUnit.MINUTES ) .setConstraints(constraints) .build() @@ -43,6 +48,23 @@ class WorkManagerScheduler(private val context: Context) : TaskScheduler { 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) .cancelUniqueWork(AnilistNotificationWorker.WORK_NAME) } + + TaskType.SUBSCRIPTION_NOTIFICATION -> { + androidx.work.WorkManager.getInstance(context) + .cancelUniqueWork(SubscriptionNotificationWorker.WORK_NAME) + } } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt index ace8029c..fc3f1793 100644 --- a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt +++ b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationReceiver.kt @@ -11,15 +11,16 @@ import ani.dantotsu.util.Logger import kotlinx.coroutines.runBlocking class AnilistNotificationReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { + 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) + runBlocking { + AnilistNotificationTask().execute(context) } + val anilistInterval = + AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)] + AlarmManagerScheduler(context).scheduleRepeatingTask( + TaskScheduler.TaskType.ANILIST_NOTIFICATION, + anilistInterval + ) } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt index 645b9733..f3506fc8 100644 --- a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt +++ b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt @@ -13,6 +13,7 @@ class AnilistNotificationWorker(appContext: Context, workerParams: WorkerParamet return if (AnilistNotificationTask().execute(applicationContext)) { Result.success() } else { + Logger.log("AnilistNotificationWorker: doWork failed") Result.retry() } } diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt index 65c78e98..c084bba2 100644 --- a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationReceiver.kt @@ -7,12 +7,10 @@ import ani.dantotsu.util.Logger import kotlinx.coroutines.runBlocking class CommentNotificationReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { + override fun onReceive(context: Context, intent: Intent?) { Logger.log("CommentNotificationReceiver: onReceive") - if (context != null) { - runBlocking { - CommentNotificationTask().execute(context) - } + runBlocking { + CommentNotificationTask().execute(context) } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt index 5b75b481..47d61fd5 100644 --- a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt @@ -14,6 +14,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet return if (CommentNotificationTask().execute(applicationContext)) { Result.success() } else { + Logger.log("CommentNotificationWorker: doWork failed") Result.retry() } } @@ -27,7 +28,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet } companion object { - val checkIntervals = arrayOf(0L, 720, 1440) + val checkIntervals = arrayOf(0L, 480, 720, 1440) const val WORK_NAME = "ani.dantotsu.notifications.comment.CommentNotificationWorker" } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt similarity index 75% rename from app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt rename to app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt index 1929ce3c..9c8498a4 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt +++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionHelper.kt @@ -1,6 +1,5 @@ -package ani.dantotsu.subcriptions +package ani.dantotsu.notifications.subscription -import android.content.Context import ani.dantotsu.R import ani.dantotsu.currContext 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.PrefName import ani.dantotsu.tryWithSuspend +import ani.dantotsu.util.Logger import kotlinx.coroutines.withTimeoutOrNull class SubscriptionHelper { companion object { private fun loadSelected( - context: Context, - mediaId: Int, - isAdult: Boolean, - isAnime: Boolean + mediaId: Int ): Selected { val data = PrefManager.getNullableCustomVal("${mediaId}-select", null, Selected::class.java) @@ -37,26 +34,25 @@ class SubscriptionHelper { return data } - private fun saveSelected(context: Context, mediaId: Int, data: Selected) { + private fun saveSelected( mediaId: Int, data: Selected) { PrefManager.setCustomVal("${mediaId}-select", data) } - fun getAnimeParser(context: Context, isAdult: Boolean, id: Int): AnimeParser { - val sources = if (isAdult) HAnimeSources else AnimeSources - val selected = loadSelected(context, id, isAdult, true) + 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( - context: Context, parser: AnimeParser, - id: Int, - isAdult: Boolean + id: Int ): Episode? { - val selected = loadSelected(context, id, isAdult, true) + val selected = loadSelected(id) val ep = withTimeoutOrNull(10 * 1000) { tryWithSuspend { val show = parser.loadSavedShowResponse(id) ?: throw Exception( @@ -76,23 +72,21 @@ class SubscriptionHelper { return ep?.apply { selected.latest = number.toFloat() - saveSelected(context, id, selected) + saveSelected(id, selected) } } - fun getMangaParser(context: Context, isAdult: Boolean, id: Int): MangaParser { - val sources = if (isAdult) HMangaSources else MangaSources - val selected = loadSelected(context, id, isAdult, false) + fun getMangaParser(id: Int): MangaParser { + val sources = MangaSources + val selected = loadSelected(id) return sources[selected.sourceIndex] } suspend fun getChapter( - context: Context, parser: MangaParser, - id: Int, - isAdult: Boolean + id: Int ): MangaChapter? { - val selected = loadSelected(context, id, isAdult, true) + val selected = loadSelected(id) val chp = withTimeoutOrNull(10 * 1000) { tryWithSuspend { val show = parser.loadSavedShowResponse(id) ?: throw Exception( @@ -112,7 +106,7 @@ class SubscriptionHelper { return chp?.apply { selected.latest = MangaNameAdapter.findChapterNumber(number) ?: 0f - saveSelected(context, id, selected) + saveSelected(id, selected) } } @@ -124,21 +118,21 @@ class SubscriptionHelper { val image: String? ) : java.io.Serializable - private const val subscriptions = "subscriptions" + private const val SUBSCRIPTIONS = "subscriptions" @Suppress("UNCHECKED_CAST") fun getSubscriptions(): Map = (PrefManager.getNullableCustomVal( - subscriptions, + SUBSCRIPTIONS, null, Map::class.java ) as? Map) - ?: mapOf().also { PrefManager.setCustomVal(subscriptions, it) } + ?: mapOf().also { PrefManager.setCustomVal(SUBSCRIPTIONS, it) } @Suppress("UNCHECKED_CAST") - fun saveSubscription(context: Context, media: Media, subscribed: Boolean) { + fun saveSubscription(media: Media, subscribed: Boolean) { val data = PrefManager.getNullableCustomVal( - subscriptions, + SUBSCRIPTIONS, null, Map::class.java ) as? MutableMap @@ -157,7 +151,7 @@ class SubscriptionHelper { } else { data.remove(media.id) } - PrefManager.setCustomVal(subscriptions, data) + PrefManager.setCustomVal(SUBSCRIPTIONS, data) } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationReceiver.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationReceiver.kt new file mode 100644 index 00000000..72afe484 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationReceiver.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt new file mode 100644 index 00000000..ecbf6bd2 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt @@ -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 + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationWorker.kt new file mode 100644 index 00000000..22086b3b --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationWorker.kt @@ -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" + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index 9806767a..119f4d2f 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.first object AnimeSources : WatchSources() { override var list: List> = emptyList() var pinnedAnimeSources: List = emptyList() + var isInitialized = false suspend fun init(fromExtensions: StateFlow>) { pinnedAnimeSources = @@ -23,6 +24,7 @@ object AnimeSources : WatchSources() { { OfflineAnimeParser() }, "Downloaded" ) + isInitialized = true // Update as StateFlow emits new values fromExtensions.collect { extensions -> diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt index 34c3e0a0..c610654f 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.first object MangaSources : MangaReadSources() { override var list: List> = emptyList() var pinnedMangaSources: List = emptyList() + var isInitialized = false suspend fun init(fromExtensions: StateFlow>) { pinnedMangaSources = @@ -23,6 +24,7 @@ object MangaSources : MangaReadSources() { { OfflineMangaParser() }, "Downloaded" ) + isInitialized = true // Update as StateFlow emits new values fromExtensions.collect { extensions -> diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 80c99b4a..4394156c 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -6,7 +6,6 @@ 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 @@ -54,7 +53,9 @@ import ani.dantotsu.navBarHeight import ani.dantotsu.notifications.TaskScheduler import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker +import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker.Companion.checkIntervals import ani.dantotsu.openLinkInBrowser +import ani.dantotsu.openSettings import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.pop @@ -68,11 +69,6 @@ import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.snackString import ani.dantotsu.startMainActivity 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.toast import com.google.android.material.snackbar.Snackbar @@ -652,8 +648,8 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } } - var curTime = PrefManager.getVal(PrefName.SubscriptionsTimeS, defaultTime) - val timeNames = timeMinutes.map { + var curTime = PrefManager.getVal(PrefName.SubscriptionNotificationInterval) + val timeNames = checkIntervals.map { val mins = it % 60 val hours = it / 60 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 binding.settingsSubscriptionsTime.text = getString(R.string.subscriptions_checking_time_s, timeNames[i]) - PrefManager.setVal(PrefName.SubscriptionsTimeS, curTime) + PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) dialog.dismiss() - startSubscription(true) + TaskScheduler.create(this, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this) }.show() dialog.window?.setDimAmount(0.8f) } binding.settingsSubscriptionsTime.setOnLongClickListener { - startSubscription(true) + TaskScheduler.create(this, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this) true } @@ -699,6 +699,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene binding.settingsAnilistSubscriptionsTime.text = getString(R.string.anilist_notifications_checking_time, aItems[i]) dialog.dismiss() + TaskScheduler.create(this, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this) } .create() dialog.window?.setDimAmount(0.8f) @@ -743,6 +746,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene binding.settingsCommentSubscriptionsTime.text = getString(R.string.comment_notification_checking_time, cItems[i]) dialog.dismiss() + TaskScheduler.create(this, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this) } .create() dialog.window?.setDimAmount(0.8f) @@ -753,16 +759,6 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, 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 { diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 13486d4d..5149c031 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -17,7 +17,6 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files ContinueMedia(Pref(Location.General, Boolean::class, true)), RecentlyListOnly(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)), CheckUpdate(Pref(Location.General, Boolean::class, true)), 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())), CommentNotificationInterval(Pref(Location.General, Int::class, 0)), AnilistNotificationInterval(Pref(Location.General, Int::class, 3)), + SubscriptionNotificationInterval(Pref(Location.General, Int::class, 2)), LastAnilistNotificationId(Pref(Location.General, Int::class, 0)), AnilistFilteredTypes(Pref(Location.General, Set::class, setOf())), UseAlarmManager(Pref(Location.General, Boolean::class, false)), diff --git a/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt b/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt deleted file mode 100644 index c9c59c09..00000000 --- a/app/src/main/java/ani/dantotsu/subcriptions/AlarmReceiver.kt +++ /dev/null @@ -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) - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/subcriptions/Notifications.kt b/app/src/main/java/ani/dantotsu/subcriptions/Notifications.kt deleted file mode 100644 index 0599a5be..00000000 --- a/app/src/main/java/ani/dantotsu/subcriptions/Notifications.kt +++ /dev/null @@ -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 - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt b/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt deleted file mode 100644 index fb4e529d..00000000 --- a/app/src/main/java/ani/dantotsu/subcriptions/Subscription.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt deleted file mode 100644 index 00c7650f..00000000 --- a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionWorker.kt +++ /dev/null @@ -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 - ) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/util/Logger.kt b/app/src/main/java/ani/dantotsu/util/Logger.kt index 22c0000c..5820abd8 100644 --- a/app/src/main/java/ani/dantotsu/util/Logger.kt +++ b/app/src/main/java/ani/dantotsu/util/Logger.kt @@ -86,7 +86,7 @@ object Logger { fun log(message: String) { val trace = Thread.currentThread().stackTrace[3] loggerExecutor.execute { - if (file == null) Log.d("Internal Logger", "$message)") + if (file == null) Log.d("Internal Logger", message) else { val className = trace.className val methodName = trace.methodName diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index 2e4ff107..1b8810e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -63,6 +63,15 @@ object Notifications { const val CHANNEL_ANILIST = "anilist_channel" 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. @@ -118,6 +127,9 @@ object Notifications { buildNotificationChannelGroup(GROUP_ANILIST) { setName("Anilist") }, + buildNotificationChannelGroup(GROUP_SUBSCRIPTION_CHECK) { + setName("Subscription Checks") + }, ), ) @@ -154,6 +166,14 @@ object Notifications { setName("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) { setName("Global Updates") },