Compare commits

...

19 Commits

Author SHA1 Message Date
Ankit Grai
c3f6d0ecee fix : dialog key names (#605) 2025-05-22 12:51:18 +05:30
aayush262
5124d6a2d8 fix: discord login 2025-05-22 12:50:06 +05:30
Ankit Grai
e83a0fe7da fix : long press progress dialog reset not working (#603) 2025-05-19 17:11:24 +05:30
rebel onion
61a8350043 fix: avoid waiting on network for local exts 2025-05-15 01:47:06 -05:00
rebel onion
baffbc845c fix: help bounds check /w custom speeds 2025-05-15 01:16:23 -05:00
rebel onion
afd9f6b884 fix: subtitles not showing 2025-05-15 01:13:47 -05:00
rebel onion
7d0894cd92 chore: bump extension interface 2025-05-14 22:35:50 -05:00
Rishvaish
dec2ed7959 hope for the best
* Update README.md

* To install multiple mangas

users can enter the value required to install as there is an EditText field instead of the Text View

* Issues

1)Installation of many mangas at same time now made to one to increase the installation efficiency
2)Installation order from the latest progresses chapter to the limit index
3)Tried to resolve the app crash bug

* Issues

1)Installation of many mangas at same time now made to one to increase the installation efficiency
2)Installation order from the latest progresses chapter to the limit index
3)Tried to resolve the app crash bug

---------

