feat: anilist notifications (real)

This commit is contained in:
rebelonion 2024-03-14 06:00:48 -05:00
parent 19697f4f39
commit 4d2a08c258
12 changed files with 390 additions and 95 deletions

View file

@ -7,12 +7,14 @@ import android.os.Bundle
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.OneTimeWorkRequest
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import ani.dantotsu.aniyomi.anime.custom.AppModule import ani.dantotsu.aniyomi.anime.custom.AppModule
import ani.dantotsu.aniyomi.anime.custom.PreferenceModule import ani.dantotsu.aniyomi.anime.custom.PreferenceModule
import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.notifications.NotificationWorker import ani.dantotsu.notifications.CommentNotificationWorker
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.others.DisabledReports import ani.dantotsu.others.DisabledReports
import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.MangaSources import ani.dantotsu.parsers.MangaSources
@ -34,7 +36,6 @@ import kotlinx.coroutines.launch
import logcat.AndroidLogcatLogger import logcat.AndroidLogcatLogger
import logcat.LogPriority import logcat.LogPriority
import logcat.LogcatLogger import logcat.LogcatLogger
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -122,18 +123,51 @@ class App : MultiDexApplication() {
CommentsAPI.fetchAuthToken() CommentsAPI.fetchAuthToken()
} }
startWorkers()
}
private fun startWorkers() {
val constraints = Constraints.Builder() val constraints = Constraints.Builder()
.setRequiredNetworkType(androidx.work.NetworkType.CONNECTED) .setRequiredNetworkType(androidx.work.NetworkType.CONNECTED)
.build() .build()
val recurringWork = PeriodicWorkRequest.Builder(NotificationWorker::class.java,
15, java.util.concurrent.TimeUnit.MINUTES) // CommentNotificationWorker
.setConstraints(constraints) val commentInterval = CommentNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.CommentNotificationInterval)]
.build() if (commentInterval.toInt() != 0) {
androidx.work.WorkManager.getInstance(this).enqueueUniquePeriodicWork( val recurringWork = PeriodicWorkRequest.Builder(CommentNotificationWorker::class.java,
NotificationWorker.WORK_NAME, commentInterval, java.util.concurrent.TimeUnit.MINUTES)
androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, .setConstraints(constraints)
recurringWork .build()
) androidx.work.WorkManager.getInstance(this).enqueueUniquePeriodicWork(
CommentNotificationWorker.WORK_NAME,
androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
recurringWork
)
} else {
androidx.work.WorkManager.getInstance(this).cancelUniqueWork(CommentNotificationWorker.WORK_NAME)
//run once
androidx.work.WorkManager.getInstance(this).enqueue(OneTimeWorkRequest.Companion.from(CommentNotificationWorker::class.java))
}
// AnilistNotificationWorker
val anilistInterval = AnilistNotificationWorker.checkIntervals[PrefManager.getVal(PrefName.AnilistNotificationInterval)]
if (anilistInterval.toInt() != 0) {
val recurringWork = PeriodicWorkRequest.Builder(
AnilistNotificationWorker::class.java,
anilistInterval, java.util.concurrent.TimeUnit.MINUTES)
.setConstraints(constraints)
.build()
androidx.work.WorkManager.getInstance(this).enqueueUniquePeriodicWork(
AnilistNotificationWorker.WORK_NAME,
androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
recurringWork
)
} else {
androidx.work.WorkManager.getInstance(this).cancelUniqueWork(AnilistNotificationWorker.WORK_NAME)
//run once
androidx.work.WorkManager.getInstance(this).enqueue(OneTimeWorkRequest.Companion.from(AnilistNotificationWorker::class.java))
}
androidx.work.WorkManager.getInstance(this).cancelUniqueWork("ani.dantotsu.notifications.NotificationWorker")
} }

View file

