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:resource="@xml/currently_airing_widget_info" />
</receiver>
<receiver android:name=".subcriptions.NotificationClickReceiver" />
<receiver android:name=".notifications.IncognitoNotificationClickReceiver" />
<activity
@ -307,14 +307,6 @@
android:exported="false"
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"
android:exported="true">
<intent-filter>
@ -330,7 +322,7 @@
</receiver>
<receiver android:name=".notifications.anilist.AnilistNotificationReceiver"/>
<receiver android:name=".notifications.comment.CommentNotificationReceiver"/>
<receiver android:name=".notifications.subscription.SubscriptionNotificationReceiver"/>
<meta-data
android:name="preloaded_fonts"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

@ -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(

View file

@ -13,9 +13,8 @@ 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)
@ -37,12 +36,10 @@ class BootCompletedReceiver : 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 (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) {
@ -61,4 +58,3 @@ class AlarmPermissionStateReceiver : BroadcastReceiver() {
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -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)
}
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)) {
Result.success()
} else {
Logger.log("AnilistNotificationWorker: doWork failed")
Result.retry()
}
}

View file

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

View file

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

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.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<Int, SubscribeMedia> =
(PrefManager.getNullableCustomVal(
subscriptions,
SUBSCRIPTIONS,
null,
Map::class.java
) as? Map<Int, SubscribeMedia>)
?: mapOf<Int, SubscribeMedia>().also { PrefManager.setCustomVal(subscriptions, it) }
?: mapOf<Int, SubscribeMedia>().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<Int, SubscribeMedia>
@ -157,7 +151,7 @@ class SubscriptionHelper {
} else {
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() {
override var list: List<Lazier<BaseParser>> = emptyList()
var pinnedAnimeSources: List<String> = emptyList()
var isInitialized = false
suspend fun init(fromExtensions: StateFlow<List<AnimeExtension.Installed>>) {
pinnedAnimeSources =
@ -23,6 +24,7 @@ object AnimeSources : WatchSources() {
{ OfflineAnimeParser() },
"Downloaded"
)
isInitialized = true
// Update as StateFlow emits new values
fromExtensions.collect { extensions ->

View file

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

View file

@ -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<Int>(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 {

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)),
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<String>())),
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<String>())),
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) {
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

View file

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