From dc959796e6697d6b02f825d6b612984e4928ca39 Mon Sep 17 00:00:00 2001 From: Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> Date: Sun, 22 Oct 2023 02:28:39 -0500 Subject: [PATCH] various bugfixes --- app/src/main/AndroidManifest.xml | 17 +- .../aniyomi/anime/custom/InjektModules.kt | 4 + .../ani/dantotsu/connections/discord/Login.kt | 9 + app/src/main/java/ani/dantotsu/media/Media.kt | 8 +- .../dantotsu/media/MediaDetailsViewModel.kt | 10 +- .../media/manga/MangaChapterAdapter.kt | 12 +- .../dantotsu/media/manga/MangaNameAdapter.kt | 20 ++ .../manga/mangareader/ChapterLoaderDialog.kt | 4 +- .../manga/mangareader/MangaReaderActivity.kt | 39 ++- .../java/ani/dantotsu/parsers/AnimeParser.kt | 11 + .../ani/dantotsu/parsers/AniyomiAdapter.kt | 18 +- .../java/ani/dantotsu/parsers/BaseParser.kt | 4 +- .../java/ani/dantotsu/parsers/BaseSources.kt | 7 +- .../java/ani/dantotsu/parsers/MangaParser.kt | 15 +- .../java/ani/dantotsu/parsers/MangaSources.kt | 6 +- .../settings/AnimeExtensionsFragment.kt | 323 +++++++++++------- .../dantotsu/settings/ExtensionsActivity.kt | 9 +- .../java/ani/dantotsu/settings/FAQActivity.kt | 106 +++++- .../settings/MangaExtensionsFragment.kt | 252 +++++++++----- .../ani/dantotsu/settings/SettingsActivity.kt | 16 +- .../subcriptions/SubscriptionHelper.kt | 5 +- .../tachiyomi/animesource/model/Video.kt | 3 +- .../res/drawable-v24/ic_banner_foreground.xml | 67 ++++ .../main/res/mipmap-anydpi-v26/ic_banner.xml | 5 + app/src/main/res/mipmap-xhdpi/ic_banner.png | Bin 0 -> 5245 bytes .../main/res/values/ic_banner_background.xml | 4 + 26 files changed, 685 insertions(+), 289 deletions(-) create mode 100644 app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt create mode 100644 app/src/main/res/drawable-v24/ic_banner_foreground.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_banner.xml create mode 100644 app/src/main/res/mipmap-xhdpi/ic_banner.png create mode 100644 app/src/main/res/values/ic_banner_background.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c530597a..7201388e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,13 @@ + + + @@ -10,7 +17,8 @@ - + @@ -47,7 +55,7 @@ android:theme="@style/Theme.Dantotsu" android:usesCleartextTraffic="true" tools:ignore="AllowBackup" - > + android:banner="@drawable/ic_banner_foreground"> - + + + + = Build.VERSION_CODES.P) { + val process = getProcessName() + if (packageName != process) WebView.setDataDirectorySuffix(process) + } setContentView(R.layout.activity_discord) val webView = findViewById(R.id.discordWebview) + webView.apply { settings.javaScriptEnabled = true settings.databaseEnabled = true diff --git a/app/src/main/java/ani/dantotsu/media/Media.kt b/app/src/main/java/ani/dantotsu/media/Media.kt index adf59db9..1e5ac178 100644 --- a/app/src/main/java/ani/dantotsu/media/Media.kt +++ b/app/src/main/java/ani/dantotsu/media/Media.kt @@ -113,6 +113,10 @@ data class Media( this.relation = mediaEdge.relationType?.toString() } - fun mainName() = nameMAL ?: name ?: nameRomaji + fun mainName() = name ?: nameMAL ?: nameRomaji fun mangaName() = if (countryOfOrigin != "JP") mainName() else nameRomaji -} \ No newline at end of file +} + +object MediaSingleton { + var media: Media? = null +} diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index 8bbcc2ec..1842a8e6 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -1,6 +1,8 @@ package ani.dantotsu.media import android.app.Activity +import android.content.Context +import android.content.SharedPreferences import android.os.Handler import android.os.Looper import androidx.fragment.app.FragmentManager @@ -40,6 +42,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class MediaDetailsViewModel : ViewModel() { val scrolledToTop = MutableLiveData(true) @@ -48,11 +52,13 @@ class MediaDetailsViewModel : ViewModel() { saveData("$id-select", data, activity) } + fun loadSelected(media: Media): Selected { + val sharedPreferences = Injekt.get() val data = loadData("${media.id}-select") ?: Selected().let { it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) { - true -> loadData("settings_def_anime_source_s_r") ?: 0 - else -> loadData("settings_def_manga_source_s_r") ?: 0 + true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0) + else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0) } it.preferDub = loadData("settings_prefer_dub") ?: false saveSelected(media.id, it) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt index 45feca24..a5a2b19e 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt @@ -9,6 +9,8 @@ import ani.dantotsu.databinding.ItemEpisodeCompactBinding import ani.dantotsu.media.Media import ani.dantotsu.setAnimation import ani.dantotsu.connections.updateProgress +import java.util.regex.Matcher +import java.util.regex.Pattern class MangaChapterAdapter( private var type: Int, @@ -63,12 +65,12 @@ class MangaChapterAdapter( val ep = arr[position] binding.itemEpisodeNumber.text = ep.number 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 else { binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeCont.setOnLongClickListener { - updateProgress(media, ep.number) + updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString()) true } } @@ -91,14 +93,14 @@ class MangaChapterAdapter( } else binding.itemChapterTitle.visibility = View.GONE 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.itemEpisodeViewed.visibility = View.VISIBLE } else { binding.itemEpisodeViewedCover.visibility = View.GONE binding.itemEpisodeViewed.visibility = View.GONE binding.root.setOnLongClickListener { - updateProgress(media, ep.number) + updateProgress(media, MangaNameAdapter.findChapterNumber(ep.number).toString()) true } } @@ -113,4 +115,6 @@ class MangaChapterAdapter( fun updateType(t: Int) { type = t } + + } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt new file mode 100644 index 00000000..05e9b360 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaNameAdapter.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ChapterLoaderDialog.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ChapterLoaderDialog.kt index 26cb4ffe..7c199da7 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/ChapterLoaderDialog.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/ChapterLoaderDialog.kt @@ -14,6 +14,7 @@ import ani.dantotsu.currActivity import ani.dantotsu.databinding.BottomSheetSelectorBinding import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.media.MediaDetailsViewModel +import ani.dantotsu.media.MediaSingleton import ani.dantotsu.others.getSerialized import ani.dantotsu.tryWith import kotlinx.coroutines.Dispatchers @@ -49,7 +50,8 @@ class ChapterLoaderDialog : BottomSheetDialogFragment() { activity?.runOnUiThread { tryWith { dismiss() } 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) } } diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt index 484fce01..09d5fb61 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt @@ -30,8 +30,10 @@ import ani.dantotsu.connections.updateProgress import ani.dantotsu.databinding.ActivityMangaReaderBinding import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsViewModel +import ani.dantotsu.media.MediaSingleton import ani.dantotsu.media.manga.MangaCache import ani.dantotsu.media.manga.MangaChapter +import ani.dantotsu.media.manga.MangaNameAdapter import ani.dantotsu.others.ImageViewDialog import ani.dantotsu.others.getSerialized import ani.dantotsu.parsers.HMangaSources @@ -46,7 +48,12 @@ import ani.dantotsu.settings.UserInterfaceSettings import com.alexvasilkov.gestures.views.GestureFrameLayout import com.bumptech.glide.load.resource.bitmap.BitmapTransformation 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.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -164,10 +171,13 @@ class MangaReaderActivity : AppCompatActivity() { media = if (model.getMedia().value == null) try { - (intent.getSerialized("media")) ?: return + //(intent.getSerialized("media")) ?: return + MediaSingleton.media ?: return } catch (e: Exception) { logError(e) return + } finally { + MediaSingleton.media = null } else model.getMedia().value ?: return model.setMedia(media) @@ -180,6 +190,29 @@ class MangaReaderActivity : AppCompatActivity() { model.mangaReadSources = if (media.isAdult) HMangaSources else MangaSources 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().installedExtensionsFlow) + } + model.mangaReadSources = mangaSources + }else{ + val mangaSources = HMangaSources + val scope = lifecycleScope + scope.launch(Dispatchers.IO) { + mangaSources.init(Injekt.get().installedExtensionsFlow) + } + model.mangaReadSources = mangaSources + } + }catch (e: Exception){ + Firebase.crashlytics.recordException(e) + logError(e) + } + } binding.mangaReaderSource.text = model.mangaReadSources!!.names[media.selected!!.sourceIndex] binding.mangaReaderTitle.text = media.userPreferredName @@ -677,7 +710,7 @@ class MangaReaderActivity : AppCompatActivity() { progressDialog?.setCancelable(false) ?.setPositiveButton(getString(R.string.yes)) { dialog, _ -> saveData("${media.id}_save_progress", true) - updateProgress(media, media.manga!!.selectedChapter!!) + updateProgress(media, MangaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!).toString()) dialog.dismiss() runnable.run() } @@ -689,7 +722,7 @@ class MangaReaderActivity : AppCompatActivity() { progressDialog?.show() } else { if (loadData("${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() } } else { diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt index b5187aa6..e7b3a235 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeParser.kt @@ -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?, sAnime: SAnime): List = emptyList() + override suspend fun loadVideoServers(episodeLink: String, extra: Map?, sEpisode: SEpisode): List = emptyList() + + override suspend fun search(query: String): List = emptyList() +} + /** * A class for containing Episode data of a particular parser * **/ diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 2d681d57..2fc41476 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -66,6 +66,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { override val saveName = extension.name override val hostUrl = extension.sources.first().name override val isDubAvailableSeparately = false + override val isNSFW = extension.isNsfw override suspend fun loadEpisodes(animeLink: String, extra: Map?, sAnime: SAnime): List { val source = extension.sources.first() if (source is AnimeCatalogueSource) { @@ -176,6 +177,7 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { override val name = extension.name override val saveName = extension.name override val hostUrl = extension.sources.first().name + override val isNSFW = extension.isNsfw override suspend fun loadChapters(mangaLink: String, extra: Map?, sManga: SManga): List { 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 { - val parsedChapterTitle = parseChapterTitle(sChapter.name) + /*val parsedChapterTitle = parseChapterTitle(sChapter.name) val number = if (sChapter.chapter_number.toInt() != -1){ sChapter.chapter_number.toString() } else if(parsedChapterTitle.first != null || parsedChapterTitle.second != null){ (parsedChapterTitle.first ?: "") + "." + (parsedChapterTitle.second ?: "") }else{ sChapter.name - } + }*/ return MangaChapter( - number, + sChapter.name, sChapter.url, - if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) { - parsedChapterTitle.third - } else { - sChapter.name - }, + //if (parsedChapterTitle.first != null || parsedChapterTitle.second != null) { + // parsedChapterTitle.third + //} else { + sChapter.name, + //}, null, sChapter ) diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt index 369714a9..02b9f075 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt @@ -80,8 +80,8 @@ abstract class BaseParser { } else { val romajiRatio = FuzzySearch.ratio(closestRomaji?.name ?: "", mediaObj.nameRomaji) val mainNameRatio = FuzzySearch.ratio(response.name, mediaObj.mainName()) - logger("Fuzzy ratio for closest match in results: $mainNameRatio") - logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio") + logger("Fuzzy ratio for closest match in results: $mainNameRatio for ${response.name}") + logger("Fuzzy ratio for closest match in RomajiResults: $romajiRatio for ${closestRomaji?.name ?: "None"}") if (romajiRatio > mainNameRatio) { logger("RomajiResults has a closer match. Replacing response.") diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt index fb612bb3..db55d916 100644 --- a/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/BaseSources.kt @@ -11,9 +11,11 @@ import eu.kanade.tachiyomi.animesource.model.SAnime abstract class WatchSources : BaseSources() { 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 { return tryWithSuspend(true) { val res = get(i).autoSearch(media) ?: return@tryWithSuspend mutableMapOf() @@ -40,7 +42,8 @@ abstract class WatchSources : BaseSources() { abstract class MangaReadSources : BaseSources() { 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 { diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt b/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt index 492d1088..fd3fef4b 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaParser.kt @@ -33,7 +33,7 @@ abstract class MangaParser : BaseParser() { * **/ abstract suspend fun loadImages(chapterLink: String, sChapter: SChapter): List - override suspend fun autoSearch(mediaObj: Media): ShowResponse? { + /*override suspend fun autoSearch(mediaObj: Media): ShowResponse? { var response = loadSavedShowResponse(mediaObj.id) if (response != null) { saveShowResponse(mediaObj.id, response, true) @@ -48,11 +48,22 @@ abstract class MangaParser : BaseParser() { saveShowResponse(mediaObj.id, response) } return response - } + }*/ 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?, sManga: SManga): List = emptyList() + + override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List = emptyList() + + override suspend fun search(query: String): List = emptyList() +} + data class MangaChapter( /** * Number of the Chapter in "String", diff --git a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt index c25e0369..0afdbefe 100644 --- a/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/MangaSources.kt @@ -29,7 +29,9 @@ object MangaSources : MangaReadSources() { } object HMangaSources : MangaReadSources() { - val aList: List> = lazyList( - ) + val aList: List> = lazyList() + suspend fun init(fromExtensions: StateFlow>) { + //todo + } override val list = listOf(aList,MangaSources.list).flatten() } diff --git a/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt index 879336db..857c29d4 100644 --- a/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/AnimeExtensionsFragment.kt @@ -15,7 +15,9 @@ import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getSystemService import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding @@ -33,7 +35,8 @@ import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { +class AnimeExtensionsFragment : Fragment(), + SearchQueryHandler { private var _binding: FragmentAnimeExtensionsBinding? = null private val binding get() = _binding!! @@ -42,104 +45,115 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { private lateinit var extensionsRecyclerView: RecyclerView private lateinit var allextenstionsRecyclerView: RecyclerView private val animeExtensionManager: AnimeExtensionManager = Injekt.get() - private val extensionsAdapter = AnimeExtensionsAdapter ({ pkg -> - if(pkg.hasUpdate){ + private val extensionsAdapter = AnimeExtensionsAdapter({ pkg -> + if (isAdded) { // Check if the fragment is currently added to its activity + val context = requireContext() // Store context in a variable val notificationManager = - requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - animeExtensionManager.updateExtension(pkg) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { installStep -> - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_PROGRESS - ) - .setSmallIcon(R.drawable.ic_round_sync_24) - .setContentTitle("Updating extension") - .setContentText("Step: $installStep") - .setPriority(NotificationCompat.PRIORITY_LOW) - notificationManager.notify(1, builder.build()) - }, - { error -> - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_ERROR - ) - .setSmallIcon(R.drawable.ic_round_info_24) - .setContentTitle("Update failed") - .setContentText("Error: ${error.message}") - .setPriority(NotificationCompat.PRIORITY_HIGH) - notificationManager.notify(1, builder.build()) - }, - { - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_PROGRESS - ) - .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) - .setContentTitle("Update complete") - .setContentText("The extension has been successfully updated.") - .setPriority(NotificationCompat.PRIORITY_LOW) - notificationManager.notify(1, builder.build()) - } - ) - }else { - animeExtensionManager.uninstallExtension(pkg.pkgName) + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once + + if (pkg.hasUpdate) { + animeExtensionManager.updateExtension(pkg) + .observeOn(AndroidSchedulers.mainThread()) // Observe on main thread + .subscribe( + { installStep -> + val builder = NotificationCompat.Builder( + context, + Notifications.CHANNEL_DOWNLOADER_PROGRESS + ) + .setSmallIcon(R.drawable.ic_round_sync_24) + .setContentTitle("Updating extension") + .setContentText("Step: $installStep") + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(1, builder.build()) + }, + { error -> + Log.e("AnimeExtensionsAdapter", "Error: ", error) // Log the error + val builder = NotificationCompat.Builder( + context, + Notifications.CHANNEL_DOWNLOADER_ERROR + ) + .setSmallIcon(R.drawable.ic_round_info_24) + .setContentTitle("Update failed") + .setContentText("Error: ${error.message}") + .setPriority(NotificationCompat.PRIORITY_HIGH) + notificationManager.notify(1, builder.build()) + }, + { + val builder = NotificationCompat.Builder( + context, + Notifications.CHANNEL_DOWNLOADER_PROGRESS + ) + .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) + .setContentTitle("Update complete") + .setContentText("The extension has been successfully updated.") + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(1, builder.build()) + } + ) + } else { + animeExtensionManager.uninstallExtension(pkg.pkgName) + } } }, skipIcons) + private val allExtensionsAdapter = AllAnimeExtensionsAdapter(lifecycleScope, { pkgName -> - val notificationManager = - requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = + requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - // Start the installation process - animeExtensionManager.installExtension(pkgName) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { installStep -> - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_PROGRESS - ) - .setSmallIcon(R.drawable.ic_round_sync_24) - .setContentTitle("Installing extension") - .setContentText("Step: $installStep") - .setPriority(NotificationCompat.PRIORITY_LOW) - notificationManager.notify(1, builder.build()) - }, - { error -> - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_ERROR - ) - .setSmallIcon(R.drawable.ic_round_info_24) - .setContentTitle("Installation failed") - .setContentText("Error: ${error.message}") - .setPriority(NotificationCompat.PRIORITY_HIGH) - notificationManager.notify(1, builder.build()) - }, - { - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_PROGRESS - ) - .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) - .setContentTitle("Installation complete") - .setContentText("The extension has been successfully installed.") - .setPriority(NotificationCompat.PRIORITY_LOW) - notificationManager.notify(1, builder.build()) - } - ) - }, skipIcons) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + // Start the installation process + animeExtensionManager.installExtension(pkgName) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { installStep -> + val builder = NotificationCompat.Builder( + requireContext(), + Notifications.CHANNEL_DOWNLOADER_PROGRESS + ) + .setSmallIcon(R.drawable.ic_round_sync_24) + .setContentTitle("Installing extension") + .setContentText("Step: $installStep") + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(1, builder.build()) + }, + { error -> + val builder = NotificationCompat.Builder( + requireContext(), + Notifications.CHANNEL_DOWNLOADER_ERROR + ) + .setSmallIcon(R.drawable.ic_round_info_24) + .setContentTitle("Installation failed") + .setContentText("Error: ${error.message}") + .setPriority(NotificationCompat.PRIORITY_HIGH) + notificationManager.notify(1, builder.build()) + }, + { + val builder = NotificationCompat.Builder( + requireContext(), + Notifications.CHANNEL_DOWNLOADER_PROGRESS + ) + .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) + .setContentTitle("Installation complete") + .setContentText("The extension has been successfully installed.") + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(1, builder.build()) + } + ) + }, skipIcons) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { _binding = FragmentAnimeExtensionsBinding.inflate(inflater, container, false) extensionsRecyclerView = binding.animeExtensionsRecyclerView - extensionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) + extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) extensionsRecyclerView.adapter = extensionsAdapter allextenstionsRecyclerView = binding.allAnimeExtensionsRecyclerView - allextenstionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) + allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) allextenstionsRecyclerView.adapter = allExtensionsAdapter lifecycleScope.launch { @@ -156,11 +170,11 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { Pair(availableExtensions, installedExtensions) }.collect { pair -> val (availableExtensions, installedExtensions) = pair + allExtensionsAdapter.updateData(availableExtensions, installedExtensions) } } val extensionsRecyclerView: RecyclerView = binding.animeExtensionsRecyclerView - return binding.root } @@ -169,7 +183,6 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { allExtensionsAdapter.filter("") // Reset the filter allextenstionsRecyclerView.visibility = View.VISIBLE extensionsRecyclerView.visibility = View.VISIBLE - println("asdf: ${allExtensionsAdapter.getItemCount()}") } else { allExtensionsAdapter.filter(query) 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() { + private class AnimeExtensionsAdapter( + private val onUninstallClicked: (AnimeExtension.Installed) -> Unit, + skipIcons: Boolean + ) : ListAdapter( + DIFF_CALLBACK_INSTALLED + ) { - private var extensions: List = emptyList() val skipIcons = skipIcons fun updateData(newExtensions: List) { - extensions = newExtensions - notifyDataSetChanged() + submitList(newExtensions) // Use submitList instead of manual list handling } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -199,15 +215,20 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val extension = extensions[position] + val extension = getItem(position) // Use getItem() from ListAdapter holder.extensionNameTextView.text = extension.name if (!skipIcons) { holder.extensionIconImageView.setImageDrawable(extension.icon) } - if(extension.hasUpdate){ + if (extension.hasUpdate) { holder.closeTextView.text = "Update" - holder.closeTextView.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.warning)) - }else{ + holder.closeTextView.setTextColor( + ContextCompat.getColor( + holder.itemView.context, + R.color.warning + ) + ) + } else { holder.closeTextView.text = "Uninstall" } 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) { val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: TextView = view.findViewById(R.id.closeTextView) } + + companion object { + val DIFF_CALLBACK_INSTALLED = + object : DiffUtil.ItemCallback() { + 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() { - private var extensions: List = emptyList() + + private class AllAnimeExtensionsAdapter( + private val coroutineScope: CoroutineScope, + private val onButtonClicked: (AnimeExtension.Available) -> Unit, + skipIcons: Boolean + ) : ListAdapter( + DIFF_CALLBACK_AVAILABLE + ) { val skipIcons = skipIcons - fun updateData(newExtensions: List, installedExtensions: List = emptyList()) { - val installedPkgNames = installedExtensions.map { it.pkgName }.toSet() - extensions = newExtensions.filter { it.pkgName !in installedPkgNames } - filteredExtensions = extensions - notifyDataSetChanged() + fun updateData( + newExtensions: List, + installedExtensions: List = 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): AllAnimeExtensionsAdapter.ViewHolder { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): AllAnimeExtensionsAdapter.ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_extension_all, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val extension = filteredExtensions[position] + val extension = getItem(position) + holder.extensionNameTextView.text = extension.name + if (!skipIcons) { - coroutineScope.launch { - val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl) - holder.extensionIconImageView.setImageDrawable(drawable) - } + Glide.with(holder.itemView.context) + .load(extension.iconUrl) + .into(holder.extensionIconImageView) } + holder.closeTextView.text = "Install" holder.closeTextView.setOnClickListener { onButtonClicked(extension) } } - override fun getItemCount(): Int = filteredExtensions.size - - private var filteredExtensions: List = emptyList() - fun filter(query: String) { - filteredExtensions = if (query.isEmpty()) { - extensions + val filteredExtensions = if (query.isEmpty()) { + currentList } 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) { @@ -276,18 +329,24 @@ class AnimeExtensionsFragment : Fragment(), SearchQueryHandler { val closeTextView: TextView = view.findViewById(R.id.closeTextView) } - suspend fun urlToDrawable(context: Context, url: String): Drawable? { - return withContext(Dispatchers.IO) { - try { - return@withContext Glide.with(context) - .load(url) - .submit() - .get() - } catch (e: Exception) { - e.printStackTrace() - return@withContext null + companion object { + val DIFF_CALLBACK_AVAILABLE = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: AnimeExtension.Available, + newItem: AnimeExtension.Available + ): Boolean { + return oldItem.pkgName == newItem.pkgName + } + + override fun areContentsTheSame( + oldItem: AnimeExtension.Available, + newItem: AnimeExtension.Available + ): Boolean { + return oldItem == newItem + } } - } } } + } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index 9bd7f041..8cd639d2 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -13,6 +13,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout +import android.widget.ProgressBar import android.widget.SearchView import android.widget.TextView import androidx.activity.OnBackPressedCallback @@ -49,17 +50,13 @@ import uy.kohesive.injekt.injectLazy import javax.inject.Inject -class ExtensionsActivity : AppCompatActivity() { +class ExtensionsActivity : AppCompatActivity() { private val restartMainActivity = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() = startMainActivity(this@ExtensionsActivity) } lateinit var binding: ActivityExtensionsBinding - - - - @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -132,4 +129,4 @@ class ExtensionsActivity : AppCompatActivity() { interface SearchQueryHandler { fun updateContentBasedOnQuery(query: String?) -} \ No newline at end of file +} diff --git a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt index 12576863..685687b2 100644 --- a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt @@ -11,26 +11,96 @@ import ani.dantotsu.initActivity class FAQActivity : AppCompatActivity() { 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(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_2), currContext()!!.getString(R.string.answer_2)), - Triple(R.drawable.ic_round_auto_awesome_24, currContext()!!.getString(R.string.question_17), currContext()!!.getString(R.string.answer_17)), - Triple(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)) + Triple( + R.drawable.ic_round_help_24, + currContext()!!.getString(R.string.question_1), + currContext()!!.getString(R.string.answer_1) + ), + Triple( + R.drawable.ic_round_auto_awesome_24, + currContext()!!.getString(R.string.question_2), + currContext()!!.getString(R.string.answer_2) + ), + Triple( + R.drawable.ic_round_auto_awesome_24, + currContext()!!.getString(R.string.question_17), + currContext()!!.getString(R.string.answer_17) + ), + Triple( + 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?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt index 53da89e1..44a57c3e 100644 --- a/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/MangaExtensionsFragment.kt @@ -4,6 +4,7 @@ import android.app.NotificationManager import android.content.Context import android.graphics.drawable.Drawable import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -13,7 +14,9 @@ import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.databinding.FragmentMangaBinding @@ -32,7 +35,8 @@ import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class MangaExtensionsFragment : Fragment(), SearchQueryHandler { +class MangaExtensionsFragment : Fragment(), + SearchQueryHandler { private var _binding: FragmentMangaExtensionsBinding? = null private val binding get() = _binding!! @@ -40,52 +44,58 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler { private lateinit var extensionsRecyclerView: RecyclerView private lateinit var allextenstionsRecyclerView: RecyclerView - private val mangaExtensionManager:MangaExtensionManager = Injekt.get() - private val extensionsAdapter = MangaExtensionsAdapter ({ pkg -> - if(pkg.hasUpdate){ + private val mangaExtensionManager: MangaExtensionManager = Injekt.get() + private val extensionsAdapter = MangaExtensionsAdapter({ pkg -> + if (isAdded) { // Check if the fragment is currently added to its activity + val context = requireContext() // Store context in a variable val notificationManager = - requireContext().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - mangaExtensionManager.updateExtension(pkg) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { installStep -> - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_PROGRESS - ) - .setSmallIcon(R.drawable.ic_round_sync_24) - .setContentTitle("Updating extension") - .setContentText("Step: $installStep") - .setPriority(NotificationCompat.PRIORITY_LOW) - notificationManager.notify(1, builder.build()) - }, - { error -> - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_ERROR - ) - .setSmallIcon(R.drawable.ic_round_info_24) - .setContentTitle("Update failed") - .setContentText("Error: ${error.message}") - .setPriority(NotificationCompat.PRIORITY_HIGH) - notificationManager.notify(1, builder.build()) - }, - { - val builder = NotificationCompat.Builder( - requireContext(), - Notifications.CHANNEL_DOWNLOADER_PROGRESS - ) - .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) - .setContentTitle("Update complete") - .setContentText("The extension has been successfully updated.") - .setPriority(NotificationCompat.PRIORITY_LOW) - notificationManager.notify(1, builder.build()) - } - ) - }else { - mangaExtensionManager.uninstallExtension(pkg.pkgName) + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once + + if (pkg.hasUpdate) { + mangaExtensionManager.updateExtension(pkg) + .observeOn(AndroidSchedulers.mainThread()) // Observe on main thread + .subscribe( + { installStep -> + val builder = NotificationCompat.Builder( + context, + Notifications.CHANNEL_DOWNLOADER_PROGRESS + ) + .setSmallIcon(R.drawable.ic_round_sync_24) + .setContentTitle("Updating extension") + .setContentText("Step: $installStep") + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(1, builder.build()) + }, + { error -> + Log.e("MangaExtensionsAdapter", "Error: ", error) // Log the error + val builder = NotificationCompat.Builder( + context, + Notifications.CHANNEL_DOWNLOADER_ERROR + ) + .setSmallIcon(R.drawable.ic_round_info_24) + .setContentTitle("Update failed") + .setContentText("Error: ${error.message}") + .setPriority(NotificationCompat.PRIORITY_HIGH) + notificationManager.notify(1, builder.build()) + }, + { + val builder = NotificationCompat.Builder( + context, + Notifications.CHANNEL_DOWNLOADER_PROGRESS + ) + .setSmallIcon(androidx.media3.ui.R.drawable.exo_ic_check) + .setContentTitle("Update complete") + .setContentText("The extension has been successfully updated.") + .setPriority(NotificationCompat.PRIORITY_LOW) + notificationManager.notify(1, builder.build()) + } + ) + } else { + mangaExtensionManager.uninstallExtension(pkg.pkgName) + } } }, skipIcons) + private val allExtensionsAdapter = AllMangaExtensionsAdapter(lifecycleScope, { pkgName -> @@ -132,15 +142,19 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler { ) }, 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) extensionsRecyclerView = binding.mangaExtensionsRecyclerView - extensionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) + extensionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) extensionsRecyclerView.adapter = extensionsAdapter allextenstionsRecyclerView = binding.allMangaExtensionsRecyclerView - allextenstionsRecyclerView.layoutManager = LinearLayoutManager( requireContext()) + allextenstionsRecyclerView.layoutManager = LinearLayoutManager(requireContext()) allextenstionsRecyclerView.adapter = allExtensionsAdapter lifecycleScope.launch { @@ -161,7 +175,6 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler { } } val extensionsRecyclerView: RecyclerView = binding.mangaExtensionsRecyclerView - return binding.root } @@ -181,14 +194,18 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler { super.onDestroyView();_binding = null } - private class MangaExtensionsAdapter(private val onUninstallClicked: (MangaExtension.Installed) -> Unit, skipIcons: Boolean) : RecyclerView.Adapter() { + private class MangaExtensionsAdapter( + private val onUninstallClicked: (MangaExtension.Installed) -> Unit, + skipIcons: Boolean + ) : ListAdapter( + DIFF_CALLBACK_INSTALLED + ) { - private var extensions: List = emptyList() val skipIcons = skipIcons + // Use submitList to update data fun updateData(newExtensions: List) { - extensions = newExtensions - notifyDataSetChanged() + submitList(newExtensions) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -198,75 +215,118 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler { } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val extension = extensions[position] + val extension = getItem(position) // Use getItem from ListAdapter + holder.extensionNameTextView.text = extension.name - if(!skipIcons) { + if (!skipIcons) { holder.extensionIconImageView.setImageDrawable(extension.icon) } - if(extension.hasUpdate){ + + if (extension.hasUpdate) { holder.closeTextView.text = "Update" - holder.closeTextView.setTextColor(ContextCompat.getColor(holder.itemView.context, R.color.warning)) - }else{ + holder.closeTextView.setTextColor( + ContextCompat.getColor( + holder.itemView.context, + R.color.warning + ) + ) + } else { holder.closeTextView.text = "Uninstall" } + holder.closeTextView.setOnClickListener { onUninstallClicked(extension) } } - override fun getItemCount(): Int = extensions.size - inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: TextView = view.findViewById(R.id.closeTextView) } + + companion object { + val DIFF_CALLBACK_INSTALLED = + object : DiffUtil.ItemCallback() { + 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() { - private var extensions: List = emptyList() - val skipIcons = skipIcons - fun updateData(newExtensions: List, installedExtensions: List = emptyList()) { - val installedPkgNames = installedExtensions.map { it.pkgName }.toSet() - extensions = newExtensions.filter { it.pkgName !in installedPkgNames } - filteredExtensions = extensions - notifyDataSetChanged() + private class AllMangaExtensionsAdapter( + private val coroutineScope: CoroutineScope, + private val onButtonClicked: (MangaExtension.Available) -> Unit, + skipIcons: Boolean + ) : ListAdapter( + 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, + installedExtensions: List = 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) .inflate(R.layout.item_extension_all, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val extension = filteredExtensions[position] + val extension = getItem(position) // Use getItem from ListAdapter + holder.extensionNameTextView.text = extension.name if (!skipIcons) { - coroutineScope.launch { - val drawable = urlToDrawable(holder.itemView.context, extension.iconUrl) - holder.extensionIconImageView.setImageDrawable(drawable) - } + Glide.with(holder.itemView.context) + .load(extension.iconUrl) + .into(holder.extensionIconImageView) } + holder.closeTextView.text = "Install" holder.closeTextView.setOnClickListener { onButtonClicked(extension) } } - override fun getItemCount(): Int = filteredExtensions.size - - private var filteredExtensions: List = emptyList() - + // Filtering function fun filter(query: String) { - filteredExtensions = if (query.isEmpty()) { - extensions + val filteredExtensions = if (query.isEmpty()) { + currentList } 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) { @@ -275,18 +335,24 @@ class MangaExtensionsFragment : Fragment(), SearchQueryHandler { val closeTextView: TextView = view.findViewById(R.id.closeTextView) } - suspend fun urlToDrawable(context: Context, url: String): Drawable? { - return withContext(Dispatchers.IO) { - try { - return@withContext Glide.with(context) - .load(url) - .submit() - .get() - } catch (e: Exception) { - e.printStackTrace() - return@withContext null + companion object { + val DIFF_CALLBACK_AVAILABLE = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: MangaExtension.Available, + newItem: MangaExtension.Available + ): Boolean { + return oldItem.pkgName == newItem.pkgName + } + + override fun areContentsTheSame( + oldItem: MangaExtension.Available, + newItem: MangaExtension.Available + ): Boolean { + return oldItem == newItem + } } - } } } + } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index ee02f3e1..a6b52638 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -100,16 +100,18 @@ OS Version: $CODENAME $RELEASE ($SDK_INT) getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_material_you", isChecked).apply() } - val animeSource = loadData("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) { - binding.mangaSource.setText(MangaSources.names[animeSource], false) + //val animeSource = loadData("settings_def_anime_source_s")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0 + val animeSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_anime_source_s_r", 0) + 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.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() } @@ -186,7 +188,8 @@ OS Version: $CODENAME $RELEASE ($SDK_INT) saveData("settings_prefer_dub", isChecked) } - val mangaSource = loadData("settings_def_manga_source_s")?.let { if (it >= MangaSources.names.size) 0 else it } ?: 0 + //val mangaSource = loadData("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) { 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. 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() } diff --git a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt index f637929f..c4f2b445 100644 --- a/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt +++ b/app/src/main/java/ani/dantotsu/subcriptions/SubscriptionHelper.kt @@ -14,11 +14,12 @@ import kotlinx.coroutines.withTimeoutOrNull class SubscriptionHelper { companion object { private fun loadSelected(context: Context, mediaId: Int, isAdult: Boolean, isAnime: Boolean): Selected { + val sharedPreferences = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) val data = loadData("${mediaId}-select", context) ?: Selected().let { it.sourceIndex = if (isAdult) 0 - else if (isAnime) {loadData("settings_def_anime_source_s_r", context) ?: 0} - else loadData("settings_def_manga_source_s_r", context) ?: 0 + else if (isAnime) {sharedPreferences.getInt("settings_def_anime_source_s_r",0)} + else {sharedPreferences.getInt("settings_def_manga_source_s_r",0)} it.preferDub = loadData("settings_prefer_dub", context) ?: false it } diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt index e1a8c60e..41cd83d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/Video.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import okhttp3.Headers import rx.subjects.Subject +import java.io.Serializable data class Track(val url: String, val lang: String) @@ -17,7 +18,7 @@ open class Video( // "url", "language-label-2", "url2", "language-label-2" val subtitleTracks: List = emptyList(), val audioTracks: List = emptyList(), -) : ProgressListener { +) : Serializable, ProgressListener { @Suppress("UNUSED_PARAMETER") constructor( diff --git a/app/src/main/res/drawable-v24/ic_banner_foreground.xml b/app/src/main/res/drawable-v24/ic_banner_foreground.xml new file mode 100644 index 00000000..e26ad38f --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_banner_foreground.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml new file mode 100644 index 00000000..a0a0dece --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_banner.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-xhdpi/ic_banner.png b/app/src/main/res/mipmap-xhdpi/ic_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8c3cad591ce5469d999f9f9cdf231ac6589944 GIT binary patch literal 5245 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/ic_banner_background.xml b/app/src/main/res/values/ic_banner_background.xml new file mode 100644 index 00000000..15db34b8 --- /dev/null +++ b/app/src/main/res/values/ic_banner_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file