diff --git a/README.md b/README.md index a1b1d673..6b713a2a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ 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! + + ## Terms of Use By downloading, installing, or using this application, you agree to: - Use the application in compliance with all applicable laws diff --git a/app/build.gradle b/app/build.gradle index 535d8922..0604d87e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,9 +17,8 @@ android { applicationId "ani.dantotsu" minSdk 21 targetSdk 35 - versionCode((System.currentTimeMillis() / 60000).toInteger()) versionName "3.2.2" - versionCode versionName.split("\\.").collect { it.toInteger() * 100 }.join("") as Integer + versionCode 300200200 signingConfig signingConfigs.debug } @@ -48,10 +47,6 @@ android { manifestPlaceholders.icon_placeholder_round = "@mipmap/ic_launcher_alpha_round" debuggable System.getenv("CI") == null isDefault true - debuggable true - jniDebuggable true - minifyEnabled false - shrinkResources false } debug { applicationIdSuffix ".beta" @@ -85,26 +80,25 @@ android { dependencies { // FireBase - googleImplementation platform('com.google.firebase:firebase-bom:33.13.0') - googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.4.0' - googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.4.3' + googleImplementation platform('com.google.firebase:firebase-bom:33.0.0') + googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.0.0' + googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.0.0' // Core - implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.browser:browser:1.8.0' - implementation 'androidx.core:core-ktx:1.16.0' - implementation 'androidx.fragment:fragment-ktx:1.8.6' - implementation 'androidx.activity:activity-ktx:1.10.1' + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.fragment:fragment-ktx:1.6.2' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.multidex:multidex:2.0.1' - implementation "androidx.work:work-runtime-ktx:2.10.1" + implementation "androidx.work:work-runtime-ktx:2.9.0" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.github.Blatzar:NiceHttp:0.4.4' - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' implementation 'androidx.preference:preference-ktx:1.2.1' - implementation 'androidx.webkit:webkit:1.13.0' + implementation 'androidx.webkit:webkit:1.11.0' implementation "com.anggrayudi:storage:1.5.5" implementation "androidx.biometric:biometric:1.1.0" @@ -118,7 +112,7 @@ dependencies { implementation 'jp.wasabeef:glide-transformations:4.3.0' // Exoplayer - ext.exo_version = '1.6.1' + ext.exo_version = '1.5.0' implementation "androidx.media3:media3-exoplayer:$exo_version" implementation "androidx.media3:media3-ui:$exo_version" implementation "androidx.media3:media3-exoplayer-hls:$exo_version" @@ -129,7 +123,7 @@ dependencies { implementation "androidx.media3:media3-cast:$exo_version" implementation "androidx.mediarouter:mediarouter:1.7.0" // Media3 extension - implementation "com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.4" + implementation "com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.3" // UI implementation 'com.google.android.material:material:1.12.0' @@ -138,7 +132,7 @@ dependencies { implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.alexvasilkov:gesture-views:2.8.3' implementation 'com.github.VipulOG:ebook-reader:0.1.6' - implementation 'androidx.paging:paging-runtime-ktx:3.3.6' + implementation 'androidx.paging:paging-runtime-ktx:3.2.1' implementation 'com.github.eltos:simpledialogfragments:v3.7' implementation 'com.github.AAChartModel:AAChartCore-Kotlin:7.2.3' @@ -167,13 +161,13 @@ dependencies { implementation 'ca.gosyer:voyager-navigator:1.0.0-rc07' implementation 'com.squareup.logcat:logcat:0.1' implementation 'uy.kohesive.injekt:injekt-core:1.16.+' - implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14' - implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14' + implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12' + implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps' - implementation 'com.squareup.okio:okio:3.9.1' - implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.14' - implementation 'org.jsoup:jsoup:1.18.1' - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.7.3' + implementation 'com.squareup.okio:okio:3.8.0' + implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12' + implementation 'org.jsoup:jsoup:1.16.1' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.3' implementation 'com.jakewharton.rxrelay:rxrelay:1.2.0' implementation 'com.github.tachiyomiorg:unifile:17bec43' implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1' diff --git a/app/src/main/java/ani/dantotsu/App.kt b/app/src/main/java/ani/dantotsu/App.kt index 0b5751e0..057c6d22 100644 --- a/app/src/main/java/ani/dantotsu/App.kt +++ b/app/src/main/java/ani/dantotsu/App.kt @@ -113,28 +113,21 @@ class App : MultiDexApplication() { } } - val scope = CoroutineScope(Dispatchers.IO) - scope.launch { + CoroutineScope(Dispatchers.IO).launch { animeExtensionManager = Injekt.get() - launch { - animeExtensionManager.findAvailableExtensions() - } + animeExtensionManager.findAvailableExtensions() Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}") AnimeSources.init(animeExtensionManager.installedExtensionsFlow) } - scope.launch { + CoroutineScope(Dispatchers.IO).launch { mangaExtensionManager = Injekt.get() - launch { - mangaExtensionManager.findAvailableExtensions() - } + mangaExtensionManager.findAvailableExtensions() Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}") MangaSources.init(mangaExtensionManager.installedExtensionsFlow) } - scope.launch { + CoroutineScope(Dispatchers.IO).launch { novelExtensionManager = Injekt.get() - launch { - novelExtensionManager.findAvailableExtensions() - } + novelExtensionManager.findAvailableExtensions() Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}") NovelSources.init(novelExtensionManager.installedExtensionsFlow) } diff --git a/app/src/main/java/ani/dantotsu/Network.kt b/app/src/main/java/ani/dantotsu/Network.kt index f9d8b7f2..ce536e8d 100644 --- a/app/src/main/java/ani/dantotsu/Network.kt +++ b/app/src/main/java/ani/dantotsu/Network.kt @@ -11,11 +11,7 @@ import com.lagradost.nicehttp.addGenericDns import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper.Companion.defaultUserAgentProvider import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi @@ -89,42 +85,12 @@ object Mapper : ResponseParser { } } -/** - * Performs parallel processing of collection items without blocking threads. - * Each operation runs in its own coroutine on the specified dispatcher. - * - * @param dispatcher The CoroutineDispatcher to use for parallel operations (defaults to IO) - * @param f The suspend function to apply to each item - * @return List of results in the same order as the original collection - */ -suspend fun Collection.asyncMap( - dispatcher: CoroutineDispatcher = Dispatchers.IO, - f: suspend (A) -> B -): List = coroutineScope { - map { item -> - async(dispatcher) { - f(item) - } - }.awaitAll() +fun Collection.asyncMap(f: suspend (A) -> B): List = runBlocking { + map { async { f(it) } }.map { it.await() } } -/** - * Performs parallel processing of collection items without blocking threads, - * filtering out null results. - * - * @param dispatcher The CoroutineDispatcher to use for parallel operations (defaults to IO) - * @param f The suspend function to apply to each item - * @return List of non-null results in the same order as the original collection - */ -suspend fun Collection.asyncMapNotNull( - dispatcher: CoroutineDispatcher = Dispatchers.IO, - f: suspend (A) -> B? -): List = coroutineScope { - map { item -> - async(dispatcher) { - f(item) - } - }.mapNotNull { it.await() } +fun Collection.asyncMapNotNull(f: suspend (A) -> B?): List = runBlocking { + map { async { f(it) } }.mapNotNull { it.await() } } fun logError(e: Throwable, post: Boolean = true, snackbar: Boolean = true) { diff --git a/app/src/main/java/ani/dantotsu/connections/discord/Login.kt b/app/src/main/java/ani/dantotsu/connections/discord/Login.kt index f33c03e9..513da55b 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/Login.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/Login.kt @@ -47,9 +47,9 @@ class Login : AppCompatActivity() { view.evaluateJavascript( """ (function() { - const m = []; webpackChunkdiscord_app.push([[""], {}, e => {for (let c in e.c)m.push(e.c[c])}]); - return m.find(n => n?.exports?.default?.getToken !== void 0)?.exports?.default?.getToken(); -})() + 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(); + return wreq; + })() """.trimIndent() ) { result -> login(result.trim('"')) diff --git a/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt b/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt index 20bafb39..eec99b96 100644 --- a/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt +++ b/app/src/main/java/ani/dantotsu/connections/discord/RPC.kt @@ -50,8 +50,9 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) { val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json) suspend fun String.discordUrl() = assetApi.getDiscordUri(this) - return json.encodeToString(Presence.Response( - 3, + return json.encodeToString( + Presence.Response( + 3, Presence( activities = listOf( Activity( diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 1e1d2e19..bcb21c8c 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -4,7 +4,6 @@ import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.content.Intent import android.content.res.Configuration -import android.graphics.Color import android.os.Bundle import android.text.SpannableStringBuilder import android.view.GestureDetector @@ -13,8 +12,6 @@ import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateDecelerateInterpolator import android.widget.ImageView -import androidx.activity.SystemBarStyle -import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -22,10 +19,8 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.core.text.bold import androidx.core.text.color -import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isVisible import androidx.core.view.marginBottom -import androidx.core.view.setPadding import androidx.core.view.updateLayoutParams import androidx.core.view.updateMargins import androidx.fragment.app.Fragment @@ -84,7 +79,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) var media: Media = intent.getSerialized("media") ?: mediaSingleton ?: emptyMedia() val id = intent.getIntExtra("mediaId", -1) @@ -115,7 +109,6 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi // Ui init initActivity(this) - binding.mediaViewPager.updateLayoutParams { bottomMargin = navBarHeight } @@ -139,12 +132,10 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi val navBarBottomMargin = if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE ) 0 else navBarHeight - binding.mediaBottomBarContainer.setPadding( - navBar.paddingLeft, - navBar.paddingTop, - navBar.paddingRight + navBarRightMargin, - navBar.paddingBottom + navBarBottomMargin - ) + navBar.updateLayoutParams { + rightMargin = navBarRightMargin + bottomMargin = navBarBottomMargin + } binding.mediaBanner.updateLayoutParams { height += statusBarHeight } binding.mediaBannerNoKen.updateLayoutParams { height += statusBarHeight } binding.mediaClose.updateLayoutParams { topMargin += statusBarHeight } diff --git a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt index 7fa32de9..5c66f6a2 100644 --- a/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt +++ b/app/src/main/java/ani/dantotsu/media/SubtitleDownloader.kt @@ -1,8 +1,6 @@ package ani.dantotsu.media import android.content.Context -import androidx.core.net.toFile -import androidx.core.net.toUri import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.parsers.SubtitleType @@ -23,32 +21,28 @@ class SubtitleDownloader { suspend fun loadSubtitleType(url: String): SubtitleType = withContext(Dispatchers.IO) { return@withContext try { - if (!url.startsWith("file")) { - // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it - val networkHelper = Injekt.get() - val request = Request.Builder() - .url(url) - .build() + // Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it + val networkHelper = Injekt.get() + val request = Request.Builder() + .url(url) + .build() - val response = networkHelper.client.newCall(request).execute() + val response = networkHelper.client.newCall(request).execute() - // Check if response is successful - if (response.isSuccessful) { - val responseBody = response.body.string() + // Check if response is successful + if (response.isSuccessful) { + val responseBody = response.body.string() - val subtitleType = getType(responseBody) - - subtitleType - } else { - SubtitleType.UNKNOWN + val subtitleType = when { + responseBody.contains("[Script Info]") -> SubtitleType.ASS + responseBody.contains("WEBVTT") -> SubtitleType.VTT + else -> SubtitleType.SRT } - } else { - val uri = url.toUri() - val file = uri.toFile() - val fileBody = file.readText() - val subtitleType = getType(fileBody) + subtitleType + } else { + SubtitleType.UNKNOWN } } catch (e: Exception) { Logger.log(e) @@ -56,15 +50,6 @@ 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 @Deprecated("handled externally") suspend fun downloadSubtitle( diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt index 75332806..4de053dd 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt @@ -2,6 +2,7 @@ package ani.dantotsu.media.anime import android.animation.ObjectAnimator import android.annotation.SuppressLint +import android.app.AlertDialog import android.app.Dialog import android.app.PictureInPictureParams import android.app.PictureInPictureUiState @@ -18,6 +19,7 @@ import android.media.AudioManager.AUDIOFOCUS_GAIN import android.media.AudioManager.AUDIOFOCUS_LOSS import android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT import android.media.AudioManager.STREAM_MUSIC +import android.net.Uri import android.os.Build import android.os.Bundle import android.os.CountDownTimer @@ -73,7 +75,9 @@ import androidx.media3.common.TrackSelectionOverride import androidx.media3.common.Tracks import androidx.media3.common.text.CueGroup import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DataSource import androidx.media3.datasource.DefaultDataSource +import androidx.media3.datasource.HttpDataSource import androidx.media3.datasource.cache.CacheDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource import androidx.media3.exoplayer.DefaultLoadControl @@ -171,10 +175,10 @@ import java.util.Timer import java.util.TimerTask import java.util.concurrent.Executors import java.util.concurrent.TimeUnit +import kotlin.collections.set import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt -import androidx.core.net.toUri @UnstableApi @SuppressLint("ClickableViewAccessibility") @@ -1308,7 +1312,7 @@ class ExoplayerView : setTitle(R.string.speed) singleChoiceItems(speedsName, curSpeed) { i -> PrefManager.setCustomVal("${media.id}_speed", i) - speed = speeds.getOrNull(i) ?: 1f + speed = speeds[i] curSpeed = i playbackParameters = PlaybackParameters(speed) exoPlayer.playbackParameters = playbackParameters @@ -1358,7 +1362,7 @@ class ExoplayerView : val showProgressDialog = if (PrefManager.getVal(PrefName.AskIndividualPlayer)) { PrefManager.getCustomVal( - "${media.id}_progressDialog", + "${media.id}_ProgressDialog", true, ) } else { @@ -1380,7 +1384,7 @@ class ExoplayerView : setCancelable(false) setPosButton(R.string.yes) { PrefManager.setCustomVal( - "${media.id}_progressDialog", + "${media.id}_ProgressDialog", false, ) PrefManager.setCustomVal( @@ -1391,7 +1395,7 @@ class ExoplayerView : } setNegButton(R.string.no) { PrefManager.setCustomVal( - "${media.id}_progressDialog", + "${media.id}_ProgressDialog", false, ) PrefManager.setCustomVal( @@ -1605,27 +1609,29 @@ class ExoplayerView : emptyList().toMutableList() ext.subtitles.forEach { subtitle -> val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle.file.url + // var localFile: String? = null if (subtitle.type == SubtitleType.UNKNOWN) { runBlocking { val type = SubtitleDownloader.loadSubtitleType(subtitleUrl) - val fileUri = (subtitleUrl).toUri() + val fileUri = Uri.parse(subtitleUrl) sub += MediaItem.SubtitleConfiguration .Builder(fileUri) .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .setMimeType( when (type) { - SubtitleType.VTT -> MimeTypes.TEXT_VTT + SubtitleType.VTT -> MimeTypes.TEXT_SSA SubtitleType.ASS -> MimeTypes.TEXT_SSA - SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP - else -> MimeTypes.TEXT_UNKNOWN + SubtitleType.SRT -> MimeTypes.TEXT_SSA + else -> MimeTypes.TEXT_SSA }, ).setId("69") .setLanguage(subtitle.language) .build() } + println("sub: $sub") } else { - val subUri = subtitleUrl.toUri() + val subUri = Uri.parse(subtitleUrl) sub += MediaItem.SubtitleConfiguration .Builder(subUri) @@ -1655,18 +1661,27 @@ class ExoplayerView : followRedirects(true) followSslRedirects(true) }.build() - val httpDataSourceFactory = - OkHttpDataSource.Factory(httpClient).apply { - setDefaultRequestProperties(defaultHeaders) - video?.file?.headers?.let { - setDefaultRequestProperties(it) + val dataSourceFactory = + DataSource.Factory { + val dataSource: HttpDataSource = + OkHttpDataSource.Factory(httpClient).createDataSource() + defaultHeaders.forEach { + dataSource.setRequestProperty(it.key, it.value) } + video?.file?.headers?.forEach { + dataSource.setRequestProperty(it.key, it.value) + } + dataSource } - val defaultDataSourceFactory = DefaultDataSource.Factory(this, httpDataSourceFactory) + val dafuckDataSourceFactory = DefaultDataSource.Factory(this) cacheFactory = CacheDataSource.Factory().apply { setCache(VideoCache.getInstance(this@ExoplayerView)) - setUpstreamDataSourceFactory(defaultDataSourceFactory) + if (ext.server.offline) { + setUpstreamDataSourceFactory(dafuckDataSourceFactory) + } else { + setUpstreamDataSourceFactory(dataSourceFactory) + } setCacheWriteDataSinkFactory(null) } @@ -1928,7 +1943,7 @@ class ExoplayerView : if (PrefManager.getVal(PrefName.TextviewSubtitles)) { exoSubtitleView.visibility = View.GONE customSubtitleView.visibility = View.VISIBLE - val newCues = cueGroup.cues.map { it.text.toString() ?: "" } + val newCues = cueGroup.cues.map { it.text.toString() } if (newCues.isEmpty()) { customSubtitleView.text = "" @@ -2488,7 +2503,7 @@ class ExoplayerView : val videoURL = video?.file?.url ?: return val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle!!.file.url val shareVideo = Intent(Intent.ACTION_VIEW) - shareVideo.setDataAndType(videoURL.toUri(), "video/*") + shareVideo.setDataAndType(Uri.parse(videoURL), "video/*") shareVideo.setPackage("com.instantbits.cast.webvideo") if (subtitle != null) shareVideo.putExtra("subtitle", subtitleUrl) shareVideo.putExtra( @@ -2510,7 +2525,7 @@ class ExoplayerView : } catch (ex: ActivityNotFoundException) { val intent = Intent(Intent.ACTION_VIEW) val uriString = "market://details?id=com.instantbits.cast.webvideo" - intent.data = uriString.toUri() + intent.data = Uri.parse(uriString) startActivity(intent) } } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt index f47f416f..359c64a5 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaCache.kt @@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File -import java.io.FileNotFoundException import java.io.FileOutputStream data class ImageData( @@ -77,7 +76,7 @@ fun saveImage( uri?.let { contentResolver.openOutputStream(it)?.use { os -> bitmap.compress(format, quality, os) - } ?: throw FileNotFoundException("Failed to open output stream for URI: $uri") + } } } else { val directory = @@ -87,20 +86,12 @@ fun saveImage( } 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 -> bitmap.compress(format, quality, outputStream) } } - } catch (e: FileNotFoundException) { - println("File not found: ${e.message}") } catch (e: Exception) { + // Handle exception here println("Exception while saving image: ${e.message}") } } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index 2ad28a75..63ef7408 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -66,7 +66,6 @@ import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.source.ConfigurableSource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt @@ -233,35 +232,25 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } fun multiDownload(n: Int) { - lifecycleScope.launch { - // Get the last viewed chapter - val selected = media.userProgress ?: 0 - val chapters = media.manga?.chapters?.values?.toList() - // Ensure chapters are available in the extensions - if (chapters.isNullOrEmpty() || n < 1) return@launch - // Find the index of the last viewed chapter - val progressChapterIndex = (chapters.indexOfFirst { - MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected - } + 1).coerceAtLeast(0) - // Calculate the end value for the range of chapters to download - val endIndex = (progressChapterIndex + n).coerceAtMost(chapters.size) - // Get the list of chapters to download - val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex) - // Trigger the download for each chapter sequentially - for (chapter in chaptersToDownload) { - try { - 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) { + // Get last viewed chapter + val selected = media.userProgress + val chapters = media.manga?.chapters?.values?.toList() + // Filter by selected language + val progressChapterIndex = (chapters?.indexOfFirst { + MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected + } ?: 0) + 1 + + if (progressChapterIndex < 0 || n < 1 || chapters == null) return + + // Calculate the end index + val endIndex = minOf(progressChapterIndex + n, chapters.size) + + // Make sure there are enough chapters + val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex) + + + for (chapter in chaptersToDownload) { onMangaChapterDownloadClick(chapter) - delay(2000) // A 2-second download } } diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index f534ce6b..f343d70b 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -26,6 +26,7 @@ object AnimeSources : WatchSources() { ) isInitialized = true + // Update as StateFlow emits new values fromExtensions.collect { extensions -> list = sortPinnedAnimeSources( createParsersFromExtensions(extensions), diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 0f2a438a..adfd857e 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -226,18 +226,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { ?: return emptyList()) return try { - // TODO(1.6): Remove else block when dropping support for ext lib <1.6 - 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) } - } + val videos = source.getVideoList(sEpisode) + videos.map { videoToVideoServer(it) } } catch (e: Exception) { Logger.log("Exception occurred: ${e.message}") emptyList() @@ -586,7 +576,7 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac number, format!!, FileUrl(videoUrl, headersMap), - null + if (aniVideo.totalContentLength == 0L) null else aniVideo.bytesDownloaded.toDouble() ) } @@ -646,6 +636,7 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac } private fun trackToSubtitle(track: Track): Subtitle { + //use Dispatchers.IO to make a HTTP request to determine the subtitle type var type: SubtitleType? runBlocking { type = findSubtitleType(track.url) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt index 38230a86..f0bf91d0 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt @@ -193,7 +193,8 @@ class SettingsCommonActivity : AppCompatActivity() { PrefManager.setVal(PrefName.OverridePassword, true) } val password = view.passwordInput.text.toString() - val confirmPassword = view.confirmPasswordInput.text.toString() + val confirmPassword = + view.confirmPasswordInput.text.toString() if (password == confirmPassword && password.isNotEmpty()) { PrefManager.setVal(PrefName.AppPassword, password) if (view.biometricCheckbox.isChecked) { @@ -201,11 +202,13 @@ class SettingsCommonActivity : AppCompatActivity() { BiometricManager .from(applicationContext) .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == - BiometricManager.BIOMETRIC_SUCCESS + BiometricManager.BIOMETRIC_SUCCESS if (canBiometricPrompt) { val biometricPrompt = - BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> + BiometricPromptUtils.createBiometricPrompt( + this@SettingsCommonActivity + ) { _ -> val token = UUID.randomUUID().toString() PrefManager.setVal( PrefName.BiometricToken, @@ -235,12 +238,14 @@ class SettingsCommonActivity : AppCompatActivity() { setOnShowListener { view.passwordInput.requestFocus() val canAuthenticate = - BiometricManager.from(applicationContext).canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK, - ) == BiometricManager.BIOMETRIC_SUCCESS + BiometricManager.from(applicationContext) + .canAuthenticate( + BiometricManager.Authenticators.BIOMETRIC_WEAK, + ) == BiometricManager.BIOMETRIC_SUCCESS view.biometricCheckbox.isVisible = canAuthenticate view.biometricCheckbox.isChecked = - PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() + PrefManager.getVal(PrefName.BiometricToken, "") + .isNotEmpty() view.forgotPasswordCheckbox.isChecked = PrefManager.getVal(PrefName.OverridePassword) } @@ -314,7 +319,8 @@ class SettingsCommonActivity : AppCompatActivity() { setTitle(R.string.change_download_location) setMessage(R.string.download_location_msg) setPosButton(R.string.ok) { - val oldUri = PrefManager.getVal(PrefName.DownloadsDir) + val oldUri = + PrefManager.getVal(PrefName.DownloadsDir) launcher.registerForCallback { success -> if (success) { toast(getString(R.string.please_wait)) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt index c5ca440a..f1225ed4 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt @@ -82,9 +82,18 @@ class SettingsNotificationActivity : AppCompatActivity() { setTitle(R.string.subscriptions_checking_time) singleChoiceItems(timeNames, curTime) { i -> curTime = i - it.settingsTitle.text = getString(R.string.subscriptions_checking_time_s, timeNames[i]) - PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) - TaskScheduler.create(context, PrefManager.getVal(PrefName.UseAlarmManager)).scheduleAllTasks(context) + it.settingsTitle.text = getString( + R.string.subscriptions_checking_time_s, + timeNames[i] + ) + PrefManager.setVal( + PrefName.SubscriptionNotificationInterval, + curTime + ) + TaskScheduler.create( + context, + PrefManager.getVal(PrefName.UseAlarmManager) + ).scheduleAllTasks(context) } show() } @@ -120,21 +129,22 @@ class SettingsNotificationActivity : AppCompatActivity() { .toMutableSet() val selected = types.map { filteredTypes.contains(it) }.toBooleanArray() context.customAlertDialog().apply { - setTitle(R.string.anilist_notification_filters) - multiChoiceItems( + setTitle(R.string.anilist_notification_filters) + multiChoiceItems( types.map { name -> name.replace("_", " ").lowercase().replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() - } }.toTypedArray(), + if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() + } + }.toTypedArray(), selected ) { updatedSelected -> - types.forEachIndexed { index, type -> - if (updatedSelected[index]) { - filteredTypes.add(type) - } else { - filteredTypes.remove(type) + types.forEachIndexed { index, type -> + if (updatedSelected[index]) { + filteredTypes.add(type) + } else { + filteredTypes.remove(type) + } } - } PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes) } show() @@ -152,8 +162,8 @@ class SettingsNotificationActivity : AppCompatActivity() { icon = R.drawable.ic_round_notifications_none_24, onClick = { context.customAlertDialog().apply { - setTitle(R.string.subscriptions_checking_time) - singleChoiceItems( + setTitle(R.string.subscriptions_checking_time) + singleChoiceItems( aItems.toTypedArray(), PrefManager.getVal(PrefName.AnilistNotificationInterval) ) { i -> @@ -181,11 +191,11 @@ class SettingsNotificationActivity : AppCompatActivity() { icon = R.drawable.ic_round_notifications_none_24, onClick = { context.customAlertDialog().apply { - setTitle(R.string.subscriptions_checking_time) - singleChoiceItems( + setTitle(R.string.subscriptions_checking_time) + singleChoiceItems( cItems.toTypedArray(), PrefManager.getVal(PrefName.CommentNotificationInterval) - ) { i -> + ) { i -> PrefManager.setVal(PrefName.CommentNotificationInterval, i) it.settingsTitle.text = getString( @@ -225,9 +235,9 @@ class SettingsNotificationActivity : AppCompatActivity() { switch = { isChecked, view -> if (isChecked) { context.customAlertDialog().apply { - setTitle(R.string.use_alarm_manager) - setMessage(R.string.use_alarm_manager_confirm) - setPosButton(R.string.use) { + setTitle(R.string.use_alarm_manager) + setMessage(R.string.use_alarm_manager_confirm) + setPosButton(R.string.use) { PrefManager.setVal(PrefName.UseAlarmManager, true) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) { diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt index 8133640a..648c26bd 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsThemeActivity.kt @@ -96,7 +96,8 @@ class SettingsThemeActivity : AppCompatActivity(), SimpleDialog.OnDialogResultLi themeSwitcher.apply { setText(themeText) setAdapter( - ArrayAdapter(context, + ArrayAdapter( + context, R.layout.item_dropdown, ThemeManager.Companion.Theme.entries.map { it.theme.substring( diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt index 9fa7552e..188da9d3 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt @@ -52,14 +52,15 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() { } groupedSubscriptions.forEach { (parserName, mediaList) -> - adapter.add(SubscriptionSource( - parserName, - mediaList.toMutableList(), - adapter, - getParserIcon(parserName) - ) { group -> - adapter.remove(group) - }) + adapter.add( + SubscriptionSource( + parserName, + mediaList.toMutableList(), + adapter, + getParserIcon(parserName) + ) { group -> + adapter.remove(group) + }) } } diff --git a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt index 036dd1b4..74683ab1 100644 --- a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt +++ b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt @@ -3,8 +3,6 @@ package ani.dantotsu.util import android.app.Activity import android.app.AlertDialog import android.content.Context -import android.os.Build -import android.view.WindowManager import android.view.View import ani.dantotsu.R @@ -207,14 +205,8 @@ class AlertDialogBuilder(private val context: Context) { onShow?.invoke() } dialog.window?.apply { - setDimAmount(0.5f) + setDimAmount(0.8f) attributes.windowAnimations = android.R.style.Animation_Dialog - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val params = attributes - params.flags = params.flags or WindowManager.LayoutParams.FLAG_BLUR_BEHIND - params.setBlurBehindRadius(20) - attributes = params - } } dialog.show() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt index 4b54360a..171b07b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/AnimeSource.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.animesource -import eu.kanade.tachiyomi.animesource.model.Hoster import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video @@ -49,25 +48,6 @@ interface AnimeSource { return fetchEpisodeList(anime).awaitSingle() } - /** - * Get the list of hoster for an episode. The first hoster in the list should - * be the preferred hoster. - * - * @since extensions-lib 16 - * @param episode the episode. - * @return the hosters for the episode. - */ - suspend fun getHosterList(episode: SEpisode): List = throw IllegalStateException("Not used") - - /** - * Get the list of videos for a hoster. - * - * @since extensions-lib 16 - * @param hoster the hoster. - * @return the videos for the hoster. - */ - suspend fun getVideoList(hoster: Hoster): List