diff --git a/app/build.gradle b/app/build.gradle index ed1679cd..a95c9e55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,7 +11,7 @@ android { defaultConfig { applicationId "ani.dantotsu" - minSdk 23 + minSdk 21 targetSdk 34 versionCode((System.currentTimeMillis() / 60000).toInteger()) versionName "2.2.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5883981b..2b9f5cad 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -261,6 +261,17 @@ + + + + + + + + + + + + + + + + + + + + true + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val cap = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) + return@tryWith if (cap != null) { + when { + cap.hasTransport(TRANSPORT_BLUETOOTH) || + cap.hasTransport(TRANSPORT_CELLULAR) || + cap.hasTransport(TRANSPORT_ETHERNET) || + cap.hasTransport(TRANSPORT_LOWPAN) || + cap.hasTransport(TRANSPORT_USB) || + cap.hasTransport(TRANSPORT_VPN) || + cap.hasTransport(TRANSPORT_WIFI) || + cap.hasTransport(TRANSPORT_WIFI_AWARE) -> true - else -> false - } - } else false + else -> false + } + } else false + } else { + @Suppress("DEPRECATION") + return@tryWith connectivityManager.activeNetworkInfo?.run { + type == ConnectivityManager.TYPE_BLUETOOTH || + type == ConnectivityManager.TYPE_ETHERNET || + type == ConnectivityManager.TYPE_MOBILE || + type == ConnectivityManager.TYPE_MOBILE_DUN || + type == ConnectivityManager.TYPE_MOBILE_HIPRI || + type == ConnectivityManager.TYPE_WIFI || + type == ConnectivityManager.TYPE_WIMAX || + type == ConnectivityManager.TYPE_VPN + } ?: false + } } ?: false } diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 4268aac0..d692f9b7 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -2,6 +2,7 @@ package ani.dantotsu import android.animation.ObjectAnimator import android.annotation.SuppressLint +import android.app.AlertDialog import android.content.Intent import android.content.res.Configuration import android.content.res.Resources @@ -14,6 +15,7 @@ import android.os.Handler import android.os.Looper import android.provider.Settings import android.util.TypedValue +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AnticipateInterpolator @@ -28,6 +30,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.doOnAttach import androidx.core.view.updateLayoutParams import androidx.core.view.updateMargins +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle @@ -47,17 +50,21 @@ import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.NoInternet import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.others.CustomBottomDialog +import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.activity.FeedActivity import ani.dantotsu.profile.activity.NotificationActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager.asLiveBool 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 import com.google.android.material.snackbar.Snackbar +import com.google.android.material.textfield.TextInputEditText import eu.kanade.domain.source.service.SourcePreferences import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin @@ -91,6 +98,60 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + val action = intent.action + val type = intent.type + if (Intent.ACTION_VIEW == action && type != null) { + val uri: Uri? = intent.data + try { + if (uri == null) { + throw Exception("Uri is null") + } + val jsonString = + contentResolver.openInputStream(uri)?.readBytes() + ?: throw Exception("Error reading file") + val name = + DocumentFile.fromSingleUri(this, uri)?.name ?: "settings" + //.sani is encrypted, .ani is not + if (name.endsWith(".sani")) { + passwordAlertDialog { password -> + if (password != null) { + val salt = jsonString.copyOfRange(0, 16) + val encrypted = jsonString.copyOfRange(16, jsonString.size) + val decryptedJson = try { + PreferenceKeystore.decryptWithPassword( + password, + encrypted, + salt + ) + } catch (e: Exception) { + toast("Incorrect password") + return@passwordAlertDialog + } + if (PreferencePackager.unpack(decryptedJson)) { + val intent = Intent(this, this.javaClass) + this.finish() + startActivity(intent) + } + } else { + toast("Password cannot be empty") + } + } + } else if (name.endsWith(".ani")) { + val decryptedJson = jsonString.toString(Charsets.UTF_8) + if (PreferencePackager.unpack(decryptedJson)) { + val intent = Intent(this, this.javaClass) + this.finish() + startActivity(intent) + } + } else { + toast("Invalid file type") + } + } catch (e: Exception) { + e.printStackTrace() + toast("Error importing settings") + } + } + val _bottomBar = findViewById(R.id.navbar) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { @@ -214,7 +275,7 @@ class MainActivity : AppCompatActivity() { binding.root.doOnAttach { initActivity(this) - window.navigationBarColor = getColor(android.R.color.transparent) + window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent) selectedOption = if (fragment != null) { when (fragment) { AnimeFragment::class.java.name -> 0 @@ -329,6 +390,22 @@ class MainActivity : AppCompatActivity() { snackString(this@MainActivity.getString(R.string.anilist_not_found)) } } + val username = intent.extras?.getString("username") + if (username != null) { + val nameInt = username.toIntOrNull() + if (nameInt != null) { + startActivity( + Intent(this@MainActivity, ProfileActivity::class.java) + .putExtra("userId", nameInt) + ) + } else { + startActivity( + Intent(this@MainActivity, ProfileActivity::class.java) + .putExtra("username", username) + ) + } + } + delay(500) startSubscription() } @@ -381,7 +458,7 @@ class MainActivity : AppCompatActivity() { override fun onRestart() { super.onRestart() - window.navigationBarColor = getColor(android.R.color.transparent) + window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent) } private val Int.toPx get() = TypedValue.applyDimension( @@ -398,6 +475,44 @@ class MainActivity : AppCompatActivity() { params.updateMargins(bottom = 32.toPx) } + private fun passwordAlertDialog(callback: (CharArray?) -> Unit) { + val password = CharArray(16).apply { fill('0') } + + // Inflate the dialog layout + val dialogView = + LayoutInflater.from(this).inflate(R.layout.dialog_user_agent, null) + dialogView.findViewById(R.id.userAgentTextBox)?.hint = "Password" + val subtitleTextView = dialogView.findViewById(R.id.subtitle) + subtitleTextView?.visibility = View.VISIBLE + subtitleTextView?.text = "Enter your password to decrypt the file" + + val dialog = AlertDialog.Builder(this, R.style.MyPopup) + .setTitle("Enter Password") + .setView(dialogView) + .setPositiveButton("OK", null) + .setNegativeButton("Cancel") { dialog, _ -> + password.fill('0') + dialog.dismiss() + callback(null) + } + .create() + + dialog.window?.setDimAmount(0.8f) + dialog.show() + + // Override the positive button here + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val editText = dialog.findViewById(R.id.userAgentTextBox) + if (editText?.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + dialog.dismiss() + callback(password) + } else { + toast("Password cannot be empty") + } + } + } + //ViewPager private class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 68f18f39..6b1bc6d7 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -54,6 +54,8 @@ class AnilistQueries { Anilist.chapterRead = user.statistics?.manga?.chaptersRead Anilist.adult = user.options?.displayAdultContent ?: false Anilist.unreadNotificationCount = user.unreadNotificationCount ?: 0 + val unread = PrefManager.getVal(PrefName.UnreadCommentNotifications) + Anilist.unreadNotificationCount += unread return true } @@ -1345,6 +1347,18 @@ Page(page:$page,perPage:50) { ) } + suspend fun getUserProfile(username: String): Query.UserProfileResponse? { + val id = getUserId(username) ?: return null + return getUserProfile(id) + } + + suspend fun getUserId(username: String): Int? { + return executeQuery( + """{User(name:"$username"){id}}""", + force = true + )?.data?.user?.id + } + suspend fun getUserStatistics(id: Int, sort: String = "ID"): Query.StatisticsResponse? { return executeQuery( """{User(id:$id){id name mediaListOptions{scoreFormat}statistics{anime{...UserStatistics}manga{...UserStatistics}}}}fragment UserStatistics on UserStatistics{count meanScore standardDeviation minutesWatched episodesWatched chaptersRead volumesRead formats(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds format}statuses(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds status}scores(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds score}lengths(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds length}releaseYears(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds releaseYear}startYears(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds startYear}genres(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds genre}tags(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds tag{id name}}countries(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds country}voiceActors(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds voiceActor{id name{first middle last full native alternative userPreferred}}characterIds}staff(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds staff{id name{first middle last full native alternative userPreferred}}}studios(sort:$sort){count meanScore minutesWatched chaptersRead mediaIds studio{id name isAnimationStudio}}}""", @@ -1392,7 +1406,10 @@ Page(page:$page,perPage:50) { """{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 ) - if (res != null) { + if (res != null && resetNotification) { + val commentNotifications = PrefManager.getVal(PrefName.UnreadCommentNotifications, 0) + res.data.user.unreadNotificationCount += commentNotifications + PrefManager.setVal(PrefName.UnreadCommentNotifications, 0) Anilist.unreadNotificationCount = 0 } return res @@ -1404,7 +1421,8 @@ Page(page:$page,perPage:50) { else if (global) "isFollowing:false,hasRepliesOrTypeText:true," else "isFollowing:true,type_not:MESSAGE," return executeQuery( - """{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}}}}}}""", + force = true ) } diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt b/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt index 98d63d27..7df91d75 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/UrlMedia.kt @@ -11,20 +11,27 @@ import ani.dantotsu.themes.ThemeManager class UrlMedia : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - ThemeManager(this).applyTheme() - var id: Int? = intent?.extras?.getInt("media", 0) ?: 0 - var isMAL = false - var continueMedia = true - if (id == 0) { - continueMedia = false - val data: Uri? = intent?.data - 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) - ) + val data: Uri? = intent?.data + val type = data?.pathSegments?.getOrNull(0) + if (type == "anime" || type == "manga") { + 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() + } else loadMedia = id + startMainActivity( + this, + bundleOf("mediaId" to id, "mal" to isMAL, "continue" to continueMedia) + ) + } else if (type == "user") { + val username = data.pathSegments?.getOrNull(1) + startMainActivity(this, bundleOf("username" to username)) + } else { + startMainActivity(this) + } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt index 073fadc1..f526078a 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt @@ -20,7 +20,9 @@ enum class NotificationType(val value: String) { RELATED_MEDIA_ADDITION("RELATED_MEDIA_ADDITION"), MEDIA_DATA_CHANGE("MEDIA_DATA_CHANGE"), MEDIA_MERGE("MEDIA_MERGE"), - MEDIA_DELETION("MEDIA_DELETION") + MEDIA_DELETION("MEDIA_DELETION"), + //custom + COMMENT_REPLY("COMMENT_REPLY"), } @Serializable @@ -40,7 +42,7 @@ data class NotificationResponse( @Serializable data class NotificationUser( @SerialName("unreadNotificationCount") - val unreadNotificationCount: Int, + var unreadNotificationCount: Int, ) : java.io.Serializable @Serializable @@ -56,41 +58,41 @@ data class Notification( @SerialName("id") val id: Int, @SerialName("userId") - val userId: Int?, + val userId: Int? = null, @SerialName("CommentId") val commentId: Int?, @SerialName("type") val notificationType: String, @SerialName("activityId") - val activityId: Int?, + val activityId: Int? = null, @SerialName("animeId") - val mediaId: Int?, + val mediaId: Int? = null, @SerialName("episode") - val episode: Int?, + val episode: Int? = null, @SerialName("contexts") - val contexts: List?, + val contexts: List? = null, @SerialName("context") - val context: String?, + val context: String? = null, @SerialName("reason") - val reason: String?, + val reason: String? = null, @SerialName("deletedMediaTitle") - val deletedMediaTitle: String?, + val deletedMediaTitle: String? = null, @SerialName("deletedMediaTitles") - val deletedMediaTitles: List?, + val deletedMediaTitles: List? = null, @SerialName("createdAt") val createdAt: Int, @SerialName("media") - val media: ani.dantotsu.connections.anilist.api.Media?, + val media: ani.dantotsu.connections.anilist.api.Media? = null, @SerialName("user") - val user: ani.dantotsu.connections.anilist.api.User?, + val user: ani.dantotsu.connections.anilist.api.User? = null, @SerialName("message") - val message: MessageActivity?, + val message: MessageActivity? = null, @SerialName("activity") - val activity: ActivityUnion?, + val activity: ActivityUnion? = null, @SerialName("Thread") - val thread: Thread?, + val thread: Thread? = null, @SerialName("comment") - val comment: ThreadComment?, + val comment: ThreadComment? = null, ) : java.io.Serializable @Serializable diff --git a/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt b/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt index e06af9e4..dc9bf872 100644 --- a/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt +++ b/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt @@ -31,12 +31,15 @@ object CommentsAPI { var isMod: Boolean = false var totalVotes: Int = 0 - suspend fun getCommentsForId(id: Int, page: Int = 1, tag: Int?): CommentResponse? { + suspend fun getCommentsForId(id: Int, page: Int = 1, tag: Int?, sort: String?): CommentResponse? { var url = "$address/comments/$id/$page" val request = requestBuilder() tag?.let { url += "?tag=$it" } + sort?.let { + url += if (tag != null) "&sort=$it" else "?sort=$it" + } val json = try { request.get(url) } catch (e: IOException) { diff --git a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt index 8b19d9ab..22b20dcb 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -54,6 +54,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import tachiyomi.core.util.lang.launchIO import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File @@ -355,15 +356,13 @@ class AnimeDownloaderService : Service() { return false } - @OptIn(DelicateCoroutinesApi::class) private fun saveMediaInfo(task: AnimeDownloadTask) { - GlobalScope.launch(Dispatchers.IO) { + launchIO { val directory = File( getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "${DownloadsManager.animeLocation}/${task.title}" ) val episodeDirectory = File(directory, task.episode) - if (!directory.exists()) directory.mkdirs() if (!episodeDirectory.exists()) episodeDirectory.mkdirs() val file = File(directory, "media.json") diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index cad2f85e..188c9dc3 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -37,6 +37,7 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapterImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job @@ -47,6 +48,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import tachiyomi.core.util.lang.launchIO import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File @@ -287,8 +289,9 @@ class MangaDownloaderService : Service() { } } + @OptIn(DelicateCoroutinesApi::class) private fun saveMediaInfo(task: DownloadTask) { - GlobalScope.launch(Dispatchers.IO) { + launchIO { val directory = File( getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga/${task.title}" diff --git a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt index 06043976..0c3575a3 100644 --- a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt @@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapterImpl import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job @@ -42,6 +43,7 @@ import kotlinx.coroutines.withContext import okhttp3.Request import okio.buffer import okio.sink +import tachiyomi.core.util.lang.launchIO import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File @@ -347,8 +349,9 @@ class NovelDownloaderService : Service() { } } + @OptIn(DelicateCoroutinesApi::class) private fun saveMediaInfo(task: DownloadTask) { - GlobalScope.launch(Dispatchers.IO) { + launchIO { val directory = File( getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel/${task.title}" diff --git a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt index 8efa8ac3..5f89464e 100644 --- a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt @@ -17,6 +17,7 @@ import ani.dantotsu.openLinkInBrowser import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.toast +import ani.dantotsu.util.Logger import com.google.android.material.textfield.TextInputEditText class LoginFragment : Fragment() { @@ -50,7 +51,7 @@ class LoginFragment : Fragment() { DocumentFile.fromSingleUri(requireActivity(), uri)?.name ?: "settings" //.sani is encrypted, .ani is not if (name.endsWith(".sani")) { - passwordAlertDialog() { password -> + passwordAlertDialog { password -> if (password != null) { val salt = jsonString.copyOfRange(0, 16) val encrypted = jsonString.copyOfRange(16, jsonString.size) @@ -78,7 +79,7 @@ class LoginFragment : Fragment() { toast("Invalid file type") } } catch (e: Exception) { - e.printStackTrace() + Logger.log(e) toast("Error importing settings") } } diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt index a30da85f..ad8c378e 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt @@ -7,7 +7,7 @@ import java.util.regex.Pattern class AnimeNameAdapter { companion object { const val episodeRegex = - "(episode|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*" + "(episode|episodio|ep|e)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*\\(?\\s*(sub|subbed|dub|dubbed)*\\s*\\)?\\s*" const val failedEpisodeNumberRegex = "(? "lowest_rated" else -> return@setOnMenuItemClickListener false } - PrefManager.setVal(PrefName.CommentSortOrder, sortOrder) - sortComments(sortOrder) + if (totalPages > pagesLoaded) { + lifecycleScope.launch { + loadAndDisplayComments() + activity.binding.commentReplyToContainer.visibility = View.GONE + } + } else { + sortComments(sortOrder) + } binding.commentsList.scrollToPosition(0) true } @@ -197,7 +202,8 @@ class CommentsFragment : Fragment() { } } } else { - snackString("No more comments") + //snackString("No more comments") fix spam? + Logger.log("No more comments") } } } @@ -219,7 +225,12 @@ class CommentsFragment : Fragment() { private suspend fun fetchComments(): CommentResponse? { return withContext(Dispatchers.IO) { - CommentsAPI.getCommentsForId(mediaId, pagesLoaded + 1, filterTag) + CommentsAPI.getCommentsForId( + mediaId, + pagesLoaded + 1, + filterTag, + PrefManager.getVal(PrefName.CommentSortOrder, "newest") + ) } } @@ -253,7 +264,7 @@ class CommentsFragment : Fragment() { 300, activity.binding.commentInput.text.length ) - snackString("CommentNotificationWorker cannot be longer than 300 characters") + snackString("Comment cannot be longer than 300 characters") } } }) @@ -377,7 +388,11 @@ class CommentsFragment : Fragment() { section.clear() val comments = withContext(Dispatchers.IO) { - CommentsAPI.getCommentsForId(mediaId, tag = filterTag) + CommentsAPI.getCommentsForId( + mediaId, + tag = filterTag, + sort = PrefManager.getVal(PrefName.CommentSortOrder, "newest") + ) } val sortedComments = sortComments(comments?.comments) @@ -460,6 +475,7 @@ class CommentsFragment : Fragment() { } InteractionState.REPLY -> { + activity.binding.commentReplyToContainer.visibility = View.GONE activity.binding.commentInput.setText("") val imm = activity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(activity.binding.commentInput.windowToken, 0) @@ -587,7 +603,7 @@ class CommentsFragment : Fragment() { private fun processComment() { val commentText = activity.binding.commentInput.text.toString() if (commentText.isEmpty()) { - snackString("CommentNotificationWorker cannot be empty") + snackString("Comment cannot be empty") return } @@ -604,6 +620,7 @@ class CommentsFragment : Fragment() { null ) } + resetOldState() } } @@ -623,7 +640,7 @@ class CommentsFragment : Fragment() { groups.forEach { item -> if (item is CommentItem && item.comment.commentId == commentWithInteraction?.comment?.commentId) { updateCommentItem(item, commentText) - snackString("CommentNotificationWorker edited") + snackString("Comment edited") } } } diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt index 9cbffc50..8da7c35b 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt @@ -5,6 +5,7 @@ import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.ItemNovelResponseBinding @@ -59,11 +60,11 @@ class NovelResponseAdapter( } if (binding.itemEpisodeFiller.text.contains("Downloading")) { binding.itemEpisodeFiller.setTextColor( - fragment.requireContext().getColor(android.R.color.holo_blue_light) + ContextCompat.getColor(fragment.requireContext(), android.R.color.holo_blue_light) ) } else if (binding.itemEpisodeFiller.text.contains("Downloaded")) { binding.itemEpisodeFiller.setTextColor( - fragment.requireContext().getColor(android.R.color.holo_green_light) + ContextCompat.getColor(fragment.requireContext(), android.R.color.holo_green_light) ) } else { binding.itemEpisodeFiller.setTextColor(color) diff --git a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt index eb7d776c..50a9befb 100644 --- a/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt +++ b/app/src/main/java/ani/dantotsu/notifications/anilist/AnilistNotificationWorker.kt @@ -16,6 +16,7 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.profile.activity.ActivityItemBuilder import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -78,12 +79,13 @@ class AnilistNotificationWorker(appContext: Context, workerParams: WorkerParamet flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK putExtra("FRAGMENT_TO_LOAD", "NOTIFICATIONS") if (notificationId != null) { + Logger.log("notificationId: $notificationId") putExtra("activityId", notificationId) } } val pendingIntent = PendingIntent.getActivity( applicationContext, - 0, + notificationId ?: 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) diff --git a/app/src/main/java/ani/dantotsu/notifications/CommentNotificationWorker.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt similarity index 82% rename from app/src/main/java/ani/dantotsu/notifications/CommentNotificationWorker.kt rename to app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt index 7f0d6d5e..5ecc65db 100644 --- a/app/src/main/java/ani/dantotsu/notifications/CommentNotificationWorker.kt +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationWorker.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.notifications +package ani.dantotsu.notifications.comment import android.Manifest import android.app.PendingIntent @@ -20,6 +20,7 @@ import ani.dantotsu.R import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.data.notification.Notifications import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -58,8 +59,12 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet if (newRecentGlobal != null) { PrefManager.setVal(PrefName.RecentGlobalNotification, newRecentGlobal) } + if (notifications.isNullOrEmpty()) return@launch + PrefManager.setVal(PrefName.UnreadCommentNotifications, + PrefManager.getVal(PrefName.UnreadCommentNotifications) + (notifications?.size ?: 0) + ) - notifications?.forEach { + notifications.forEach { val type: NotificationType = when (it.type) { 1 -> NotificationType.COMMENT_REPLY 2 -> NotificationType.COMMENT_WARNING @@ -71,6 +76,15 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet NotificationType.COMMENT_WARNING -> { val title = "You received a warning" val message = it.content ?: "Be more thoughtful with your comments" + + val commentStore = CommentStore( + title, + message, + it.mediaId, + it.commentId + ) + addNotificationToStore(commentStore) + createNotification( NotificationType.COMMENT_WARNING, message, @@ -83,9 +97,18 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet } NotificationType.COMMENT_REPLY -> { - val title = "New CommentNotificationWorker Reply" + val title = "New Comment Reply" val mediaName = names[it.mediaId]?.title ?: "Unknown" val message = "${it.username} replied to your comment in $mediaName" + + val commentStore = CommentStore( + title, + message, + it.mediaId, + it.commentId + ) + addNotificationToStore(commentStore) + createNotification( NotificationType.COMMENT_REPLY, message, @@ -100,6 +123,15 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet NotificationType.APP_GLOBAL -> { val title = "Update from Dantotsu" val message = it.content ?: "New feature available" + + val commentStore = CommentStore( + title, + message, + null, + null + ) + addNotificationToStore(commentStore) + createNotification( NotificationType.APP_GLOBAL, message, @@ -143,6 +175,22 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet return Result.success() } + private fun addNotificationToStore(notification: CommentStore) { + val notificationStore = PrefManager.getNullableVal>( + PrefName.CommentNotificationStore, + null + ) ?: listOf() + val newStore = notificationStore.toMutableList() + if (newStore.size > 10) { + newStore.remove(newStore.minByOrNull { it.time }) + } + if (newStore.any { it.content == notification.content }) { + return + } + newStore.add(notification) + PrefManager.setVal(PrefName.CommentNotificationStore, newStore) + } + private fun createNotification( notificationType: NotificationType, message: String, @@ -152,6 +200,10 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet color: String, imageUrl: String ): android.app.Notification? { + Logger.log( + "Creating notification of type $notificationType" + + ", message: $message, title: $title, mediaId: $mediaId, commentId: $commentId" + ) val notification = when (notificationType) { NotificationType.COMMENT_WARNING -> { val intent = Intent(applicationContext, MainActivity::class.java).apply { @@ -162,7 +214,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet } val pendingIntent = PendingIntent.getActivity( applicationContext, - 0, + commentId, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) @@ -185,7 +237,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet } val pendingIntent = PendingIntent.getActivity( applicationContext, - 0, + commentId, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) @@ -214,7 +266,7 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet } val pendingIntent = PendingIntent.getActivity( applicationContext, - 0, + System.currentTimeMillis().toInt(), intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) @@ -266,6 +318,6 @@ class CommentNotificationWorker(appContext: Context, workerParams: WorkerParamet companion object { val checkIntervals = arrayOf(0L, 720, 1440) - const val WORK_NAME = "ani.dantotsu.notifications.CommentNotificationWorker" + const val WORK_NAME = "ani.dantotsu.notifications.comment.CommentNotificationWorker" } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt new file mode 100644 index 00000000..bc6ab98b --- /dev/null +++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt @@ -0,0 +1,20 @@ +package ani.dantotsu.notifications.comment + +import kotlinx.serialization.Serializable + + +@Suppress("INAPPROPRIATE_CONST_NAME") +@Serializable +data class CommentStore( + val title: String, + val content: String, + val mediaId: Int? = null, + val commentId: Int? = null, + val time: Long = System.currentTimeMillis(), +) : java.io.Serializable { + companion object { + + @Suppress("INAPPROPRIATE_CONST_NAME") + private const val serialVersionUID = 1L + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/notifications/MediaNameFetch.kt b/app/src/main/java/ani/dantotsu/notifications/comment/MediaNameFetch.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/notifications/MediaNameFetch.kt rename to app/src/main/java/ani/dantotsu/notifications/comment/MediaNameFetch.kt index 47345104..15e0e7e6 100644 --- a/app/src/main/java/ani/dantotsu/notifications/MediaNameFetch.kt +++ b/app/src/main/java/ani/dantotsu/notifications/comment/MediaNameFetch.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.notifications +package ani.dantotsu.notifications.comment import ani.dantotsu.client import com.google.gson.Gson diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 04b50c54..5ef0adf1 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -82,6 +82,9 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } private fun getDub(): Boolean { + if (sourceLanguage >= extension.sources.size) { + sourceLanguage = extension.sources.size - 1 + } val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource ?: return false currContext()?.let { context -> @@ -103,6 +106,9 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } fun setDub(setDub: Boolean) { + if (sourceLanguage >= extension.sources.size) { + sourceLanguage = extension.sources.size - 1 + } val configurableSource = extension.sources[sourceLanguage] as? ConfigurableAnimeSource ?: return val type = when (setDub) { diff --git a/app/src/main/java/ani/dantotsu/profile/ChartItem.kt b/app/src/main/java/ani/dantotsu/profile/ChartItem.kt index 604f9913..98b2889e 100644 --- a/app/src/main/java/ani/dantotsu/profile/ChartItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/ChartItem.kt @@ -7,7 +7,10 @@ import ani.dantotsu.databinding.ItemChartBinding import com.github.aachartmodel.aainfographics.aachartcreator.AAChartView import com.github.aachartmodel.aainfographics.aachartcreator.AAMoveOverEventMessageModel import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions +import com.xwray.groupie.OnItemClickListener +import com.xwray.groupie.OnItemLongClickListener import com.xwray.groupie.viewbinding.BindableItem +import com.xwray.groupie.viewbinding.GroupieViewHolder class ChartItem( private val title: String, @@ -31,6 +34,7 @@ class ChartItem( ) { } } + binding.chartView.setLayerType(View.LAYER_TYPE_SOFTWARE, null) binding.chartView.callBack = callback binding.chartView.reload() binding.chartView.aa_drawChartWithChartOptions(aaOptions) @@ -49,4 +53,32 @@ class ChartItem( override fun initializeViewBinding(view: View): ItemChartBinding { return ItemChartBinding.bind(view) } + + override fun bind(viewHolder: GroupieViewHolder, position: Int) { + viewHolder.setIsRecyclable(false) + super.bind(viewHolder, position) + } + + override fun bind( + viewHolder: GroupieViewHolder, + position: Int, + payloads: MutableList + ) { + viewHolder.setIsRecyclable(false) + super.bind(viewHolder, position, payloads) + } + + override fun bind( + viewHolder: GroupieViewHolder, + position: Int, + payloads: MutableList, + onItemClickListener: OnItemClickListener?, + onItemLongClickListener: OnItemLongClickListener? + ) { + viewHolder.setIsRecyclable(false) + super.bind(viewHolder, position, payloads, onItemClickListener, onItemLongClickListener) + } + override fun getViewType(): Int { + return 0 + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt index c57f07d0..c6281ca6 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt @@ -68,8 +68,11 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene binding.profileViewPager.isUserInputEnabled = false lifecycleScope.launch(Dispatchers.IO) { - val userid = intent.getIntExtra("userId", 0) - val respond = Anilist.query.getUserProfile(userid) + val userid = intent.getIntExtra("userId", -1) + val username = intent.getStringExtra("username") ?: "" + val respond = + if (userid != -1) Anilist.query.getUserProfile(userid) else + Anilist.query.getUserProfile(username) val user = respond?.data?.user if (user == null) { toast("User not found") diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt index c968b04d..cb441cba 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt @@ -65,7 +65,7 @@ class ProfileFragment : Fragment() { binding.profileUserBio.setInitialScale(1) val styledHtml = getFullAniHTML( user.about ?: "", - activity.getColor(R.color.bg_opp) + ContextCompat.getColor(activity, R.color.bg_opp) ) binding.profileUserBio.loadDataWithBaseURL( null, @@ -76,7 +76,7 @@ class ProfileFragment : Fragment() { ) binding.profileUserBio.setBackgroundColor( ContextCompat.getColor( - requireContext(), + activity, android.R.color.transparent ) ) @@ -86,7 +86,7 @@ class ProfileFragment : Fragment() { super.onPageFinished(view, url) binding.profileUserBio.setBackgroundColor( ContextCompat.getColor( - requireContext(), + activity, android.R.color.transparent ) ) @@ -146,7 +146,7 @@ class ProfileFragment : Fragment() { } binding.profileFavStaffRecycler.adapter = AuthorAdapter(favStaff) binding.profileFavStaffRecycler.layoutManager = LinearLayoutManager( - requireContext(), + activity, LinearLayoutManager.HORIZONTAL, false ) @@ -155,7 +155,7 @@ class ProfileFragment : Fragment() { } binding.profileFavCharactersRecycler.adapter = CharacterAdapter(favCharacter) binding.profileFavCharactersRecycler.layoutManager = LinearLayoutManager( - requireContext(), + activity, LinearLayoutManager.HORIZONTAL, false ) @@ -177,9 +177,9 @@ class ProfileFragment : Fragment() { recyclerView.visibility = View.GONE if (it != null) { if (it.isNotEmpty()) { - recyclerView.adapter = MediaAdaptor(0, it, requireActivity(), fav=true) + recyclerView.adapter = MediaAdaptor(0, it, activity, fav=true) recyclerView.layoutManager = LinearLayoutManager( - requireContext(), + activity, LinearLayoutManager.HORIZONTAL, false ) diff --git a/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt b/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt index 1fdeef98..b90cba33 100644 --- a/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt @@ -51,9 +51,9 @@ class StatsFragment : user = arguments?.getSerializable("user") as Query.UserProfile binding.statisticList.adapter = adapter - binding.statisticList.setHasFixedSize(true) + binding.statisticList.recycledViewPool.setMaxRecycledViews(0, 0) binding.statisticList.isNestedScrollingEnabled = true - binding.statisticList.layoutManager = LinearLayoutManager(requireContext()) + binding.statisticList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) binding.statisticProgressBar.visibility = View.VISIBLE binding.compare.visibility = if (user.id == Anilist.userid) View.GONE else View.VISIBLE binding.filterContainer.updateLayoutParams { topMargin = statusBarHeight } @@ -104,9 +104,15 @@ class StatsFragment : binding.filterContainer.visibility = View.GONE } + override fun onPause() { + super.onPause() + binding.statisticList.visibility = View.GONE + } + override fun onResume() { super.onResume() if (this::binding.isInitialized) { + binding.statisticList.visibility = View.VISIBLE binding.root.requestLayout() if (!loadedFirstTime) { activity.lifecycleScope.launch { diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt index d810258b..ef50027e 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt @@ -79,6 +79,10 @@ class ActivityItemBuilder { NotificationType.MEDIA_DELETION -> { "${notification.deletedMediaTitle} has been deleted from the site" } + + NotificationType.COMMENT_REPLY -> { + notification.context ?: "You should not see this" + } } } diff --git a/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt b/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt index f13d93e1..7ef09eb5 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt @@ -19,6 +19,7 @@ import ani.dantotsu.databinding.FragmentFeedBinding import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import com.xwray.groupie.GroupieAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -80,7 +81,8 @@ class FeedFragment : Fragment() { binding.listRecyclerView.setOnTouchListener { _, event -> if (event?.action == MotionEvent.ACTION_UP) { if (activityList.size % AnilistQueries.ITEMS_PER_PAGE != 0 && !global) { - snackString("No more activities") + //snackString("No more activities") fix spam? + Logger.log("No more activities") } else if (!scrollView.canScrollVertically(1) && !binding.feedRefresh.isVisible && binding.listRecyclerView.adapter!!.itemCount != 0 && (binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1) diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt index 01dac01f..4c21bd48 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt @@ -18,6 +18,7 @@ import ani.dantotsu.databinding.ActivityFollowBinding import ani.dantotsu.initActivity import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.navBarHeight +import ani.dantotsu.notifications.comment.CommentStore import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName @@ -29,6 +30,7 @@ import com.xwray.groupie.GroupieAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.util.Locale class NotificationActivity : AppCompatActivity() { private lateinit var binding: ActivityFollowBinding @@ -73,7 +75,25 @@ class NotificationActivity : AppCompatActivity() { notifications.filter { it.id == activityId } } else { notifications + }.toMutableList() + val commentStore = PrefManager.getNullableVal>( + PrefName.CommentNotificationStore, + null + ) ?: listOf() + commentStore.forEach { + val notification = Notification( + "COMMENT_REPLY", + System.currentTimeMillis().toInt(), + commentId = it.commentId, + notificationType = "COMMENT_REPLY", + mediaId = it.mediaId, + context = it.title + "\n" + it.content, + createdAt = (it.time / 1000L).toInt(), + ) + notificationList = notificationList + notification } + notificationList = notificationList.sortedByDescending { it.createdAt } + adapter.update(notificationList.map { NotificationItem(it, ::onNotificationClick) }) } withContext(Dispatchers.Main) { @@ -81,7 +101,8 @@ class NotificationActivity : AppCompatActivity() { binding.listRecyclerView.setOnTouchListener { _, event -> if (event?.action == MotionEvent.ACTION_UP) { if (adapter.itemCount % AnilistQueries.ITEMS_PER_PAGE != 0) { - snackString("No more notifications") + //snackString("No more notifications") fix spam? + Logger.log("No more notifications") } else if (!binding.listRecyclerView.canScrollVertically(1) && !binding.followRefresh.isVisible && binding.listRecyclerView.adapter!!.itemCount != 0 && (binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1) @@ -105,7 +126,6 @@ class NotificationActivity : AppCompatActivity() { } } } - private fun loadPage(onFinish: () -> Unit = {}) { lifecycleScope.launch(Dispatchers.IO) { val res = Anilist.query.getNotifications(Anilist.userid ?: 0, page) @@ -120,7 +140,7 @@ class NotificationActivity : AppCompatActivity() { } } - private fun onNotificationClick(id: Int, type: NotificationClickType) { + private fun onNotificationClick(id: Int, optional: Int?, type: NotificationClickType) { when (type) { NotificationClickType.USER -> { ContextCompat.startActivity( @@ -143,6 +163,16 @@ class NotificationActivity : AppCompatActivity() { ) } + NotificationClickType.COMMENT -> { + ContextCompat.startActivity(this, Intent(this, MediaDetailsActivity::class.java) + .putExtra("FRAGMENT_TO_LOAD", "COMMENTS") + .putExtra("mediaId", id) + .putExtra("commentId", optional ?: -1), + null + ) + + } + NotificationClickType.UNDEFINED -> { // Do nothing } @@ -151,7 +181,7 @@ class NotificationActivity : AppCompatActivity() { companion object { enum class NotificationClickType { - USER, MEDIA, ACTIVITY, UNDEFINED + USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt index cc9a5270..961d4c1a 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt @@ -14,7 +14,7 @@ import com.xwray.groupie.viewbinding.BindableItem class NotificationItem( private val notification: Notification, - val clickCallback: (Int, NotificationClickType) -> Unit + val clickCallback: (Int, Int?, NotificationClickType) -> Unit ) : BindableItem() { private lateinit var binding: ItemNotificationBinding override fun bind(viewBinding: ItemNotificationBinding, position: Int) { @@ -31,7 +31,7 @@ class NotificationItem( return ItemNotificationBinding.bind(view) } - private fun image(user: Boolean = false) { + private fun image(user: Boolean = false, commentNotification: Boolean = false) { val cover = if (user) notification.user?.bannerImage ?: notification.user?.avatar?.medium else notification.media?.bannerImage @@ -52,7 +52,13 @@ class NotificationItem( binding.notificationCover.visibility = View.GONE binding.notificationCoverUser.visibility = View.VISIBLE binding.notificationCoverUserContainer.visibility = View.VISIBLE - binding.notificationCoverUser.loadImage(notification.user?.avatar?.large) + if (commentNotification) { + binding.notificationCoverUser.setImageResource(R.drawable.ic_dantotsu_round) + binding.notificationCoverUser.scaleX = 1.4f + binding.notificationCoverUser.scaleY = 1.4f + } else { + binding.notificationCoverUser.loadImage(notification.user?.avatar?.large) + } binding.notificationBannerImage.layoutParams.height = userHeight } else { binding.notificationCover.visibility = View.VISIBLE @@ -75,12 +81,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.activityId ?: 0, NotificationClickType.ACTIVITY + notification.activityId ?: 0, null, NotificationClickType.ACTIVITY ) } } @@ -90,12 +96,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.activityId ?: 0, NotificationClickType.ACTIVITY + notification.activityId ?: 0, null, NotificationClickType.ACTIVITY ) } } @@ -105,12 +111,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.userId ?: 0, NotificationClickType.USER + notification.userId ?: 0, null, NotificationClickType.USER ) } } @@ -120,12 +126,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.activityId ?: 0, NotificationClickType.ACTIVITY + notification.activityId ?: 0, null, NotificationClickType.ACTIVITY ) } } @@ -135,12 +141,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } } @@ -150,12 +156,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } } @@ -165,12 +171,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } } @@ -180,7 +186,7 @@ class NotificationItem( image() binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.media?.id ?: 0, NotificationClickType.MEDIA + notification.media?.id ?: 0, null, NotificationClickType.MEDIA ) } } @@ -190,12 +196,12 @@ class NotificationItem( binding.notificationCover.loadImage(notification.user?.avatar?.large) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.activityId ?: 0, NotificationClickType.ACTIVITY + notification.activityId ?: 0, null, NotificationClickType.ACTIVITY ) } } @@ -205,12 +211,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.activityId ?: 0, NotificationClickType.ACTIVITY + notification.activityId ?: 0, null, NotificationClickType.ACTIVITY ) } } @@ -220,12 +226,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } } @@ -235,12 +241,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } } @@ -250,12 +256,12 @@ class NotificationItem( image(true) binding.notificationCoverUser.setOnClickListener { clickCallback( - notification.user?.id ?: 0, NotificationClickType.USER + notification.user?.id ?: 0, null, NotificationClickType.USER ) } binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.activityId ?: 0, NotificationClickType.ACTIVITY + notification.activityId ?: 0, null, NotificationClickType.ACTIVITY ) } } @@ -265,7 +271,7 @@ class NotificationItem( image() binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.media?.id ?: 0, NotificationClickType.MEDIA + notification.media?.id ?: 0, null, NotificationClickType.MEDIA ) } } @@ -275,7 +281,7 @@ class NotificationItem( image() binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.media?.id ?: 0, NotificationClickType.MEDIA + notification.media?.id ?: 0, null, NotificationClickType.MEDIA ) } } @@ -285,7 +291,7 @@ class NotificationItem( image() binding.notificationBannerImage.setOnClickListener { clickCallback( - notification.media?.id ?: 0, NotificationClickType.MEDIA + notification.media?.id ?: 0, null, NotificationClickType.MEDIA ) } } @@ -293,6 +299,17 @@ class NotificationItem( NotificationType.MEDIA_DELETION -> { binding.notificationCover.visibility = View.GONE } + + NotificationType.COMMENT_REPLY -> { + image(user = true, commentNotification = true) + if (notification.commentId != null && notification.mediaId != null) { + binding.notificationBannerImage.setOnClickListener { + clickCallback( + notification.mediaId, notification.commentId, NotificationClickType.COMMENT + ) + } + } + } } } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 1df64f08..907ba068 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -47,7 +47,7 @@ import ani.dantotsu.initActivity import ani.dantotsu.loadImage import ani.dantotsu.util.Logger import ani.dantotsu.navBarHeight -import ani.dantotsu.notifications.CommentNotificationWorker +import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker import ani.dantotsu.openLinkInBrowser import ani.dantotsu.others.AppUpdater diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index a429392b..9b2beb3c 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -3,6 +3,7 @@ package ani.dantotsu.settings.saving import android.graphics.Color import ani.dantotsu.connections.comments.AuthResponse import ani.dantotsu.connections.mal.MAL +import ani.dantotsu.notifications.comment.CommentStore import ani.dantotsu.settings.saving.internal.Location import ani.dantotsu.settings.saving.internal.Pref @@ -171,6 +172,8 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files CommentTokenExpiry(Pref(Location.Irrelevant, Long::class, 0L)), LogToFile(Pref(Location.Irrelevant, Boolean::class, false)), RecentGlobalNotification(Pref(Location.Irrelevant, Int::class, 0)), + CommentNotificationStore(Pref(Location.Irrelevant, List::class, listOf())), + UnreadCommentNotifications(Pref(Location.Irrelevant, Int::class, 0)), //Protected DiscordToken(Pref(Location.Protected, String::class, "")), diff --git a/app/src/main/java/ani/dantotsu/util/Logger.kt b/app/src/main/java/ani/dantotsu/util/Logger.kt index a6780748..22c0000c 100644 --- a/app/src/main/java/ani/dantotsu/util/Logger.kt +++ b/app/src/main/java/ani/dantotsu/util/Logger.kt @@ -2,6 +2,7 @@ package ani.dantotsu.util import android.content.Context import android.content.Intent +import android.os.Build import android.util.Log import androidx.core.content.FileProvider import ani.dantotsu.BuildConfig @@ -31,8 +32,8 @@ object Logger { } file?.writeText("log started\n") file?.appendText("date/time: ${Date()}\n") - file?.appendText("device: ${android.os.Build.MODEL}\n") - file?.appendText("os version: ${android.os.Build.VERSION.RELEASE}\n") + file?.appendText("device: ${Build.MODEL}\n") + file?.appendText("os version: ${Build.VERSION.RELEASE}\n") file?.appendText( "app version: ${ context.packageManager.getPackageInfo( @@ -46,29 +47,35 @@ object Logger { context.packageManager.getPackageInfo( context.packageName, 0 - ).versionCode + ).run { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + longVersionCode + else + @Suppress("DEPRECATION") versionCode + + } }\n" ) - file?.appendText("sdk version: ${android.os.Build.VERSION.SDK_INT}\n") - file?.appendText("manufacturer: ${android.os.Build.MANUFACTURER}\n") - file?.appendText("brand: ${android.os.Build.BRAND}\n") - file?.appendText("product: ${android.os.Build.PRODUCT}\n") - file?.appendText("device: ${android.os.Build.DEVICE}\n") - file?.appendText("hardware: ${android.os.Build.HARDWARE}\n") - file?.appendText("host: ${android.os.Build.HOST}\n") - file?.appendText("id: ${android.os.Build.ID}\n") - file?.appendText("type: ${android.os.Build.TYPE}\n") - file?.appendText("user: ${android.os.Build.USER}\n") - file?.appendText("tags: ${android.os.Build.TAGS}\n") - file?.appendText("time: ${android.os.Build.TIME}\n") - file?.appendText("radio: ${android.os.Build.RADIO}\n") - file?.appendText("bootloader: ${android.os.Build.BOOTLOADER}\n") - file?.appendText("board: ${android.os.Build.BOARD}\n") - file?.appendText("fingerprint: ${android.os.Build.FINGERPRINT}\n") - file?.appendText("supported_abis: ${android.os.Build.SUPPORTED_ABIS.joinToString()}\n") - file?.appendText("supported_32_bit_abis: ${android.os.Build.SUPPORTED_32_BIT_ABIS.joinToString()}\n") - file?.appendText("supported_64_bit_abis: ${android.os.Build.SUPPORTED_64_BIT_ABIS.joinToString()}\n") - file?.appendText("is emulator: ${android.os.Build.FINGERPRINT.contains("generic")}\n") + file?.appendText("sdk version: ${Build.VERSION.SDK_INT}\n") + file?.appendText("manufacturer: ${Build.MANUFACTURER}\n") + file?.appendText("brand: ${Build.BRAND}\n") + file?.appendText("product: ${Build.PRODUCT}\n") + file?.appendText("device: ${Build.DEVICE}\n") + file?.appendText("hardware: ${Build.HARDWARE}\n") + file?.appendText("host: ${Build.HOST}\n") + file?.appendText("id: ${Build.ID}\n") + file?.appendText("type: ${Build.TYPE}\n") + file?.appendText("user: ${Build.USER}\n") + file?.appendText("tags: ${Build.TAGS}\n") + file?.appendText("time: ${Build.TIME}\n") + file?.appendText("radio: ${Build.getRadioVersion()}\n") + file?.appendText("bootloader: ${Build.BOOTLOADER}\n") + file?.appendText("board: ${Build.BOARD}\n") + file?.appendText("fingerprint: ${Build.FINGERPRINT}\n") + file?.appendText("supported_abis: ${Build.SUPPORTED_ABIS.joinToString()}\n") + file?.appendText("supported_32_bit_abis: ${Build.SUPPORTED_32_BIT_ABIS.joinToString()}\n") + file?.appendText("supported_64_bit_abis: ${Build.SUPPORTED_64_BIT_ABIS.joinToString()}\n") + file?.appendText("is emulator: ${Build.FINGERPRINT.contains("generic")}\n") file?.appendText("--------------------------------\n") } catch (e: Exception) { Injekt.get().logException(e) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index 5db7ff8f..2e4ff107 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -147,7 +147,7 @@ object Notifications { setGroup(GROUP_COMMENTS) }, buildNotificationChannel(CHANNEL_COMMENT_WARING, IMPORTANCE_HIGH) { - setName("CommentNotificationWorker Warnings") + setName("Comment Warnings") setGroup(GROUP_COMMENTS) }, buildNotificationChannel(CHANNEL_ANILIST, IMPORTANCE_DEFAULT) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 231443bb..280af466 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -14,6 +14,7 @@ import android.os.PowerManager import android.util.TypedValue import androidx.annotation.AttrRes import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat import androidx.core.content.PermissionChecker import androidx.core.content.getSystemService import androidx.core.graphics.alpha @@ -85,7 +86,7 @@ fun Context.getThemeColor(attr: Int): Int { val tv = TypedValue() return if (this.theme.resolveAttribute(attr, tv, true)) { if (tv.resourceId != 0) { - getColor(tv.resourceId) + ContextCompat.getColor(this, tv.resourceId) } else { tv.data } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt index 67ce5a03..53049e4b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt @@ -10,6 +10,7 @@ import androidx.core.app.NotificationChannelGroupCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat.NotificationWithIdAndTag +import androidx.core.content.ContextCompat import androidx.core.content.PermissionChecker import androidx.core.content.getSystemService @@ -65,7 +66,7 @@ fun Context.notificationBuilder( block: (NotificationCompat.Builder.() -> Unit)? = null ): NotificationCompat.Builder { val builder = NotificationCompat.Builder(this, channelId) - .setColor(getColor(android.R.color.holo_blue_dark)) + .setColor(ContextCompat.getColor(this, android.R.color.holo_blue_dark)) if (block != null) { builder.block() } diff --git a/app/src/main/res/layout/fragment_statistics.xml b/app/src/main/res/layout/fragment_statistics.xml index 9f202f6d..dafdaeae 100644 --- a/app/src/main/res/layout/fragment_statistics.xml +++ b/app/src/main/res/layout/fragment_statistics.xml @@ -125,6 +125,7 @@ android:id="@+id/statisticList" android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="vertical" android:nestedScrollingEnabled="true" tools:listitem="@layout/item_chart" /> diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml new file mode 100644 index 00000000..8729e48d --- /dev/null +++ b/app/src/main/res/values-v23/themes.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 504f52b4..b5fe8d03 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -486,6 +486,7 @@ Read on Dantotsu Watch on Dantotsu + View Profile in Dantotsu "Continue : Episode " "Continue : " "Episode " diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 87eb6f03..ececfa8f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -29,10 +29,6 @@ - -