Co-authored-by: rebel onion <87634197+rebelonion@users.noreply.github.com>
2025-04-23 14:58:42 +05:30
aayush262
e4630df3e0 move stuff to dev (#587)
* Update README.md

* Fixed missing manga pages when downloading (#586)

* To install multiple mangas (#582)

users can enter the value required to install as there is an EditText field instead of the Text View

---------

Co-authored-by: rebel onion <87634197+rebelonion@users.noreply.github.com>
Co-authored-by: Daniele Santoru <30676094+danyev3@users.noreply.github.com>
Co-authored-by: Rishvaish <68911202+rishabpuranika@users.noreply.github.com>
2025-04-02 10:52:58 +05:30
Sadwhy
6fd3515d2c stuff (#567)
* Add blur to dialog for devices that support it

* More adjustable seek time

* Bump exo player
2025-01-17 13:03:01 +05:30
rebel onion
6fa2f11db2 Merge branch 'main' into dev 2025-01-16 00:15:21 -06:00
rebel onion
a5babea27c chore: version bump 2025-01-16 00:14:25 -06:00
rebelonion
8a9b8cca7e fix: Serializable 2025-01-13 14:23:02 -06:00
rebel onion
7479f5f43b Update stable.md 2025-01-09 19:58:00 -06:00
Sadwhy
3ac9307329 Use custom alert builder for all dialogs [skip ci] 2025-01-09 18:04:22 +05:30
rebel onion
f606bef2a5 Merge pull request #559 from rebelonion/dev
Dev
2025-01-06 08:29:48 -06:00
rebel onion
f9f9767ecc chore: clean 2025-01-06 08:22:44 -06:00
rebel onion
31a67c8edb Merge branch 'dev' of https://github.com/rebelonion/Dantotsu into dev 2025-01-05 20:23:59 -06:00
rebel onion
7fc69b4edd feat: alt update 2025-01-05 20:23:50 -06:00
123 changed files with 2888 additions and 2062 deletions

View File

@@ -14,8 +14,6 @@ Dantotsu is an [Anilist](https://anilist.co/) only client.
> **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge! > **Dantotsu (断トツ; Dan-totsu)** literally means "the best of the best" in Japanese. Try it out for yourself and be the judge!
<a href="https://www.buymeacoffee.com/rebelonion"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=rebelonion&button_colour=FFDD00&font_colour=030201&font_family=Poppins&outline_colour=000000&coffee_colour=ffffff" /></a>
## Terms of Use ## Terms of Use
By downloading, installing, or using this application, you agree to: By downloading, installing, or using this application, you agree to:
- Use the application in compliance with all applicable laws - Use the application in compliance with all applicable laws

View File

@@ -18,8 +18,8 @@ android {
minSdk 21 minSdk 21
targetSdk 35 targetSdk 35
versionCode((System.currentTimeMillis() / 60000).toInteger()) versionCode((System.currentTimeMillis() / 60000).toInteger())
versionName "3.2.0" versionName "3.2.1"
versionCode 300200000 versionCode versionName.split("\\.").collect { it.toInteger() * 100 }.join("") as Integer
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
@@ -48,6 +48,10 @@ android {
manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round" manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round"
debuggable System.getenv("CI") == null debuggable System.getenv("CI") == null
isDefault true isDefault true
debuggable true
jniDebuggable true
minifyEnabled false
shrinkResources false
} }
debug { debug {
applicationIdSuffix ".beta" applicationIdSuffix ".beta"
@@ -81,25 +85,26 @@ android {
dependencies { dependencies {
// FireBase // FireBase
googleImplementation platform('com.google.firebase:firebase-bom:33.0.0') googleImplementation platform('com.google.firebase:firebase-bom:33.13.0')
googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.0.0' googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.4.0'
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.0.0' googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.4.3'
// Core // Core
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.browser:browser:1.8.0' implementation 'androidx.browser:browser:1.8.0'
implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.fragment:fragment-ktx:1.6.2' implementation 'androidx.fragment:fragment-ktx:1.8.6'
implementation 'androidx.activity:activity-ktx:1.10.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.work:work-runtime-ktx:2.9.0" implementation "androidx.work:work-runtime-ktx:2.10.1"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.Blatzar:NiceHttp:0.4.4' implementation 'com.github.Blatzar:NiceHttp:0.4.4'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.webkit:webkit:1.11.0' implementation 'androidx.webkit:webkit:1.13.0'
implementation "com.anggrayudi:storage:1.5.5" implementation "com.anggrayudi:storage:1.5.5"
implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.biometric:biometric:1.1.0"
@@ -113,7 +118,7 @@ dependencies {
implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'jp.wasabeef:glide-transformations:4.3.0'
// Exoplayer // Exoplayer
ext.exo_version = '1.5.0' ext.exo_version = '1.6.1'
implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-exoplayer:$exo_version"
implementation "androidx.media3:media3-ui:$exo_version" implementation "androidx.media3:media3-ui:$exo_version"
implementation "androidx.media3:media3-exoplayer-hls:$exo_version" implementation "androidx.media3:media3-exoplayer-hls:$exo_version"
@@ -124,7 +129,7 @@ dependencies {
implementation "androidx.media3:media3-cast:$exo_version" implementation "androidx.media3:media3-cast:$exo_version"
implementation "androidx.mediarouter:mediarouter:1.7.0" implementation "androidx.mediarouter:mediarouter:1.7.0"
// Media3 extension // Media3 extension
implementation "com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.3" implementation "com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.4"
// UI // UI
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
@@ -133,7 +138,7 @@ dependencies {
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.alexvasilkov:gesture-views:2.8.3' implementation 'com.alexvasilkov:gesture-views:2.8.3'
implementation 'com.github.VipulOG:ebook-reader:0.1.6' implementation 'com.github.VipulOG:ebook-reader:0.1.6'
implementation 'androidx.paging:paging-runtime-ktx:3.2.1' implementation 'androidx.paging:paging-runtime-ktx:3.3.6'
implementation 'com.github.eltos:simpledialogfragments:v3.7' implementation 'com.github.eltos:simpledialogfragments:v3.7'
implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.3' implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.3'
@@ -162,13 +167,13 @@ dependencies {
implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07' implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07'
implementation 'com.squareup.logcat:logcat:0.1' implementation 'com.squareup.logcat:logcat:0.1'
implementation 'uy.kohesive.injekt:injekt-core:1.16.+' implementation 'uy.kohesive.injekt:injekt-core:1.16.+'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12' implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12' implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
implementation 'com.squareup.okio:okio:3.8.0' implementation 'com.squareup.okio:okio:3.9.1'
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12' implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.14'
implementation 'org.jsoup:jsoup:1.16.1' implementation 'org.jsoup:jsoup:1.18.1'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.3' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.7.3'
implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0' implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0'
implementation 'com.github.tachiyomiorg:unifile:17bec43' implementation 'com.github.tachiyomiorg:unifile:17bec43'
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1' implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'

View File

@@ -18,7 +18,9 @@ import ani.dantotsu.Mapper
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.buildMarkwon import ani.dantotsu.buildMarkwon
import ani.dantotsu.client import ani.dantotsu.client
import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.decodeBase64ToString
import ani.dantotsu.logError import ani.dantotsu.logError
import ani.dantotsu.openLinkInBrowser import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
@@ -37,26 +39,88 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
object AppUpdater { object AppUpdater {
private val fallbackStableUrl: String
get() = "aHR0cHM6Ly9hcGkuZGFudG90c3UuYXBwL3VwZGF0ZXMvc3RhYmxl".decodeBase64ToString()
private val fallbackBetaUrl: String
get() = "aHR0cHM6Ly9hcGkuZGFudG90c3UuYXBwL3VwZGF0ZXMvYmV0YQ==".decodeBase64ToString()
@Serializable
data class FallbackResponse(
val version: String,
val changelog: String,
val downloadUrl: String? = null
)
private suspend fun fetchUpdateInfo(repo: String, isDebug: Boolean): Pair<String, String>? {
return try {
fetchFromGithub(repo, isDebug)
} catch (e: Exception) {
Logger.log("Github fetch failed, trying fallback: ${e.message}")
try {
fetchFromFallback(isDebug)
} catch (e: Exception) {
Logger.log("Fallback fetch failed: ${e.message}")
null
}
}
}
private suspend fun fetchFromGithub(repo: String, isDebug: Boolean): Pair<String, String> {
return if (isDebug) {
val res = client.get("https://api.github.com/repos/$repo/releases")
.parsed<JsonArray>().map {
Mapper.json.decodeFromJsonElement<GithubResponse>(it)
}
val r = res.filter { it.prerelease }.filter { !it.tagName.contains("fdroid") }
.maxByOrNull {
it.timeStamp()
} ?: throw Exception("No Pre Release Found")
val v = r.tagName.substringAfter("v", "")
(r.body ?: "") to v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
} else {
val res = client.get("https://raw.githubusercontent.com/$repo/main/stable.md").text
res to res.substringAfter("# ").substringBefore("\n")
}
}
private suspend fun fetchFromFallback(isDebug: Boolean): Pair<String, String> {
val url = if (isDebug) fallbackBetaUrl else fallbackStableUrl
val response = CommentsAPI.requestBuilder().get(url).parsed<FallbackResponse>()
return response.changelog to response.version
}
private suspend fun fetchApkUrl(repo: String, version: String, isDebug: Boolean): String? {
return try {
fetchApkUrlFromGithub(repo, version)
} catch (e: Exception) {
Logger.log("Github APK fetch failed, trying fallback: ${e.message}")
try {
fetchApkUrlFromFallback(version, isDebug)
} catch (e: Exception) {
Logger.log("Fallback APK fetch failed: ${e.message}")
null
}
}
}
private suspend fun fetchApkUrlFromGithub(repo: String, version: String): String? {
val apks = client.get("https://api.github.com/repos/$repo/releases/tags/v$version")
.parsed<GithubResponse>().assets?.filter {
it.browserDownloadURL.endsWith(".apk")
}
return apks?.firstOrNull()?.browserDownloadURL
}
private suspend fun fetchApkUrlFromFallback(version: String, isDebug: Boolean): String? {
val url = if (isDebug) fallbackBetaUrl else fallbackStableUrl
return CommentsAPI.requestBuilder().get("$url/$version").parsed<FallbackResponse>().downloadUrl
}
suspend fun check(activity: FragmentActivity, post: Boolean = false) { suspend fun check(activity: FragmentActivity, post: Boolean = false) {
if (post) snackString(currContext()?.getString(R.string.checking_for_update)) if (post) snackString(currContext()?.getString(R.string.checking_for_update))
val repo = activity.getString(R.string.repo) val repo = activity.getString(R.string.repo)
tryWithSuspend { tryWithSuspend {
val (md, version) = if (BuildConfig.DEBUG) { val (md, version) = fetchUpdateInfo(repo, BuildConfig.DEBUG) ?: return@tryWithSuspend
val res = client.get("https://api.github.com/repos/$repo/releases")
.parsed<JsonArray>().map {
Mapper.json.decodeFromJsonElement<GithubResponse>(it)
}
val r = res.filter { it.prerelease }.filter { !it.tagName.contains("fdroid") }
.maxByOrNull {
it.timeStamp()
} ?: throw Exception("No Pre Release Found")
val v = r.tagName.substringAfter("v", "")
(r.body ?: "") to v.ifEmpty { throw Exception("Weird Version : ${r.tagName}") }
} else {
val res =
client.get("https://raw.githubusercontent.com/$repo/main/stable.md").text
res to res.substringAfter("# ").substringBefore("\n")
}
Logger.log("Git Version : $version") Logger.log("Git Version : $version")
val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false) val dontShow = PrefManager.getCustomVal("dont_ask_for_update_$version", false)
@@ -69,7 +133,7 @@ object AppUpdater {
) )
addView( addView(
TextView(activity).apply { TextView(activity).apply {
val markWon = try { //slower phones can destroy the activity before this is done val markWon = try {
buildMarkwon(activity, false) buildMarkwon(activity, false)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
return@runOnUiThread return@runOnUiThread
@@ -89,17 +153,11 @@ object AppUpdater {
setPositiveButton(currContext()!!.getString(R.string.lets_go)) { setPositiveButton(currContext()!!.getString(R.string.lets_go)) {
MainScope().launch(Dispatchers.IO) { MainScope().launch(Dispatchers.IO) {
try { try {
val apks = val apkUrl = fetchApkUrl(repo, version, BuildConfig.DEBUG)
client.get("https://api.github.com/repos/$repo/releases/tags/v$version") if (apkUrl != null) {
.parsed<GithubResponse>().assets?.filter { activity.downloadUpdate(version, apkUrl)
it.browserDownloadURL.endsWith( } else {
".apk" openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
)
}
val apkToDownload = apks?.first()
apkToDownload?.browserDownloadURL.apply {
if (this != null) activity.downloadUpdate(version, this)
else openLinkInBrowser("https://github.com/repos/$repo/releases/tag/v$version")
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
@@ -112,8 +170,7 @@ object AppUpdater {
} }
show(activity.supportFragmentManager, "dialog") show(activity.supportFragmentManager, "dialog")
} }
} } else {
else {
if (post) snackString(currContext()?.getString(R.string.no_update_found)) if (post) snackString(currContext()?.getString(R.string.no_update_found))
} }
} }
@@ -144,8 +201,7 @@ object AppUpdater {
//Blatantly kanged from https://github.com/LagradOst/CloudStream-3/blob/master/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt //Blatantly kanged from https://github.com/LagradOst/CloudStream-3/blob/master/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt
private fun Activity.downloadUpdate(version: String, url: String): Boolean { private fun Activity.downloadUpdate(version: String, url: String) {
toast(getString(R.string.downloading_update, version)) toast(getString(R.string.downloading_update, version))
val downloadManager = this.getSystemService<DownloadManager>()!! val downloadManager = this.getSystemService<DownloadManager>()!!
@@ -167,7 +223,7 @@ object AppUpdater {
logError(e) logError(e)
-1 -1
} }
if (id == -1L) return true if (id == -1L) return
ContextCompat.registerReceiver( ContextCompat.registerReceiver(
this, this,
object : BroadcastReceiver() { object : BroadcastReceiver() {
@@ -188,7 +244,6 @@ object AppUpdater {
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), }, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
ContextCompat.RECEIVER_EXPORTED ContextCompat.RECEIVER_EXPORTED
) )
return true
} }
private fun openApk(context: Context, uri: Uri) { private fun openApk(context: Context, uri: Uri) {

View File

@@ -113,10 +113,8 @@
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:host="*"/>
<data android:mimeType="application/epub+zip"/> <data android:mimeType="application/epub+zip"/>
<data android:mimeType="application/x-mobipocket-ebook" /> <data android:mimeType="application/x-mobipocket-ebook" />
<data android:mimeType="application/vnd.amazon.ebook" /> <data android:mimeType="application/vnd.amazon.ebook" />
@@ -385,10 +383,10 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" /> <data android:scheme="content" />
<data android:scheme="file" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
<data android:pathPattern=".*\\.ani" /> <data android:pathPattern=".*\\.ani" />
<data android:pathPattern=".*\\.sani" /> <data android:pathPattern=".*\\.sani" />
<data android:host="*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View File

@@ -107,27 +107,34 @@ class App : MultiDexApplication() {
if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 0) { if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 0) {
if (BuildConfig.FLAVOR.contains("fdroid")) { if (BuildConfig.FLAVOR.contains("fdroid")) {
PrefManager.setVal(PrefName.CommentsEnabled, 2) PrefManager.setVal(PrefName.CommentsEnabled, 2)
} else { } else {
PrefManager.setVal(PrefName.CommentsEnabled, 1) PrefManager.setVal(PrefName.CommentsEnabled, 1)
} }
} }
CoroutineScope(Dispatchers.IO).launch { val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
animeExtensionManager = Injekt.get() animeExtensionManager = Injekt.get()
animeExtensionManager.findAvailableExtensions() launch {
animeExtensionManager.findAvailableExtensions()
}
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}") Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
AnimeSources.init(animeExtensionManager.installedExtensionsFlow) AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
} }
CoroutineScope(Dispatchers.IO).launch { scope.launch {
mangaExtensionManager = Injekt.get() mangaExtensionManager = Injekt.get()
mangaExtensionManager.findAvailableExtensions() launch {
mangaExtensionManager.findAvailableExtensions()
}
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}") Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
MangaSources.init(mangaExtensionManager.installedExtensionsFlow) MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
} }
CoroutineScope(Dispatchers.IO).launch { scope.launch {
novelExtensionManager = Injekt.get() novelExtensionManager = Injekt.get()
novelExtensionManager.findAvailableExtensions() launch {
novelExtensionManager.findAvailableExtensions()
}
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}") Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
NovelSources.init(novelExtensionManager.installedExtensionsFlow) NovelSources.init(novelExtensionManager.installedExtensionsFlow)
} }
@@ -138,7 +145,7 @@ class App : MultiDexApplication() {
downloadAddonManager.init() downloadAddonManager.init()
if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1) { if (PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1) {
CommentsAPI.fetchAuthToken(this@App) CommentsAPI.fetchAuthToken(this@App)
} }
val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager) val useAlarmManager = PrefManager.getVal<Boolean>(PrefName.UseAlarmManager)
val scheduler = TaskScheduler.create(this@App, useAlarmManager) val scheduler = TaskScheduler.create(this@App, useAlarmManager)

View File

@@ -117,7 +117,6 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withC
import com.bumptech.glide.load.resource.gif.GifDrawable import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
@@ -138,12 +137,9 @@ import io.noties.markwon.html.TagHandlerNoOp
import io.noties.markwon.image.AsyncDrawable import io.noties.markwon.image.AsyncDrawable
import io.noties.markwon.image.glide.GlideImagesPlugin import io.noties.markwon.image.glide.GlideImagesPlugin
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@@ -157,6 +153,8 @@ import java.util.TimeZone
import java.util.Timer import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
import kotlin.collections.set import kotlin.collections.set
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@@ -854,6 +852,7 @@ fun savePrefsToDownloads(
} }
) )
} }
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
fun savePrefs(serialized: String, path: String, title: String, context: Context): File? { fun savePrefs(serialized: String, path: String, title: String, context: Context): File? {
var file = File(path, "$title.ani") var file = File(path, "$title.ani")
@@ -921,6 +920,7 @@ fun shareImage(title: String, bitmap: Bitmap, context: Context) {
intent.putExtra(Intent.EXTRA_STREAM, contentUri) intent.putExtra(Intent.EXTRA_STREAM, contentUri)
context.startActivity(Intent.createChooser(intent, "Share $title")) context.startActivity(Intent.createChooser(intent, "Share $title"))
} }
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
fun saveImage(image: Bitmap, path: String, imageFileName: String): File? { fun saveImage(image: Bitmap, path: String, imageFileName: String): File? {
val imageFile = File(path, "$imageFileName.png") val imageFile = File(path, "$imageFileName.png")
@@ -1467,6 +1467,7 @@ fun buildMarkwon(
} }
return false return false
} }
override fun onLoadFailed( override fun onLoadFailed(
e: GlideException?, e: GlideException?,
model: Any?, model: Any?,
@@ -1495,9 +1496,9 @@ fun buildMarkwon(
} }
fun getYoutubeId(url: String): String { fun getYoutubeId(url: String): String {
val regex = """(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|(?:youtu\.be|youtube\.com)/)([^"&?/\s]{11})|youtube\.com/""".toRegex() val regex =
"""(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|(?:youtu\.be|youtube\.com)/)([^"&?/\s]{11})|youtube\.com/""".toRegex()
val matchResult = regex.find(url) val matchResult = regex.find(url)
return matchResult?.groupValues?.getOrNull(1) ?: "" return matchResult?.groupValues?.getOrNull(1) ?: ""
} }
@@ -1524,3 +1525,13 @@ fun getLanguageName(language: String): String? {
} }
return null return null
} }
@OptIn(ExperimentalEncodingApi::class)
fun String.decodeBase64ToString(): String {
return try {
String(Base64.decode(this), Charsets.UTF_8)
} catch (e: Exception) {
Logger.log(e)
""
}
}

View File

@@ -26,9 +26,17 @@ interface DownloadAddonApiV2 {
statCallback: (Double) -> Unit statCallback: (Double) -> Unit
): Long ): Long
suspend fun customFFMpeg(command: String, videoUrls: List<String>, logCallback: (String) -> Unit): Long suspend fun customFFMpeg(
command: String,
videoUrls: List<String>,
logCallback: (String) -> Unit
): Long
suspend fun customFFProbe(command: String, videoUrls: List<String>, logCallback: (String) -> Unit) suspend fun customFFProbe(
command: String,
videoUrls: List<String>,
logCallback: (String) -> Unit
)
fun getState(sessionId: Long): String fun getState(sessionId: Long): String

View File

@@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.addons.AddonDownloader.Companion.hasUpdate import ani.dantotsu.addons.AddonDownloader.Companion.hasUpdate
import ani.dantotsu.addons.AddonInstallReceiver
import ani.dantotsu.addons.AddonListener import ani.dantotsu.addons.AddonListener
import ani.dantotsu.addons.AddonLoader import ani.dantotsu.addons.AddonLoader
import ani.dantotsu.addons.AddonManager import ani.dantotsu.addons.AddonManager
import ani.dantotsu.addons.LoadResult import ani.dantotsu.addons.LoadResult
import ani.dantotsu.addons.AddonInstallReceiver
import ani.dantotsu.media.AddonType import ani.dantotsu.media.AddonType
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName

View File

@@ -41,7 +41,8 @@ class TorrentServerService : Service() {
flags: Int, flags: Int,
startId: Int, startId: Int,
): Int { ): Int {
extension = Injekt.get<TorrentAddonManager>().extension?.extension ?: return START_NOT_STICKY extension =
Injekt.get<TorrentAddonManager>().extension?.extension ?: return START_NOT_STICKY
intent?.let { intent?.let {
if (it.action != null) { if (it.action != null) {
when (it.action) { when (it.action) {

View File

@@ -8,7 +8,6 @@ import androidx.media3.common.util.UnstableApi
import androidx.media3.database.StandaloneDatabaseProvider import androidx.media3.database.StandaloneDatabaseProvider
import ani.dantotsu.addons.download.DownloadAddonManager import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.torrent.TorrentAddonManager import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.parsers.novel.NovelExtensionManager import ani.dantotsu.parsers.novel.NovelExtensionManager

View File

@@ -145,7 +145,10 @@ class AnilistMutations {
return result?.get("errors") == null return result?.get("errors") == null
} }
suspend fun updateCustomLists(animeCustomLists: List<String>?, mangaCustomLists: List<String>?): Boolean { suspend fun updateCustomLists(
animeCustomLists: List<String>?,
mangaCustomLists: List<String>?
): Boolean {
val query = """ val query = """
mutation (${"$"}animeListOptions: MediaListOptionsInput, ${"$"}mangaListOptions: MediaListOptionsInput) { mutation (${"$"}animeListOptions: MediaListOptionsInput, ${"$"}mangaListOptions: MediaListOptionsInput) {
UpdateUser(animeListOptions: ${"$"}animeListOptions, mangaListOptions: ${"$"}mangaListOptions) { UpdateUser(animeListOptions: ${"$"}animeListOptions, mangaListOptions: ${"$"}mangaListOptions) {
@@ -291,7 +294,8 @@ class AnilistMutations {
isFollower isFollower
} }
} }
""".trimIndent()) """.trimIndent()
)
} }
suspend fun toggleLike(id: Int, type: String): ToggleLike? { suspend fun toggleLike(id: Int, type: String): ToggleLike? {
@@ -302,7 +306,8 @@ class AnilistMutations {
__typename __typename
} }
} }
""".trimIndent()) """.trimIndent()
)
} }
suspend fun postActivity(text: String, edit: Int? = null): String { suspend fun postActivity(text: String, edit: Int? = null): String {
@@ -316,10 +321,16 @@ class AnilistMutations {
""".trimIndent() """.trimIndent()
val result = executeQuery<JsonObject>(query) val result = executeQuery<JsonObject>(query)
val errors = result?.get("errors") val errors = result?.get("errors")
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success") return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
?: "Success")
} }
suspend fun postMessage(userId: Int, text: String, edit: Int? = null, isPrivate: Boolean = false): String { suspend fun postMessage(
userId: Int,
text: String,
edit: Int? = null,
isPrivate: Boolean = false
): String {
val encodedText = text.replace("", "").stringSanitizer() val encodedText = text.replace("", "").stringSanitizer()
val query = """ val query = """
mutation { mutation {
@@ -335,7 +346,8 @@ class AnilistMutations {
""".trimIndent() """.trimIndent()
val result = executeQuery<JsonObject>(query) val result = executeQuery<JsonObject>(query)
val errors = result?.get("errors") val errors = result?.get("errors")
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success") return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
?: "Success")
} }
suspend fun postReply(activityId: Int, text: String, edit: Int? = null): String { suspend fun postReply(activityId: Int, text: String, edit: Int? = null): String {
@@ -353,7 +365,8 @@ class AnilistMutations {
""".trimIndent() """.trimIndent()
val result = executeQuery<JsonObject>(query) val result = executeQuery<JsonObject>(query)
val errors = result?.get("errors") val errors = result?.get("errors")
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success") return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
?: "Success")
} }
suspend fun postReview(summary: String, body: String, mediaId: Int, score: Int): String { suspend fun postReview(summary: String, body: String, mediaId: Int, score: Int): String {
@@ -373,7 +386,8 @@ class AnilistMutations {
""".trimIndent() """.trimIndent()
val result = executeQuery<JsonObject>(query) val result = executeQuery<JsonObject>(query)
val errors = result?.get("errors") val errors = result?.get("errors")
return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success") return errors?.toString() ?: (currContext()?.getString(ani.dantotsu.R.string.success)
?: "Success")
} }
suspend fun deleteActivityReply(activityId: Int): Boolean { suspend fun deleteActivityReply(activityId: Int): Boolean {

View File

@@ -359,19 +359,24 @@ class AnilistSearch : ViewModel() {
var searched = false var searched = false
var notSet = true var notSet = true
lateinit var aniMangaSearchResults: AniMangaSearchResults lateinit var aniMangaSearchResults: AniMangaSearchResults
private val aniMangaResult: MutableLiveData<AniMangaSearchResults?> = MutableLiveData<AniMangaSearchResults?>(null) private val aniMangaResult: MutableLiveData<AniMangaSearchResults?> =
MutableLiveData<AniMangaSearchResults?>(null)
lateinit var characterSearchResults: CharacterSearchResults lateinit var characterSearchResults: CharacterSearchResults
private val characterResult: MutableLiveData<CharacterSearchResults?> = MutableLiveData<CharacterSearchResults?>(null) private val characterResult: MutableLiveData<CharacterSearchResults?> =
MutableLiveData<CharacterSearchResults?>(null)
lateinit var studioSearchResults: StudioSearchResults lateinit var studioSearchResults: StudioSearchResults
private val studioResult: MutableLiveData<StudioSearchResults?> = MutableLiveData<StudioSearchResults?>(null) private val studioResult: MutableLiveData<StudioSearchResults?> =
MutableLiveData<StudioSearchResults?>(null)
lateinit var staffSearchResults: StaffSearchResults lateinit var staffSearchResults: StaffSearchResults
private val staffResult: MutableLiveData<StaffSearchResults?> = MutableLiveData<StaffSearchResults?>(null) private val staffResult: MutableLiveData<StaffSearchResults?> =
MutableLiveData<StaffSearchResults?>(null)
lateinit var userSearchResults: UserSearchResults lateinit var userSearchResults: UserSearchResults
private val userResult: MutableLiveData<UserSearchResults?> = MutableLiveData<UserSearchResults?>(null) private val userResult: MutableLiveData<UserSearchResults?> =
MutableLiveData<UserSearchResults?>(null)
fun <T> getSearch(type: SearchType): MutableLiveData<T?> { fun <T> getSearch(type: SearchType): MutableLiveData<T?> {
return when (type) { return when (type) {
@@ -517,12 +522,13 @@ class AnilistSearch : ViewModel() {
) )
) )
private suspend fun loadNextCharacterPage(r: CharacterSearchResults) = characterResult.postValue( private suspend fun loadNextCharacterPage(r: CharacterSearchResults) =
Anilist.query.searchCharacters( characterResult.postValue(
r.page + 1, Anilist.query.searchCharacters(
r.search, r.page + 1,
r.search,
)
) )
)
private suspend fun loadNextStudiosPage(r: StudioSearchResults) = studioResult.postValue( private suspend fun loadNextStudiosPage(r: StudioSearchResults) = studioResult.postValue(
Anilist.query.searchStudios( Anilist.query.searchStudios(

View File

@@ -34,7 +34,8 @@ fun characterInformation(includeMediaInfo: Boolean) = """
month month
day day
} }
${if (includeMediaInfo) """ ${
if (includeMediaInfo) """
media(page: 0,sort:[POPULARITY_DESC,SCORE_DESC]) { media(page: 0,sort:[POPULARITY_DESC,SCORE_DESC]) {
$standardPageInformation $standardPageInformation
edges { edges {
@@ -79,7 +80,8 @@ fun characterInformation(includeMediaInfo: Boolean) = """
} }
} }
} }
}""".prepare() else ""} }""".prepare() else ""
}
""".prepare() """.prepare()
fun studioInformation(page: Int, perPage: Int) = """ fun studioInformation(page: Int, perPage: Int) = """

View File

@@ -14,6 +14,7 @@ data class FeedResponse(
val page: ActivityPage val page: ActivityPage
) : java.io.Serializable ) : java.io.Serializable
} }
@Serializable @Serializable
data class ActivityPage( data class ActivityPage(
@SerialName("activities") @SerialName("activities")

View File

@@ -189,7 +189,7 @@ data class MediaTitle(
// The currently authenticated users preferred title language. Default romaji for non-authenticated // The currently authenticated users preferred title language. Default romaji for non-authenticated
@SerialName("userPreferred") var userPreferred: String, @SerialName("userPreferred") var userPreferred: String,
): java.io.Serializable ) : java.io.Serializable
@Serializable @Serializable
enum class MediaType { enum class MediaType {
@@ -239,6 +239,7 @@ data class AiringSchedule(
// The associate media of the airing episode // The associate media of the airing episode
@SerialName("media") var media: Media?, @SerialName("media") var media: Media?,
) )
@Serializable @Serializable
data class MediaStreamingEpisode( data class MediaStreamingEpisode(
// The title of the episode // The title of the episode
@@ -252,7 +253,8 @@ data class MediaStreamingEpisode(
// The site location of the streaming episode // The site location of the streaming episode
@SerialName("site") var site: String?, @SerialName("site") var site: String?,
) ) : java.io.Serializable
@Serializable @Serializable
data class MediaCoverImage( data class MediaCoverImage(
// The cover image url of the media at its largest size. If this size isn't available, large will be provided instead. // The cover image url of the media at its largest size. If this size isn't available, large will be provided instead.

View File

@@ -69,7 +69,7 @@ data class User(
// The user's previously used names. // The user's previously used names.
// @SerialName("previousNames") var previousNames: List<UserPreviousName>?, // @SerialName("previousNames") var previousNames: List<UserPreviousName>?,
): java.io.Serializable ) : java.io.Serializable
@Serializable @Serializable
data class UserOptions( data class UserOptions(
@@ -123,8 +123,10 @@ data class UserStatisticTypes(
enum class UserTitleLanguage { enum class UserTitleLanguage {
@SerialName("ENGLISH") @SerialName("ENGLISH")
ENGLISH, ENGLISH,
@SerialName("ROMAJI") @SerialName("ROMAJI")
ROMAJI, ROMAJI,
@SerialName("NATIVE") @SerialName("NATIVE")
NATIVE NATIVE
} }
@@ -133,8 +135,10 @@ enum class UserTitleLanguage {
enum class UserStaffNameLanguage { enum class UserStaffNameLanguage {
@SerialName("ROMAJI_WESTERN") @SerialName("ROMAJI_WESTERN")
ROMAJI_WESTERN, ROMAJI_WESTERN,
@SerialName("ROMAJI") @SerialName("ROMAJI")
ROMAJI, ROMAJI,
@SerialName("NATIVE") @SerialName("NATIVE")
NATIVE NATIVE
} }
@@ -143,12 +147,16 @@ enum class UserStaffNameLanguage {
enum class ScoreFormat { enum class ScoreFormat {
@SerialName("POINT_100") @SerialName("POINT_100")
POINT_100, POINT_100,
@SerialName("POINT_10_DECIMAL") @SerialName("POINT_10_DECIMAL")
POINT_10_DECIMAL, POINT_10_DECIMAL,
@SerialName("POINT_10") @SerialName("POINT_10")
POINT_10, POINT_10,
@SerialName("POINT_5") @SerialName("POINT_5")
POINT_5, POINT_5,
@SerialName("POINT_3") @SerialName("POINT_3")
POINT_3, POINT_3,
} }

View File

@@ -372,6 +372,7 @@ object CommentsAPI {
} }
errorMessage("Failed to login after multiple attempts") errorMessage("Failed to login after multiple attempts")
} }
private fun errorMessage(reason: String) { private fun errorMessage(reason: String) {
if (commentsEnabled) Logger.log(reason) if (commentsEnabled) Logger.log(reason)
if (isOnline && commentsEnabled) snackString(reason) if (isOnline && commentsEnabled) snackString(reason)
@@ -410,7 +411,7 @@ object CommentsAPI {
return map return map
} }
private fun requestBuilder(client: OkHttpClient = Injekt.get<NetworkHelper>().client): Requests { fun requestBuilder(client: OkHttpClient = Injekt.get<NetworkHelper>().client): Requests {
return Requests( return Requests(
client, client,
headerBuilder() headerBuilder()

View File

@@ -47,9 +47,9 @@ class Login : AppCompatActivity() {
view.evaluateJavascript( view.evaluateJavascript(
""" """
(function() { (function() {
const wreq = (webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken(); const m = []; webpackChunkdiscord_app.push([[""], {}, e => {for (let c in e.c)m.push(e.c[c])}]);
return wreq; return m.find(n => n?.exports?.default?.getToken !== void 0)?.exports?.default?.getToken();
})() })()
""".trimIndent() """.trimIndent()
) { result -> ) { result ->
login(result.trim('"')) login(result.trim('"'))

View File

@@ -8,8 +8,8 @@ import ani.dantotsu.settings.saving.PrefName
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import kotlin.coroutines.CoroutineContext
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
open class RPC(val token: String, val coroutineContext: CoroutineContext) { open class RPC(val token: String, val coroutineContext: CoroutineContext) {
@@ -34,6 +34,7 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
val stopTimestamp: Long? = null, val stopTimestamp: Long? = null,
val buttons: MutableList<Link> = mutableListOf() val buttons: MutableList<Link> = mutableListOf()
) )
suspend fun createPresence(data: RPCData): String { suspend fun createPresence(data: RPCData): String {
val json = Json { val json = Json {
encodeDefaults = true encodeDefaults = true

View File

@@ -30,7 +30,6 @@ import ani.dantotsu.bottomBar
import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.connections.crashlytics.CrashlyticsInterface
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.download.DownloadCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat import ani.dantotsu.download.DownloadCompat.Companion.loadMediaCompat
import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat import ani.dantotsu.download.DownloadCompat.Companion.loadOfflineAnimeModelCompat
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
@@ -209,7 +208,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
setMessage("Are you sure you want to delete ${item.title}?") setMessage("Are you sure you want to delete ${item.title}?")
setPosButton(R.string.yes) { setPosButton(R.string.yes) {
downloadManager.removeMedia(item.title, type) downloadManager.removeMedia(item.title, type)
val mediaIds = PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values ?: emptySet() val mediaIds =
PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values
?: emptySet()
if (mediaIds.isEmpty()) { if (mediaIds.isEmpty()) {
snackString("No media found") // if this happens, terrible things have happened snackString("No media found") // if this happens, terrible things have happened
} }
@@ -287,10 +288,12 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
} }
downloadsJob = Job() downloadsJob = Job()
CoroutineScope(Dispatchers.IO + downloadsJob).launch { CoroutineScope(Dispatchers.IO + downloadsJob).launch {
val animeTitles = downloadManager.animeDownloadedTypes.map { it.titleName.findValidName() }.distinct() val animeTitles =
downloadManager.animeDownloadedTypes.map { it.titleName.findValidName() }.distinct()
val newAnimeDownloads = mutableListOf<OfflineAnimeModel>() val newAnimeDownloads = mutableListOf<OfflineAnimeModel>()
for (title in animeTitles) { for (title in animeTitles) {
val tDownloads = downloadManager.animeDownloadedTypes.filter { it.titleName.findValidName() == title } val tDownloads =
downloadManager.animeDownloadedTypes.filter { it.titleName.findValidName() == title }
val download = tDownloads.firstOrNull() ?: continue val download = tDownloads.firstOrNull() ?: continue
val offlineAnimeModel = loadOfflineAnimeModel(download) val offlineAnimeModel = loadOfflineAnimeModel(download)
if (offlineAnimeModel.title == "unknown") offlineAnimeModel.title = title if (offlineAnimeModel.title == "unknown") offlineAnimeModel.title = title

View File

@@ -232,12 +232,18 @@ class MangaDownloaderService : Service() {
image.page, image.page,
image.source image.source
) )
if (bitmap == null) {
snackString("${task.chapter} - Retrying to download page ${index.ofLength(3)}, attempt ${retryCount + 1}.")
}
retryCount++ retryCount++
} }
if (bitmap != null) { if (bitmap == null) {
saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap) outputDir.deleteRecursively(this@MangaDownloaderService, false)
throw Exception("${task.chapter} - Unable to download all pages after $retryCount attempts. Try again.")
} }
saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap)
farthest++ farthest++
builder.setProgress(task.imageData.size, farthest, false) builder.setProgress(task.imageData.size, farthest, false)

View File

@@ -177,6 +177,7 @@ object Helper {
downloadManager downloadManager
} }
} }
@Deprecated("exoplayer download manager is no longer used") @Deprecated("exoplayer download manager is no longer used")
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun getSimpleCache(context: Context): SimpleCache { fun getSimpleCache(context: Context): SimpleCache {
@@ -189,6 +190,7 @@ object Helper {
simpleCache!! simpleCache!!
} }
} }
@Synchronized @Synchronized
@Deprecated("exoplayer download manager is no longer used") @Deprecated("exoplayer download manager is no longer used")
private fun getDownloadDirectory(context: Context): File { private fun getDownloadDirectory(context: Context): File {
@@ -200,12 +202,16 @@ object Helper {
} }
return downloadDirectory!! return downloadDirectory!!
} }
@Deprecated("exoplayer download manager is no longer used") @Deprecated("exoplayer download manager is no longer used")
private var download: DownloadManager? = null private var download: DownloadManager? = null
@Deprecated("exoplayer download manager is no longer used") @Deprecated("exoplayer download manager is no longer used")
private const val DOWNLOAD_CONTENT_DIRECTORY = "Anime_Downloads" private const val DOWNLOAD_CONTENT_DIRECTORY = "Anime_Downloads"
@Deprecated("exoplayer download manager is no longer used") @Deprecated("exoplayer download manager is no longer used")
private var simpleCache: SimpleCache? = null private var simpleCache: SimpleCache? = null
@Deprecated("exoplayer download manager is no longer used") @Deprecated("exoplayer download manager is no longer used")
private var downloadDirectory: File? = null private var downloadDirectory: File? = null
} }

View File

@@ -22,9 +22,9 @@ import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
import ani.dantotsu.bottomBar import ani.dantotsu.bottomBar
import ani.dantotsu.connections.anilist.AniMangaSearchResults
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistAnimeViewModel import ani.dantotsu.connections.anilist.AnilistAnimeViewModel
import ani.dantotsu.connections.anilist.AniMangaSearchResults
import ani.dantotsu.connections.anilist.getUserId import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentAnimeBinding import ani.dantotsu.databinding.FragmentAnimeBinding
import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.MediaAdaptor
@@ -277,8 +277,9 @@ class AnimeFragment : Fragment() {
running = true running = true
scope.launch { scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Anilist.userid = PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null) Anilist.userid =
?.toIntOrNull() PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
?.toIntOrNull()
if (Anilist.userid == null) { if (Anilist.userid == null) {
getUserId(requireContext()) { getUserId(requireContext()) {
load() load()

View File

@@ -20,7 +20,6 @@ import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.MediaPageTransformer import ani.dantotsu.MediaPageTransformer
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType.Companion.toAnilistString
import ani.dantotsu.databinding.ItemAnimePageBinding import ani.dantotsu.databinding.ItemAnimePageBinding
import ani.dantotsu.databinding.LayoutTrendingBinding import ani.dantotsu.databinding.LayoutTrendingBinding
import ani.dantotsu.getAppString import ani.dantotsu.getAppString
@@ -266,7 +265,15 @@ class AnimePageAdapter : RecyclerView.Adapter<AnimePageAdapter.AnimePageViewHold
} }
} }
fun init(adaptor: MediaAdaptor, recyclerView: RecyclerView, progress: View, title: View , more: View , string: String, media : MutableList<Media>) { fun init(
adaptor: MediaAdaptor,
recyclerView: RecyclerView,
progress: View,
title: View,
more: View,
string: String,
media: MutableList<Media>
) {
progress.visibility = View.GONE progress.visibility = View.GONE
recyclerView.adapter = adaptor recyclerView.adapter = adaptor
recyclerView.layoutManager = recyclerView.layoutManager =

View File

@@ -469,7 +469,9 @@ class HomeFragment : Fragment() {
scope.launch { scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// Get user data first // Get user data first
Anilist.userid = PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)?.toIntOrNull() Anilist.userid =
PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
?.toIntOrNull()
if (Anilist.userid == null) { if (Anilist.userid == null) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
getUserId(requireContext()) { getUserId(requireContext()) {

View File

@@ -102,7 +102,7 @@ class LoginFragment : Fragment() {
requireActivity().customAlertDialog().apply { requireActivity().customAlertDialog().apply {
setTitle("Enter Password") setTitle("Enter Password")
setCustomView(dialogView.root) setCustomView(dialogView.root)
setPosButton(R.string.ok){ setPosButton(R.string.ok) {
val editText = dialogView.userAgentTextBox val editText = dialogView.userAgentTextBox
if (editText.text?.isNotBlank() == true) { if (editText.text?.isNotBlank() == true) {
editText.text?.toString()?.trim()?.toCharArray(password) editText.text?.toString()?.trim()?.toCharArray(password)

View File

@@ -20,9 +20,9 @@ import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.Refresh import ani.dantotsu.Refresh
import ani.dantotsu.bottomBar import ani.dantotsu.bottomBar
import ani.dantotsu.connections.anilist.AniMangaSearchResults
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistMangaViewModel import ani.dantotsu.connections.anilist.AnilistMangaViewModel
import ani.dantotsu.connections.anilist.AniMangaSearchResults
import ani.dantotsu.connections.anilist.getUserId import ani.dantotsu.connections.anilist.getUserId
import ani.dantotsu.databinding.FragmentMangaBinding import ani.dantotsu.databinding.FragmentMangaBinding
import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.MediaAdaptor
@@ -169,7 +169,10 @@ class MangaFragment : Fragment() {
} }
model.getPopularManga().observe(viewLifecycleOwner) { model.getPopularManga().observe(viewLifecycleOwner) {
if (it != null) { if (it != null) {
mangaPageAdapter.updateTrendingManga(MediaAdaptor(0, it, requireActivity()), it) mangaPageAdapter.updateTrendingManga(
MediaAdaptor(0, it, requireActivity()),
it
)
} }
} }
model.getPopularManhwa().observe(viewLifecycleOwner) { model.getPopularManhwa().observe(viewLifecycleOwner) {
@@ -262,8 +265,9 @@ class MangaFragment : Fragment() {
running = true running = true
scope.launch { scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Anilist.userid = PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null) Anilist.userid =
?.toIntOrNull() PrefManager.getNullableVal<String>(PrefName.AnilistUserId, null)
?.toIntOrNull()
if (Anilist.userid == null) { if (Anilist.userid == null) {
getUserId(requireContext()) { getUserId(requireContext()) {
load() load()

View File

@@ -266,10 +266,10 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
adaptor: MediaAdaptor, adaptor: MediaAdaptor,
recyclerView: RecyclerView, recyclerView: RecyclerView,
progress: View, progress: View,
title: View , title: View,
more: View , more: View,
string: String, string: String,
media : MutableList<Media> media: MutableList<Media>
) { ) {
progress.visibility = View.GONE progress.visibility = View.GONE
recyclerView.adapter = adaptor recyclerView.adapter = adaptor

View File

@@ -38,7 +38,7 @@ class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs)
fun setColor(int: Int) { fun setColor(int: Int) {
paint.color = if (int < booleanList.size && booleanList[int]) { paint.color = if (int < booleanList.size && booleanList[int]) {
Color.GRAY Color.GRAY
} else { } else {
if (isUser) secondColor else primaryColor if (isUser) secondColor else primaryColor
} }
@@ -58,7 +58,7 @@ class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs)
} else { } else {
val effectiveAngle = totalAngle / parts val effectiveAngle = totalAngle / parts
for (i in 0 until parts) { for (i in 0 until parts) {
val startAngle = i * (effectiveAngle + gapAngle) -90f val startAngle = i * (effectiveAngle + gapAngle) - 90f
path.reset() path.reset()
path.addArc( path.addArc(
centerX - radius, centerX - radius,
@@ -74,7 +74,7 @@ class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs)
} }
fun setParts(parts: Int, list : List<Boolean> = mutableListOf(), isUser: Boolean) { fun setParts(parts: Int, list: List<Boolean> = mutableListOf(), isUser: Boolean) {
this.parts = parts this.parts = parts
this.booleanList = list this.booleanList = list
this.isUser = isUser this.isUser = isUser

View File

@@ -46,8 +46,8 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
val key = "activities" val key = "activities"
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf()) val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
if (activity.getOrNull(position) != null) { if (activity.getOrNull(position) != null) {
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity)
val startIndex = if ( startFrom > 0) startFrom else 0 val startIndex = if (startFrom > 0) startFrom else 0
binding.stories.setStoriesList( binding.stories.setStoriesList(
activityList = activity[position].activity, activityList = activity[position].activity,
startIndex = startIndex + 1 startIndex = startIndex + 1
@@ -58,6 +58,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
} }
} }
private fun findFirstNonMatch(watchedActivity: Set<Int>, activity: List<Activity>): Int { private fun findFirstNonMatch(watchedActivity: Set<Int>, activity: List<Activity>): Int {
for (activityItem in activity) { for (activityItem in activity) {
if (activityItem.id !in watchedActivity) { if (activityItem.id !in watchedActivity) {
@@ -86,13 +87,14 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
binding.stories.pause() binding.stories.pause()
} }
} }
override fun onStoriesEnd() { override fun onStoriesEnd() {
position += 1 position += 1
if (position < activity.size) { if (position < activity.size) {
val key = "activities" val key = "activities"
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf()) val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity)
val startIndex= if ( startFrom > 0) startFrom else 0 val startIndex = if (startFrom > 0) startFrom else 0
binding.stories.startAnimation(slideOutLeft) binding.stories.startAnimation(slideOutLeft)
binding.stories.setStoriesList(activity[position].activity, startIndex + 1) binding.stories.setStoriesList(activity[position].activity, startIndex + 1)
binding.stories.startAnimation(slideInRight) binding.stories.startAnimation(slideInRight)
@@ -106,15 +108,16 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
if (position >= 0 && activity[position].activity.isNotEmpty()) { if (position >= 0 && activity[position].activity.isNotEmpty()) {
val key = "activities" val key = "activities"
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf()) val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity)
val startIndex = if ( startFrom > 0) startFrom else 0 val startIndex = if (startFrom > 0) startFrom else 0
binding.stories.startAnimation(slideOutRight) binding.stories.startAnimation(slideOutRight)
binding.stories.setStoriesList(activity[position].activity,startIndex + 1) binding.stories.setStoriesList(activity[position].activity, startIndex + 1)
binding.stories.startAnimation(slideInLeft) binding.stories.startAnimation(slideInLeft)
} else { } else {
finish() finish()
} }
} }
companion object { companion object {
var user: ArrayList<User> = arrayListOf() var user: ArrayList<User> = arrayListOf()
} }

View File

@@ -262,8 +262,6 @@ class Stories @JvmOverloads constructor(
} }
private fun rightPanelTouch() { private fun rightPanelTouch() {
Logger.log("rightPanelTouch: $storyIndex") Logger.log("rightPanelTouch: $storyIndex")
if (storyIndex == activityList.size) { if (storyIndex == activityList.size) {
@@ -366,8 +364,8 @@ class Stories @JvmOverloads constructor(
if ( if (
story.status?.contains("completed") == false && story.status?.contains("completed") == false &&
!story.status.contains("plans") && !story.status.contains("plans") &&
!story.status.contains("repeating")&& !story.status.contains("repeating") &&
!story.status.contains("paused")&& !story.status.contains("paused") &&
!story.status.contains("dropped") !story.status.contains("dropped")
) { ) {
"of ${story.media?.title?.userPreferred}" "of ${story.media?.title?.userPreferred}"
@@ -469,6 +467,7 @@ class Stories @JvmOverloads constructor(
} }
} }
} }
private var startClickTime = 0L private var startClickTime = 0L
private var startX = 0f private var startX = 0f
private var startY = 0f private var startY = 0f
@@ -478,7 +477,8 @@ class Stories @JvmOverloads constructor(
onTouchView(view, event) onTouchView(view, event)
return true return true
} }
private fun onTouchView(view: View, event: MotionEvent, isText: Boolean = false){
private fun onTouchView(view: View, event: MotionEvent, isText: Boolean = false) {
val maxClickDuration = 200 val maxClickDuration = 200
val screenWidth = view.width val screenWidth = view.width
val leftHalf = screenWidth / 2 val leftHalf = screenWidth / 2
@@ -492,6 +492,7 @@ class Stories @JvmOverloads constructor(
pause() pause()
isLongPress = false isLongPress = false
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
val deltaX = event.x - startX val deltaX = event.x - startX
val deltaY = event.y - startY val deltaY = event.y - startY
@@ -499,6 +500,7 @@ class Stories @JvmOverloads constructor(
isLongPress = true isLongPress = true
} }
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
val clickDuration = Calendar.getInstance().timeInMillis - startClickTime val clickDuration = Calendar.getInstance().timeInMillis - startClickTime
if (isText) { if (isText) {

View File

@@ -46,7 +46,7 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
.putExtra("type", "activity"), .putExtra("type", "activity"),
null null
) )
}else{ } else {
ContextCompat.startActivity( ContextCompat.startActivity(
itemView.context, itemView.context,
Intent( Intent(
@@ -76,10 +76,15 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
setAnimation(b.root.context, b.root) setAnimation(b.root.context, b.root)
val user = user[position] val user = user[position]
b.profileUserAvatar.loadImage(user.pfp) b.profileUserAvatar.loadImage(user.pfp)
b.profileUserName.text = if (Anilist.userid == user.id) getAppString(R.string.your_story) else user.name b.profileUserName.text =
if (Anilist.userid == user.id) getAppString(R.string.your_story) else user.name
val watchedActivity = PrefManager.getCustomVal<Set<Int>>("activities", setOf()) val watchedActivity = PrefManager.getCustomVal<Set<Int>>("activities", setOf())
val booleanList = user.activity.map { watchedActivity.contains(it.id) } val booleanList = user.activity.map { watchedActivity.contains(it.id) }
b.profileUserStatusIndicator.setParts(user.activity.size, booleanList, user.id == Anilist.userid) b.profileUserStatusIndicator.setParts(
user.activity.size,
booleanList,
user.id == Anilist.userid
)
} }

View File

@@ -55,7 +55,11 @@ class CharacterAdapter(
).toBundle() ).toBundle()
) )
} }
itemView.setOnLongClickListener { copyToClipboard(characterList[bindingAdapterPosition].name ?: ""); true } itemView.setOnLongClickListener {
copyToClipboard(
characterList[bindingAdapterPosition].name ?: ""
); true
}
} }
} }
} }

View File

@@ -7,11 +7,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.buildMarkwon
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.databinding.ItemCharacterDetailsBinding import ani.dantotsu.databinding.ItemCharacterDetailsBinding
import ani.dantotsu.others.SpoilerPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) : class CharacterDetailsAdapter(private val character: Character, private val activity: Activity) :
RecyclerView.Adapter<CharacterDetailsAdapter.GenreViewHolder>() { RecyclerView.Adapter<CharacterDetailsAdapter.GenreViewHolder>() {
@@ -24,7 +22,9 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) { override fun onBindViewHolder(holder: GenreViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
val desc = val desc =
(if (character.age != "null") "${currActivity()!!.getString(R.string.age)} ${character.age}" else "") + (if (character.id == 4004)
"![za wardo](https://media1.tenor.com/m/_z1tmCJnL2wAAAAd/za-warudo.gif) \n" else "") +
(if (character.age != "null") "${currActivity()!!.getString(R.string.age)} ${character.age}" else "") +
(if (character.dateOfBirth.toString() != "") (if (character.dateOfBirth.toString() != "")
"${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") + "${currActivity()!!.getString(R.string.birthday)} ${character.dateOfBirth.toString()}" else "") +
(if (character.gender != "null") (if (character.gender != "null")
@@ -41,8 +41,7 @@ class CharacterDetailsAdapter(private val character: Character, private val acti
} else "") + "\n" + character.description } else "") + "\n" + character.description
binding.characterDesc.isTextSelectable binding.characterDesc.isTextSelectable
val markWon = Markwon.builder(activity).usePlugin(SoftBreakAddsNewLinePlugin.create()) val markWon = buildMarkwon(activity)
.usePlugin(SpoilerPlugin()).build()
markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||")) markWon.setMarkdown(binding.characterDesc, desc.replace("~!", "||").replace("!~", "||"))
binding.voiceActorRecycler.adapter = AuthorAdapter(character.voiceActor ?: arrayListOf()) binding.voiceActorRecycler.adapter = AuthorAdapter(character.voiceActor ?: arrayListOf())
binding.voiceActorRecycler.layoutManager = LinearLayoutManager( binding.voiceActorRecycler.layoutManager = LinearLayoutManager(

View File

@@ -9,7 +9,7 @@ import android.view.animation.Animation
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.databinding.ItemSearchHeaderBinding import ani.dantotsu.databinding.ItemSearchHeaderBinding
abstract class HeaderInterface: RecyclerView.Adapter<HeaderInterface.SearchHeaderViewHolder>() { abstract class HeaderInterface : RecyclerView.Adapter<HeaderInterface.SearchHeaderViewHolder>() {
private val itemViewType = 6969 private val itemViewType = 6969
var search: Runnable? = null var search: Runnable? = null
var requestFocus: Runnable? = null var requestFocus: Runnable? = null

View File

@@ -147,7 +147,7 @@ fun Media?.deleteFromList(
scope.launch { scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
this@deleteFromList?.let { media -> this@deleteFromList?.let { media ->
val _id = id ?: Anilist.query.userMediaDetails(media).userListId; val _id = id ?: Anilist.query.userMediaDetails(media).userListId
_id?.let { listId -> _id?.let { listId ->
try { try {
Anilist.mutation.deleteList(listId) Anilist.mutation.deleteList(listId)

View File

@@ -4,6 +4,7 @@ import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.view.GestureDetector import android.view.GestureDetector
@@ -12,6 +13,8 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.ImageView import android.widget.ImageView
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -19,8 +22,10 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.bold import androidx.core.text.bold
import androidx.core.text.color import androidx.core.text.color
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.marginBottom import androidx.core.view.marginBottom
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@@ -79,6 +84,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia() var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia()
val id = intent.getIntExtra("mediaId", -1) val id = intent.getIntExtra("mediaId", -1)
@@ -109,6 +115,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
// Ui init // Ui init
initActivity(this) initActivity(this)
binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.mediaViewPager.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight bottomMargin = navBarHeight
} }
@@ -132,10 +139,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
val navBarBottomMargin = if (resources.configuration.orientation == val navBarBottomMargin = if (resources.configuration.orientation ==
Configuration.ORIENTATION_LANDSCAPE Configuration.ORIENTATION_LANDSCAPE
) 0 else navBarHeight ) 0 else navBarHeight
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> { navBar.setPadding(
rightMargin = navBarRightMargin navBar.paddingLeft,
bottomMargin = navBarBottomMargin navBar.paddingTop,
} navBar.paddingRight + navBarRightMargin,
navBar.paddingBottom + navBarBottomMargin
)
binding.mediaBanner.updateLayoutParams { height += statusBarHeight } binding.mediaBanner.updateLayoutParams { height += statusBarHeight }
binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight } binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight }
binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight } binding.mediaClose.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin += statusBarHeight }
@@ -251,10 +260,12 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi
fun total() { fun total() {
val text = SpannableStringBuilder().apply { val text = SpannableStringBuilder().apply {
val white = this@MediaDetailsActivity.getThemeColor(com.google.android.material.R.attr.colorOnBackground) val white =
this@MediaDetailsActivity.getThemeColor(com.google.android.material.R.attr.colorOnBackground)
if (media.userStatus != null) { if (media.userStatus != null) {
append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num)) append(if (media.anime != null) getString(R.string.watched_num) else getString(R.string.read_num))
val colorSecondary = getThemeColor(com.google.android.material.R.attr.colorSecondary) val colorSecondary =
getThemeColor(com.google.android.material.R.attr.colorSecondary)
bold { color(colorSecondary) { append("${media.userProgress}") } } bold { color(colorSecondary) { append("${media.userProgress}") } }
append( append(
if (media.anime != null) getString(R.string.episodes_out_of) else getString( if (media.anime != null) getString(R.string.episodes_out_of) else getString(

View File

@@ -100,6 +100,7 @@ class MediaDetailsViewModel : ViewModel() {
if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s)) if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s))
} }
} }
private val anifyEpisodes: MutableLiveData<Map<String, Episode>> = private val anifyEpisodes: MutableLiveData<Map<String, Episode>> =
MutableLiveData<Map<String, Episode>>(null) MutableLiveData<Map<String, Episode>>(null)

View File

@@ -18,9 +18,8 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import java.util.ArrayList
class MediaListViewActivity: AppCompatActivity() { class MediaListViewActivity : AppCompatActivity() {
private lateinit var binding: ActivityMediaListViewBinding private lateinit var binding: ActivityMediaListViewBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -52,7 +51,8 @@ class MediaListViewActivity: AppCompatActivity() {
binding.listAppBar.setBackgroundColor(primaryColor) binding.listAppBar.setBackgroundColor(primaryColor)
binding.listTitle.setTextColor(primaryTextColor) binding.listTitle.setTextColor(primaryTextColor)
val screenWidth = resources.displayMetrics.run { widthPixels / density } val screenWidth = resources.displayMetrics.run { widthPixels / density }
val mediaList = passedMedia ?: intent.getSerialized("media") as? ArrayList<Media> ?: ArrayList() val mediaList =
passedMedia ?: intent.getSerialized("media") as? ArrayList<Media> ?: ArrayList()
if (passedMedia != null) passedMedia = null if (passedMedia != null) passedMedia = null
val view = PrefManager.getCustomVal("mediaView", 0) val view = PrefManager.getCustomVal("mediaView", 0)
var mediaView: View = when (view) { var mediaView: View = when (view) {

View File

@@ -44,7 +44,10 @@ class MediaSocialAdapter(
profileUserName.text = user.name profileUserName.text = user.name
profileInfo.apply { profileInfo.apply {
text = when (user.status) { text = when (user.status) {
"CURRENT" -> if (type == "ANIME") getAppString(R.string.watching) else getAppString(R.string.reading) "CURRENT" -> if (type == "ANIME") getAppString(R.string.watching) else getAppString(
R.string.reading
)
else -> user.status ?: "" else -> user.status ?: ""
} }
visibility = View.VISIBLE visibility = View.VISIBLE
@@ -63,10 +66,12 @@ class MediaSocialAdapter(
profileCompactProgressContainer.visibility = View.VISIBLE profileCompactProgressContainer.visibility = View.VISIBLE
profileUserAvatar.setOnClickListener { profileUserAvatar.setOnClickListener {
ContextCompat.startActivity(root.context, ContextCompat.startActivity(
root.context,
Intent(root.context, ProfileActivity::class.java) Intent(root.context, ProfileActivity::class.java)
.putExtra("userId", user.id), .putExtra("userId", user.id),
null) null
)
} }
profileUserAvatarContainer.setOnLongClickListener { profileUserAvatarContainer.setOnLongClickListener {
ImageViewDialog.newInstance( ImageViewDialog.newInstance(

View File

@@ -1,23 +1,15 @@
package ani.dantotsu.media package ani.dantotsu.media
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.view.View import android.view.View
import androidx.activity.ComponentActivity
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.ItemReviewsBinding import ani.dantotsu.databinding.ItemReviewsBinding
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.openImage import ani.dantotsu.openImage
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.ActivityItemBuilder import ani.dantotsu.profile.activity.ActivityItemBuilder
import ani.dantotsu.toast import ani.dantotsu.toast
@@ -40,7 +32,7 @@ class ReviewAdapter(
binding.reviewUserAvatar.loadImage(review.user?.avatar?.medium) binding.reviewUserAvatar.loadImage(review.user?.avatar?.medium)
binding.reviewText.text = review.summary binding.reviewText.text = review.summary
binding.reviewPostTime.text = ActivityItemBuilder.getDateTime(review.createdAt) binding.reviewPostTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
val text = "[${review.score/ 10.0f}]" val text = "[${review.score / 10.0f}]"
binding.reviewTag.text = text binding.reviewTag.text = text
binding.root.setOnClickListener { binding.root.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
@@ -85,6 +77,7 @@ class ReviewAdapter(
override fun initializeViewBinding(view: View): ItemReviewsBinding { override fun initializeViewBinding(view: View): ItemReviewsBinding {
return ItemReviewsBinding.bind(view) return ItemReviewsBinding.bind(view)
} }
private fun userVote(type: String) { private fun userVote(type: String) {
when (type) { when (type) {
"NO_VOTE" -> { "NO_VOTE" -> {

View File

@@ -20,7 +20,6 @@ import ani.dantotsu.initActivity
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.openImage import ani.dantotsu.openImage
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.ActivityItemBuilder import ani.dantotsu.profile.activity.ActivityItemBuilder
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
@@ -52,8 +51,9 @@ class ReviewViewActivity : AppCompatActivity() {
binding.userAvatar.loadImage(review.user?.avatar?.medium) binding.userAvatar.loadImage(review.user?.avatar?.medium)
binding.userTime.text = ActivityItemBuilder.getDateTime(review.createdAt) binding.userTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
binding.userContainer.setOnClickListener { binding.userContainer.setOnClickListener {
startActivity(Intent(this, ProfileActivity::class.java) startActivity(
.putExtra("userId", review.user?.id) Intent(this, ProfileActivity::class.java)
.putExtra("userId", review.user?.id)
) )
} }
binding.userAvatar.openImage( binding.userAvatar.openImage(
@@ -61,8 +61,9 @@ class ReviewViewActivity : AppCompatActivity() {
review.user?.avatar?.medium ?: "" review.user?.avatar?.medium ?: ""
) )
binding.userAvatar.setOnClickListener { binding.userAvatar.setOnClickListener {
startActivity(Intent(this, ProfileActivity::class.java) startActivity(
.putExtra("userId", review.user?.id) Intent(this, ProfileActivity::class.java)
.putExtra("userId", review.user?.id)
) )
} }
binding.profileUserBio.settings.loadWithOverviewMode = true binding.profileUserBio.settings.loadWithOverviewMode = true

View File

@@ -13,10 +13,10 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.connections.anilist.AniMangaSearchResults
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.AnilistSearch import ani.dantotsu.connections.anilist.AnilistSearch
import ani.dantotsu.connections.anilist.AnilistSearch.SearchType import ani.dantotsu.connections.anilist.AnilistSearch.SearchType
import ani.dantotsu.connections.anilist.AniMangaSearchResults
import ani.dantotsu.connections.anilist.CharacterSearchResults import ani.dantotsu.connections.anilist.CharacterSearchResults
import ani.dantotsu.connections.anilist.StaffSearchResults import ani.dantotsu.connections.anilist.StaffSearchResults
import ani.dantotsu.connections.anilist.StudioSearchResults import ani.dantotsu.connections.anilist.StudioSearchResults
@@ -377,7 +377,10 @@ class SearchActivity : AppCompatActivity() {
} }
SearchType.CHARACTER -> { SearchType.CHARACTER -> {
characterAdaptor.notifyItemRangeRemoved(0, model.characterSearchResults.results.size) characterAdaptor.notifyItemRangeRemoved(
0,
model.characterSearchResults.results.size
)
model.characterSearchResults.results.clear() model.characterSearchResults.results.clear()
} }

View File

@@ -57,6 +57,7 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Sear
searchHistoryAdapter = SearchHistoryAdapter(type) { searchHistoryAdapter = SearchHistoryAdapter(type) {
binding.searchBarText.setText(it) binding.searchBarText.setText(it)
binding.searchBarText.setSelection(it.length)
} }
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context) binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
binding.searchHistoryList.adapter = searchHistoryAdapter binding.searchHistoryList.adapter = searchHistoryAdapter

View File

@@ -346,7 +346,9 @@ class SearchFilterBottomDialog : BottomSheetDialogFragment() {
binding.searchGenresGrid.isChecked = false binding.searchGenresGrid.isChecked = false
binding.searchFilterTags.adapter = binding.searchFilterTags.adapter =
FilterChipAdapter(Anilist.tags?.get(activity.aniMangaResult.isAdult) ?: listOf()) { chip -> FilterChipAdapter(
Anilist.tags?.get(activity.aniMangaResult.isAdult) ?: listOf()
) { chip ->
val tag = chip.text.toString() val tag = chip.text.toString()
chip.isChecked = selectedTags.contains(tag) chip.isChecked = selectedTags.contains(tag)
chip.isCloseIconVisible = exTags.contains(tag) chip.isCloseIconVisible = exTags.contains(tag)

View File

@@ -55,7 +55,11 @@ class StudioAdapter(
).toBundle() ).toBundle()
) )
} }
itemView.setOnLongClickListener { copyToClipboard(studioList[bindingAdapterPosition].name ?: ""); true } itemView.setOnLongClickListener {
copyToClipboard(
studioList[bindingAdapterPosition].name
); true
}
} }
} }
} }

View File

@@ -1,6 +1,8 @@
package ani.dantotsu.media package ani.dantotsu.media
import android.content.Context import android.content.Context
import androidx.core.net.toFile
import androidx.core.net.toUri
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
import ani.dantotsu.parsers.SubtitleType import ani.dantotsu.parsers.SubtitleType
@@ -21,28 +23,32 @@ class SubtitleDownloader {
suspend fun loadSubtitleType(url: String): SubtitleType = suspend fun loadSubtitleType(url: String): SubtitleType =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
return@withContext try { return@withContext try {
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it if (!url.startsWith("file")) {
val networkHelper = Injekt.get<NetworkHelper>() // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
val request = Request.Builder() val networkHelper = Injekt.get<NetworkHelper>()
.url(url) val request = Request.Builder()
.build() .url(url)
.build()
val response = networkHelper.client.newCall(request).execute() val response = networkHelper.client.newCall(request).execute()
// Check if response is successful // Check if response is successful
if (response.isSuccessful) { if (response.isSuccessful) {
val responseBody = response.body.string() val responseBody = response.body.string()
val subtitleType = when { val subtitleType = getType(responseBody)
responseBody.contains("[Script Info]") -> SubtitleType.ASS
responseBody.contains("WEBVTT") -> SubtitleType.VTT subtitleType
else -> SubtitleType.SRT } else {
SubtitleType.UNKNOWN
} }
subtitleType
} else { } else {
SubtitleType.UNKNOWN val uri = url.toUri()
val file = uri.toFile()
val fileBody = file.readText()
val subtitleType = getType(fileBody)
subtitleType
} }
} catch (e: Exception) { } catch (e: Exception) {
Logger.log(e) Logger.log(e)
@@ -50,6 +56,15 @@ class SubtitleDownloader {
} }
} }
private fun getType(content: String): SubtitleType {
return when {
content.contains("[Script Info]") -> SubtitleType.ASS
content.contains("WEBVTT") -> SubtitleType.VTT
content.contains("SRT") -> SubtitleType.SRT
else -> SubtitleType.UNKNOWN
}
}
//actually downloads lol //actually downloads lol
@Deprecated("handled externally") @Deprecated("handled externally")
suspend fun downloadSubtitle( suspend fun downloadSubtitle(

View File

@@ -31,6 +31,7 @@ class SupportingSearchAdapter(private val activity: SearchActivity, private val
searchHistoryAdapter = SearchHistoryAdapter(type) { searchHistoryAdapter = SearchHistoryAdapter(type) {
binding.searchBarText.setText(it) binding.searchBarText.setText(it)
binding.searchBarText.setSelection(it.length)
} }
binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context) binding.searchHistoryList.layoutManager = LinearLayoutManager(binding.root.context)
binding.searchHistoryList.adapter = searchHistoryAdapter binding.searchHistoryList.adapter = searchHistoryAdapter

View File

@@ -20,8 +20,8 @@ import ani.dantotsu.R
import ani.dantotsu.currActivity import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemMediaSourceBinding
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.databinding.ItemMediaSourceBinding
import ani.dantotsu.displayTimer import ani.dantotsu.displayTimer
import ani.dantotsu.isOnline import ani.dantotsu.isOnline
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
@@ -61,7 +61,8 @@ class AnimeWatchAdapter(
private var _binding: ItemMediaSourceBinding? = null private var _binding: ItemMediaSourceBinding? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) val bind =
ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(bind) return ViewHolder(bind)
} }
@@ -285,7 +286,7 @@ class AnimeWatchAdapter(
fragment.requireContext().customAlertDialog().apply { fragment.requireContext().customAlertDialog().apply {
setTitle(" Delete Progress for all episodes of ${media.nameRomaji}") setTitle(" Delete Progress for all episodes of ${media.nameRomaji}")
setMessage("This will delete all the locally stored progress for all episodes") setMessage("This will delete all the locally stored progress for all episodes")
setPosButton(R.string.ok){ setPosButton(R.string.ok) {
val prefix = "${media.id}_" val prefix = "${media.id}_"
val regex = Regex("^${prefix}\\d+$") val regex = Regex("^${prefix}\\d+$")
@@ -300,7 +301,7 @@ class AnimeWatchAdapter(
} }
} }
resetProgressDef.text = getString(currContext()!!,R.string.clear_stored_episode) resetProgressDef.text = getString(currContext()!!, R.string.clear_stored_episode)
// Hidden // Hidden
mangaScanlatorContainer.visibility = View.GONE mangaScanlatorContainer.visibility = View.GONE
@@ -327,7 +328,7 @@ class AnimeWatchAdapter(
fragment.requireContext().customAlertDialog().apply { fragment.requireContext().customAlertDialog().apply {
setTitle(" Delete Progress for all episodes of ${media.nameRomaji}") setTitle(" Delete Progress for all episodes of ${media.nameRomaji}")
setMessage("This will delete all the locally stored progress for all episodes") setMessage("This will delete all the locally stored progress for all episodes")
setPosButton(R.string.ok){ setPosButton(R.string.ok) {
val prefix = "${media.id}_" val prefix = "${media.id}_"
val regex = Regex("^${prefix}\\d+$") val regex = Regex("^${prefix}\\d+$")
@@ -478,7 +479,8 @@ class AnimeWatchAdapter(
binding.sourceProgressBar.visibility = View.GONE binding.sourceProgressBar.visibility = View.GONE
val sourceFound = media.anime.episodes!!.isNotEmpty() val sourceFound = media.anime.episodes!!.isNotEmpty()
val isDownloadedSource = watchSources[media.selected!!.sourceIndex] is OfflineAnimeParser val isDownloadedSource =
watchSources[media.selected!!.sourceIndex] is OfflineAnimeParser
if (isDownloadedSource) { if (isDownloadedSource) {
binding.sourceNotFound.text = if (sourceFound) { binding.sourceNotFound.text = if (sourceFound) {
@@ -487,7 +489,8 @@ class AnimeWatchAdapter(
currActivity()!!.getString(R.string.download_not_found) currActivity()!!.getString(R.string.download_not_found)
} }
} else { } else {
binding.sourceNotFound.text = currActivity()!!.getString(R.string.source_not_found) binding.sourceNotFound.text =
currActivity()!!.getString(R.string.source_not_found)
} }
binding.sourceNotFound.isGone = sourceFound binding.sourceNotFound.isGone = sourceFound

View File

@@ -1,7 +1,6 @@
package ani.dantotsu.media.anime package ani.dantotsu.media.anime
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -28,10 +27,8 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.addons.download.DownloadAddonManager import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.connections.anilist.api.MediaStreamingEpisode
import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.databinding.FragmentMediaSourceBinding
import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadedType
import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager
@@ -49,7 +46,6 @@ import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.subscription.SubscriptionHelper import ani.dantotsu.notifications.subscription.SubscriptionHelper
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
import ani.dantotsu.others.Anify
import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.AnimeParser import ani.dantotsu.parsers.AnimeParser
import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.AnimeSources
@@ -236,13 +232,16 @@ class AnimeWatchFragment : Fragment() {
episodes.forEach { (i, episode) -> episodes.forEach { (i, episode) ->
if (media.anime?.anifyEpisodes != null) { if (media.anime?.anifyEpisodes != null) {
if (media.anime!!.anifyEpisodes!!.containsKey(i)) { if (media.anime!!.anifyEpisodes!!.containsKey(i)) {
episode.desc = media.anime!!.anifyEpisodes!![i]?.desc ?: episode.desc episode.desc =
media.anime!!.anifyEpisodes!![i]?.desc ?: episode.desc
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely( episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
episode.title ?: "" episode.title ?: ""
).isBlank() ).isBlank()
) media.anime!!.anifyEpisodes!![i]?.title ?: episode.title else episode.title ) media.anime!!.anifyEpisodes!![i]?.title
?: episode.title else episode.title
?: media.anime!!.anifyEpisodes!![i]?.title ?: episode.title ?: media.anime!!.anifyEpisodes!![i]?.title ?: episode.title
episode.thumb = media.anime!!.anifyEpisodes!![i]?.thumb ?: episode.thumb episode.thumb =
media.anime!!.anifyEpisodes!![i]?.thumb ?: episode.thumb
} }
} }
@@ -255,13 +254,16 @@ class AnimeWatchFragment : Fragment() {
} }
if (media.anime?.kitsuEpisodes != null) { if (media.anime?.kitsuEpisodes != null) {
if (media.anime!!.kitsuEpisodes!!.containsKey(i)) { if (media.anime!!.kitsuEpisodes!!.containsKey(i)) {
episode.desc = media.anime!!.kitsuEpisodes!![i]?.desc ?: episode.desc episode.desc =
media.anime!!.kitsuEpisodes!![i]?.desc ?: episode.desc
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely( episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
episode.title ?: "" episode.title ?: ""
).isBlank() ).isBlank()
) media.anime!!.kitsuEpisodes!![i]?.title ?: episode.title else episode.title ) media.anime!!.kitsuEpisodes!![i]?.title
?: media.anime!!.kitsuEpisodes!![i]?.title ?: episode.title ?: episode.title else episode.title
episode.thumb = media.anime!!.kitsuEpisodes!![i]?.thumb ?: episode.thumb ?: media.anime!!.kitsuEpisodes!![i]?.title ?: episode.title
episode.thumb =
media.anime!!.kitsuEpisodes!![i]?.thumb ?: episode.thumb
} }
} }
} }
@@ -400,29 +402,30 @@ class AnimeWatchFragment : Fragment() {
requireContext() requireContext()
.customAlertDialog() .customAlertDialog()
.apply { .apply {
setTitle("Select a Source") setTitle("Select a Source")
singleChoiceItems(names) { which -> singleChoiceItems(names) { which ->
selectedSetting = allSettings[which] selectedSetting = allSettings[which]
itemSelected = true itemSelected = true
requireActivity().runOnUiThread { requireActivity().runOnUiThread {
val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { val fragment =
changeUIVisibility(true) AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) {
loadEpisodes(media.selected!!.sourceIndex, true) changeUIVisibility(true)
loadEpisodes(media.selected!!.sourceIndex, true)
}
parentFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
.replace(R.id.fragmentExtensionsContainer, fragment)
.addToBackStack(null)
.commit()
} }
parentFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
.replace(R.id.fragmentExtensionsContainer, fragment)
.addToBackStack(null)
.commit()
} }
} onDismiss {
onDismiss { if (!itemSelected) {
if (!itemSelected) { changeUIVisibility(true)
changeUIVisibility(true) }
} }
show()
} }
show()
}
} else { } else {
// If there's only one setting, proceed with the fragment transaction // If there's only one setting, proceed with the fragment transaction
requireActivity().runOnUiThread { requireActivity().runOnUiThread {

View File

@@ -1,6 +1,5 @@
package ani.dantotsu.media.anime package ani.dantotsu.media.anime
import android.app.AlertDialog
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -13,7 +12,6 @@ import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.databinding.ItemEpisodeCompactBinding
import ani.dantotsu.databinding.ItemEpisodeGridBinding import ani.dantotsu.databinding.ItemEpisodeGridBinding
import ani.dantotsu.databinding.ItemEpisodeListBinding import ani.dantotsu.databinding.ItemEpisodeListBinding

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@ package ani.dantotsu.media.anime
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.ComponentName import android.content.ComponentName
import android.content.DialogInterface import android.content.DialogInterface
@@ -446,7 +445,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
var subtitleToDownload: Subtitle? = null var subtitleToDownload: Subtitle? = null
requireActivity().customAlertDialog().apply { requireActivity().customAlertDialog().apply {
setTitle(R.string.download_subtitle) setTitle(R.string.download_subtitle)
singleChoiceItems(subtitleNames.toTypedArray()) {which -> singleChoiceItems(subtitleNames.toTypedArray()) { which ->
subtitleToDownload = subtitles[which] subtitleToDownload = subtitles[which]
} }
setPosButton(R.string.download) { setPosButton(R.string.download) {
@@ -483,7 +482,7 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
) )
} else { } else {
val downloadAddonManager: DownloadAddonManager = Injekt.get() val downloadAddonManager: DownloadAddonManager = Injekt.get()
if (!downloadAddonManager.isAvailable()){ if (!downloadAddonManager.isAvailable()) {
val context = currContext() ?: requireContext() val context = currContext() ?: requireContext()
context.customAlertDialog().apply { context.customAlertDialog().apply {
setTitle(R.string.download_addon_not_installed) setTitle(R.string.download_addon_not_installed)
@@ -564,17 +563,21 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
snackString(R.string.no_video_selected) snackString(R.string.no_video_selected)
} }
} }
fun checkAudioTracks() { fun checkAudioTracks() {
val audioTracks = extractor.audioTracks.map { it.lang } val audioTracks = extractor.audioTracks.map { it.lang }
if (audioTracks.isNotEmpty()) { if (audioTracks.isNotEmpty()) {
val audioNamesArray = audioTracks.toTypedArray() val audioNamesArray = audioTracks.toTypedArray()
val checkedItems = BooleanArray(audioNamesArray.size) { false } val checkedItems = BooleanArray(audioNamesArray.size) { false }
currContext.customAlertDialog().apply{ // ToTest currContext.customAlertDialog().apply { // ToTest
setTitle(R.string.download_audio_tracks) setTitle(R.string.download_audio_tracks)
multiChoiceItems(audioNamesArray, checkedItems) { multiChoiceItems(audioNamesArray, checkedItems) {
it.forEachIndexed { index, isChecked -> it.forEachIndexed { index, isChecked ->
val audioPair = Pair(extractor.audioTracks[index].url, extractor.audioTracks[index].lang) val audioPair = Pair(
extractor.audioTracks[index].url,
extractor.audioTracks[index].lang
)
if (isChecked) { if (isChecked) {
selectedAudioTracks.add(audioPair) selectedAudioTracks.add(audioPair)
} else { } else {
@@ -606,7 +609,8 @@ class SelectorDialogFragment : BottomSheetDialogFragment() {
setTitle(R.string.download_subtitle) setTitle(R.string.download_subtitle)
multiChoiceItems(subtitleNamesArray, checkedItems) { multiChoiceItems(subtitleNamesArray, checkedItems) {
it.forEachIndexed { index, isChecked -> it.forEachIndexed { index, isChecked ->
val subtitlePair = Pair(subtitles[index].file.url, subtitles[index].language) val subtitlePair =
Pair(subtitles[index].file.url, subtitles[index].language)
if (isChecked) { if (isChecked) {
selectedSubtitles.add(subtitlePair) selectedSubtitles.add(subtitlePair)
} else { } else {

View File

@@ -63,8 +63,12 @@ class TrackGroupDialogFragment(
override fun onBindViewHolder(holder: StreamViewHolder, position: Int) { override fun onBindViewHolder(holder: StreamViewHolder, position: Int) {
val binding = holder.binding val binding = holder.binding
trackGroups[position].let { trackGroup -> trackGroups[position].let { trackGroup ->
if (overrideTrackNames?.getOrNull(position - (trackGroups.size - (overrideTrackNames?.size?:0))) != null) { if (overrideTrackNames?.getOrNull(
val pair = overrideTrackNames!![position - (trackGroups.size - overrideTrackNames!!.size)] position - (trackGroups.size - (overrideTrackNames?.size ?: 0))
) != null
) {
val pair =
overrideTrackNames!![position - (trackGroups.size - overrideTrackNames!!.size)]
binding.subtitleTitle.text = binding.subtitleTitle.text =
"[${pair.second}] ${pair.first}" "[${pair.second}] ${pair.first}"
} else when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) { } else when (val language = trackGroup.getTrackFormat(0).language?.lowercase()) {

View File

@@ -15,7 +15,6 @@ import ani.dantotsu.databinding.ItemCommentsBinding
import ani.dantotsu.getAppString import ani.dantotsu.getAppString
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.openImage import ani.dantotsu.openImage
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.setAnimation import ani.dantotsu.setAnimation
import ani.dantotsu.snackString import ani.dantotsu.snackString

View File

@@ -2,7 +2,6 @@ package ani.dantotsu.media.comments
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context.INPUT_METHOD_SERVICE import android.content.Context.INPUT_METHOD_SERVICE
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
@@ -12,7 +11,6 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
@@ -33,7 +31,6 @@ import ani.dantotsu.setBaseline
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.toast
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import ani.dantotsu.util.customAlertDialog import ani.dantotsu.util.customAlertDialog
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
@@ -587,19 +584,19 @@ class CommentsFragment : Fragment() {
private fun showCommentRulesDialog() { private fun showCommentRulesDialog() {
activity.customAlertDialog().apply { activity.customAlertDialog().apply {
setTitle("Commenting Rules") setTitle("Commenting Rules")
.setMessage( .setMessage(
"🚨 BREAK ANY RULE = YOU'RE GONE\n\n" + "🚨 BREAK ANY RULE = YOU'RE GONE\n\n" +
"1. NO RACISM, DISCRIMINATION, OR HATE SPEECH\n" + "1. NO RACISM, DISCRIMINATION, OR HATE SPEECH\n" +
"2. NO SPAMMING OR SELF-PROMOTION\n" + "2. NO SPAMMING OR SELF-PROMOTION\n" +
"3. ABSOLUTELY NO NSFW CONTENT\n" + "3. ABSOLUTELY NO NSFW CONTENT\n" +
"4. ENGLISH ONLY NO EXCEPTIONS\n" + "4. ENGLISH ONLY NO EXCEPTIONS\n" +
"5. NO IMPERSONATION, HARASSMENT, OR ABUSE\n" + "5. NO IMPERSONATION, HARASSMENT, OR ABUSE\n" +
"6. NO ILLEGAL CONTENT OR EXTREME DISRESPECT TOWARDS ANY FANDOM\n" + "6. NO ILLEGAL CONTENT OR EXTREME DISRESPECT TOWARDS ANY FANDOM\n" +
"7. DO NOT REQUEST OR SHARE REPOSITORIES/EXTENSIONS\n" + "7. DO NOT REQUEST OR SHARE REPOSITORIES/EXTENSIONS\n" +
"8. SPOILERS ALLOWED ONLY WITH SPOILER TAGS AND A WARNING\n" + "8. SPOILERS ALLOWED ONLY WITH SPOILER TAGS AND A WARNING\n" +
"9. NO SEXUALIZING OR INAPPROPRIATE COMMENTS ABOUT MINOR CHARACTERS\n" + "9. NO SEXUALIZING OR INAPPROPRIATE COMMENTS ABOUT MINOR CHARACTERS\n" +
"10. IF IT'S WRONG, DON'T POST IT!\n\n" "10. IF IT'S WRONG, DON'T POST IT!\n\n"
) )
setPosButton("I Understand") { setPosButton("I Understand") {
PrefManager.setVal(PrefName.FirstComment, false) PrefManager.setVal(PrefName.FirstComment, false)
processComment() processComment()

View File

@@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream import java.io.FileOutputStream
data class ImageData( data class ImageData(
@@ -76,7 +77,7 @@ fun saveImage(
uri?.let { uri?.let {
contentResolver.openOutputStream(it)?.use { os -> contentResolver.openOutputStream(it)?.use { os ->
bitmap.compress(format, quality, os) bitmap.compress(format, quality, os)
} } ?: throw FileNotFoundException("Failed to open output stream for URI: $uri")
} }
} else { } else {
val directory = val directory =
@@ -86,12 +87,20 @@ fun saveImage(
} }
val file = File(directory, filename) val file = File(directory, filename)
// Check if the file already exists
if (file.exists()) {
println("File already exists: ${file.absolutePath}")
return
}
FileOutputStream(file).use { outputStream -> FileOutputStream(file).use { outputStream ->
bitmap.compress(format, quality, outputStream) bitmap.compress(format, quality, outputStream)
} }
} }
} catch (e: FileNotFoundException) {
println("File not found: ${e.message}")
} catch (e: Exception) { } catch (e: Exception) {
// Handle exception here
println("Exception while saving image: ${e.message}") println("Exception while saving image: ${e.message}")
} }
} }

View File

@@ -1,6 +1,5 @@
package ani.dantotsu.media.manga package ani.dantotsu.media.manga
import android.app.AlertDialog
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup

View File

@@ -7,9 +7,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getString import androidx.core.content.ContextCompat.getString
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
@@ -22,8 +24,8 @@ import ani.dantotsu.currActivity
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.databinding.CustomDialogLayoutBinding import ani.dantotsu.databinding.CustomDialogLayoutBinding
import ani.dantotsu.databinding.DialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemMediaSourceBinding
import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.databinding.ItemMediaSourceBinding
import ani.dantotsu.isOnline import ani.dantotsu.isOnline
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
@@ -75,7 +77,8 @@ class MangaReadAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) val bind =
ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(bind) return ViewHolder(bind)
} }
@@ -99,11 +102,11 @@ class MangaReadAdapter(
} }
val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode)
//for removing saved progress //for removing saved progress
binding.sourceTitle.setOnLongClickListener{ binding.sourceTitle.setOnLongClickListener {
fragment.requireContext().customAlertDialog().apply { fragment.requireContext().customAlertDialog().apply {
setTitle(" Delete Progress for all chapters of ${media.nameRomaji}") setTitle(" Delete Progress for all chapters of ${media.nameRomaji}")
setMessage("This will delete all the locally stored progress for chapters") setMessage("This will delete all the locally stored progress for chapters")
setPosButton(R.string.ok){ setPosButton(R.string.ok) {
clearCustomValsForMedia("${media.id}", "_Chapter") clearCustomValsForMedia("${media.id}", "_Chapter")
clearCustomValsForMedia("${media.id}", "_Vol") clearCustomValsForMedia("${media.id}", "_Vol")
snackString("Deleted the progress of Chapters for ${media.nameRomaji}") snackString("Deleted the progress of Chapters for ${media.nameRomaji}")
@@ -264,19 +267,22 @@ class MangaReadAdapter(
} }
// Multi download // Multi download
downloadNo.text = "0" //downloadNo.text = "0"
mediaDownloadTop.setOnClickListener { mediaDownloadTop.setOnClickListener {
// Alert dialog asking for the number of chapters to download
fragment.requireContext().customAlertDialog().apply { fragment.requireContext().customAlertDialog().apply {
setTitle("Multi Chapter Downloader") setTitle("Multi Chapter Downloader")
setMessage("Enter the number of chapters to download") setMessage("Enter the number of chapters to download")
val input = NumberPicker(currContext()) val input = View.inflate(currContext(), R.layout.dialog_layout, null)
input.minValue = 1 val editText = input.findViewById<EditText>(R.id.downloadNo)
input.maxValue = 20
input.value = 1
setCustomView(input) setCustomView(input)
setPosButton(R.string.ok) { setPosButton(R.string.ok) {
downloadNo.text = "${input.value}" val value = editText.text.toString().toIntOrNull()
if (value != null && value > 0) {
downloadNo.setText(value.toString(), TextView.BufferType.EDITABLE)
fragment.multiDownload(value)
} else {
toast("Please enter a valid number")
}
} }
setNegButton(R.string.cancel) setNegButton(R.string.cancel)
show() show()
@@ -286,7 +292,7 @@ class MangaReadAdapter(
fragment.requireContext().customAlertDialog().apply { fragment.requireContext().customAlertDialog().apply {
setTitle(" Delete Progress for all chapters of ${media.nameRomaji}") setTitle(" Delete Progress for all chapters of ${media.nameRomaji}")
setMessage("This will delete all the locally stored progress for chapters") setMessage("This will delete all the locally stored progress for chapters")
setPosButton(R.string.ok){ setPosButton(R.string.ok) {
// Usage // Usage
clearCustomValsForMedia("${media.id}", "_Chapter") clearCustomValsForMedia("${media.id}", "_Chapter")
clearCustomValsForMedia("${media.id}", "_Vol") clearCustomValsForMedia("${media.id}", "_Vol")
@@ -297,7 +303,7 @@ class MangaReadAdapter(
show() show()
} }
} }
resetProgressDef.text = getString(currContext()!!,R.string.clear_stored_chapter) resetProgressDef.text = getString(currContext()!!, R.string.clear_stored_chapter)
// Scanlator // Scanlator
mangaScanlatorContainer.isVisible = options.count() > 1 mangaScanlatorContainer.isVisible = options.count() > 1
@@ -331,12 +337,17 @@ class MangaReadAdapter(
val checkBox = CheckBox(currContext()).apply { val checkBox = CheckBox(currContext()).apply {
text = option text = option
setOnCheckedChangeListener { _, _ -> setOnCheckedChangeListener { _, _ ->
tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) tickAllButton.setImageResource(
getToggleImageResource(
checkboxContainer
)
)
} }
} }
if (media.selected!!.scanlators != null) { if (media.selected!!.scanlators != null) {
checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true checkBox.isChecked =
media.selected!!.scanlators?.contains(option) != true
scanlatorSelectionListener?.onScanlatorsSelected() scanlatorSelectionListener?.onScanlatorsSelected()
} else { } else {
checkBox.isChecked = true checkBox.isChecked = true
@@ -376,8 +387,9 @@ class MangaReadAdapter(
setCustomView(root) setCustomView(root)
setPosButton("OK") { setPosButton("OK") {
if (run) fragment.onIconPressed(style, reversed) if (run) fragment.onIconPressed(style, reversed)
if (downloadNo.text != "0") { val value = downloadNo.text.toString().toIntOrNull()
fragment.multiDownload(downloadNo.text.toString().toInt()) if (value != null && value > 0) {
fragment.multiDownload(value)
} }
if (refresh) fragment.loadChapters(source, true) if (refresh) fragment.loadChapters(source, true)
} }
@@ -488,10 +500,12 @@ class MangaReadAdapter(
} }
} }
val formattedChapters = filteredChapters.map { val formattedChapters = filteredChapters.map {
MediaNameAdapter.findChapterNumber(it.value.number)?.toInt()?.toString() to it.key MediaNameAdapter.findChapterNumber(it.value.number)?.toInt()
?.toString() to it.key
} }
if (formattedChapters.any { it.first == continueNumber }) { if (formattedChapters.any { it.first == continueNumber }) {
var continueEp = media.manga.chapters!![formattedChapters.first { it.first == continueNumber }.second] var continueEp =
media.manga.chapters!![formattedChapters.first { it.first == continueNumber }.second]
binding.sourceContinue.visibility = View.VISIBLE binding.sourceContinue.visibility = View.VISIBLE
handleProgress( handleProgress(
binding.itemMediaProgressCont, binding.itemMediaProgressCont,
@@ -501,9 +515,11 @@ class MangaReadAdapter(
continueEp!!.number continueEp!!.number
) )
if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) { if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) {
val numberPlusOne = formattedChapters.indexOfFirst { it.first?.toIntOrNull() == continueNumber.toInt() + 1 } val numberPlusOne =
formattedChapters.indexOfFirst { it.first?.toIntOrNull() == continueNumber.toInt() + 1 }
if (numberPlusOne != -1) { if (numberPlusOne != -1) {
continueEp = media.manga.chapters!![formattedChapters[numberPlusOne].second] continueEp =
media.manga.chapters!![formattedChapters[numberPlusOne].second]
} }
} }
binding.itemMediaImage.loadImage(media.banner ?: media.cover) binding.itemMediaImage.loadImage(media.banner ?: media.cover)
@@ -530,7 +546,8 @@ class MangaReadAdapter(
binding.sourceProgressBar.visibility = View.GONE binding.sourceProgressBar.visibility = View.GONE
val sourceFound = filteredChapters.isNotEmpty() val sourceFound = filteredChapters.isNotEmpty()
val isDownloadedSource = mangaReadSources[media.selected!!.sourceIndex] is OfflineMangaParser val isDownloadedSource =
mangaReadSources[media.selected!!.sourceIndex] is OfflineMangaParser
if (isDownloadedSource) { if (isDownloadedSource) {
binding.sourceNotFound.text = if (sourceFound) { binding.sourceNotFound.text = if (sourceFound) {
@@ -539,7 +556,8 @@ class MangaReadAdapter(
currActivity()!!.getString(R.string.download_not_found) currActivity()!!.getString(R.string.download_not_found)
} }
} else { } else {
binding.sourceNotFound.text = currActivity()!!.getString(R.string.source_not_found) binding.sourceNotFound.text =
currActivity()!!.getString(R.string.source_not_found)
} }
binding.sourceNotFound.isGone = sourceFound binding.sourceNotFound.isGone = sourceFound

View File

@@ -66,6 +66,7 @@ import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@@ -232,25 +233,35 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
} }
fun multiDownload(n: Int) { fun multiDownload(n: Int) {
// Get last viewed chapter lifecycleScope.launch {
val selected = media.userProgress // Get the last viewed chapter
val chapters = media.manga?.chapters?.values?.toList() val selected = media.userProgress ?: 0
// Filter by selected language val chapters = media.manga?.chapters?.values?.toList()
val progressChapterIndex = (chapters?.indexOfFirst { // Ensure chapters are available in the extensions
MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected if (chapters.isNullOrEmpty() || n < 1) return@launch
} ?: 0) + 1 // Find the index of the last viewed chapter
val progressChapterIndex = (chapters.indexOfFirst {
if (progressChapterIndex < 0 || n < 1 || chapters == null) return MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected
} + 1).coerceAtLeast(0)
// Calculate the end index // Calculate the end value for the range of chapters to download
val endIndex = minOf(progressChapterIndex + n, chapters.size) val endIndex = (progressChapterIndex + n).coerceAtMost(chapters.size)
// Get the list of chapters to download
// Make sure there are enough chapters val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex)
val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex) // Trigger the download for each chapter sequentially
for (chapter in chaptersToDownload) {
try {
for (chapter in chaptersToDownload) { downloadChapterSequentially(chapter)
} catch (e: Exception) {
Toast.makeText(requireContext(), "Failed to download chapter: ${chapter.title}", Toast.LENGTH_SHORT).show()
}
}
Toast.makeText(requireContext(), "All downloads completed!", Toast.LENGTH_SHORT).show()
}
}
private suspend fun downloadChapterSequentially(chapter: MangaChapter) {
withContext(Dispatchers.IO) {
onMangaChapterDownloadClick(chapter) onMangaChapterDownloadClick(chapter)
delay(2000) // A 2-second download
} }
} }
@@ -261,13 +272,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
val chapters = loadedChapters[media.selected!!.sourceIndex] val chapters = loadedChapters[media.selected!!.sourceIndex]
if (chapters != null) { if (chapters != null) {
headerAdapter.options = getScanlators(chapters) headerAdapter.options = getScanlators(chapters)
val filteredChapters = if (model.mangaReadSources?.get(media.selected!!.sourceIndex) is OfflineMangaParser) { val filteredChapters =
chapters if (model.mangaReadSources?.get(media.selected!!.sourceIndex) is OfflineMangaParser) {
} else { chapters
chapters.filterNot { (_, chapter) -> } else {
chapter.scanlator in headerAdapter.hiddenScanlators chapters.filterNot { (_, chapter) ->
chapter.scanlator in headerAdapter.hiddenScanlators
}
} }
}
media.manga?.chapters = filteredChapters.toMutableMap() media.manga?.chapters = filteredChapters.toMutableMap()
@@ -397,17 +409,18 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
selectedSetting = allSettings[which] selectedSetting = allSettings[which]
itemSelected = true itemSelected = true
val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { val fragment =
changeUIVisibility(true) MangaSourcePreferencesFragment().getInstance(selectedSetting.id) {
loadChapters(media.selected!!.sourceIndex, true) changeUIVisibility(true)
} loadChapters(media.selected!!.sourceIndex, true)
}
parentFragmentManager.beginTransaction() parentFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_up, R.anim.slide_down) .setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
.replace(R.id.fragmentExtensionsContainer, fragment) .replace(R.id.fragmentExtensionsContainer, fragment)
.addToBackStack(null) .addToBackStack(null)
.commit() .commit()
} }
onDismiss{ onDismiss {
if (!itemSelected) { if (!itemSelected) {
changeUIVisibility(true) changeUIVisibility(true)
} }
@@ -472,7 +485,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
scanlator = chapter.scanlator ?: "Unknown", scanlator = chapter.scanlator ?: "Unknown",
imageData = images, imageData = images,
sourceMedia = media, sourceMedia = media,
retries = 2, retries = 25,
simultaneousDownloads = 2 simultaneousDownloads = 2
) )
@@ -590,7 +603,9 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
// Find latest chapter for subscription // Find latest chapter for subscription
selected.latest = selected.latest =
media.manga?.chapters?.values?.maxOfOrNull { MediaNameAdapter.findChapterNumber(it.number) ?: 0f } ?: 0f media.manga?.chapters?.values?.maxOfOrNull {
MediaNameAdapter.findChapterNumber(it.number) ?: 0f
} ?: 0f
selected.latest = selected.latest =
media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest

View File

@@ -78,7 +78,8 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
_binding = BottomSheetSelectorBinding.inflate(inflater, container, false) _binding = BottomSheetSelectorBinding.inflate(inflater, container, false)
val window = dialog?.window val window = dialog?.window
window?.statusBarColor = Color.TRANSPARENT window?.statusBarColor = Color.TRANSPARENT
window?.navigationBarColor = requireContext().getThemeColor(com.google.android.material.R.attr.colorSurface) window?.navigationBarColor =
requireContext().getThemeColor(com.google.android.material.R.attr.colorSurface)
return binding.root return binding.root
} }

View File

@@ -2,7 +2,6 @@ package ani.dantotsu.media.manga.mangareader
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
@@ -58,8 +57,6 @@ import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.MediaSingleton import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.media.anime.ExoplayerView
import ani.dantotsu.media.anime.ExoplayerView.Companion
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
@@ -196,8 +193,9 @@ class MangaReaderActivity : AppCompatActivity() {
finish() finish()
return@addCallback return@addCallback
} }
val chapter = (MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!.number) val chapter =
?.minus(1L) ?: 0).toString() (MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!.number)
?.minus(1L) ?: 0).toString()
if (chapter == "0.0" && PrefManager.getVal(PrefName.ChapterZeroReader) if (chapter == "0.0" && PrefManager.getVal(PrefName.ChapterZeroReader)
// Not asking individually or incognito // Not asking individually or incognito
&& !showProgressDialog && !PrefManager.getVal<Boolean>(PrefName.Incognito) && !showProgressDialog && !PrefManager.getVal<Boolean>(PrefName.Incognito)

View File

@@ -55,6 +55,7 @@ class Swipy @JvmOverloads constructor(
else else
VerticalPosition.Top VerticalPosition.Top
} }
!it.canScrollVertically(1) -> VerticalPosition.Bottom !it.canScrollVertically(1) -> VerticalPosition.Bottom
!it.canScrollVertically(-1) -> VerticalPosition.Top !it.canScrollVertically(-1) -> VerticalPosition.Top
else -> VerticalPosition.None else -> VerticalPosition.None
@@ -67,6 +68,7 @@ class Swipy @JvmOverloads constructor(
else else
HorizontalPosition.Left HorizontalPosition.Left
} }
!it.canScrollHorizontally(1) -> HorizontalPosition.Right !it.canScrollHorizontally(1) -> HorizontalPosition.Right
!it.canScrollHorizontally(-1) -> HorizontalPosition.Left !it.canScrollHorizontally(-1) -> HorizontalPosition.Left
else -> HorizontalPosition.None else -> HorizontalPosition.None
@@ -97,12 +99,14 @@ class Swipy @JvmOverloads constructor(
initialDown = if (vertical) ev.getY(0) else ev.getX(0) initialDown = if (vertical) ev.getY(0) else ev.getX(0)
isBeingDragged = false isBeingDragged = false
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
val pointerIndex = ev.findPointerIndex(activePointerId) val pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex >= 0) { if (pointerIndex >= 0) {
startDragging(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)) startDragging(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex))
} }
} }
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev) MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isBeingDragged = false isBeingDragged = false
@@ -122,6 +126,7 @@ class Swipy @JvmOverloads constructor(
activePointerId = ev.getPointerId(0) activePointerId = ev.getPointerId(0)
isBeingDragged = false isBeingDragged = false
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
pointerIndex = ev.findPointerIndex(activePointerId) pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex >= 0) { if (pointerIndex >= 0) {
@@ -130,28 +135,36 @@ class Swipy @JvmOverloads constructor(
if (isBeingDragged) handleDrag(pos) if (isBeingDragged) handleDrag(pos)
} }
} }
MotionEvent.ACTION_POINTER_DOWN -> { MotionEvent.ACTION_POINTER_DOWN -> {
pointerIndex = ev.actionIndex pointerIndex = ev.actionIndex
if (pointerIndex >= 0) activePointerId = ev.getPointerId(pointerIndex) if (pointerIndex >= 0) activePointerId = ev.getPointerId(pointerIndex)
} }
MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev) MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev)
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
resetSwipes() resetSwipes()
pointerIndex = ev.findPointerIndex(activePointerId) pointerIndex = ev.findPointerIndex(activePointerId)
if (pointerIndex >= 0) finishSpinner(if (vertical) ev.getY(pointerIndex) else ev.getX(pointerIndex)) if (pointerIndex >= 0) finishSpinner(
if (vertical) ev.getY(pointerIndex) else ev.getX(
pointerIndex
)
)
activePointerId = INVALID_POINTER activePointerId = INVALID_POINTER
return false return false
} }
MotionEvent.ACTION_CANCEL -> return false MotionEvent.ACTION_CANCEL -> return false
} }
return true return true
} }
private fun startDragging(pos: Float) { private fun startDragging(pos: Float) {
val posDiff = if ((vertical && verticalPos == VerticalPosition.Top) || (!vertical && horizontalPos == HorizontalPosition.Left)) val posDiff =
pos - initialDown if ((vertical && verticalPos == VerticalPosition.Top) || (!vertical && horizontalPos == HorizontalPosition.Left))
else pos - initialDown
initialDown - pos else
initialDown - pos
if (posDiff > touchSlop && !isBeingDragged) { if (posDiff > touchSlop && !isBeingDragged) {
initialMotion = initialDown + touchSlop initialMotion = initialDown + touchSlop
isBeingDragged = true isBeingDragged = true

View File

@@ -14,8 +14,6 @@ import ani.dantotsu.setAnimation
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import ani.dantotsu.util.customAlertDialog import ani.dantotsu.util.customAlertDialog
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
class NovelResponseAdapter( class NovelResponseAdapter(
val fragment: NovelReadFragment, val fragment: NovelReadFragment,
@@ -41,7 +39,8 @@ class NovelResponseAdapter(
setAnimation(fragment.requireContext(), holder.binding.root) setAnimation(fragment.requireContext(), holder.binding.root)
binding.itemMediaImage.loadImage(novel.coverUrl, 400, 0) binding.itemMediaImage.loadImage(novel.coverUrl, 400, 0)
val color =fragment.requireContext().getThemeColor(com.google.android.material.R.attr.colorOnBackground) val color = fragment.requireContext()
.getThemeColor(com.google.android.material.R.attr.colorOnBackground)
binding.itemEpisodeTitle.text = novel.name binding.itemEpisodeTitle.text = novel.name
binding.itemEpisodeFiller.text = binding.itemEpisodeFiller.text =
if (downloadedCheckCallback.downloadedCheck(novel)) { if (downloadedCheckCallback.downloadedCheck(novel)) {

View File

@@ -50,10 +50,10 @@ class ListViewModel : ViewModel() {
search, search,
ignoreCase = true ignoreCase = true
) == true || media.synonyms.any { it.contains(search, ignoreCase = true) } || ) == true || media.synonyms.any { it.contains(search, ignoreCase = true) } ||
media.nameRomaji.contains( media.nameRomaji.contains(
search, search,
ignoreCase = true ignoreCase = true
) )
} as ArrayList<Media> } as ArrayList<Media>
}.toMutableMap() }.toMutableMap()

View File

@@ -5,7 +5,6 @@ import ani.dantotsu.currContext
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaNameAdapter
import ani.dantotsu.media.Selected import ani.dantotsu.media.Selected
import ani.dantotsu.media.emptyMedia
import ani.dantotsu.parsers.AnimeParser import ani.dantotsu.parsers.AnimeParser
import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.AnimeSources
import ani.dantotsu.parsers.BaseParser import ani.dantotsu.parsers.BaseParser
@@ -63,11 +62,11 @@ class SubscriptionHelper {
val show = parser.loadSavedShowResponse(subscribeMedia.id) val show = parser.loadSavedShowResponse(subscribeMedia.id)
?: forceLoadShowResponse(subscribeMedia, selected, parser) ?: forceLoadShowResponse(subscribeMedia, selected, parser)
?: throw Exception( ?: throw Exception(
currContext()?.getString( currContext()?.getString(
R.string.failed_to_load_data, R.string.failed_to_load_data,
subscribeMedia.id subscribeMedia.id
)
) )
)
show.sAnime?.let { show.sAnime?.let {
parser.getLatestEpisode( parser.getLatestEpisode(
show.link, show.extra, show.link, show.extra,
@@ -103,11 +102,11 @@ class SubscriptionHelper {
val show = parser.loadSavedShowResponse(subscribeMedia.id) val show = parser.loadSavedShowResponse(subscribeMedia.id)
?: forceLoadShowResponse(subscribeMedia, selected, parser) ?: forceLoadShowResponse(subscribeMedia, selected, parser)
?: throw Exception( ?: throw Exception(
currContext()?.getString( currContext()?.getString(
R.string.failed_to_load_data, R.string.failed_to_load_data,
subscribeMedia.id subscribeMedia.id
)
) )
)
show.sManga?.let { show.sManga?.let {
parser.getLatestChapter( parser.getLatestChapter(
show.link, show.extra, show.link, show.extra,
@@ -123,7 +122,11 @@ class SubscriptionHelper {
} }
} }
private suspend fun forceLoadShowResponse(subscribeMedia: SubscribeMedia, selected: Selected, parser: BaseParser): ShowResponse? { private suspend fun forceLoadShowResponse(
subscribeMedia: SubscribeMedia,
selected: Selected,
parser: BaseParser
): ShowResponse? {
val tempMedia = Media( val tempMedia = Media(
id = subscribeMedia.id, id = subscribeMedia.id,
name = null, name = null,

View File

@@ -127,8 +127,10 @@ class SubscriptionNotificationTask : Task {
banner = media.banner banner = media.banner
) )
) )
PrefManager.setVal(PrefName.UnreadCommentNotifications, PrefManager.setVal(
PrefManager.getVal<Int>(PrefName.UnreadCommentNotifications) + 1) PrefName.UnreadCommentNotifications,
PrefManager.getVal<Int>(PrefName.UnreadCommentNotifications) + 1
)
val notification = createNotification( val notification = createNotification(
context.applicationContext, context.applicationContext,
media, media,
@@ -240,7 +242,7 @@ class SubscriptionNotificationTask : Task {
if (newStore.size >= 100) { if (newStore.size >= 100) {
newStore.remove(newStore.minByOrNull { it.time }) newStore.remove(newStore.minByOrNull { it.time })
} }
if (newStore.any { it.title == notification.title && it.content == notification.content}) { if (newStore.any { it.title == notification.title && it.content == notification.content }) {
return return
} }

View File

@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import ani.dantotsu.R import ani.dantotsu.R
@@ -37,7 +36,7 @@ class OfflineFragment : Fragment() {
PrefManager.setVal(PrefName.OfflineMode, false) PrefManager.setVal(PrefName.OfflineMode, false)
startMainActivity(requireActivity()) startMainActivity(requireActivity())
} else { } else {
if (isOnline(requireContext()) ) { if (isOnline(requireContext())) {
startMainActivity(requireActivity()) startMainActivity(requireActivity())
} }
} }

View File

@@ -10,7 +10,7 @@ import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.decodeFromJsonElement
object Anify { object Anify {
suspend fun fetchAndParseMetadata(id :Int): Map<String, Episode> { suspend fun fetchAndParseMetadata(id: Int): Map<String, Episode> {
val response = client.get("https://anify.eltik.cc/content-metadata/$id") val response = client.get("https://anify.eltik.cc/content-metadata/$id")
.parsed<JsonArray>().map { .parsed<JsonArray>().map {
Mapper.json.decodeFromJsonElement<AnifyElement>(it) Mapper.json.decodeFromJsonElement<AnifyElement>(it)
@@ -24,15 +24,16 @@ object Anify {
) )
} ?: emptyMap() } ?: emptyMap()
} }
@Serializable @Serializable
data class AnifyElement ( data class AnifyElement(
@SerialName("providerId") @SerialName("providerId")
val providerID: String? = null, val providerID: String? = null,
val data: List<Datum>? = null val data: List<Datum>? = null
) )
@Serializable @Serializable
data class Datum ( data class Datum(
val id: String? = null, val id: String? = null,
val description: String? = null, val description: String? = null,
val hasDub: Boolean? = null, val hasDub: Boolean? = null,

View File

@@ -24,11 +24,11 @@ class CrashActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme() ThemeManager(this).applyTheme()
initActivity(this) initActivity(this)
binding = ActivityCrashBinding.inflate(layoutInflater)
window.setFlags( window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE WindowManager.LayoutParams.FLAG_SECURE
) )
binding = ActivityCrashBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> { binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight

View File

@@ -13,132 +13,132 @@ import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
class Xubtitle class Xubtitle
@JvmOverloads @JvmOverloads
constructor( constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleAttr: Int = 0,
) : AppCompatTextView(context, attrs, defStyleAttr) { ) : AppCompatTextView(context, attrs, defStyleAttr) {
private var outlineThickness: Float = 0f private var outlineThickness: Float = 0f
private var effectColor: Int = currentTextColor private var effectColor: Int = currentTextColor
private var currentEffect: Effect = Effect.NONE private var currentEffect: Effect = Effect.NONE
private val shadowPaint = Paint().apply { isAntiAlias = true } private val shadowPaint = Paint().apply { isAntiAlias = true }
private val outlinePaint = Paint().apply { isAntiAlias = true } private val outlinePaint = Paint().apply { isAntiAlias = true }
private var shineShader: Shader? = null private var shineShader: Shader? = null
enum class Effect { enum class Effect {
NONE, NONE,
OUTLINE, OUTLINE,
SHINE, SHINE,
DROP_SHADOW, DROP_SHADOW,
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
val text = text.toString() val text = text.toString()
val textPaint = val textPaint =
TextPaint(paint).apply { TextPaint(paint).apply {
color = currentTextColor color = currentTextColor
} }
val staticLayout = val staticLayout =
StaticLayout.Builder StaticLayout.Builder
.obtain(text, 0, text.length, textPaint, width) .obtain(text, 0, text.length, textPaint, width)
.setAlignment(Layout.Alignment.ALIGN_CENTER) .setAlignment(Layout.Alignment.ALIGN_CENTER)
.setLineSpacing(0f, 1f) .setLineSpacing(0f, 1f)
.build() .build()
when (currentEffect) { when (currentEffect) {
Effect.OUTLINE -> { Effect.OUTLINE -> {
textPaint.style = Paint.Style.STROKE textPaint.style = Paint.Style.STROKE
textPaint.strokeWidth = outlineThickness textPaint.strokeWidth = outlineThickness
textPaint.color = effectColor textPaint.color = effectColor
staticLayout.draw(canvas) staticLayout.draw(canvas)
textPaint.style = Paint.Style.FILL textPaint.style = Paint.Style.FILL
textPaint.color = currentTextColor textPaint.color = currentTextColor
staticLayout.draw(canvas) staticLayout.draw(canvas)
} }
Effect.DROP_SHADOW -> { Effect.DROP_SHADOW -> {
setLayerType(LAYER_TYPE_SOFTWARE, null) setLayerType(LAYER_TYPE_SOFTWARE, null)
textPaint.setShadowLayer(outlineThickness, 4f, 4f, effectColor) textPaint.setShadowLayer(outlineThickness, 4f, 4f, effectColor)
staticLayout.draw(canvas) staticLayout.draw(canvas)
textPaint.clearShadowLayer() textPaint.clearShadowLayer()
} }
Effect.SHINE -> { Effect.SHINE -> {
val shadowShader = val shadowShader =
LinearGradient( LinearGradient(
0f, 0f,
0f, 0f,
width.toFloat(), width.toFloat(),
height.toFloat(), height.toFloat(),
intArrayOf(Color.WHITE, effectColor, Color.BLACK), intArrayOf(Color.WHITE, effectColor, Color.BLACK),
null, null,
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP,
)
val shadowPaint =
Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
textSize = textPaint.textSize
typeface = textPaint.typeface
shader = shadowShader
}
canvas.drawText(
text,
x + 4f, // Shadow offset
y + 4f,
shadowPaint,
) )
val shader = val shadowPaint =
LinearGradient( Paint().apply {
0f, isAntiAlias = true
0f, style = Paint.Style.FILL
width.toFloat(), textSize = textPaint.textSize
height.toFloat(), typeface = textPaint.typeface
intArrayOf(effectColor, Color.WHITE, Color.WHITE), shader = shadowShader
null, }
Shader.TileMode.CLAMP,
)
textPaint.shader = shader
staticLayout.draw(canvas)
textPaint.shader = null
}
Effect.NONE -> { canvas.drawText(
staticLayout.draw(canvas) text,
} x + 4f, // Shadow offset
y + 4f,
shadowPaint,
)
val shader =
LinearGradient(
0f,
0f,
width.toFloat(),
height.toFloat(),
intArrayOf(effectColor, Color.WHITE, Color.WHITE),
null,
Shader.TileMode.CLAMP,
)
textPaint.shader = shader
staticLayout.draw(canvas)
textPaint.shader = null
}
Effect.NONE -> {
staticLayout.draw(canvas)
} }
} }
fun applyOutline(
color: Int,
outlineThickness: Float,
) {
this.effectColor = color
this.outlineThickness = outlineThickness
currentEffect = Effect.OUTLINE
}
// Too hard for me to figure it out
fun applyShineEffect(color: Int) {
this.effectColor = color
currentEffect = Effect.SHINE
}
fun applyDropShadow(
color: Int,
outlineThickness: Float,
) {
this.effectColor = color
this.outlineThickness = outlineThickness
currentEffect = Effect.DROP_SHADOW
}
} }
fun applyOutline(
color: Int,
outlineThickness: Float,
) {
this.effectColor = color
this.outlineThickness = outlineThickness
currentEffect = Effect.OUTLINE
}
// Too hard for me to figure it out
fun applyShineEffect(color: Int) {
this.effectColor = color
currentEffect = Effect.SHINE
}
fun applyDropShadow(
color: Int,
outlineThickness: Float,
) {
this.effectColor = color
this.outlineThickness = outlineThickness
currentEffect = Effect.DROP_SHADOW
}
}

View File

@@ -26,7 +26,6 @@ object AnimeSources : WatchSources() {
) )
isInitialized = true isInitialized = true
// Update as StateFlow emits new values
fromExtensions.collect { extensions -> fromExtensions.collect { extensions ->
list = sortPinnedAnimeSources( list = sortPinnedAnimeSources(
createParsersFromExtensions(extensions), createParsersFromExtensions(extensions),

View File

@@ -226,8 +226,18 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
?: return emptyList()) ?: return emptyList())
return try { return try {
val videos = source.getVideoList(sEpisode) // TODO(1.6): Remove else block when dropping support for ext lib <1.6
videos.map { videoToVideoServer(it) } if ((source as AnimeHttpSource).javaClass.declaredMethods.any { it.name == "getHosterList" }){
val hosters = source.getHosterList(sEpisode)
val allVideos = hosters.flatMap { hoster ->
val videos = source.getVideoList(hoster)
videos.map { it.copy(videoTitle = "${hoster.hosterName} - ${it.videoTitle}") }
}
allVideos.map { videoToVideoServer(it) }
} else {
val videos = source.getVideoList(sEpisode)
videos.map { videoToVideoServer(it) }
}
} catch (e: Exception) { } catch (e: Exception) {
Logger.log("Exception occurred: ${e.message}") Logger.log("Exception occurred: ${e.message}")
emptyList() emptyList()
@@ -576,7 +586,7 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac
number, number,
format!!, format!!,
FileUrl(videoUrl, headersMap), FileUrl(videoUrl, headersMap),
if (aniVideo.totalContentLength == 0L) null else aniVideo.bytesDownloaded.toDouble() null
) )
} }
@@ -636,7 +646,6 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac
} }
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? var type: SubtitleType?
runBlocking { runBlocking {
type = findSubtitleType(track.url) type = findSubtitleType(track.url)

View File

@@ -1,4 +1,3 @@
package ani.dantotsu.parsers package ani.dantotsu.parsers
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@@ -6,7 +5,6 @@ import ani.dantotsu.FileUrl
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.currContext import ani.dantotsu.currContext
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.okHttpClient
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime

View File

@@ -168,7 +168,7 @@ class ExtensionTestItem(
return return
} }
val serverResultStart = System.currentTimeMillis() val serverResultStart = System.currentTimeMillis()
val serverResult = extension.loadImages("", chapterResult.first().sChapter) val serverResult = extension.loadImages("", chapterResult.first().sChapter)
serverResultData.time = (System.currentTimeMillis() - serverResultStart).toInt() serverResultData.time = (System.currentTimeMillis() - serverResultStart).toInt()
serverResultData.size = serverResult.size serverResultData.size = serverResult.size
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@@ -266,8 +266,10 @@ class ExtensionTestItem(
) )
binding.searchResultText.isVisible = true binding.searchResultText.isVisible = true
if (searchResultData.size == 0) { if (searchResultData.size == 0) {
val text = context.getString(R.string.title_search_test, val text = context.getString(
context.getString(R.string.no_results_found)) R.string.title_search_test,
context.getString(R.string.no_results_found)
)
binding.searchResultText.text = text binding.searchResultText.text = text
binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds( binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_cancel, 0, 0, 0 R.drawable.ic_circle_cancel, 0, 0, 0
@@ -277,8 +279,10 @@ class ExtensionTestItem(
) )
return return
} }
val text = context.getString(R.string.title_search_test, val text = context.getString(
context.getString(R.string.results_found, searchResultData.size.toString())) R.string.title_search_test,
context.getString(R.string.results_found, searchResultData.size.toString())
)
binding.searchResultText.text = text + "\n${searchResultData.time}ms" binding.searchResultText.text = text + "\n${searchResultData.time}ms"
binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds( binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_circle_check, 0, 0, 0 R.drawable.ic_circle_check, 0, 0, 0
@@ -297,13 +301,21 @@ class ExtensionTestItem(
) )
binding.episodeResultText.isVisible = true binding.episodeResultText.isVisible = true
if (episodeResultData.size == 0) { if (episodeResultData.size == 0) {
val text = when(extensionType) { val text = when (extensionType) {
"anime" -> context.getString(R.string.episode_search_test, "anime" -> context.getString(
context.getString(R.string.no_results_found)) R.string.episode_search_test,
"manga" -> context.getString(R.string.chapter_search_test, context.getString(R.string.no_results_found)
context.getString(R.string.no_results_found)) )
else -> context.getString(R.string.book_search_test,
context.getString(R.string.no_results_found)) "manga" -> context.getString(
R.string.chapter_search_test,
context.getString(R.string.no_results_found)
)
else -> context.getString(
R.string.book_search_test,
context.getString(R.string.no_results_found)
)
} }
binding.episodeResultText.text = text binding.episodeResultText.text = text
binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds( binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds(
@@ -314,13 +326,21 @@ class ExtensionTestItem(
) )
return return
} }
val text = when(extensionType) { val text = when (extensionType) {
"anime" -> context.getString(R.string.episode_search_test, "anime" -> context.getString(
context.getString(R.string.results_found, episodeResultData.size.toString())) R.string.episode_search_test,
"manga" -> context.getString(R.string.chapter_search_test, context.getString(R.string.results_found, episodeResultData.size.toString())
context.getString(R.string.results_found, episodeResultData.size.toString())) )
else -> context.getString(R.string.book_search_test,
context.getString(R.string.results_found, episodeResultData.size.toString())) "manga" -> context.getString(
R.string.chapter_search_test,
context.getString(R.string.results_found, episodeResultData.size.toString())
)
else -> context.getString(
R.string.book_search_test,
context.getString(R.string.results_found, episodeResultData.size.toString())
)
} }
binding.episodeResultText.text = text + "\n${episodeResultData.time}ms" binding.episodeResultText.text = text + "\n${episodeResultData.time}ms"
binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds( binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds(
@@ -348,13 +368,21 @@ class ExtensionTestItem(
) )
binding.serverResultText.isVisible = true binding.serverResultText.isVisible = true
if (serverResultData.size == 0) { if (serverResultData.size == 0) {
val text = when(extensionType) { val text = when (extensionType) {
"anime" -> context.getString(R.string.video_search_test, "anime" -> context.getString(
context.getString(R.string.no_results_found)) R.string.video_search_test,
"manga" -> context.getString(R.string.image_search_test, context.getString(R.string.no_results_found)
context.getString(R.string.no_results_found)) )
else -> context.getString(R.string.book_search_test,
context.getString(R.string.no_results_found)) "manga" -> context.getString(
R.string.image_search_test,
context.getString(R.string.no_results_found)
)
else -> context.getString(
R.string.book_search_test,
context.getString(R.string.no_results_found)
)
} }
binding.serverResultText.text = text + "\n${serverResultData.time}ms" binding.serverResultText.text = text + "\n${serverResultData.time}ms"
binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds( binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds(
@@ -365,13 +393,21 @@ class ExtensionTestItem(
) )
return return
} }
val text = when(extensionType) { val text = when (extensionType) {
"anime" -> context.getString(R.string.video_search_test, "anime" -> context.getString(
context.getString(R.string.results_found, serverResultData.size.toString())) R.string.video_search_test,
"manga" -> context.getString(R.string.image_search_test, context.getString(R.string.results_found, serverResultData.size.toString())
context.getString(R.string.results_found, serverResultData.size.toString())) )
else -> context.getString(R.string.book_search_test,
context.getString(R.string.results_found, serverResultData.size.toString())) "manga" -> context.getString(
R.string.image_search_test,
context.getString(R.string.results_found, serverResultData.size.toString())
)
else -> context.getString(
R.string.book_search_test,
context.getString(R.string.results_found, serverResultData.size.toString())
)
} }
binding.serverResultText.text = text binding.serverResultText.text = text
binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds( binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds(

View File

@@ -110,7 +110,7 @@ class ExtensionTestSettingsBottomDialog : BottomSheetDialogFragment() {
} }
private fun setupAdapter() { private fun setupAdapter() {
val namesAndUrls: Map<String,Drawable?> = when (extensionType) { val namesAndUrls: Map<String, Drawable?> = when (extensionType) {
"anime" -> animeExtension.installedExtensionsFlow.value.associate { it.name to it.icon } "anime" -> animeExtension.installedExtensionsFlow.value.associate { it.name to it.icon }
"manga" -> mangaExtensions.installedExtensionsFlow.value.associate { it.name to it.icon } "manga" -> mangaExtensions.installedExtensionsFlow.value.associate { it.name to it.icon }
"novel" -> novelExtensions.installedExtensionsFlow.value.associate { it.name to it.icon } "novel" -> novelExtensions.installedExtensionsFlow.value.associate { it.name to it.icon }

View File

@@ -33,7 +33,7 @@ class OfflineMangaParser : MangaParser() {
directory.listFiles().forEach { directory.listFiles().forEach {
val scanlator = downloadManager.mangaDownloadedTypes.find { items -> val scanlator = downloadManager.mangaDownloadedTypes.find { items ->
items.titleName == mangaLink && items.titleName == mangaLink &&
items.chapterName == it.name items.chapterName == it.name
}?.scanlator ?: "Unknown" }?.scanlator ?: "Unknown"
if (it.isDirectory) { if (it.isDirectory) {
val chapter = MangaChapter( val chapter = MangaChapter(

View File

@@ -73,6 +73,7 @@ class ParserTestActivity : AppCompatActivity() {
} }
} }
} }
"manga" -> { "manga" -> {
ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name -> ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
val extension = val extension =
@@ -89,6 +90,7 @@ class ParserTestActivity : AppCompatActivity() {
} }
} }
} }
"novel" -> { "novel" -> {
ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name -> ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
val extension = val extension =

View File

@@ -120,8 +120,10 @@ class NovelExtensionManager(private val context: Context) {
* @param extension The anime extension to be installed. * @param extension The anime extension to be installed.
*/ */
fun installExtension(extension: NovelExtension.Available): Observable<InstallStep> { fun installExtension(extension: NovelExtension.Available): Observable<InstallStep> {
return installer.downloadAndInstall(api.getNovelApkUrl(extension), extension.pkgName, return installer.downloadAndInstall(
extension.name, MediaType.NOVEL) api.getNovelApkUrl(extension), extension.pkgName,
extension.name, MediaType.NOVEL
)
} }
/** /**

View File

@@ -53,7 +53,8 @@ class ChartBuilder {
scrollPos: Float? = null, scrollPos: Float? = null,
normalize: Boolean = false normalize: Boolean = false
): AAOptions { ): AAOptions {
val primaryColor = context.getThemeColor(com.google.android.material.R.attr.colorPrimary) val primaryColor =
context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
var chartType = passedChartType var chartType = passedChartType
var aaChartType = passedAaChartType var aaChartType = passedAaChartType
var categories = passedCategories var categories = passedCategories
@@ -303,7 +304,8 @@ class ChartBuilder {
} }
private fun setColors(aaOptions: AAOptions, context: Context) { private fun setColors(aaOptions: AAOptions, context: Context) {
val backgroundColor = context.getThemeColor(com.google.android.material.R.attr.colorSurfaceVariant) val backgroundColor =
context.getThemeColor(com.google.android.material.R.attr.colorSurfaceVariant)
val backgroundStyle = AAStyle().color( val backgroundStyle = AAStyle().color(
AAColor.rgbaColor( AAColor.rgbaColor(
Color.red(backgroundColor), Color.red(backgroundColor),
@@ -312,7 +314,8 @@ class ChartBuilder {
1f 1f
) )
) )
val colorOnBackground = context.getThemeColor(com.google.android.material.R.attr.colorOnSurface) val colorOnBackground =
context.getThemeColor(com.google.android.material.R.attr.colorOnSurface)
val onBackgroundStyle = AAStyle().color( val onBackgroundStyle = AAStyle().color(
AAColor.rgbaColor( AAColor.rgbaColor(
Color.red(colorOnBackground), Color.red(colorOnBackground),

View File

@@ -36,10 +36,10 @@ class FollowerItem(
} }
override fun getLayout(): Int { override fun getLayout(): Int {
return if(grid) R.layout.item_follower_grid else R.layout.item_follower return if (grid) R.layout.item_follower_grid else R.layout.item_follower
} }
override fun initializeViewBinding(view: View): ViewBinding { override fun initializeViewBinding(view: View): ViewBinding {
return if(grid) ItemFollowerGridBinding.bind(view) else ItemFollowerBinding.bind(view) return if (grid) ItemFollowerGridBinding.bind(view) else ItemFollowerBinding.bind(view)
} }
} }

View File

@@ -156,17 +156,28 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
openLinkInBrowser(getString(R.string.anilist_link, user.name)) openLinkInBrowser(getString(R.string.anilist_link, user.name))
true true
} }
R.id.action_share_profile -> { R.id.action_share_profile -> {
val shareIntent = Intent(Intent.ACTION_SEND) val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain" shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.anilist_link, user.name)) shareIntent.putExtra(
startActivity(Intent.createChooser(shareIntent, "Share Profile")) Intent.EXTRA_TEXT,
getString(R.string.anilist_link, user.name)
)
startActivity(
Intent.createChooser(
shareIntent,
"Share Profile"
)
)
true true
} }
R.id.action_copy_user_id -> { R.id.action_copy_user_id -> {
copyToClipboard(user.id.toString(), true) copyToClipboard(user.id.toString(), true)
true true
} }
else -> false else -> false
} }
} }

View File

@@ -35,11 +35,11 @@ class UsersAdapter(private val user: MutableList<User>, private val grid: Boolea
parent, parent,
false false
) else ) else
ItemFollowerBinding.inflate( ItemFollowerBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false
) )
) )
} }

View File

@@ -16,8 +16,8 @@ import ani.dantotsu.profile.User
import ani.dantotsu.profile.UsersDialogFragment import ani.dantotsu.profile.UsersDialogFragment
import ani.dantotsu.setAnimation import ani.dantotsu.setAnimation
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
import ani.dantotsu.util.ActivityMarkdownCreator import ani.dantotsu.util.ActivityMarkdownCreator
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.viewbinding.BindableItem import com.xwray.groupie.viewbinding.BindableItem
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -52,14 +52,19 @@ class ActivityItem(
} }
binding.activityRepliesContainer.setOnClickListener { binding.activityRepliesContainer.setOnClickListener {
RepliesBottomDialog.newInstance(activity.id) RepliesBottomDialog.newInstance(activity.id)
.show((context as FragmentActivity).supportFragmentManager, "replies") .show((context as FragmentActivity).supportFragmentManager, "replies")
} }
binding.replyCount.text = activity.replyCount.toString() binding.replyCount.text = activity.replyCount.toString()
binding.activityReplies.setColorFilter(ContextCompat.getColor(binding.root.context, R.color.bg_opp)) binding.activityReplies.setColorFilter(
ContextCompat.getColor(
binding.root.context,
R.color.bg_opp
)
)
binding.activityLikeContainer.setOnLongClickListener { binding.activityLikeContainer.setOnLongClickListener {
UsersDialogFragment().apply { UsersDialogFragment().apply {
userList(userList) userList(userList)
show((context as FragmentActivity).supportFragmentManager, "dialog") show((context as FragmentActivity).supportFragmentManager, "dialog")
} }
true true
} }
@@ -84,7 +89,8 @@ class ActivityItem(
} }
} }
} }
binding.activityDelete.isVisible = activity.userId == Anilist.userid || activity.messenger?.id == Anilist.userid binding.activityDelete.isVisible =
activity.userId == Anilist.userid || activity.messenger?.id == Anilist.userid
binding.activityDelete.setOnClickListener { binding.activityDelete.setOnClickListener {
scope.launch { scope.launch {
val res = Anilist.mutation.deleteActivity(activity.id) val res = Anilist.mutation.deleteActivity(activity.id)
@@ -161,7 +167,8 @@ class ActivityItem(
"MessageActivity" -> { "MessageActivity" -> {
binding.activityBannerContainer.visibility = View.GONE binding.activityBannerContainer.visibility = View.GONE
binding.activityContent.visibility = View.VISIBLE binding.activityContent.visibility = View.VISIBLE
binding.activityPrivate.visibility = if (activity.isPrivate == true) View.VISIBLE else View.GONE binding.activityPrivate.visibility =
if (activity.isPrivate == true) View.VISIBLE else View.GONE
if (!(context as android.app.Activity).isDestroyed) { if (!(context as android.app.Activity).isDestroyed) {
val markwon = buildMarkwon(context, false) val markwon = buildMarkwon(context, false)
markwon.setMarkdown( markwon.setMarkdown(

View File

@@ -14,8 +14,8 @@ import ani.dantotsu.loadImage
import ani.dantotsu.profile.User import ani.dantotsu.profile.User
import ani.dantotsu.profile.UsersDialogFragment import ani.dantotsu.profile.UsersDialogFragment
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
import ani.dantotsu.util.ActivityMarkdownCreator import ani.dantotsu.util.ActivityMarkdownCreator
import ani.dantotsu.util.AniMarkdown.Companion.getBasicAniHTML
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.viewbinding.BindableItem import com.xwray.groupie.viewbinding.BindableItem
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -26,7 +26,7 @@ import kotlinx.coroutines.withContext
class ActivityReplyItem( class ActivityReplyItem(
private val reply: ActivityReply, private val reply: ActivityReply,
private val parentId : Int, private val parentId: Int,
private val fragActivity: FragmentActivity, private val fragActivity: FragmentActivity,
private val parentAdapter: GroupieAdapter, private val parentAdapter: GroupieAdapter,
private val clickCallback: (Int, type: String) -> Unit, private val clickCallback: (Int, type: String) -> Unit,

View File

@@ -1,26 +1,21 @@
package ani.dantotsu.profile.activity package ani.dantotsu.profile.activity
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.ActivityFeedBinding
import ani.dantotsu.databinding.ActivityNotificationBinding import ani.dantotsu.databinding.ActivityNotificationBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.profile.activity.ActivityFragment.Companion.ActivityType
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.profile.activity.ActivityFragment.Companion.ActivityType
import ani.dantotsu.profile.notification.NotificationActivity
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
class FeedActivity : AppCompatActivity() { class FeedActivity : AppCompatActivity() {
@@ -50,9 +45,12 @@ class FeedActivity : AppCompatActivity() {
binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
val getOne = intent.getIntExtra("activityId", -1) val getOne = intent.getIntExtra("activityId", -1)
if (getOne != -1) { navBar.visibility = View.GONE } if (getOne != -1) {
navBar.visibility = View.GONE
}
binding.notificationViewPager.isUserInputEnabled = false binding.notificationViewPager.isUserInputEnabled = false
binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) binding.notificationViewPager.adapter =
ViewPagerAdapter(supportFragmentManager, lifecycle, getOne)
binding.notificationViewPager.setOffscreenPageLimit(4) binding.notificationViewPager.setOffscreenPageLimit(4)
binding.notificationViewPager.setCurrentItem(selected, false) binding.notificationViewPager.setCurrentItem(selected, false)
navBar.selectTabAt(selected) navBar.selectTabAt(selected)
@@ -83,7 +81,11 @@ class FeedActivity : AppCompatActivity() {
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return when (position) { return when (position) {
0 -> ActivityFragment.newInstance(if (activityId != -1) ActivityType.ONE else ActivityType.USER, activityId = activityId) 0 -> ActivityFragment.newInstance(
if (activityId != -1) ActivityType.ONE else ActivityType.USER,
activityId = activityId
)
else -> ActivityFragment.newInstance(ActivityType.GLOBAL) else -> ActivityFragment.newInstance(ActivityType.GLOBAL)
} }
} }

View File

@@ -9,17 +9,20 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.databinding.ActivityNotificationBinding import ani.dantotsu.databinding.ActivityNotificationBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity
import ani.dantotsu.navBarHeight import ani.dantotsu.navBarHeight
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.MEDIA
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.ONE
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.USER
import ani.dantotsu.profile.notification.NotificationFragment.Companion.newInstance
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.*
import ani.dantotsu.profile.notification.NotificationFragment.Companion.newInstance
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
class NotificationActivity : AppCompatActivity() { class NotificationActivity : AppCompatActivity() {
@@ -58,7 +61,8 @@ class NotificationActivity : AppCompatActivity() {
val getOne = intent.getIntExtra("activityId", -1) val getOne = intent.getIntExtra("activityId", -1)
if (getOne != -1) navBar.isVisible = false if (getOne != -1) navBar.isVisible = false
binding.notificationViewPager.isUserInputEnabled = false binding.notificationViewPager.isUserInputEnabled = false
binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne, CommentsEnabled) binding.notificationViewPager.adapter =
ViewPagerAdapter(supportFragmentManager, lifecycle, getOne, CommentsEnabled)
binding.notificationViewPager.setCurrentItem(selected, false) binding.notificationViewPager.setCurrentItem(selected, false)
navBar.selectTabAt(selected) navBar.selectTabAt(selected)
navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {

View File

@@ -60,7 +60,8 @@ class NotificationItem(
PrefName.SubscriptionNotificationStore, PrefName.SubscriptionNotificationStore,
null null
) ?: listOf() ) ?: listOf()
val newList = list.filter { (it.time / 1000L).toInt() != notification.createdAt} val newList =
list.filter { (it.time / 1000L).toInt() != notification.createdAt }
PrefManager.setVal(PrefName.SubscriptionNotificationStore, newList) PrefManager.setVal(PrefName.SubscriptionNotificationStore, newList)
parentAdapter.remove(this@NotificationItem) parentAdapter.remove(this@NotificationItem)
} }

View File

@@ -33,7 +33,7 @@ class RepoItem(
val url: String, val url: String,
private val mediaType: MediaType, private val mediaType: MediaType,
val onRemove: (String, MediaType) -> Unit val onRemove: (String, MediaType) -> Unit
) :BindableItem<ItemRepoBinding>() { ) : BindableItem<ItemRepoBinding>() {
override fun getLayout() = R.layout.item_repo override fun getLayout() = R.layout.item_repo
override fun bind(viewBinding: ItemRepoBinding, position: Int) { override fun bind(viewBinding: ItemRepoBinding, position: Int) {
@@ -88,7 +88,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
) )
adapter.addAll(repositories.map { RepoItem(it, mediaType, ::onRepositoryRemoved) }) adapter.addAll(repositories.map { RepoItem(it, mediaType, ::onRepositoryRemoved) })
binding.repositoryInput.hint = when(mediaType) { binding.repositoryInput.hint = when (mediaType) {
MediaType.ANIME -> getString(R.string.anime_add_repository) MediaType.ANIME -> getString(R.string.anime_add_repository)
MediaType.MANGA -> getString(R.string.manga_add_repository) MediaType.MANGA -> getString(R.string.manga_add_repository)
MediaType.NOVEL -> getString(R.string.novel_add_repository) MediaType.NOVEL -> getString(R.string.novel_add_repository)
@@ -110,7 +110,8 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
binding.repositoryInput.setOnEditorActionListener { textView, action, keyEvent -> binding.repositoryInput.setOnEditorActionListener { textView, action, keyEvent ->
if (action == EditorInfo.IME_ACTION_DONE || if (action == EditorInfo.IME_ACTION_DONE ||
(keyEvent?.action == KeyEvent.ACTION_UP && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)) { (keyEvent?.action == KeyEvent.ACTION_UP && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)
) {
val url = textView.text.toString() val url = textView.text.toString()
if (url.isNotBlank()) { if (url.isNotBlank()) {
val error = isValidUrl(url) val error = isValidUrl(url)
@@ -215,6 +216,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
Injekt.get<AnimeExtensionManager>().findAvailableExtensions() Injekt.get<AnimeExtensionManager>().findAvailableExtensions()
} }
} }
MediaType.MANGA -> { MediaType.MANGA -> {
val manga = val manga =
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos) PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
@@ -224,6 +226,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
Injekt.get<MangaExtensionManager>().findAvailableExtensions() Injekt.get<MangaExtensionManager>().findAvailableExtensions()
} }
} }
MediaType.NOVEL -> { MediaType.NOVEL -> {
val novel = val novel =
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos) PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)
@@ -247,6 +250,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
Injekt.get<AnimeExtensionManager>().findAvailableExtensions() Injekt.get<AnimeExtensionManager>().findAvailableExtensions()
} }
} }
MediaType.MANGA -> { MediaType.MANGA -> {
val manga = val manga =
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos) PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos)
@@ -256,6 +260,7 @@ class AddRepositoryBottomSheet : BottomSheetDialogFragment() {
Injekt.get<MangaExtensionManager>().findAvailableExtensions() Injekt.get<MangaExtensionManager>().findAvailableExtensions()
} }
} }
MediaType.NOVEL -> { MediaType.NOVEL -> {
val novel = val novel =
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos) PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos)

View File

@@ -56,7 +56,8 @@ class AnilistSettingsActivity : AppCompatActivity() {
} }
val currentTitleLang = Anilist.titleLanguage val currentTitleLang = Anilist.titleLanguage
val titleFormat = UserTitleLanguage.entries.firstOrNull { it.name == currentTitleLang } ?: UserTitleLanguage.ENGLISH val titleFormat = UserTitleLanguage.entries.firstOrNull { it.name == currentTitleLang }
?: UserTitleLanguage.ENGLISH
settingsAnilistTitleLanguage.setText(titleLang[titleFormat.ordinal]) settingsAnilistTitleLanguage.setText(titleLang[titleFormat.ordinal])
settingsAnilistTitleLanguage.setAdapter( settingsAnilistTitleLanguage.setAdapter(
@@ -78,7 +79,9 @@ class AnilistSettingsActivity : AppCompatActivity() {
} }
val currentStaffNameLang = Anilist.staffNameLanguage val currentStaffNameLang = Anilist.staffNameLanguage
val staffNameFormat = UserStaffNameLanguage.entries.firstOrNull { it.name == currentStaffNameLang } ?: UserStaffNameLanguage.ROMAJI_WESTERN val staffNameFormat =
UserStaffNameLanguage.entries.firstOrNull { it.name == currentStaffNameLang }
?: UserStaffNameLanguage.ROMAJI_WESTERN
settingsAnilistStaffLanguage.setText(staffNameLang[staffNameFormat.ordinal]) settingsAnilistStaffLanguage.setText(staffNameLang[staffNameFormat.ordinal])
settingsAnilistStaffLanguage.setAdapter( settingsAnilistStaffLanguage.setAdapter(
@@ -99,8 +102,9 @@ class AnilistSettingsActivity : AppCompatActivity() {
settingsAnilistStaffLanguage.clearFocus() settingsAnilistStaffLanguage.clearFocus()
} }
val currentMergeTimeDisplay = activityMergeTimeMap.entries.firstOrNull { it.value == Anilist.activityMergeTime }?.key val currentMergeTimeDisplay =
?: "${Anilist.activityMergeTime} mins" activityMergeTimeMap.entries.firstOrNull { it.value == Anilist.activityMergeTime }?.key
?: "${Anilist.activityMergeTime} mins"
settingsAnilistActivityMergeTime.setText(currentMergeTimeDisplay) settingsAnilistActivityMergeTime.setText(currentMergeTimeDisplay)
settingsAnilistActivityMergeTime.setAdapter( settingsAnilistActivityMergeTime.setAdapter(
ArrayAdapter(context, R.layout.item_dropdown, activityMergeTimeMap.keys.toList()) ArrayAdapter(context, R.layout.item_dropdown, activityMergeTimeMap.keys.toList())
@@ -117,7 +121,8 @@ class AnilistSettingsActivity : AppCompatActivity() {
} }
val currentScoreFormat = Anilist.scoreFormat val currentScoreFormat = Anilist.scoreFormat
val scoreFormat = ScoreFormat.entries.firstOrNull{ it.name == currentScoreFormat } ?: ScoreFormat.POINT_100 val scoreFormat = ScoreFormat.entries.firstOrNull { it.name == currentScoreFormat }
?: ScoreFormat.POINT_100
settingsAnilistScoreFormat.setText(scoreFormats[scoreFormat.ordinal]) settingsAnilistScoreFormat.setText(scoreFormats[scoreFormat.ordinal])
settingsAnilistScoreFormat.setAdapter( settingsAnilistScoreFormat.setAdapter(
ArrayAdapter(context, R.layout.item_dropdown, scoreFormats) ArrayAdapter(context, R.layout.item_dropdown, scoreFormats)
@@ -139,7 +144,8 @@ class AnilistSettingsActivity : AppCompatActivity() {
settingsAnilistScoreFormat.clearFocus() settingsAnilistScoreFormat.clearFocus()
} }
val currentRowOrder = rowOrderMap.entries.firstOrNull { it.value == Anilist.rowOrder }?.key ?: "Score" val currentRowOrder =
rowOrderMap.entries.firstOrNull { it.value == Anilist.rowOrder }?.key ?: "Score"
settingsAnilistRowOrder.setText(currentRowOrder) settingsAnilistRowOrder.setText(currentRowOrder)
settingsAnilistRowOrder.setAdapter( settingsAnilistRowOrder.setAdapter(
ArrayAdapter(context, R.layout.item_dropdown, rowOrderMap.keys.toList()) ArrayAdapter(context, R.layout.item_dropdown, rowOrderMap.keys.toList())
@@ -155,7 +161,8 @@ class AnilistSettingsActivity : AppCompatActivity() {
settingsAnilistRowOrder.clearFocus() settingsAnilistRowOrder.clearFocus()
} }
val containers = listOf(binding.animeCustomListsContainer, binding.mangaCustomListsContainer) val containers =
listOf(binding.animeCustomListsContainer, binding.mangaCustomListsContainer)
val customLists = listOf(Anilist.animeCustomLists, Anilist.mangaCustomLists) val customLists = listOf(Anilist.animeCustomLists, Anilist.mangaCustomLists)
val buttons = listOf(binding.addAnimeListButton, binding.addMangaListButton) val buttons = listOf(binding.addAnimeListButton, binding.addMangaListButton)
@@ -175,7 +182,8 @@ class AnilistSettingsActivity : AppCompatActivity() {
saveCustomLists() saveCustomLists()
} }
val currentTimezone = Anilist.timezone?.let { Anilist.getDisplayTimezone(it, context) } ?: context.getString(R.string.selected_no_time_zone) val currentTimezone = Anilist.timezone?.let { Anilist.getDisplayTimezone(it, context) }
?: context.getString(R.string.selected_no_time_zone)
settingsAnilistTimezone.setText(currentTimezone) settingsAnilistTimezone.setText(currentTimezone)
settingsAnilistTimezone.setAdapter( settingsAnilistTimezone.setAdapter(
ArrayAdapter(context, R.layout.item_dropdown, Anilist.timeZone) ArrayAdapter(context, R.layout.item_dropdown, Anilist.timeZone)

View File

@@ -169,8 +169,12 @@ class ExtensionsActivity : AppCompatActivity() {
customAlertDialog().apply { customAlertDialog().apply {
setTitle("Language") setTitle("Language")
singleChoiceItems(languageOptions, index) { selected -> singleChoiceItems(languageOptions, index) { selected ->
PrefManager.setVal(PrefName.LangSort, LanguageMapper.Companion.Language.entries[selected].code) PrefManager.setVal(
val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}") PrefName.LangSort,
LanguageMapper.Companion.Language.entries[selected].code
)
val currentFragment =
supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}")
if (currentFragment is SearchQueryHandler) { if (currentFragment is SearchQueryHandler) {
currentFragment.notifyDataChanged() currentFragment.notifyDataChanged()
} }

View File

@@ -70,7 +70,7 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler {
if (allSettings.isNotEmpty()) { if (allSettings.isNotEmpty()) {
var selectedSetting = allSettings[0] var selectedSetting = allSettings[0]
if (allSettings.size > 1) { if (allSettings.size > 1) {
val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) } val names = allSettings.map { getLanguageName(it.lang) }
.toTypedArray() .toTypedArray()
var selectedIndex = 0 var selectedIndex = 0
requireContext().customAlertDialog().apply { requireContext().customAlertDialog().apply {

View File

@@ -290,7 +290,8 @@ class PlayerSettingsActivity :
PrefManager.setVal(PrefName.UseInternalCast, isChecked) PrefManager.setVal(PrefName.UseInternalCast, isChecked)
} }
binding.playerSettingsAdditionalCodec.isChecked = PrefManager.getVal(PrefName.UseAdditionalCodec) binding.playerSettingsAdditionalCodec.isChecked =
PrefManager.getVal(PrefName.UseAdditionalCodec)
binding.playerSettingsAdditionalCodec.setOnCheckedChangeListener { _, isChecked -> binding.playerSettingsAdditionalCodec.setOnCheckedChangeListener { _, isChecked ->
PrefManager.setVal(PrefName.UseAdditionalCodec, isChecked) PrefManager.setVal(PrefName.UseAdditionalCodec, isChecked)
} }

View File

@@ -6,9 +6,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.BottomSheetDialogFragment
import ani.dantotsu.databinding.BottomSheetProxyBinding import ani.dantotsu.databinding.BottomSheetProxyBinding
import ani.dantotsu.restartApp
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.restartApp
class ProxyDialogFragment : BottomSheetDialogFragment() { class ProxyDialogFragment : BottomSheetDialogFragment() {
private var _binding: BottomSheetProxyBinding? = null private var _binding: BottomSheetProxyBinding? = null
@@ -16,8 +16,10 @@ class ProxyDialogFragment : BottomSheetDialogFragment() {
private var proxyHost: String? = PrefManager.getVal<String>(PrefName.Socks5ProxyHost).orEmpty() private var proxyHost: String? = PrefManager.getVal<String>(PrefName.Socks5ProxyHost).orEmpty()
private var proxyPort: String? = PrefManager.getVal<String>(PrefName.Socks5ProxyPort).orEmpty() private var proxyPort: String? = PrefManager.getVal<String>(PrefName.Socks5ProxyPort).orEmpty()
private var proxyUsername: String? = PrefManager.getVal<String>(PrefName.Socks5ProxyUsername).orEmpty() private var proxyUsername: String? =
private var proxyPassword: String? = PrefManager.getVal<String>(PrefName.Socks5ProxyPassword).orEmpty() PrefManager.getVal<String>(PrefName.Socks5ProxyUsername).orEmpty()
private var proxyPassword: String? =
PrefManager.getVal<String>(PrefName.Socks5ProxyPassword).orEmpty()
private var authEnabled: Boolean = PrefManager.getVal<Boolean>(PrefName.ProxyAuthEnabled) private var authEnabled: Boolean = PrefManager.getVal<Boolean>(PrefName.ProxyAuthEnabled)
private val proxyEnabled: Boolean = PrefManager.getVal<Boolean>(PrefName.EnableSocks5Proxy) private val proxyEnabled: Boolean = PrefManager.getVal<Boolean>(PrefName.EnableSocks5Proxy)

View File

@@ -142,8 +142,10 @@ class SettingsAboutActivity : AppCompatActivity() {
icon = R.drawable.ic_incognito_24, icon = R.drawable.ic_incognito_24,
onClick = { onClick = {
val text = TextView(context) val text = TextView(context)
val pPLink = "https://raw.githubusercontent.com/rebelonion/Dantotsu/main/privacy_policy.md" val pPLink =
val backup = "https://gcore.jsdelivr.net/gh/rebelonion/dantotsu/privacy_policy.md" "https://raw.githubusercontent.com/rebelonion/Dantotsu/main/privacy_policy.md"
val backup =
"https://gcore.jsdelivr.net/gh/rebelonion/dantotsu/privacy_policy.md"
text.text = getString(R.string.loading) text.text = getString(R.string.loading)
val markWon = try { val markWon = try {
buildMarkwon(this@SettingsAboutActivity, false) buildMarkwon(this@SettingsAboutActivity, false)
@@ -177,7 +179,7 @@ class SettingsAboutActivity : AppCompatActivity() {
} }
), ),
) )
) )
binding.settingsRecyclerView.layoutManager = binding.settingsRecyclerView.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)

View File

@@ -211,16 +211,16 @@ class SettingsAccountActivity : AppCompatActivity() {
binding.settingsRecyclerView.adapter = SettingsAdapter( binding.settingsRecyclerView.adapter = SettingsAdapter(
arrayListOf( arrayListOf(
Settings( Settings(
type = 2, type = 2,
name = getString(R.string.enable_rpc), name = getString(R.string.enable_rpc),
desc = getString(R.string.enable_rpc_desc), desc = getString(R.string.enable_rpc_desc),
icon = R.drawable.interests_24, icon = R.drawable.interests_24,
isChecked = PrefManager.getVal(PrefName.rpcEnabled), isChecked = PrefManager.getVal(PrefName.rpcEnabled),
switch = { isChecked, _ -> switch = { isChecked, _ ->
PrefManager.setVal(PrefName.rpcEnabled, isChecked) PrefManager.setVal(PrefName.rpcEnabled, isChecked)
}, },
isVisible = Discord.token != null isVisible = Discord.token != null
), ),
Settings( Settings(
type = 1, type = 1,
name = getString(R.string.anilist_settings), name = getString(R.string.anilist_settings),
@@ -235,30 +235,31 @@ class SettingsAccountActivity : AppCompatActivity() {
isActivity = true isActivity = true
), ),
Settings( Settings(
type = 2, type = 2,
name = getString(R.string.comments_button), name = getString(R.string.comments_button),
desc = getString(R.string.comments_button_desc), desc = getString(R.string.comments_button_desc),
icon = R.drawable.ic_round_comment_24, icon = R.drawable.ic_round_comment_24,
isChecked = PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1, isChecked = PrefManager.getVal<Int>(PrefName.CommentsEnabled) == 1,
switch = { isChecked, _ -> switch = { isChecked, _ ->
PrefManager.setVal(PrefName.CommentsEnabled, if (isChecked) 1 else 2 ) PrefManager.setVal(PrefName.CommentsEnabled, if (isChecked) 1 else 2)
reload() reload()
}, },
isVisible = Anilist.token != null isVisible = Anilist.token != null
), ),
) )
) )
binding.settingsRecyclerView.layoutManager = binding.settingsRecyclerView.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
} }
fun reload() { fun reload() {
snackString(getString(R.string.restart_app_extra)) snackString(getString(R.string.restart_app_extra))
//snackString(R.string.restart_app_extra) //snackString(R.string.restart_app_extra)
//?.setDuration(Snackbar.LENGTH_LONG) //?.setDuration(Snackbar.LENGTH_LONG)
//?.setAction(R.string.do_it) { //?.setAction(R.string.do_it) {
//startMainActivity(this@SettingsAccountActivity) //startMainActivity(this@SettingsAccountActivity)
//} Disabled for now. Doesn't update the ADDRESS even after this //} Disabled for now. Doesn't update the ADDRESS even after this
} }
} }

View File

@@ -11,8 +11,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.addons.AddonDownloader import ani.dantotsu.addons.AddonDownloader
import ani.dantotsu.addons.download.DownloadAddonManager import ani.dantotsu.addons.download.DownloadAddonManager
import ani.dantotsu.addons.torrent.TorrentServerService
import ani.dantotsu.addons.torrent.TorrentAddonManager import ani.dantotsu.addons.torrent.TorrentAddonManager
import ani.dantotsu.addons.torrent.TorrentServerService
import ani.dantotsu.databinding.ActivitySettingsAddonsBinding import ani.dantotsu.databinding.ActivitySettingsAddonsBinding
import ani.dantotsu.databinding.ItemSettingsBinding import ani.dantotsu.databinding.ItemSettingsBinding
import ani.dantotsu.initActivity import ani.dantotsu.initActivity

View File

@@ -19,6 +19,7 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.util.customAlertDialog
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@@ -57,23 +58,16 @@ class SettingsAnimeActivity : AppCompatActivity() {
desc = getString(R.string.purge_anime_downloads_desc), desc = getString(R.string.purge_anime_downloads_desc),
icon = R.drawable.ic_round_delete_24, icon = R.drawable.ic_round_delete_24,
onClick = { onClick = {
val dialog = AlertDialog.Builder(context, R.style.MyPopup) context.customAlertDialog().apply {
.setTitle(R.string.purge_anime_downloads) setTitle(R.string.purge_anime_downloads)
.setMessage( setMessage(R.string.purge_confirm, getString(R.string.anime))
getString( setPosButton(R.string.yes, onClick = {
R.string.purge_confirm,
getString(R.string.anime)
)
)
.setPositiveButton(R.string.yes) { dialog, _ ->
val downloadsManager = Injekt.get<DownloadsManager>() val downloadsManager = Injekt.get<DownloadsManager>()
downloadsManager.purgeDownloads(MediaType.ANIME) downloadsManager.purgeDownloads(MediaType.ANIME)
dialog.dismiss() })
}.setNegativeButton(R.string.no) { dialog, _ -> setNegButton(R.string.no)
dialog.dismiss() show()
}.create() }
dialog.window?.setDimAmount(0.8f)
dialog.show()
} }
), ),

View File

@@ -8,8 +8,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.EditText
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
@@ -47,7 +45,6 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.UUID import java.util.UUID
class SettingsCommonActivity : AppCompatActivity() { class SettingsCommonActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsCommonBinding private lateinit var binding: ActivitySettingsCommonBinding
private lateinit var launcher: LauncherWrapper private lateinit var launcher: LauncherWrapper
@@ -64,23 +61,27 @@ class SettingsCommonActivity : AppCompatActivity() {
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) { if (uri != null) {
try { try {
val jsonString = contentResolver.openInputStream(uri)?.readBytes() val jsonString =
?: throw Exception("Error reading file") contentResolver.openInputStream(uri)?.readBytes()
?: throw Exception("Error reading file")
val name = DocumentFile.fromSingleUri(this, uri)?.name ?: "settings" val name = DocumentFile.fromSingleUri(this, uri)?.name ?: "settings"
//.sani is encrypted, .ani is not // .sani is encrypted, .ani is not
if (name.endsWith(".sani")) { if (name.endsWith(".sani")) {
passwordAlertDialog(false) { password -> passwordAlertDialog(false) { password ->
if (password != null) { if (password != null) {
val salt = jsonString.copyOfRange(0, 16) val salt = jsonString.copyOfRange(0, 16)
val encrypted = jsonString.copyOfRange(16, jsonString.size) val encrypted = jsonString.copyOfRange(16, jsonString.size)
val decryptedJson = try { val decryptedJson =
PreferenceKeystore.decryptWithPassword( try {
password, encrypted, salt PreferenceKeystore.decryptWithPassword(
) password,
} catch (e: Exception) { encrypted,
toast(getString(R.string.incorrect_password)) salt,
return@passwordAlertDialog )
} } catch (e: Exception) {
toast(getString(R.string.incorrect_password))
return@passwordAlertDialog
}
if (PreferencePackager.unpack(decryptedJson)) restartApp() if (PreferencePackager.unpack(decryptedJson)) restartApp()
} else { } else {
toast(getString(R.string.password_cannot_be_empty)) toast(getString(R.string.password_cannot_be_empty))
@@ -102,7 +103,6 @@ class SettingsCommonActivity : AppCompatActivity() {
launcher = LauncherWrapper(this, contract) launcher = LauncherWrapper(this, contract)
binding.apply { binding.apply {
settingsCommonLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> { settingsCommonLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
topMargin = statusBarHeight topMargin = statusBarHeight
bottomMargin = navBarHeight bottomMargin = navBarHeight
@@ -110,27 +110,30 @@ class SettingsCommonActivity : AppCompatActivity() {
commonSettingsBack.setOnClickListener { commonSettingsBack.setOnClickListener {
onBackPressedDispatcher.onBackPressed() onBackPressedDispatcher.onBackPressed()
} }
val exDns = listOf( val exDns =
"None", listOf(
"Cloudflare", "None",
"Google", "Cloudflare",
"AdGuard", "Google",
"Quad9", "AdGuard",
"AliDNS", "Quad9",
"DNSPod", "AliDNS",
"360", "DNSPod",
"Quad101", "360",
"Mullvad", "Quad101",
"Controld", "Mullvad",
"Njalla", "Controld",
"Shecan", "Njalla",
"Libre" "Shecan",
) "Libre",
)
settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)]) settingsExtensionDns.setText(exDns[PrefManager.getVal(PrefName.DohProvider)])
settingsExtensionDns.setAdapter( settingsExtensionDns.setAdapter(
ArrayAdapter( ArrayAdapter(
context, R.layout.item_dropdown, exDns context,
) R.layout.item_dropdown,
exDns,
),
) )
settingsExtensionDns.setOnItemClickListener { _, _, i, _ -> settingsExtensionDns.setOnItemClickListener { _, _, i, _ ->
PrefManager.setVal(PrefName.DohProvider, i) PrefManager.setVal(PrefName.DohProvider, i)
@@ -138,283 +141,281 @@ class SettingsCommonActivity : AppCompatActivity() {
restartApp() restartApp()
} }
settingsRecyclerView.adapter = SettingsAdapter( settingsRecyclerView.adapter =
arrayListOf( SettingsAdapter(
Settings( arrayListOf(
type = 1, Settings(
name = getString(R.string.ui_settings), type = 1,
desc = getString(R.string.ui_settings_desc), name = getString(R.string.ui_settings),
icon = R.drawable.ic_round_auto_awesome_24, desc = getString(R.string.ui_settings_desc),
onClick = { icon = R.drawable.ic_round_auto_awesome_24,
startActivity( onClick = {
Intent( startActivity(
context, Intent(
UserInterfaceSettingsActivity::class.java context,
UserInterfaceSettingsActivity::class.java,
),
) )
) },
}, isActivity = true,
isActivity = true ),
), Settings(
Settings( type = 1,
type = 2, name = getString(R.string.download_manager_select),
name = getString(R.string.open_animanga_directly), desc = getString(R.string.download_manager_select_desc),
desc = getString(R.string.open_animanga_directly_info), icon = R.drawable.ic_download_24,
icon = R.drawable.ic_round_search_24, onClick = {
isChecked = PrefManager.getVal(PrefName.AniMangaSearchDirect), val managers = arrayOf("Default", "1DM", "ADM")
switch = { isChecked, _ -> customAlertDialog().apply {
PrefManager.setVal(PrefName.AniMangaSearchDirect, isChecked) setTitle(getString(R.string.download_manager))
} singleChoiceItems(
), managers,
Settings( PrefManager.getVal(PrefName.DownloadManager),
type = 1, ) { count ->
name = getString(R.string.download_manager_select), PrefManager.setVal(PrefName.DownloadManager, count)
desc = getString(R.string.download_manager_select_desc),
icon = R.drawable.ic_download_24,
onClick = {
val managers = arrayOf("Default", "1DM", "ADM")
customAlertDialog().apply {
setTitle(getString(R.string.download_manager))
singleChoiceItems(
managers,
PrefManager.getVal(PrefName.DownloadManager)
) { count ->
PrefManager.setVal(PrefName.DownloadManager, count)
}
show()
}
}
),
Settings(
type = 1,
name = getString(R.string.app_lock),
desc = getString(R.string.app_lock_desc),
icon = R.drawable.ic_round_lock_open_24,
onClick = {
customAlertDialog().apply {
val view = DialogSetPasswordBinding.inflate(layoutInflater)
setTitle(R.string.app_lock)
setCustomView(view.root)
setPosButton(R.string.ok) {
if (view.forgotPasswordCheckbox.isChecked) {
PrefManager.setVal(PrefName.OverridePassword, true)
} }
val password = view.passwordInput.text.toString() show()
val confirmPassword = view.confirmPasswordInput.text.toString() }
if (password == confirmPassword && password.isNotEmpty()) { },
PrefManager.setVal(PrefName.AppPassword, password) ),
if (view.biometricCheckbox.isChecked) { Settings(
val canBiometricPrompt = type = 1,
BiometricManager.from(applicationContext) name = getString(R.string.app_lock),
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS desc = getString(R.string.app_lock_desc),
icon = R.drawable.ic_round_lock_open_24,
if (canBiometricPrompt) { onClick = {
val biometricPrompt = customAlertDialog().apply {
BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> val view = DialogSetPasswordBinding.inflate(layoutInflater)
val token = UUID.randomUUID().toString() setTitle(R.string.app_lock)
PrefManager.setVal( setCustomView(view.root)
PrefName.BiometricToken, setPosButton(R.string.ok) {
token if (view.forgotPasswordCheckbox.isChecked) {
) PrefManager.setVal(PrefName.OverridePassword, true)
toast(R.string.success)
}
val promptInfo =
BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity)
biometricPrompt.authenticate(promptInfo)
}
} else {
PrefManager.setVal(PrefName.BiometricToken, "")
toast(R.string.success)
} }
} else { val password = view.passwordInput.text.toString()
toast(R.string.password_mismatch) val confirmPassword = view.confirmPasswordInput.text.toString()
} if (password == confirmPassword && password.isNotEmpty()) {
} PrefManager.setVal(PrefName.AppPassword, password)
setNegButton(R.string.cancel) if (view.biometricCheckbox.isChecked) {
setNeutralButton(R.string.remove){ val canBiometricPrompt =
PrefManager.setVal(PrefName.AppPassword, "") BiometricManager
PrefManager.setVal(PrefName.BiometricToken, "") .from(applicationContext)
PrefManager.setVal(PrefName.OverridePassword, false) .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ==
toast(R.string.success) BiometricManager.BIOMETRIC_SUCCESS
}
setOnShowListener {
view.passwordInput.requestFocus()
val canAuthenticate =
BiometricManager.from(applicationContext).canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_WEAK
) == BiometricManager.BIOMETRIC_SUCCESS
view.biometricCheckbox.isVisible = canAuthenticate
view.biometricCheckbox.isChecked =
PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()
view.forgotPasswordCheckbox.isChecked =
PrefManager.getVal(PrefName.OverridePassword)
}
show()
}
}
), if (canBiometricPrompt) {
Settings( val biometricPrompt =
type = 1, BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ ->
name = getString(R.string.backup_restore), val token = UUID.randomUUID().toString()
desc = getString(R.string.backup_restore_desc), PrefManager.setVal(
icon = R.drawable.backup_restore, PrefName.BiometricToken,
onClick = { token,
StoragePermissions.downloadsPermission(context) )
val selectedArray = mutableListOf(false) toast(R.string.success)
val filteredLocations = Location.entries.filter { it.exportable } }
selectedArray.addAll(List(filteredLocations.size - 1) { false }) val promptInfo =
val dialog = AlertDialog.Builder(context, R.style.MyPopup) BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity)
.setTitle(R.string.backup_restore).setMultiChoiceItems( biometricPrompt.authenticate(promptInfo)
filteredLocations.map { it.name }.toTypedArray(), }
selectedArray.toBooleanArray()
) { _, which, isChecked ->
selectedArray[which] = isChecked
}.setPositiveButton(R.string.button_restore) { dialog, _ ->
openDocumentLauncher.launch(arrayOf("*/*"))
dialog.dismiss()
}.setNegativeButton(R.string.button_backup) { 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),
context,
password
)
} else { } else {
toast(R.string.password_cannot_be_empty) PrefManager.setVal(PrefName.BiometricToken, "")
toast(R.string.success)
} }
} else {
toast(R.string.password_mismatch)
} }
} else {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null
)
} }
}.setNeutralButton(R.string.cancel) { dialog, _ -> setNegButton(R.string.cancel)
dialog.dismiss() setNeutralButton(R.string.remove) {
}.create() PrefManager.setVal(PrefName.AppPassword, "")
dialog.window?.setDimAmount(0.8f) PrefManager.setVal(PrefName.BiometricToken, "")
dialog.show() PrefManager.setVal(PrefName.OverridePassword, false)
}, toast(R.string.success)
), }
Settings( setOnShowListener {
type = 1, view.passwordInput.requestFocus()
name = getString(R.string.change_download_location), val canAuthenticate =
desc = getString(R.string.change_download_location_desc), BiometricManager.from(applicationContext).canAuthenticate(
icon = R.drawable.ic_round_source_24, BiometricManager.Authenticators.BIOMETRIC_WEAK,
onClick = { ) == BiometricManager.BIOMETRIC_SUCCESS
context.customAlertDialog().apply{ view.biometricCheckbox.isVisible = canAuthenticate
setTitle(R.string.change_download_location) view.biometricCheckbox.isChecked =
setMessage(R.string.download_location_msg) PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()
setPosButton(R.string.ok){ view.forgotPasswordCheckbox.isChecked =
val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir) PrefManager.getVal(PrefName.OverridePassword)
launcher.registerForCallback { success -> }
if (success) { show()
toast(getString(R.string.please_wait)) }
val newUri = },
PrefManager.getVal<String>(PrefName.DownloadsDir) ),
GlobalScope.launch(Dispatchers.IO) { Settings(
Injekt.get<DownloadsManager>().moveDownloadsDir( type = 1,
context, Uri.parse(oldUri), Uri.parse(newUri) name = getString(R.string.backup_restore),
) { finished, message -> desc = getString(R.string.backup_restore_desc),
if (finished) { icon = R.drawable.backup_restore,
toast(getString(R.string.success)) onClick = {
} else { StoragePermissions.downloadsPermission(context)
toast(message) val filteredLocations = Location.entries.filter { it.exportable }
} val selectedArray = BooleanArray(filteredLocations.size) { false }
context.customAlertDialog().apply {
setTitle(R.string.backup_restore)
multiChoiceItems(
filteredLocations.map { it.name }.toTypedArray(),
selectedArray,
) { updatedSelection ->
for (i in updatedSelection.indices) {
selectedArray[i] = updatedSelection[i]
}
}
setPosButton(R.string.button_restore) {
openDocumentLauncher.launch(arrayOf("*/*"))
}
setNegButton(R.string.button_backup) {
if (!selectedArray.contains(true)) {
toast(R.string.no_location_selected)
return@setNegButton
}
val selected =
filteredLocations.filterIndexed { index, _ -> selectedArray[index] }
if (selected.contains(Location.Protected)) {
passwordAlertDialog(true) { password ->
if (password != null) {
savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
password,
)
} else {
toast(R.string.password_cannot_be_empty)
} }
} }
} else { } else {
toast(getString(R.string.error)) savePrefsToDownloads(
"DantotsuSettings",
PrefManager.exportAllPrefs(selected),
context,
null,
)
} }
} }
launcher.launch() setNeutralButton(R.string.cancel) {}
show()
} }
setNegButton(R.string.cancel) },
show() ),
} Settings(
} type = 1,
), name = getString(R.string.change_download_location),
Settings( desc = getString(R.string.change_download_location_desc),
type = 2, icon = R.drawable.ic_round_source_24,
name = getString(R.string.always_continue_content), onClick = {
desc = getString(R.string.always_continue_content_desc), context.customAlertDialog().apply {
icon = R.drawable.ic_round_delete_24, setTitle(R.string.change_download_location)
isChecked = PrefManager.getVal(PrefName.ContinueMedia), setMessage(R.string.download_location_msg)
switch = { isChecked, _ -> setPosButton(R.string.ok) {
PrefManager.setVal(PrefName.ContinueMedia, isChecked) val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir)
} launcher.registerForCallback { success ->
), if (success) {
Settings( toast(getString(R.string.please_wait))
type = 2, val newUri =
name = getString(R.string.hide_private), PrefManager.getVal<String>(PrefName.DownloadsDir)
desc = getString(R.string.hide_private_desc), GlobalScope.launch(Dispatchers.IO) {
icon = R.drawable.ic_round_remove_red_eye_24, Injekt.get<DownloadsManager>().moveDownloadsDir(
isChecked = PrefManager.getVal(PrefName.HidePrivate), context,
switch = { isChecked, _ -> Uri.parse(oldUri),
PrefManager.setVal(PrefName.HidePrivate, isChecked) Uri.parse(newUri),
restartApp() ) { finished, message ->
} if (finished) {
), toast(getString(R.string.success))
Settings( } else {
type = 2, toast(message)
name = getString(R.string.search_source_list), }
desc = getString(R.string.search_source_list_desc), }
icon = R.drawable.ic_round_search_sources_24, }
isChecked = PrefManager.getVal(PrefName.SearchSources), } else {
switch = { isChecked, _ -> toast(getString(R.string.error))
PrefManager.setVal(PrefName.SearchSources, isChecked) }
} }
), launcher.launch()
Settings( }
type = 2, setNegButton(R.string.cancel)
name = getString(R.string.recentlyListOnly), show()
desc = getString(R.string.recentlyListOnly_desc), }
icon = R.drawable.ic_round_new_releases_24, },
isChecked = PrefManager.getVal(PrefName.RecentlyListOnly), ),
switch = { isChecked, _ -> Settings(
PrefManager.setVal(PrefName.RecentlyListOnly, isChecked) type = 2,
} name = getString(R.string.always_continue_content),
), desc = getString(R.string.always_continue_content_desc),
Settings( icon = R.drawable.ic_round_delete_24,
type = 2, isChecked = PrefManager.getVal(PrefName.ContinueMedia),
name = getString(R.string.adult_only_content), switch = { isChecked, _ ->
desc = getString(R.string.adult_only_content_desc), PrefManager.setVal(PrefName.ContinueMedia, isChecked)
icon = R.drawable.ic_round_nsfw_24, },
isChecked = PrefManager.getVal(PrefName.AdultOnly), ),
switch = { isChecked, _ -> Settings(
PrefManager.setVal(PrefName.AdultOnly, isChecked) type = 2,
restartApp() name = getString(R.string.hide_private),
}, desc = getString(R.string.hide_private_desc),
isVisible = Anilist.adult icon = R.drawable.ic_round_remove_red_eye_24,
isChecked = PrefManager.getVal(PrefName.HidePrivate),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.HidePrivate, isChecked)
restartApp()
},
),
Settings(
type = 2,
name = getString(R.string.search_source_list),
desc = getString(R.string.search_source_list_desc),
icon = R.drawable.ic_round_search_sources_24,
isChecked = PrefManager.getVal(PrefName.SearchSources),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.SearchSources, isChecked)
},
),
Settings(
type = 2,
name = getString(R.string.recentlyListOnly),
desc = getString(R.string.recentlyListOnly_desc),
icon = R.drawable.ic_round_new_releases_24,
isChecked = PrefManager.getVal(PrefName.RecentlyListOnly),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.RecentlyListOnly, isChecked)
},
),
Settings(
type = 2,
name = getString(R.string.adult_only_content),
desc = getString(R.string.adult_only_content_desc),
icon = R.drawable.ic_round_nsfw_24,
isChecked = PrefManager.getVal(PrefName.AdultOnly),
switch = { isChecked, _ ->
PrefManager.setVal(PrefName.AdultOnly, isChecked)
restartApp()
},
isVisible = Anilist.adult,
),
), ),
) )
)
settingsRecyclerView.apply { settingsRecyclerView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
setHasFixedSize(true) setHasFixedSize(true)
} }
var previousStart: View = when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) { var previousStart: View =
0 -> uiSettingsAnime when (PrefManager.getVal<Int>(PrefName.DefaultStartUpTab)) {
1 -> uiSettingsHome 0 -> uiSettingsAnime
2 -> uiSettingsManga 1 -> uiSettingsHome
else -> uiSettingsHome 2 -> uiSettingsManga
} else -> uiSettingsHome
}
previousStart.alpha = 1f previousStart.alpha = 1f
fun uiDefault(mode: Int, current: View) {
fun uiDefault(
mode: Int,
current: View,
) {
previousStart.alpha = 0.33f previousStart.alpha = 0.33f
previousStart = current previousStart = current
current.alpha = 1f current.alpha = 1f
@@ -433,11 +434,13 @@ class SettingsCommonActivity : AppCompatActivity() {
uiSettingsManga.setOnClickListener { uiSettingsManga.setOnClickListener {
uiDefault(2, it) uiDefault(2, it)
} }
} }
} }
private fun passwordAlertDialog(isExporting: Boolean, callback: (CharArray?) -> Unit) { private fun passwordAlertDialog(
isExporting: Boolean,
callback: (CharArray?) -> Unit,
) {
val password = CharArray(16).apply { fill('0') } val password = CharArray(16).apply { fill('0') }
// Inflate the dialog layout // Inflate the dialog layout
@@ -447,7 +450,9 @@ class SettingsCommonActivity : AppCompatActivity() {
box.setSingleLine() box.setSingleLine()
val dialog = val dialog =
AlertDialog.Builder(this, R.style.MyPopup).setTitle(getString(R.string.enter_password)) AlertDialog
.Builder(this, R.style.MyPopup)
.setTitle(getString(R.string.enter_password))
.setView(dialogView.root) .setView(dialogView.root)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel) { dialog, _ -> .setNegativeButton(R.string.cancel) { dialog, _ ->
@@ -459,7 +464,10 @@ class SettingsCommonActivity : AppCompatActivity() {
fun handleOkAction() { fun handleOkAction() {
val editText = dialogView.userAgentTextBox val editText = dialogView.userAgentTextBox
if (editText.text?.isNotBlank() == true) { if (editText.text?.isNotBlank() == true) {
editText.text?.toString()?.trim()?.toCharArray(password) editText.text
?.toString()
?.trim()
?.toCharArray(password)
dialog.dismiss() dialog.dismiss()
callback(password) callback(password)
} else { } else {
@@ -475,18 +483,20 @@ class SettingsCommonActivity : AppCompatActivity() {
} }
} }
dialogView.subtitle.visibility = View.VISIBLE dialogView.subtitle.visibility = View.VISIBLE
if (!isExporting) dialogView.subtitle.text = if (!isExporting) {
getString(R.string.enter_password_to_decrypt_file) dialogView.subtitle.text =
getString(R.string.enter_password_to_decrypt_file)
}
dialog.window?.apply {
dialog.window?.setDimAmount(0.8f) setDimAmount(0.8f)
attributes.windowAnimations = android.R.style.Animation_Dialog
}
dialog.show() dialog.show()
// Override the positive button here // Override the positive button here
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
handleOkAction() handleOkAction()
} }
} }
} }

View File

@@ -59,7 +59,8 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val window = dialog?.window val window = dialog?.window
window?.statusBarColor = Color.CYAN window?.statusBarColor = Color.CYAN
window?.navigationBarColor = requireContext().getThemeColor(com.google.android.material.R.attr.colorSurface) window?.navigationBarColor =
requireContext().getThemeColor(com.google.android.material.R.attr.colorSurface)
val notificationIcon = if (Anilist.unreadNotificationCount > 0) { val notificationIcon = if (Anilist.unreadNotificationCount > 0) {
R.drawable.ic_round_notifications_active_24 R.drawable.ic_round_notifications_active_24
} else { } else {
@@ -70,7 +71,7 @@ class SettingsDialogFragment : BottomSheetDialogFragment() {
if (Anilist.token != null) { if (Anilist.token != null) {
binding.settingsLogin.setText(R.string.logout) binding.settingsLogin.setText(R.string.logout)
binding.settingsLogin.setOnClickListener { binding.settingsLogin.setOnClickListener {
requireContext().customAlertDialog().apply{ requireContext().customAlertDialog().apply {
setTitle(R.string.logout) setTitle(R.string.logout)
setMessage(R.string.logout_confirm) setMessage(R.string.logout_confirm)
setPosButton(R.string.yes) { setPosButton(R.string.yes) {

Some files were not shown because too many files have changed in this diff Show More