diff --git a/app/build.gradle b/app/build.gradle index 4064fb4e..b4ab706b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,11 +75,11 @@ android { dependencies { - // FireBase +// FireBase googleImplementation platform('com.google.firebase:firebase-bom:32.7.4') googleImplementation 'com.google.firebase:firebase-analytics-ktx:21.5.1' googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:18.6.2' -// Core +// Core implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.browser:browser:1.8.0' implementation 'androidx.core:core-ktx:1.12.0' @@ -96,7 +96,7 @@ dependencies { implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.webkit:webkit:1.10.0' -// Glide +// Glide ext.glide_version = '4.16.0' api "com.github.bumptech.glide:glide:$glide_version" implementation "com.github.bumptech.glide:glide:$glide_version" @@ -104,7 +104,7 @@ dependencies { implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version" implementation 'jp.wasabeef:glide-transformations:4.3.0' -// Exoplayer +// Exoplayer ext.exo_version = '1.3.0' implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-ui:$exo_version" @@ -112,14 +112,13 @@ dependencies { implementation "androidx.media3:media3-exoplayer-dash:$exo_version" implementation "androidx.media3:media3-datasource-okhttp:$exo_version" implementation "androidx.media3:media3-session:$exo_version" - //media3 casting +// Media3 Casting implementation "androidx.media3:media3-cast:$exo_version" - implementation "androidx.mediarouter:mediarouter:1.6.0" + implementation "androidx.mediarouter:mediarouter:1.7.0" -// UI +// UI implementation 'com.google.android.material:material:1.11.0' - //implementation 'nl.joery.animatedbottombar:library:1.1.0' - implementation 'com.github.rebelonion:AnimatedBottomBar:v1.1.0' + implementation 'com.github.RepoDevil:AnimatedBottomBar:7fcb9af' implementation 'com.flaviofaria:kenburnsview:1.0.7' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.alexvasilkov:gesture-views:2.8.3' @@ -128,7 +127,7 @@ dependencies { implementation 'com.github.eltos:simpledialogfragments:v3.7' implementation 'com.github.AAChartModel:AAChartCore-Kotlin:93972bc' - // Markwon +// Markwon ext.markwon_version = '4.6.2' implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:editor:$markwon_version" @@ -138,15 +137,15 @@ dependencies { implementation "io.noties.markwon:html:$markwon_version" implementation "io.noties.markwon:image-glide:$markwon_version" -// Groupie +// Groupie ext.groupie_version = '2.10.1' implementation "com.github.lisawray.groupie:groupie:$groupie_version" implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version" -// string matching +// String Matching implementation 'me.xdrop:fuzzywuzzy:1.4.0' -// Aniyomi +// Aniyomi implementation 'io.reactivex:rxjava:1.3.8' implementation 'io.reactivex:rxandroid:1.2.1' implementation 'ru.beryukhov:flowreactivenetwork:1.0.4' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cf650323..7cc2e732 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,7 +19,7 @@ + android:maxSdkVersion="29" /> @@ -53,11 +53,13 @@ android:label="@string/app_name" android:largeHeap="true" android:requestLegacyExternalStorage="true" + android:enableOnBackInvokedCallback="true" android:roundIcon="${icon_placeholder_round}" android:supportsRtl="true" android:theme="@style/Theme.Dantotsu" android:usesCleartextTraffic="true" - tools:ignore="AllowBackup"> + tools:ignore="AllowBackup" + tools:targetApi="tiramisu"> @@ -117,6 +119,7 @@ - @@ -354,11 +353,7 @@ - (context: Context, layoutId: Int, items: List) : ArrayAdapter(context, layoutId, items) { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 23d8e508..8a45b6e3 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -5,7 +5,6 @@ import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Intent import android.content.res.Configuration -import android.content.res.Resources import android.graphics.drawable.Animatable import android.graphics.drawable.GradientDrawable import android.net.Uri @@ -14,7 +13,6 @@ import android.os.Bundle 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 @@ -161,16 +159,16 @@ class MainActivity : AppCompatActivity() { } } - val _bottomBar = findViewById(R.id.navbar) + val bottomNavBar = findViewById(R.id.navbar) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val backgroundDrawable = _bottomBar.background as GradientDrawable + val backgroundDrawable = bottomNavBar.background as GradientDrawable val currentColor = backgroundDrawable.color?.defaultColor ?: 0 val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xF9000000.toInt() backgroundDrawable.setColor(semiTransparentColor) - _bottomBar.background = backgroundDrawable + bottomNavBar.background = backgroundDrawable } - _bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) + bottomNavBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) val offset = try { val statusBarHeightId = resources.getIdentifier("status_bar_height", "dimen", "android") @@ -339,7 +337,7 @@ class MainActivity : AppCompatActivity() { startActivity(Intent(this, NoInternet::class.java)) } else { val model: AnilistHomeViewModel by viewModels() - model.genres.observe(this) { it -> + model.genres.observe(this) { if (it != null) { if (it) { val navbar = binding.includedNavbar.navbar @@ -364,7 +362,7 @@ class MainActivity : AppCompatActivity() { mainViewPager.setCurrentItem(newIndex, false) } }) - if (mainViewPager.getCurrentItem() != selectedOption) { + if (mainViewPager.currentItem != selectedOption) { navbar.selectTabAt(selectedOption) mainViewPager.post { mainViewPager.setCurrentItem( @@ -467,18 +465,12 @@ class MainActivity : AppCompatActivity() { window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent) } - private val Int.toPx get() = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics - ).toInt() - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) + val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 8 else 32 val params : ViewGroup.MarginLayoutParams = binding.includedNavbar.navbar.layoutParams as ViewGroup.MarginLayoutParams - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) - params.updateMargins(bottom = 8.toPx) - else - params.updateMargins(bottom = 32.toPx) + params.updateMargins(bottom = margin.toPx) } private fun passwordAlertDialog(callback: (CharArray?) -> Unit) { diff --git a/app/src/main/java/ani/dantotsu/Network.kt b/app/src/main/java/ani/dantotsu/Network.kt index aa71cb48..91840b3e 100644 --- a/app/src/main/java/ani/dantotsu/Network.kt +++ b/app/src/main/java/ani/dantotsu/Network.kt @@ -1,6 +1,5 @@ package ani.dantotsu -import android.content.Context import android.os.Build import androidx.fragment.app.FragmentActivity import ani.dantotsu.others.webview.CloudFlare @@ -35,7 +34,7 @@ lateinit var defaultHeaders: Map lateinit var okHttpClient: OkHttpClient lateinit var client: Requests -fun initializeNetwork(context: Context) { +fun initializeNetwork() { val networkHelper = Injekt.get() 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 c83fd07f..b0088286 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -387,6 +387,7 @@ class AnilistQueries { returnArray.addAll(map.values) return returnArray } + @Suppress("UNCHECKED_CAST") val list = PrefManager.getNullableCustomVal( "continueAnimeList", listOf(), @@ -544,6 +545,7 @@ class AnilistQueries { returnMap["current$type"] = returnArray return } + @Suppress("UNCHECKED_CAST") val list = PrefManager.getNullableCustomVal( "continueAnimeList", listOf(), @@ -573,6 +575,7 @@ class AnilistQueries { subMap[m.id] = m } } + @Suppress("UNCHECKED_CAST") val list = PrefManager.getNullableCustomVal( "continueAnimeList", listOf(), @@ -734,7 +737,7 @@ class AnilistQueries { } sorted["All"] = all - val listSort: String = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder) + val listSort: String? = if (anime) PrefManager.getVal(PrefName.AnimeListSortOrder) else PrefManager.getVal(PrefName.MangaListSortOrder) val sort = listSort ?: sortOrder ?: options?.rowOrder for (i in sorted.keys) { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt index 5eb28a79..a4092b91 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt @@ -112,8 +112,8 @@ class AnilistHomeViewModel : ViewModel() { suspend fun loadMain(context: FragmentActivity) { Anilist.getSavedToken() - MAL.getSavedToken(context) - Discord.getSavedToken(context) + MAL.getSavedToken() + Discord.getSavedToken() if (!BuildConfig.FLAVOR.contains("fdroid")) { if (PrefManager.getVal(PrefName.CheckUpdate)) AppUpdater.check(context) } @@ -159,7 +159,7 @@ class AnilistAnimeViewModel : ViewModel() { fun getPopular(): LiveData = animePopular suspend fun loadPopular( type: String, - search_val: String? = null, + searchVal: String? = null, genres: ArrayList? = null, sort: String = Anilist.sortBy[1], onList: Boolean = true, @@ -167,7 +167,7 @@ class AnilistAnimeViewModel : ViewModel() { animePopular.postValue( Anilist.query.search( type, - search = search_val, + search = searchVal, onList = if (onList) null else false, sort = sort, genres = genres @@ -231,7 +231,7 @@ class AnilistMangaViewModel : ViewModel() { fun getPopular(): LiveData = mangaPopular suspend fun loadPopular( type: String, - search_val: String? = null, + searchVal: String? = null, genres: ArrayList? = null, sort: String = Anilist.sortBy[1], onList: Boolean = true, @@ -239,7 +239,7 @@ class AnilistMangaViewModel : ViewModel() { mangaPopular.postValue( Anilist.query.search( type, - search = search_val, + search = searchVal, onList = if (onList) null else false, sort = sort, genres = genres diff --git a/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt b/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt index ea34f1bf..0a2f7b3f 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/Discord.kt @@ -20,14 +20,14 @@ object Discord { var avatar: String? = null - fun getSavedToken(context: Context): Boolean { + fun getSavedToken(): Boolean { token = PrefManager.getVal( PrefName.DiscordToken, null as String? ) return token != null } - fun saveToken(context: Context, token: String) { + fun saveToken(token: String) { PrefManager.setVal(PrefName.DiscordToken, token) } diff --git a/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt b/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt index 0a29ab60..7f2c23b0 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/DiscordService.kt @@ -5,16 +5,12 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service -import android.content.ContentValues import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import android.os.Build -import android.os.Environment import android.os.IBinder import android.os.PowerManager -import android.provider.MediaStore import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -37,7 +33,6 @@ import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener import java.io.File -import java.io.OutputStreamWriter class DiscordService : Service() { private var heartbeat: Int = 0 @@ -162,8 +157,8 @@ class DiscordService : Service() { inner class DiscordWebSocketListener : WebSocketListener() { - var retryAttempts = 0 - val maxRetryAttempts = 10 + private var retryAttempts = 0 + private val maxRetryAttempts = 10 override fun onOpen(webSocket: WebSocket, response: Response) { super.onOpen(webSocket, response) this@DiscordService.webSocket = webSocket @@ -232,7 +227,7 @@ class DiscordService : Service() { resume() resume = false } else { - identify(webSocket, baseContext) + identify(webSocket) log("WebSocket: Identified") } } @@ -245,13 +240,13 @@ class DiscordService : Service() { } } - fun identify(webSocket: WebSocket, context: Context) { + private fun identify(webSocket: WebSocket) { val properties = JsonObject() properties.addProperty("os", "linux") properties.addProperty("browser", "unknown") properties.addProperty("device", "unknown") val d = JsonObject() - d.addProperty("token", getToken(context)) + d.addProperty("token", getToken()) d.addProperty("intents", 0) d.add("properties", properties) val payload = JsonObject() @@ -311,7 +306,7 @@ class DiscordService : Service() { } } - fun getToken(context: Context): String { + fun getToken(): String { val token = PrefManager.getVal(PrefName.DiscordToken, null as String?) return if (token == null) { log("WebSocket: Token not found") @@ -375,10 +370,10 @@ class DiscordService : Service() { log("WebSocket: Simple Test Presence Saved") } - fun setPresence(String: String) { + fun setPresence(string: String) { log("WebSocket: Sending Presence payload") - log(String) - webSocket.send(String) + log(string) + webSocket.send(string) } fun log(string: String) { @@ -388,7 +383,7 @@ class DiscordService : Service() { fun resume() { log("Sending Resume payload") val d = JsonObject() - d.addProperty("token", getToken(baseContext)) + d.addProperty("token", getToken()) d.addProperty("session_id", sessionId) d.addProperty("seq", sequence) val json = JsonObject() @@ -404,8 +399,7 @@ class DiscordService : Service() { Thread.sleep(heartbeat.toLong()) heartbeatSend(webSocket, sequence) log("WebSocket: Heartbeat Sent") - } catch (e: InterruptedException) { - } + } catch (ignored: InterruptedException) { } } } diff --git a/app/src/main/java/ani/dantotsu/connections/discord/Login.kt b/app/src/main/java/ani/dantotsu/connections/discord/Login.kt index 676f11e2..513da55b 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/Login.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/Login.kt @@ -75,7 +75,7 @@ class Login : AppCompatActivity() { } Toast.makeText(this, "Logged in successfully", Toast.LENGTH_SHORT).show() finish() - saveToken(this, token) + saveToken(token) startMainActivity(this@Login) } diff --git a/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt b/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt index 9eaffdca..770d0e05 100644 --- a/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt +++ b/app/src/main/java/ani/dantotsu/connections/mal/MAL.kt @@ -5,7 +5,6 @@ import android.content.Context import android.net.Uri import android.util.Base64 import androidx.browser.customtabs.CustomTabsIntent -import androidx.fragment.app.FragmentActivity import ani.dantotsu.R import ani.dantotsu.client import ani.dantotsu.currContext @@ -64,7 +63,7 @@ object MAL { } - suspend fun getSavedToken(context: FragmentActivity): Boolean { + suspend fun getSavedToken(): Boolean { return tryWithSuspend(false) { var res: ResponseToken = PrefManager.getNullableVal(PrefName.MALToken, null) @@ -77,7 +76,7 @@ object MAL { } ?: false } - fun removeSavedToken(context: Context) { + fun removeSavedToken() { token = null username = null userid = null diff --git a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt index 0cbfa1bc..4eb7f5a3 100644 --- a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt +++ b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt @@ -3,6 +3,7 @@ package ani.dantotsu.download import android.content.Context import android.os.Environment import android.widget.Toast +import ani.dantotsu.media.MediaType import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import com.google.gson.Gson @@ -15,11 +16,11 @@ class DownloadsManager(private val context: Context) { private val downloadsList = loadDownloads().toMutableList() val mangaDownloadedTypes: List - get() = downloadsList.filter { it.type == DownloadedType.Type.MANGA } + get() = downloadsList.filter { it.type == MediaType.MANGA } val animeDownloadedTypes: List - get() = downloadsList.filter { it.type == DownloadedType.Type.ANIME } + get() = downloadsList.filter { it.type == MediaType.ANIME } val novelDownloadedTypes: List - get() = downloadsList.filter { it.type == DownloadedType.Type.NOVEL } + get() = downloadsList.filter { it.type == MediaType.NOVEL } private fun saveDownloads() { val jsonString = gson.toJson(downloadsList) @@ -47,14 +48,8 @@ class DownloadsManager(private val context: Context) { saveDownloads() } - fun removeMedia(title: String, type: DownloadedType.Type) { - val subDirectory = if (type == DownloadedType.Type.MANGA) { - "Manga" - } else if (type == DownloadedType.Type.ANIME) { - "Anime" - } else { - "Novel" - } + fun removeMedia(title: String, type: MediaType) { + val subDirectory = type.asText() val directory = File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$subDirectory/$title" @@ -71,53 +66,45 @@ class DownloadsManager(private val context: Context) { cleanDownloads() } when (type) { - DownloadedType.Type.MANGA -> { - downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.MANGA } + MediaType.MANGA -> { + downloadsList.removeAll { it.title == title && it.type == MediaType.MANGA } } - DownloadedType.Type.ANIME -> { - downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.ANIME } + MediaType.ANIME -> { + downloadsList.removeAll { it.title == title && it.type == MediaType.ANIME } } - DownloadedType.Type.NOVEL -> { - downloadsList.removeAll { it.title == title && it.type == DownloadedType.Type.NOVEL } + MediaType.NOVEL -> { + downloadsList.removeAll { it.title == title && it.type == MediaType.NOVEL } } } saveDownloads() } private fun cleanDownloads() { - cleanDownload(DownloadedType.Type.MANGA) - cleanDownload(DownloadedType.Type.ANIME) - cleanDownload(DownloadedType.Type.NOVEL) + cleanDownload(MediaType.MANGA) + cleanDownload(MediaType.ANIME) + cleanDownload(MediaType.NOVEL) } - private fun cleanDownload(type: DownloadedType.Type) { + private fun cleanDownload(type: MediaType) { // remove all folders that are not in the downloads list - val subDirectory = if (type == DownloadedType.Type.MANGA) { - "Manga" - } else if (type == DownloadedType.Type.ANIME) { - "Anime" - } else { - "Novel" - } + val subDirectory = type.asText() val directory = File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$subDirectory" ) - val downloadsSubLists = if (type == DownloadedType.Type.MANGA) { - mangaDownloadedTypes - } else if (type == DownloadedType.Type.ANIME) { - animeDownloadedTypes - } else { - novelDownloadedTypes + val downloadsSubLists = when (type) { + MediaType.MANGA -> mangaDownloadedTypes + MediaType.ANIME -> animeDownloadedTypes + else -> novelDownloadedTypes } if (directory.exists()) { val files = directory.listFiles() if (files != null) { for (file in files) { if (!downloadsSubLists.any { it.title == file.name }) { - val deleted = file.deleteRecursively() + file.deleteRecursively() } } } @@ -153,7 +140,7 @@ class DownloadsManager(private val context: Context) { return downloadsList.contains(downloadedType) } - fun queryDownload(title: String, chapter: String, type: DownloadedType.Type? = null): Boolean { + fun queryDownload(title: String, chapter: String, type: MediaType? = null): Boolean { return if (type == null) { downloadsList.any { it.title == title && it.chapter == chapter } } else { @@ -162,21 +149,25 @@ class DownloadsManager(private val context: Context) { } private fun removeDirectory(downloadedType: DownloadedType) { - val directory = if (downloadedType.type == DownloadedType.Type.MANGA) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" - ) - } else if (downloadedType.type == DownloadedType.Type.ANIME) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" - ) - } else { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" - ) + val directory = when (downloadedType.type) { + MediaType.MANGA -> { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" + ) + } + MediaType.ANIME -> { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" + ) + } + else -> { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" + ) + } } // Check if the directory exists and delete it recursively @@ -193,21 +184,25 @@ class DownloadsManager(private val context: Context) { } fun exportDownloads(downloadedType: DownloadedType) { //copies to the downloads folder available to the user - val directory = if (downloadedType.type == DownloadedType.Type.MANGA) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" - ) - } else if (downloadedType.type == DownloadedType.Type.ANIME) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" - ) - } else { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" - ) + val directory = when (downloadedType.type) { + MediaType.MANGA -> { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "Dantotsu/Manga/${downloadedType.title}/${downloadedType.chapter}" + ) + } + MediaType.ANIME -> { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "Dantotsu/Anime/${downloadedType.title}/${downloadedType.chapter}" + ) + } + else -> { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "Dantotsu/Novel/${downloadedType.title}/${downloadedType.chapter}" + ) + } } val destination = File( context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), @@ -225,13 +220,17 @@ class DownloadsManager(private val context: Context) { } } - fun purgeDownloads(type: DownloadedType.Type) { - val directory = if (type == DownloadedType.Type.MANGA) { - File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga") - } else if (type == DownloadedType.Type.ANIME) { - File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime") - } else { - File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel") + fun purgeDownloads(type: MediaType) { + val directory = when (type) { + MediaType.MANGA -> { + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Manga") + } + MediaType.ANIME -> { + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Anime") + } + else -> { + File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/Novel") + } } if (directory.exists()) { val deleted = directory.deleteRecursively() @@ -255,56 +254,53 @@ class DownloadsManager(private val context: Context) { fun getDirectory( context: Context, - type: DownloadedType.Type, + type: MediaType, title: String, chapter: String? = null ): File { - return if (type == DownloadedType.Type.MANGA) { - if (chapter != null) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "$mangaLocation/$title/$chapter" - ) - } else { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "$mangaLocation/$title" - ) + return when (type) { + MediaType.MANGA -> { + if (chapter != null) { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "$mangaLocation/$title/$chapter" + ) + } else { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "$mangaLocation/$title" + ) + } } - } else if (type == DownloadedType.Type.ANIME) { - if (chapter != null) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "$animeLocation/$title/$chapter" - ) - } else { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "$animeLocation/$title" - ) + MediaType.ANIME -> { + if (chapter != null) { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "$animeLocation/$title/$chapter" + ) + } else { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "$animeLocation/$title" + ) + } } - } else { - if (chapter != null) { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "$novelLocation/$title/$chapter" - ) - } else { - File( - context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), - "$novelLocation/$title" - ) + else -> { + if (chapter != null) { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "$novelLocation/$title/$chapter" + ) + } else { + File( + context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), + "$novelLocation/$title" + ) + } } } } } - } -data class DownloadedType(val title: String, val chapter: String, val type: Type) : Serializable { - enum class Type { - MANGA, - ANIME, - NOVEL - } -} +data class DownloadedType(val title: String, val chapter: String, val type: MediaType) : Serializable 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 22b20dcb..fc498743 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/AnimeDownloaderService.kt @@ -27,14 +27,15 @@ import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.video.ExoplayerDownloadService import ani.dantotsu.download.video.Helper -import ani.dantotsu.util.Logger import ani.dantotsu.media.Media +import ani.dantotsu.media.MediaType import ani.dantotsu.media.SubtitleDownloader import ani.dantotsu.media.anime.AnimeWatchFragment import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Video import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.animesource.model.SAnime @@ -242,7 +243,7 @@ class AnimeDownloaderService : Service() { DownloadedType( task.title, task.episode, - DownloadedType.Type.ANIME, + MediaType.ANIME, ) ) } @@ -273,7 +274,7 @@ class AnimeDownloaderService : Service() { DownloadedType( task.title, task.episode, - DownloadedType.Type.ANIME, + MediaType.ANIME, ) ) Injekt.get().logException( @@ -302,7 +303,7 @@ class AnimeDownloaderService : Service() { DownloadedType( task.title, task.episode, - DownloadedType.Type.ANIME, + MediaType.ANIME, ) ) currentTasks.removeAll { it.getTaskName() == task.getTaskName() } diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt index 63cc384a..3ab53d55 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeAdapter.kt @@ -1,7 +1,6 @@ package ani.dantotsu.download.anime -import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.View @@ -38,7 +37,6 @@ class OfflineAnimeAdapter( return position.toLong() } - @SuppressLint("SetTextI18n") override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view: View = convertView ?: when (style) { @@ -61,14 +59,14 @@ class OfflineAnimeAdapter( if (style == 0) { val bannerView = view.findViewById(R.id.itemCompactBanner) // for large view val episodes = view.findViewById(R.id.itemTotal) - episodes.text = " Episodes" + episodes.text = context.getString(R.string.episodes) bannerView.setImageURI(item.banner ?: item.image) totalepisodes.text = item.totalEpisodeList } else if (style == 1) { val watchedEpisodes = view.findViewById(R.id.itemCompactUserProgress) // for compact view watchedEpisodes.text = item.watchedEpisode - totalepisodes.text = " | " + item.totalEpisode + totalepisodes.text = context.getString(R.string.total_divider, item.totalEpisode) } // Bind item data to the views diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt index 4ff5497c..64329759 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt @@ -22,6 +22,7 @@ import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.core.view.marginBottom import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi @@ -33,15 +34,16 @@ import ani.dantotsu.currContext import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity -import ani.dantotsu.util.Logger import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity +import ani.dantotsu.media.MediaType import ani.dantotsu.navBarHeight import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.textfield.TextInputLayout @@ -187,8 +189,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { gridView.setOnItemLongClickListener { _, _, position, _ -> // Get the OfflineAnimeModel that was clicked val item = adapter.getItem(position) as OfflineAnimeModel - val type: DownloadedType.Type = - DownloadedType.Type.ANIME + val type: MediaType = MediaType.ANIME // Alert dialog to confirm deletion val builder = @@ -250,7 +251,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { val visibility = first != null && first.top < 0 scrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() - scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE + scrollTop.isVisible = visibility } }) initActivity(requireActivity()) @@ -292,11 +293,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { } private fun getMedia(downloadedType: DownloadedType): Media? { - val type = when (downloadedType.type) { - DownloadedType.Type.MANGA -> "Manga" - DownloadedType.Type.ANIME -> "Anime" - else -> "Novel" - } + val type = downloadedType.type.asText() val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$type/${downloadedType.title}" @@ -326,11 +323,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { } private fun loadOfflineAnimeModel(downloadedType: DownloadedType): OfflineAnimeModel { - val type = when (downloadedType.type) { - DownloadedType.Type.MANGA -> "Manga" - DownloadedType.Type.ANIME -> "Anime" - else -> "Novel" - } + val type = downloadedType.type.asText() val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$type/${downloadedType.title}" 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 188c9dc3..9b884883 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -21,8 +21,8 @@ import ani.dantotsu.R import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager -import ani.dantotsu.util.Logger import ani.dantotsu.media.Media +import ani.dantotsu.media.MediaType import ani.dantotsu.media.manga.ImageData import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FAILED import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_FINISHED @@ -30,6 +30,7 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_PROG import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STARTED import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_DOWNLOADER_PROGRESS @@ -211,8 +212,7 @@ class MangaDownloaderService : Service() { while (bitmap == null && retryCount < task.retries) { bitmap = image.fetchAndProcessImage( image.page, - image.source, - this@MangaDownloaderService + image.source ) retryCount++ } @@ -246,7 +246,7 @@ class MangaDownloaderService : Service() { DownloadedType( task.title, task.chapter, - DownloadedType.Type.MANGA + MediaType.MANGA ) ) broadcastDownloadFinished(task.chapter) diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt index 0bc9ef08..8c6aef99 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaAdapter.kt @@ -37,7 +37,6 @@ class OfflineMangaAdapter( return position.toLong() } - @SuppressLint("SetTextI18n") override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val view: View = convertView ?: when (style) { @@ -60,14 +59,14 @@ class OfflineMangaAdapter( if (style == 0) { val bannerView = view.findViewById(R.id.itemCompactBanner) // for large view val chapters = view.findViewById(R.id.itemTotal) - chapters.text = " Chapters" + chapters.text = context.getString(R.string.chapters) bannerView.setImageURI(item.banner ?: item.image) totalChapter.text = item.totalChapter } else if (style == 1) { val readChapter = view.findViewById(R.id.itemCompactUserProgress) // for compact view readChapter.text = item.readChapter - totalChapter.text = " | " + item.totalChapter + totalChapter.text = context.getString(R.string.total_divider, item.totalChapter) } // Bind item data to the views diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index 0c97a8df..99250edf 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -20,6 +20,7 @@ import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.core.view.marginBottom import androidx.fragment.app.Fragment import ani.dantotsu.R @@ -30,15 +31,16 @@ import ani.dantotsu.currContext import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity -import ani.dantotsu.util.Logger import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity +import ani.dantotsu.media.MediaType import ani.dantotsu.navBarHeight import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.SettingsDialogFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.textfield.TextInputLayout @@ -178,11 +180,11 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { gridView.setOnItemLongClickListener { _, _, position, _ -> // Get the OfflineMangaModel that was clicked val item = adapter.getItem(position) as OfflineMangaModel - val type: DownloadedType.Type = + val type: MediaType = if (downloadManager.mangaDownloadedTypes.any { it.title == item.title }) { - DownloadedType.Type.MANGA + MediaType.MANGA } else { - DownloadedType.Type.NOVEL + MediaType.NOVEL } // Alert dialog to confirm deletion val builder = @@ -234,7 +236,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { ) { val first = view.getChildAt(0) val visibility = first != null && first.top < 0 - scrollTop.visibility = if (visibility) View.VISIBLE else View.GONE + scrollTop.isVisible = visibility scrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat() } @@ -288,11 +290,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } private fun getMedia(downloadedType: DownloadedType): Media? { - val type = when (downloadedType.type) { - DownloadedType.Type.MANGA -> "Manga" - DownloadedType.Type.ANIME -> "Anime" - else -> "Novel" - } + val type = downloadedType.type.asText() val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$type/${downloadedType.title}" @@ -316,11 +314,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { } private fun loadOfflineMangaModel(downloadedType: DownloadedType): OfflineMangaModel { - val type = when (downloadedType.type) { - DownloadedType.Type.MANGA -> "Manga" - DownloadedType.Type.ANIME -> "Anime" - else -> "Novel" - } + val type = downloadedType.type.asText() val directory = File( currContext()?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "Dantotsu/$type/${downloadedType.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 0c3575a3..9cb46d31 100644 --- a/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/novel/NovelDownloaderService.kt @@ -20,10 +20,11 @@ import ani.dantotsu.R import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager -import ani.dantotsu.util.Logger import ani.dantotsu.media.Media +import ani.dantotsu.media.MediaType import ani.dantotsu.media.novel.NovelReadFragment import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import com.google.gson.GsonBuilder import com.google.gson.InstanceCreator import eu.kanade.tachiyomi.data.notification.Notifications @@ -335,7 +336,7 @@ class NovelDownloaderService : Service() { DownloadedType( task.title, task.chapter, - DownloadedType.Type.NOVEL + MediaType.NOVEL ) ) broadcastDownloadFinished(task.originalLink) diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt index 4e82e76c..6cb688fa 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -9,7 +9,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build -import android.util.Log import androidx.annotation.OptIn import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat @@ -37,6 +36,7 @@ import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.logError import ani.dantotsu.media.Media +import ani.dantotsu.media.MediaType import ani.dantotsu.okHttpClient import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.SubtitleType @@ -49,13 +49,14 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File import java.io.IOException -import java.util.concurrent.* +import java.util.concurrent.Executors +@SuppressLint("UnsafeOptInUsageError") object Helper { + private var simpleCache: SimpleCache? = null - @SuppressLint("UnsafeOptInUsageError") fun downloadVideo(context: Context, video: Video, subtitle: Subtitle?) { val dataSourceFactory = DataSource.Factory { val dataSource: HttpDataSource = @@ -157,16 +158,14 @@ object Helper { download: Download, finalException: Exception? ) { - if (download.state == Download.STATE_COMPLETED) { - Logger.log("Download Completed") - } else if (download.state == Download.STATE_FAILED) { - Logger.log("Download Failed") - } else if (download.state == Download.STATE_STOPPED) { - Logger.log("Download Stopped") - } else if (download.state == Download.STATE_QUEUED) { - Logger.log("Download Queued") - } else if (download.state == Download.STATE_DOWNLOADING) { - Logger.log("Download Downloading") + when (download.state) { + Download.STATE_COMPLETED -> Logger.log("Download Completed") + Download.STATE_FAILED -> Logger.log("Download Failed") + Download.STATE_STOPPED -> Logger.log("Download Stopped") + Download.STATE_QUEUED -> Logger.log("Download Queued") + Download.STATE_DOWNLOADING -> Logger.log("Download Downloading") + Download.STATE_REMOVING -> Logger.log("Download Removing") + Download.STATE_RESTARTING -> Logger.log("Download Restarting") } } } @@ -220,7 +219,7 @@ object Helper { val downloadsManger = Injekt.get() val downloadCheck = downloadsManger - .queryDownload(title, episode, DownloadedType.Type.ANIME) + .queryDownload(title, episode, MediaType.ANIME) if (downloadCheck) { AlertDialog.Builder(context, R.style.MyPopup) @@ -243,7 +242,7 @@ object Helper { DownloadedType( title, episode, - DownloadedType.Type.ANIME + MediaType.ANIME ) ) AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask) diff --git a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt index 2c60b3ac..43c7db04 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt @@ -10,6 +10,7 @@ import android.view.ViewGroup import android.view.animation.LayoutAnimationController import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.MutableLiveData @@ -21,6 +22,7 @@ import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemAnimePageBinding +import ani.dantotsu.databinding.LayoutTrendingBinding import ani.dantotsu.loadImage import ani.dantotsu.media.CalendarActivity import ani.dantotsu.media.GenreActivity @@ -41,6 +43,7 @@ import com.google.android.material.textfield.TextInputLayout class AnimePageAdapter : RecyclerView.Adapter() { val ready = MutableLiveData(false) lateinit var binding: ItemAnimePageBinding + private lateinit var trendingBinding: LayoutTrendingBinding private var trendHandler: Handler? = null private lateinit var trendRun: Runnable var trendingViewPager: ViewPager2? = null @@ -53,14 +56,15 @@ class AnimePageAdapter : RecyclerView.Adapter(R.id.animeSearchBar) + val textInputLayout = holder.itemView.findViewById(R.id.searchBar) val currentColor = textInputLayout.boxBackgroundColor val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() textInputLayout.boxBackgroundColor = semiTransparentColor val materialCardView = - holder.itemView.findViewById(R.id.animeUserAvatarContainer) + holder.itemView.findViewById(R.id.userAvatarContainer) materialCardView.setCardBackgroundColor(semiTransparentColor) val typedValue = TypedValue() currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) @@ -69,33 +73,29 @@ class AnimePageAdapter : RecyclerView.Adapter { + if (PrefManager.getVal(PrefName.SmallView)) trendingBinding.trendingContainer.updateLayoutParams { bottomMargin = (-108f).px } updateAvatar() - binding.animeSearchBar.hint = "ANIME" - binding.animeSearchBarText.setOnClickListener { + trendingBinding.searchBar.hint = "ANIME" + trendingBinding.searchBarText.setOnClickListener { ContextCompat.startActivity( it.context, - Intent(it.context, SearchActivity::class.java).putExtra("type", "ANIME"), + Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"), null ) } - binding.animeSearchBar.setEndIconOnClickListener { - binding.animeSearchBarText.performClick() - } - - binding.animeUserAvatar.setSafeOnClickListener { + trendingBinding.userAvatar.setSafeOnClickListener { val dialogFragment = SettingsDialogFragment.newInstance(SettingsDialogFragment.Companion.PageType.ANIME) dialogFragment.show((it.context as AppCompatActivity).supportFragmentManager, "dialog") } - binding.animeUserAvatar.setOnLongClickListener { view -> + trendingBinding.userAvatar.setOnLongClickListener { view -> ContextCompat.startActivity( view.context, Intent(view.context, ProfileActivity::class.java) @@ -104,8 +104,12 @@ class AnimePageAdapter : RecyclerView.Adapter 0) View.VISIBLE else View.GONE - binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString() + trendingBinding.searchBar.setEndIconOnClickListener { + trendingBinding.searchBar.performClick() + } + + trendingBinding.notificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString() listOf( binding.animePreviousSeason, @@ -134,8 +138,7 @@ class AnimePageAdapter : RecyclerView.Adapter 0) View.VISIBLE else View.GONE - binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString() + trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString() } } diff --git a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt index fae3fedf..917d0fec 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.view.animation.LayoutAnimationController import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.fragment.app.Fragment @@ -81,7 +82,7 @@ class HomeFragment : Fragment() { if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause() blurImage(binding.homeUserBg, Anilist.bg) binding.homeUserDataProgressBar.visibility = View.GONE - binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0 binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString() binding.homeAnimeList.setOnClickListener { @@ -375,7 +376,7 @@ class HomeFragment : Fragment() { override fun onResume() { if (!model.loaded) Refresh.activity[1]!!.postValue(true) if (_binding != null) { - binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + binding.homeNotificationCount.isVisible = Anilist.unreadNotificationCount > 0 binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString() } super.onResume() diff --git a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt index 2aef3b6b..7ca0687f 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt @@ -10,6 +10,7 @@ import android.view.ViewGroup import android.view.animation.LayoutAnimationController import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.MutableLiveData @@ -21,6 +22,7 @@ import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemMangaPageBinding +import ani.dantotsu.databinding.LayoutTrendingBinding import ani.dantotsu.loadImage import ani.dantotsu.media.GenreActivity import ani.dantotsu.media.MediaAdaptor @@ -40,6 +42,7 @@ import com.google.android.material.textfield.TextInputLayout class MangaPageAdapter : RecyclerView.Adapter() { val ready = MutableLiveData(false) lateinit var binding: ItemMangaPageBinding + private lateinit var trendingBinding: LayoutTrendingBinding private var trendHandler: Handler? = null private lateinit var trendRun: Runnable var trendingViewPager: ViewPager2? = null @@ -52,33 +55,34 @@ class MangaPageAdapter : RecyclerView.Adapter(R.id.mangaSearchBar) + val textInputLayout = holder.itemView.findViewById(R.id.searchBar) val currentColor = textInputLayout.boxBackgroundColor val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() textInputLayout.boxBackgroundColor = semiTransparentColor val materialCardView = - holder.itemView.findViewById(R.id.mangaUserAvatarContainer) + holder.itemView.findViewById(R.id.userAvatarContainer) materialCardView.setCardBackgroundColor(semiTransparentColor) val typedValue = TypedValue() currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) val color = typedValue.data - textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt() - materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt()) + textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000 + materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000) - binding.mangaTitleContainer.updatePadding(top = statusBarHeight) + trendingBinding.titleContainer.updatePadding(top = statusBarHeight) - if (PrefManager.getVal(PrefName.SmallView)) binding.mangaTrendingContainer.updateLayoutParams { + if (PrefManager.getVal(PrefName.SmallView)) trendingBinding.trendingContainer.updateLayoutParams { bottomMargin = (-108f).px } updateAvatar() - binding.mangaNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE - binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString() - binding.mangaSearchBar.hint = "MANGA" - binding.mangaSearchBarText.setOnClickListener { + trendingBinding.notificationCount.isVisible = Anilist.unreadNotificationCount > 0 + trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString() + trendingBinding.searchBar.hint = "MANGA" + trendingBinding.searchBarText.setOnClickListener { ContextCompat.startActivity( it.context, Intent(it.context, SearchActivity::class.java).putExtra("type", "MANGA"), @@ -86,12 +90,12 @@ class MangaPageAdapter : RecyclerView.Adapter + trendingBinding.userAvatar.setOnLongClickListener { view -> ContextCompat.startActivity( view.context, Intent(view.context, ProfileActivity::class.java) @@ -100,8 +104,8 @@ class MangaPageAdapter : RecyclerView.Adapter 0) View.VISIBLE else View.GONE - binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString() + trendingBinding.notificationCount.text = Anilist.unreadNotificationCount.toString() } } diff --git a/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt b/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt index 35195960..431c5ddd 100644 --- a/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt @@ -24,7 +24,6 @@ class AuthorAdapter( return AuthorViewHolder(binding) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) { val binding = holder.binding setAnimation(binding.root.context, holder.binding.root) diff --git a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt index bbe66477..1a9d73e5 100644 --- a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt @@ -6,7 +6,6 @@ import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.Window -import android.view.WindowManager import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat @@ -16,8 +15,8 @@ import androidx.lifecycle.lifecycleScope import ani.dantotsu.R import ani.dantotsu.Refresh import ani.dantotsu.databinding.ActivityListBinding +import ani.dantotsu.hideSystemBarsExtendView import ani.dantotsu.media.user.ListViewPagerAdapter -import ani.dantotsu.navBarHeight import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight @@ -34,7 +33,6 @@ class CalendarActivity : AppCompatActivity() { private var selectedTabIdx = 1 private val model: OtherDetailsViewModel by viewModels() - @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,10 +72,7 @@ class CalendarActivity : AppCompatActivity() { } else { binding.root.fitsSystemWindows = false requestWindowFeature(Window.FEATURE_NO_TITLE) - window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) + hideSystemBarsExtendView() binding.settingsContainer.updateLayoutParams { topMargin = statusBarHeight } diff --git a/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt b/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt index d8c4961c..78eeb273 100644 --- a/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/CharacterAdapter.kt @@ -24,12 +24,12 @@ class CharacterAdapter( return CharacterViewHolder(binding) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) { val binding = holder.binding setAnimation(binding.root.context, holder.binding.root) val character = characterList[position] - binding.itemCompactRelation.text = character.role + " " + val whitespace = "${character.role} " + binding.itemCompactRelation.text = whitespace binding.itemCompactImage.loadImage(character.image) binding.itemCompactTitle.text = character.name } diff --git a/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt index 4778f708..11db3506 100644 --- a/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/CharacterDetailsActivity.kt @@ -8,6 +8,7 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.math.MathUtils.clamp +import androidx.core.view.isGone import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.MutableLiveData @@ -152,7 +153,7 @@ class CharacterDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChang } override fun onResume() { - binding.characterProgress.visibility = if (!loaded) View.VISIBLE else View.GONE + binding.characterProgress.isGone = loaded super.onResume() } diff --git a/app/src/main/java/ani/dantotsu/media/CharacterDetailsAdapter.kt b/app/src/main/java/ani/dantotsu/media/CharacterDetailsAdapter.kt index 2e419492..5c78feb0 100644 --- a/app/src/main/java/ani/dantotsu/media/CharacterDetailsAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/CharacterDetailsAdapter.kt @@ -20,15 +20,16 @@ class CharacterDetailsAdapter(private val character: Character, private val acti return GenreViewHolder(binding) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: GenreViewHolder, position: Int) { val binding = holder.binding val desc = - (if (character.age != "null") currActivity()!!.getString(R.string.age) + " " + character.age else "") + - (if (character.dateOfBirth.toString() != "") currActivity()!!.getString(R.string.birthday) + " " + character.dateOfBirth.toString() else "") + - (if (character.gender != "null") currActivity()!!.getString(R.string.gender) + " " + when (character.gender) { - "Male" -> currActivity()!!.getString(R.string.male) - "Female" -> currActivity()!!.getString(R.string.female) + (if (character.age != "null") "${currActivity()!!.getString(R.string.age)} ${character.age}" else "") + + (if (character.dateOfBirth.toString() != "") + "${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") + + (if (character.gender != "null") + currActivity()!!.getString(R.string.gender) + " " + when (character.gender) { + currActivity()!!.getString(R.string.male) -> currActivity()!!.getString(R.string.male) + currActivity()!!.getString(R.string.female) -> currActivity()!!.getString(R.string.female) else -> character.gender } else "") + "\n" + character.description diff --git a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt index 8c415e2d..aebd8dbf 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt @@ -17,6 +17,7 @@ import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat import androidx.core.util.Pair import androidx.core.view.ViewCompat +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.RecyclerView @@ -85,7 +86,7 @@ class MediaAdaptor( } - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") + @SuppressLint("ClickableViewAccessibility") override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (type) { 0 -> { @@ -94,8 +95,8 @@ class MediaAdaptor( val media = mediaList?.getOrNull(position) if (media != null) { b.itemCompactImage.loadImage(media.cover) - b.itemCompactOngoing.visibility = - if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE + b.itemCompactOngoing.isVisible = + media.status == currActivity()!!.getString(R.string.status_releasing) b.itemCompactTitle.text = media.userPreferredName b.itemCompactScore.text = ((if (media.userScore == 0) (media.meanScore @@ -140,8 +141,8 @@ class MediaAdaptor( if (media != null) { b.itemCompactImage.loadImage(media.cover) blurImage(b.itemCompactBanner, media.banner ?: media.cover) - b.itemCompactOngoing.visibility = - if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE + b.itemCompactOngoing.isVisible = + media.status == currActivity()!!.getString(R.string.status_releasing) b.itemCompactTitle.text = media.userPreferredName b.itemCompactScore.text = ((if (media.userScore == 0) (media.meanScore @@ -188,8 +189,8 @@ class MediaAdaptor( ) ) blurImage(b.itemCompactBanner, media.banner ?: media.cover) - b.itemCompactOngoing.visibility = - if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE + b.itemCompactOngoing.isVisible = + media.status == currActivity()!!.getString(R.string.status_releasing) b.itemCompactTitle.text = media.userPreferredName b.itemCompactScore.text = ((if (media.userScore == 0) (media.meanScore @@ -237,8 +238,8 @@ class MediaAdaptor( ) ) blurImage(b.itemCompactBanner, media.banner ?: media.cover) - b.itemCompactOngoing.visibility = - if (media.status == currActivity()!!.getString(R.string.status_releasing)) View.VISIBLE else View.GONE + b.itemCompactOngoing.isVisible = + media.status == currActivity()!!.getString(R.string.status_releasing) b.itemCompactTitle.text = media.userPreferredName b.itemCompactScore.text = ((if (media.userScore == 0) (media.meanScore diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 9a528b80..38ded425 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -2,9 +2,9 @@ package ani.dantotsu.media import android.animation.ObjectAnimator import android.annotation.SuppressLint -import android.app.Activity import android.content.Intent import android.graphics.Rect +import android.content.res.Configuration import android.os.Bundle import android.text.SpannableStringBuilder import android.util.TypedValue @@ -12,9 +12,7 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.animation.AccelerateDecelerateInterpolator -import android.widget.FrameLayout import android.widget.ImageView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -22,8 +20,10 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.core.text.bold import androidx.core.text.color +import androidx.core.view.isVisible import androidx.core.view.marginBottom import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle @@ -62,6 +62,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import nl.joery.animatedbottombar.AnimatedBottomBar import kotlin.math.abs @@ -70,12 +71,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi lateinit var binding: ActivityMediaBinding private val scope = lifecycleScope private val model: MediaDetailsViewModel by viewModels() - lateinit var tabLayout: TripleNavAdapter var selected = 0 + lateinit var navBar: AnimatedBottomBar var anime = true private var adult = false - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia() @@ -83,8 +84,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi if (id != -1) { runBlocking { withContext(Dispatchers.IO) { - media = - Anilist.query.getMedia(id, false) ?: emptyMedia() + media = Anilist.query.getMedia(id, false) ?: emptyMedia() } } } @@ -100,26 +100,34 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi binding = ActivityMediaBinding.inflate(layoutInflater) setContentView(binding.root) screenWidth = resources.displayMetrics.widthPixels.toFloat() + navBar = binding.mediaBottomBar - val isVertical = resources.configuration.orientation - //Ui init + // Ui init initActivity(this) - binding.mediaViewPager.updateLayoutParams { bottomMargin += navBarHeight } + binding.mediaViewPager.updateLayoutParams { bottomMargin = navBarHeight } val oldMargin = binding.mediaViewPager.marginBottom AndroidBug5497Workaround.assistActivity(this) { if (it) { binding.mediaViewPager.updateLayoutParams { bottomMargin = 0 } - binding.mediaTabContainer.visibility = View.GONE + navBar.visibility = View.GONE } else { binding.mediaViewPager.updateLayoutParams { bottomMargin = oldMargin } - binding.mediaTabContainer.visibility = View.VISIBLE + navBar.visibility = View.VISIBLE } } + val navBarRightMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0 + val navBarBottomMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight + navBar.updateLayoutParams { + rightMargin = navBarRightMargin + bottomMargin = navBarBottomMargin + } binding.mediaBanner.updateLayoutParams { height += statusBarHeight } binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight } binding.mediaClose.updateLayoutParams { topMargin += statusBarHeight } @@ -147,7 +155,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi val banner = if (bannerAnimations) binding.mediaBanner else binding.mediaBannerNoKen val viewPager = binding.mediaViewPager - //tabLayout = binding.mediaTab as AnimatedBottomBar viewPager.isUserInputEnabled = false viewPager.setPageTransformer(ZoomOutPageTransformer()) @@ -157,9 +164,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi binding.mediaCoverImage.loadImage(media.cover) binding.mediaCoverImage.setOnLongClickListener { + val coverTitle = "${media.userPreferredName}[Cover]" ImageViewDialog.newInstance( this, - media.userPreferredName + "[Cover]", + coverTitle, media.cover ) } @@ -176,9 +184,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi } override fun onLongClick(event: MotionEvent) { + val bannerTitle = "${media.userPreferredName}[Banner]" ImageViewDialog.newInstance( this@MediaDetailsActivity, - media.userPreferredName + "[Banner]", + bannerTitle, media.banner ?: media.cover ) banner.performClick() @@ -186,7 +195,8 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi }) banner.setOnTouchListener { _, motionEvent -> gestureDetector.onTouchEvent(motionEvent);true } if (PrefManager.getVal(PrefName.Incognito)) { - binding.mediaTitle.text = " ${media.userPreferredName}" + val mediaTitle = " ${media.userPreferredName}" + binding.mediaTitle.text = mediaTitle binding.incognito.visibility = View.VISIBLE } else { binding.mediaTitle.text = media.userPreferredName @@ -246,13 +256,13 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi @SuppressLint("ResourceType") fun total() { val text = SpannableStringBuilder().apply { - val typedValue = TypedValue() + val mediaTypedValue = TypedValue() this@MediaDetailsActivity.theme.resolveAttribute( com.google.android.material.R.attr.colorOnBackground, - typedValue, + mediaTypedValue, true ) - val white = typedValue.data + val white = mediaTypedValue.data if (media.userStatus != null) { append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num)) val typedValue = TypedValue() @@ -342,14 +352,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi progress() } } - tabLayout = TripleNavAdapter( - binding.mediaTab1, - binding.mediaTab2, - binding.mediaTab3, - media.anime != null, - media.format ?: "", - isVertical == 1 - ) adult = media.isAdult if (media.anime != null) { viewPager.adapter = @@ -365,22 +367,36 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi anime = false } - selected = media.selected!!.window binding.mediaTitle.translationX = -screenWidth - tabLayout.selectionListener = { selected, newId -> - binding.commentInputLayout.visibility = if (selected == 2) View.VISIBLE else View.GONE - this.selected = selected - selectFromID(newId) - viewPager.setCurrentItem(selected, false) - val sel = model.loadSelected(media, isDownload) - sel.window = selected - model.saveSelected(media.id, sel) + val infoTab = navBar.createTab(R.drawable.ic_round_info_24, R.string.info, R.id.info) + val watchTab = if (anime) { + navBar.createTab(R.drawable.ic_round_movie_filter_24, R.string.watch, R.id.watch) + } else if (media.format == "NOVEL") { + navBar.createTab(R.drawable.ic_round_book_24, R.string.read, R.id.read) + } else { + navBar.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read) } - tabLayout.selectTab(selected) - selectFromID(tabLayout.selected) - viewPager.setCurrentItem(selected, false) + val commentTab = navBar.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment) + navBar.addTab(infoTab) + navBar.addTab(watchTab) + navBar.addTab(commentTab) + navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { + override fun onTabSelected( + lastIndex: Int, + lastTab: AnimatedBottomBar.Tab?, + newIndex: Int, + newTab: AnimatedBottomBar.Tab + ) { + selected = newIndex + binding.commentInputLayout.isVisible = selected == 2 + viewPager.setCurrentItem(selected, true) + val sel = model.loadSelected(media, isDownload) + sel.window = selected + model.saveSelected(media.id, sel) + } + }) if (model.continueMedia == null && media.cameFromContinue) { model.continueMedia = PrefManager.getVal(PrefName.ContinueMedia) @@ -390,6 +406,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi if (frag != null) { selected = 2 } + navBar.selectTabAt(selected) + binding.commentInputLayout.isVisible = selected == 2 + viewPager.setCurrentItem(selected, false) val live = Refresh.activity.getOrPut(this.hashCode()) { MutableLiveData(true) } live.observe(this) { @@ -402,40 +421,19 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi } } - private fun selectFromID(id: Int) { - when (id) { - R.id.info -> { - selected = 0 - } - - R.id.watch, R.id.read -> { - selected = 1 - } - - R.id.comment -> { - selected = 2 - } - } - } - - private fun idFromSelect(): Int { - if (anime) when (selected) { - 0 -> return R.id.info - 1 -> return R.id.watch - 2 -> return R.id.comment - } - else when (selected) { - 0 -> return R.id.info - 1 -> return R.id.read - 2 -> return R.id.comment - } - return R.id.info + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + val rightMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0 + val bottomMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight + val params : ViewGroup.MarginLayoutParams = + navBar.layoutParams as ViewGroup.MarginLayoutParams + params.updateMargins(right = rightMargin, bottom = bottomMargin) } override fun onResume() { - if (this::tabLayout.isInitialized) { - tabLayout.selectTab(selected) - } + navBar.selectTabAt(selected) super.onResume() } @@ -443,7 +441,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi ANIME, MANGA, NOVEL } - //ViewPager + // ViewPager private class ViewPagerAdapter( fragmentManager: FragmentManager, lifecycle: Lifecycle, @@ -602,4 +600,4 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi companion object { var mediaSingleton: Media? = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index 650e722d..b9d005e3 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -52,12 +52,16 @@ class MediaDetailsViewModel : ViewModel() { it } if (isDownload) { - data.sourceIndex = if (media.anime != null) { - AnimeSources.list.size - 1 - } else if (media.format == "MANGA" || media.format == "ONE_SHOT") { - MangaSources.list.size - 1 - } else { - NovelSources.list.size - 1 + data.sourceIndex = when { + media.anime != null -> { + AnimeSources.list.size - 1 + } + media.format == "MANGA" || media.format == "ONE_SHOT" -> { + MangaSources.list.size - 1 + } + else -> { + NovelSources.list.size - 1 + } } } return data @@ -152,10 +156,10 @@ class MediaDetailsViewModel : ViewModel() { watchSources?.get(i)?.apply { if (!post && !allowsPreloading) return@apply ep.sEpisode?.let { - loadByVideoServers(link, ep.extra, it) { - if (it.videos.isNotEmpty()) { - list.add(it) - ep.extractorCallback?.invoke(it) + loadByVideoServers(link, ep.extra, it) { extractor -> + if (extractor.videos.isNotEmpty()) { + list.add(extractor) + ep.extractorCallback?.invoke(extractor) } } } diff --git a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt index 1cec1eb6..ecd0e537 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt @@ -16,6 +16,8 @@ import android.widget.TextView import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -37,7 +39,7 @@ import java.io.Serializable import java.net.URLEncoder -@SuppressLint("SetTextI18n") + class MediaInfoFragment : Fragment() { private var _binding: FragmentMediaInfoBinding? = null private val binding get() = _binding!! @@ -46,6 +48,8 @@ class MediaInfoFragment : Fragment() { private var type = "ANIME" private val genreModel: GenresViewModel by activityViewModels() + private val tripleTab = "\t\t\t" + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -63,8 +67,8 @@ class MediaInfoFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val model: MediaDetailsViewModel by activityViewModels() val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) - binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE - binding.mediaInfoContainer.visibility = if (loaded) View.VISIBLE else View.GONE + binding.mediaInfoProgressBar.isGone = loaded + binding.mediaInfoContainer.isVisible = loaded binding.mediaInfoContainer.updateLayoutParams { bottomMargin += 128f.px + navBarHeight } model.scrolledToTop.observe(viewLifecycleOwner) { @@ -78,14 +82,16 @@ class MediaInfoFragment : Fragment() { binding.mediaInfoProgressBar.visibility = View.GONE binding.mediaInfoContainer.visibility = View.VISIBLE - binding.mediaInfoName.text = "\t\t\t" + (media.name ?: media.nameRomaji) + val infoName = tripleTab + (media.name ?: media.nameRomaji) + binding.mediaInfoName.text = infoName binding.mediaInfoName.setOnLongClickListener { copyToClipboard(media.name ?: media.nameRomaji) true } if (media.name != null) binding.mediaInfoNameRomajiContainer.visibility = View.VISIBLE - binding.mediaInfoNameRomaji.text = "\t\t\t" + media.nameRomaji + val infoNameRomanji = tripleTab + media.nameRomaji + binding.mediaInfoNameRomaji.text = infoNameRomanji binding.mediaInfoNameRomaji.setOnLongClickListener { copyToClipboard(media.nameRomaji) true @@ -127,8 +133,9 @@ class MediaInfoFragment : Fragment() { } binding.mediaInfoDurationContainer.visibility = View.VISIBLE binding.mediaInfoSeasonContainer.visibility = View.VISIBLE - binding.mediaInfoSeason.text = - (media.anime.season ?: "??") + " " + (media.anime.seasonYear ?: "??") + val seasonInfo = "${(media.anime.season ?: "??")} ${(media.anime.seasonYear ?: "??")}" + binding.mediaInfoSeason.text = seasonInfo + if (media.anime.mainStudio != null) { binding.mediaInfoStudioContainer.visibility = View.VISIBLE binding.mediaInfoStudio.text = media.anime.mainStudio!!.name @@ -162,9 +169,12 @@ class MediaInfoFragment : Fragment() { } } binding.mediaInfoTotalTitle.setText(R.string.total_eps) - binding.mediaInfoTotal.text = - if (media.anime.nextAiringEpisode != null) (media.anime.nextAiringEpisode.toString() + " | " + (media.anime.totalEpisodes - ?: "~").toString()) else (media.anime.totalEpisodes ?: "~").toString() + val infoTotal = if (media.anime.nextAiringEpisode != null) + "${media.anime.nextAiringEpisode} | ${media.anime.totalEpisodes ?: "~"}" + else + (media.anime.totalEpisodes ?: "~").toString() + binding.mediaInfoTotal.text = infoTotal + } else if (media.manga != null) { type = "MANGA" binding.mediaInfoTotalTitle.setText(R.string.total_chaps) @@ -191,8 +201,9 @@ class MediaInfoFragment : Fragment() { (media.description ?: "null").replace("\\n", "
").replace("\\\"", "\""), HtmlCompat.FROM_HTML_MODE_LEGACY ) - binding.mediaInfoDescription.text = - "\t\t\t" + if (desc.toString() != "null") desc else getString(R.string.no_description_available) + val infoDesc = tripleTab + if (desc.toString() != "null") desc else getString(R.string.no_description_available) + binding.mediaInfoDescription.text = infoDesc + binding.mediaInfoDescription.setOnClickListener { if (binding.mediaInfoDescription.maxLines == 5) { ObjectAnimator.ofInt(binding.mediaInfoDescription, "maxLines", 100) @@ -550,7 +561,7 @@ class MediaInfoFragment : Fragment() { } override fun onResume() { - binding.mediaInfoProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE + binding.mediaInfoProgressBar.isGone = loaded super.onResume() } diff --git a/app/src/main/java/ani/dantotsu/media/MediaListDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaListDialogFragment.kt index 0430709b..e8c15685 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaListDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaListDialogFragment.kt @@ -36,7 +36,6 @@ class MediaListDialogFragment : BottomSheetDialogFragment() { return binding.root } - @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.mediaListContainer.updateLayoutParams { bottomMargin += navBarHeight } var media: Media? @@ -168,9 +167,10 @@ class MediaListDialogFragment : BottomSheetDialogFragment() { val init = if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString() .toInt() else 0 - if (init < (total - ?: 5000) - ) binding.mediaListProgress.setText((init + 1).toString()) + if (init < (total ?: 5000)) { + val progressText = "${init + 1}" + binding.mediaListProgress.setText(progressText) + } if (init + 1 == (total ?: 5000)) { binding.mediaListStatus.setText(statusStrings[2], false) onComplete() diff --git a/app/src/main/java/ani/dantotsu/media/MediaListDialogSmallFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaListDialogSmallFragment.kt index 982eca8e..8848c125 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaListDialogSmallFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaListDialogSmallFragment.kt @@ -54,7 +54,6 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() { } - @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.mediaListContainer.updateLayoutParams { bottomMargin += navBarHeight } val scope = viewLifecycleOwner.lifecycleScope @@ -68,7 +67,7 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() { MAL.query.deleteList(media.anime != null, media.idMAL) } catch (e: Exception) { withContext(Dispatchers.Main) { - snackString("Failed to delete because of... ${e.message}") + snackString(getString(R.string.delete_fail_reason, e.message)) } return@withContext } @@ -154,7 +153,10 @@ class MediaListDialogSmallFragment : BottomSheetDialogFragment() { val init = if (binding.mediaListProgress.text.toString() != "") binding.mediaListProgress.text.toString() .toInt() else 0 - if (init < (total ?: 5000)) binding.mediaListProgress.setText((init + 1).toString()) + if (init < (total ?: 5000)) { + val progressText = "${init + 1}" + binding.mediaListProgress.setText(progressText) + } if (init + 1 == (total ?: 5000)) { binding.mediaListStatus.setText(statusStrings[2], false) } diff --git a/app/src/main/java/ani/dantotsu/media/MediaType.kt b/app/src/main/java/ani/dantotsu/media/MediaType.kt new file mode 100644 index 00000000..6762d98e --- /dev/null +++ b/app/src/main/java/ani/dantotsu/media/MediaType.kt @@ -0,0 +1,26 @@ +package ani.dantotsu.media + +enum class MediaType { + ANIME, + MANGA, + NOVEL; + + fun asText(): String { + return when (this) { + ANIME -> "Anime" + MANGA -> "Manga" + NOVEL -> "Novel" + } + } + + companion object { + fun fromText(string : String): MediaType { + return when (string) { + "Anime" -> ANIME + "Manga" -> MANGA + "Novel" -> NOVEL + else -> { ANIME } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/ProgressAdapter.kt b/app/src/main/java/ani/dantotsu/media/ProgressAdapter.kt index 081d47fc..eb11c29f 100644 --- a/app/src/main/java/ani/dantotsu/media/ProgressAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/ProgressAdapter.kt @@ -27,7 +27,7 @@ class ProgressAdapter(private val horizontal: Boolean = true, searched: Boolean) return ProgressViewHolder(binding) } - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") + @SuppressLint("ClickableViewAccessibility") override fun onBindViewHolder(holder: ProgressViewHolder, position: Int) { val progressBar = holder.binding.root bar = progressBar diff --git a/app/src/main/java/ani/dantotsu/media/SearchActivity.kt b/app/src/main/java/ani/dantotsu/media/SearchActivity.kt index 33403915..e1915f22 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchActivity.kt @@ -6,6 +6,7 @@ import android.os.Parcelable import android.view.View import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.core.view.updatePaddingRelative import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter @@ -137,7 +138,7 @@ class SearchActivity : AppCompatActivity() { model.searchResults.results.addAll(it.results) mediaAdaptor.notifyItemRangeInserted(prev, it.results.size) - progressAdapter.bar?.visibility = if (it.hasNextPage) View.VISIBLE else View.GONE + progressAdapter.bar?.isVisible = it.hasNextPage } } diff --git a/app/src/main/java/ani/dantotsu/media/SourceAdapter.kt b/app/src/main/java/ani/dantotsu/media/SourceAdapter.kt index c1dd18b2..e56adde6 100644 --- a/app/src/main/java/ani/dantotsu/media/SourceAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SourceAdapter.kt @@ -1,6 +1,5 @@ package ani.dantotsu.media -import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView @@ -22,7 +21,6 @@ abstract class SourceAdapter( return SourceViewHolder(binding) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: SourceViewHolder, position: Int) { val binding = holder.binding val character = sources[position] diff --git a/app/src/main/java/ani/dantotsu/media/SourceSearchDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/SourceSearchDialogFragment.kt index 3925bcce..06b784e8 100644 --- a/app/src/main/java/ani/dantotsu/media/SourceSearchDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/SourceSearchDialogFragment.kt @@ -65,7 +65,7 @@ class SourceSearchDialogFragment : BottomSheetDialogFragment() { i = media!!.selected!!.sourceIndex val source = if (media!!.anime != null) { - (if (!media!!.isAdult) AnimeSources else HAnimeSources)[i!!] + (if (media!!.isAdult) HAnimeSources else AnimeSources)[i!!] } else { anime = false (if (media!!.isAdult) HMangaSources else MangaSources)[i!!] diff --git a/app/src/main/java/ani/dantotsu/media/StudioActivity.kt b/app/src/main/java/ani/dantotsu/media/StudioActivity.kt index ac28cd5f..a90caeb4 100644 --- a/app/src/main/java/ani/dantotsu/media/StudioActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/StudioActivity.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.view.isGone import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.MutableLiveData @@ -114,7 +115,7 @@ class StudioActivity : AppCompatActivity() { } override fun onResume() { - binding.studioProgressBar.visibility = if (!loaded) View.VISIBLE else View.GONE + binding.studioProgressBar.isGone = loaded super.onResume() } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt index e5ebd81b..6672f37d 100644 --- a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt +++ b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt @@ -17,7 +17,7 @@ class SubtitleDownloader { companion object { //doesn't really download the subtitles -\_(o_o)_/- - suspend fun loadSubtitleType(context: Context, url: String): SubtitleType = + suspend fun loadSubtitleType(url: String): SubtitleType = withContext(Dispatchers.IO) { // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it val networkHelper = Injekt.get() @@ -60,7 +60,7 @@ class SubtitleDownloader { if (!directory.exists()) { //just in case directory.mkdirs() } - val type = loadSubtitleType(context, url) + val type = loadSubtitleType(url) val subtiteFile = File(directory, "subtitle.${type}") if (subtiteFile.exists()) { subtiteFile.delete() diff --git a/app/src/main/java/ani/dantotsu/media/TripleNavAdapter.kt b/app/src/main/java/ani/dantotsu/media/TripleNavAdapter.kt deleted file mode 100644 index b76a0dff..00000000 --- a/app/src/main/java/ani/dantotsu/media/TripleNavAdapter.kt +++ /dev/null @@ -1,136 +0,0 @@ -package ani.dantotsu.media - -import android.graphics.Color -import android.view.ViewGroup -import androidx.core.view.updateLayoutParams -import ani.dantotsu.R -import ani.dantotsu.navBarHeight -import nl.joery.animatedbottombar.AnimatedBottomBar - -class TripleNavAdapter( - private val nav1: AnimatedBottomBar, - private val nav2: AnimatedBottomBar, - private val nav3: AnimatedBottomBar, - anime: Boolean, - format: String, - private val isScreenVertical: Boolean = false -) { - var selected: Int = 0 - var selectionListener: ((Int, Int) -> Unit)? = null - init { - nav1.tabs.clear() - nav2.tabs.clear() - nav3.tabs.clear() - val infoTab = nav1.createTab(R.drawable.ic_round_info_24, R.string.info, R.id.info) - val watchTab = if (anime) { - nav2.createTab(R.drawable.ic_round_movie_filter_24, R.string.watch, R.id.watch) - } else if (format == "NOVEL") { - nav2.createTab(R.drawable.ic_round_book_24, R.string.read, R.id.read) - } else { - nav2.createTab(R.drawable.ic_round_import_contacts_24, R.string.read, R.id.read) - } - val commentTab = nav3.createTab(R.drawable.ic_round_comment_24, R.string.comments, R.id.comment) - nav1.addTab(infoTab) - nav1.visibility = ViewGroup.VISIBLE - if (isScreenVertical) { - nav2.visibility = ViewGroup.GONE - nav3.visibility = ViewGroup.GONE - nav1.addTab(watchTab) - nav1.addTab(commentTab) - nav1.updateLayoutParams { - bottomMargin = navBarHeight - } - nav2.updateLayoutParams { - bottomMargin = navBarHeight - } - nav3.updateLayoutParams { - bottomMargin = navBarHeight - } - } else { - nav1.indicatorColor = Color.TRANSPARENT - nav2.indicatorColor = Color.TRANSPARENT - nav3.indicatorColor = Color.TRANSPARENT - nav2.visibility = ViewGroup.VISIBLE - nav3.visibility = ViewGroup.VISIBLE - nav2.addTab(watchTab) - nav3.addTab(commentTab) - nav2.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { - override fun onTabSelected( - lastIndex: Int, - lastTab: AnimatedBottomBar.Tab?, - newIndex: Int, - newTab: AnimatedBottomBar.Tab - ) { - selected = 1 - deselectOthers(selected) - selectionListener?.invoke(selected, newTab.id) - } - }) - nav3.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { - override fun onTabSelected( - lastIndex: Int, - lastTab: AnimatedBottomBar.Tab?, - newIndex: Int, - newTab: AnimatedBottomBar.Tab - ) { - selected = 2 - deselectOthers(selected) - selectionListener?.invoke(selected, newTab.id) - } - }) - } - nav1.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { - override fun onTabSelected( - lastIndex: Int, - lastTab: AnimatedBottomBar.Tab?, - newIndex: Int, - newTab: AnimatedBottomBar.Tab - ) { - if (!isScreenVertical) { - selected = 0 - deselectOthers(selected) - } else selected = newIndex - selectionListener?.invoke(selected, newTab.id) - } - }) - } - - private fun deselectOthers(selected: Int) { - if (selected == 0) { - nav2.clearSelection() - nav3.clearSelection() - } - if (selected == 1) { - nav1.clearSelection() - nav3.clearSelection() - } - if (selected == 2) { - nav1.clearSelection() - nav2.clearSelection() - } - } - - fun selectTab(tab: Int) { - selected = tab - if (!isScreenVertical) { - when (tab) { - 0 -> nav1.selectTabAt(0) - 1 -> nav2.selectTabAt(0) - 2 -> nav3.selectTabAt(0) - } - deselectOthers(selected) - } else { - nav1.selectTabAt(selected) - } - } - - fun setVisibility(visibility: Int) { - if (isScreenVertical) { - nav1.visibility = visibility - return - } - nav1.visibility = visibility - nav2.visibility = visibility - nav3.visibility = visibility - } -} \ No newline at end of file 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 ad8c378e..16142902 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|episodio|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 = "(? mr.value.replaceFirst(mr.groupValues[1], "") diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index 4b53fe42..3b69477f 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -13,6 +13,8 @@ import android.widget.LinearLayout import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.startActivity +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* @@ -54,7 +56,6 @@ class AnimeWatchAdapter( private var nestedDialog: AlertDialog? = null - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = holder.binding _binding = binding @@ -97,15 +98,12 @@ class AnimeWatchAdapter( null ) } - val offline = if (!isOnline(binding.root.context) || PrefManager.getVal( - PrefName.OfflineMode - ) - ) View.GONE else View.VISIBLE + val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) - binding.animeSourceNameContainer.visibility = offline - binding.animeSourceSettings.visibility = offline - binding.animeSourceSearch.visibility = offline - binding.animeSourceTitle.visibility = offline + binding.animeSourceNameContainer.isGone = offline + binding.animeSourceSettings.isGone = offline + binding.animeSourceSearch.isGone = offline + binding.animeSourceTitle.isGone = offline //Source Selection var source = @@ -117,8 +115,7 @@ class AnimeWatchAdapter( this.selectDub = media.selected!!.preferDub binding.animeSourceTitle.text = showUserText showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } - binding.animeSourceDubbedCont.visibility = - if (isDubAvailableSeparately()) View.VISIBLE else View.GONE + binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() } } @@ -137,8 +134,7 @@ class AnimeWatchAdapter( changing = true binding.animeSourceDubbed.isChecked = selectDub changing = false - binding.animeSourceDubbedCont.visibility = - if (isDubAvailableSeparately()) View.VISIBLE else View.GONE + binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() source = i setLanguageList(0, i) } @@ -158,8 +154,7 @@ class AnimeWatchAdapter( changing = true binding.animeSourceDubbed.isChecked = selectDub changing = false - binding.animeSourceDubbedCont.visibility = - if (isDubAvailableSeparately()) View.VISIBLE else View.GONE + binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() setLanguageList(i, source) } subscribeButton(false) @@ -223,9 +218,9 @@ class AnimeWatchAdapter( else -> dialogBinding.animeSourceList } when (style) { - 0 -> dialogBinding.layoutText.text = "List" - 1 -> dialogBinding.layoutText.text = "Grid" - 2 -> dialogBinding.layoutText.text = "Compact" + 0 -> dialogBinding.layoutText.setText(R.string.list) + 1 -> dialogBinding.layoutText.setText(R.string.grid) + 2 -> dialogBinding.layoutText.setText(R.string.compact) else -> dialogBinding.animeSourceList } selected.alpha = 1f @@ -237,24 +232,24 @@ class AnimeWatchAdapter( dialogBinding.animeSourceList.setOnClickListener { selected(it as ImageButton) style = 0 - dialogBinding.layoutText.text = "List" + dialogBinding.layoutText.setText(R.string.list) run = true } dialogBinding.animeSourceGrid.setOnClickListener { selected(it as ImageButton) style = 1 - dialogBinding.layoutText.text = "Grid" + dialogBinding.layoutText.setText(R.string.grid) run = true } dialogBinding.animeSourceCompact.setOnClickListener { selected(it as ImageButton) style = 2 - dialogBinding.layoutText.text = "Compact" + dialogBinding.layoutText.setText(R.string.compact) run = true } dialogBinding.animeWebviewContainer.setOnClickListener { if (!WebViewUtil.supportsWebView(fragment.requireContext())) { - toast("WebView not installed") + toast(R.string.webview_not_installed) } //start CookieCatcher activity if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { @@ -307,7 +302,6 @@ class AnimeWatchAdapter( } //Chips - @SuppressLint("SetTextI18n") fun updateChips(limit: Int, names: Array, arr: Array, selected: Int = 0) { val binding = _binding if (binding != null) { @@ -329,7 +323,8 @@ class AnimeWatchAdapter( 0 ) } - chip.text = "${names[limit * (position)]} - ${names[last - 1]}" + val chipText = "${names[limit * (position)]} - ${names[last - 1]}" + chip.text = chipText chip.setTextColor( ContextCompat.getColorStateList( fragment.requireContext(), @@ -363,7 +358,6 @@ class AnimeWatchAdapter( _binding?.animeSourceChipGroup?.removeAllViews() } - @SuppressLint("SetTextI18n") fun handleEpisodes() { val binding = _binding if (binding != null) { @@ -371,9 +365,9 @@ class AnimeWatchAdapter( val episodes = media.anime.episodes!!.keys.toTypedArray() val anilistEp = (media.userProgress ?: 0).plus(1) - val appEp = - PrefManager.getCustomVal("${media.id}_current_ep", "")?.toIntOrNull() - ?: 1 + val appEp = PrefManager.getCustomVal( + "${media.id}_current_ep", "" + )?.toIntOrNull() ?: 1 var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() if (episodes.contains(continueEp)) { @@ -409,15 +403,19 @@ class AnimeWatchAdapter( ep.thumb ?: FileUrl[media.banner ?: media.cover], 0 ) if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE + binding.animeSourceContinueText.text = - currActivity()!!.getString(R.string.continue_episode) + "${ep.number}${if (ep.filler) " - Filler" else ""}${"\n$cleanedTitle"}" + currActivity()!!.getString(R.string.continue_episode, ep.number, if (ep.filler) + currActivity()!!.getString(R.string.filler_tag) + else + "", cleanedTitle) binding.animeSourceContinue.setOnClickListener { fragment.onEpisodeClick(continueEp) } if (fragment.continueEp) { - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < PrefManager.getVal( - PrefName.WatchPercentage - ) + if ( + (binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams) + .weight < PrefManager.getVal(PrefName.WatchPercentage) ) { binding.animeSourceContinue.performClick() fragment.continueEp = false @@ -428,13 +426,10 @@ class AnimeWatchAdapter( } binding.animeSourceProgressBar.visibility = View.GONE - if (media.anime.episodes!!.isNotEmpty()) { - binding.animeSourceNotFound.visibility = View.GONE - binding.faqbutton.visibility = View.GONE} - else { - binding.animeSourceNotFound.visibility = View.VISIBLE - binding.faqbutton.visibility = View.VISIBLE - } + + val sourceFound = media.anime.episodes!!.isNotEmpty() + binding.animeSourceNotFound.isGone = sourceFound + binding.faqbutton.isGone = sourceFound } else { binding.animeSourceContinue.visibility = View.GONE binding.animeSourceNotFound.visibility = View.GONE diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index 50eef688..28d10c6b 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -17,6 +17,8 @@ import androidx.annotation.OptIn import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import androidx.core.math.MathUtils +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -27,19 +29,24 @@ import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 -import ani.dantotsu.* +import ani.dantotsu.FileUrl +import ani.dantotsu.R import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.video.ExoplayerDownloadService +import ani.dantotsu.dp import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsViewModel +import ani.dantotsu.media.MediaType +import ani.dantotsu.navBarHeight import ani.dantotsu.others.LanguageMapper import ani.dantotsu.parsers.AnimeParser import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.HAnimeSources +import ani.dantotsu.setNavigationTheme import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName @@ -340,16 +347,12 @@ class AnimeWatchFragment : Fragment() { val changeUIVisibility: (Boolean) -> Unit = { show -> val activity = activity if (activity is MediaDetailsActivity && isAdded) { - val visibility = if (show) View.VISIBLE else View.GONE - activity.findViewById(R.id.mediaAppBar).visibility = visibility - activity.findViewById(R.id.mediaViewPager).visibility = visibility - activity.findViewById(R.id.mediaCover).visibility = visibility - activity.findViewById(R.id.mediaClose).visibility = visibility - - activity.tabLayout.setVisibility(visibility) - - activity.findViewById(R.id.fragmentExtensionsContainer).visibility = - if (show) View.GONE else View.VISIBLE + activity.findViewById(R.id.mediaAppBar).isVisible = show + activity.findViewById(R.id.mediaViewPager).isVisible = show + activity.findViewById(R.id.mediaCover).isVisible = show + activity.findViewById(R.id.mediaClose).isVisible = show + activity.navBar.isVisible = show + activity.findViewById(R.id.fragmentExtensionsContainer).isGone = show } } var itemSelected = false @@ -435,7 +438,7 @@ class AnimeWatchFragment : Fragment() { DownloadedType( media.mainName(), i, - DownloadedType.Type.ANIME + MediaType.ANIME ) ) episodeAdapter.purgeDownload(i) @@ -447,7 +450,7 @@ class AnimeWatchFragment : Fragment() { DownloadedType( media.mainName(), i, - DownloadedType.Type.ANIME + MediaType.ANIME ) ) val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i) diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 7a3d8aec..3f06bf4d 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -8,18 +8,21 @@ import android.view.ViewGroup import android.view.animation.LinearInterpolator import android.widget.LinearLayout import androidx.annotation.OptIn +import androidx.core.view.isVisible import androidx.lifecycle.coroutineScope import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.DownloadIndex import androidx.recyclerview.widget.RecyclerView -import ani.dantotsu.* +import ani.dantotsu.R import ani.dantotsu.connections.updateProgress +import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.databinding.ItemEpisodeGridBinding import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.video.Helper import ani.dantotsu.media.Media +import ani.dantotsu.setAnimation import ani.dantotsu.settings.saving.PrefManager import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl @@ -97,7 +100,6 @@ class EpisodeAdapter( return type } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val ep = arr[position] val title = if (!ep.title.isNullOrEmpty() && ep.title != "null") { @@ -125,8 +127,7 @@ class EpisodeAdapter( binding.itemEpisodeFiller.visibility = View.GONE binding.itemEpisodeFillerView.visibility = View.GONE } - binding.itemEpisodeDesc.visibility = - if (ep.desc != null && ep.desc?.trim(' ') != "") View.VISIBLE else View.GONE + binding.itemEpisodeDesc.isVisible = !ep.desc.isNullOrBlank() binding.itemEpisodeDesc.text = ep.desc ?: "" holder.bind(ep.number, ep.downloadProgress, ep.desc) @@ -203,8 +204,7 @@ class EpisodeAdapter( val binding = holder.binding setAnimation(fragment.requireContext(), holder.binding.root) binding.itemEpisodeNumber.text = ep.number - binding.itemEpisodeFillerView.visibility = - if (ep.filler) View.VISIBLE else View.GONE + binding.itemEpisodeFillerView.isVisible = ep.filler if (media.userProgress != null) { if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) binding.itemEpisodeViewedCover.visibility = View.VISIBLE @@ -429,7 +429,7 @@ class EpisodeAdapter( if (bytes < 0) return null val unit = 1000 if (bytes < unit) return "$bytes B" - val exp = (Math.log(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() val pre = ("KMGTPE")[exp - 1] return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre) } diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt index 7ae06a1a..ea95e077 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -16,7 +16,10 @@ import android.graphics.Color import android.graphics.drawable.Animatable import android.hardware.SensorManager import android.media.AudioManager -import android.media.AudioManager.* +import android.media.AudioManager.AUDIOFOCUS_GAIN +import android.media.AudioManager.AUDIOFOCUS_LOSS +import android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT +import android.media.AudioManager.STREAM_MUSIC import android.net.Uri import android.os.Build import android.os.Bundle @@ -27,8 +30,18 @@ import android.provider.Settings.System import android.util.AttributeSet import android.util.Rational import android.util.TypedValue -import android.view.* -import android.view.KeyEvent.* +import android.view.GestureDetector +import android.view.KeyEvent +import android.view.KeyEvent.ACTION_UP +import android.view.KeyEvent.KEYCODE_B +import android.view.KeyEvent.KEYCODE_DPAD_LEFT +import android.view.KeyEvent.KEYCODE_DPAD_RIGHT +import android.view.KeyEvent.KEYCODE_N +import android.view.KeyEvent.KEYCODE_SPACE +import android.view.MotionEvent +import android.view.OrientationEventListener +import android.view.View +import android.view.ViewGroup import android.view.animation.AnimationUtils import android.widget.AdapterView import android.widget.ImageButton @@ -46,27 +59,43 @@ import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.media3.cast.CastPlayer import androidx.media3.cast.SessionAvailabilityListener -import androidx.media3.common.* +import androidx.media3.common.C import androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE import androidx.media3.common.C.TRACK_TYPE_VIDEO +import androidx.media3.common.MediaItem +import androidx.media3.common.MimeTypes +import androidx.media3.common.PlaybackException +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.TrackSelectionOverride +import androidx.media3.common.Tracks import androidx.media3.common.util.UnstableApi -import androidx.media3.common.util.Util import androidx.media3.datasource.DataSource -import androidx.media3.datasource.DefaultDataSourceFactory +import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.HttpDataSource import androidx.media3.datasource.cache.CacheDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource +import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.exoplayer.util.EventLogger import androidx.media3.session.MediaSession -import androidx.media3.ui.* -import androidx.media3.ui.CaptionStyleCompat.* -import androidx.media3.exoplayer.DefaultLoadControl +import androidx.media3.ui.AspectRatioFrameLayout +import androidx.media3.ui.CaptionStyleCompat +import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_DEPRESSED +import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW +import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_NONE +import androidx.media3.ui.CaptionStyleCompat.EDGE_TYPE_OUTLINE +import androidx.media3.ui.DefaultTimeBar +import androidx.media3.ui.PlayerView +import androidx.media3.ui.SubtitleView import androidx.mediarouter.app.MediaRouteButton -import ani.dantotsu.* +import ani.dantotsu.GesturesListener +import ani.dantotsu.NoPaddingArrayAdapter import ani.dantotsu.R +import ani.dantotsu.brightnessConverter +import ani.dantotsu.circularReveal import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.discord.Discord @@ -75,19 +104,38 @@ import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton import ani.dantotsu.connections.discord.RPC import ani.dantotsu.connections.updateProgress import ani.dantotsu.databinding.ActivityExoplayerBinding +import ani.dantotsu.defaultHeaders import ani.dantotsu.download.video.Helper +import ani.dantotsu.dp +import ani.dantotsu.getCurrentBrightnessValue +import ani.dantotsu.hideSystemBars +import ani.dantotsu.hideSystemBarsExtendView +import ani.dantotsu.isOnline +import ani.dantotsu.logError import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.SubtitleDownloader +import ani.dantotsu.okHttpClient import ani.dantotsu.others.AniSkip import ani.dantotsu.others.AniSkip.getType import ani.dantotsu.others.ResettableTimer import ani.dantotsu.others.getSerialized -import ani.dantotsu.parsers.* +import ani.dantotsu.parsers.AnimeSources +import ani.dantotsu.parsers.HAnimeSources +import ani.dantotsu.parsers.Subtitle +import ani.dantotsu.parsers.SubtitleType +import ani.dantotsu.parsers.Video +import ani.dantotsu.parsers.VideoExtractor +import ani.dantotsu.parsers.VideoType +import ani.dantotsu.px import ani.dantotsu.settings.PlayerSettingsActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.snackString +import ani.dantotsu.startMainActivity import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.toast +import ani.dantotsu.tryWithSuspend import ani.dantotsu.util.Logger import com.bumptech.glide.Glide import com.google.android.gms.cast.framework.CastButtonFactory @@ -103,14 +151,17 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.* -import java.util.concurrent.* +import java.util.Calendar +import java.util.Timer +import java.util.TimerTask +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @UnstableApi -@SuppressLint("SetTextI18n", "ClickableViewAccessibility") +@SuppressLint("ClickableViewAccessibility") class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener { private val resumeWindow = "resumeWindow" @@ -344,15 +395,14 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL isCastApiAvailable = GoogleApiAvailability.getInstance() .isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS try { - castContext = CastContext.getSharedInstance(this) + castContext = CastContext.getSharedInstance(this, Executors.newSingleThreadExecutor()).result castPlayer = CastPlayer(castContext!!) castPlayer!!.setSessionAvailabilityListener(this) } catch (e: Exception) { isCastApiAvailable = false } - WindowCompat.setDecorFitsSystemWindows(window, false) - hideSystemBars() + hideSystemBarsExtendView() onBackPressedDispatcher.addCallback(this) { finishAndRemoveTask() @@ -397,21 +447,25 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL orientationListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { override fun onOrientationChanged(orientation: Int) { - if (orientation in 45..135) { - if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { - exoRotate.visibility = View.VISIBLE + when (orientation) { + in 45..135 -> { + if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { + exoRotate.visibility = View.VISIBLE + } + rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE } - rotation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE - } else if (orientation in 225..315) { - if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { - exoRotate.visibility = View.VISIBLE + in 225..315 -> { + if (rotation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { + exoRotate.visibility = View.VISIBLE + } + rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } - rotation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - } else if (orientation in 315..360 || orientation in 0..45) { - if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { - exoRotate.visibility = View.VISIBLE + in 315..360, in 0..45 -> { + if (rotation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + exoRotate.visibility = View.VISIBLE + } + rotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } - rotation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } } } @@ -703,11 +757,13 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL fun seek(forward: Boolean, event: MotionEvent? = null) { val seekTime = PrefManager.getVal(PrefName.SeekTime) val (card, text) = if (forward) { - forwardText.text = "+${seekTime * ++seekTimesF}" + val text = "+${seekTime * ++seekTimesF}" + forwardText.text = text handler.post { exoPlayer.seekTo(exoPlayer.currentPosition + seekTime * 1000) } fastForwardCard to forwardText } else { - rewindText.text = "-${seekTime * ++seekTimesR}" + val text = "-${seekTime * ++seekTimesR}" + rewindText.text = text handler.post { exoPlayer.seekTo(exoPlayer.currentPosition - seekTime * 1000) } fastRewindCard to rewindText } @@ -941,7 +997,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL episodeArr = episodes.keys.toList() currentEpisodeIndex = episodeArr.indexOf(media.anime!!.selectedEpisode!!) - episodeTitleArr = arrayListOf() + episodeTitleArr = arrayListOf() episodes.forEach { val episode = it.value val cleanedTitle = AnimeNameAdapter.removeEpisodeNumberCompletely(episode.title ?: "") @@ -1052,7 +1108,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL RPC.Link(getString(R.string.view_anime), media.shareLink ?: ""), RPC.Link( "Stream on Dantotsu", - "https://github.com/rebelonion/Dantotsu/" + getString(R.string.github) ) ) ) @@ -1264,6 +1320,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL media.anime!!.selectedEpisode!! ) + @Suppress("UNCHECKED_CAST") val list = (PrefManager.getNullableCustomVal("continueAnimeList", listOf(), List::class.java) as List).toMutableList() if (list.contains(media.id)) list.remove(media.id) list.add(media.id) @@ -1293,7 +1350,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } //Subtitles - exoSubtitle.visibility = if (ext.subtitles.isNotEmpty()) View.VISIBLE else View.GONE + exoSubtitle.isVisible = ext.subtitles.isNotEmpty() exoSubtitle.setOnClickListener { subClick() } @@ -1301,9 +1358,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL if (subtitle != null) { //var localFile: String? = null if (subtitle?.type == SubtitleType.UNKNOWN) { - val context = this runBlocking { - val type = SubtitleDownloader.loadSubtitleType(context, subtitle!!.file.url) + val type = SubtitleDownloader.loadSubtitleType(subtitle!!.file.url) val fileUri = Uri.parse(subtitle!!.file.url) sub = MediaItem.SubtitleConfiguration .Builder(fileUri) @@ -1358,8 +1414,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL } dataSource } - val dafuckDataSourceFactory = - DefaultDataSourceFactory(this, Util.getUserAgent(this, R.string.app_name.toString())) + val dafuckDataSourceFactory = DefaultDataSource.Factory(this) cacheFactory = CacheDataSource.Factory().apply { setCache(Helper.getSimpleCache(this@ExoplayerView)) if (ext.server.offline) { @@ -1659,7 +1714,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL aspectRatio = Rational(width, height) - videoInfo.text = "Quality: ${height}p" + videoInfo.text = getString(R.string.video_quality, height) if (exoPlayer.duration < playbackPosition) exoPlayer.seekTo(0) @@ -1735,28 +1790,26 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL timer = null return } - if (timer == null) { - timer = object : CountDownTimer(5000, 1000) { - override fun onTick(millisUntilFinished: Long) { - if (new == null){ - skipTimeButton.visibility = View.GONE - exoSkip.visibility = View.VISIBLE - disappeared = false - functionstarted = false - cancelTimer() - } - } - - override fun onFinish() { + timer = object : CountDownTimer(5000, 1000) { + override fun onTick(millisUntilFinished: Long) { + if (new == null) { skipTimeButton.visibility = View.GONE exoSkip.visibility = View.VISIBLE - disappeared = true + disappeared = false functionstarted = false cancelTimer() } } - timer?.start() + + override fun onFinish() { + skipTimeButton.visibility = View.GONE + exoSkip.visibility = View.VISIBLE + disappeared = true + functionstarted = false + cancelTimer() + } } + timer?.start() } if (PrefManager.getVal(PrefName.ShowTimeStampButton)) { diff --git a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt index f83b22ad..22a7e097 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -12,6 +12,7 @@ import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope @@ -302,7 +303,6 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { ) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: UrlViewHolder, position: Int) { val binding = holder.binding val video = extractor.videos[position] @@ -401,12 +401,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { dismiss() } if (video.format == VideoType.CONTAINER) { - binding.urlSize.visibility = if (video.size != null) View.VISIBLE else View.GONE - binding.urlSize.text = - // if video size is null or 0, show "Unknown Size" else show the size in MB - (if (video.extraNote != null) " : " else "") + (if (video.size == 0.0) "Unknown Size" else (DecimalFormat( - "#.##" - ).format(video.size ?: 0).toString() + " MB")) + binding.urlSize.isVisible = video.size != null + // if video size is null or 0, show "Unknown Size" else show the size in MB + val sizeText = getString(R.string.mb_size, "${if (video.extraNote != null) " : " else ""}${ + if (video.size == 0.0) getString(R.string.size_unknown) else DecimalFormat("#.##").format(video.size ?: 0) + }") + binding.urlSize.text = sizeText } binding.urlNote.visibility = View.VISIBLE binding.urlNote.text = video.format.name diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt index b78d4dce..d5b887bd 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt @@ -11,6 +11,7 @@ import ani.dantotsu.connections.comments.Comment import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.copyToClipboard import ani.dantotsu.databinding.ItemCommentsBinding +import ani.dantotsu.getAppString import ani.dantotsu.loadImage import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.profile.ProfileActivity @@ -28,6 +29,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.Date +import java.util.Locale import java.util.TimeZone import kotlin.math.abs import kotlin.math.sqrt @@ -52,7 +54,6 @@ class CommentItem(val comment: Comment, adapter.add(repliesSection) } - @SuppressLint("SetTextI18n") override fun bind(viewBinding: ItemCommentsBinding, position: Int) { binding = viewBinding setAnimation(binding.root.context, binding.root) @@ -76,8 +77,15 @@ class CommentItem(val comment: Comment, if ((comment.replyCount ?: 0) > 0) { viewBinding.commentTotalReplies.visibility = View.VISIBLE viewBinding.commentRepliesDivider.visibility = View.VISIBLE - viewBinding.commentTotalReplies.text = if(repliesVisible) "Hide Replies" else - "View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}" + viewBinding.commentTotalReplies.context.run { + viewBinding.commentTotalReplies.text = if (repliesVisible) + getString(R.string.hide_replies) + else + if (comment.replyCount == 1) + getString(R.string.view_reply) + else + getString(R.string.view_replies_count, comment.replyCount) + } } else { viewBinding.commentTotalReplies.visibility = View.GONE viewBinding.commentRepliesDivider.visibility = View.GONE @@ -87,10 +95,15 @@ class CommentItem(val comment: Comment, if (repliesVisible) { repliesSection.clear() removeSubCommentIds() - viewBinding.commentTotalReplies.text = "View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}" + viewBinding.commentTotalReplies.context.run { + viewBinding.commentTotalReplies.text = if (comment.replyCount == 1) + getString(R.string.view_reply) + else + getString(R.string.view_replies_count, comment.replyCount) + } repliesVisible = false } else { - viewBinding.commentTotalReplies.text = "Hide Replies" + viewBinding.commentTotalReplies.setText(R.string.hide_replies) repliesSection.clear() commentsFragment.viewReplyCallback(this) repliesVisible = true @@ -128,35 +141,37 @@ class CommentItem(val comment: Comment, 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.commentDelete.setOnClickListener { - dialogBuilder("Delete Comment", "Are you sure you want to delete this comment?") { - val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) - scope.launch { + dialogBuilder(getAppString(R.string.delete_comment), getAppString(R.string.delete_comment_confirm)) { + CoroutineScope(Dispatchers.Main + SupervisorJob()).launch { val success = CommentsAPI.deleteComment(comment.commentId) if (success) { - snackString("Comment Deleted") + snackString(R.string.comment_deleted) parentSection.remove(this@CommentItem) } } } } viewBinding.commentBanUser.setOnClickListener { - dialogBuilder("Ban User", "Are you sure you want to ban this user?") { - val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) - scope.launch { + dialogBuilder(getAppString(R.string.ban_user), getAppString(R.string.ban_user_confirm)) { + CoroutineScope(Dispatchers.Main + SupervisorJob()).launch { val success = CommentsAPI.banUser(comment.userId) if (success) { - snackString("User Banned") + snackString(R.string.user_banned) } } } } viewBinding.commentReport.setOnClickListener { - dialogBuilder("Report Comment", "Only report comments that violate the rules. Are you sure you want to report this comment?") { - val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) - scope.launch { - val success = CommentsAPI.reportComment(comment.commentId, comment.username, commentsFragment.mediaName, comment.userId) + dialogBuilder(getAppString(R.string.report_comment), getAppString(R.string.report_comment_confirm)) { + CoroutineScope(Dispatchers.Main + SupervisorJob()).launch { + val success = CommentsAPI.reportComment( + comment.commentId, + comment.username, + commentsFragment.mediaName, + comment.userId + ) if (success) { - snackString("Comment Reported") + snackString(R.string.comment_reported) } } } @@ -210,7 +225,8 @@ class CommentItem(val comment: Comment, } comment.profilePictureUrl?.let { viewBinding.commentUserAvatar.loadImage(it) } viewBinding.commentUserName.text = comment.username - viewBinding.commentUserLevel.text = "[${levelColor.second}]" + val userColor = "[${levelColor.second}]" + viewBinding.commentUserLevel.text = userColor viewBinding.commentUserLevel.setTextColor(levelColor.first) viewBinding.commentUserTime.text = formatTimestamp(comment.timestamp) } @@ -243,6 +259,7 @@ class CommentItem(val comment: Comment, private fun removeSubCommentIds(){ subCommentIds.forEach { id -> + @Suppress("UNCHECKED_CAST") val parentComments = parentSection.groups as? List ?: emptyList() val commentToRemove = parentComments.find { it.comment.commentId == id } commentToRemove?.let { @@ -272,9 +289,10 @@ class CommentItem(val comment: Comment, } } + @SuppressLint("SimpleDateFormat") private fun formatTimestamp(timestamp: String): String { return try { - val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT) dateFormat.timeZone = TimeZone.getTimeZone("UTC") val parsedDate = dateFormat.parse(timestamp) val currentDate = Date() @@ -297,8 +315,9 @@ class CommentItem(val comment: Comment, } companion object { + @SuppressLint("SimpleDateFormat") fun timestampToMillis(timestamp: String): Long { - val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT) dateFormat.timeZone = TimeZone.getTimeZone("UTC") val parsedDate = dateFormat.parse(timestamp) return parsedDate?.time ?: 0 diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt index 5e699799..0ec2d3bf 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt @@ -523,11 +523,10 @@ class CommentsFragment : Fragment() { } - @SuppressLint("SetTextI18n") fun replyTo(comment: CommentItem, username: String) { if (comment.isReplying) { activity.binding.commentReplyToContainer.visibility = View.VISIBLE - activity.binding.commentReplyTo.text = "Replying to $username" + activity.binding.commentReplyTo.text = getString(R.string.replying_to, username) activity.binding.commentReplyToCancel.setOnClickListener { comment.replying(false) replyCallback(comment) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt index ebf1c57f..359c64a5 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media.manga import android.content.ContentResolver import android.content.ContentValues -import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri @@ -10,8 +9,8 @@ import android.os.Build import android.os.Environment import android.provider.MediaStore import android.util.LruCache -import ani.dantotsu.util.Logger import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.Dispatchers @@ -25,15 +24,13 @@ data class ImageData( ) { suspend fun fetchAndProcessImage( page: Page, - httpSource: HttpSource, - context: Context + httpSource: HttpSource ): Bitmap? { return withContext(Dispatchers.IO) { try { // Fetch the image val response = httpSource.getImage(page) - Logger.log("Response: ${response.code}") - Logger.log("Response: ${response.message}") + Logger.log("Response: ${response.code} - ${response.message}") // Convert the Response to an InputStream val inputStream = response.body.byteStream() diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt index d19aa011..67d9a696 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt @@ -262,7 +262,6 @@ class MangaChapterAdapter( } } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is ChapterCompactViewHolder -> { diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt index ae755faa..d265b69a 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt @@ -5,8 +5,8 @@ import java.util.regex.Pattern class MangaNameAdapter { companion object { - const val chapterRegex = "(chapter|chap|ch|c)[\\s:.\\-]*([\\d]+\\.?[\\d]*)[\\s:.\\-]*" - const val filedChapterNumberRegex = "(?= mangaReadSources.names.size) 0 else it } @@ -188,8 +187,8 @@ class MangaReadAdapter( else -> dialogBinding.animeSourceList } when (style) { - 0 -> dialogBinding.layoutText.text = "List" - 1 -> dialogBinding.layoutText.text = "Compact" + 0 -> dialogBinding.layoutText.setText(R.string.list) + 1 -> dialogBinding.layoutText.setText(R.string.compact) else -> dialogBinding.animeSourceList } selected.alpha = 1f @@ -201,18 +200,18 @@ class MangaReadAdapter( dialogBinding.animeSourceList.setOnClickListener { selected(it as ImageButton) style = 0 - dialogBinding.layoutText.text = "List" + dialogBinding.layoutText.setText(R.string.list) run = true } dialogBinding.animeSourceCompact.setOnClickListener { selected(it as ImageButton) style = 1 - dialogBinding.layoutText.text = "Compact" + dialogBinding.layoutText.setText(R.string.compact) run = true } dialogBinding.animeWebviewContainer.setOnClickListener { if (!WebViewUtil.supportsWebView(fragment.requireContext())) { - toast("WebView not installed") + toast(R.string.webview_not_installed) } //start CookieCatcher activity if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { @@ -249,8 +248,7 @@ class MangaReadAdapter( } //Scanlator - dialogBinding.animeScanlatorContainer.visibility = - if (options.count() > 1) View.VISIBLE else View.GONE + dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1 dialogBinding.scanlatorNo.text = "${options.count()}" dialogBinding.animeScanlatorTop.setOnClickListener { val dialogView2 = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null) @@ -359,7 +357,6 @@ class MangaReadAdapter( } //Chips - @SuppressLint("SetTextI18n") fun updateChips(limit: Int, names: Array, arr: Array, selected: Int = 0) { val binding = _binding if (binding != null) { @@ -395,7 +392,8 @@ class MangaReadAdapter( names[last - 1] } //chip.text = "${names[limit * (position)]} - ${names[last - 1]}" - chip.text = "$startChapterString - $endChapterString" + val chipText = "$startChapterString - $endChapterString" + chip.text = chipText chip.setTextColor( ContextCompat.getColorStateList( fragment.requireContext(), @@ -429,7 +427,6 @@ class MangaReadAdapter( _binding?.animeSourceChipGroup?.removeAllViews() } - @SuppressLint("SetTextI18n") fun handleChapters() { val binding = _binding @@ -466,7 +463,7 @@ class MangaReadAdapter( val ep = media.manga.chapters!![continueEp]!! binding.itemEpisodeImage.loadImage(media.banner ?: media.cover) binding.animeSourceContinueText.text = - currActivity()!!.getString(R.string.continue_chapter) + "${ep.number}${if (!ep.title.isNullOrEmpty()) "\n${ep.title}" else ""}" + currActivity()!!.getString(R.string.continue_chapter, ep.number, if (!ep.title.isNullOrEmpty()) ep.title else "") binding.animeSourceContinue.setOnClickListener { fragment.onMangaChapterClick(continueEp) } @@ -481,13 +478,9 @@ class MangaReadAdapter( binding.animeSourceContinue.visibility = View.GONE } binding.animeSourceProgressBar.visibility = View.GONE - if (media.manga.chapters!!.isNotEmpty()) { - binding.animeSourceNotFound.visibility = View.GONE - binding.faqbutton.visibility = View.GONE - } else { - binding.animeSourceNotFound.visibility = View.VISIBLE - binding.faqbutton.visibility = View.VISIBLE - } + val sourceFound = media.manga.chapters!!.isNotEmpty() + binding.animeSourceNotFound.isGone = sourceFound + binding.faqbutton.isGone = sourceFound } else { binding.animeSourceContinue.visibility = View.GONE binding.animeSourceNotFound.visibility = View.GONE @@ -519,8 +512,7 @@ class MangaReadAdapter( parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) } ) val items = adapter.count - binding?.animeSourceLanguageContainer?.visibility = - if (items > 1) View.VISIBLE else View.GONE + binding?.animeSourceLanguageContainer?.isVisible = items > 1 binding?.animeSourceLanguage?.setAdapter(adapter) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 621e07fb..8d11693c 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -20,6 +20,8 @@ import androidx.cardview.widget.CardView import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.math.MathUtils.clamp +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -28,21 +30,25 @@ import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 -import ani.dantotsu.* +import ani.dantotsu.R import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.manga.MangaDownloaderService import ani.dantotsu.download.manga.MangaServiceDataSingleton +import ani.dantotsu.dp import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsViewModel +import ani.dantotsu.media.MediaType import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog +import ani.dantotsu.navBarHeight import ani.dantotsu.others.LanguageMapper import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.MangaParser import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.setNavigationTheme import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName @@ -354,14 +360,12 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { val changeUIVisibility: (Boolean) -> Unit = { show -> val activity = activity if (activity is MediaDetailsActivity && isAdded) { - val visibility = if (show) View.VISIBLE else View.GONE - activity.findViewById(R.id.mediaAppBar).visibility = visibility - activity.findViewById(R.id.mediaViewPager).visibility = visibility - activity.findViewById(R.id.mediaCover).visibility = visibility - activity.findViewById(R.id.mediaClose).visibility = visibility - activity.tabLayout.setVisibility(visibility) - activity.findViewById(R.id.fragmentExtensionsContainer).visibility = - if (show) View.GONE else View.VISIBLE + activity.findViewById(R.id.mediaAppBar).isVisible = show + activity.findViewById(R.id.mediaViewPager).isVisible = show + activity.findViewById(R.id.mediaCover).isVisible = show + activity.findViewById(R.id.mediaClose).isVisible = show + activity.navBar.isVisible = show + activity.findViewById(R.id.fragmentExtensionsContainer).isGone = show } } var itemSelected = false @@ -492,7 +496,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { DownloadedType( media.mainName(), i, - DownloadedType.Type.MANGA + MediaType.MANGA ) ) chapterAdapter.deleteDownload(i) @@ -510,7 +514,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { DownloadedType( media.mainName(), i, - DownloadedType.Type.MANGA + MediaType.MANGA ) ) chapterAdapter.purgeDownload(i) diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt index c313d71d..6db754dc 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/BaseImageAdapter.kt @@ -13,10 +13,14 @@ import androidx.core.view.GestureDetectorCompat import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView -import ani.dantotsu.* +import ani.dantotsu.FileUrl +import ani.dantotsu.GesturesListener +import ani.dantotsu.R import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaChapter +import ani.dantotsu.px import ani.dantotsu.settings.CurrentReaderSettings +import ani.dantotsu.tryWithSuspend import com.alexvasilkov.gestures.views.GestureFrameLayout import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -118,13 +122,13 @@ abstract class BaseImageAdapter( abstract suspend fun loadImage(position: Int, parent: View): Boolean companion object { - suspend fun Context.loadBitmap_old( + suspend fun Context.loadBitmapOld( link: FileUrl, transforms: List ): Bitmap? { //still used in some places return tryWithSuspend { withContext(Dispatchers.IO) { - Glide.with(this@loadBitmap_old) + Glide.with(this@loadBitmapOld) .asBitmap() .let { if (link.url.startsWith("file://")) { @@ -168,8 +172,7 @@ abstract class BaseImageAdapter( mangaCache.get(link.url)?.let { imageData -> val bitmap = imageData.fetchAndProcessImage( imageData.page, - imageData.source, - context = this@loadBitmap + imageData.source ) it.load(bitmap) .skipMemoryCache(true) diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt index 157c6146..747bb8dd 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt @@ -10,8 +10,19 @@ import android.content.res.Resources import android.graphics.Bitmap import android.os.Build import android.os.Bundle -import android.view.* -import android.view.KeyEvent.* +import android.view.HapticFeedbackConstants +import android.view.KeyEvent +import android.view.KeyEvent.ACTION_DOWN +import android.view.KeyEvent.KEYCODE_DPAD_DOWN +import android.view.KeyEvent.KEYCODE_DPAD_UP +import android.view.KeyEvent.KEYCODE_PAGE_DOWN +import android.view.KeyEvent.KEYCODE_PAGE_UP +import android.view.KeyEvent.KEYCODE_VOLUME_DOWN +import android.view.KeyEvent.KEYCODE_VOLUME_UP +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager import android.view.animation.OvershootInterpolator import android.widget.AdapterView import android.widget.CheckBox @@ -20,13 +31,16 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.math.MathUtils.clamp import androidx.core.view.GestureDetectorCompat +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 -import ani.dantotsu.* +import ani.dantotsu.GesturesListener +import ani.dantotsu.NoPaddingArrayAdapter +import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.discord.Discord @@ -34,7 +48,12 @@ import ani.dantotsu.connections.discord.DiscordService import ani.dantotsu.connections.discord.DiscordServiceRunningSingleton import ani.dantotsu.connections.discord.RPC import ani.dantotsu.connections.updateProgress +import ani.dantotsu.currContext import ani.dantotsu.databinding.ActivityMangaReaderBinding +import ani.dantotsu.dp +import ani.dantotsu.hideSystemBarsExtendView +import ani.dantotsu.isOnline +import ani.dantotsu.logError import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaSingleton @@ -45,14 +64,25 @@ import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.MangaImage import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.px +import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.CurrentReaderSettings import ani.dantotsu.settings.CurrentReaderSettings.Companion.applyWebtoon -import ani.dantotsu.settings.CurrentReaderSettings.Directions.* -import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.* -import ani.dantotsu.settings.CurrentReaderSettings.Layouts.* +import ani.dantotsu.settings.CurrentReaderSettings.Directions.BOTTOM_TO_TOP +import ani.dantotsu.settings.CurrentReaderSettings.Directions.LEFT_TO_RIGHT +import ani.dantotsu.settings.CurrentReaderSettings.Directions.RIGHT_TO_LEFT +import ani.dantotsu.settings.CurrentReaderSettings.Directions.TOP_TO_BOTTOM +import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.Automatic +import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.Force +import ani.dantotsu.settings.CurrentReaderSettings.DualPageModes.No +import ani.dantotsu.settings.CurrentReaderSettings.Layouts.CONTINUOUS_PAGED +import ani.dantotsu.settings.CurrentReaderSettings.Layouts.PAGED import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.showSystemBarsRetractView +import ani.dantotsu.snackString import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.tryWith import com.alexvasilkov.gestures.views.GestureFrameLayout import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView @@ -65,11 +95,11 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.io.ObjectInputStream import java.io.ObjectOutputStream -import java.util.* +import java.util.Timer +import java.util.TimerTask import kotlin.math.min import kotlin.properties.Delegates -@SuppressLint("SetTextI18n") class MangaReaderActivity : AppCompatActivity() { private val mangaCache = Injekt.get() @@ -88,7 +118,6 @@ class MangaReaderActivity : AppCompatActivity() { private var isContVisible = false private var showProgressDialog = true - private var hidescrollbar = false private var maxChapterPage = 0L private var currentChapterPage = 0L @@ -123,7 +152,7 @@ class MangaReaderActivity : AppCompatActivity() { } private fun hideSystemBars() { - if (PrefManager.getVal(PrefName.ShowSystemBars)) + if (PrefManager.getVal(PrefName.ShowSystemBars)) showSystemBarsRetractView() else hideSystemBarsExtendView() @@ -223,8 +252,7 @@ class MangaReaderActivity : AppCompatActivity() { chapter = chapters[media.manga!!.selectedChapter] ?: return model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources - binding.mangaReaderSource.visibility = - if (PrefManager.getVal(PrefName.ShowSource)) View.VISIBLE else View.GONE + binding.mangaReaderSource.isVisible = PrefManager.getVal(PrefName.ShowSource) if (model.mangaReadSources!!.names.isEmpty()) { //try to reload sources try { @@ -369,7 +397,7 @@ class MangaReaderActivity : AppCompatActivity() { RPC.Link(getString(R.string.view_manga), media.shareLink ?: ""), RPC.Link( "Stream on Dantotsu", - "https://github.com/rebelonion/Dantotsu/" + getString(R.string.github) ) ) ) @@ -741,12 +769,12 @@ class MangaReaderActivity : AppCompatActivity() { goneTimer.schedule(timerTask, controllerDuration) } - enum class pressPos { + enum class PressPos { LEFT, RIGHT, CENTER } fun handleController(shouldShow: Boolean? = null, event: MotionEvent? = null) { - var pressLocation = pressPos.CENTER + var pressLocation = PressPos.CENTER if (!sliding) { if (event != null && defaultSettings.layout == PAGED) { if (event.action != MotionEvent.ACTION_UP) return @@ -756,23 +784,23 @@ class MangaReaderActivity : AppCompatActivity() { //if in the 1st 1/5th of the screen width, left and lower than 1/5th of the screen height, left if (screenWidth / 5 in x + 1.. screenWidth - screenWidth / 5 && y > screenWidth / 5) { pressLocation = if (defaultSettings.direction == RIGHT_TO_LEFT || defaultSettings.direction == BOTTOM_TO_TOP) { - pressPos.LEFT + PressPos.LEFT } else { - pressPos.RIGHT + PressPos.RIGHT } } } // if pressLocation is left or right go to previous or next page (paged mode only) - if (pressLocation == pressPos.LEFT) { + if (pressLocation == PressPos.LEFT) { if (binding.mangaReaderPager.currentItem > 0) { //if the current images zoomed in, go back to normal before going to previous page @@ -783,7 +811,7 @@ class MangaReaderActivity : AppCompatActivity() { return } - } else if (pressLocation == pressPos.RIGHT) { + } else if (pressLocation == PressPos.RIGHT) { if (binding.mangaReaderPager.currentItem < maxChapterPage - 1) { //if the current images zoomed in, go back to normal before going to next page if (imageAdapter?.isZoomed() == true) { @@ -961,7 +989,7 @@ class MangaReaderActivity : AppCompatActivity() { if (!incognito && PrefManager.getCustomVal( "${media.id}_save_progress", true - ) && if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHReader) else true + ) && if (media.isAdult) PrefManager.getVal(PrefName.UpdateForHReader) else true ) updateProgress( media, diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt index 859dfdac..867912a7 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt @@ -9,7 +9,6 @@ import android.os.Environment import android.os.Handler import android.os.Looper import android.os.Parcelable -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -28,6 +27,7 @@ import ani.dantotsu.download.novel.NovelDownloaderService import ani.dantotsu.download.novel.NovelServiceDataSingleton import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsViewModel +import ani.dantotsu.media.MediaType import ani.dantotsu.media.novel.novelreader.NovelReaderActivity import ani.dantotsu.navBarHeight import ani.dantotsu.parsers.ShowResponse @@ -90,7 +90,7 @@ class NovelReadFragment : Fragment(), DownloadedType( media.mainName(), novel.name, - DownloadedType.Type.NOVEL + MediaType.NOVEL ) ) ) { @@ -122,7 +122,7 @@ class NovelReadFragment : Fragment(), DownloadedType( media.mainName(), novel.name, - DownloadedType.Type.NOVEL + MediaType.NOVEL ) ) } @@ -133,7 +133,7 @@ class NovelReadFragment : Fragment(), DownloadedType( media.mainName(), novel.name, - DownloadedType.Type.NOVEL + MediaType.NOVEL ) ) } 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 8da7c35b..e523ff29 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.ItemNovelResponseBinding @@ -71,8 +72,7 @@ class NovelResponseAdapter( } binding.itemEpisodeDesc2.text = novel.extra?.get("1") ?: "" val desc = novel.extra?.get("2") - binding.itemEpisodeDesc.visibility = - if (desc != null && desc.trim(' ') != "") View.VISIBLE else View.GONE + binding.itemEpisodeDesc.isVisible = !desc.isNullOrBlank() binding.itemEpisodeDesc.text = desc ?: "" binding.root.setOnClickListener { diff --git a/app/src/main/java/ani/dantotsu/media/novel/UrlAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/UrlAdapter.kt index 96e9bcf8..dabd635c 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/UrlAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/UrlAdapter.kt @@ -31,7 +31,6 @@ class UrlAdapter( ) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: UrlViewHolder, position: Int) { val binding = holder.binding val url = urls[position] diff --git a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt index 7ce2f136..81a80cfa 100644 --- a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt @@ -6,7 +6,6 @@ import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.Window -import android.view.WindowManager import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu @@ -17,7 +16,7 @@ import androidx.lifecycle.lifecycleScope import ani.dantotsu.R import ani.dantotsu.Refresh import ani.dantotsu.databinding.ActivityListBinding -import ani.dantotsu.navBarHeight +import ani.dantotsu.hideSystemBarsExtendView import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight @@ -33,7 +32,6 @@ class ListActivity : AppCompatActivity() { private val scope = lifecycleScope private var selectedTabIdx = 0 - @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -72,10 +70,7 @@ class ListActivity : AppCompatActivity() { } else { binding.root.fitsSystemWindows = false requestWindowFeature(Window.FEATURE_NO_TITLE) - window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) + hideSystemBarsExtendView() binding.settingsContainer.updateLayoutParams { topMargin = statusBarHeight } @@ -83,8 +78,8 @@ class ListActivity : AppCompatActivity() { setContentView(binding.root) val anime = intent.getBooleanExtra("anime", true) - binding.listTitle.text = - intent.getStringExtra("username") + "'s " + (if (anime) "Anime" else "Manga") + " List" + binding.listTitle.text = getString(R.string.user_list, intent.getStringExtra("username"), + if (anime) getString(R.string.anime) else getString(R.string.manga)) binding.listTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab?) { this@ListActivity.selectedTabIdx = tab?.position ?: 0 diff --git a/app/src/main/java/ani/dantotsu/others/AndroidBug5497Workaround.kt b/app/src/main/java/ani/dantotsu/others/AndroidBug5497Workaround.kt index 46e8a297..b25d10e2 100644 --- a/app/src/main/java/ani/dantotsu/others/AndroidBug5497Workaround.kt +++ b/app/src/main/java/ani/dantotsu/others/AndroidBug5497Workaround.kt @@ -11,7 +11,7 @@ class AndroidBug5497Workaround private constructor(activity: Activity, private v private val frameLayoutParams: FrameLayout.LayoutParams init { - val content = activity.findViewById(android.R.id.content) as FrameLayout + val content: FrameLayout = activity.findViewById(android.R.id.content) mChildOfContent = content.getChildAt(0) mChildOfContent.viewTreeObserver.addOnGlobalLayoutListener { possiblyResizeChildOfContent() } frameLayoutParams = mChildOfContent.layoutParams as FrameLayout.LayoutParams @@ -42,9 +42,15 @@ class AndroidBug5497Workaround private constructor(activity: Activity, private v return r.bottom } + /** + * Fixes windowSoftInputMode adjustResize when used with setDecorFitsSystemWindows(false) + * + * @see adjustResize breaks when activity is fullscreen + */ companion object { - // For more information, see https://issuetracker.google.com/issues/36911528 - // To use this class, simply invoke assistActivity() on an Activity that already has its content view set. + /** + * Called on an Activity after the content view has been set. + */ fun assistActivity(activity: Activity, callback: (Boolean) -> Unit) { AndroidBug5497Workaround(activity, callback) } diff --git a/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt b/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt index e11715c7..346bd592 100644 --- a/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt +++ b/app/src/main/java/ani/dantotsu/others/ImageViewDialog.kt @@ -14,7 +14,7 @@ import ani.dantotsu.R import ani.dantotsu.databinding.BottomSheetImageBinding import ani.dantotsu.downloadsPermission import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap -import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old +import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmapOld import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.mergeBitmap import ani.dantotsu.openLinkInBrowser import ani.dantotsu.saveImageToDownloads @@ -84,9 +84,9 @@ class ImageViewDialog : BottomSheetDialogFragment() { viewLifecycleOwner.lifecycleScope.launch { val binding = _binding ?: return@launch - var bitmap = context.loadBitmap_old(image, trans1 ?: listOf()) + var bitmap = context.loadBitmapOld(image, trans1 ?: listOf()) var bitmap2 = - if (image2 != null) context.loadBitmap_old(image2, trans2 ?: listOf()) else null + if (image2 != null) context.loadBitmapOld(image2, trans2 ?: listOf()) else null if (bitmap == null) { bitmap = context.loadBitmap(image, trans1 ?: listOf()) bitmap2 = diff --git a/app/src/main/java/ani/dantotsu/others/OutlineTextView.kt b/app/src/main/java/ani/dantotsu/others/OutlineTextView.kt index e2c17cc0..7a0cf925 100644 --- a/app/src/main/java/ani/dantotsu/others/OutlineTextView.kt +++ b/app/src/main/java/ani/dantotsu/others/OutlineTextView.kt @@ -1,9 +1,11 @@ package ani.dantotsu.others import android.content.Context +import android.content.res.Resources import android.graphics.Canvas import android.graphics.Paint import android.util.AttributeSet +import android.util.TypedValue import androidx.appcompat.widget.AppCompatTextView import ani.dantotsu.R @@ -54,14 +56,14 @@ class OutlineTextView : AppCompatTextView { setStrokeWidth(strokeWidth) } + private val Float.toPx get() = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics + ) private fun setStrokeWidth(width: Float) { - strokeWidth = width.toPx(context) + strokeWidth = width.toPx } - private fun Float.toPx(context: Context) = - (this * context.resources.displayMetrics.scaledDensity + 0.5F) - override fun invalidate() { if (isDrawing) return super.invalidate() diff --git a/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt b/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt index 73ae553a..e9e171f8 100644 --- a/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt +++ b/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt @@ -12,19 +12,19 @@ import androidx.appcompat.app.AppCompatActivity import ani.dantotsu.R import ani.dantotsu.themes.ThemeManager import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class CookieCatcher : AppCompatActivity() { @SuppressLint("SetJavaScriptEnabled") - @Suppress("UNCHECKED_CAST") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() //get url from intent - val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - val headers: Map = intent.getSerializableExtra("headers") as? Map ?: emptyMap() + val url = intent.getStringExtra("url") ?: getString(R.string.cursed_yt) + val headers: Map = intent.getSerializableExtraCompat("headers") as? Map ?: emptyMap() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val process = Application.getProcessName() diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 5ef0adf1..fc271de6 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -11,11 +11,11 @@ import android.os.Environment import android.provider.MediaStore import ani.dantotsu.FileUrl import ani.dantotsu.currContext -import ani.dantotsu.util.Logger import ani.dantotsu.media.anime.AnimeNameAdapter import ani.dantotsu.media.manga.ImageData import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimesPage @@ -59,8 +59,6 @@ class AniyomiAdapter { fun aniyomiToAnimeParser(extension: AnimeExtension.Installed): DynamicAnimeParser { return DynamicAnimeParser(extension) } - - } class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { @@ -192,7 +190,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { // Group by season, sort within each season, and then renumber while keeping episode number 0 as is val seasonGroups = res.groupBy { AnimeNameAdapter.findSeasonNumber(it.name) ?: 0 } - seasonGroups.keys.sortedBy { it.toInt() } + seasonGroups.keys.sortedBy { it } .flatMap { season -> seasonGroups[season]?.sortedBy { it.episode_number }?.map { episode -> if (episode.episode_number != 0f) { // Skip renumbering for episode number 0 @@ -209,7 +207,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } ?: emptyList() } } - return sortedEpisodes.map { SEpisodeToEpisode(it) } + return sortedEpisodes.map { sEpisodeToEpisode(it) } } catch (e: Exception) { Logger.log("Exception: $e") } @@ -244,7 +242,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { return try { val videos = source.getVideoList(sEpisode) - videos.map { VideoToVideoServer(it) } + videos.map { videoToVideoServer(it) } } catch (e: Exception) { Logger.log("Exception occurred: ${e.message}") emptyList() @@ -296,7 +294,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } } - private fun SEpisodeToEpisode(sEpisode: SEpisode): Episode { + private fun sEpisodeToEpisode(sEpisode: SEpisode): Episode { //if the float episode number is a whole number, convert it to an int val episodeNumberInt = if (sEpisode.episode_number % 1 == 0f) { @@ -324,7 +322,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { ) } - private fun VideoToVideoServer(video: Video): VideoServer { + private fun videoToVideoServer(video: Video): VideoServer { return VideoServer( video.quality, video.url, @@ -363,7 +361,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { return try { val res = source.getChapterList(sManga) val reversedRes = res.reversed() - val chapterList = reversedRes.map { SChapterToMangaChapter(it) } + val chapterList = reversedRes.map { sChapterToMangaChapter(it) } Logger.log("chapterList size: ${chapterList.size}") Logger.log("chapterList: ${chapterList[1].title}") Logger.log("chapterList: ${chapterList[1].description}") @@ -382,7 +380,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { sourceLanguage = 0 extension.sources[sourceLanguage] } as? HttpSource ?: return emptyList() - var imageDataList: List = listOf() + val imageDataList: MutableList = mutableListOf() val ret = coroutineScope { try { Logger.log("source.name " + source.name) @@ -632,7 +630,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { } - private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter { + private fun sChapterToMangaChapter(sChapter: SChapter): MangaChapter { return MangaChapter( sChapter.name, sChapter.url, @@ -676,8 +674,8 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() { get() = videoServer override suspend fun extract(): VideoContainer { - val vidList = listOfNotNull(videoServer.video?.let { AniVideoToSaiVideo(it) }) - val subList = videoServer.video?.subtitleTracks?.map { TrackToSubtitle(it) } ?: emptyList() + val vidList = listOfNotNull(videoServer.video?.let { aniVideoToSaiVideo(it) }) + val subList = videoServer.video?.subtitleTracks?.map { trackToSubtitle(it) } ?: emptyList() return if (vidList.isNotEmpty()) { VideoContainer(vidList, subList) @@ -686,7 +684,7 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() { } } - private fun AniVideoToSaiVideo(aniVideo: Video): ani.dantotsu.parsers.Video { + private fun aniVideoToSaiVideo(aniVideo: Video): ani.dantotsu.parsers.Video { // Find the number value from the .quality string val number = Regex("""\d+""").find(aniVideo.quality)?.value?.toInt() ?: 0 @@ -789,9 +787,9 @@ class VideoServerPassthrough(val videoServer: VideoServer) : VideoExtractor() { } - private fun TrackToSubtitle(track: Track): Subtitle { + private fun trackToSubtitle(track: Track): Subtitle { //use Dispatchers.IO to make a HTTP request to determine the subtitle type - var type: SubtitleType? = null + var type: SubtitleType? runBlocking { type = findSubtitleType(track.url) } diff --git a/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt index 1c9c3b6c..57098894 100644 --- a/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/OfflineAnimeParser.kt @@ -4,6 +4,7 @@ import android.net.Uri import android.os.Environment import ani.dantotsu.currContext import ani.dantotsu.download.DownloadsManager +import ani.dantotsu.media.MediaType import ani.dantotsu.media.anime.AnimeNameAdapter import ani.dantotsu.tryWithSuspend import eu.kanade.tachiyomi.animesource.model.SAnime @@ -132,16 +133,16 @@ class OfflineVideoExtractor(val videoServer: VideoServer) : VideoExtractor() { currContext()?.let { DownloadsManager.getDirectory( it, - ani.dantotsu.download.DownloadedType.Type.ANIME, + MediaType.ANIME, title, episode - ).listFiles()?.forEach { - if (it.name.contains("subtitle")) { + ).listFiles()?.forEach { file -> + if (file.name.contains("subtitle")) { return listOf( Subtitle( "Downloaded Subtitle", - Uri.fromFile(it).toString(), - determineSubtitletype(it.absolutePath) + Uri.fromFile(file).toString(), + determineSubtitletype(file.absolutePath) ) ) } diff --git a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstallReceiver.kt b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionFileObserver.kt similarity index 98% rename from app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstallReceiver.kt rename to app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionFileObserver.kt index 056118a5..b69cea44 100644 --- a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstallReceiver.kt +++ b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionFileObserver.kt @@ -1,7 +1,6 @@ package ani.dantotsu.parsers.novel import android.os.FileObserver -import android.util.Log import ani.dantotsu.parsers.novel.FileObserver.fileObserver import ani.dantotsu.util.Logger import java.io.File diff --git a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstaller.kt b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstaller.kt index 14d7b75e..818713c2 100644 --- a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstaller.kt +++ b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionInstaller.kt @@ -10,7 +10,6 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore -import android.util.Log import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.core.net.toUri @@ -63,7 +62,7 @@ internal class NovelExtensionInstaller(private val context: Context) { * @param url The url of the apk. * @param extension The extension to install. */ - fun downloadAndInstall(url: String, extension: NovelExtension) = Observable.defer { + fun downloadAndInstall(url: String, extension: NovelExtension): Observable = Observable.defer { val pkgName = extension.pkgName val oldDownload = activeDownloads[pkgName] diff --git a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt index 78535115..d4c94da5 100644 --- a/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt +++ b/app/src/main/java/ani/dantotsu/parsers/novel/NovelExtensionLoader.kt @@ -5,11 +5,10 @@ import android.content.pm.PackageInfo import android.content.pm.PackageManager.GET_SIGNATURES import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES import android.os.Build -import android.util.Log import ani.dantotsu.connections.crashlytics.CrashlyticsInterface -import ani.dantotsu.util.Logger import ani.dantotsu.parsers.NovelInterface import ani.dantotsu.snackString +import ani.dantotsu.util.Logger import dalvik.system.PathClassLoader import eu.kanade.tachiyomi.util.lang.Hash import uy.kohesive.injekt.Injekt @@ -134,10 +133,10 @@ internal object NovelExtensionLoader { } Logger.log("isFileWritable: ${file.canWrite()}") val classLoader = PathClassLoader(file.absolutePath, null, context.classLoader) - val className = + val extensionClassName = "some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className" - val loadedClass = classLoader.loadClass(className) - val instance = loadedClass.newInstance() + val loadedClass = classLoader.loadClass(extensionClassName) + val instance = loadedClass.getDeclaredConstructor().newInstance() val novelInterfaceInstance = instance as? NovelInterface listOfNotNull(novelInterfaceInstance) } catch (e: Exception) { diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt index 78867e37..5dac823a 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt @@ -3,6 +3,7 @@ package ani.dantotsu.profile import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.content.Intent +import android.content.res.Configuration import android.os.Bundle import android.util.TypedValue import android.view.View @@ -11,6 +12,7 @@ import android.widget.PopupMenu import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle @@ -47,7 +49,6 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene private var selected: Int = 0 private lateinit var navBar: AnimatedBottomBar - @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() @@ -56,8 +57,14 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene setContentView(binding.root) screenWidth = resources.displayMetrics.widthPixels.toFloat() navBar = binding.profileNavBar - navBar.updateLayoutParams { bottomMargin = navBarHeight } - + val navBarRightMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0 + val navBarBottomMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight + navBar.updateLayoutParams { + rightMargin = navBarRightMargin + bottomMargin = navBarBottomMargin + } val feedTab = navBar.createTab(R.drawable.ic_round_filter_24, "Feed") val profileTab = navBar.createTab(R.drawable.ic_round_person_24, "Profile") val statsTab = navBar.createTab(R.drawable.ic_stats_24, "Stats") @@ -105,20 +112,30 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene val userLevel = intent.getStringExtra("userLVL") ?: "" binding.followButton.visibility = if (user.id == Anilist.userid || Anilist.userid == null) View.GONE else View.VISIBLE - binding.followButton.text = - if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow" - if (user.isFollowing && user.isFollower) binding.followButton.text = "Mutual" + binding.followButton.text = getString( + when { + user.isFollowing -> R.string.unfollow + user.isFollower -> R.string.follows_you + else -> R.string.follow + } + ) + if (user.isFollowing && user.isFollower) binding.followButton.text = getString(R.string.mutual) binding.followButton.setOnClickListener { lifecycleScope.launch(Dispatchers.IO) { val res = Anilist.query.toggleFollow(user.id) if (res?.data?.toggleFollow != null) { withContext(Dispatchers.Main) { - snackString("Success") + snackString(R.string.success) user.isFollowing = res.data.toggleFollow.isFollowing - binding.followButton.text = - if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow" - if (user.isFollowing && user.isFollower) binding.followButton.text = - "Mutual" + binding.followButton.text = getString( + when { + user.isFollowing -> R.string.unfollow + user.isFollower -> R.string.follows_you + else -> R.string.follow + } + ) + if (user.isFollowing && user.isFollower) + binding.followButton.text = getString(R.string.mutual) } } } @@ -172,7 +189,8 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene ) } - binding.profileUserName.text = "${user.name} $userLevel" + val userLevelText = "${user.name} $userLevel" + binding.profileUserName.text = userLevelText if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.profileBannerImage.pause() blurImage(binding.profileBannerImage, user.bannerImage ?: user.avatar?.medium) binding.profileBannerImage.updateLayoutParams { height += statusBarHeight } @@ -196,7 +214,7 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene ContextCompat.startActivity( this@ProfileActivity, Intent(this@ProfileActivity, FollowActivity::class.java) - .putExtra("title", "Followers") + .putExtra("title", getString(R.string.followers)) .putExtra("userId", user.id), null ) @@ -279,6 +297,17 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene } } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + val rightMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) navBarHeight else 0 + val bottomMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight + val params : ViewGroup.MarginLayoutParams = + navBar.layoutParams as ViewGroup.MarginLayoutParams + params.updateMargins(right = rightMargin, bottom = bottomMargin) + } + override fun onResume() { if (this::navBar.isInitialized) { navBar.selectTabAt(selected) @@ -301,4 +330,4 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene else -> ProfileFragment.newInstance(user) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt index e361199d..d008f966 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt @@ -11,6 +11,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.LiveData @@ -33,6 +34,7 @@ import ani.dantotsu.setSlideIn import ani.dantotsu.setSlideUp import ani.dantotsu.util.AniMarkdown.Companion.getFullAniHTML import ani.dantotsu.util.Logger +import eu.kanade.tachiyomi.util.system.getSerializableCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -57,7 +59,7 @@ class ProfileFragment : Fragment() { super.onViewCreated(view, savedInstanceState) activity = requireActivity() as ProfileActivity - user = arguments?.getSerializable("user") as Query.UserProfile + user = arguments?.getSerializableCompat("user") as Query.UserProfile viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { model.setData(user.id) } @@ -101,8 +103,7 @@ class ProfileFragment : Fragment() { } } - binding.userInfoContainer.visibility = - if (user.about != null) View.VISIBLE else View.GONE + binding.userInfoContainer.isVisible = user.about != null binding.statsEpisodesWatched.text = user.statistics.anime.episodesWatched.toString() diff --git a/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt b/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt index b90cba33..2f01cac6 100644 --- a/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/StatsFragment.kt @@ -20,6 +20,7 @@ import ani.dantotsu.profile.ChartBuilder.Companion.StatType import ani.dantotsu.statusBarHeight import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType import com.xwray.groupie.GroupieAdapter +import eu.kanade.tachiyomi.util.system.getSerializableCompat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -48,7 +49,7 @@ class StatsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) activity = requireActivity() as ProfileActivity - user = arguments?.getSerializable("user") as Query.UserProfile + user = arguments?.getSerializableCompat("user") as Query.UserProfile binding.statisticList.adapter = adapter binding.statisticList.recycledViewPool.setMaxRecycledViews(0, 0) @@ -95,7 +96,7 @@ class StatsFragment : } } else { stats.removeAll( - stats.filter { it?.id == Anilist.userid } + stats.filter { it?.id == Anilist.userid }.toSet() ) loadStats(type == MediaType.ANIME) } @@ -445,6 +446,7 @@ class StatsFragment : }.toMutableList() chartPackets.clear() chartPackets.addAll(standardizedPackets) + @Suppress("UNCHECKED_CAST") val genreChart = ChartBuilder.buildChart( activity, ChartType.TwoDimensional, @@ -499,6 +501,7 @@ class StatsFragment : }.toMutableList() chartPackets.clear() chartPackets.addAll(standardizedPackets) + @Suppress("UNCHECKED_CAST") val tagChart = ChartBuilder.buildChart( activity, ChartType.TwoDimensional, @@ -553,6 +556,7 @@ class StatsFragment : }.toMutableList() chartPackets.clear() chartPackets.addAll(standardizedPackets) + @Suppress("UNCHECKED_CAST") val countryChart = ChartBuilder.buildChart( activity, ChartType.OneDimensional, @@ -609,6 +613,7 @@ class StatsFragment : }.toMutableList() chartPackets.clear() chartPackets.addAll(standardizedPackets) + @Suppress("UNCHECKED_CAST") val voiceActorsChart = ChartBuilder.buildChart( activity, ChartType.TwoDimensional, @@ -663,6 +668,7 @@ class StatsFragment : }.toMutableList() chartPackets.clear() chartPackets.addAll(standardizedPackets) + @Suppress("UNCHECKED_CAST") val studioChart = ChartBuilder.buildChart( activity, ChartType.TwoDimensional, @@ -720,6 +726,7 @@ class StatsFragment : }.toMutableList() chartPackets.clear() chartPackets.addAll(standardizedPackets) + @Suppress("UNCHECKED_CAST") val staffChart = ChartBuilder.buildChart( activity, ChartType.TwoDimensional, diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt index c55e6a82..0f9ed865 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -3,6 +3,7 @@ package ani.dantotsu.profile.activity import android.annotation.SuppressLint import android.view.View import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R @@ -38,7 +39,6 @@ class ActivityItem( private lateinit var binding: ItemActivityBinding private lateinit var repliesAdapter: GroupieAdapter - @SuppressLint("SetTextI18n") override fun bind(viewBinding: ItemActivityBinding, position: Int) { binding = viewBinding setAnimation(binding.root.context, binding.root) @@ -58,8 +58,7 @@ class ActivityItem( val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red) val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp) binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor) - binding.commentRepliesContainer.visibility = - if (activity.replyCount > 0) View.VISIBLE else View.GONE + binding.commentRepliesContainer.isVisible = activity.replyCount > 0 binding.commentRepliesContainer.setOnClickListener { when (binding.activityReplies.visibility) { View.GONE -> { @@ -73,13 +72,13 @@ class ActivityItem( } ?: emptyList() repliesAdapter.addAll(replyItems) binding.activityReplies.visibility = View.VISIBLE - binding.commentTotalReplies.text = "Hide replies" + binding.commentTotalReplies.setText(R.string.hide_replies) } else -> { repliesAdapter.clear() binding.activityReplies.visibility = View.GONE - binding.commentTotalReplies.text = "View replies" + binding.commentTotalReplies.setText(R.string.view_replies) } } @@ -127,7 +126,9 @@ class ActivityItem( binding.activityContent.visibility = View.GONE binding.activityBannerContainer.visibility = View.VISIBLE binding.activityMediaName.text = activity.media?.title?.userPreferred - binding.activityText.text = "${activity.user!!.name} ${activity.status} ${activity.progress ?: activity.media?.title?.userPreferred}" + val activityText = "${activity.user!!.name} ${activity.status} ${activity.progress + ?: activity.media?.title?.userPreferred}" + binding.activityText.text = activityText binding.activityCover.loadImage(cover) blurImage(binding.activityBannerImage, banner ?: cover) binding.activityAvatarContainer.setOnClickListener { diff --git a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt index 98f0492b..0b35f047 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt @@ -1,9 +1,11 @@ package ani.dantotsu.profile.activity +import android.content.res.Configuration import android.os.Bundle import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.view.updateLayoutParams +import androidx.core.view.updateMargins import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle @@ -28,14 +30,16 @@ class FeedActivity : AppCompatActivity() { binding = ActivityFeedBinding.inflate(layoutInflater) setContentView(binding.root) navBar = binding.feedNavBar - navBar.updateLayoutParams { bottomMargin += navBarHeight } + val navBarMargin = if (resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight + navBar.updateLayoutParams { bottomMargin = navBarMargin } val personalTab = navBar.createTab(R.drawable.ic_round_person_24, "Following") val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global") navBar.addTab(personalTab) navBar.addTab(globalTab) binding.listTitle.text = getString(R.string.activities) binding.feedViewPager.updateLayoutParams { - bottomMargin += navBarHeight + bottomMargin = navBarMargin topMargin += statusBarHeight } binding.listToolbar.updateLayoutParams { topMargin += statusBarHeight } @@ -57,10 +61,20 @@ class FeedActivity : AppCompatActivity() { } }) binding.listBack.setOnClickListener { - onBackPressed() + onBackPressedDispatcher.onBackPressed() } } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + val margin = if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 0 else navBarHeight + val params : ViewGroup.MarginLayoutParams = + binding.feedViewPager.layoutParams as ViewGroup.MarginLayoutParams + val paramsNav : ViewGroup.MarginLayoutParams = navBar.layoutParams as ViewGroup.MarginLayoutParams + params.updateMargins(bottom = margin) + paramsNav.updateMargins(bottom = margin) + } + override fun onResume() { super.onResume() navBar.selectTabAt(selected) @@ -80,4 +94,4 @@ class FeedActivity : AppCompatActivity() { } } } -} \ No newline at end of file +} 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 9b68f377..a8214004 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt @@ -11,6 +11,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.databinding.ActivityFollowBinding @@ -37,14 +38,14 @@ class NotificationActivity : AppCompatActivity() { private var currentPage: Int = 1 private var hasNextPage: Boolean = true - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() initActivity(this) binding = ActivityFollowBinding.inflate(layoutInflater) setContentView(binding.root) - binding.listTitle.text = "Notifications" + binding.listTitle.text = getString(R.string.notifications) binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } @@ -57,7 +58,7 @@ class NotificationActivity : AppCompatActivity() { binding.followerGrid.visibility = ViewGroup.GONE binding.followerList.visibility = ViewGroup.GONE binding.listBack.setOnClickListener { - onBackPressed() + onBackPressedDispatcher.onBackPressed() } binding.listProgressBar.visibility = ViewGroup.VISIBLE val activityId = intent.getIntExtra("activityId", -1) 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 bf56db20..0276e3f7 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt @@ -12,6 +12,7 @@ import ani.dantotsu.databinding.ItemNotificationBinding import ani.dantotsu.loadImage import ani.dantotsu.profile.activity.NotificationActivity.Companion.NotificationClickType import ani.dantotsu.setAnimation +import ani.dantotsu.toPx import com.xwray.groupie.viewbinding.BindableItem class NotificationItem( @@ -40,23 +41,11 @@ class NotificationItem( ?: notification.media?.coverImage?.large blurImage(binding.notificationBannerImage, cover) - val defaultHeight = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 153f, - binding.root.context.resources.displayMetrics - ).toInt() + val defaultHeight = 153.toPx - val userHeight = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 90f, - binding.root.context.resources.displayMetrics - ).toInt() + val userHeight = 90.toPx - val textMarginStart = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 125f, - binding.root.context.resources.displayMetrics - ).toInt() + val textMarginStart = 125.toPx if (user) { binding.notificationCover.visibility = View.GONE diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index a97a644d..6c10242d 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -29,7 +29,6 @@ import com.google.android.material.tabs.TabLayoutMediator class ExtensionsActivity : AppCompatActivity() { lateinit var binding: ActivityExtensionsBinding - @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt index f7108bc7..0149b8ba 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt @@ -14,6 +14,8 @@ import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.core.app.NotificationCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DiffUtil @@ -59,15 +61,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { val name = pkg.name val changeUIVisibility: (Boolean) -> Unit = { show -> val activity = requireActivity() as ExtensionsActivity - val visibility = if (show) View.VISIBLE else View.GONE - activity.findViewById(R.id.viewPager).visibility = visibility - activity.findViewById(R.id.tabLayout).visibility = visibility - activity.findViewById(R.id.searchView).visibility = visibility - activity.findViewById(R.id.languageselect).visibility = visibility + activity.findViewById(R.id.viewPager).isVisible = show + activity.findViewById(R.id.tabLayout).isVisible = show + activity.findViewById(R.id.searchView).isVisible = show + activity.findViewById(R.id.languageselect).isVisible = show activity.findViewById(R.id.extensions).text = if (show) getString(R.string.extensions) else name - activity.findViewById(R.id.fragmentExtensionsContainer).visibility = - if (show) View.GONE else View.VISIBLE + activity.findViewById(R.id.fragmentExtensionsContainer).isGone = show } var itemSelected = false val allSettings = pkg.sources.filterIsInstance() @@ -294,13 +294,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { return ViewHolder(view) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) val nsfw = if (extension.isNsfw) "(18+)" else "" val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) holder.extensionNameTextView.text = extension.name - holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" + val versionText = "$lang ${extension.versionName} $nsfw" + holder.extensionVersionTextView.text = versionText if (!skipIcons) { holder.extensionIconImageView.setImageDrawable(extension.icon) } diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt index 2da2a777..12ee40be 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt @@ -15,6 +15,8 @@ import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.core.app.NotificationCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DiffUtil @@ -57,15 +59,13 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { val name = pkg.name val changeUIVisibility: (Boolean) -> Unit = { show -> val activity = requireActivity() as ExtensionsActivity - val visibility = if (show) View.VISIBLE else View.GONE - activity.findViewById(R.id.viewPager).visibility = visibility - activity.findViewById(R.id.tabLayout).visibility = visibility - activity.findViewById(R.id.searchView).visibility = visibility - activity.findViewById(R.id.languageselect).visibility = visibility + activity.findViewById(R.id.viewPager).isVisible = show + activity.findViewById(R.id.tabLayout).isVisible = show + activity.findViewById(R.id.searchView).isVisible = show + activity.findViewById(R.id.languageselect).isVisible = show activity.findViewById(R.id.extensions).text = if (show) getString(R.string.extensions) else name - activity.findViewById(R.id.fragmentExtensionsContainer).visibility = - if (show) View.GONE else View.VISIBLE + activity.findViewById(R.id.fragmentExtensionsContainer).isGone = show } var itemSelected = false val allSettings = pkg.sources.filterIsInstance() @@ -290,13 +290,14 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { MangaSources.performReorderMangaSources() } - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") + @SuppressLint("ClickableViewAccessibility") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) // Use getItem() from ListAdapter val nsfw = if (extension.isNsfw) "(18+)" else "" val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) holder.extensionNameTextView.text = extension.name - holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" + val versionText = "$lang ${extension.versionName} $nsfw" + holder.extensionVersionTextView.text = versionText if (!skipIcons) { holder.extensionIconImageView.setImageDrawable(extension.icon) } diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt index 5c46a5c9..05314985 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledNovelExtensionsFragment.kt @@ -221,13 +221,13 @@ class InstalledNovelExtensionsFragment : Fragment(), SearchQueryHandler { return ViewHolder(view) } - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) // Use getItem() from ListAdapter val nsfw = "" val lang = LanguageMapper.mapLanguageCodeToName("all") holder.extensionNameTextView.text = extension.name - holder.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" + val versionText = "$lang ${extension.versionName} $nsfw" + holder.extensionVersionTextView.text = versionText if (!skipIcons) { holder.extensionIconImageView.setImageDrawable(extension.icon) } diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt index 5faccc17..0a7b7d9e 100644 --- a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt @@ -127,6 +127,7 @@ class PlayerSettingsActivity : AppCompatActivity() { binding.playerSettingsTimeStamps.isChecked = PrefManager.getVal(PrefName.TimeStampsEnabled) binding.playerSettingsTimeStamps.setOnCheckedChangeListener { _, isChecked -> PrefManager.setVal(PrefName.TimeStampsEnabled, isChecked) + binding.playerSettingsAutoSkipOpEd.isEnabled = isChecked } binding.playerSettingsTimeStampsAutoHide.isChecked = PrefManager.getVal(PrefName.AutoHideTimeStamps) @@ -148,6 +149,7 @@ class PlayerSettingsActivity : AppCompatActivity() { // Auto binding.playerSettingsAutoSkipOpEd.isChecked = PrefManager.getVal(PrefName.AutoSkipOPED) + binding.playerSettingsAutoSkipOpEd.isEnabled = binding.playerSettingsTimeStamps.isChecked binding.playerSettingsAutoSkipOpEd.setOnCheckedChangeListener { _, isChecked -> PrefManager.setVal(PrefName.AutoSkipOPED, isChecked) } @@ -172,7 +174,7 @@ class PlayerSettingsActivity : AppCompatActivity() { binding.playerSettingsAskChapterZero.isChecked = PrefManager.getVal(PrefName.ChapterZeroPlayer) binding.playerSettingsAskChapterZero.isEnabled = - !PrefManager.getVal(PrefName.AskIndividualPlayer) + !binding.playerSettingsAskUpdateProgress.isChecked binding.playerSettingsAskChapterZero.setOnCheckedChangeListener { _, isChecked -> PrefManager.setVal(PrefName.ChapterZeroPlayer, isChecked) } @@ -411,7 +413,7 @@ class PlayerSettingsActivity : AppCompatActivity() { "Magenta" ) val subBackgroundDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.outline_sub_color)) + .setTitle(getString(R.string.sub_background_color_select)) binding.videoSubColorBackground.setOnClickListener { val dialog = subBackgroundDialog.setSingleChoiceItems( colorsSubBackground, @@ -438,7 +440,7 @@ class PlayerSettingsActivity : AppCompatActivity() { "Magenta" ) val subWindowDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.outline_sub_color)) + .setTitle(getString(R.string.sub_window_color_select)) binding.videoSubColorWindow.setOnClickListener { val dialog = subWindowDialog.setSingleChoiceItems( colorsSubWindow, diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 4394156c..12b0701d 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -21,7 +21,6 @@ import android.view.animation.AnimationUtils import android.view.inputmethod.EditorInfo import android.widget.ArrayAdapter import android.widget.TextView -import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.OptIn @@ -41,20 +40,28 @@ import ani.dantotsu.connections.discord.Discord import ani.dantotsu.connections.mal.MAL import ani.dantotsu.copyToClipboard import ani.dantotsu.currContext +import ani.dantotsu.databinding.ActivitySettingsAboutBinding +import ani.dantotsu.databinding.ActivitySettingsAccountsBinding +import ani.dantotsu.databinding.ActivitySettingsAnimeBinding import ani.dantotsu.databinding.ActivitySettingsBinding -import ani.dantotsu.download.DownloadedType +import ani.dantotsu.databinding.ActivitySettingsCommonBinding +import ani.dantotsu.databinding.ActivitySettingsExtensionsBinding +import ani.dantotsu.databinding.ActivitySettingsMangaBinding +import ani.dantotsu.databinding.ActivitySettingsNotificationsBinding +import ani.dantotsu.databinding.ActivitySettingsThemeBinding import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.video.ExoplayerDownloadService import ani.dantotsu.downloadsPermission import ani.dantotsu.initActivity import ani.dantotsu.loadImage -import ani.dantotsu.util.Logger +import ani.dantotsu.media.MediaType import ani.dantotsu.navBarHeight import ani.dantotsu.notifications.TaskScheduler -import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.anilist.AnilistNotificationWorker +import ani.dantotsu.notifications.comment.CommentNotificationWorker import ani.dantotsu.notifications.subscription.SubscriptionNotificationWorker.Companion.checkIntervals import ani.dantotsu.openLinkInBrowser +import ani.dantotsu.openLinkInYouTube import ani.dantotsu.openSettings import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.CustomBottomDialog @@ -71,6 +78,7 @@ import ani.dantotsu.startMainActivity import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast +import ani.dantotsu.util.Logger import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText import eltos.simpledialogfragment.SimpleDialog @@ -92,11 +100,18 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene override fun handleOnBackPressed() = startMainActivity(this@SettingsActivity) } lateinit var binding: ActivitySettingsBinding + private lateinit var bindingAccounts: ActivitySettingsAccountsBinding + private lateinit var bindingTheme: ActivitySettingsThemeBinding + private lateinit var bindingExtensions: ActivitySettingsExtensionsBinding + private lateinit var bindingCommon: ActivitySettingsCommonBinding + private lateinit var bindingAnime: ActivitySettingsAnimeBinding + private lateinit var bindingManga: ActivitySettingsMangaBinding + private lateinit var bindingNotifications: ActivitySettingsNotificationsBinding + private lateinit var bindingAbout: ActivitySettingsAboutBinding private val extensionInstaller = Injekt.get().extensionInstaller() private var cursedCounter = 0 @OptIn(UnstableApi::class) - @SuppressLint("SetTextI18n", "Recycle") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() @@ -125,13 +140,13 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene salt ) } catch (e: Exception) { - toast("Incorrect password") + toast(getString(R.string.incorrect_password)) return@passwordAlertDialog } if (PreferencePackager.unpack(decryptedJson)) restartApp() } else { - toast("Password cannot be empty") + toast(getString(R.string.password_cannot_be_empty)) } } } else if (name.endsWith(".ani")) { @@ -139,11 +154,11 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene if (PreferencePackager.unpack(decryptedJson)) restartApp() } else { - toast("Unknown file type") + toast(getString(R.string.unknown_file_type)) } } catch (e: Exception) { e.printStackTrace() - toast("Error importing settings") + toast(getString(R.string.error_importing_settings)) } } } @@ -166,253 +181,420 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene onBackPressedDispatcher.onBackPressed() } - binding.settingsUseMaterialYou.isChecked = PrefManager.getVal(PrefName.UseMaterialYou) - binding.settingsUseMaterialYou.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.UseMaterialYou, isChecked) - if (isChecked) binding.settingsUseCustomTheme.isChecked = false - restartApp() - } - - binding.settingsUseCustomTheme.isChecked = PrefManager.getVal(PrefName.UseCustomTheme) - binding.settingsUseCustomTheme.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.UseCustomTheme, isChecked) - if (isChecked) { - binding.settingsUseMaterialYou.isChecked = false + bindingAccounts = ActivitySettingsAccountsBinding.bind(binding.root).apply { + settingsAccountHelp.setOnClickListener { + val title = getString(R.string.account_help) + val full = getString(R.string.full_account_help) + CustomBottomDialog.newInstance().apply { + setTitleText(title) + addView( + TextView(it.context).apply { + val markWon = Markwon.builder(it.context) + .usePlugin(SoftBreakAddsNewLinePlugin.create()).build() + markWon.setMarkdown(this, full) + } + ) + }.show(supportFragmentManager, "dialog") } - restartApp() - } + fun reload() { + if (Anilist.token != null) { + settingsAnilistLogin.setText(R.string.logout) + settingsAnilistLogin.setOnClickListener { + Anilist.removeSavedToken() + restartMainActivity.isEnabled = true + reload() + } + settingsAnilistUsername.visibility = View.VISIBLE + settingsAnilistUsername.text = Anilist.username + settingsAnilistAvatar.loadImage(Anilist.avatar) - binding.settingsUseSourceTheme.isChecked = PrefManager.getVal(PrefName.UseSourceTheme) - binding.settingsUseSourceTheme.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.UseSourceTheme, isChecked) - restartApp() - } + settingsMALLoginRequired.visibility = View.GONE + settingsMALLogin.visibility = View.VISIBLE + settingsMALUsername.visibility = View.VISIBLE - binding.settingsUseOLED.isChecked = PrefManager.getVal(PrefName.UseOLED) - binding.settingsUseOLED.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.UseOLED, isChecked) - restartApp() - } + if (MAL.token != null) { + settingsMALLogin.setText(R.string.logout) + settingsMALLogin.setOnClickListener { + MAL.removeSavedToken() + restartMainActivity.isEnabled = true + reload() + } + settingsMALUsername.visibility = View.VISIBLE + settingsMALUsername.text = MAL.username + settingsMALAvatar.loadImage(MAL.avatar) + } else { + settingsMALAvatar.setImageResource(R.drawable.ic_round_person_24) + settingsMALUsername.visibility = View.GONE + settingsMALLogin.setText(R.string.login) + settingsMALLogin.setOnClickListener { + MAL.loginIntent(this@SettingsActivity) + } + } + } else { + settingsAnilistAvatar.setImageResource(R.drawable.ic_round_person_24) + settingsAnilistUsername.visibility = View.GONE + settingsAnilistLogin.setText(R.string.login) + settingsAnilistLogin.setOnClickListener { + Anilist.loginIntent(this@SettingsActivity) + } + settingsMALLoginRequired.visibility = View.VISIBLE + settingsMALLogin.visibility = View.GONE + settingsMALUsername.visibility = View.GONE + } - val themeString: String = PrefManager.getVal(PrefName.Theme) - binding.themeSwitcher.setText( - themeString.substring(0, 1) + themeString.substring(1).lowercase() - ) + if (Discord.token != null) { + val id = PrefManager.getVal(PrefName.DiscordId, null as String?) + val avatar = PrefManager.getVal(PrefName.DiscordAvatar, null as String?) + val username = PrefManager.getVal(PrefName.DiscordUserName, null as String?) + if (id != null && avatar != null) { + settingsDiscordAvatar.loadImage("https://cdn.discordapp.com/avatars/$id/$avatar.png") + } + settingsDiscordUsername.visibility = View.VISIBLE + settingsDiscordUsername.text = + username ?: Discord.token?.replace(Regex("."), "*") + settingsDiscordLogin.setText(R.string.logout) + settingsDiscordLogin.setOnClickListener { + Discord.removeSavedToken(this@SettingsActivity) + restartMainActivity.isEnabled = true + reload() + } - binding.themeSwitcher.setAdapter( - ArrayAdapter( - this, - R.layout.item_dropdown, - ThemeManager.Companion.Theme.entries - .map { it.theme.substring(0, 1) + it.theme.substring(1).lowercase() }) - ) + imageSwitcher.visibility = View.VISIBLE + var initialStatus = when (PrefManager.getVal(PrefName.DiscordStatus)) { + "online" -> R.drawable.discord_status_online + "idle" -> R.drawable.discord_status_idle + "dnd" -> R.drawable.discord_status_dnd + else -> R.drawable.discord_status_online + } + imageSwitcher.setImageResource(initialStatus) - binding.themeSwitcher.setOnItemClickListener { _, _, i, _ -> - PrefManager.setVal(PrefName.Theme, ThemeManager.Companion.Theme.entries[i].theme) - //ActivityHelper.shouldRefreshMainActivity = true - binding.themeSwitcher.clearFocus() - restartApp() + val zoomInAnimation = + AnimationUtils.loadAnimation(this@SettingsActivity, R.anim.bounce_zoom) + imageSwitcher.setOnClickListener { + var status = "online" + initialStatus = when (initialStatus) { + R.drawable.discord_status_online -> { + status = "idle" + R.drawable.discord_status_idle + } - } + R.drawable.discord_status_idle -> { + status = "dnd" + R.drawable.discord_status_dnd + } + R.drawable.discord_status_dnd -> { + status = "online" + R.drawable.discord_status_online + } - binding.customTheme.setOnClickListener { - val originalColor: Int = PrefManager.getVal(PrefName.CustomThemeInt) + else -> R.drawable.discord_status_online + } - class CustomColorDialog : SimpleColorDialog() { //idk where to put it - override fun onPositiveButtonClick() { - restartApp() - super.onPositiveButtonClick() + PrefManager.setVal(PrefName.DiscordStatus, status) + imageSwitcher.setImageResource(initialStatus) + imageSwitcher.startAnimation(zoomInAnimation) + } + } else { + imageSwitcher.visibility = View.GONE + settingsDiscordAvatar.setImageResource(R.drawable.ic_round_person_24) + settingsDiscordUsername.visibility = View.GONE + settingsDiscordLogin.setText(R.string.login) + settingsDiscordLogin.setOnClickListener { + Discord.warning(this@SettingsActivity) + .show(supportFragmentManager, "dialog") + } } } - - val tag = "colorPicker" - CustomColorDialog().title("Custom Theme") - .colorPreset(originalColor) - .colors(this, SimpleColorDialog.MATERIAL_COLOR_PALLET) - .allowCustom(true) - .showOutline(0x46000000) - .gridNumColumn(5) - .choiceMode(SimpleColorDialog.SINGLE_CHOICE) - .neg() - .show(this, tag) + reload() } - binding.settingsPlayer.setOnClickListener { - startActivity(Intent(this, PlayerSettingsActivity::class.java)) + bindingTheme = ActivitySettingsThemeBinding.bind(binding.root).apply { + settingsUseMaterialYou.isChecked = + PrefManager.getVal(PrefName.UseMaterialYou) + settingsUseMaterialYou.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.UseMaterialYou, isChecked) + if (isChecked) settingsUseCustomTheme.isChecked = false + restartApp() + } + + settingsUseCustomTheme.isChecked = + PrefManager.getVal(PrefName.UseCustomTheme) + settingsUseCustomTheme.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.UseCustomTheme, isChecked) + if (isChecked) { + settingsUseMaterialYou.isChecked = false + } + + restartApp() + } + + settingsUseSourceTheme.isChecked = + PrefManager.getVal(PrefName.UseSourceTheme) + settingsUseSourceTheme.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.UseSourceTheme, isChecked) + restartApp() + } + + settingsUseOLED.isChecked = PrefManager.getVal(PrefName.UseOLED) + settingsUseOLED.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.UseOLED, isChecked) + restartApp() + } + + val themeString: String = PrefManager.getVal(PrefName.Theme) + val themeText = themeString.substring(0, 1) + themeString.substring(1).lowercase() + themeSwitcher.setText(themeText) + + themeSwitcher.setAdapter( + ArrayAdapter( + this@SettingsActivity, + R.layout.item_dropdown, + ThemeManager.Companion.Theme.entries + .map { it.theme.substring(0, 1) + it.theme.substring(1).lowercase() }) + ) + + themeSwitcher.setOnItemClickListener { _, _, i, _ -> + PrefManager.setVal(PrefName.Theme, ThemeManager.Companion.Theme.entries[i].theme) + //ActivityHelper.shouldRefreshMainActivity = true + themeSwitcher.clearFocus() + restartApp() + + } + + + customTheme.setOnClickListener { + val originalColor: Int = PrefManager.getVal(PrefName.CustomThemeInt) + + class CustomColorDialog : SimpleColorDialog() { //idk where to put it + override fun onPositiveButtonClick() { + restartApp() + super.onPositiveButtonClick() + } + } + + val tag = "colorPicker" + CustomColorDialog().title(R.string.custom_theme) + .colorPreset(originalColor) + .colors(this@SettingsActivity, SimpleColorDialog.MATERIAL_COLOR_PALLET) + .allowCustom(true) + .showOutline(0x46000000) + .gridNumColumn(5) + .choiceMode(SimpleColorDialog.SINGLE_CHOICE) + .neg() + .show(this@SettingsActivity, tag) + } + + var previous: View = when (PrefManager.getVal(PrefName.DarkMode)) { + 0 -> settingsUiAuto + 1 -> settingsUiLight + 2 -> settingsUiDark + else -> settingsUiAuto + } + previous.alpha = 1f + fun uiTheme(mode: Int, current: View) { + previous.alpha = 0.33f + previous = current + current.alpha = 1f + PrefManager.setVal(PrefName.DarkMode, mode) + Refresh.all() + finish() + startActivity(Intent(this@SettingsActivity, SettingsActivity::class.java)) + initActivity(this@SettingsActivity) + } + + settingsUiAuto.setOnClickListener { + uiTheme(0, it) + } + + settingsUiLight.setOnClickListener { + settingsUseOLED.isChecked = false + uiTheme(1, it) + } + + settingsUiDark.setOnClickListener { + uiTheme(2, it) + } } val managers = arrayOf("Default", "1DM", "ADM") val downloadManagerDialog = - AlertDialog.Builder(this, R.style.MyPopup).setTitle("Download Manager") + AlertDialog.Builder(this, R.style.MyPopup).setTitle(R.string.download_manager) var downloadManager: Int = PrefManager.getVal(PrefName.DownloadManager) - binding.settingsDownloadManager.setOnClickListener { - val dialog = downloadManagerDialog.setSingleChoiceItems( - managers, - downloadManager - ) { dialog, count -> - downloadManager = count - PrefManager.setVal(PrefName.DownloadManager, downloadManager) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) - } - binding.importExportSettings.setOnClickListener { - downloadsPermission(this) - val selectedArray = mutableListOf(false) - val filteredLocations = Location.entries.filter { it.exportable } - selectedArray.addAll(List(filteredLocations.size - 1) { false }) - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Import/Export Settings") - .setMultiChoiceItems( - filteredLocations.map { it.name }.toTypedArray(), - selectedArray.toBooleanArray() - ) { _, which, isChecked -> - selectedArray[which] = isChecked - } - .setPositiveButton("Import...") { dialog, _ -> - openDocumentLauncher.launch(arrayOf("*/*")) - dialog.dismiss() - } - .setNegativeButton("Export...") { dialog, _ -> - if (!selectedArray.contains(true)) { - toast("No location selected") - return@setNegativeButton - } - dialog.dismiss() - val selected = - filteredLocations.filterIndexed { index, _ -> selectedArray[index] } - if (selected.contains(Location.Protected)) { - passwordAlertDialog(true) { password -> - if (password != null) { - savePrefsToDownloads( - "DantotsuSettings", - PrefManager.exportAllPrefs(selected), - this@SettingsActivity, - password - ) - } else { - toast("Password cannot be empty") - } - } - } else { - savePrefsToDownloads( - "DantotsuSettings", - PrefManager.exportAllPrefs(selected), + bindingAnime = ActivitySettingsAnimeBinding.bind(binding.root).apply { + settingsPlayer.setOnClickListener { + startActivity(Intent(this@SettingsActivity, PlayerSettingsActivity::class.java)) + } + + purgeAnimeDownloads.setOnClickListener { + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.purge_anime_downloads) + .setMessage(getString(R.string.purge_confirm, getString(R.string.anime))) + .setPositiveButton(R.string.yes) { dialog, _ -> + val downloadsManager = Injekt.get() + downloadsManager.purgeDownloads(MediaType.ANIME) + DownloadService.sendRemoveAllDownloads( this@SettingsActivity, - null + ExoplayerDownloadService::class.java, + false ) + dialog.dismiss() } - } - .setNeutralButton("Cancel") { dialog, _ -> - dialog.dismiss() - } - .create() - dialog.window?.setDimAmount(0.8f) - dialog.show() - } + .setNegativeButton(R.string.no) { dialog, _ -> + dialog.dismiss() + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } - binding.purgeAnimeDownloads.setOnClickListener { - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Purge Anime Downloads") - .setMessage("Are you sure you want to purge all anime downloads?") - .setPositiveButton("Yes") { dialog, _ -> - val downloadsManager = Injekt.get() - downloadsManager.purgeDownloads(DownloadedType.Type.ANIME) - DownloadService.sendRemoveAllDownloads( - this, - ExoplayerDownloadService::class.java, - false - ) - dialog.dismiss() - } - .setNegativeButton("No") { dialog, _ -> - dialog.dismiss() - } - .create() - dialog.window?.setDimAmount(0.8f) - dialog.show() - } + settingsPreferDub.isChecked = PrefManager.getVal(PrefName.SettingsPreferDub) + settingsPreferDub.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.SettingsPreferDub, isChecked) + } - binding.purgeMangaDownloads.setOnClickListener { - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Purge Manga Downloads") - .setMessage("Are you sure you want to purge all manga downloads?") - .setPositiveButton("Yes") { dialog, _ -> - val downloadsManager = Injekt.get() - downloadsManager.purgeDownloads(DownloadedType.Type.MANGA) - dialog.dismiss() - } - .setNegativeButton("No") { dialog, _ -> - dialog.dismiss() - } - .create() - dialog.window?.setDimAmount(0.8f) - dialog.show() - } - binding.purgeNovelDownloads.setOnClickListener { - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Purge Novel Downloads") - .setMessage("Are you sure you want to purge all novel downloads?") - .setPositiveButton("Yes") { dialog, _ -> - val downloadsManager = Injekt.get() - downloadsManager.purgeDownloads(DownloadedType.Type.NOVEL) - dialog.dismiss() - } - .setNegativeButton("No") { dialog, _ -> - dialog.dismiss() - } - .create() - dialog.window?.setDimAmount(0.8f) - dialog.show() - } + settingsShowYt.isChecked = PrefManager.getVal(PrefName.ShowYtButton) + settingsShowYt.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.ShowYtButton, isChecked) + } - binding.settingsForceLegacyInstall.isChecked = - extensionInstaller.get() == BasePreferences.ExtensionInstaller.LEGACY - binding.settingsForceLegacyInstall.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - extensionInstaller.set(BasePreferences.ExtensionInstaller.LEGACY) - } else { - extensionInstaller.set(BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) + var previousEp: View = when (PrefManager.getVal(PrefName.AnimeDefaultView)) { + 0 -> settingsEpList + 1 -> settingsEpGrid + 2 -> settingsEpCompact + else -> settingsEpList + } + previousEp.alpha = 1f + fun uiEp(mode: Int, current: View) { + previousEp.alpha = 0.33f + previousEp = current + current.alpha = 1f + PrefManager.setVal(PrefName.AnimeDefaultView, mode) + } + + settingsEpList.setOnClickListener { + uiEp(0, it) + } + + settingsEpGrid.setOnClickListener { + uiEp(1, it) + } + + settingsEpCompact.setOnClickListener { + uiEp(2, it) } } - binding.skipExtensionIcons.isChecked = PrefManager.getVal(PrefName.SkipExtensionIcons) - binding.skipExtensionIcons.setOnCheckedChangeListener { _, isChecked -> - PrefManager.getVal(PrefName.SkipExtensionIcons, isChecked) - } - binding.NSFWExtension.isChecked = PrefManager.getVal(PrefName.NSFWExtension) - binding.NSFWExtension.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.NSFWExtension, isChecked) + bindingManga = ActivitySettingsMangaBinding.bind(binding.root).apply { + purgeMangaDownloads.setOnClickListener { + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.purge_manga_downloads) + .setMessage(getString(R.string.purge_confirm, getString(R.string.manga))) + .setPositiveButton(R.string.yes) { dialog, _ -> + val downloadsManager = Injekt.get() + downloadsManager.purgeDownloads(MediaType.MANGA) + dialog.dismiss() + } + .setNegativeButton(R.string.no) { dialog, _ -> + dialog.dismiss() + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + purgeNovelDownloads.setOnClickListener { + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.purge_novel_downloads) + .setMessage(getString(R.string.purge_confirm, getString(R.string.novels))) + .setPositiveButton(R.string.yes) { dialog, _ -> + val downloadsManager = Injekt.get() + downloadsManager.purgeDownloads(MediaType.NOVEL) + dialog.dismiss() + } + .setNegativeButton(R.string.no) { dialog, _ -> + dialog.dismiss() + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + + settingsReader.setOnClickListener { + startActivity(Intent(this@SettingsActivity, ReaderSettingsActivity::class.java)) + } + + var previousChp: View = when (PrefManager.getVal(PrefName.MangaDefaultView)) { + 0 -> settingsChpList + 1 -> settingsChpCompact + else -> settingsChpList + } + previousChp.alpha = 1f + fun uiChp(mode: Int, current: View) { + previousChp.alpha = 0.33f + previousChp = current + current.alpha = 1f + PrefManager.setVal(PrefName.MangaDefaultView, mode) + } + + settingsChpList.setOnClickListener { + uiChp(0, it) + } + + settingsChpCompact.setOnClickListener { + uiChp(1, it) + } } - binding.userAgent.setOnClickListener { - val dialogView = layoutInflater.inflate(R.layout.dialog_user_agent, null) - val editText = dialogView.findViewById(R.id.userAgentTextBox) - editText.setText(PrefManager.getVal(PrefName.DefaultUserAgent)) - val alertDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("User Agent") - .setView(dialogView) - .setPositiveButton("OK") { dialog, _ -> - PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString()) - dialog.dismiss() + bindingExtensions = ActivitySettingsExtensionsBinding.bind(binding.root).apply { + settingsForceLegacyInstall.isChecked = + extensionInstaller.get() == BasePreferences.ExtensionInstaller.LEGACY + settingsForceLegacyInstall.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + extensionInstaller.set(BasePreferences.ExtensionInstaller.LEGACY) + } else { + extensionInstaller.set(BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) } - .setNeutralButton("Reset") { dialog, _ -> - PrefManager.removeVal(PrefName.DefaultUserAgent) - editText.setText("") - dialog.dismiss() - } - .setNegativeButton("Cancel") { dialog, _ -> - dialog.dismiss() - } - .create() + } - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) + skipExtensionIcons.isChecked = + PrefManager.getVal(PrefName.SkipExtensionIcons) + skipExtensionIcons.setOnCheckedChangeListener { _, isChecked -> + PrefManager.getVal(PrefName.SkipExtensionIcons, isChecked) + } + NSFWExtension.isChecked = PrefManager.getVal(PrefName.NSFWExtension) + NSFWExtension.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.NSFWExtension, isChecked) + + } + + userAgent.setOnClickListener { + val dialogView = layoutInflater.inflate(R.layout.dialog_user_agent, null) + val editText = dialogView.findViewById(R.id.userAgentTextBox) + editText.setText(PrefManager.getVal(PrefName.DefaultUserAgent)) + val alertDialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.user_agent) + .setView(dialogView) + .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString()) + dialog.dismiss() + } + .setNeutralButton(getString(R.string.reset)) { dialog, _ -> + PrefManager.removeVal(PrefName.DefaultUserAgent) + editText.setText("") + dialog.dismiss() + } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> + dialog.dismiss() + } + .create() + + alertDialog.show() + alertDialog.window?.setDimAmount(0.8f) + } } @@ -432,162 +614,373 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene "Shecan", "Libre" ) - binding.settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)]) - binding.settingsExtensionDns.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, exDns)) - binding.settingsExtensionDns.setOnItemClickListener { _, _, i, _ -> - PrefManager.setVal(PrefName.DohProvider, i) - binding.settingsExtensionDns.clearFocus() - Toast.makeText(this, "Restart app to apply changes", Toast.LENGTH_LONG).show() + + bindingCommon = ActivitySettingsCommonBinding.bind(binding.root).apply { + settingsDownloadManager.setOnClickListener { + val dialog = downloadManagerDialog.setSingleChoiceItems( + managers, + downloadManager + ) { dialog, count -> + downloadManager = count + PrefManager.setVal(PrefName.DownloadManager, downloadManager) + dialog.dismiss() + }.show() + dialog.window?.setDimAmount(0.8f) + } + + importExportSettings.setOnClickListener { + downloadsPermission(this@SettingsActivity) + val selectedArray = mutableListOf(false) + val filteredLocations = Location.entries.filter { it.exportable } + selectedArray.addAll(List(filteredLocations.size - 1) { false }) + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.import_export_settings) + .setMultiChoiceItems( + filteredLocations.map { it.name }.toTypedArray(), + selectedArray.toBooleanArray() + ) { _, which, isChecked -> + selectedArray[which] = isChecked + } + .setPositiveButton(R.string.button_import) { dialog, _ -> + openDocumentLauncher.launch(arrayOf("*/*")) + dialog.dismiss() + } + .setNegativeButton(R.string.button_export) { dialog, _ -> + if (!selectedArray.contains(true)) { + toast(R.string.no_location_selected) + return@setNegativeButton + } + dialog.dismiss() + val selected = + filteredLocations.filterIndexed { index, _ -> selectedArray[index] } + if (selected.contains(Location.Protected)) { + passwordAlertDialog(true) { password -> + if (password != null) { + savePrefsToDownloads( + "DantotsuSettings", + PrefManager.exportAllPrefs(selected), + this@SettingsActivity, + password + ) + } else { + toast(R.string.password_cannot_be_empty) + } + } + } else { + savePrefsToDownloads( + "DantotsuSettings", + PrefManager.exportAllPrefs(selected), + this@SettingsActivity, + null + ) + } + } + .setNeutralButton(R.string.cancel) { dialog, _ -> + dialog.dismiss() + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + + settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)]) + settingsExtensionDns.setAdapter(ArrayAdapter(this@SettingsActivity, R.layout.item_dropdown, exDns)) + settingsExtensionDns.setOnItemClickListener { _, _, i, _ -> + PrefManager.setVal(PrefName.DohProvider, i) + settingsExtensionDns.clearFocus() + restartApp() + } + + settingsDownloadInSd.isChecked = PrefManager.getVal(PrefName.SdDl) + settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + val arrayOfFiles = ContextCompat.getExternalFilesDirs(this@SettingsActivity, null) + if (arrayOfFiles.size > 1 && arrayOfFiles[1] != null) { + PrefManager.setVal(PrefName.SdDl, true) + } else { + settingsDownloadInSd.isChecked = false + PrefManager.setVal(PrefName.SdDl, true) + snackString(getString(R.string.noSdFound)) + } + } else PrefManager.setVal(PrefName.SdDl, true) + } + + settingsContinueMedia.isChecked = PrefManager.getVal(PrefName.ContinueMedia) + settingsContinueMedia.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.ContinueMedia, isChecked) + } + + settingsRecentlyListOnly.isChecked = PrefManager.getVal(PrefName.RecentlyListOnly) + settingsRecentlyListOnly.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.RecentlyListOnly, isChecked) + } + + var previousStart: View = when (PrefManager.getVal(PrefName.DefaultStartUpTab)) { + 0 -> uiSettingsAnime + 1 -> uiSettingsHome + 2 -> uiSettingsManga + else -> uiSettingsHome + } + previousStart.alpha = 1f + fun uiDefault(mode: Int, current: View) { + previousStart.alpha = 0.33f + previousStart = current + current.alpha = 1f + PrefManager.setVal(PrefName.DefaultStartUpTab, mode) + initActivity(this@SettingsActivity) + } + + uiSettingsAnime.setOnClickListener { + uiDefault(0, it) + } + + uiSettingsHome.setOnClickListener { + uiDefault(1, it) + } + + uiSettingsManga.setOnClickListener { + uiDefault(2, it) + } + + settingsUi.setOnClickListener { + startActivity(Intent(this@SettingsActivity, UserInterfaceSettingsActivity::class.java)) + } } - binding.settingsDownloadInSd.isChecked = PrefManager.getVal(PrefName.SdDl) - binding.settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - val arrayOfFiles = ContextCompat.getExternalFilesDirs(this, null) - if (arrayOfFiles.size > 1 && arrayOfFiles[1] != null) { - PrefManager.setVal(PrefName.SdDl, true) + bindingNotifications = ActivitySettingsNotificationsBinding.bind(binding.root).apply { + var curTime = PrefManager.getVal(PrefName.SubscriptionNotificationInterval) + val timeNames = checkIntervals.map { + val mins = it % 60 + val hours = it / 60 + if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}" + else getString(R.string.do_not_update) + }.toTypedArray() + + settingsSubscriptionsTime.text = + getString(R.string.subscriptions_checking_time_s, timeNames[curTime]) + val speedDialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.subscriptions_checking_time) + settingsSubscriptionsTime.setOnClickListener { + val dialog = speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i -> + curTime = i + settingsSubscriptionsTime.text = + getString(R.string.subscriptions_checking_time_s, timeNames[i]) + PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) + dialog.dismiss() + TaskScheduler.create(this@SettingsActivity, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this@SettingsActivity) + }.show() + dialog.window?.setDimAmount(0.8f) + } + + settingsSubscriptionsTime.setOnLongClickListener { + TaskScheduler.create(this@SettingsActivity, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this@SettingsActivity) + 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) + } + settingsAnilistSubscriptionsTime.text = + getString(R.string.anilist_notifications_checking_time, aItems[PrefManager.getVal(PrefName.AnilistNotificationInterval)]) + settingsAnilistSubscriptionsTime.setOnClickListener { + + val selected = PrefManager.getVal(PrefName.AnilistNotificationInterval) + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.subscriptions_checking_time) + .setSingleChoiceItems(aItems.toTypedArray(), selected) { dialog, i -> + PrefManager.setVal(PrefName.AnilistNotificationInterval, i) + settingsAnilistSubscriptionsTime.text = + getString(R.string.anilist_notifications_checking_time, aItems[i]) + dialog.dismiss() + TaskScheduler.create(this@SettingsActivity, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this@SettingsActivity) + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + + settingsAnilistNotifications.setOnClickListener { + val types = NotificationType.entries.map { it.name } + val filteredTypes = PrefManager.getVal>(PrefName.AnilistFilteredTypes).toMutableSet() + val selected = types.map { filteredTypes.contains(it) }.toBooleanArray() + val dialog = AlertDialog.Builder(this@SettingsActivity, 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) + } + + settingsCommentSubscriptionsTime.text = + getString(R.string.comment_notification_checking_time, cItems[PrefManager.getVal(PrefName.CommentNotificationInterval)]) + settingsCommentSubscriptionsTime.setOnClickListener { + val selected = PrefManager.getVal(PrefName.CommentNotificationInterval) + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.subscriptions_checking_time) + .setSingleChoiceItems(cItems.toTypedArray(), selected) { dialog, i -> + PrefManager.setVal(PrefName.CommentNotificationInterval, i) + settingsCommentSubscriptionsTime.text = + getString(R.string.comment_notification_checking_time, cItems[i]) + dialog.dismiss() + TaskScheduler.create(this@SettingsActivity, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(this@SettingsActivity) + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + + settingsNotificationsCheckingSubscriptions.isChecked = + PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) + settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.SubscriptionCheckingNotifications, isChecked) + } + + settingsNotificationsCheckingSubscriptions.setOnLongClickListener { + openSettings(this@SettingsActivity, null) + } + + settingsNotificationsUseAlarmManager.isChecked = + PrefManager.getVal(PrefName.UseAlarmManager) + + settingsNotificationsUseAlarmManager.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + val alertDialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.use_alarm_manager) + .setMessage(R.string.use_alarm_manager_confirm) + .setPositiveButton(R.string.use) { dialog, _ -> + PrefManager.setVal(PrefName.UseAlarmManager, true) + if (SDK_INT >= Build.VERSION_CODES.S) { + if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) { + val intent = Intent("android.settings.REQUEST_SCHEDULE_EXACT_ALARM") + startActivity(intent) + settingsNotificationsCheckingSubscriptions.isChecked = true + } + } + dialog.dismiss() + } + .setNegativeButton(R.string.cancel) { dialog, _ -> + settingsNotificationsCheckingSubscriptions.isChecked = false + PrefManager.setVal(PrefName.UseAlarmManager, false) + dialog.dismiss() + } + .create() + alertDialog.window?.setDimAmount(0.8f) + alertDialog.show() } else { - binding.settingsDownloadInSd.isChecked = false - PrefManager.setVal(PrefName.SdDl, true) - snackString(getString(R.string.noSdFound)) + PrefManager.setVal(PrefName.UseAlarmManager, false) + TaskScheduler.create(this@SettingsActivity, true).cancelAllTasks() + TaskScheduler.create(this@SettingsActivity, false).scheduleAllTasks(this@SettingsActivity) } - } else PrefManager.setVal(PrefName.SdDl, true) + } } - binding.settingsContinueMedia.isChecked = PrefManager.getVal(PrefName.ContinueMedia) - binding.settingsContinueMedia.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.ContinueMedia, isChecked) - } + bindingAbout = ActivitySettingsAboutBinding.bind(binding.root).apply { + settingsDev.setOnClickListener { + DevelopersDialogFragment().show(supportFragmentManager, "dialog") + } + settingsForks.setOnClickListener { + ForksDialogFragment().show(supportFragmentManager, "dialog") + } + settingsDisclaimer.setOnClickListener { + val title = getString(R.string.disclaimer) + val text = TextView(this@SettingsActivity) + text.setText(R.string.full_disclaimer) - binding.settingsRecentlyListOnly.isChecked = PrefManager.getVal(PrefName.RecentlyListOnly) - binding.settingsRecentlyListOnly.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.RecentlyListOnly, isChecked) - } - binding.settingsPreferDub.isChecked = PrefManager.getVal(PrefName.SettingsPreferDub) - binding.settingsPreferDub.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.SettingsPreferDub, isChecked) - } + CustomBottomDialog.newInstance().apply { + setTitleText(title) + addView(text) + setNegativeButton(currContext()!!.getString(R.string.close)) { + dismiss() + } + show(supportFragmentManager, "dialog") + } + } - binding.settingsReader.setOnClickListener { - startActivity(Intent(this, ReaderSettingsActivity::class.java)) - } + settingsFAQ.setOnClickListener { + startActivity(Intent(this@SettingsActivity, FAQActivity::class.java)) + } - var previous: View = when (PrefManager.getVal(PrefName.DarkMode)) { - 0 -> binding.settingsUiAuto - 1 -> binding.settingsUiLight - 2 -> binding.settingsUiDark - else -> binding.settingsUiAuto - } - previous.alpha = 1f - fun uiTheme(mode: Int, current: View) { - previous.alpha = 0.33f - previous = current - current.alpha = 1f - PrefManager.setVal(PrefName.DarkMode, mode) - Refresh.all() - finish() - startActivity(Intent(this, SettingsActivity::class.java)) - initActivity(this) - } + if (!BuildConfig.FLAVOR.contains("fdroid")) { + binding.settingsLogo.setOnLongClickListener { + lifecycleScope.launch(Dispatchers.IO) { + AppUpdater.check(this@SettingsActivity, true) + } + true + } - binding.settingsUiAuto.setOnClickListener { - uiTheme(0, it) - } + settingsCheckUpdate.isChecked = PrefManager.getVal(PrefName.CheckUpdate) + settingsCheckUpdate.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.CheckUpdate, isChecked) + if (!isChecked) { + snackString(getString(R.string.long_click_to_check_update)) + } + } - binding.settingsUiLight.setOnClickListener { - binding.settingsUseOLED.isChecked = false - uiTheme(1, it) - } + settingsCheckUpdate.setOnLongClickListener { + lifecycleScope.launch(Dispatchers.IO) { + AppUpdater.check(this@SettingsActivity, true) + } + true + } - binding.settingsUiDark.setOnClickListener { - uiTheme(2, it) - } + settingsShareUsername.isChecked = PrefManager.getVal(PrefName.SharedUserID) + settingsShareUsername.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.SharedUserID, isChecked) + } - var previousStart: View = when (PrefManager.getVal(PrefName.DefaultStartUpTab)) { - 0 -> binding.uiSettingsAnime - 1 -> binding.uiSettingsHome - 2 -> binding.uiSettingsManga - else -> binding.uiSettingsHome - } - previousStart.alpha = 1f - fun uiDefault(mode: Int, current: View) { - previousStart.alpha = 0.33f - previousStart = current - current.alpha = 1f - PrefManager.setVal(PrefName.DefaultStartUpTab, mode) - initActivity(this) - } + } else { + settingsCheckUpdate.visibility = View.GONE + settingsShareUsername.visibility = View.GONE + settingsCheckUpdate.isEnabled = false + settingsShareUsername.isEnabled = false + settingsCheckUpdate.isChecked = false + settingsShareUsername.isChecked = false + } + settingsLogToFile.isChecked = PrefManager.getVal(PrefName.LogToFile) + settingsLogToFile.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.LogToFile, isChecked) + restartApp() + } - binding.uiSettingsAnime.setOnClickListener { - uiDefault(0, it) - } - - binding.uiSettingsHome.setOnClickListener { - uiDefault(1, it) - } - - binding.uiSettingsManga.setOnClickListener { - uiDefault(2, it) - } - - binding.settingsShowYt.isChecked = PrefManager.getVal(PrefName.ShowYtButton) - binding.settingsShowYt.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.ShowYtButton, isChecked) - } - - var previousEp: View = when (PrefManager.getVal(PrefName.AnimeDefaultView)) { - 0 -> binding.settingsEpList - 1 -> binding.settingsEpGrid - 2 -> binding.settingsEpCompact - else -> binding.settingsEpList - } - previousEp.alpha = 1f - fun uiEp(mode: Int, current: View) { - previousEp.alpha = 0.33f - previousEp = current - current.alpha = 1f - PrefManager.setVal(PrefName.AnimeDefaultView, mode) - } - - binding.settingsEpList.setOnClickListener { - uiEp(0, it) - } - - binding.settingsEpGrid.setOnClickListener { - uiEp(1, it) - } - - binding.settingsEpCompact.setOnClickListener { - uiEp(2, it) - } - - var previousChp: View = when (PrefManager.getVal(PrefName.MangaDefaultView)) { - 0 -> binding.settingsChpList - 1 -> binding.settingsChpCompact - else -> binding.settingsChpList - } - previousChp.alpha = 1f - fun uiChp(mode: Int, current: View) { - previousChp.alpha = 0.33f - previousChp = current - current.alpha = 1f - PrefManager.setVal(PrefName.MangaDefaultView, mode) - } - - binding.settingsChpList.setOnClickListener { - uiChp(0, it) - } - - binding.settingsChpCompact.setOnClickListener { - uiChp(1, it) + settingsShareLog.setOnClickListener { + Logger.shareLog(this@SettingsActivity) + } } binding.settingBuyMeCoffee.setOnClickListener { lifecycleScope.launch { it.pop() } - openLinkInBrowser("https://www.buymeacoffee.com/rebelonion") + openLinkInBrowser(getString(R.string.coffee)) } lifecycleScope.launch { binding.settingBuyMeCoffee.pop() @@ -602,13 +995,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) } - binding.settingsUi.setOnClickListener { - startActivity(Intent(this, UserInterfaceSettingsActivity::class.java)) - } - binding.settingsFAQ.setOnClickListener { - startActivity(Intent(this, FAQActivity::class.java)) - } (binding.settingsLogo.drawable as Animatable).start() val array = resources.getStringArray(R.array.tips) @@ -617,9 +1004,8 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene cursedCounter++ (binding.settingsLogo.drawable as Animatable).start() if (cursedCounter % 7 == 0) { - Toast.makeText(this, "youwu have been cuwsed :pwayge:", Toast.LENGTH_LONG).show() - val url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - openLinkInBrowser(url) + toast(R.string.you_cursed) + openLinkInYouTube(getString(R.string.cursed_yt)) //PrefManager.setVal(PrefName.ImageUrl, !PrefManager.getVal(PrefName.ImageUrl, false)) } else { snackString(array[(Math.random() * array.size).toInt()], this) @@ -627,364 +1013,22 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } - binding.settingsDev.setOnClickListener { - DevelopersDialogFragment().show(supportFragmentManager, "dialog") - } - binding.settingsForks.setOnClickListener { - ForksDialogFragment().show(supportFragmentManager, "dialog") - } - binding.settingsDisclaimer.setOnClickListener { - val title = getString(R.string.disclaimer) - val text = TextView(this) - text.setText(R.string.full_disclaimer) - - CustomBottomDialog.newInstance().apply { - setTitleText(title) - addView(text) - setNegativeButton(currContext()!!.getString(R.string.close)) { - dismiss() - } - show(supportFragmentManager, "dialog") - } - } - - var curTime = PrefManager.getVal(PrefName.SubscriptionNotificationInterval) - val timeNames = checkIntervals.map { - val mins = it % 60 - val hours = it / 60 - if (it > 0) "${if (hours > 0) "$hours hrs " else ""}${if (mins > 0) "$mins mins" else ""}" - else getString(R.string.do_not_update) - }.toTypedArray() - binding.settingsSubscriptionsTime.text = - getString(R.string.subscriptions_checking_time_s, timeNames[curTime]) - val speedDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(R.string.subscriptions_checking_time) - binding.settingsSubscriptionsTime.setOnClickListener { - val dialog = speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i -> - curTime = i - binding.settingsSubscriptionsTime.text = - getString(R.string.subscriptions_checking_time_s, timeNames[i]) - PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) - dialog.dismiss() - TaskScheduler.create(this, - PrefManager.getVal(PrefName.UseAlarmManager) - ).scheduleAllTasks(this) - }.show() - dialog.window?.setDimAmount(0.8f) - } - - binding.settingsSubscriptionsTime.setOnLongClickListener { - TaskScheduler.create(this, - PrefManager.getVal(PrefName.UseAlarmManager) - ).scheduleAllTasks(this) - 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(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() - TaskScheduler.create(this, - PrefManager.getVal(PrefName.UseAlarmManager) - ).scheduleAllTasks(this) - } - .create() - dialog.window?.setDimAmount(0.8f) - dialog.show() - } - - binding.settingsAnilistNotifications.setOnClickListener { - val types = NotificationType.entries.map { it.name } - val filteredTypes = PrefManager.getVal>(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(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() - TaskScheduler.create(this, - PrefManager.getVal(PrefName.UseAlarmManager) - ).scheduleAllTasks(this) - } - .create() - dialog.window?.setDimAmount(0.8f) - dialog.show() - } - - binding.settingsNotificationsCheckingSubscriptions.isChecked = - PrefManager.getVal(PrefName.SubscriptionCheckingNotifications) - binding.settingsNotificationsCheckingSubscriptions.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.SubscriptionCheckingNotifications, isChecked) - } - - binding.settingsNotificationsCheckingSubscriptions.setOnLongClickListener { - openSettings(this, null) - } - - binding.settingsNotificationsUseAlarmManager.isChecked = - PrefManager.getVal(PrefName.UseAlarmManager) - - binding.settingsNotificationsUseAlarmManager.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - val alertDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Use Alarm Manager") - .setMessage("Using Alarm Manger can help fight against battery optimization, but may consume more battery. It also requires the Alarm Manager permission.") - .setPositiveButton("Use") { dialog, _ -> - PrefManager.setVal(PrefName.UseAlarmManager, true) - if (SDK_INT >= Build.VERSION_CODES.S) { - if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) { - val intent = Intent("android.settings.REQUEST_SCHEDULE_EXACT_ALARM") - startActivity(intent) - binding.settingsNotificationsCheckingSubscriptions.isChecked = true - } - } - dialog.dismiss() - } - .setNegativeButton("Cancel") { dialog, _ -> - binding.settingsNotificationsCheckingSubscriptions.isChecked = false - PrefManager.setVal(PrefName.UseAlarmManager, false) - dialog.dismiss() - } - .create() - alertDialog.window?.setDimAmount(0.8f) - alertDialog.show() - } else { - PrefManager.setVal(PrefName.UseAlarmManager, false) - TaskScheduler.create(this, true).cancelAllTasks() - TaskScheduler.create(this, false).scheduleAllTasks(this) - } - } - - if (!BuildConfig.FLAVOR.contains("fdroid")) { - binding.settingsLogo.setOnLongClickListener { - lifecycleScope.launch(Dispatchers.IO) { - AppUpdater.check(this@SettingsActivity, true) - } - true - } - - binding.settingsCheckUpdate.isChecked = PrefManager.getVal(PrefName.CheckUpdate) - binding.settingsCheckUpdate.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.CheckUpdate, isChecked) - if (!isChecked) { - snackString(getString(R.string.long_click_to_check_update)) - } - } - - binding.settingsCheckUpdate.setOnLongClickListener { - lifecycleScope.launch(Dispatchers.IO) { - AppUpdater.check(this@SettingsActivity, true) - } - true - } - - binding.settingsShareUsername.isChecked = PrefManager.getVal(PrefName.SharedUserID) - binding.settingsShareUsername.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.SharedUserID, isChecked) - } - - } else { - binding.settingsCheckUpdate.visibility = View.GONE - binding.settingsShareUsername.visibility = View.GONE - binding.settingsCheckUpdate.isEnabled = false - binding.settingsShareUsername.isEnabled = false - binding.settingsCheckUpdate.isChecked = false - binding.settingsShareUsername.isChecked = false - } - - binding.settingsLogToFile.isChecked = PrefManager.getVal(PrefName.LogToFile) - binding.settingsLogToFile.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.LogToFile, isChecked) - restartApp() - } - - binding.settingsShareLog.setOnClickListener { - Logger.shareLog(this) - } - - binding.settingsAccountHelp.setOnClickListener { - val title = getString(R.string.account_help) - val full = getString(R.string.full_account_help) - CustomBottomDialog.newInstance().apply { - setTitleText(title) - addView( - TextView(it.context).apply { - val markWon = Markwon.builder(it.context) - .usePlugin(SoftBreakAddsNewLinePlugin.create()).build() - markWon.setMarkdown(this, full) - } - ) - }.show(supportFragmentManager, "dialog") - } - - fun reload() { - if (Anilist.token != null) { - binding.settingsAnilistLogin.setText(R.string.logout) - binding.settingsAnilistLogin.setOnClickListener { - Anilist.removeSavedToken() - restartMainActivity.isEnabled = true - reload() - } - binding.settingsAnilistUsername.visibility = View.VISIBLE - binding.settingsAnilistUsername.text = Anilist.username - binding.settingsAnilistAvatar.loadImage(Anilist.avatar) - - binding.settingsMALLoginRequired.visibility = View.GONE - binding.settingsMALLogin.visibility = View.VISIBLE - binding.settingsMALUsername.visibility = View.VISIBLE - - if (MAL.token != null) { - binding.settingsMALLogin.setText(R.string.logout) - binding.settingsMALLogin.setOnClickListener { - MAL.removeSavedToken(it.context) - restartMainActivity.isEnabled = true - reload() - } - binding.settingsMALUsername.visibility = View.VISIBLE - binding.settingsMALUsername.text = MAL.username - binding.settingsMALAvatar.loadImage(MAL.avatar) - } else { - binding.settingsMALAvatar.setImageResource(R.drawable.ic_round_person_24) - binding.settingsMALUsername.visibility = View.GONE - binding.settingsMALLogin.setText(R.string.login) - binding.settingsMALLogin.setOnClickListener { - MAL.loginIntent(this) - } - } - } else { - binding.settingsAnilistAvatar.setImageResource(R.drawable.ic_round_person_24) - binding.settingsAnilistUsername.visibility = View.GONE - binding.settingsAnilistLogin.setText(R.string.login) - binding.settingsAnilistLogin.setOnClickListener { - Anilist.loginIntent(this) - } - binding.settingsMALLoginRequired.visibility = View.VISIBLE - binding.settingsMALLogin.visibility = View.GONE - binding.settingsMALUsername.visibility = View.GONE - } - - if (Discord.token != null) { - val id = PrefManager.getVal(PrefName.DiscordId, null as String?) - val avatar = PrefManager.getVal(PrefName.DiscordAvatar, null as String?) - val username = PrefManager.getVal(PrefName.DiscordUserName, null as String?) - if (id != null && avatar != null) { - binding.settingsDiscordAvatar.loadImage("https://cdn.discordapp.com/avatars/$id/$avatar.png") - } - binding.settingsDiscordUsername.visibility = View.VISIBLE - binding.settingsDiscordUsername.text = - username ?: Discord.token?.replace(Regex("."), "*") - binding.settingsDiscordLogin.setText(R.string.logout) - binding.settingsDiscordLogin.setOnClickListener { - Discord.removeSavedToken(this) - restartMainActivity.isEnabled = true - reload() - } - - binding.imageSwitcher.visibility = View.VISIBLE - var initialStatus = when (PrefManager.getVal(PrefName.DiscordStatus)) { - "online" -> R.drawable.discord_status_online - "idle" -> R.drawable.discord_status_idle - "dnd" -> R.drawable.discord_status_dnd - else -> R.drawable.discord_status_online - } - binding.imageSwitcher.setImageResource(initialStatus) - - val zoomInAnimation = AnimationUtils.loadAnimation(this, R.anim.bounce_zoom) - binding.imageSwitcher.setOnClickListener { - var status = "online" - initialStatus = when (initialStatus) { - R.drawable.discord_status_online -> { - status = "idle" - R.drawable.discord_status_idle - } - R.drawable.discord_status_idle -> { - status = "dnd" - R.drawable.discord_status_dnd - } - R.drawable.discord_status_dnd -> { - status = "online" - R.drawable.discord_status_online - } - else -> R.drawable.discord_status_online - } - - PrefManager.setVal(PrefName.DiscordStatus, status) - binding.imageSwitcher.setImageResource(initialStatus) - binding.imageSwitcher.startAnimation(zoomInAnimation) - } - } else { - binding.imageSwitcher.visibility = View.GONE - binding.settingsDiscordAvatar.setImageResource(R.drawable.ic_round_person_24) - binding.settingsDiscordUsername.visibility = View.GONE - binding.settingsDiscordLogin.setText(R.string.login) - binding.settingsDiscordLogin.setOnClickListener { - Discord.warning(this).show(supportFragmentManager, "dialog") - } - } - } - reload() - lifecycleScope.launch(Dispatchers.IO) { delay(2000) runOnUiThread { if (Random.nextInt(0, 100) > 69) { CustomBottomDialog.newInstance().apply { - title = "Enjoying the App?" + title = getString(R.string.enjoying_app) addView(TextView(this@SettingsActivity).apply { - text = - "Consider donating!" + text = context.getString(R.string.consider_donating) }) - setNegativeButton("no moners :(") { - snackString("That's alright, you'll be a rich man soon :prayge:") + setNegativeButton(getString(R.string.no_moners)) { + snackString(R.string.you_be_rich) dismiss() } - setPositiveButton("denote :)") { + setPositiveButton(getString(R.string.donate)) { binding.settingBuyMeCoffee.performClick() dismiss() } @@ -1017,7 +1061,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene context.packageName )!!.component ) - setAction("Do it!") { + setAction(getString(R.string.do_it)) { context.startActivity(mainIntent) Runtime.getRuntime().exit(0) } @@ -1031,14 +1075,14 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene // Inflate the dialog layout val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_user_agent, null) val box = dialogView.findViewById(R.id.userAgentTextBox) - box?.hint = "Password" + box?.hint = getString(R.string.password) box?.setSingleLine() val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Enter Password") + .setTitle(getString(R.string.enter_password)) .setView(dialogView) - .setPositiveButton("OK", null) - .setNegativeButton("Cancel") { dialog, _ -> + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel) { dialog, _ -> password.fill('0') dialog.dismiss() callback(null) @@ -1051,7 +1095,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene dialog.dismiss() callback(password) } else { - toast("Password cannot be empty") + toast(getString(R.string.password_cannot_be_empty)) } } box?.setOnEditorActionListener { _, actionId, _ -> @@ -1065,7 +1109,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene val subtitleTextView = dialogView.findViewById(R.id.subtitle) subtitleTextView?.visibility = View.VISIBLE if (!isExporting) - subtitleTextView?.text = "Enter your password to decrypt the file" + subtitleTextView?.text = getString(R.string.enter_password_to_decrypt_file) dialog.window?.setDimAmount(0.8f) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt index 89a65b17..535b7273 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt @@ -9,9 +9,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.MainActivity -import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.BottomSheetSettingsBinding @@ -24,13 +24,15 @@ import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.NoInternet import ani.dantotsu.incognitoNotification import ani.dantotsu.loadImage -import ani.dantotsu.profile.activity.NotificationActivity import ani.dantotsu.offline.OfflineFragment +import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.activity.FeedActivity +import ani.dantotsu.profile.activity.NotificationActivity import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.startMainActivity +import eu.kanade.tachiyomi.util.system.getSerializableCompat import java.util.Timer import kotlin.concurrent.schedule @@ -41,7 +43,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { private lateinit var pageType: PageType override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - pageType = arguments?.getSerializable("pageType") as? PageType ?: PageType.HOME + pageType = arguments?.getSerializableCompat("pageType") as? PageType ?: PageType.HOME } override fun onCreateView( @@ -94,7 +96,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { Anilist.loginIntent(requireActivity()) } } - binding.settingsNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + binding.settingsNotificationCount.isVisible = Anilist.unreadNotificationCount > 0 binding.settingsNotificationCount.text = Anilist.unreadNotificationCount.toString() binding.settingsUserAvatar.setOnClickListener{ ContextCompat.startActivity( diff --git a/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt index 53fe43cb..1d7d5f92 100644 --- a/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/paging/AnimePagingSource.kt @@ -202,12 +202,12 @@ class AnimeExtensionAdapter(private val clickListener: OnAnimeInstallClickListen val extensionIconImageView: ImageView = binding.extensionIconImageView - @SuppressLint("SetTextI18n") fun bind(extension: AnimeExtension.Available) { val nsfw = if (extension.isNsfw) "(18+)" else "" val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) binding.extensionNameTextView.text = extension.name - binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" + val versionText = "$lang ${extension.versionName} $nsfw" + binding.extensionVersionTextView.text = versionText } fun clear() { diff --git a/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt index 140eb549..1227d667 100644 --- a/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/paging/MangaPagingSource.kt @@ -199,12 +199,12 @@ class MangaExtensionAdapter(private val clickListener: OnMangaInstallClickListen val extensionIconImageView: ImageView = binding.extensionIconImageView - @SuppressLint("SetTextI18n") fun bind(extension: MangaExtension.Available) { val nsfw = if (extension.isNsfw) "(18+)" else "" val lang = LanguageMapper.mapLanguageCodeToName(extension.lang) binding.extensionNameTextView.text = extension.name - binding.extensionVersionTextView.text = "$lang ${extension.versionName} $nsfw" + val versionText = "$lang ${extension.versionName} $nsfw" + binding.extensionVersionTextView.text = versionText } fun clear() { diff --git a/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt b/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt index 46379b76..fa2bf1ae 100644 --- a/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt +++ b/app/src/main/java/ani/dantotsu/settings/paging/NovelPagingSource.kt @@ -41,13 +41,14 @@ import kotlinx.coroutines.withContext class NovelExtensionsViewModelFactory( private val novelExtensionManager: NovelExtensionManager ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return NovelExtensionsViewModel(novelExtensionManager) as T } } class NovelExtensionsViewModel( - private val novelExtensionManager: NovelExtensionManager + novelExtensionManager: NovelExtensionManager ) : ViewModel() { private val searchQuery = MutableStateFlow("") private var currentPagingSource: NovelExtensionPagingSource? = null @@ -102,21 +103,20 @@ class NovelExtensionPagingSource( } else { availableExtensions.filter { it.name.contains(query, ignoreCase = true) } } - val filternfsw = filteredExtensions /*val filternfsw = if(isNsfwEnabled) { currently not implemented filteredExtensions } else { filteredExtensions.filterNot { it.isNsfw } }*/ return try { - val sublist = filternfsw.subList( + val sublist = filteredExtensions.subList( fromIndex = position, - toIndex = (position + params.loadSize).coerceAtMost(filternfsw.size) + toIndex = (position + params.loadSize).coerceAtMost(filteredExtensions.size) ) LoadResult.Page( data = sublist, prevKey = if (position == 0) null else position - params.loadSize, - nextKey = if (position + params.loadSize >= filternfsw.size) null else position + params.loadSize + nextKey = if (position + params.loadSize >= filteredExtensions.size) null else position + params.loadSize ) } catch (e: Exception) { LoadResult.Error(e) diff --git a/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt b/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt index 6bac3fcc..54cd0dff 100644 --- a/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt +++ b/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt @@ -6,7 +6,7 @@ import ani.dantotsu.util.ColorEditor.Companion.toHexColor class AniMarkdown { //istg anilist has the worst api companion object { private fun convertNestedImageToHtml(markdown: String): String { - val regex = """\[\!\[(.*?)\]\((.*?)\)\]\((.*?)\)""".toRegex() + val regex = """\[!\[(.*?)]\((.*?)\)]\((.*?)\)""".toRegex() return regex.replace(markdown) { matchResult -> val altText = matchResult.groupValues[1] val imageUrl = matchResult.groupValues[2] @@ -16,7 +16,7 @@ class AniMarkdown { //istg anilist has the worst api } private fun convertImageToHtml(markdown: String): String { - val regex = """\!\[(.*?)\]\((.*?)\)""".toRegex() + val regex = """!\[(.*?)]\((.*?)\)""".toRegex() return regex.replace(markdown) { matchResult -> val altText = matchResult.groupValues[1] val imageUrl = matchResult.groupValues[2] @@ -25,7 +25,7 @@ class AniMarkdown { //istg anilist has the worst api } private fun convertLinkToHtml(markdown: String): String { - val regex = """\[(.*?)\]\((.*?)\)""".toRegex() + val regex = """\[(.*?)]\((.*?)\)""".toRegex() return regex.replace(markdown) { matchResult -> val linkText = matchResult.groupValues[1] val linkUrl = matchResult.groupValues[2] @@ -50,7 +50,7 @@ class AniMarkdown { //istg anilist has the worst api private fun underlineToHtml(html: String): String { return html.replace("(?s)___(.*?)___".toRegex(), "
$1
") .replace("(?s)__(.*?)__".toRegex(), "
$1
") - .replace("(?s)[\\s]+_([^_]+)_[\\s]+".toRegex(), "$1") + .replace("(?s)\\s+_([^_]+)_\\s+".toRegex(), "$1") } fun getBasicAniHTML(html: String): String { diff --git a/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsFactory.kt b/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsFactory.kt index 75f35588..8cf8029a 100644 --- a/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsFactory.kt +++ b/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsFactory.kt @@ -1,7 +1,6 @@ package ani.dantotsu.widgets import android.content.Context -import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory import android.widget.RemoteViews @@ -12,7 +11,7 @@ import java.io.InputStream import java.net.HttpURLConnection import java.net.URL -class CurrentlyAiringRemoteViewsFactory(private val context: Context, intent: Intent) : +class CurrentlyAiringRemoteViewsFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory { private var widgetItems = mutableListOf() diff --git a/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsService.kt b/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsService.kt index a0ff2efc..4d042b33 100644 --- a/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsService.kt +++ b/app/src/main/java/ani/dantotsu/widgets/CurrentlyAiringRemoteViewsService.kt @@ -7,6 +7,6 @@ import ani.dantotsu.util.Logger class CurrentlyAiringRemoteViewsService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { Logger.log("CurrentlyAiringRemoteViewsFactory onGetViewFactory") - return CurrentlyAiringRemoteViewsFactory(applicationContext, intent) + return CurrentlyAiringRemoteViewsFactory(applicationContext) } } diff --git a/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt b/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt index e9a5c3a7..a52789f5 100644 --- a/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt +++ b/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt @@ -20,7 +20,7 @@ class ExtensionInstallerPreference( val entries - get() = ExtensionInstaller.values().run { + get() = ExtensionInstaller.entries.toTypedArray().run { if (context.hasMiuiPackageInstaller) { filter { it != ExtensionInstaller.PACKAGEINSTALLER } } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt index 53da5305..171b07b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt @@ -58,8 +58,7 @@ interface AnimeSource { */ @Suppress("DEPRECATION") suspend fun getVideoList(episode: SEpisode): List