@ -1339,9 +1339,10 @@ Page(page:$page,perPage:50) {
return """MediaListCollection(userId: ${id}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } }""" return """MediaListCollection(userId: ${id}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } }"""
} }
suspend fun getNotifications(id: Int, page: Int = 1): NotificationResponse? { suspend fun getNotifications(id: Int, page: Int = 1, resetNotification: Boolean = true): NotificationResponse? {
val reset = if (resetNotification) "true" else "false"
val res = executeQuery<NotificationResponse>( val res = executeQuery<NotificationResponse>(
"""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){notifications(resetNotificationCount:true){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", """{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){notifications(resetNotificationCount:$reset){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""",
force = true force = true
) )
if (res != null) { if (res != null) {
@ -1354,10 +1355,9 @@ Page(page:$page,perPage:50) {
val filter = if (userId != null) "userId:$userId," val filter = if (userId != null) "userId:$userId,"
else if (global) "isFollowing:false,type:TEXT," else if (global) "isFollowing:false,type:TEXT,"
else "isFollowing:true,type_not:MESSAGE," else "isFollowing:true,type_not:MESSAGE,"
val res = executeQuery<FeedResponse>( return executeQuery<FeedResponse>(
"""{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""" """{Page(page:$page,perPage:$ITEMS_PER_PAGE){activities(${filter}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount likeCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}"""
) )
return res
} }
companion object { companion object {

View file

@ -699,7 +699,7 @@ class Query {
// // Activity reply query // // Activity reply query
// val ActivityReply: ActivityReply?, // val ActivityReply: ActivityReply?,
// // Comment query // // CommentNotificationWorker query
// val ThreadComment: List<ThreadComment>?, // val ThreadComment: List<ThreadComment>?,
// // Notification query // // Notification query

View file

@ -129,12 +129,12 @@ class CommentItem(val comment: Comment,
viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE
viewBinding.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE viewBinding.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE
viewBinding.commentDelete.setOnClickListener { viewBinding.commentDelete.setOnClickListener {
dialogBuilder("Delete Comment", "Are you sure you want to delete this comment?") { dialogBuilder("Delete CommentNotificationWorker", "Are you sure you want to delete this comment?") {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch { scope.launch {
val success = CommentsAPI.deleteComment(comment.commentId) val success = CommentsAPI.deleteComment(comment.commentId)
if (success) { if (success) {
snackString("Comment Deleted") snackString("CommentNotificationWorker Deleted")
parentSection.remove(this@CommentItem) parentSection.remove(this@CommentItem)
} }
} }
@ -152,12 +152,12 @@ class CommentItem(val comment: Comment,
} }
} }
viewBinding.commentReport.setOnClickListener { viewBinding.commentReport.setOnClickListener {
dialogBuilder("Report Comment", "Only report comments that violate the rules. Are you sure you want to report this comment?") { dialogBuilder("Report CommentNotificationWorker", "Only report comments that violate the rules. Are you sure you want to report this comment?") {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch { scope.launch {
val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId) val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId)
if (success) { if (success) {
snackString("Comment Reported") snackString("CommentNotificationWorker Reported")
} }
} }
} }

View file

@ -270,7 +270,7 @@ class CommentsFragment : Fragment() {
} }
//adds additional comments to the section //adds additional comments to the section
private suspend fun updateUIWithComment(comment: Comment) { private suspend fun updateUIWithComment(comment: CommentNotificationWorker) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
section.add( section.add(
CommentItem( CommentItem(
@ -297,7 +297,7 @@ class CommentsFragment : Fragment() {
override fun afterTextChanged(s: android.text.Editable?) { override fun afterTextChanged(s: android.text.Editable?) {
if (binding.commentInput.text.length > 300) { if (binding.commentInput.text.length > 300) {
binding.commentInput.text.delete(300, binding.commentInput.text.length) binding.commentInput.text.delete(300, binding.commentInput.text.length)
snackString("Comment cannot be longer than 300 characters") snackString("CommentNotificationWorker cannot be longer than 300 characters")
} }
} }
}) })
@ -626,7 +626,7 @@ class CommentsFragment : Fragment() {
private fun processComment() { private fun processComment() {
val commentText = binding.commentInput.text.toString() val commentText = binding.commentInput.text.toString()
if (commentText.isEmpty()) { if (commentText.isEmpty()) {
snackString("Comment cannot be empty") snackString("CommentNotificationWorker cannot be empty")
return return
} }
@ -662,7 +662,7 @@ class CommentsFragment : Fragment() {
groups.forEach { item -> groups.forEach { item ->
if (item is CommentItem && item.comment.commentId == commentWithInteraction?.comment?.commentId) { if (item is CommentItem && item.comment.commentId == commentWithInteraction?.comment?.commentId) {
updateCommentItem(item, commentText) updateCommentItem(item, commentText)
snackString("Comment edited") snackString("CommentNotificationWorker edited")
} }
} }
} }

View file

@ -24,10 +24,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.util.Locale
class NotificationWorker(appContext: Context, workerParams: WorkerParameters) : class CommentNotificationWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) { Worker(appContext, workerParams) {
override fun doWork(): Result { override fun doWork(): Result {
val scope = CoroutineScope(Dispatchers.IO) val scope = CoroutineScope(Dispatchers.IO)
@ -62,7 +61,7 @@ class NotificationWorker(appContext: Context, workerParams: WorkerParameters) :
"" ""
) )
} else { } else {
val title = "New Comment Reply" val title = "New CommentNotificationWorker Reply"
val mediaName = names[it.mediaId]?.title ?: "Unknown" val mediaName = names[it.mediaId]?.title ?: "Unknown"
val message = "${it.username} replied to your comment in $mediaName" val message = "${it.username} replied to your comment in $mediaName"
createNotification( createNotification(
@ -187,6 +186,7 @@ class NotificationWorker(appContext: Context, workerParams: WorkerParameters) :
} }
companion object { companion object {
const val WORK_NAME = "ani.dantotsu.notifications.NotificationWorker" val checkIntervals = arrayOf(0L, 720, 1440)
const val WORK_NAME = "ani.dantotsu.notifications.CommentNotificationWorker"
} }
} }

View file

@ -0,0 +1,98 @@
package ani.dantotsu.notifications.anilist
import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.profile.activity.ActivityItemBuilder
import ani.dantotsu.profile.activity.FeedActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import eu.kanade.tachiyomi.data.notification.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class AnilistNotificationWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
PrefManager.init(applicationContext) //make sure prefs are initialized
val userId = PrefManager.getVal<String>(PrefName.AnilistUserId)
if (userId.isNotEmpty()) {
Anilist.getSavedToken()
val res = Anilist.query.getNotifications(userId.toInt(), resetNotification = false)
val unreadNotificationCount = res?.data?.user?.unreadNotificationCount ?: 0
if (unreadNotificationCount > 0) {
val unreadNotifications = res?.data?.page?.notifications?.sortedBy { it.id }
?.takeLast(unreadNotificationCount)
val lastId = PrefManager.getVal<Int>(PrefName.LastAnilistNotificationId)
val newNotifications = unreadNotifications?.filter { it.id > lastId }
val filteredTypes =
PrefManager.getVal<Set<String>>(PrefName.AnilistFilteredTypes)
newNotifications?.forEach {
if (!filteredTypes.contains(it.notificationType)) {
val content = ActivityItemBuilder.getContent(it)
val notification = createNotification(applicationContext, content)
if (ActivityCompat.checkSelfPermission(
applicationContext,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
NotificationManagerCompat.from(applicationContext)
.notify(
Notifications.CHANNEL_ANILIST,
System.currentTimeMillis().toInt(),
notification
)
}
}
}
if (newNotifications?.isNotEmpty() == true) {
PrefManager.setVal(PrefName.LastAnilistNotificationId, 0)
}
}
}
}
return Result.success()
}
private fun createNotification(
context: Context,
content: String
): android.app.Notification {
val title = "New Anilist Notification"
val intent = Intent(applicationContext, FeedActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return NotificationCompat.Builder(context, Notifications.CHANNEL_ANILIST)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
}
companion object {
val checkIntervals = arrayOf(0L, 30, 60, 120, 240, 360, 720, 1440)
const val WORK_NAME = "ani.dantotsu.notifications.anilist.AnilistNotificationWorker"
}
}

View file

@ -17,7 +17,6 @@ import android.view.ViewGroup
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
@ -34,6 +33,7 @@ import ani.dantotsu.BuildConfig
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.NotificationType
import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.discord.Discord
import ani.dantotsu.connections.mal.MAL import ani.dantotsu.connections.mal.MAL
import ani.dantotsu.copyToClipboard import ani.dantotsu.copyToClipboard
@ -47,6 +47,8 @@ import ani.dantotsu.initActivity
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.CommentNotificationWorker
import ani.dantotsu.notifications.anilist.AnilistNotificationWorker
import ani.dantotsu.openLinkInBrowser import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.AppUpdater
import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.others.CustomBottomDialog
@ -673,6 +675,75 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
true true
} }
val aTimeNames = AnilistNotificationWorker.checkIntervals.map { it.toInt() }
val aItems = aTimeNames.map {
val mins = it % 60
val hours = it / 60
if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}"
else getString(R.string.do_not_update)
}
binding.settingsAnilistSubscriptionsTime.text =
getString(R.string.anilist_notifications_checking_time, aItems[PrefManager.getVal(PrefName.AnilistNotificationInterval)])
binding.settingsAnilistSubscriptionsTime.setOnClickListener {
val selected = PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval)
val dialog = AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(R.string.subscriptions_checking_time)
.setSingleChoiceItems(aItems.toTypedArray(), selected) { dialog, i ->
PrefManager.setVal(PrefName.AnilistNotificationInterval, i)
binding.settingsAnilistSubscriptionsTime.text =
getString(R.string.anilist_notifications_checking_time, aItems[i])
dialog.dismiss()
}
.create()
dialog.window?.setDimAmount(0.8f)
dialog.show()
}
binding.settingsAnilistNotifications.setOnClickListener {
val types = NotificationType.entries.map { it.name }
val filteredTypes = PrefManager.getVal<Set<String>>(PrefName.AnilistFilteredTypes).toMutableSet()
val selected = types.map { filteredTypes.contains(it) }.toBooleanArray()
val dialog = AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(R.string.anilist_notification_filters)
.setMultiChoiceItems(types.toTypedArray(), selected) { _, which, isChecked ->
val type = types[which]
if (isChecked) {
filteredTypes.add(type)
} else {
filteredTypes.remove(type)
}
PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes)
}
.create()
dialog.window?.setDimAmount(0.8f)
dialog.show()
}
val cTimeNames = CommentNotificationWorker.checkIntervals.map { it.toInt() }
val cItems = cTimeNames.map {
val mins = it % 60
val hours = it / 60
if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}"
else getString(R.string.do_not_update)
}
binding.settingsCommentSubscriptionsTime.text =
getString(R.string.comment_notification_checking_time, cItems[PrefManager.getVal(PrefName.CommentNotificationInterval)])
binding.settingsCommentSubscriptionsTime.setOnClickListener {
val selected = PrefManager.getVal<Int>(PrefName.CommentNotificationInterval)
val dialog = AlertDialog.Builder(this, R.style.MyPopup)
.setTitle(R.string.subscriptions_checking_time)
.setSingleChoiceItems(cItems.toTypedArray(), selected) { dialog, i ->
PrefManager.setVal(PrefName.CommentNotificationInterval, i)
binding.settingsCommentSubscriptionsTime.text =
getString(R.string.comment_notification_checking_time, cItems[i])
dialog.dismiss()
}
.create()
dialog.window?.setDimAmount(0.8f)
dialog.show()
}
binding.settingsNotificationsCheckingSubscriptions.isChecked = binding.settingsNotificationsCheckingSubscriptions.isChecked =
PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) PrefManager.getVal(PrefName.SubscriptionCheckingNotifications)
binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked -> binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked ->

View file

@ -33,7 +33,10 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())), MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
MangaSearchHistory(Pref(Location.General, Set::class, setOf<String>())), MangaSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
NovelSourcesOrder(Pref(Location.General, List::class, listOf<String>())), NovelSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
NotificationInterval(Pref(Location.General, Int::class, 0)), CommentNotificationInterval(Pref(Location.General, Int::class, 0)),
AnilistNotificationInterval(Pref(Location.General, Int::class, 3)),
LastAnilistNotificationId(Pref(Location.General, Int::class, 0)),
AnilistFilteredTypes(Pref(Location.General, Set::class, setOf<String>())),
//User Interface //User Interface
UseOLED(Pref(Location.UI, Boolean::class, false)), UseOLED(Pref(Location.UI, Boolean::class, false)),

View file

@ -53,6 +53,13 @@ object Notifications {
const val CHANNEL_COMMENT_WARING = "comment_warning_channel" const val CHANNEL_COMMENT_WARING = "comment_warning_channel"
const val ID_COMMENT_REPLY = -801 const val ID_COMMENT_REPLY = -801
/**
* Notification channel and ids used for anilist updates.
*/
const val GROUP_ANILIST = "group_anilist"
const val CHANNEL_ANILIST = "anilist_channel"
const val ID_ANILIST = -901
/** /**
* Notification channel and ids used for app and extension updates. * Notification channel and ids used for app and extension updates.
@ -105,6 +112,9 @@ object Notifications {
buildNotificationChannelGroup(GROUP_COMMENTS) { buildNotificationChannelGroup(GROUP_COMMENTS) {
setName("Comments") setName("Comments")
}, },
buildNotificationChannelGroup(GROUP_ANILIST) {
setName("Anilist")
},
), ),
) )
@ -134,9 +144,13 @@ object Notifications {
setGroup(GROUP_COMMENTS) setGroup(GROUP_COMMENTS)
}, },
buildNotificationChannel(CHANNEL_COMMENT_WARING, IMPORTANCE_HIGH) { buildNotificationChannel(CHANNEL_COMMENT_WARING, IMPORTANCE_HIGH) {
setName("Comment Warnings") setName("CommentNotificationWorker Warnings")
setGroup(GROUP_COMMENTS) setGroup(GROUP_COMMENTS)
}, },
buildNotificationChannel(CHANNEL_ANILIST, IMPORTANCE_DEFAULT) {
setName("Anilist")
setGroup(GROUP_ANILIST)
},
buildNotificationChannel(CHANNEL_APP_UPDATE, IMPORTANCE_DEFAULT) { buildNotificationChannel(CHANNEL_APP_UPDATE, IMPORTANCE_DEFAULT) {
setGroup(GROUP_APK_UPDATES) setGroup(GROUP_APK_UPDATES)
setName("App Updates") setName("App Updates")

View file

@ -971,70 +971,6 @@
app:showText="false" app:showText="false"
app:thumbTint="@color/button_switch_track" /> app:thumbTint="@color/button_switch_track" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/settingsShareUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_search_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:text="@string/share_username_in_crash_reports"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/settingsLogToFile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_edit_note_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:text="@string/log_to_file"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.58"
android:fontFamily="@font/poppins_bold"
android:text="@string/logging_warning" />
<ImageButton
android:id="@+id/settingsShareLog"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_round_share_24"
android:padding="16dp" />
</LinearLayout>
</ani.dantotsu.others.Xpandable> </ani.dantotsu.others.Xpandable>
<ani.dantotsu.others.Xpandable <ani.dantotsu.others.Xpandable
@ -1440,6 +1376,81 @@
android:text="@string/subscriptions_info" android:text="@string/subscriptions_info"
android:textSize="14sp" /> android:textSize="14sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="-16dp"
android:background="?android:attr/listDivider" />
<Button
android:id="@+id/settingsAnilistNotifications"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="-31dp"
android:layout_marginEnd="-31dp"
android:background="@drawable/ui_bg"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingStart="31dp"
android:paddingEnd="31dp"
android:text="@string/anilist_notification_filters"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:icon="@drawable/ic_anilist"
app:iconPadding="16dp"
app:iconSize="24dp"
app:iconTint="?attr/colorPrimary" />
<Button
android:id="@+id/settingsAnilistSubscriptionsTime"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="-11dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:text="@string/anilist_notifications_checking_time"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:icon="@drawable/ic_round_notifications_none_24"
app:iconPadding="16dp"
app:iconSize="24dp" />
<Button
android:id="@+id/settingsCommentSubscriptionsTime"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="-11dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:text="@string/comment_notification_checking_time"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:icon="@drawable/ic_round_notifications_none_24"
app:iconPadding="16dp"
app:iconSize="24dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="-16dp"
android:background="?android:attr/listDivider" />
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/settingsNotificationsCheckingSubscriptions" android:id="@+id/settingsNotificationsCheckingSubscriptions"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -1517,6 +1528,67 @@
app:drawableTint="?attr/colorPrimary" app:drawableTint="?attr/colorPrimary"
app:showText="false" app:showText="false"
app:thumbTint="@color/button_switch_track" /> app:thumbTint="@color/button_switch_track" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/settingsShareUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_search_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:text="@string/share_username_in_crash_reports"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/settingsLogToFile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="false"
android:drawableStart="@drawable/ic_round_edit_note_24"
android:drawablePadding="16dp"
android:elegantTextHeight="true"
android:fontFamily="@font/poppins_bold"
android:minHeight="64dp"
android:text="@string/log_to_file"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:drawableTint="?attr/colorPrimary"
app:showText="false"
app:thumbTint="@color/button_switch_track" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:alpha="0.58"
android:fontFamily="@font/poppins_bold"
android:text="@string/logging_warning" />
<ImageButton
android:id="@+id/settingsShareLog"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_round_share_24"
android:padding="16dp" />
</LinearLayout>
<Button <Button
android:id="@+id/settingsDev" android:id="@+id/settingsDev"

View file

@ -700,4 +700,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="blur">Blur</string> <string name="blur">Blur</string>
<string name="hide_scroll_bar">Hide Scroll Bar</string> <string name="hide_scroll_bar">Hide Scroll Bar</string>
<string name="view_on_anilist">View on AniList</string> <string name="view_on_anilist">View on AniList</string>
<string name="anilist_notification_filters">Anilist Notification Filters</string>
<string name="anilist_notifications_checking_time">Anilist notifications update frequency : %1$s</string>
<string name="comment_notification_checking_time">Comment notifications update frequency : %1$s</string>
</resources> </resources>