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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 ISi9i=0%3ODF8}}l 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