various bugfixes

This commit is contained in:
Finnley Somdahl 2023-10-22 02:28:39 -05:00
parent 0b9f2bb019
commit dc959796e6
26 changed files with 685 additions and 289 deletions

View file

@ -2,6 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
@ -10,7 +17,8 @@
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- For background jobs --> <!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -47,7 +55,7 @@
android:theme="@style/Theme.Dantotsu" android:theme="@style/Theme.Dantotsu"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:ignore="AllowBackup" tools:ignore="AllowBackup"
> android:banner="@drawable/ic_banner_foreground">
<activity <activity
android:name="ani.dantotsu.media.novel.novelreader.NovelReaderActivity" android:name="ani.dantotsu.media.novel.novelreader.NovelReaderActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
@ -206,9 +214,12 @@
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.Main" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity> </activity>
<activity <activity
android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity" android:name="eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallActivity"

View file

@ -2,6 +2,7 @@ package ani.dantotsu.aniyomi.anime.custom
import android.app.Application import android.app.Application
import android.content.Context
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
@ -28,6 +29,9 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { MangaExtensionManager(app) } addSingletonFactory { MangaExtensionManager(app) }
val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
addSingleton(sharedPreferences)
addSingletonFactory { addSingletonFactory {
Json { Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true

View file

@ -1,21 +1,30 @@
package ani.dantotsu.connections.discord package ani.dantotsu.connections.discord
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application.getProcessName
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.connections.discord.Discord.saveToken import ani.dantotsu.connections.discord.Discord.saveToken
import ani.dantotsu.startMainActivity import ani.dantotsu.startMainActivity
class Login : AppCompatActivity() { class Login : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()
if (packageName != process) WebView.setDataDirectorySuffix(process)
}
setContentView(R.layout.activity_discord) setContentView(R.layout.activity_discord)
val webView = findViewById<WebView>(R.id.discordWebview) val webView = findViewById<WebView>(R.id.discordWebview)
webView.apply { webView.apply {
settings.javaScriptEnabled = true settings.javaScriptEnabled = true
settings.databaseEnabled = true settings.databaseEnabled = true

View file

@ -113,6 +113,10 @@ data class Media(
this.relation = mediaEdge.relationType?.toString() this.relation = mediaEdge.relationType?.toString()
} }
fun mainName() = nameMAL ?: name ?: nameRomaji fun mainName() = name ?: nameMAL ?: nameRomaji
fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji
} }
object MediaSingleton {
var media: Media? = null
}

View file

@ -1,6 +1,8 @@
package ani.dantotsu.media package ani.dantotsu.media
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@ -40,6 +42,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MediaDetailsViewModel : ViewModel() { class MediaDetailsViewModel : ViewModel() {
val scrolledToTop = MutableLiveData(true) val scrolledToTop = MutableLiveData(true)
@ -48,11 +52,13 @@ class MediaDetailsViewModel : ViewModel() {
saveData("$id-select", data, activity) saveData("$id-select", data, activity)
} }
fun loadSelected(media: Media): Selected { fun loadSelected(media: Media): Selected {
val sharedPreferences = Injekt.get<SharedPreferences>()
val data = loadData<Selected>("${media.id}-select") ?: Selected().let { val data = loadData<Selected>("${media.id}-select") ?: Selected().let {
it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) { it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) {
true -> loadData("settings_def_anime_source_s_r") ?: 0 true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0)
else -> loadData("settings_def_manga_source_s_r") ?: 0 else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0)
} }
it.preferDub = loadData("settings_prefer_dub") ?: false it.preferDub = loadData("settings_prefer_dub") ?: false
saveSelected(media.id, it) saveSelected(media.id, it)

View file

@ -9,6 +9,8 @@ import ani.dantotsu.databinding.ItemEpisodeCompactBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.setAnimation import ani.dantotsu.setAnimation
import ani.dantotsu.connections.updateProgress import ani.dantotsu.connections.updateProgress
import java.util.regex.Matcher
import java.util.regex.Pattern
class MangaChapterAdapter( class MangaChapterAdapter(
private var type: Int, private var type: Int,
@ -63,12 +65,12 @@ class MangaChapterAdapter(
val ep = arr[position] val ep = arr[position]
binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeNumber.text = ep.number
if (media.userProgress != null) { if (media.userProgress != null) {
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat())
binding.itemEpisodeViewedCover.visibility = View.VISIBLE binding.itemEpisodeViewedCover.visibility = View.VISIBLE
else { else {
binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeViewedCover.visibility = View.GONE
binding.itemEpisodeCont.setOnLongClickListener { binding.itemEpisodeCont.setOnLongClickListener {
updateProgress(media, ep.number) updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString())
true true
} }
} }
@ -91,14 +93,14 @@ class MangaChapterAdapter(
} else binding.itemChapterTitle.visibility = View.GONE } else binding.itemChapterTitle.visibility = View.GONE
if (media.userProgress != null) { if (media.userProgress != null) {
if ((ep.number.toFloatOrNull() ?: 9999f) <= media.userProgress!!.toFloat()) { if ((MangaNameAdapter.findChapterNumber(ep.number) ?: 9999f) <= media.userProgress!!.toFloat()) {
binding.itemEpisodeViewedCover.visibility = View.VISIBLE binding.itemEpisodeViewedCover.visibility = View.VISIBLE
binding.itemEpisodeViewed.visibility = View.VISIBLE binding.itemEpisodeViewed.visibility = View.VISIBLE
} else { } else {
binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeViewedCover.visibility = View.GONE
binding.itemEpisodeViewed.visibility = View.GONE binding.itemEpisodeViewed.visibility = View.GONE
binding.root.setOnLongClickListener { binding.root.setOnLongClickListener {
updateProgress(media, ep.number) updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString())
true true
} }
} }
@ -113,4 +115,6 @@ class MangaChapterAdapter(
fun updateType(t: Int) { fun updateType(t: Int) {
type = t type = t
} }
} }

View file

@ -0,0 +1,20 @@
package ani.dantotsu.media.manga
import java.util.regex.Matcher
import java.util.regex.Pattern
class MangaNameAdapter {
companion object {
fun findChapterNumber(text: String): Float? {
val regex = "(chapter|chap|ch|c)[\\s:.\\-]*([\\d]+\\.?[\\d]*)"
val pattern: Pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE)
val matcher: Matcher = pattern.matcher(text)
return if (matcher.find()) {
matcher.group(2)?.toFloat()
} else {
null
}
}
}
}

View file

@ -14,6 +14,7 @@ import ani.dantotsu.currActivity
import ani.dantotsu.databinding.BottomSheetSelectorBinding import ani.dantotsu.databinding.BottomSheetSelectorBinding
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.others.getSerialized import ani.dantotsu.others.getSerialized
import ani.dantotsu.tryWith import ani.dantotsu.tryWith
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -49,7 +50,8 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() {
activity?.runOnUiThread { activity?.runOnUiThread {
tryWith { dismiss() } tryWith { dismiss() }
if(launch) { if(launch) {
val intent = Intent(activity, MangaReaderActivity::class.java).apply { putExtra("media", m) } MediaSingleton.media = m
val intent = Intent(activity, MangaReaderActivity::class.java)//.apply { putExtra("media", m) }
activity.startActivity(intent) activity.startActivity(intent)
} }
} }

View file

@ -30,8 +30,10 @@ import ani.dantotsu.connections.updateProgress
import ani.dantotsu.databinding.ActivityMangaReaderBinding import ani.dantotsu.databinding.ActivityMangaReaderBinding
import ani.dantotsu.media.Media import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.MediaSingleton
import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaCache
import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.media.manga.MangaNameAdapter
import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.others.getSerialized import ani.dantotsu.others.getSerialized
import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.HMangaSources
@ -46,7 +48,12 @@ import ani.dantotsu.settings.UserInterfaceSettings
import com.alexvasilkov.gestures.views.GestureFrameLayout import com.alexvasilkov.gestures.views.GestureFrameLayout
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -164,10 +171,13 @@ class MangaReaderActivity : AppCompatActivity() {
media = if (model.getMedia().value == null) media = if (model.getMedia().value == null)
try { try {
(intent.getSerialized("media")) ?: return //(intent.getSerialized("media")) ?: return
MediaSingleton.media ?: return
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
return return
} finally {
MediaSingleton.media = null
} }
else model.getMedia().value ?: return else model.getMedia().value ?: return
model.setMedia(media) model.setMedia(media)
@ -180,6 +190,29 @@ class MangaReaderActivity : AppCompatActivity() {
model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources
binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE binding.mangaReaderSource.visibility = if (settings.showSource) View.VISIBLE else View.GONE
if(model.mangaReadSources!!.names.isEmpty()){
//try to reload sources
try {
if (media.isAdult) {
val mangaSources = MangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
}
model.mangaReadSources = mangaSources
}else{
val mangaSources = HMangaSources
val scope = lifecycleScope
scope.launch(Dispatchers.IO) {
mangaSources.init(Injekt.get<MangaExtensionManager>().installedExtensionsFlow)
}
model.mangaReadSources = mangaSources
}
}catch (e: Exception){
Firebase.crashlytics.recordException(e)
logError(e)
}
}
binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex] binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex]
binding.mangaReaderTitle.text = media.userPreferredName binding.mangaReaderTitle.text = media.userPreferredName
@ -677,7 +710,7 @@ class MangaReaderActivity : AppCompatActivity() {
progressDialog?.setCancelable(false) progressDialog?.setCancelable(false)
?.setPositiveButton(getString(R.string.yes)) { dialog, _ -> ?.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
saveData("${media.id}_save_progress", true) saveData("${media.id}_save_progress", true)
updateProgress(media, media.manga!!.selectedChapter!!) updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
dialog.dismiss() dialog.dismiss()
runnable.run() runnable.run()
} }
@ -689,7 +722,7 @@ class MangaReaderActivity : AppCompatActivity() {
progressDialog?.show() progressDialog?.show()
} else { } else {
if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true) if (loadData<Boolean>("${media.id}_save_progress") != false && if (media.isAdult) settings.updateForH else true)
updateProgress(media, media.manga!!.selectedChapter!!) updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString())
runnable.run() runnable.run()
} }
} else { } else {

View file

@ -171,6 +171,17 @@ abstract class AnimeParser : BaseParser() {
} }
} }
class EmptyAnimeParser: AnimeParser() {
override val name: String = "None"
override val saveName: String = "None"
override val isDubAvailableSeparately: Boolean = false
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> = emptyList()
override suspend fun loadVideoServers(episodeLink: String, extra: Map<String, String>?, sEpisode: SEpisode): List<VideoServer> = emptyList()
override suspend fun search(query: String): List<ShowResponse> = emptyList()
}
/** /**
* A class for containing Episode data of a particular parser * A class for containing Episode data of a particular parser
* **/ * **/

View file

@ -66,6 +66,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
override val saveName = extension.name override val saveName = extension.name
override val hostUrl = extension.sources.first().name override val hostUrl = extension.sources.first().name
override val isDubAvailableSeparately = false override val isDubAvailableSeparately = false
override val isNSFW = extension.isNsfw
override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> { override suspend fun loadEpisodes(animeLink: String, extra: Map<String, String>?, sAnime: SAnime): List<Episode> {
val source = extension.sources.first() val source = extension.sources.first()
if (source is AnimeCatalogueSource) { if (source is AnimeCatalogueSource) {
@ -176,6 +177,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
override val name = extension.name override val name = extension.name
override val saveName = extension.name override val saveName = extension.name
override val hostUrl = extension.sources.first().name override val hostUrl = extension.sources.first().name
override val isNSFW = extension.isNsfw
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> { override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> {
val source = extension.sources.first() as? CatalogueSource ?: return emptyList() val source = extension.sources.first() as? CatalogueSource ?: return emptyList()
@ -385,22 +387,22 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter { private fun SChapterToMangaChapter(sChapter: SChapter): MangaChapter {
val parsedChapterTitle = parseChapterTitle(sChapter.name) /*val parsedChapterTitle = parseChapterTitle(sChapter.name)
val number = if (sChapter.chapter_number.toInt() != -1){ val number = if (sChapter.chapter_number.toInt() != -1){
sChapter.chapter_number.toString() sChapter.chapter_number.toString()
} else if(parsedChapterTitle.first != null || parsedChapterTitle.second != null){ } else if(parsedChapterTitle.first != null || parsedChapterTitle.second != null){
(parsedChapterTitle.first ?: "") + "." + (parsedChapterTitle.second ?: "") (parsedChapterTitle.first ?: "") + "." + (parsedChapterTitle.second ?: "")
}else{ }else{
sChapter.name sChapter.name
} }*/
return MangaChapter( return MangaChapter(
number, sChapter.name,
sChapter.url, sChapter.url,
if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) { //if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) {
parsedChapterTitle.third // parsedChapterTitle.third
} else { //} else {
sChapter.name sChapter.name,
}, //},
null, null,
sChapter sChapter
) )

View file

@ -80,8 +80,8 @@ abstract class BaseParser {
} else { } else {
val romajiRatio = FuzzySearch.ratio(closestRomaji?.name ?: "", mediaObj.nameRomaji) val romajiRatio = FuzzySearch.ratio(closestRomaji?.name ?: "", mediaObj.nameRomaji)
val mainNameRatio = FuzzySearch.ratio(response.name, mediaObj.mainName()) val mainNameRatio = FuzzySearch.ratio(response.name, mediaObj.mainName())
logger("Fuzzy ratio for closest match in results: $mainNameRatio") logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name}")
logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio") logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name ?: "None"}")
if (romajiRatio > mainNameRatio) { if (romajiRatio > mainNameRatio) {
logger("RomajiResults has a closer match. Replacing response.") logger("RomajiResults has a closer match. Replacing response.")

View file

@ -11,9 +11,11 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
abstract class WatchSources : BaseSources() { abstract class WatchSources : BaseSources() {
override operator fun get(i: Int): AnimeParser { override operator fun get(i: Int): AnimeParser {
return (list.getOrNull(i)?:list[0]).get.value as AnimeParser return (list.getOrNull(i) ?: list.firstOrNull())?.get?.value as? AnimeParser
?: EmptyAnimeParser()
} }
suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> { suspend fun loadEpisodesFromMedia(i: Int, media: Media): MutableMap<String, Episode> {
return tryWithSuspend(true) { return tryWithSuspend(true) {
val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf() val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf()
@ -40,7 +42,8 @@ abstract class WatchSources : BaseSources() {
abstract class MangaReadSources : BaseSources() { abstract class MangaReadSources : BaseSources() {
override operator fun get(i: Int): MangaParser { override operator fun get(i: Int): MangaParser {
return (list.getOrNull(i)?:list[0]).get.value as MangaParser return (list.getOrNull(i)?:list.firstOrNull())?.get?.value as? MangaParser
?: EmptyMangaParser()
} }
suspend fun loadChaptersFromMedia(i: Int, media: Media): MutableMap<String, MangaChapter> { suspend fun loadChaptersFromMedia(i: Int, media: Media): MutableMap<String, MangaChapter> {

View file

@ -33,7 +33,7 @@ abstract class MangaParser : BaseParser() {
* **/ * **/
abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage>
override suspend fun autoSearch(mediaObj: Media): ShowResponse? { /*override suspend fun autoSearch(mediaObj: Media): ShowResponse? {
var response = loadSavedShowResponse(mediaObj.id) var response = loadSavedShowResponse(mediaObj.id)
if (response != null) { if (response != null) {
saveShowResponse(mediaObj.id, response, true) saveShowResponse(mediaObj.id, response, true)
@ -48,11 +48,22 @@ abstract class MangaParser : BaseParser() {
saveShowResponse(mediaObj.id, response) saveShowResponse(mediaObj.id, response)
} }
return response return response
} }*/
open fun getTransformation(): BitmapTransformation? = null open fun getTransformation(): BitmapTransformation? = null
} }
class EmptyMangaParser: MangaParser() {
override val name: String = "None"
override val saveName: String = "None"
override suspend fun loadChapters(mangaLink: String, extra: Map<String, String>?, sManga: SManga): List<MangaChapter> = emptyList()
override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List<MangaImage> = emptyList()
override suspend fun search(query: String): List<ShowResponse> = emptyList()
}
data class MangaChapter( data class MangaChapter(
/** /**
* Number of the Chapter in "String", * Number of the Chapter in "String",

View file

@ -29,7 +29,9 @@ object MangaSources : MangaReadSources() {
} }
object HMangaSources : MangaReadSources() { object HMangaSources : MangaReadSources() {
val aList: List<Lazier<BaseParser>> = lazyList( val aList: List<Lazier<BaseParser>> = lazyList()
) suspend fun init(fromExtensions: StateFlow<List<MangaExtension.Installed>>) {
//todo
}
override val list = listOf(aList,MangaSources.list).flatten() override val list = listOf(aList,MangaSources.list).flatten()
} }

View file

@ -15,7 +15,9 @@ import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding
@ -33,7 +35,8 @@ import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { class AnimeExtensionsFragment : Fragment(),
SearchQueryHandler {
private var _binding: FragmentAnimeExtensionsBinding? = null private var _binding: FragmentAnimeExtensionsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -42,104 +45,115 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
private lateinit var extensionsRecyclerView: RecyclerView private lateinit var extensionsRecyclerView: RecyclerView
private lateinit var allextenstionsRecyclerView: RecyclerView private lateinit var allextenstionsRecyclerView: RecyclerView
private val animeExtensionManager: AnimeExtensionManager = Injekt.get<AnimeExtensionManager>() private val animeExtensionManager: AnimeExtensionManager = Injekt.get<AnimeExtensionManager>()
private val extensionsAdapter = AnimeExtensionsAdapter ({ pkg -> private val extensionsAdapter = AnimeExtensionsAdapter({ pkg ->
if(pkg.hasUpdate){ if (isAdded) { // Check if the fragment is currently added to its activity
val context = requireContext() // Store context in a variable
val notificationManager = val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
animeExtensionManager.updateExtension(pkg)
.observeOn(AndroidSchedulers.mainThread()) if (pkg.hasUpdate) {
.subscribe( animeExtensionManager.updateExtension(pkg)
{ installStep -> .observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
val builder = NotificationCompat.Builder( .subscribe(
requireContext(), { installStep ->
Notifications.CHANNEL_DOWNLOADER_PROGRESS val builder = NotificationCompat.Builder(
) context,
.setSmallIcon(R.drawable.ic_round_sync_24) Notifications.CHANNEL_DOWNLOADER_PROGRESS
.setContentTitle("Updating extension") )
.setContentText("Step: $installStep") .setSmallIcon(R.drawable.ic_round_sync_24)
.setPriority(NotificationCompat.PRIORITY_LOW) .setContentTitle("Updating extension")
notificationManager.notify(1, builder.build()) .setContentText("Step: $installStep")
}, .setPriority(NotificationCompat.PRIORITY_LOW)
{ error -> notificationManager.notify(1, builder.build())
val builder = NotificationCompat.Builder( },
requireContext(), { error ->
Notifications.CHANNEL_DOWNLOADER_ERROR Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error
) val builder = NotificationCompat.Builder(
.setSmallIcon(R.drawable.ic_round_info_24) context,
.setContentTitle("Update failed") Notifications.CHANNEL_DOWNLOADER_ERROR
.setContentText("Error: ${error.message}") )
.setPriority(NotificationCompat.PRIORITY_HIGH) .setSmallIcon(R.drawable.ic_round_info_24)
notificationManager.notify(1, builder.build()) .setContentTitle("Update failed")
}, .setContentText("Error: ${error.message}")
{ .setPriority(NotificationCompat.PRIORITY_HIGH)
val builder = NotificationCompat.Builder( notificationManager.notify(1, builder.build())
requireContext(), },
Notifications.CHANNEL_DOWNLOADER_PROGRESS {
) val builder = NotificationCompat.Builder(
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) context,
.setContentTitle("Update complete") Notifications.CHANNEL_DOWNLOADER_PROGRESS
.setContentText("The extension has been successfully updated.") )
.setPriority(NotificationCompat.PRIORITY_LOW) .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
notificationManager.notify(1, builder.build()) .setContentTitle("Update complete")
} .setContentText("The extension has been successfully updated.")
) .setPriority(NotificationCompat.PRIORITY_LOW)
}else { notificationManager.notify(1, builder.build())
animeExtensionManager.uninstallExtension(pkg.pkgName) }
)
} else {
animeExtensionManager.uninstallExtension(pkg.pkgName)
}
} }
}, skipIcons) }, skipIcons)
private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope, { pkgName -> private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope, { pkgName ->
val notificationManager = val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Start the installation process // Start the installation process
animeExtensionManager.installExtension(pkgName) animeExtensionManager.installExtension(pkgName)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
{ installStep -> { installStep ->
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
requireContext(), requireContext(),
Notifications.CHANNEL_DOWNLOADER_PROGRESS Notifications.CHANNEL_DOWNLOADER_PROGRESS
) )
.setSmallIcon(R.drawable.ic_round_sync_24) .setSmallIcon(R.drawable.ic_round_sync_24)
.setContentTitle("Installing extension") .setContentTitle("Installing extension")
.setContentText("Step: $installStep") .setContentText("Step: $installStep")
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build()) notificationManager.notify(1, builder.build())
}, },
{ error -> { error ->
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
requireContext(), requireContext(),
Notifications.CHANNEL_DOWNLOADER_ERROR Notifications.CHANNEL_DOWNLOADER_ERROR
) )
.setSmallIcon(R.drawable.ic_round_info_24) .setSmallIcon(R.drawable.ic_round_info_24)
.setContentTitle("Installation failed") .setContentTitle("Installation failed")
.setContentText("Error: ${error.message}") .setContentText("Error: ${error.message}")
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
notificationManager.notify(1, builder.build()) notificationManager.notify(1, builder.build())
}, },
{ {
val builder = NotificationCompat.Builder( val builder = NotificationCompat.Builder(
requireContext(), requireContext(),
Notifications.CHANNEL_DOWNLOADER_PROGRESS Notifications.CHANNEL_DOWNLOADER_PROGRESS
) )
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
.setContentTitle("Installation complete") .setContentTitle("Installation complete")
.setContentText("The extension has been successfully installed.") .setContentText("The extension has been successfully installed.")
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
notificationManager.notify(1, builder.build()) notificationManager.notify(1, builder.build())
} }
) )
}, skipIcons) }, skipIcons)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false) _binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false)
extensionsRecyclerView = binding.animeExtensionsRecyclerView extensionsRecyclerView = binding.animeExtensionsRecyclerView
extensionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
extensionsRecyclerView.adapter = extensionsAdapter extensionsRecyclerView.adapter = extensionsAdapter
allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView
allextenstionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
allextenstionsRecyclerView.adapter = allExtensionsAdapter allextenstionsRecyclerView.adapter = allExtensionsAdapter
lifecycleScope.launch { lifecycleScope.launch {
@ -156,11 +170,11 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
Pair(availableExtensions, installedExtensions) Pair(availableExtensions, installedExtensions)
}.collect { pair -> }.collect { pair ->
val (availableExtensions, installedExtensions) = pair val (availableExtensions, installedExtensions) = pair
allExtensionsAdapter.updateData(availableExtensions, installedExtensions) allExtensionsAdapter.updateData(availableExtensions, installedExtensions)
} }
} }
val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView
return binding.root return binding.root
} }
@ -169,7 +183,6 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
allExtensionsAdapter.filter("") // Reset the filter allExtensionsAdapter.filter("") // Reset the filter
allextenstionsRecyclerView.visibility = View.VISIBLE allextenstionsRecyclerView.visibility = View.VISIBLE
extensionsRecyclerView.visibility = View.VISIBLE extensionsRecyclerView.visibility = View.VISIBLE
println("asdf: ${allExtensionsAdapter.getItemCount()}")
} else { } else {
allExtensionsAdapter.filter(query) allExtensionsAdapter.filter(query)
allextenstionsRecyclerView.visibility = View.VISIBLE allextenstionsRecyclerView.visibility = View.VISIBLE
@ -182,14 +195,17 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
} }
private class AnimeExtensionsAdapter(private val onUninstallClicked: (AnimeExtension.Installed) -> Unit, skipIcons: Boolean) : RecyclerView.Adapter<AnimeExtensionsAdapter.ViewHolder>() { private class AnimeExtensionsAdapter(
private val onUninstallClicked: (AnimeExtension.Installed) -> Unit,
skipIcons: Boolean
) : ListAdapter<AnimeExtension.Installed, AnimeExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_INSTALLED
) {
private var extensions: List<AnimeExtension.Installed> = emptyList()
val skipIcons = skipIcons val skipIcons = skipIcons
fun updateData(newExtensions: List<AnimeExtension.Installed>) { fun updateData(newExtensions: List<AnimeExtension.Installed>) {
extensions = newExtensions submitList(newExtensions) // Use submitList instead of manual list handling
notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@ -199,15 +215,20 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position] val extension = getItem(position) // Use getItem() from ListAdapter
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
if (!skipIcons) { if (!skipIcons) {
holder.extensionIconImageView.setImageDrawable(extension.icon) holder.extensionIconImageView.setImageDrawable(extension.icon)
} }
if(extension.hasUpdate){ if (extension.hasUpdate) {
holder.closeTextView.text = "Update" holder.closeTextView.text = "Update"
holder.closeTextView.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.warning)) holder.closeTextView.setTextColor(
}else{ ContextCompat.getColor(
holder.itemView.context,
R.color.warning
)
)
} else {
holder.closeTextView.text = "Uninstall" holder.closeTextView.text = "Uninstall"
} }
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
@ -215,59 +236,91 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
} }
} }
override fun getItemCount(): Int = extensions.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView) val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
val closeTextView: TextView = view.findViewById(R.id.closeTextView) val closeTextView: TextView = view.findViewById(R.id.closeTextView)
} }
companion object {
val DIFF_CALLBACK_INSTALLED =
object : DiffUtil.ItemCallback<AnimeExtension.Installed>() {
override fun areItemsTheSame(
oldItem: AnimeExtension.Installed,
newItem: AnimeExtension.Installed
): Boolean {
return oldItem.pkgName == newItem.pkgName
}
override fun areContentsTheSame(
oldItem: AnimeExtension.Installed,
newItem: AnimeExtension.Installed
): Boolean {
return oldItem == newItem
}
}
}
} }
private class AllAnimeExtensionsAdapter(private val coroutineScope: CoroutineScope,
private val onButtonClicked: (AnimeExtension.Available) -> Unit, skipIcons: Boolean) : RecyclerView.Adapter<AllAnimeExtensionsAdapter.ViewHolder>() { private class AllAnimeExtensionsAdapter(
private var extensions: List<AnimeExtension.Available> = emptyList() private val coroutineScope: CoroutineScope,
private val onButtonClicked: (AnimeExtension.Available) -> Unit,
skipIcons: Boolean
) : ListAdapter<AnimeExtension.Available, AllAnimeExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_AVAILABLE
) {
val skipIcons = skipIcons val skipIcons = skipIcons
fun updateData(newExtensions: List<AnimeExtension.Available>, installedExtensions: List<AnimeExtension.Installed> = emptyList()) { fun updateData(
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet() newExtensions: List<AnimeExtension.Available>,
extensions = newExtensions.filter { it.pkgName !in installedPkgNames } installedExtensions: List<AnimeExtension.Installed> = emptyList()
filteredExtensions = extensions ) {
notifyDataSetChanged() coroutineScope.launch(Dispatchers.Default) {
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
// Switch back to main thread to update UI
withContext(Dispatchers.Main) {
submitList(filteredExtensions)
}
}
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllAnimeExtensionsAdapter.ViewHolder {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): AllAnimeExtensionsAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context) val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension_all, parent, false) .inflate(R.layout.item_extension_all, parent, false)
return ViewHolder(view) return ViewHolder(view)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = filteredExtensions[position] val extension = getItem(position)
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
if (!skipIcons) { if (!skipIcons) {
coroutineScope.launch { Glide.with(holder.itemView.context)
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl) .load(extension.iconUrl)
holder.extensionIconImageView.setImageDrawable(drawable) .into(holder.extensionIconImageView)
}
} }
holder.closeTextView.text = "Install" holder.closeTextView.text = "Install"
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onButtonClicked(extension) onButtonClicked(extension)
} }
} }
override fun getItemCount(): Int = filteredExtensions.size
private var filteredExtensions: List<AnimeExtension.Available> = emptyList()
fun filter(query: String) { fun filter(query: String) {
filteredExtensions = if (query.isEmpty()) { val filteredExtensions = if (query.isEmpty()) {
extensions currentList
} else { } else {
extensions.filter { it.name.contains(query, ignoreCase = true) } currentList.filter { it.name.contains(query, ignoreCase = true) }
} }
notifyDataSetChanged() submitList(filteredExtensions)
} }
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -276,18 +329,24 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler {
val closeTextView: TextView = view.findViewById(R.id.closeTextView) val closeTextView: TextView = view.findViewById(R.id.closeTextView)
} }
suspend fun urlToDrawable(context: Context, url: String): Drawable? { companion object {
return withContext(Dispatchers.IO) { val DIFF_CALLBACK_AVAILABLE =
try { object : DiffUtil.ItemCallback<AnimeExtension.Available>() {
return@withContext Glide.with(context) override fun areItemsTheSame(
.load(url) oldItem: AnimeExtension.Available,
.submit() newItem: AnimeExtension.Available
.get() ): Boolean {
} catch (e: Exception) { return oldItem.pkgName == newItem.pkgName
e.printStackTrace() }
return@withContext null
override fun areContentsTheSame(
oldItem: AnimeExtension.Available,
newItem: AnimeExtension.Available
): Boolean {
return oldItem == newItem
}
} }
}
} }
} }
} }

View file

@ -13,6 +13,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.SearchView import android.widget.SearchView
import android.widget.TextView import android.widget.TextView
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
@ -49,17 +50,13 @@ import uy.kohesive.injekt.injectLazy
import javax.inject.Inject import javax.inject.Inject
class ExtensionsActivity : AppCompatActivity() { class ExtensionsActivity : AppCompatActivity() {
private val restartMainActivity = object : OnBackPressedCallback(false) { private val restartMainActivity = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity) override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity)
} }
lateinit var binding: ActivityExtensionsBinding lateinit var binding: ActivityExtensionsBinding
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -132,4 +129,4 @@ class ExtensionsActivity : AppCompatActivity() {
interface SearchQueryHandler { interface SearchQueryHandler {
fun updateContentBasedOnQuery(query: String?) fun updateContentBasedOnQuery(query: String?)
} }

View file

@ -11,26 +11,96 @@ import ani.dantotsu.initActivity
class FAQActivity : AppCompatActivity() { class FAQActivity : AppCompatActivity() {
private lateinit var binding: ActivityFaqBinding private lateinit var binding: ActivityFaqBinding
private val faqs = listOf( private val faqs by lazy {
listOf(
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_1), currContext()!!.getString(R.string.answer_1)), Triple(
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_2), currContext()!!.getString(R.string.answer_2)), R.drawable.ic_round_help_24,
Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_17), currContext()!!.getString(R.string.answer_17)), currContext()!!.getString(R.string.question_1),
Triple(R.drawable.ic_round_download_24, currContext()!!.getString(R.string.question_3), currContext()!!.getString(R.string.answer_3)), currContext()!!.getString(R.string.answer_1)
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_16), currContext()!!.getString(R.string.answer_16)), ),
Triple(R.drawable.ic_round_dns_24, currContext()!!.getString(R.string.question_4), currContext()!!.getString(R.string.answer_4)), Triple(
Triple(R.drawable.ic_baseline_screen_lock_portrait_24, currContext()!!.getString(R.string.question_5), currContext()!!.getString(R.string.answer_5)), R.drawable.ic_round_auto_awesome_24,
Triple(R.drawable.ic_anilist, currContext()!!.getString(R.string.question_6), currContext()!!.getString(R.string.answer_6)), currContext()!!.getString(R.string.question_2),
Triple(R.drawable.ic_round_movie_filter_24, currContext()!!.getString(R.string.question_7), currContext()!!.getString(R.string.answer_7)), currContext()!!.getString(R.string.answer_2)
Triple(R.drawable.ic_round_menu_book_24, currContext()!!.getString(R.string.question_8), currContext()!!.getString(R.string.answer_8)), ),
Triple(R.drawable.ic_round_lock_open_24, currContext()!!.getString(R.string.question_9), currContext()!!.getString(R.string.answer_9)), Triple(
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_10), currContext()!!.getString(R.string.answer_10)), R.drawable.ic_round_auto_awesome_24,
Triple(R.drawable.ic_round_smart_button_24, currContext()!!.getString(R.string.question_11), currContext()!!.getString(R.string.answer_11)), currContext()!!.getString(R.string.question_17),
Triple(R.drawable.ic_round_info_24, currContext()!!.getString(R.string.question_12), currContext()!!.getString(R.string.answer_12)), currContext()!!.getString(R.string.answer_17)
Triple(R.drawable.ic_round_help_24, currContext()!!.getString(R.string.question_13), currContext()!!.getString(R.string.answer_13)), ),
Triple(R.drawable.ic_round_art_track_24, currContext()!!.getString(R.string.question_14), currContext()!!.getString(R.string.answer_14)), Triple(
Triple(R.drawable.ic_round_video_settings_24, currContext()!!.getString(R.string.question_15), currContext()!!.getString(R.string.answer_15)) R.drawable.ic_round_download_24,
currContext()!!.getString(R.string.question_3),
currContext()!!.getString(R.string.answer_3)
),
Triple(
R.drawable.ic_round_help_24,
currContext()!!.getString(R.string.question_16),
currContext()!!.getString(R.string.answer_16)
),
Triple(
R.drawable.ic_round_dns_24,
currContext()!!.getString(R.string.question_4),
currContext()!!.getString(R.string.answer_4)
),
Triple(
R.drawable.ic_baseline_screen_lock_portrait_24,
currContext()!!.getString(R.string.question_5),
currContext()!!.getString(R.string.answer_5)
),
Triple(
R.drawable.ic_anilist,
currContext()!!.getString(R.string.question_6),
currContext()!!.getString(R.string.answer_6)
),
Triple(
R.drawable.ic_round_movie_filter_24,
currContext()!!.getString(R.string.question_7),
currContext()!!.getString(R.string.answer_7)
),
Triple(
R.drawable.ic_round_menu_book_24,
currContext()!!.getString(R.string.question_8),
currContext()!!.getString(R.string.answer_8)
),
Triple(
R.drawable.ic_round_lock_open_24,
currContext()!!.getString(R.string.question_9),
currContext()!!.getString(R.string.answer_9)
),
Triple(
R.drawable.ic_round_smart_button_24,
currContext()!!.getString(R.string.question_10),
currContext()!!.getString(R.string.answer_10)
),
Triple(
R.drawable.ic_round_smart_button_24,
currContext()!!.getString(R.string.question_11),
currContext()!!.getString(R.string.answer_11)
),
Triple(
R.drawable.ic_round_info_24,
currContext()!!.getString(R.string.question_12),
currContext()!!.getString(R.string.answer_12)
),
Triple(
R.drawable.ic_round_help_24,
currContext()!!.getString(R.string.question_13),
currContext()!!.getString(R.string.answer_13)
),
Triple(
R.drawable.ic_round_art_track_24,
currContext()!!.getString(R.string.question_14),
currContext()!!.getString(R.string.answer_14)
),
Triple(
R.drawable.ic_round_video_settings_24,
currContext()!!.getString(R.string.question_15),
currContext()!!.getString(R.string.answer_15)
)
) )
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -4,6 +4,7 @@ import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -13,7 +14,9 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.databinding.FragmentMangaBinding import ani.dantotsu.databinding.FragmentMangaBinding
@ -32,7 +35,8 @@ import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class MangaExtensionsFragment : Fragment(), SearchQueryHandler { class MangaExtensionsFragment : Fragment(),
SearchQueryHandler {
private var _binding: FragmentMangaExtensionsBinding? = null private var _binding: FragmentMangaExtensionsBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
@ -40,52 +44,58 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
private lateinit var extensionsRecyclerView: RecyclerView private lateinit var extensionsRecyclerView: RecyclerView
private lateinit var allextenstionsRecyclerView: RecyclerView private lateinit var allextenstionsRecyclerView: RecyclerView
private val mangaExtensionManager:MangaExtensionManager = Injekt.get<MangaExtensionManager>() private val mangaExtensionManager: MangaExtensionManager = Injekt.get<MangaExtensionManager>()
private val extensionsAdapter = MangaExtensionsAdapter ({ pkg -> private val extensionsAdapter = MangaExtensionsAdapter({ pkg ->
if(pkg.hasUpdate){ if (isAdded) { // Check if the fragment is currently added to its activity
val context = requireContext() // Store context in a variable
val notificationManager = val notificationManager =
requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once
mangaExtensionManager.updateExtension(pkg)
.observeOn(AndroidSchedulers.mainThread()) if (pkg.hasUpdate) {
.subscribe( mangaExtensionManager.updateExtension(pkg)
{ installStep -> .observeOn(AndroidSchedulers.mainThread()) // Observe on main thread
val builder = NotificationCompat.Builder( .subscribe(
requireContext(), { installStep ->
Notifications.CHANNEL_DOWNLOADER_PROGRESS val builder = NotificationCompat.Builder(
) context,
.setSmallIcon(R.drawable.ic_round_sync_24) Notifications.CHANNEL_DOWNLOADER_PROGRESS
.setContentTitle("Updating extension") )
.setContentText("Step: $installStep") .setSmallIcon(R.drawable.ic_round_sync_24)
.setPriority(NotificationCompat.PRIORITY_LOW) .setContentTitle("Updating extension")
notificationManager.notify(1, builder.build()) .setContentText("Step: $installStep")
}, .setPriority(NotificationCompat.PRIORITY_LOW)
{ error -> notificationManager.notify(1, builder.build())
val builder = NotificationCompat.Builder( },
requireContext(), { error ->
Notifications.CHANNEL_DOWNLOADER_ERROR Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error
) val builder = NotificationCompat.Builder(
.setSmallIcon(R.drawable.ic_round_info_24) context,
.setContentTitle("Update failed") Notifications.CHANNEL_DOWNLOADER_ERROR
.setContentText("Error: ${error.message}") )
.setPriority(NotificationCompat.PRIORITY_HIGH) .setSmallIcon(R.drawable.ic_round_info_24)
notificationManager.notify(1, builder.build()) .setContentTitle("Update failed")
}, .setContentText("Error: ${error.message}")
{ .setPriority(NotificationCompat.PRIORITY_HIGH)
val builder = NotificationCompat.Builder( notificationManager.notify(1, builder.build())
requireContext(), },
Notifications.CHANNEL_DOWNLOADER_PROGRESS {
) val builder = NotificationCompat.Builder(
.setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) context,
.setContentTitle("Update complete") Notifications.CHANNEL_DOWNLOADER_PROGRESS
.setContentText("The extension has been successfully updated.") )
.setPriority(NotificationCompat.PRIORITY_LOW) .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check)
notificationManager.notify(1, builder.build()) .setContentTitle("Update complete")
} .setContentText("The extension has been successfully updated.")
) .setPriority(NotificationCompat.PRIORITY_LOW)
}else { notificationManager.notify(1, builder.build())
mangaExtensionManager.uninstallExtension(pkg.pkgName) }
)
} else {
mangaExtensionManager.uninstallExtension(pkg.pkgName)
}
} }
}, skipIcons) }, skipIcons)
private val allExtensionsAdapter = private val allExtensionsAdapter =
AllMangaExtensionsAdapter(lifecycleScope, { pkgName -> AllMangaExtensionsAdapter(lifecycleScope, { pkgName ->
@ -132,15 +142,19 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
) )
}, skipIcons) }, skipIcons)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false) _binding = FragmentMangaExtensionsBinding.inflate(inflater, container, false)
extensionsRecyclerView = binding.mangaExtensionsRecyclerView extensionsRecyclerView = binding.mangaExtensionsRecyclerView
extensionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
extensionsRecyclerView.adapter = extensionsAdapter extensionsRecyclerView.adapter = extensionsAdapter
allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView
allextenstionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext())
allextenstionsRecyclerView.adapter = allExtensionsAdapter allextenstionsRecyclerView.adapter = allExtensionsAdapter
lifecycleScope.launch { lifecycleScope.launch {
@ -161,7 +175,6 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
} }
} }
val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView
return binding.root return binding.root
} }
@ -181,14 +194,18 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
super.onDestroyView();_binding = null super.onDestroyView();_binding = null
} }
private class MangaExtensionsAdapter(private val onUninstallClicked: (MangaExtension.Installed) -> Unit, skipIcons: Boolean) : RecyclerView.Adapter<MangaExtensionsAdapter.ViewHolder>() { private class MangaExtensionsAdapter(
private val onUninstallClicked: (MangaExtension.Installed) -> Unit,
skipIcons: Boolean
) : ListAdapter<MangaExtension.Installed, MangaExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_INSTALLED
) {
private var extensions: List<MangaExtension.Installed> = emptyList()
val skipIcons = skipIcons val skipIcons = skipIcons
// Use submitList to update data
fun updateData(newExtensions: List<MangaExtension.Installed>) { fun updateData(newExtensions: List<MangaExtension.Installed>) {
extensions = newExtensions submitList(newExtensions)
notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@ -198,75 +215,118 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = extensions[position] val extension = getItem(position) // Use getItem from ListAdapter
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
if(!skipIcons) { if (!skipIcons) {
holder.extensionIconImageView.setImageDrawable(extension.icon) holder.extensionIconImageView.setImageDrawable(extension.icon)
} }
if(extension.hasUpdate){
if (extension.hasUpdate) {
holder.closeTextView.text = "Update" holder.closeTextView.text = "Update"
holder.closeTextView.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.warning)) holder.closeTextView.setTextColor(
}else{ ContextCompat.getColor(
holder.itemView.context,
R.color.warning
)
)
} else {
holder.closeTextView.text = "Uninstall" holder.closeTextView.text = "Uninstall"
} }
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onUninstallClicked(extension) onUninstallClicked(extension)
} }
} }
override fun getItemCount(): Int = extensions.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView) val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView)
val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView)
val closeTextView: TextView = view.findViewById(R.id.closeTextView) val closeTextView: TextView = view.findViewById(R.id.closeTextView)
} }
companion object {
val DIFF_CALLBACK_INSTALLED =
object : DiffUtil.ItemCallback<MangaExtension.Installed>() {
override fun areItemsTheSame(
oldItem: MangaExtension.Installed,
newItem: MangaExtension.Installed
): Boolean {
return oldItem.pkgName == newItem.pkgName
}
override fun areContentsTheSame(
oldItem: MangaExtension.Installed,
newItem: MangaExtension.Installed
): Boolean {
return oldItem == newItem
}
}
}
} }
private class AllMangaExtensionsAdapter(private val coroutineScope: CoroutineScope,
private val onButtonClicked: (MangaExtension.Available) -> Unit, skipIcons: Boolean) : RecyclerView.Adapter<AllMangaExtensionsAdapter.ViewHolder>() {
private var extensions: List<MangaExtension.Available> = emptyList()
val skipIcons = skipIcons
fun updateData(newExtensions: List<MangaExtension.Available>, installedExtensions: List<MangaExtension.Installed> = emptyList()) { private class AllMangaExtensionsAdapter(
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet() private val coroutineScope: CoroutineScope,
extensions = newExtensions.filter { it.pkgName !in installedPkgNames } private val onButtonClicked: (MangaExtension.Available) -> Unit,
filteredExtensions = extensions skipIcons: Boolean
notifyDataSetChanged() ) : ListAdapter<MangaExtension.Available, AllMangaExtensionsAdapter.ViewHolder>(
DIFF_CALLBACK_AVAILABLE
) {
init {
setHasStableIds(true)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AllMangaExtensionsAdapter.ViewHolder {
val skipIcons = skipIcons
// Use submitList to update the data
fun updateData(
newExtensions: List<MangaExtension.Available>,
installedExtensions: List<MangaExtension.Installed> = emptyList()
) {
coroutineScope.launch(Dispatchers.Default) {
val installedPkgNames = installedExtensions.map { it.pkgName }.toSet()
val filteredExtensions = newExtensions.filter { it.pkgName !in installedPkgNames }
// Switch back to main thread to update UI
withContext(Dispatchers.Main) {
submitList(filteredExtensions)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context) val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_extension_all, parent, false) .inflate(R.layout.item_extension_all, parent, false)
return ViewHolder(view) return ViewHolder(view)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val extension = filteredExtensions[position] val extension = getItem(position) // Use getItem from ListAdapter
holder.extensionNameTextView.text = extension.name holder.extensionNameTextView.text = extension.name
if (!skipIcons) { if (!skipIcons) {
coroutineScope.launch { Glide.with(holder.itemView.context)
val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl) .load(extension.iconUrl)
holder.extensionIconImageView.setImageDrawable(drawable) .into(holder.extensionIconImageView)
}
} }
holder.closeTextView.text = "Install" holder.closeTextView.text = "Install"
holder.closeTextView.setOnClickListener { holder.closeTextView.setOnClickListener {
onButtonClicked(extension) onButtonClicked(extension)
} }
} }
override fun getItemCount(): Int = filteredExtensions.size // Filtering function
private var filteredExtensions: List<MangaExtension.Available> = emptyList()
fun filter(query: String) { fun filter(query: String) {
filteredExtensions = if (query.isEmpty()) { val filteredExtensions = if (query.isEmpty()) {
extensions currentList
} else { } else {
extensions.filter { it.name.contains(query, ignoreCase = true) } currentList.filter { it.name.contains(query, ignoreCase = true) }
} }
notifyDataSetChanged() submitList(filteredExtensions)
} }
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -275,18 +335,24 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler {
val closeTextView: TextView = view.findViewById(R.id.closeTextView) val closeTextView: TextView = view.findViewById(R.id.closeTextView)
} }
suspend fun urlToDrawable(context: Context, url: String): Drawable? { companion object {
return withContext(Dispatchers.IO) { val DIFF_CALLBACK_AVAILABLE =
try { object : DiffUtil.ItemCallback<MangaExtension.Available>() {
return@withContext Glide.with(context) override fun areItemsTheSame(
.load(url) oldItem: MangaExtension.Available,
.submit() newItem: MangaExtension.Available
.get() ): Boolean {
} catch (e: Exception) { return oldItem.pkgName == newItem.pkgName
e.printStackTrace() }
return@withContext null
override fun areContentsTheSame(
oldItem: MangaExtension.Available,
newItem: MangaExtension.Available
): Boolean {
return oldItem == newItem
}
} }
}
} }
} }
} }

View file

@ -100,16 +100,18 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_material_you", isChecked).apply() getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_material_you", isChecked).apply()
} }
val animeSource = loadData<Int>("settings_def_anime_source_s")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0 //val animeSource = loadData<Int>("settings_def_anime_source_s")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0
if (MangaSources.names.isNotEmpty() && animeSource in 0 until MangaSources.names.size) { val animeSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_anime_source_s_r", 0)
binding.mangaSource.setText(MangaSources.names[animeSource], false) if (AnimeSources.names.isNotEmpty() && animeSource in 0 until AnimeSources.names.size) {
binding.animeSource.setText(AnimeSources.names[animeSource], false)
} }
binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names)) binding.animeSource.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, AnimeSources.names))
binding.animeSource.setOnItemClickListener { _, _, i, _ -> binding.animeSource.setOnItemClickListener { _, _, i, _ ->
saveData("settings_def_anime_source_s", i) //saveData("settings_def_anime_source_s", i)
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("settings_def_anime_source_s_r", i).apply()
binding.animeSource.clearFocus() binding.animeSource.clearFocus()
} }
@ -186,7 +188,8 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
saveData("settings_prefer_dub", isChecked) saveData("settings_prefer_dub", isChecked)
} }
val mangaSource = loadData<Int>("settings_def_manga_source_s")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0 //val mangaSource = loadData<Int>("settings_def_manga_source_s")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0
val mangaSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_manga_source_s_r", 0)
if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) { if (MangaSources.names.isNotEmpty() && mangaSource in 0 until MangaSources.names.size) {
binding.mangaSource.setText(MangaSources.names[mangaSource], false) binding.mangaSource.setText(MangaSources.names[mangaSource], false)
} }
@ -196,7 +199,8 @@ OS Version: $CODENAME $RELEASE ($SDK_INT)
// Set up the item click listener for the dropdown. // Set up the item click listener for the dropdown.
binding.mangaSource.setOnItemClickListener { _, _, i, _ -> binding.mangaSource.setOnItemClickListener { _, _, i, _ ->
saveData("settings_def_manga_source_s", i) //saveData("settings_def_manga_source_s", i)
getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("settings_def_manga_source_s_r", i).apply()
binding.mangaSource.clearFocus() binding.mangaSource.clearFocus()
} }

View file

@ -14,11 +14,12 @@ import kotlinx.coroutines.withTimeoutOrNull
class SubscriptionHelper { class SubscriptionHelper {
companion object { companion object {
private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected { private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected {
val sharedPreferences = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)
val data = loadData<Selected>("${mediaId}-select", context) ?: Selected().let { val data = loadData<Selected>("${mediaId}-select", context) ?: Selected().let {
it.sourceIndex = it.sourceIndex =
if (isAdult) 0 if (isAdult) 0
else if (isAnime) {loadData("settings_def_anime_source_s_r", context) ?: 0} else if (isAnime) {sharedPreferences.getInt("settings_def_anime_source_s_r",0)}
else loadData("settings_def_manga_source_s_r", context) ?: 0 else {sharedPreferences.getInt("settings_def_manga_source_s_r",0)}
it.preferDub = loadData("settings_prefer_dub", context) ?: false it.preferDub = loadData("settings_prefer_dub", context) ?: false
it it
} }

View file

@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import okhttp3.Headers import okhttp3.Headers
import rx.subjects.Subject import rx.subjects.Subject
import java.io.Serializable
data class Track(val url: String, val lang: String) data class Track(val url: String, val lang: String)
@ -17,7 +18,7 @@ open class Video(
// "url", "language-label-2", "url2", "language-label-2" // "url", "language-label-2", "url2", "language-label-2"
val subtitleTracks: List<Track> = emptyList(), val subtitleTracks: List<Track> = emptyList(),
val audioTracks: List<Track> = emptyList(), val audioTracks: List<Track> = emptyList(),
) : ProgressListener { ) : Serializable, ProgressListener {
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
constructor( constructor(

View file

@ -0,0 +1,67 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="320dp"
android:height="180dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.6666667"
android:scaleY="0.6666667"
android:translateX="18"
android:translateY="18">
<group android:scaleX="0.5625"
android:translateX="-5.535">
<path
android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient
android:startY="49.59793"
android:startX="42.9492"
android:endY="92.4963"
android:endX="85.84757"
android:type="linear">
<item
android:color="#44000000"
android:offset="0"/>
<item
android:color="#00000000"
android:offset="1"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:fillColor="#3DDC84"
android:fillType="evenOdd"
android:strokeWidth="1"
android:strokeColor="#00000000"/>
</group>
<group android:scaleX="0.492"
android:scaleY="0.24750866"
android:translateX="277.2"
android:translateY="73.782364">
<group android:translateY="145.33594">
<path android:pathData="M12.796875,-0L12.796875,-101.109375L34.171875,-101.109375Q42.828125,-101.109375,50.234375,-100.234375Q57.65625,-99.359375,63.84375,-97.171875Q70.03125,-95,74.703125,-91.375Q79.390625,-87.75,82.765625,-82.125Q86.0625,-76.5,87.75,-68.765625Q89.4375,-61.03125,89.4375,-50.5625Q89.4375,-40.078125,87.75,-32.34375Q86.0625,-24.609375,82.765625,-18.984375Q79.390625,-13.359375,74.703125,-9.734375Q70.03125,-6.125,63.84375,-3.9375Q57.65625,-1.765625,50.234375,-0.875Q42.828125,-0,34.171875,-0L12.796875,-0ZM36.359375,-10.828125Q47.109375,-10.828125,54.59375,-12.96875Q62.09375,-15.125,66.875,-19.96875Q71.578125,-24.828125,73.71875,-32.3125Q75.875,-39.796875,75.875,-50.5625Q75.875,-61.3125,73.71875,-68.796875Q71.578125,-76.296875,66.875,-81.140625Q62.09375,-86,54.59375,-88.140625Q47.109375,-90.28125,36.359375,-90.28125L26.015625,-90.28125L26.015625,-10.828125L36.359375,-10.828125Z"
android:fillColor="#000000"/>
<path android:pathData="M113.109375,-60.1875L110.015625,-70.03125Q121.828125,-74.75,134.76562,-74.75Q146.07812,-74.75,152.0625,-70.953125Q158.875,-66.59375,158.875,-56.109375L158.875,-27.359375Q158.875,-21.59375,159.01562,-15.546875Q159.09375,-11.953125,159.71875,-8.09375Q160.28125,-4.4375,161.20312,-1.125L149.67188,1.40625Q148.04688,-3.3125,147.625,-9.140625L146.21875,-9.28125Q137.5,1.6875,124,1.6875Q114.15625,1.6875,108.171875,-3.765625Q102.203125,-9.21875,102.203125,-19.125Q102.203125,-26.515625,105.46875,-31.21875Q108.75,-35.9375,114.71875,-39.171875Q124.140625,-44.296875,141.09375,-44.296875Q143.34375,-44.296875,147.14062,-44.15625L147.14062,-54.84375Q147.14062,-59.90625,144.04688,-62.15625Q140.95312,-64.40625,134.34375,-64.40625Q124.703125,-64.40625,113.109375,-60.1875ZM147.14062,-20.53125L147.14062,-35.296875Q145.23438,-35.4375,141.57812,-35.4375Q135.39062,-35.4375,130.1875,-34.390625Q122.453125,-32.84375,118.203125,-29.5Q113.953125,-26.15625,113.953125,-19.90625Q113.953125,-8.65625,126.1875,-8.65625Q132.51562,-8.65625,138,-12.03125Q142.5,-14.84375,147.14062,-20.53125Z"
android:fillColor="#000000"/>
<path android:pathData="M179.5,-71.859375L191.17188,-74.46875Q192.5,-70.25,193.0625,-66.3125Q193.34375,-64.40625,193.76562,-58.78125L195.25,-58.78125Q199.10938,-65.890625,204.8125,-70.109375Q211.0625,-74.75,218.03125,-74.75Q228.5,-74.75,233.98438,-69.6875Q238.70312,-65.390625,240.53125,-56.53125Q241.71875,-50.484375,241.71875,-38.53125L241.71875,0L229.625,0L229.625,-41.90625Q229.625,-52.390625,227.59375,-56.890625Q224.5625,-63.5625,215.42188,-63.5625Q208.95312,-63.5625,202.76562,-57.375Q198.625,-53.234375,193.90625,-45.21875L193.90625,0L181.8125,0L181.8125,-45Q181.8125,-54.5,181.32812,-61.390625Q181.04688,-65.671875,179.5,-71.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M269.28125,-89.859375L281.375,-89.859375L281.375,-73.125L305.98438,-73.125L305.98438,-62.9375L281.375,-62.9375L281.375,-20.953125Q281.375,-16.734375,281.625,-14.515625Q281.875,-12.3125,282.85938,-11.046875Q284.6875,-8.515625,290.23438,-8.515625Q293.32812,-8.515625,296.21875,-9.140625Q299.17188,-9.78125,301.625,-10.546875Q303.59375,-6.265625,305.21875,-1.125Q296.92188,1.6875,288.48438,1.6875Q282.35938,1.6875,278.67188,0.3125Q274.98438,-1.0625,272.875,-4.15625Q270.6875,-7.390625,269.98438,-11.890625Q269.28125,-16.390625,269.28125,-24.328125L269.28125,-62.9375L258.73438,-62.9375L258.73438,-73.125L269.28125,-73.125L269.28125,-89.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M351.03125,-74.75Q354.46875,-74.75,358.1875,-74.21875Q361.92188,-73.6875,365.51562,-72.21875Q369.09375,-70.671875,372.29688,-68.03125Q375.5,-65.390625,377.95312,-61.171875Q380.42188,-56.890625,381.82812,-50.875Q383.23438,-44.859375,383.23438,-36.5625Q383.23438,-28.265625,381.82812,-22.25Q380.42188,-16.25,377.95312,-12.03125Q375.5,-7.734375,372.29688,-5.0625Q369.09375,-2.390625,365.51562,-0.921875Q361.92188,0.640625,358.1875,1.15625Q354.46875,1.6875,351.03125,1.6875Q347.65625,1.6875,343.95312,1.15625Q340.26562,0.640625,336.6875,-0.921875Q333.09375,-2.390625,329.92188,-5.0625Q326.76562,-7.734375,324.3125,-12.03125Q321.84375,-16.25,320.4375,-22.25Q319.03125,-28.265625,319.03125,-36.5625Q319.03125,-44.859375,320.4375,-50.875Q321.84375,-56.890625,324.3125,-61.171875Q326.76562,-65.390625,329.92188,-68.03125Q333.09375,-70.671875,336.6875,-72.21875Q340.26562,-73.6875,343.92188,-74.21875Q347.57812,-74.75,351.03125,-74.75ZM351.03125,-64.828125Q347.01562,-64.828125,343.46875,-63.5625Q339.92188,-62.296875,337.25,-59.140625Q334.5,-55.90625,332.875,-50.484375Q331.26562,-45.078125,331.26562,-36.5625Q331.26562,-28.125,332.875,-22.671875Q334.5,-17.234375,337.25,-14.0625Q339.92188,-10.828125,343.46875,-9.59375Q347.01562,-8.375,351.03125,-8.375Q355.10938,-8.375,358.71875,-9.59375Q362.34375,-10.828125,365.09375,-14.0625Q367.82812,-17.234375,369.40625,-22.671875Q371,-28.125,371,-36.5625Q371,-45.078125,369.40625,-50.484375Q367.82812,-55.90625,365.09375,-59.140625Q362.34375,-62.296875,358.71875,-63.5625Q355.10938,-64.828125,351.03125,-64.828125Z"
android:fillColor="#000000"/>
<path android:pathData="M408.28125,-89.859375L420.375,-89.859375L420.375,-73.125L444.98438,-73.125L444.98438,-62.9375L420.375,-62.9375L420.375,-20.953125Q420.375,-16.734375,420.625,-14.515625Q420.875,-12.3125,421.85938,-11.046875Q423.6875,-8.515625,429.23438,-8.515625Q432.32812,-8.515625,435.21875,-9.140625Q438.17188,-9.78125,440.625,-10.546875Q442.59375,-6.265625,444.21875,-1.125Q435.92188,1.6875,427.48438,1.6875Q421.35938,1.6875,417.67188,0.3125Q413.98438,-1.0625,411.875,-4.15625Q409.6875,-7.390625,408.98438,-11.890625Q408.28125,-16.390625,408.28125,-24.328125L408.28125,-62.9375L397.73438,-62.9375L397.73438,-73.125L408.28125,-73.125L408.28125,-89.859375Z"
android:fillColor="#000000"/>
<path android:pathData="M484.54688,-74.75Q490.3125,-74.75,496.10938,-73.546875Q501.90625,-72.359375,506.90625,-70.25Q505.92188,-68,504.85938,-65.8125Q503.8125,-63.640625,502.54688,-61.53125Q500.78125,-62.234375,498.64062,-62.9375Q496.5,-63.640625,494.14062,-64.125Q491.78125,-64.625,489.28125,-64.9375Q486.79688,-65.25,484.40625,-65.25Q481.79688,-65.25,479.375,-64.71875Q476.95312,-64.203125,475.07812,-63Q473.21875,-61.8125,472.09375,-59.875Q470.96875,-57.9375,470.96875,-55.0625Q470.96875,-52.109375,472.23438,-50.09375Q473.5,-48.09375,475.67188,-46.75Q477.85938,-45.421875,480.78125,-44.5Q483.70312,-43.59375,487,-42.828125Q492.14062,-41.5625,496.5625,-39.96875Q501,-38.390625,504.23438,-35.859375Q507.46875,-33.328125,509.29688,-29.59375Q511.125,-25.875,511.125,-20.390625Q511.125,-14.625,508.82812,-10.46875Q506.54688,-6.328125,502.64062,-3.625Q498.75,-0.921875,493.54688,0.375Q488.34375,1.6875,482.4375,1.6875Q479.34375,1.6875,476.0625,1.296875Q472.79688,0.921875,469.59375,0.171875Q466.40625,-0.5625,463.3125,-1.609375Q460.21875,-2.671875,457.46875,-3.9375Q458.45312,-6.1875,459.54688,-8.328125Q460.64062,-10.484375,461.82812,-12.59375Q464.15625,-11.609375,466.75,-10.71875Q469.35938,-9.84375,472.09375,-9.203125Q474.84375,-8.578125,477.51562,-8.1875Q480.1875,-7.8125,482.64062,-7.8125Q485.73438,-7.8125,488.65625,-8.515625Q491.57812,-9.21875,493.85938,-10.71875Q496.14062,-12.234375,497.51562,-14.625Q498.89062,-17.015625,498.89062,-20.390625Q498.89062,-23.5625,497.57812,-25.59375Q496.28125,-27.640625,494.09375,-29Q491.92188,-30.375,489,-31.25Q486.09375,-32.140625,482.85938,-32.90625Q477.9375,-34.109375,473.53125,-35.65625Q469.14062,-37.203125,465.875,-39.6875Q462.60938,-42.1875,460.67188,-45.875Q458.73438,-49.578125,458.73438,-55.0625Q458.73438,-60.1875,460.84375,-63.90625Q462.95312,-67.640625,466.5,-70.03125Q470.0625,-72.421875,474.73438,-73.578125Q479.40625,-74.75,484.54688,-74.75Z"
android:fillColor="#000000"/>
<path android:pathData="M590.28125,-1.265625L578.6094,1.34375Q577.28125,-2.890625,576.71875,-6.828125Q576.4375,-8.65625,576.0156,-14.28125L574.53125,-14.28125Q570.6719,-7.171875,564.96875,-2.953125Q558.71875,1.6875,551.75,1.6875Q541.28125,1.6875,535.7969,-3.375Q531.0781,-7.671875,529.25,-16.53125Q528.0625,-22.578125,528.0625,-34.53125L528.0625,-73.125L540.15625,-73.125L540.15625,-31.15625Q540.15625,-20.671875,542.1875,-16.171875Q545.21875,-9.5,554.3594,-9.5Q560.8281,-9.5,567.0156,-15.6875Q571.15625,-19.828125,575.875,-27.84375L575.875,-73.125L587.96875,-73.125L587.96875,-27.984375Q587.96875,-18.640625,588.4531,-11.75Q588.7344,-7.453125,590.28125,-1.265625Z"
android:fillColor="#000000"/>
<path android:pathData="M644.78125,0L644.78125,-95.984375L683.03125,-95.984375L683.03125,0L644.78125,0ZM649.5625,-4.78125L678.25,-4.78125L678.25,-91.203125L649.5625,-91.203125L649.5625,-4.78125Z"
android:fillColor="#000000"/>
</group>
</group>
</group>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@drawable/ic_banner_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#FFFFFF</color>
</resources>