Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a93b4f5b11 | ||
![]() |
69c44b7d20 | ||
![]() |
a684aac0b1 | ||
![]() |
6c49839f87 | ||
![]() |
7053a7b4b2 | ||
![]() |
1c156053d0 |
27 changed files with 262 additions and 569 deletions
|
@ -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!
|
> **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=000000&font_family=Cookie&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
|
||||||
|
|
|
@ -17,9 +17,8 @@ android {
|
||||||
applicationId "ani.dantotsu"
|
applicationId "ani.dantotsu"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode((System.currentTimeMillis() / 60000).toInteger())
|
|
||||||
versionName "3.2.2"
|
versionName "3.2.2"
|
||||||
versionCode versionName.split("\\.").collect { it.toInteger() * 100 }.join("") as Integer
|
versionCode 300200200
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,10 +47,6 @@ 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"
|
||||||
|
@ -85,26 +80,25 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// FireBase
|
// FireBase
|
||||||
googleImplementation platform('com.google.firebase:firebase-bom:33.13.0')
|
googleImplementation platform('com.google.firebase:firebase-bom:33.0.0')
|
||||||
googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.4.0'
|
googleImplementation 'com.google.firebase:firebase-analytics-ktx:22.0.0'
|
||||||
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.4.3'
|
googleImplementation 'com.google.firebase:firebase-crashlytics-ktx:19.0.0'
|
||||||
// Core
|
// Core
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.browser:browser:1.8.0'
|
implementation 'androidx.browser:browser:1.8.0'
|
||||||
implementation 'androidx.core:core-ktx:1.16.0'
|
implementation 'androidx.core:core-ktx:1.13.1'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.8.6'
|
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||||
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.10.1"
|
implementation "androidx.work:work-runtime-ktx:2.9.0"
|
||||||
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.7.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
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 "com.anggrayudi:storage:1.5.5"
|
||||||
implementation "androidx.biometric:biometric:1.1.0"
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
|
|
||||||
|
@ -118,7 +112,7 @@ dependencies {
|
||||||
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
implementation 'jp.wasabeef:glide-transformations:4.3.0'
|
||||||
|
|
||||||
// Exoplayer
|
// Exoplayer
|
||||||
ext.exo_version = '1.6.1'
|
ext.exo_version = '1.5.0'
|
||||||
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"
|
||||||
|
@ -129,7 +123,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.4"
|
implementation "com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.3"
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
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.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.3.6'
|
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||||
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'
|
||||||
|
|
||||||
|
@ -167,13 +161,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.14'
|
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14'
|
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.12'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||||
implementation 'com.squareup.okio:okio:3.9.1'
|
implementation 'com.squareup.okio:okio:3.8.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.14'
|
implementation 'com.squareup.okhttp3:okhttp-brotli:5.0.0-alpha.12'
|
||||||
implementation 'org.jsoup:jsoup:1.18.1'
|
implementation 'org.jsoup:jsoup:1.16.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.7.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.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'
|
||||||
|
|
|
@ -113,28 +113,21 @@ class App : MultiDexApplication() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val scope = CoroutineScope(Dispatchers.IO)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
scope.launch {
|
|
||||||
animeExtensionManager = Injekt.get()
|
animeExtensionManager = Injekt.get()
|
||||||
launch {
|
animeExtensionManager.findAvailableExtensions()
|
||||||
animeExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Anime Extensions: ${animeExtensionManager.installedExtensionsFlow.first()}")
|
||||||
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
AnimeSources.init(animeExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
scope.launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
mangaExtensionManager = Injekt.get()
|
mangaExtensionManager = Injekt.get()
|
||||||
launch {
|
mangaExtensionManager.findAvailableExtensions()
|
||||||
mangaExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Manga Extensions: ${mangaExtensionManager.installedExtensionsFlow.first()}")
|
||||||
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
MangaSources.init(mangaExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
scope.launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
novelExtensionManager = Injekt.get()
|
novelExtensionManager = Injekt.get()
|
||||||
launch {
|
novelExtensionManager.findAvailableExtensions()
|
||||||
novelExtensionManager.findAvailableExtensions()
|
|
||||||
}
|
|
||||||
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
Logger.log("Novel Extensions: ${novelExtensionManager.installedExtensionsFlow.first()}")
|
||||||
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
NovelSources.init(novelExtensionManager.installedExtensionsFlow)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,7 @@ import com.lagradost.nicehttp.addGenericDns
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper.Companion.defaultUserAgentProvider
|
import eu.kanade.tachiyomi.network.NetworkHelper.Companion.defaultUserAgentProvider
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
@ -89,42 +85,12 @@ object Mapper : ResponseParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun <A, B> Collection<A>.asyncMap(f: suspend (A) -> B): List<B> = runBlocking {
|
||||||
* Performs parallel processing of collection items without blocking threads.
|
map { async { f(it) } }.map { it.await() }
|
||||||
* 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 <A, B> Collection<A>.asyncMap(
|
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
|
||||||
f: suspend (A) -> B
|
|
||||||
): List<B> = coroutineScope {
|
|
||||||
map { item ->
|
|
||||||
async(dispatcher) {
|
|
||||||
f(item)
|
|
||||||
}
|
|
||||||
}.awaitAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun <A, B> Collection<A>.asyncMapNotNull(f: suspend (A) -> B?): List<B> = runBlocking {
|
||||||
* Performs parallel processing of collection items without blocking threads,
|
map { async { f(it) } }.mapNotNull { it.await() }
|
||||||
* 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 <A, B> Collection<A>.asyncMapNotNull(
|
|
||||||
dispatcher: CoroutineDispatcher = Dispatchers.IO,
|
|
||||||
f: suspend (A) -> B?
|
|
||||||
): List<B> = coroutineScope {
|
|
||||||
map { item ->
|
|
||||||
async(dispatcher) {
|
|
||||||
f(item)
|
|
||||||
}
|
|
||||||
}.mapNotNull { it.await() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logError(e: Throwable, post: Boolean = true, snackbar: Boolean = true) {
|
fun logError(e: Throwable, post: Boolean = true, snackbar: Boolean = true) {
|
||||||
|
|
|
@ -47,9 +47,9 @@ class Login : AppCompatActivity() {
|
||||||
view.evaluateJavascript(
|
view.evaluateJavascript(
|
||||||
"""
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
const m = []; webpackChunkdiscord_app.push([[""], {}, e => {for (let c in e.c)m.push(e.c[c])}]);
|
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 m.find(n => n?.exports?.default?.getToken !== void 0)?.exports?.default?.getToken();
|
return wreq;
|
||||||
})()
|
})()
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
) { result ->
|
) { result ->
|
||||||
login(result.trim('"'))
|
login(result.trim('"'))
|
||||||
|
|
|
@ -50,8 +50,9 @@ open class RPC(val token: String, val coroutineContext: CoroutineContext) {
|
||||||
val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json)
|
val assetApi = RPCExternalAsset(data.applicationId, token!!, client, json)
|
||||||
suspend fun String.discordUrl() = assetApi.getDiscordUri(this)
|
suspend fun String.discordUrl() = assetApi.getDiscordUri(this)
|
||||||
|
|
||||||
return json.encodeToString(Presence.Response(
|
return json.encodeToString(
|
||||||
3,
|
Presence.Response(
|
||||||
|
3,
|
||||||
Presence(
|
Presence(
|
||||||
activities = listOf(
|
activities = listOf(
|
||||||
Activity(
|
Activity(
|
||||||
|
|
|
@ -4,7 +4,6 @@ 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
|
||||||
|
@ -13,8 +12,6 @@ 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
|
||||||
|
@ -22,10 +19,8 @@ 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
|
||||||
|
@ -84,7 +79,6 @@ 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)
|
||||||
|
@ -115,7 +109,6 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -139,12 +132,10 @@ 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
|
||||||
binding.mediaBottomBarContainer.setPadding(
|
navBar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
navBar.paddingLeft,
|
rightMargin = navBarRightMargin
|
||||||
navBar.paddingTop,
|
bottomMargin = navBarBottomMargin
|
||||||
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 }
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
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
|
||||||
|
@ -23,32 +21,28 @@ 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 {
|
||||||
if (!url.startsWith("file")) {
|
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
||||||
// Initialize the NetworkHelper instance. Replace this line based on how you usually initialize it
|
val networkHelper = Injekt.get<NetworkHelper>()
|
||||||
val networkHelper = Injekt.get<NetworkHelper>()
|
val request = Request.Builder()
|
||||||
val request = Request.Builder()
|
.url(url)
|
||||||
.url(url)
|
.build()
|
||||||
.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 = getType(responseBody)
|
val subtitleType = when {
|
||||||
|
responseBody.contains("[Script Info]") -> SubtitleType.ASS
|
||||||
subtitleType
|
responseBody.contains("WEBVTT") -> SubtitleType.VTT
|
||||||
} else {
|
else -> SubtitleType.SRT
|
||||||
SubtitleType.UNKNOWN
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val uri = url.toUri()
|
|
||||||
val file = uri.toFile()
|
|
||||||
val fileBody = file.readText()
|
|
||||||
val subtitleType = getType(fileBody)
|
|
||||||
subtitleType
|
subtitleType
|
||||||
|
} else {
|
||||||
|
SubtitleType.UNKNOWN
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.log(e)
|
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
|
//actually downloads lol
|
||||||
@Deprecated("handled externally")
|
@Deprecated("handled externally")
|
||||||
suspend fun downloadSubtitle(
|
suspend fun downloadSubtitle(
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.media.anime
|
||||||
|
|
||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
import android.app.PictureInPictureUiState
|
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
|
||||||
import android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
|
import android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
|
||||||
import android.media.AudioManager.STREAM_MUSIC
|
import android.media.AudioManager.STREAM_MUSIC
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
|
@ -73,7 +75,9 @@ import androidx.media3.common.TrackSelectionOverride
|
||||||
import androidx.media3.common.Tracks
|
import androidx.media3.common.Tracks
|
||||||
import androidx.media3.common.text.CueGroup
|
import androidx.media3.common.text.CueGroup
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.datasource.DataSource
|
||||||
import androidx.media3.datasource.DefaultDataSource
|
import androidx.media3.datasource.DefaultDataSource
|
||||||
|
import androidx.media3.datasource.HttpDataSource
|
||||||
import androidx.media3.datasource.cache.CacheDataSource
|
import androidx.media3.datasource.cache.CacheDataSource
|
||||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
|
@ -171,10 +175,10 @@ import java.util.Timer
|
||||||
import java.util.TimerTask
|
import java.util.TimerTask
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.collections.set
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@ -1308,7 +1312,7 @@ class ExoplayerView :
|
||||||
setTitle(R.string.speed)
|
setTitle(R.string.speed)
|
||||||
singleChoiceItems(speedsName, curSpeed) { i ->
|
singleChoiceItems(speedsName, curSpeed) { i ->
|
||||||
PrefManager.setCustomVal("${media.id}_speed", i)
|
PrefManager.setCustomVal("${media.id}_speed", i)
|
||||||
speed = speeds.getOrNull(i) ?: 1f
|
speed = speeds[i]
|
||||||
curSpeed = i
|
curSpeed = i
|
||||||
playbackParameters = PlaybackParameters(speed)
|
playbackParameters = PlaybackParameters(speed)
|
||||||
exoPlayer.playbackParameters = playbackParameters
|
exoPlayer.playbackParameters = playbackParameters
|
||||||
|
@ -1358,7 +1362,7 @@ class ExoplayerView :
|
||||||
val showProgressDialog =
|
val showProgressDialog =
|
||||||
if (PrefManager.getVal(PrefName.AskIndividualPlayer)) {
|
if (PrefManager.getVal(PrefName.AskIndividualPlayer)) {
|
||||||
PrefManager.getCustomVal(
|
PrefManager.getCustomVal(
|
||||||
"${media.id}_progressDialog",
|
"${media.id}_ProgressDialog",
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1380,7 +1384,7 @@ class ExoplayerView :
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
setPosButton(R.string.yes) {
|
setPosButton(R.string.yes) {
|
||||||
PrefManager.setCustomVal(
|
PrefManager.setCustomVal(
|
||||||
"${media.id}_progressDialog",
|
"${media.id}_ProgressDialog",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
PrefManager.setCustomVal(
|
PrefManager.setCustomVal(
|
||||||
|
@ -1391,7 +1395,7 @@ class ExoplayerView :
|
||||||
}
|
}
|
||||||
setNegButton(R.string.no) {
|
setNegButton(R.string.no) {
|
||||||
PrefManager.setCustomVal(
|
PrefManager.setCustomVal(
|
||||||
"${media.id}_progressDialog",
|
"${media.id}_ProgressDialog",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
PrefManager.setCustomVal(
|
PrefManager.setCustomVal(
|
||||||
|
@ -1605,27 +1609,29 @@ class ExoplayerView :
|
||||||
emptyList<MediaItem.SubtitleConfiguration>().toMutableList()
|
emptyList<MediaItem.SubtitleConfiguration>().toMutableList()
|
||||||
ext.subtitles.forEach { subtitle ->
|
ext.subtitles.forEach { subtitle ->
|
||||||
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle.file.url
|
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle.file.url
|
||||||
|
// var localFile: String? = null
|
||||||
if (subtitle.type == SubtitleType.UNKNOWN) {
|
if (subtitle.type == SubtitleType.UNKNOWN) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val type = SubtitleDownloader.loadSubtitleType(subtitleUrl)
|
val type = SubtitleDownloader.loadSubtitleType(subtitleUrl)
|
||||||
val fileUri = (subtitleUrl).toUri()
|
val fileUri = Uri.parse(subtitleUrl)
|
||||||
sub +=
|
sub +=
|
||||||
MediaItem.SubtitleConfiguration
|
MediaItem.SubtitleConfiguration
|
||||||
.Builder(fileUri)
|
.Builder(fileUri)
|
||||||
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||||
.setMimeType(
|
.setMimeType(
|
||||||
when (type) {
|
when (type) {
|
||||||
SubtitleType.VTT -> MimeTypes.TEXT_VTT
|
SubtitleType.VTT -> MimeTypes.TEXT_SSA
|
||||||
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
SubtitleType.ASS -> MimeTypes.TEXT_SSA
|
||||||
SubtitleType.SRT -> MimeTypes.APPLICATION_SUBRIP
|
SubtitleType.SRT -> MimeTypes.TEXT_SSA
|
||||||
else -> MimeTypes.TEXT_UNKNOWN
|
else -> MimeTypes.TEXT_SSA
|
||||||
},
|
},
|
||||||
).setId("69")
|
).setId("69")
|
||||||
.setLanguage(subtitle.language)
|
.setLanguage(subtitle.language)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
println("sub: $sub")
|
||||||
} else {
|
} else {
|
||||||
val subUri = subtitleUrl.toUri()
|
val subUri = Uri.parse(subtitleUrl)
|
||||||
sub +=
|
sub +=
|
||||||
MediaItem.SubtitleConfiguration
|
MediaItem.SubtitleConfiguration
|
||||||
.Builder(subUri)
|
.Builder(subUri)
|
||||||
|
@ -1655,18 +1661,27 @@ class ExoplayerView :
|
||||||
followRedirects(true)
|
followRedirects(true)
|
||||||
followSslRedirects(true)
|
followSslRedirects(true)
|
||||||
}.build()
|
}.build()
|
||||||
val httpDataSourceFactory =
|
val dataSourceFactory =
|
||||||
OkHttpDataSource.Factory(httpClient).apply {
|
DataSource.Factory {
|
||||||
setDefaultRequestProperties(defaultHeaders)
|
val dataSource: HttpDataSource =
|
||||||
video?.file?.headers?.let {
|
OkHttpDataSource.Factory(httpClient).createDataSource()
|
||||||
setDefaultRequestProperties(it)
|
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 =
|
cacheFactory =
|
||||||
CacheDataSource.Factory().apply {
|
CacheDataSource.Factory().apply {
|
||||||
setCache(VideoCache.getInstance(this@ExoplayerView))
|
setCache(VideoCache.getInstance(this@ExoplayerView))
|
||||||
setUpstreamDataSourceFactory(defaultDataSourceFactory)
|
if (ext.server.offline) {
|
||||||
|
setUpstreamDataSourceFactory(dafuckDataSourceFactory)
|
||||||
|
} else {
|
||||||
|
setUpstreamDataSourceFactory(dataSourceFactory)
|
||||||
|
}
|
||||||
setCacheWriteDataSinkFactory(null)
|
setCacheWriteDataSinkFactory(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1928,7 +1943,7 @@ class ExoplayerView :
|
||||||
if (PrefManager.getVal<Boolean>(PrefName.TextviewSubtitles)) {
|
if (PrefManager.getVal<Boolean>(PrefName.TextviewSubtitles)) {
|
||||||
exoSubtitleView.visibility = View.GONE
|
exoSubtitleView.visibility = View.GONE
|
||||||
customSubtitleView.visibility = View.VISIBLE
|
customSubtitleView.visibility = View.VISIBLE
|
||||||
val newCues = cueGroup.cues.map { it.text.toString() ?: "" }
|
val newCues = cueGroup.cues.map { it.text.toString() }
|
||||||
|
|
||||||
if (newCues.isEmpty()) {
|
if (newCues.isEmpty()) {
|
||||||
customSubtitleView.text = ""
|
customSubtitleView.text = ""
|
||||||
|
@ -2488,7 +2503,7 @@ class ExoplayerView :
|
||||||
val videoURL = video?.file?.url ?: return
|
val videoURL = video?.file?.url ?: return
|
||||||
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle!!.file.url
|
val subtitleUrl = if (!hasExtSubtitles) video!!.file.url else subtitle!!.file.url
|
||||||
val shareVideo = Intent(Intent.ACTION_VIEW)
|
val shareVideo = Intent(Intent.ACTION_VIEW)
|
||||||
shareVideo.setDataAndType(videoURL.toUri(), "video/*")
|
shareVideo.setDataAndType(Uri.parse(videoURL), "video/*")
|
||||||
shareVideo.setPackage("com.instantbits.cast.webvideo")
|
shareVideo.setPackage("com.instantbits.cast.webvideo")
|
||||||
if (subtitle != null) shareVideo.putExtra("subtitle", subtitleUrl)
|
if (subtitle != null) shareVideo.putExtra("subtitle", subtitleUrl)
|
||||||
shareVideo.putExtra(
|
shareVideo.putExtra(
|
||||||
|
@ -2510,7 +2525,7 @@ class ExoplayerView :
|
||||||
} catch (ex: ActivityNotFoundException) {
|
} catch (ex: ActivityNotFoundException) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
val uriString = "market://details?id=com.instantbits.cast.webvideo"
|
val uriString = "market://details?id=com.instantbits.cast.webvideo"
|
||||||
intent.data = uriString.toUri()
|
intent.data = Uri.parse(uriString)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ 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(
|
||||||
|
@ -77,7 +76,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 =
|
||||||
|
@ -87,20 +86,12 @@ 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}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,6 @@ 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
|
||||||
|
@ -233,35 +232,25 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun multiDownload(n: Int) {
|
fun multiDownload(n: Int) {
|
||||||
lifecycleScope.launch {
|
// Get last viewed chapter
|
||||||
// Get the last viewed chapter
|
val selected = media.userProgress
|
||||||
val selected = media.userProgress ?: 0
|
val chapters = media.manga?.chapters?.values?.toList()
|
||||||
val chapters = media.manga?.chapters?.values?.toList()
|
// Filter by selected language
|
||||||
// Ensure chapters are available in the extensions
|
val progressChapterIndex = (chapters?.indexOfFirst {
|
||||||
if (chapters.isNullOrEmpty() || n < 1) return@launch
|
MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected
|
||||||
// Find the index of the last viewed chapter
|
} ?: 0) + 1
|
||||||
val progressChapterIndex = (chapters.indexOfFirst {
|
|
||||||
MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected
|
if (progressChapterIndex < 0 || n < 1 || chapters == null) return
|
||||||
} + 1).coerceAtLeast(0)
|
|
||||||
// Calculate the end value for the range of chapters to download
|
// Calculate the end index
|
||||||
val endIndex = (progressChapterIndex + n).coerceAtMost(chapters.size)
|
val endIndex = minOf(progressChapterIndex + n, chapters.size)
|
||||||
// Get the list of chapters to download
|
|
||||||
val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex)
|
// Make sure there are enough chapters
|
||||||
// Trigger the download for each chapter sequentially
|
val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex)
|
||||||
for (chapter in chaptersToDownload) {
|
|
||||||
try {
|
|
||||||
downloadChapterSequentially(chapter)
|
for (chapter in chaptersToDownload) {
|
||||||
} 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ 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),
|
||||||
|
|
|
@ -226,18 +226,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
|
||||||
?: return emptyList())
|
?: return emptyList())
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
// TODO(1.6): Remove else block when dropping support for ext lib <1.6
|
val videos = source.getVideoList(sEpisode)
|
||||||
if ((source as AnimeHttpSource).javaClass.declaredMethods.any { it.name == "getHosterList" }){
|
videos.map { videoToVideoServer(it) }
|
||||||
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()
|
||||||
|
@ -586,7 +576,7 @@ class VideoServerPassthrough(private val videoServer: VideoServer) : VideoExtrac
|
||||||
number,
|
number,
|
||||||
format!!,
|
format!!,
|
||||||
FileUrl(videoUrl, headersMap),
|
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 {
|
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)
|
||||||
|
|
|
@ -193,7 +193,8 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
PrefManager.setVal(PrefName.OverridePassword, true)
|
PrefManager.setVal(PrefName.OverridePassword, true)
|
||||||
}
|
}
|
||||||
val password = view.passwordInput.text.toString()
|
val password = view.passwordInput.text.toString()
|
||||||
val confirmPassword = view.confirmPasswordInput.text.toString()
|
val confirmPassword =
|
||||||
|
view.confirmPasswordInput.text.toString()
|
||||||
if (password == confirmPassword && password.isNotEmpty()) {
|
if (password == confirmPassword && password.isNotEmpty()) {
|
||||||
PrefManager.setVal(PrefName.AppPassword, password)
|
PrefManager.setVal(PrefName.AppPassword, password)
|
||||||
if (view.biometricCheckbox.isChecked) {
|
if (view.biometricCheckbox.isChecked) {
|
||||||
|
@ -201,11 +202,13 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
BiometricManager
|
BiometricManager
|
||||||
.from(applicationContext)
|
.from(applicationContext)
|
||||||
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ==
|
.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) ==
|
||||||
BiometricManager.BIOMETRIC_SUCCESS
|
BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
|
||||||
if (canBiometricPrompt) {
|
if (canBiometricPrompt) {
|
||||||
val biometricPrompt =
|
val biometricPrompt =
|
||||||
BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ ->
|
BiometricPromptUtils.createBiometricPrompt(
|
||||||
|
this@SettingsCommonActivity
|
||||||
|
) { _ ->
|
||||||
val token = UUID.randomUUID().toString()
|
val token = UUID.randomUUID().toString()
|
||||||
PrefManager.setVal(
|
PrefManager.setVal(
|
||||||
PrefName.BiometricToken,
|
PrefName.BiometricToken,
|
||||||
|
@ -235,12 +238,14 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
setOnShowListener {
|
setOnShowListener {
|
||||||
view.passwordInput.requestFocus()
|
view.passwordInput.requestFocus()
|
||||||
val canAuthenticate =
|
val canAuthenticate =
|
||||||
BiometricManager.from(applicationContext).canAuthenticate(
|
BiometricManager.from(applicationContext)
|
||||||
BiometricManager.Authenticators.BIOMETRIC_WEAK,
|
.canAuthenticate(
|
||||||
) == BiometricManager.BIOMETRIC_SUCCESS
|
BiometricManager.Authenticators.BIOMETRIC_WEAK,
|
||||||
|
) == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
view.biometricCheckbox.isVisible = canAuthenticate
|
view.biometricCheckbox.isVisible = canAuthenticate
|
||||||
view.biometricCheckbox.isChecked =
|
view.biometricCheckbox.isChecked =
|
||||||
PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty()
|
PrefManager.getVal(PrefName.BiometricToken, "")
|
||||||
|
.isNotEmpty()
|
||||||
view.forgotPasswordCheckbox.isChecked =
|
view.forgotPasswordCheckbox.isChecked =
|
||||||
PrefManager.getVal(PrefName.OverridePassword)
|
PrefManager.getVal(PrefName.OverridePassword)
|
||||||
}
|
}
|
||||||
|
@ -314,7 +319,8 @@ class SettingsCommonActivity : AppCompatActivity() {
|
||||||
setTitle(R.string.change_download_location)
|
setTitle(R.string.change_download_location)
|
||||||
setMessage(R.string.download_location_msg)
|
setMessage(R.string.download_location_msg)
|
||||||
setPosButton(R.string.ok) {
|
setPosButton(R.string.ok) {
|
||||||
val oldUri = PrefManager.getVal<String>(PrefName.DownloadsDir)
|
val oldUri =
|
||||||
|
PrefManager.getVal<String>(PrefName.DownloadsDir)
|
||||||
launcher.registerForCallback { success ->
|
launcher.registerForCallback { success ->
|
||||||
if (success) {
|
if (success) {
|
||||||
toast(getString(R.string.please_wait))
|
toast(getString(R.string.please_wait))
|
||||||
|
|
|
@ -82,9 +82,18 @@ class SettingsNotificationActivity : AppCompatActivity() {
|
||||||
setTitle(R.string.subscriptions_checking_time)
|
setTitle(R.string.subscriptions_checking_time)
|
||||||
singleChoiceItems(timeNames, curTime) { i ->
|
singleChoiceItems(timeNames, curTime) { i ->
|
||||||
curTime = i
|
curTime = i
|
||||||
it.settingsTitle.text = getString(R.string.subscriptions_checking_time_s, timeNames[i])
|
it.settingsTitle.text = getString(
|
||||||
PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime)
|
R.string.subscriptions_checking_time_s,
|
||||||
TaskScheduler.create(context, PrefManager.getVal(PrefName.UseAlarmManager)).scheduleAllTasks(context)
|
timeNames[i]
|
||||||
|
)
|
||||||
|
PrefManager.setVal(
|
||||||
|
PrefName.SubscriptionNotificationInterval,
|
||||||
|
curTime
|
||||||
|
)
|
||||||
|
TaskScheduler.create(
|
||||||
|
context,
|
||||||
|
PrefManager.getVal(PrefName.UseAlarmManager)
|
||||||
|
).scheduleAllTasks(context)
|
||||||
}
|
}
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
@ -120,21 +129,22 @@ class SettingsNotificationActivity : AppCompatActivity() {
|
||||||
.toMutableSet()
|
.toMutableSet()
|
||||||
val selected = types.map { filteredTypes.contains(it) }.toBooleanArray()
|
val selected = types.map { filteredTypes.contains(it) }.toBooleanArray()
|
||||||
context.customAlertDialog().apply {
|
context.customAlertDialog().apply {
|
||||||
setTitle(R.string.anilist_notification_filters)
|
setTitle(R.string.anilist_notification_filters)
|
||||||
multiChoiceItems(
|
multiChoiceItems(
|
||||||
types.map { name ->
|
types.map { name ->
|
||||||
name.replace("_", " ").lowercase().replaceFirstChar {
|
name.replace("_", " ").lowercase().replaceFirstChar {
|
||||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||||
} }.toTypedArray(),
|
}
|
||||||
|
}.toTypedArray(),
|
||||||
selected
|
selected
|
||||||
) { updatedSelected ->
|
) { updatedSelected ->
|
||||||
types.forEachIndexed { index, type ->
|
types.forEachIndexed { index, type ->
|
||||||
if (updatedSelected[index]) {
|
if (updatedSelected[index]) {
|
||||||
filteredTypes.add(type)
|
filteredTypes.add(type)
|
||||||
} else {
|
} else {
|
||||||
filteredTypes.remove(type)
|
filteredTypes.remove(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes)
|
PrefManager.setVal(PrefName.AnilistFilteredTypes, filteredTypes)
|
||||||
}
|
}
|
||||||
show()
|
show()
|
||||||
|
@ -152,8 +162,8 @@ class SettingsNotificationActivity : AppCompatActivity() {
|
||||||
icon = R.drawable.ic_round_notifications_none_24,
|
icon = R.drawable.ic_round_notifications_none_24,
|
||||||
onClick = {
|
onClick = {
|
||||||
context.customAlertDialog().apply {
|
context.customAlertDialog().apply {
|
||||||
setTitle(R.string.subscriptions_checking_time)
|
setTitle(R.string.subscriptions_checking_time)
|
||||||
singleChoiceItems(
|
singleChoiceItems(
|
||||||
aItems.toTypedArray(),
|
aItems.toTypedArray(),
|
||||||
PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval)
|
PrefManager.getVal<Int>(PrefName.AnilistNotificationInterval)
|
||||||
) { i ->
|
) { i ->
|
||||||
|
@ -181,11 +191,11 @@ class SettingsNotificationActivity : AppCompatActivity() {
|
||||||
icon = R.drawable.ic_round_notifications_none_24,
|
icon = R.drawable.ic_round_notifications_none_24,
|
||||||
onClick = {
|
onClick = {
|
||||||
context.customAlertDialog().apply {
|
context.customAlertDialog().apply {
|
||||||
setTitle(R.string.subscriptions_checking_time)
|
setTitle(R.string.subscriptions_checking_time)
|
||||||
singleChoiceItems(
|
singleChoiceItems(
|
||||||
cItems.toTypedArray(),
|
cItems.toTypedArray(),
|
||||||
PrefManager.getVal<Int>(PrefName.CommentNotificationInterval)
|
PrefManager.getVal<Int>(PrefName.CommentNotificationInterval)
|
||||||
) { i ->
|
) { i ->
|
||||||
PrefManager.setVal(PrefName.CommentNotificationInterval, i)
|
PrefManager.setVal(PrefName.CommentNotificationInterval, i)
|
||||||
it.settingsTitle.text =
|
it.settingsTitle.text =
|
||||||
getString(
|
getString(
|
||||||
|
@ -225,9 +235,9 @@ class SettingsNotificationActivity : AppCompatActivity() {
|
||||||
switch = { isChecked, view ->
|
switch = { isChecked, view ->
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
context.customAlertDialog().apply {
|
context.customAlertDialog().apply {
|
||||||
setTitle(R.string.use_alarm_manager)
|
setTitle(R.string.use_alarm_manager)
|
||||||
setMessage(R.string.use_alarm_manager_confirm)
|
setMessage(R.string.use_alarm_manager_confirm)
|
||||||
setPosButton(R.string.use) {
|
setPosButton(R.string.use) {
|
||||||
PrefManager.setVal(PrefName.UseAlarmManager, true)
|
PrefManager.setVal(PrefName.UseAlarmManager, true)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) {
|
if (!(getSystemService(Context.ALARM_SERVICE) as AlarmManager).canScheduleExactAlarms()) {
|
||||||
|
|
|
@ -96,7 +96,8 @@ class SettingsThemeActivity : AppCompatActivity(), SimpleDialog.OnDialogResultLi
|
||||||
themeSwitcher.apply {
|
themeSwitcher.apply {
|
||||||
setText(themeText)
|
setText(themeText)
|
||||||
setAdapter(
|
setAdapter(
|
||||||
ArrayAdapter(context,
|
ArrayAdapter(
|
||||||
|
context,
|
||||||
R.layout.item_dropdown,
|
R.layout.item_dropdown,
|
||||||
ThemeManager.Companion.Theme.entries.map {
|
ThemeManager.Companion.Theme.entries.map {
|
||||||
it.theme.substring(
|
it.theme.substring(
|
||||||
|
|
|
@ -52,14 +52,15 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
groupedSubscriptions.forEach { (parserName, mediaList) ->
|
groupedSubscriptions.forEach { (parserName, mediaList) ->
|
||||||
adapter.add(SubscriptionSource(
|
adapter.add(
|
||||||
parserName,
|
SubscriptionSource(
|
||||||
mediaList.toMutableList(),
|
parserName,
|
||||||
adapter,
|
mediaList.toMutableList(),
|
||||||
getParserIcon(parserName)
|
adapter,
|
||||||
) { group ->
|
getParserIcon(parserName)
|
||||||
adapter.remove(group)
|
) { group ->
|
||||||
})
|
adapter.remove(group)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ package ani.dantotsu.util
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import android.view.WindowManager
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
|
||||||
|
@ -207,14 +205,8 @@ class AlertDialogBuilder(private val context: Context) {
|
||||||
onShow?.invoke()
|
onShow?.invoke()
|
||||||
}
|
}
|
||||||
dialog.window?.apply {
|
dialog.window?.apply {
|
||||||
setDimAmount(0.5f)
|
setDimAmount(0.8f)
|
||||||
attributes.windowAnimations = android.R.style.Animation_Dialog
|
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()
|
dialog.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.animesource
|
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.SAnime
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.animesource.model.Video
|
import eu.kanade.tachiyomi.animesource.model.Video
|
||||||
|
@ -49,25 +48,6 @@ interface AnimeSource {
|
||||||
return fetchEpisodeList(anime).awaitSingle()
|
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<Hoster> = 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<Video> = throw IllegalStateException("Not used")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of videos a episode has. Pages should be returned
|
* Get the list of videos a episode has. Pages should be returned
|
||||||
* in the expected order; the index is ignored.
|
* in the expected order; the index is ignored.
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.animesource.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SerializableVideo.Companion.serialize
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SerializableVideo.Companion.toVideoList
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
open class Hoster(
|
|
||||||
val hosterUrl: String = "",
|
|
||||||
val hosterName: String = "",
|
|
||||||
val videoList: List<Video>? = null,
|
|
||||||
val internalData: String = "",
|
|
||||||
) {
|
|
||||||
@Transient
|
|
||||||
@Volatile
|
|
||||||
var status: State = State.IDLE
|
|
||||||
|
|
||||||
enum class State {
|
|
||||||
IDLE,
|
|
||||||
LOADING,
|
|
||||||
READY,
|
|
||||||
ERROR,
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copy(
|
|
||||||
hosterUrl: String = this.hosterUrl,
|
|
||||||
hosterName: String = this.hosterName,
|
|
||||||
videoList: List<Video>? = this.videoList,
|
|
||||||
internalData: String = this.internalData,
|
|
||||||
): Hoster {
|
|
||||||
return Hoster(hosterUrl, hosterName, videoList, internalData)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val NO_HOSTER_LIST = "no_hoster_list"
|
|
||||||
|
|
||||||
fun List<Video>.toHosterList(): List<Hoster> {
|
|
||||||
return listOf(
|
|
||||||
Hoster(
|
|
||||||
hosterUrl = "",
|
|
||||||
hosterName = NO_HOSTER_LIST,
|
|
||||||
videoList = this,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class SerializableHoster(
|
|
||||||
val hosterUrl: String = "",
|
|
||||||
val hosterName: String = "",
|
|
||||||
val videoList: String? = null,
|
|
||||||
val internalData: String = "",
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun List<Hoster>.serialize(): String =
|
|
||||||
Json.encodeToString(
|
|
||||||
this.map { host ->
|
|
||||||
SerializableHoster(
|
|
||||||
host.hosterUrl,
|
|
||||||
host.hosterName,
|
|
||||||
host.videoList?.serialize(),
|
|
||||||
host.internalData,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
fun String.toHosterList(): List<Hoster> =
|
|
||||||
Json.decodeFromString<List<SerializableHoster>>(this)
|
|
||||||
.map { sHost ->
|
|
||||||
Hoster(
|
|
||||||
sHost.hosterUrl,
|
|
||||||
sHost.hosterName,
|
|
||||||
sHost.videoList?.toVideoList(),
|
|
||||||
sHost.internalData,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +1,31 @@
|
||||||
package eu.kanade.tachiyomi.animesource.model
|
package eu.kanade.tachiyomi.animesource.model
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import kotlinx.serialization.encodeToString
|
import eu.kanade.tachiyomi.network.ProgressListener
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
|
import rx.subjects.Subject
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
|
||||||
data class Track(val url: String, val lang: String) : Serializable
|
data class Track(val url: String, val lang: String) : Serializable
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
|
||||||
enum class ChapterType {
|
|
||||||
Opening,
|
|
||||||
Ending,
|
|
||||||
Recap,
|
|
||||||
MixedOp,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
|
||||||
data class TimeStamp(
|
|
||||||
val start: Double,
|
|
||||||
val end: Double,
|
|
||||||
val name: String,
|
|
||||||
val type: ChapterType = ChapterType.Other,
|
|
||||||
)
|
|
||||||
|
|
||||||
open class Video(
|
open class Video(
|
||||||
var videoUrl: String = "",
|
val url: String = "",
|
||||||
val videoTitle: String = "",
|
val quality: String = "",
|
||||||
val resolution: Int? = null,
|
var videoUrl: String? = null,
|
||||||
val bitrate: Int? = null,
|
headers: Headers? = null,
|
||||||
val headers: Headers? = null,
|
// "url", "language-label-2", "url2", "language-label-2"
|
||||||
val preferred: Boolean = false,
|
|
||||||
val subtitleTracks: List<Track> = emptyList(),
|
val subtitleTracks: List<Track> = emptyList(),
|
||||||
val audioTracks: List<Track> = emptyList(),
|
val audioTracks: List<Track> = emptyList(),
|
||||||
val timestamps: List<TimeStamp> = emptyList(),
|
) : Serializable, ProgressListener {
|
||||||
val internalData: String = "",
|
|
||||||
val initialized: Boolean = false,
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
|
||||||
val videoPageUrl: String = "",
|
|
||||||
) {
|
|
||||||
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
@Transient
|
||||||
@Deprecated("Use videoTitle instead", ReplaceWith("videoTitle"))
|
var headers: Headers? = headers
|
||||||
val quality: String
|
|
||||||
get() = videoTitle
|
|
||||||
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
|
||||||
@Deprecated("Use videoPageUrl instead", ReplaceWith("videoPageUrl"))
|
|
||||||
val url: String
|
|
||||||
get() = videoPageUrl
|
|
||||||
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
|
||||||
constructor(
|
|
||||||
url: String,
|
|
||||||
quality: String,
|
|
||||||
videoUrl: String?,
|
|
||||||
headers: Headers? = null,
|
|
||||||
subtitleTracks: List<Track> = emptyList(),
|
|
||||||
audioTracks: List<Track> = emptyList(),
|
|
||||||
) : this(
|
|
||||||
videoPageUrl = url,
|
|
||||||
videoTitle = quality,
|
|
||||||
videoUrl = videoUrl ?: "null",
|
|
||||||
headers = headers,
|
|
||||||
subtitleTracks = subtitleTracks,
|
|
||||||
audioTracks = audioTracks,
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
|
||||||
constructor(
|
|
||||||
videoUrl: String = "",
|
|
||||||
videoTitle: String = "",
|
|
||||||
resolution: Int? = null,
|
|
||||||
bitrate: Int? = null,
|
|
||||||
headers: Headers? = null,
|
|
||||||
preferred: Boolean = false,
|
|
||||||
subtitleTracks: List<Track> = emptyList(),
|
|
||||||
audioTracks: List<Track> = emptyList(),
|
|
||||||
timestamps: List<TimeStamp> = emptyList(),
|
|
||||||
internalData: String = "",
|
|
||||||
) : this(
|
|
||||||
videoUrl = videoUrl,
|
|
||||||
videoTitle = videoTitle,
|
|
||||||
resolution = resolution,
|
|
||||||
bitrate = bitrate,
|
|
||||||
headers = headers,
|
|
||||||
preferred = preferred,
|
|
||||||
subtitleTracks = subtitleTracks,
|
|
||||||
audioTracks = audioTracks,
|
|
||||||
timestamps = timestamps,
|
|
||||||
internalData = internalData,
|
|
||||||
videoPageUrl = "",
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
constructor(
|
constructor(
|
||||||
url: String,
|
url: String,
|
||||||
|
@ -108,132 +38,83 @@ open class Video(
|
||||||
@Transient
|
@Transient
|
||||||
@Volatile
|
@Volatile
|
||||||
var status: State = State.QUEUE
|
var status: State = State.QUEUE
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private val _progressFlow = MutableStateFlow(0)
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val progressFlow = _progressFlow.asStateFlow()
|
||||||
|
var progress: Int
|
||||||
|
get() = _progressFlow.value
|
||||||
set(value) {
|
set(value) {
|
||||||
|
_progressFlow.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
@Volatile
|
||||||
|
var totalBytesDownloaded: Long = 0L
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
@Volatile
|
||||||
|
var totalContentLength: Long = 0L
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
@Volatile
|
||||||
|
var bytesDownloaded: Long = 0L
|
||||||
|
set(value) {
|
||||||
|
totalBytesDownloaded += if (value < field) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
value - field
|
||||||
|
}
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copy(
|
@Transient
|
||||||
videoUrl: String = this.videoUrl,
|
var progressSubject: Subject<State, State>? = null
|
||||||
videoTitle: String = this.videoTitle,
|
|
||||||
resolution: Int? = this.resolution,
|
|
||||||
bitrate: Int? = this.bitrate,
|
|
||||||
headers: Headers? = this.headers,
|
|
||||||
preferred: Boolean = this.preferred,
|
|
||||||
subtitleTracks: List<Track> = this.subtitleTracks,
|
|
||||||
audioTracks: List<Track> = this.audioTracks,
|
|
||||||
timestamps: List<TimeStamp> = this.timestamps,
|
|
||||||
internalData: String = this.internalData,
|
|
||||||
): Video {
|
|
||||||
return Video(
|
|
||||||
videoUrl = videoUrl,
|
|
||||||
videoTitle = videoTitle,
|
|
||||||
resolution = resolution,
|
|
||||||
bitrate = bitrate,
|
|
||||||
headers = headers,
|
|
||||||
preferred = preferred,
|
|
||||||
subtitleTracks = subtitleTracks,
|
|
||||||
audioTracks = audioTracks,
|
|
||||||
timestamps = timestamps,
|
|
||||||
internalData = internalData,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copy(
|
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||||
videoUrl: String = this.videoUrl,
|
bytesDownloaded = bytesRead
|
||||||
videoTitle: String = this.videoTitle,
|
if (contentLength > totalContentLength) {
|
||||||
resolution: Int? = this.resolution,
|
totalContentLength = contentLength
|
||||||
bitrate: Int? = this.bitrate,
|
}
|
||||||
headers: Headers? = this.headers,
|
val newProgress = if (totalContentLength > 0) {
|
||||||
preferred: Boolean = this.preferred,
|
(100 * totalBytesDownloaded / totalContentLength).toInt()
|
||||||
subtitleTracks: List<Track> = this.subtitleTracks,
|
} else {
|
||||||
audioTracks: List<Track> = this.audioTracks,
|
-1
|
||||||
timestamps: List<TimeStamp> = this.timestamps,
|
}
|
||||||
internalData: String = this.internalData,
|
if (progress != newProgress) progress = newProgress
|
||||||
initialized: Boolean = this.initialized,
|
|
||||||
videoPageUrl: String = this.videoPageUrl,
|
|
||||||
): Video {
|
|
||||||
return Video(
|
|
||||||
videoUrl = videoUrl,
|
|
||||||
videoTitle = videoTitle,
|
|
||||||
resolution = resolution,
|
|
||||||
bitrate = bitrate,
|
|
||||||
headers = headers,
|
|
||||||
preferred = preferred,
|
|
||||||
subtitleTracks = subtitleTracks,
|
|
||||||
audioTracks = audioTracks,
|
|
||||||
timestamps = timestamps,
|
|
||||||
internalData = internalData,
|
|
||||||
initialized = initialized,
|
|
||||||
videoPageUrl = videoPageUrl,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
QUEUE,
|
QUEUE,
|
||||||
LOAD_VIDEO,
|
LOAD_VIDEO,
|
||||||
|
DOWNLOAD_IMAGE,
|
||||||
READY,
|
READY,
|
||||||
ERROR,
|
ERROR,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@Throws(IOException::class)
|
||||||
data class SerializableVideo(
|
private fun writeObject(out: ObjectOutputStream) {
|
||||||
val videoUrl: String = "",
|
out.defaultWriteObject()
|
||||||
val videoTitle: String = "",
|
val headersMap: Map<String, List<String>> = headers?.toMultimap() ?: emptyMap()
|
||||||
val resolution: Int? = null,
|
out.writeObject(headersMap)
|
||||||
val bitrate: Int? = null,
|
}
|
||||||
val headers: List<Pair<String, String>>? = null,
|
|
||||||
val preferred: Boolean = false,
|
|
||||||
val subtitleTracks: List<Track> = emptyList(),
|
|
||||||
val audioTracks: List<Track> = emptyList(),
|
|
||||||
val timestamps: List<TimeStamp> = emptyList(),
|
|
||||||
val internalData: String = "",
|
|
||||||
val initialized: Boolean = false,
|
|
||||||
// TODO(1.6): Remove after ext lib bump
|
|
||||||
val videoPageUrl: String = "",
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun List<Video>.serialize(): String =
|
@Throws(IOException::class, ClassNotFoundException::class)
|
||||||
Json.encodeToString(
|
private fun readObject(input: ObjectInputStream) {
|
||||||
this.map { vid ->
|
input.defaultReadObject()
|
||||||
SerializableVideo(
|
val headersMap = input.readObject() as? Map<String, List<String>>
|
||||||
vid.videoUrl,
|
headers = headersMap?.let { map ->
|
||||||
vid.videoTitle,
|
val builder = Headers.Builder()
|
||||||
vid.resolution,
|
for ((key, values) in map) {
|
||||||
vid.bitrate,
|
for (value in values) {
|
||||||
vid.headers?.toList(),
|
builder.add(key, value)
|
||||||
vid.preferred,
|
|
||||||
vid.subtitleTracks,
|
|
||||||
vid.audioTracks,
|
|
||||||
vid.timestamps,
|
|
||||||
vid.internalData,
|
|
||||||
vid.initialized,
|
|
||||||
vid.videoPageUrl,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
fun String.toVideoList(): List<Video> =
|
|
||||||
Json.decodeFromString<List<SerializableVideo>>(this)
|
|
||||||
.map { sVid ->
|
|
||||||
Video(
|
|
||||||
sVid.videoUrl,
|
|
||||||
sVid.videoTitle,
|
|
||||||
sVid.resolution,
|
|
||||||
sVid.bitrate,
|
|
||||||
sVid.headers
|
|
||||||
?.flatMap { it.toList() }
|
|
||||||
?.let { Headers.headersOf(*it.toTypedArray()) },
|
|
||||||
sVid.preferred,
|
|
||||||
sVid.subtitleTracks,
|
|
||||||
sVid.audioTracks,
|
|
||||||
sVid.timestamps,
|
|
||||||
sVid.internalData,
|
|
||||||
sVid.initialized,
|
|
||||||
sVid.videoPageUrl,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.extension.api
|
package eu.kanade.tachiyomi.extension.api
|
||||||
|
|
||||||
import ani.dantotsu.asyncMap
|
|
||||||
import ani.dantotsu.parsers.novel.AvailableNovelSources
|
import ani.dantotsu.parsers.novel.AvailableNovelSources
|
||||||
import ani.dantotsu.parsers.novel.NovelExtension
|
import ani.dantotsu.parsers.novel.NovelExtension
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
|
@ -68,7 +67,7 @@ internal class ExtensionGithubApi {
|
||||||
val repos =
|
val repos =
|
||||||
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).toMutableList()
|
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).toMutableList()
|
||||||
|
|
||||||
repos.asyncMap {
|
repos.forEach {
|
||||||
val repoUrl = if (it.contains("index.min.json")) {
|
val repoUrl = if (it.contains("index.min.json")) {
|
||||||
it
|
it
|
||||||
} else {
|
} else {
|
||||||
|
@ -156,7 +155,7 @@ internal class ExtensionGithubApi {
|
||||||
val repos =
|
val repos =
|
||||||
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).toMutableList()
|
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).toMutableList()
|
||||||
|
|
||||||
repos.asyncMap {
|
repos.forEach {
|
||||||
val repoUrl = if (it.contains("index.min.json")) {
|
val repoUrl = if (it.contains("index.min.json")) {
|
||||||
it
|
it
|
||||||
} else {
|
} else {
|
||||||
|
@ -208,7 +207,7 @@ internal class ExtensionGithubApi {
|
||||||
val repos =
|
val repos =
|
||||||
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos).toMutableList()
|
PrefManager.getVal<Set<String>>(PrefName.NovelExtensionRepos).toMutableList()
|
||||||
|
|
||||||
repos.asyncMap {
|
repos.forEach {
|
||||||
val repoUrl = if (it.contains("index.min.json")) {
|
val repoUrl = if (it.contains("index.min.json")) {
|
||||||
it
|
it
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -419,19 +419,13 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/mediaBottomBarContainer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:layout_gravity="center_horizontal|bottom">
|
|
||||||
|
|
||||||
<nl.joery.animatedbottombar.AnimatedBottomBar
|
<nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
android:id="@+id/mediaBottomBar"
|
android:id="@+id/mediaBottomBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
|
android:layout_weight="0"
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
app:abb_animationInterpolator="@anim/over_shoot"
|
app:abb_animationInterpolator="@anim/over_shoot"
|
||||||
|
@ -444,6 +438,5 @@
|
||||||
app:itemTextAppearanceActive="@style/NavBarText"
|
app:itemTextAppearanceActive="@style/NavBarText"
|
||||||
app:itemTextAppearanceInactive="@style/NavBarText"
|
app:itemTextAppearanceInactive="@style/NavBarText"
|
||||||
app:itemTextColor="@color/tab_layout_icon" />
|
app:itemTextColor="@color/tab_layout_icon" />
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -353,18 +353,11 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/mediaBottomBarContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorSurface"
|
|
||||||
android:layout_gravity="center_horizontal|bottom">
|
|
||||||
|
|
||||||
<nl.joery.animatedbottombar.AnimatedBottomBar
|
<nl.joery.animatedbottombar.AnimatedBottomBar
|
||||||
android:id="@+id/mediaBottomBar"
|
android:id="@+id/mediaBottomBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal|top"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
app:abb_animationInterpolator="@anim/over_shoot"
|
app:abb_animationInterpolator="@anim/over_shoot"
|
||||||
|
@ -378,7 +371,6 @@
|
||||||
app:itemTextAppearanceActive="@style/NavBarText"
|
app:itemTextAppearanceActive="@style/NavBarText"
|
||||||
app:itemTextAppearanceInactive="@style/NavBarText"
|
app:itemTextAppearanceInactive="@style/NavBarText"
|
||||||
app:itemTextColor="@color/tab_layout_icon" />
|
app:itemTextColor="@color/tab_layout_icon" />
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -1102,10 +1102,10 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="32dp"
|
android:layout_marginHorizontal="32dp"
|
||||||
android:stepSize="1.0"
|
android:stepSize="5.0"
|
||||||
android:value="10.0"
|
android:value="15.0"
|
||||||
android:valueFrom="1.0"
|
android:valueFrom="5.0"
|
||||||
android:valueTo="50.0"
|
android:valueTo="45.0"
|
||||||
app:labelBehavior="floating"
|
app:labelBehavior="floating"
|
||||||
app:labelStyle="@style/fontTooltip"
|
app:labelStyle="@style/fontTooltip"
|
||||||
app:thumbColor="?attr/colorSecondary"
|
app:thumbColor="?attr/colorSecondary"
|
||||||
|
|
|
@ -12,7 +12,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.7.3'
|
classpath 'com.android.tools.build:gradle:8.9.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath "com.google.devtools.ksp:symbol-processing-api:$ksp_version"
|
classpath "com.google.devtools.ksp:symbol-processing-api:$ksp_version"
|
||||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
#Wed Aug 30 19:57:04 IST 2023
|
#Wed Aug 30 19:57:04 IST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue