From 3368a1bc8df2b159b7f9c374a0b84f1f6af84fed Mon Sep 17 00:00:00 2001 From: Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:45:11 -0500 Subject: [PATCH] extension settings --- app/build.gradle | 1 + app/src/main/ic_launcher-playstore.png | Bin 27979 -> 16204 bytes .../main/java/ani/dantotsu/MainActivity.kt | 14 +- .../aniyomi/anime/custom/InjektModules.kt | 14 +- .../ani/dantotsu/home/AnimePageAdapter.kt | 14 ++ .../ani/dantotsu/home/MangaPageAdapter.kt | 15 +- .../dantotsu/media/MediaDetailsViewModel.kt | 8 +- .../main/java/ani/dantotsu/media/Selected.kt | 1 + .../dantotsu/media/anime/AnimeWatchAdapter.kt | 71 +++++++- .../media/anime/AnimeWatchFragment.kt | 171 ++++++++++++++---- .../dantotsu/media/manga/MangaReadAdapter.kt | 55 +++++- .../dantotsu/media/manga/MangaReadFragment.kt | 95 +++++++++- .../ani/dantotsu/parsers/AniyomiAdapter.kt | 47 ++++- .../InstalledAnimeExtensionsFragment.kt | 171 +++++++++++++----- .../InstalledMangaExtensionsFragment.kt | 73 ++++++++ .../AnimePreferenceFragmentCompat.kt | 88 +++++++++ .../MangaPreferenceFragmentCompat.kt | 78 ++++++++ .../tachiyomi/animesource/UnmeteredSource.kt | 8 + .../preference/SharedPreferencesDataStore.kt | 68 +++++++ .../source/anime/AndroidAnimeSourceManager.kt | 85 +++++++++ .../source/anime/AnimeSourceExtensions.kt | 34 ++++ .../source/manga/AndroidMangaSourceManager.kt | 84 +++++++++ .../source/manga/MangaSourceExtensions.kt | 34 ++++ .../kanade/tachiyomi/util/storage/DiskUtil.kt | 117 ++++++++++++ .../util/view/EditTextPreferenceExtensions.kt | 10 + .../widget/TachiyomiTextInputEditText.kt | 62 +++++++ .../core/metadata/tachiyomi/AnimeDetails.kt | 13 ++ .../core/metadata/tachiyomi/MangaDetails.kt | 13 ++ .../domain/entries/TriStateFilter.kt | 22 +++ .../domain/entries/anime/model/Anime.kt | 134 ++++++++++++++ .../domain/entries/manga/model/Manga.kt | 115 ++++++++++++ .../episode/service/EpisodeRecognition.kt | 119 ++++++++++++ .../domain/source/anime/model/AnimeSource.kt | 25 +++ .../anime/model/AnimeSourceWithCount.kt | 13 ++ .../domain/source/anime/model/Pin.kt | 42 +++++ .../source/anime/model/StubAnimeSource.kt | 33 ++++ .../anime/repository/AnimeSourceRepository.kt | 27 +++ .../anime/service/AnimeSourceManager.kt | 22 +++ .../source/manga/model/StubMangaSource.kt | 50 +++++ .../manga/service/MangaSourceManager.kt | 22 +++ .../local/entries/anime/LocalAnimeSource.kt | 98 ++++++++++ .../local/entries/manga/LocalMangaSource.kt | 70 +++++++ .../source/local/filter/anime/AnimeOrderBy.kt | 14 ++ .../source/local/filter/manga/MangaOrderBy.kt | 13 ++ .../java/tachiyomi/source/local/io/Archive.kt | 21 +++ app/src/main/res/anim/slide_down.xml | 7 + app/src/main/res/anim/slide_up.xml | 7 + app/src/main/res/drawable/bottom_nav.xml | 2 +- app/src/main/res/drawable/bottom_nav_gray.xml | 5 + .../res/drawable/ic_launcher_background.xml | 6 +- .../res/drawable/ic_launcher_foreground.xml | 2 +- .../main/res/layout-land/activity_media.xml | 16 +- .../main/res/layout/activity_extensions.xml | 6 + app/src/main/res/layout/activity_main.xml | 8 +- .../main/res/layout/activity_manga_reader.xml | 3 +- app/src/main/res/layout/activity_media.xml | 8 + .../main/res/layout/fragment_anime_watch.xml | 2 + app/src/main/res/layout/item_anime_page.xml | 8 +- app/src/main/res/layout/item_anime_watch.xml | 53 +++++- app/src/main/res/layout/item_extension.xml | 12 ++ app/src/main/res/layout/item_manga_page.xml | 8 +- .../res/mipmap-anydpi-v26/ic_launcher.xml | 1 - app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 3038 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 3180 -> 3038 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1946 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1994 -> 1946 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 4054 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 4166 -> 4054 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 6266 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 6482 -> 6266 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 8540 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 8716 -> 8540 bytes app/src/main/res/values-night/colors.xml | 2 + app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/themes.xml | 1 + app/src/main/res/xml/anime_preferences.xml | 8 + 76 files changed, 2320 insertions(+), 131 deletions(-) create mode 100644 app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt create mode 100644 app/src/main/java/ani/dantotsu/settings/extensionprefs/MangaPreferenceFragmentCompat.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/animesource/UnmeteredSource.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt create mode 100644 app/src/main/java/tachiyomi/core/metadata/tachiyomi/AnimeDetails.kt create mode 100644 app/src/main/java/tachiyomi/core/metadata/tachiyomi/MangaDetails.kt create mode 100644 app/src/main/java/tachiyomi/domain/entries/TriStateFilter.kt create mode 100644 app/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt create mode 100644 app/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt create mode 100644 app/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSource.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceWithCount.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/anime/model/Pin.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceRepository.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/anime/service/AnimeSourceManager.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/manga/model/StubMangaSource.kt create mode 100644 app/src/main/java/tachiyomi/domain/source/manga/service/MangaSourceManager.kt create mode 100644 app/src/main/java/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt create mode 100644 app/src/main/java/tachiyomi/source/local/entries/manga/LocalMangaSource.kt create mode 100644 app/src/main/java/tachiyomi/source/local/filter/anime/AnimeOrderBy.kt create mode 100644 app/src/main/java/tachiyomi/source/local/filter/manga/MangaOrderBy.kt create mode 100644 app/src/main/java/tachiyomi/source/local/io/Archive.kt create mode 100644 app/src/main/res/anim/slide_down.xml create mode 100644 app/src/main/res/anim/slide_up.xml create mode 100644 app/src/main/res/drawable/bottom_nav_gray.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/xml/anime_preferences.xml diff --git a/app/build.gradle b/app/build.gradle index 8e394e12..141d3b05 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,6 +65,7 @@ dependencies { implementation 'com.github.Blatzar:NiceHttp:0.4.3' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0' + implementation 'androidx.preference:preference:1.2.1' // Glide ext.glide_version = '4.16.0' diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index 20a7f23e3518eafa6065333b91da8c0b7f901b18..158604c1cc8923fa9b5266d5303e78a96ad32eec 100644 GIT binary patch literal 16204 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 U^!pxnaK?a=oZ8c3*%$Br4>lu}p8x;= literal 27979 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 diff --git a/app/src/main/java/ani/dantotsu/MainActivity.kt b/app/src/main/java/ani/dantotsu/MainActivity.kt index 73334e60..33ac52e7 100644 --- a/app/src/main/java/ani/dantotsu/MainActivity.kt +++ b/app/src/main/java/ani/dantotsu/MainActivity.kt @@ -85,14 +85,20 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - + val _bottomBar = findViewById(R.id.navbar) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val bottomBar = findViewById(R.id.navbar) - val backgroundDrawable = bottomBar.background as GradientDrawable + + val backgroundDrawable = _bottomBar.background as GradientDrawable val currentColor = backgroundDrawable.color?.defaultColor ?: 0 val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xE8000000.toInt() backgroundDrawable.setColor(semiTransparentColor) - bottomBar.background = backgroundDrawable + _bottomBar.background = backgroundDrawable + } + val colorOverflow = this.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) + .getBoolean("colorOverflow", false) + if (!colorOverflow) { + _bottomBar.background = ContextCompat.getDrawable(this, R.drawable.bottom_nav_gray) + } diff --git a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt index 67ca2afa..89280bae 100644 --- a/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt +++ b/app/src/main/java/ani/dantotsu/aniyomi/anime/custom/InjektModules.kt @@ -3,6 +3,7 @@ package ani.dantotsu.aniyomi.anime.custom import android.app.Application import android.content.Context +import androidx.core.content.ContextCompat import ani.dantotsu.media.manga.MangaCache import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import tachiyomi.core.preference.PreferenceStore @@ -12,7 +13,11 @@ import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences +import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager +import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager import kotlinx.serialization.json.Json +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.addSingleton @@ -26,9 +31,11 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { NetworkHelper(app, get()) } addSingletonFactory { AnimeExtensionManager(app) } - addSingletonFactory { MangaExtensionManager(app) } + addSingletonFactory { AndroidAnimeSourceManager(app, get()) } + addSingletonFactory { AndroidMangaSourceManager(app, get()) } + val sharedPreferences = app.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) addSingleton(sharedPreferences) @@ -40,6 +47,11 @@ class AppModule(val app: Application) : InjektModule { } addSingletonFactory { MangaCache() } + + ContextCompat.getMainExecutor(app).execute { + get() + get() + } } } diff --git a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt index feba6ac9..1117cf46 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt @@ -1,8 +1,11 @@ package ani.dantotsu.home +import android.content.Context import android.content.Intent +import android.graphics.Color import android.os.Handler import android.os.Looper +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -19,6 +22,7 @@ import ani.dantotsu.media.GenreActivity import ani.dantotsu.MediaPageTransformer import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemAnimePageBinding import ani.dantotsu.loadData import ani.dantotsu.loadImage @@ -58,6 +62,16 @@ class AnimePageAdapter : RecyclerView.Adapter(R.id.animeUserAvatarContainer) materialCardView.setCardBackgroundColor(semiTransparentColor) + val typedValue = TypedValue() + currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) + val color = typedValue.data + + + val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false + if (!colorOverflow) { + textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt() + materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt()) + } binding.animeTitleContainer.updatePadding(top = statusBarHeight) diff --git a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt index 5422d1cb..7f91aaaf 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt @@ -1,8 +1,10 @@ package ani.dantotsu.home +import android.content.Context import android.content.Intent import android.os.Handler import android.os.Looper +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -19,6 +21,7 @@ import ani.dantotsu.media.GenreActivity import ani.dantotsu.MediaPageTransformer import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.currContext import ani.dantotsu.databinding.ItemMangaPageBinding import ani.dantotsu.loadData import ani.dantotsu.loadImage @@ -53,10 +56,20 @@ class MangaPageAdapter : RecyclerView.Adapter(R.id.mangaSearchBar) val currentColor = textInputLayout.boxBackgroundColor - val semiTransparentColor= (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() + val semiTransparentColor = (currentColor and 0x00FFFFFF) or 0xA8000000.toInt() textInputLayout.boxBackgroundColor = semiTransparentColor val materialCardView = holder.itemView.findViewById(R.id.mangaUserAvatarContainer) materialCardView.setCardBackgroundColor(semiTransparentColor) + val typedValue = TypedValue() + currContext()?.theme?.resolveAttribute(android.R.attr.windowBackground, typedValue, true) + val color = typedValue.data + + + val colorOverflow = currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE)?.getBoolean("colorOverflow", false) ?: false + if (!colorOverflow) { + textInputLayout.boxBackgroundColor = (color and 0x00FFFFFF) or 0x28000000.toInt() + materialCardView.setCardBackgroundColor((color and 0x00FFFFFF) or 0x28000000.toInt()) + } binding.mangaTitleContainer.updatePadding(top = statusBarHeight) diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt index 1842a8e6..203859a8 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsViewModel.kt @@ -120,8 +120,8 @@ class MediaDetailsViewModel : ViewModel() { private val episodes = MutableLiveData>>(null) private val epsLoaded = mutableMapOf>() fun getEpisodes(): LiveData>> = episodes - suspend fun loadEpisodes(media: Media, i: Int) { - if (!epsLoaded.containsKey(i)) { + suspend fun loadEpisodes(media: Media, i: Int, invalidate: Boolean = false) { + if (!epsLoaded.containsKey(i) || invalidate) { epsLoaded[i] = watchSources?.loadEpisodesFromMedia(i, media) ?: return } episodes.postValue(epsLoaded) @@ -240,9 +240,9 @@ class MediaDetailsViewModel : ViewModel() { private val mangaChapters = MutableLiveData>>(null) private val mangaLoaded = mutableMapOf>() fun getMangaChapters(): LiveData>> = mangaChapters - suspend fun loadMangaChapters(media: Media, i: Int) { + suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) { logger("Loading Manga Chapters : $mangaLoaded") - if (!mangaLoaded.containsKey(i)) tryWithSuspend { + if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend { mangaLoaded[i] = mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend } mangaChapters.postValue(mangaLoaded) diff --git a/app/src/main/java/ani/dantotsu/media/Selected.kt b/app/src/main/java/ani/dantotsu/media/Selected.kt index ddb30f58..9982db51 100644 --- a/app/src/main/java/ani/dantotsu/media/Selected.kt +++ b/app/src/main/java/ani/dantotsu/media/Selected.kt @@ -9,6 +9,7 @@ data class Selected( var chip: Int = 0, //var source: String = "", var sourceIndex: Int = 0, + var langIndex: Int = 0, var preferDub: Boolean = false, var server: String? = null, var video: Int = 0, diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index feb75e7a..33dcefe5 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -1,6 +1,8 @@ package ani.dantotsu.media.anime import android.annotation.SuppressLint +import android.app.AlertDialog +import android.content.Context import android.content.Intent import android.net.Uri import android.util.TypedValue @@ -8,23 +10,38 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter +import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout +import android.widget.Toast import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.ItemAnimeWatchBinding import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.SourceSearchDialogFragment +import ani.dantotsu.parsers.AnimeSources +import ani.dantotsu.parsers.DynamicAnimeParser import ani.dantotsu.parsers.WatchSources +import ani.dantotsu.settings.ExtensionsActivity +import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.subcriptions.Notifications.Companion.openSettings import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import com.google.android.material.chip.Chip +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.lang.IndexOutOfBoundsException class AnimeWatchAdapter( private val media: Media, @@ -70,7 +87,8 @@ class AnimeWatchAdapter( } //Source Selection - val source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it } + var source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it } + setLanguageList(media.selected!!.langIndex,source) if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { binding.animeSource.setText(watchSources.names[source]) watchSources[source].apply { @@ -92,11 +110,41 @@ class AnimeWatchAdapter( binding.animeSourceDubbed.isChecked = selectDub changing = false binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE + source = i + setLanguageList(0,i) } subscribeButton(false) - fragment.loadEpisodes(i) + fragment.loadEpisodes(i, false) } + binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ -> + // Check if 'extension' and 'selected' properties exist and are accessible + (watchSources[source] as? DynamicAnimeParser)?.let { ext -> + ext.sourceLanguage = i + fragment.onLangChange(i) + fragment.onSourceChange(media.selected!!.sourceIndex).apply { + binding.animeSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + changing = true + binding.animeSourceDubbed.isChecked = selectDub + changing = false + binding.animeSourceDubbedCont.visibility = if (isDubAvailableSeparately) View.VISIBLE else View.GONE + setLanguageList(i, source) + } + subscribeButton(false) + fragment.loadEpisodes(media.selected!!.sourceIndex, true) + } ?: run { + } + } + + //settings + binding.animeSourceSettings.setOnClickListener { + (watchSources[source] as? DynamicAnimeParser)?.let { ext -> + fragment.openSettings(ext.extension) + } + } + + //Subscription subscribe = MediaDetailsActivity.PopImageButton( fragment.lifecycleScope, @@ -263,6 +311,25 @@ class AnimeWatchAdapter( } } + fun setLanguageList(lang: Int, source: Int) { + val binding = _binding + if (watchSources is AnimeSources) { + val parser = watchSources[source] as? DynamicAnimeParser + if (parser != null) { + (watchSources[source] as? DynamicAnimeParser)?.let { ext -> + ext.sourceLanguage = lang + } + try { + binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang) + }catch (e: IndexOutOfBoundsException) { + binding?.animeSourceLanguage?.setText(parser.extension.sources.firstOrNull()?.lang ?: "Unknown") + } + binding?.animeSourceLanguage?.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, parser.extension.sources.map { it.lang })) + + } + } + } + override fun getItemCount(): Int = 1 inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root) { diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index fc3893c2..0613eceb 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -1,11 +1,17 @@ package ani.dantotsu.media.anime import android.annotation.SuppressLint +import android.app.AlertDialog import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.Toast +import androidx.cardview.widget.CardView import androidx.core.math.MathUtils import androidx.core.view.updatePadding import androidx.fragment.app.Fragment @@ -13,24 +19,38 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.GridLayoutManager +import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.media.Media +import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.parsers.AnimeParser import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.HAnimeSources +import ani.dantotsu.settings.ExtensionsActivity +import ani.dantotsu.settings.InstalledAnimeExtensionsFragment import ani.dantotsu.settings.PlayerSettings import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.subcriptions.Notifications.Group.ANIME_GROUP import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import ani.dantotsu.subcriptions.SubscriptionHelper import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription +import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.navigationrail.NavigationRailView +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import kotlin.math.ceil import kotlin.math.max import kotlin.math.roundToInt @@ -214,6 +234,13 @@ class AnimeWatchFragment : Fragment() { return model.watchSources?.get(i)!! } + fun onLangChange(i: Int) { + val selected = model.loadSelected(media) + selected.langIndex = i + model.saveSelected(media.id, selected, requireActivity()) + media.selected = selected + } + fun onDubClicked(checked: Boolean) { val selected = model.loadSelected(media) model.watchSources?.get(selected.sourceIndex)?.selectDub = checked @@ -223,8 +250,8 @@ class AnimeWatchFragment : Fragment() { lifecycleScope.launch(Dispatchers.IO) { model.forceLoadEpisode(media, selected.sourceIndex) } } - fun loadEpisodes(i: Int) { - lifecycleScope.launch(Dispatchers.IO) { model.loadEpisodes(media, i) } + fun loadEpisodes(i: Int, invalidate: Boolean) { + lifecycleScope.launch(Dispatchers.IO) { model.loadEpisodes(media, i, invalidate) } } fun onIconPressed(viewType: Int, rev: Boolean) { @@ -262,45 +289,115 @@ class AnimeWatchFragment : Fragment() { else getString(R.string.unsubscribed_notification) ) } - - fun onEpisodeClick(i: String) { - model.continueMedia = false - model.saveSelected(media.id, media.selected!!, requireActivity()) - model.onEpisodeClick(media, i, requireActivity().supportFragmentManager) - } - - @SuppressLint("NotifyDataSetChanged") - private fun reload() { - val selected = model.loadSelected(media) - - //Find latest episode for subscription - selected.latest = media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f - selected.latest = media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest - - model.saveSelected(media.id, selected, requireActivity()) - headerAdapter.handleEpisodes() - episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size) - var arr: ArrayList = arrayListOf() - if (media.anime!!.episodes != null) { - val end = if (end != null && end!! < media.anime!!.episodes!!.size) end else null - arr.addAll( - media.anime!!.episodes!!.values.toList() - .slice(start..(end ?: (media.anime!!.episodes!!.size - 1))) - ) - if (reverse) - arr = (arr.reversed() as? ArrayList) ?: arr + fun openSettings(pkg: AnimeExtension.Installed){ + val changeUIVisibility: (Boolean) -> Unit = { show -> + val activity = requireActivity() as MediaDetailsActivity + val visibility = if (show) View.VISIBLE else View.GONE + activity.findViewById(R.id.mediaAppBar).visibility = visibility + activity.findViewById(R.id.mediaViewPager).visibility = visibility + activity.findViewById(R.id.mediaCover).visibility = visibility + activity.findViewById(R.id.mediaClose).visibility = visibility + try{ + activity.findViewById(R.id.mediaTab).visibility = visibility + }catch (e: ClassCastException){ + activity.findViewById(R.id.mediaTab).visibility = visibility + } + activity.findViewById(R.id.fragmentExtensionsContainer).visibility = + if (show) View.GONE else View.VISIBLE + } + val allSettings = pkg.sources.filterIsInstance() + if (allSettings.isNotEmpty()) { + var selectedSetting = allSettings[0] + if (allSettings.size > 1) { + val names = allSettings.map { it.lang }.toTypedArray() + var selectedIndex = 0 + AlertDialog.Builder(requireContext()) + .setTitle("Select a Source") + .setSingleChoiceItems(names, selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton("OK") { dialog, _ -> + selectedSetting = allSettings[selectedIndex] + dialog.dismiss() + + // Move the fragment transaction here + val fragment = + AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){ + changeUIVisibility(true) + loadEpisodes(media.selected!!.sourceIndex, true) + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.cancel() + changeUIVisibility(true) + return@setNegativeButton + } + .show() + } else { + // If there's only one setting, proceed with the fragment transaction + val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){ + changeUIVisibility(true) + loadEpisodes(media.selected!!.sourceIndex, true) + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + + changeUIVisibility(false) + } else { + Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT) + .show() } - episodeAdapter.arr = arr - episodeAdapter.updateType(style ?: uiSettings.animeDefaultView) - episodeAdapter.notifyItemRangeInserted(0, arr.size) } - override fun onDestroy() { - model.watchSources?.flushText() - super.onDestroy() - } + fun onEpisodeClick(i: String) { + model.continueMedia = false + model.saveSelected(media.id, media.selected!!, requireActivity()) + model.onEpisodeClick(media, i, requireActivity().supportFragmentManager) + } - var state: Parcelable? = null + @SuppressLint("NotifyDataSetChanged") + private fun reload() { + val selected = model.loadSelected(media) + + //Find latest episode for subscription + selected.latest = + media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f + selected.latest = + media.userProgress?.toFloat()?.takeIf { selected.latest < it } ?: selected.latest + + model.saveSelected(media.id, selected, requireActivity()) + headerAdapter.handleEpisodes() + episodeAdapter.notifyItemRangeRemoved(0, episodeAdapter.arr.size) + var arr: ArrayList = arrayListOf() + if (media.anime!!.episodes != null) { + val end = if (end != null && end!! < media.anime!!.episodes!!.size) end else null + arr.addAll( + media.anime!!.episodes!!.values.toList() + .slice(start..(end ?: (media.anime!!.episodes!!.size - 1))) + ) + if (reverse) + arr = (arr.reversed() as? ArrayList) ?: arr + } + episodeAdapter.arr = arr + episodeAdapter.updateType(style ?: uiSettings.animeDefaultView) + episodeAdapter.notifyItemRangeInserted(0, arr.size) + } + + override fun onDestroy() { + model.watchSources?.flushText() + super.onDestroy() + } + + var state: Parcelable? = null override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index 3579d6b9..8b64057d 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -17,12 +17,17 @@ import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.SourceSearchDialogFragment +import ani.dantotsu.parsers.AnimeSources +import ani.dantotsu.parsers.DynamicAnimeParser +import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.MangaReadSources +import ani.dantotsu.parsers.MangaSources import ani.dantotsu.subcriptions.Notifications.Companion.openSettings import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import com.google.android.material.chip.Chip import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import java.lang.IndexOutOfBoundsException class MangaReadAdapter( private val media: Media, @@ -50,10 +55,10 @@ class MangaReadAdapter( } //Source Selection - val source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it } + var source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it } + setLanguageList(media.selected!!.langIndex,source) if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { binding.animeSource.setText(mangaReadSources.names[source]) - mangaReadSources[source].apply { binding.animeSourceTitle.text = showUserText showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } @@ -65,9 +70,34 @@ class MangaReadAdapter( fragment.onSourceChange(i).apply { binding.animeSourceTitle.text = showUserText showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + source = i + setLanguageList(0,i) } subscribeButton(false) - fragment.loadChapters(i) + fragment.loadChapters(i, false) + } + + binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ -> + // Check if 'extension' and 'selected' properties exist and are accessible + (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> + ext.sourceLanguage = i + fragment.onLangChange(i) + fragment.onSourceChange(media.selected!!.sourceIndex).apply { + binding.animeSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + setLanguageList(i, source) + } + subscribeButton(false) + fragment.loadChapters(media.selected!!.sourceIndex, true) + } ?: run { + } + } + + //settings + binding.animeSourceSettings.setOnClickListener { + (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> + fragment.openSettings(ext.extension) + } } //Subscription @@ -224,6 +254,25 @@ class MangaReadAdapter( } } + fun setLanguageList(lang: Int, source: Int) { + val binding = _binding + if (mangaReadSources is MangaSources) { + val parser = mangaReadSources[source] as? DynamicMangaParser + if (parser != null) { + (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> + ext.sourceLanguage = lang + } + try { + binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang) + }catch (e: IndexOutOfBoundsException) { + binding?.animeSourceLanguage?.setText(parser.extension.sources.firstOrNull()?.lang ?: "Unknown") + } + binding?.animeSourceLanguage?.setAdapter(ArrayAdapter(fragment.requireContext(), R.layout.item_dropdown, parser.extension.sources.map { it.lang })) + + } + } + } + override fun getItemCount(): Int = 1 inner class ViewHolder(val binding: ItemAnimeWatchBinding) : RecyclerView.ViewHolder(binding.root) diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index c8a83cca..61af0bf5 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -1,11 +1,15 @@ package ani.dantotsu.media.manga import android.annotation.SuppressLint +import android.app.AlertDialog import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.Toast +import androidx.cardview.widget.CardView import androidx.core.math.MathUtils.clamp import androidx.core.view.updatePadding import androidx.fragment.app.Fragment @@ -13,20 +17,30 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.GridLayoutManager +import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.* import ani.dantotsu.databinding.FragmentAnimeWatchBinding import ani.dantotsu.media.manga.mangareader.ChapterLoaderDialog import ani.dantotsu.media.Media +import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.MediaDetailsViewModel import ani.dantotsu.parsers.HMangaSources import ani.dantotsu.parsers.MangaParser import ani.dantotsu.parsers.MangaSources import ani.dantotsu.settings.UserInterfaceSettings +import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment +import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment import ani.dantotsu.subcriptions.Notifications import ani.dantotsu.subcriptions.Notifications.Group.MANGA_GROUP import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import ani.dantotsu.subcriptions.SubscriptionHelper import ani.dantotsu.subcriptions.SubscriptionHelper.Companion.saveSubscription +import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.navigationrail.NavigationRailView +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension +import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.source.ConfigurableSource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.math.ceil @@ -185,8 +199,16 @@ open class MangaReadFragment : Fragment() { return model.mangaReadSources?.get(i)!! } - fun loadChapters(i: Int) { - lifecycleScope.launch(Dispatchers.IO) { model.loadMangaChapters(media, i) } + fun onLangChange(i: Int) { + val selected = model.loadSelected(media) + selected.langIndex = i + model.saveSelected(media.id, selected, requireActivity()) + media.selected = selected + } + + + fun loadChapters(i: Int, invalidate: Boolean) { + lifecycleScope.launch(Dispatchers.IO) { model.loadMangaChapters(media, i, invalidate) } } fun onIconPressed(viewType: Int, rev: Boolean) { @@ -225,6 +247,75 @@ open class MangaReadFragment : Fragment() { ) } + fun openSettings(pkg: MangaExtension.Installed){ + val changeUIVisibility: (Boolean) -> Unit = { show -> + val activity = requireActivity() as MediaDetailsActivity + val visibility = if (show) View.VISIBLE else View.GONE + activity.findViewById(R.id.mediaAppBar).visibility = visibility + activity.findViewById(R.id.mediaViewPager).visibility = visibility + activity.findViewById(R.id.mediaCover).visibility = visibility + activity.findViewById(R.id.mediaClose).visibility = visibility + try{ + activity.findViewById(R.id.mediaTab).visibility = visibility + }catch (e: ClassCastException){ + activity.findViewById(R.id.mediaTab).visibility = visibility + } + activity.findViewById(R.id.fragmentExtensionsContainer).visibility = + if (show) View.GONE else View.VISIBLE + } + val allSettings = pkg.sources.filterIsInstance() + if (allSettings.isNotEmpty()) { + var selectedSetting = allSettings[0] + if (allSettings.size > 1) { + val names = allSettings.map { it.lang }.toTypedArray() + var selectedIndex = 0 + AlertDialog.Builder(requireContext()) + .setTitle("Select a Source") + .setSingleChoiceItems(names, selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton("OK") { dialog, _ -> + selectedSetting = allSettings[selectedIndex] + dialog.dismiss() + + // Move the fragment transaction here + val fragment = + MangaSourcePreferencesFragment().getInstance(selectedSetting.id){ + changeUIVisibility(true) + loadChapters(media.selected!!.sourceIndex, true) + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.cancel() + changeUIVisibility(true) + return@setNegativeButton + } + .show() + } else { + // If there's only one setting, proceed with the fragment transaction + val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id){ + changeUIVisibility(true) + loadChapters(media.selected!!.sourceIndex, true) + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + + changeUIVisibility(false) + } else { + Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT) + .show() + } + } + fun onMangaChapterClick(i: String) { model.continueMedia = false media.manga?.chapters?.get(i)?.let { diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt index 6a4a58a7..887e6608 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt @@ -18,6 +18,8 @@ import ani.dantotsu.currContext import ani.dantotsu.logger import ani.dantotsu.media.manga.ImageData import ani.dantotsu.media.manga.MangaCache +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimesPage @@ -62,6 +64,7 @@ class AniyomiAdapter { class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { val extension: AnimeExtension.Installed + var sourceLanguage = 0 init { this.extension = extension } @@ -71,7 +74,12 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { 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() + val source = try{ + extension.sources[sourceLanguage] + }catch (e: Exception){ + sourceLanguage = 0 + extension.sources[sourceLanguage] + } if (source is AnimeCatalogueSource) { try { val res = source.getEpisodeList(sAnime) @@ -91,7 +99,12 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } override suspend fun loadVideoServers(episodeLink: String, extra: Map?, sEpisode: SEpisode): List { - val source = extension.sources.first() as? AnimeCatalogueSource ?: return emptyList() + val source = try{ + extension.sources[sourceLanguage] + }catch (e: Exception){ + sourceLanguage = 0 + extension.sources[sourceLanguage] + } as? AnimeCatalogueSource ?: return emptyList() return try { val videos = source.getVideoList(sEpisode) @@ -108,8 +121,12 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { } override suspend fun search(query: String): List { - val source = extension.sources.first() as? AnimeCatalogueSource ?: return emptyList() - + val source = try{ + extension.sources[sourceLanguage] + }catch (e: Exception){ + sourceLanguage = 0 + extension.sources[sourceLanguage] + } as? AnimeCatalogueSource ?: return emptyList() return try { val res = source.fetchSearchAnime(1, query, AnimeFilterList()).toBlocking().first() convertAnimesPageToShowResponse(res) @@ -174,6 +191,7 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() { class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { val mangaCache = Injekt.get() val extension: MangaExtension.Installed + var sourceLanguage = 0 init { this.extension = extension } @@ -183,7 +201,12 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { 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() + val source = try{ + extension.sources[sourceLanguage] + }catch (e: Exception){ + sourceLanguage = 0 + extension.sources[sourceLanguage] + } as? HttpSource ?: return emptyList() return try { val res = source.getChapterList(sManga) @@ -201,7 +224,12 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { override suspend fun loadImages(chapterLink: String, sChapter: SChapter): List { - val source = extension.sources.first() as? HttpSource ?: return emptyList() + val source = try{ + extension.sources[sourceLanguage] + }catch (e: Exception){ + sourceLanguage = 0 + extension.sources[sourceLanguage] + } as? HttpSource ?: return emptyList() return coroutineScope { try { @@ -321,7 +349,12 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() { override suspend fun search(query: String): List { - val source = extension.sources.first() as? HttpSource ?: return emptyList() + val source = try{ + extension.sources[sourceLanguage] + }catch (e: Exception){ + sourceLanguage = 0 + extension.sources[sourceLanguage] + } as? HttpSource ?: return emptyList() return try { val res = source.fetchSearchManga(1, query, FilterList()).toBlocking().first() diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt index 9294bd3c..6a1bf640 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt @@ -1,5 +1,6 @@ package ani.dantotsu.settings +import android.app.AlertDialog import android.app.NotificationManager import android.content.Context import android.os.Bundle @@ -7,8 +8,10 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment @@ -17,10 +20,15 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R import ani.dantotsu.databinding.FragmentAnimeExtensionsBinding import ani.dantotsu.loadData +import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout import com.google.firebase.crashlytics.FirebaseCrashlytics +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension @@ -30,62 +38,129 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class InstalledAnimeExtensionsFragment : Fragment() { + + private var _binding: FragmentAnimeExtensionsBinding? = null private val binding get() = _binding!! private lateinit var extensionsRecyclerView: RecyclerView val skipIcons = loadData("skip_extension_icons") ?: false private val animeExtensionManager: AnimeExtensionManager = Injekt.get() 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 = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Initialize NotificationManager once + val allSettings = pkg.sources.filterIsInstance() + if (allSettings.isNotEmpty()) { + var selectedSetting = allSettings[0] + if (allSettings.size > 1) { + val names = allSettings.map { it.lang }.toTypedArray() + var selectedIndex = 0 + AlertDialog.Builder(requireContext()) + .setTitle("Select a Source") + .setSingleChoiceItems(names, selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton("OK") { dialog, _ -> + selectedSetting = allSettings[selectedIndex] + dialog.dismiss() - 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 -> - FirebaseCrashlytics.getInstance().recordException(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: ${error.message}") - .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()) + // Move the fragment transaction here + val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){ + val activity = requireActivity() as ExtensionsActivity + activity.findViewById(R.id.viewPager).visibility = View.VISIBLE + activity.findViewById(R.id.tabLayout).visibility = View.VISIBLE + activity.findViewById(R.id.searchView).visibility = View.VISIBLE + activity.findViewById(R.id.fragmentExtensionsContainer).visibility = + View.GONE } - ) + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.cancel() + return@setNegativeButton + } + .show() } else { - animeExtensionManager.uninstallExtension(pkg.pkgName) + // If there's only one setting, proceed with the fragment transaction + val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id){ + val activity = requireActivity() as ExtensionsActivity + activity.findViewById(R.id.viewPager).visibility = View.VISIBLE + activity.findViewById(R.id.tabLayout).visibility = View.VISIBLE + activity.findViewById(R.id.searchView).visibility = View.VISIBLE + activity.findViewById(R.id.fragmentExtensionsContainer).visibility = + View.GONE + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() } + + // Hide ViewPager2 and TabLayout + val activity = requireActivity() as ExtensionsActivity + activity.findViewById(R.id.viewPager).visibility = View.GONE + activity.findViewById(R.id.tabLayout).visibility = View.GONE + activity.findViewById(R.id.searchView).visibility = View.GONE + activity.findViewById(R.id.fragmentExtensionsContainer).visibility = View.VISIBLE + } else { + Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT) + .show() } - }, skipIcons) + }, + { pkg -> + if (isAdded) { // Check if the fragment is currently added to its activity + val context = requireContext() // Store context in a variable + val notificationManager = + 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 -> + FirebaseCrashlytics.getInstance().recordException(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: ${error.message}") + .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 + ) override fun onCreateView( inflater: LayoutInflater, @@ -114,6 +189,7 @@ class InstalledAnimeExtensionsFragment : Fragment() { private class AnimeExtensionsAdapter( + private val onSettingsClicked: (AnimeExtension.Installed) -> Unit, private val onUninstallClicked: (AnimeExtension.Installed) -> Unit, skipIcons: Boolean ) : ListAdapter( @@ -152,10 +228,14 @@ class InstalledAnimeExtensionsFragment : Fragment() { holder.closeTextView.setOnClickListener { onUninstallClicked(extension) } + holder.settingsImageView.setOnClickListener { + onSettingsClicked(extension) + } } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView) + val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: TextView = view.findViewById(R.id.closeTextView) } @@ -180,5 +260,4 @@ class InstalledAnimeExtensionsFragment : Fragment() { } } - } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt index 42dd191e..51e0487d 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt @@ -1,6 +1,7 @@ package ani.dantotsu.settings +import android.app.AlertDialog import android.app.NotificationManager import android.content.Context import android.os.Bundle @@ -8,8 +9,10 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment @@ -18,13 +21,18 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R import ani.dantotsu.databinding.FragmentMangaExtensionsBinding import ani.dantotsu.loadData +import ani.dantotsu.settings.extensionprefs.MangaSourcePreferencesFragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout import com.google.firebase.crashlytics.FirebaseCrashlytics import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.extension.manga.model.MangaExtension +import eu.kanade.tachiyomi.source.ConfigurableSource import kotlinx.coroutines.launch import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.Injekt @@ -37,6 +45,66 @@ class InstalledMangaExtensionsFragment : Fragment() { val skipIcons = loadData("skip_extension_icons") ?: false private val mangaExtensionManager: MangaExtensionManager = Injekt.get() private val extensionsAdapter = MangaExtensionsAdapter({ pkg -> + val changeUIVisibility: (Boolean) -> Unit = { show -> + val activity = requireActivity() as ExtensionsActivity + val visibility = if (show) View.VISIBLE else View.GONE + activity.findViewById(R.id.viewPager).visibility = visibility + activity.findViewById(R.id.tabLayout).visibility = visibility + activity.findViewById(R.id.searchView).visibility = visibility + activity.findViewById(R.id.fragmentExtensionsContainer).visibility = + if (show) View.GONE else View.VISIBLE + } + val allSettings = pkg.sources.filterIsInstance() + if (allSettings.isNotEmpty()) { + var selectedSetting = allSettings[0] + if (allSettings.size > 1) { + val names = allSettings.map { it.lang }.toTypedArray() + var selectedIndex = 0 + AlertDialog.Builder(requireContext()) + .setTitle("Select a Source") + .setSingleChoiceItems(names, selectedIndex) { _, which -> + selectedIndex = which + } + .setPositiveButton("OK") { dialog, _ -> + selectedSetting = allSettings[selectedIndex] + dialog.dismiss() + + // Move the fragment transaction here + val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id){ + changeUIVisibility(true) + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.cancel() + changeUIVisibility(true) + return@setNegativeButton + } + .show() + } else { + // If there's only one setting, proceed with the fragment transaction + val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id){ + changeUIVisibility(true) + } + parentFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + .replace(R.id.fragmentExtensionsContainer, fragment) + .addToBackStack(null) + .commit() + } + + // Hide ViewPager2 and TabLayout + changeUIVisibility(false) + } else { + Toast.makeText(requireContext(), "Source is not configurable", Toast.LENGTH_SHORT) + .show() + } + }, + { pkg -> if (isAdded) { // Check if the fragment is currently added to its activity val context = requireContext() // Store context in a variable val notificationManager = @@ -115,6 +183,7 @@ class InstalledMangaExtensionsFragment : Fragment() { private class MangaExtensionsAdapter( + private val onSettingsClicked: (MangaExtension.Installed) -> Unit, private val onUninstallClicked: (MangaExtension.Installed) -> Unit, skipIcons: Boolean ) : ListAdapter( @@ -153,10 +222,14 @@ class InstalledMangaExtensionsFragment : Fragment() { holder.closeTextView.setOnClickListener { onUninstallClicked(extension) } + holder.settingsImageView.setOnClickListener { + onSettingsClicked(extension) + } } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val extensionNameTextView: TextView = view.findViewById(R.id.extensionNameTextView) + val settingsImageView: ImageView = view.findViewById(R.id.settingsImageView) val extensionIconImageView: ImageView = view.findViewById(R.id.extensionIconImageView) val closeTextView: TextView = view.findViewById(R.id.closeTextView) } diff --git a/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt b/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt new file mode 100644 index 00000000..b4f93561 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/extensionprefs/AnimePreferenceFragmentCompat.kt @@ -0,0 +1,88 @@ +package ani.dantotsu.settings.extensionprefs + +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import android.util.Log +import android.util.TypedValue +import android.view.View +import android.widget.FrameLayout +import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope +import androidx.preference.DialogPreference +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.forEach +import androidx.preference.getOnBindEditTextListener +import androidx.viewpager2.widget.ViewPager2 +import ani.dantotsu.R +import ani.dantotsu.settings.ExtensionsActivity +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout +import eu.kanade.tachiyomi.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore +import eu.kanade.tachiyomi.source.anime.getPreferenceKey +import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class AnimeSourcePreferencesFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceScreen = populateAnimePreferenceScreen() + //set background color + val color = TypedValue() + requireContext().theme.resolveAttribute(com.google.android.material.R.attr.backgroundColor, color, true) + view?.setBackgroundColor(color.data) + } + private var onCloseAction: (() -> Unit)? = null + + + override fun onDestroyView() { + super.onDestroyView() + onCloseAction?.invoke() + } + + fun populateAnimePreferenceScreen(): PreferenceScreen { + val sourceId = requireArguments().getLong(SOURCE_ID) + val source = Injekt.get().get(sourceId)!! + check(source is ConfigurableAnimeSource) + val sharedPreferences = requireContext().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE) + val dataStore = SharedPreferencesDataStore(sharedPreferences) + preferenceManager.preferenceDataStore = dataStore + val sourceScreen = preferenceManager.createPreferenceScreen(requireContext()) + source.setupPreferenceScreen(sourceScreen) + sourceScreen.forEach { pref -> + pref.isIconSpaceReserved = false + if (pref is DialogPreference) { + pref.dialogTitle = pref.title + println("pref.dialogTitle: ${pref.dialogTitle}") + } + for (entry in sharedPreferences.all.entries) { + Log.d("Preferences", "Key: ${entry.key}, Value: ${entry.value}") + } + + // Apply incognito IME for EditTextPreference + if (pref is EditTextPreference) { + val setListener = pref.getOnBindEditTextListener() + pref.setOnBindEditTextListener { + setListener?.onBindEditText(it) + it.setIncognito(lifecycleScope) + } + } + } + + return sourceScreen + } + fun getInstance(sourceId: Long, onCloseAction: (() -> Unit)? = null): AnimeSourcePreferencesFragment { + val fragment = AnimeSourcePreferencesFragment() + fragment.arguments = bundleOf(SOURCE_ID to sourceId) + fragment.onCloseAction = onCloseAction + return fragment + } + + companion object { //idk why it needs both + private const val SOURCE_ID = "source_id" + } +} diff --git a/app/src/main/java/ani/dantotsu/settings/extensionprefs/MangaPreferenceFragmentCompat.kt b/app/src/main/java/ani/dantotsu/settings/extensionprefs/MangaPreferenceFragmentCompat.kt new file mode 100644 index 00000000..d571ef77 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/extensionprefs/MangaPreferenceFragmentCompat.kt @@ -0,0 +1,78 @@ +package ani.dantotsu.settings.extensionprefs + +import android.content.Context +import android.os.Bundle +import android.view.View +import android.widget.FrameLayout +import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope +import androidx.preference.DialogPreference +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.forEach +import androidx.preference.getOnBindEditTextListener +import androidx.viewpager2.widget.ViewPager2 +import ani.dantotsu.R +import ani.dantotsu.settings.ExtensionsActivity +import com.google.android.material.tabs.TabLayout +import com.google.android.material.textfield.TextInputLayout +import eu.kanade.tachiyomi.PreferenceScreen +import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore +import eu.kanade.tachiyomi.source.ConfigurableSource +import eu.kanade.tachiyomi.source.manga.getPreferenceKey +import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito +import tachiyomi.domain.source.manga.service.MangaSourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class MangaSourcePreferencesFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceScreen = populateMangaPreferenceScreen() + } + private var onCloseAction: (() -> Unit)? = null + + override fun onDestroyView() { + super.onDestroyView() + onCloseAction?.invoke() + + } + + fun populateMangaPreferenceScreen(): PreferenceScreen { + val sourceId = requireArguments().getLong(SOURCE_ID) + val source = Injekt.get().get(sourceId)!! + check(source is ConfigurableSource) + val sharedPreferences = requireContext().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE) + val dataStore = SharedPreferencesDataStore(sharedPreferences) + preferenceManager.preferenceDataStore = dataStore + val sourceScreen = preferenceManager.createPreferenceScreen(requireContext()) + source.setupPreferenceScreen(sourceScreen) + sourceScreen.forEach { pref -> + pref.isIconSpaceReserved = false + if (pref is DialogPreference) { + pref.dialogTitle = pref.title + println("pref.dialogTitle: ${pref.dialogTitle}") + } + + // Apply incognito IME for EditTextPreference + if (pref is EditTextPreference) { + val setListener = pref.getOnBindEditTextListener() + pref.setOnBindEditTextListener { + setListener?.onBindEditText(it) + it.setIncognito(lifecycleScope) + } + } + } + + return sourceScreen + } + fun getInstance(sourceId: Long, onCloseAction: (() -> Unit)? = null): MangaSourcePreferencesFragment { + val fragment = MangaSourcePreferencesFragment() + fragment.arguments = bundleOf(SOURCE_ID to sourceId) + fragment.onCloseAction = onCloseAction + return fragment + } + + companion object { + private const val SOURCE_ID = "source_id" + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/UnmeteredSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/UnmeteredSource.kt new file mode 100644 index 00000000..840a223a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/UnmeteredSource.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.animesource + +/** + * A source that explicitly doesn't require traffic considerations. + * + * This typically applies for self-hosted sources. + */ +interface UnmeteredSource diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt new file mode 100644 index 00000000..ae11e587 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/SharedPreferencesDataStore.kt @@ -0,0 +1,68 @@ +package eu.kanade.tachiyomi.data.preference + +import android.content.SharedPreferences +import androidx.core.content.edit +import androidx.preference.PreferenceDataStore + +class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() { + + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + return prefs.getBoolean(key, defValue) + } + + override fun putBoolean(key: String?, value: Boolean) { + prefs.edit { + putBoolean(key, value) + } + } + + override fun getInt(key: String?, defValue: Int): Int { + return prefs.getInt(key, defValue) + } + + override fun putInt(key: String?, value: Int) { + prefs.edit { + putInt(key, value) + } + } + + override fun getLong(key: String?, defValue: Long): Long { + return prefs.getLong(key, defValue) + } + + override fun putLong(key: String?, value: Long) { + prefs.edit { + putLong(key, value) + } + } + + override fun getFloat(key: String?, defValue: Float): Float { + return prefs.getFloat(key, defValue) + } + + override fun putFloat(key: String?, value: Float) { + prefs.edit { + putFloat(key, value) + } + } + + override fun getString(key: String?, defValue: String?): String? { + return prefs.getString(key, defValue) + } + + override fun putString(key: String?, value: String?) { + prefs.edit { + putString(key, value) + } + } + + override fun getStringSet(key: String?, defValues: MutableSet?): MutableSet? { + return prefs.getStringSet(key, defValues) + } + + override fun putStringSet(key: String?, values: MutableSet?) { + prefs.edit { + putStringSet(key, values) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt new file mode 100644 index 00000000..79b30b9e --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt @@ -0,0 +1,85 @@ +package eu.kanade.tachiyomi.source.anime + +import android.content.Context +import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource +import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import tachiyomi.domain.source.anime.model.AnimeSourceData +import tachiyomi.domain.source.anime.model.StubAnimeSource +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.source.local.entries.anime.LocalAnimeSource +import java.util.concurrent.ConcurrentHashMap + +class AndroidAnimeSourceManager( + private val context: Context, + private val extensionManager: AnimeExtensionManager, +) : AnimeSourceManager { + + private val scope = CoroutineScope(Job() + Dispatchers.IO) + + private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap()) + + private val stubSourcesMap = ConcurrentHashMap() + + override val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } + + init { + scope.launch { + extensionManager.installedExtensionsFlow + .collectLatest { extensions -> + val mutableMap = ConcurrentHashMap( + mapOf( + LocalAnimeSource.ID to LocalAnimeSource( + context, + ), + ), + ) + extensions.forEach { extension -> + extension.sources.forEach { + mutableMap[it.id] = it + registerStubSource(it.toSourceData()) + } + } + sourcesMapFlow.value = mutableMap + } + } + + } + + override fun get(sourceKey: Long): AnimeSource? { + return sourcesMapFlow.value[sourceKey] + } + + override fun getOrStub(sourceKey: Long): AnimeSource { + return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) { + runBlocking { createStubSource(sourceKey) } + } + } + + override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance() + + override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance() + + override fun getStubSources(): List { + val onlineSourceIds = getOnlineSources().map { it.id } + return stubSourcesMap.values.filterNot { it.id in onlineSourceIds } + } + + private fun registerStubSource(sourceData: AnimeSourceData) { + + } + + private suspend fun createStubSource(id: Long): StubAnimeSource { + return StubAnimeSource(AnimeSourceData(id, "", "")) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt new file mode 100644 index 00000000..a359d765 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.source.anime + +import android.graphics.drawable.Drawable +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import tachiyomi.domain.source.anime.model.AnimeSourceData +import tachiyomi.domain.source.anime.model.StubAnimeSource +import tachiyomi.source.local.entries.anime.isLocal +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +fun AnimeSource.icon(): Drawable? = Injekt.get().getAppIconForSource(this.id) + +fun AnimeSource.getPreferenceKey(): String = "source_$id" + +fun AnimeSource.toSourceData(): AnimeSourceData = AnimeSourceData(id = id, lang = lang, name = name) + +fun AnimeSource.getNameForAnimeInfo(): String { + val preferences = Injekt.get() + val enabledLanguages = preferences.enabledLanguages().get() + .filterNot { it in listOf("all", "other") } + val hasOneActiveLanguages = enabledLanguages.size == 1 + val isInEnabledLanguages = lang in enabledLanguages + return when { + // For edge cases where user disables a source they got manga of in their library. + hasOneActiveLanguages && !isInEnabledLanguages -> toString() + // Hide the language tag when only one language is used. + hasOneActiveLanguages && isInEnabledLanguages -> name + else -> toString() + } +} + +fun AnimeSource.isLocalOrStub(): Boolean = isLocal() || this is StubAnimeSource diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt new file mode 100644 index 00000000..08bd2b61 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt @@ -0,0 +1,84 @@ +package eu.kanade.tachiyomi.source.manga + +import android.content.Context +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.MangaSource +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import tachiyomi.domain.source.manga.model.MangaSourceData +import tachiyomi.domain.source.manga.model.StubMangaSource +import tachiyomi.domain.source.manga.service.MangaSourceManager +import tachiyomi.source.local.entries.manga.LocalMangaSource +import java.util.concurrent.ConcurrentHashMap + +class AndroidMangaSourceManager( + private val context: Context, + private val extensionManager: MangaExtensionManager, +) : MangaSourceManager { + + private val scope = CoroutineScope(Job() + Dispatchers.IO) + + private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap()) + + private val stubSourcesMap = ConcurrentHashMap() + + override val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } + + init { + scope.launch { + extensionManager.installedExtensionsFlow + .collectLatest { extensions -> + val mutableMap = ConcurrentHashMap( + mapOf( + LocalMangaSource.ID to LocalMangaSource( + context, + ), + ), + ) + extensions.forEach { extension -> + extension.sources.forEach { + mutableMap[it.id] = it + registerStubSource(it.toSourceData()) + } + } + sourcesMapFlow.value = mutableMap + } + } + } + + override fun get(sourceKey: Long): MangaSource? { + return sourcesMapFlow.value[sourceKey] + } + + override fun getOrStub(sourceKey: Long): MangaSource { + return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) { + runBlocking { createStubSource(sourceKey) } + } + } + + override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance() + + override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance() + + override fun getStubSources(): List { + val onlineSourceIds = getOnlineSources().map { it.id } + return stubSourcesMap.values.filterNot { it.id in onlineSourceIds } + } + + private fun registerStubSource(sourceData: MangaSourceData) { + + } + + private suspend fun createStubSource(id: Long): StubMangaSource { + return StubMangaSource(MangaSourceData(id, "", "")) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt new file mode 100644 index 00000000..afd17cea --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.source.manga + +import android.graphics.drawable.Drawable +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import eu.kanade.tachiyomi.source.MangaSource +import tachiyomi.domain.source.manga.model.MangaSourceData +import tachiyomi.domain.source.manga.model.StubMangaSource +import tachiyomi.source.local.entries.manga.isLocal +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +fun MangaSource.icon(): Drawable? = Injekt.get().getAppIconForSource(this.id) + +fun MangaSource.getPreferenceKey(): String = "source_$id" + +fun MangaSource.toSourceData(): MangaSourceData = MangaSourceData(id = id, lang = lang, name = name) + +fun MangaSource.getNameForMangaInfo(): String { + val preferences = Injekt.get() + val enabledLanguages = preferences.enabledLanguages().get() + .filterNot { it in listOf("all", "other") } + val hasOneActiveLanguages = enabledLanguages.size == 1 + val isInEnabledLanguages = lang in enabledLanguages + return when { + // For edge cases where user disables a source they got manga of in their library. + hasOneActiveLanguages && !isInEnabledLanguages -> toString() + // Hide the language tag when only one language is used. + hasOneActiveLanguages && isInEnabledLanguages -> name + else -> toString() + } +} + +fun MangaSource.isLocalOrStub(): Boolean = isLocal() || this is StubMangaSource diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt new file mode 100644 index 00000000..8e3141a2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt @@ -0,0 +1,117 @@ +package eu.kanade.tachiyomi.util.storage + +import android.content.Context +import android.media.MediaScannerConnection +import android.net.Uri +import android.os.Environment +import android.os.StatFs +import androidx.core.content.ContextCompat +import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.util.lang.Hash +import java.io.File + +object DiskUtil { + + fun hashKeyForDisk(key: String): String { + return Hash.md5(key) + } + + fun getDirectorySize(f: File): Long { + var size: Long = 0 + if (f.isDirectory) { + for (file in f.listFiles().orEmpty()) { + size += getDirectorySize(file) + } + } else { + size = f.length() + } + return size + } + + /** + * Gets the available space for the disk that a file path points to, in bytes. + */ + fun getAvailableStorageSpace(f: UniFile): Long { + return try { + val stat = StatFs(f.uri.path) + stat.availableBlocksLong * stat.blockSizeLong + } catch (_: Exception) { + -1L + } + } + + /** + * Returns the root folders of all the available external storages. + */ + fun getExternalStorages(context: Context): List { + return ContextCompat.getExternalFilesDirs(context, null) + .filterNotNull() + .mapNotNull { + val file = File(it.absolutePath.substringBefore("/Android/")) + val state = Environment.getExternalStorageState(file) + if (state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY) { + file + } else { + null + } + } + } + + /** + * Don't display downloaded chapters in gallery apps creating `.nomedia`. + */ + fun createNoMediaFile(dir: UniFile?, context: Context?) { + if (dir != null && dir.exists()) { + val nomedia = dir.findFile(NOMEDIA_FILE) + if (nomedia == null) { + dir.createFile(NOMEDIA_FILE) + context?.let { scanMedia(it, dir.uri) } + } + } + } + + /** + * Scans the given file so that it can be shown in gallery apps, for example. + */ + fun scanMedia(context: Context, uri: Uri) { + MediaScannerConnection.scanFile(context, arrayOf(uri.path), null, null) + } + + /** + * Mutate the given filename to make it valid for a FAT filesystem, + * replacing any invalid characters with "_". This method doesn't allow hidden files (starting + * with a dot), but you can manually add it later. + */ + fun buildValidFilename(origName: String): String { + val name = origName.trim('.', ' ') + if (name.isEmpty()) { + return "(invalid)" + } + val sb = StringBuilder(name.length) + name.forEach { c -> + if (isValidFatFilenameChar(c)) { + sb.append(c) + } else { + sb.append('_') + } + } + // Even though vfat allows 255 UCS-2 chars, we might eventually write to + // ext4 through a FUSE layer, so use that limit minus 15 reserved characters. + return sb.toString().take(240) + } + + /** + * Returns true if the given character is a valid filename character, false otherwise. + */ + private fun isValidFatFilenameChar(c: Char): Boolean { + if (0x00.toChar() <= c && c <= 0x1f.toChar()) { + return false + } + return when (c) { + '"', '*', '/', ':', '<', '>', '?', '\\', '|', 0x7f.toChar() -> false + else -> true + } + } + + const val NOMEDIA_FILE = ".nomedia" +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt new file mode 100644 index 00000000..4428fb9a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt @@ -0,0 +1,10 @@ +@file:Suppress("PackageDirectoryMismatch") + +package androidx.preference + +/** + * Returns package-private [EditTextPreference.getOnBindEditTextListener] + */ +fun EditTextPreference.getOnBindEditTextListener(): EditTextPreference.OnBindEditTextListener? { + return onBindEditTextListener +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt new file mode 100644 index 00000000..272ac155 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt @@ -0,0 +1,62 @@ +package eu.kanade.tachiyomi.widget + +import android.content.Context +import android.util.AttributeSet +import android.widget.EditText +import androidx.core.view.inputmethod.EditorInfoCompat +import com.google.android.material.textfield.TextInputEditText +import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +/** + * A custom [TextInputEditText] that sets [EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING] to imeOptions + * if [BasePreferences.incognitoMode] is true. Some IMEs may not respect this flag. + * + * @see setIncognito + */ +class TachiyomiTextInputEditText @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : TextInputEditText(context, attrs, defStyleAttr) { + + private var scope: CoroutineScope? = null + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + setIncognito(scope!!) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + scope?.cancel() + scope = null + } + + companion object { + /** + * Sets Flow to this [EditText] that sets [EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING] to imeOptions + * if [BasePreferences.incognitoMode] is true. Some IMEs may not respect this flag. + */ + fun EditText.setIncognito(viewScope: CoroutineScope) { + Injekt.get().incognitoMode().changes() + .onEach { + imeOptions = if (it) { + imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv() + } + } + .launchIn(viewScope) + } + } +} diff --git a/app/src/main/java/tachiyomi/core/metadata/tachiyomi/AnimeDetails.kt b/app/src/main/java/tachiyomi/core/metadata/tachiyomi/AnimeDetails.kt new file mode 100644 index 00000000..414d4873 --- /dev/null +++ b/app/src/main/java/tachiyomi/core/metadata/tachiyomi/AnimeDetails.kt @@ -0,0 +1,13 @@ +package tachiyomi.core.metadata.tachiyomi + +import kotlinx.serialization.Serializable + +@Serializable +class AnimeDetails( + val title: String? = null, + val author: String? = null, + val artist: String? = null, + val description: String? = null, + val genre: List? = null, + val status: Int? = null, +) diff --git a/app/src/main/java/tachiyomi/core/metadata/tachiyomi/MangaDetails.kt b/app/src/main/java/tachiyomi/core/metadata/tachiyomi/MangaDetails.kt new file mode 100644 index 00000000..7768986e --- /dev/null +++ b/app/src/main/java/tachiyomi/core/metadata/tachiyomi/MangaDetails.kt @@ -0,0 +1,13 @@ +package tachiyomi.core.metadata.tachiyomi + +import kotlinx.serialization.Serializable + +@Serializable +class MangaDetails( + val title: String? = null, + val author: String? = null, + val artist: String? = null, + val description: String? = null, + val genre: List? = null, + val status: Int? = null, +) diff --git a/app/src/main/java/tachiyomi/domain/entries/TriStateFilter.kt b/app/src/main/java/tachiyomi/domain/entries/TriStateFilter.kt new file mode 100644 index 00000000..f56bfbfd --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/entries/TriStateFilter.kt @@ -0,0 +1,22 @@ +package tachiyomi.domain.entries + +enum class TriStateFilter { + DISABLED, // Disable filter + ENABLED_IS, // Enabled with "is" filter + ENABLED_NOT, // Enabled with "not" filter + ; + + fun next(): TriStateFilter { + return when (this) { + DISABLED -> ENABLED_IS + ENABLED_IS -> ENABLED_NOT + ENABLED_NOT -> DISABLED + } + } +} + +inline fun applyFilter(filter: TriStateFilter, predicate: () -> Boolean): Boolean = when (filter) { + TriStateFilter.DISABLED -> true + TriStateFilter.ENABLED_IS -> predicate() + TriStateFilter.ENABLED_NOT -> !predicate() +} diff --git a/app/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt b/app/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt new file mode 100644 index 00000000..ede00f27 --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt @@ -0,0 +1,134 @@ +package tachiyomi.domain.entries.anime.model + +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import tachiyomi.domain.entries.TriStateFilter +import java.io.Serializable +import kotlin.math.pow + +data class Anime( + val id: Long, + val source: Long, + val favorite: Boolean, + val lastUpdate: Long, + val nextUpdate: Long, + val calculateInterval: Int, + val dateAdded: Long, + val viewerFlags: Long, + val episodeFlags: Long, + val coverLastModified: Long, + val url: String, + val title: String, + val artist: String?, + val author: String?, + val description: String?, + val genre: List?, + val status: Long, + val thumbnailUrl: String?, + val updateStrategy: UpdateStrategy, + val initialized: Boolean, +) : Serializable { + + val sorting: Long + get() = episodeFlags and EPISODE_SORTING_MASK + + val displayMode: Long + get() = episodeFlags and EPISODE_DISPLAY_MASK + + val unseenFilterRaw: Long + get() = episodeFlags and EPISODE_UNSEEN_MASK + + val downloadedFilterRaw: Long + get() = episodeFlags and EPISODE_DOWNLOADED_MASK + + val bookmarkedFilterRaw: Long + get() = episodeFlags and EPISODE_BOOKMARKED_MASK + + val skipIntroLength: Int + get() = (viewerFlags and ANIME_INTRO_MASK).toInt() + + val nextEpisodeToAir: Int + get() = (viewerFlags and ANIME_AIRING_EPISODE_MASK).removeHexZeros(zeros = 2).toInt() + + val nextEpisodeAiringAt: Long + get() = (viewerFlags and ANIME_AIRING_TIME_MASK).removeHexZeros(zeros = 6) + + val unseenFilter: TriStateFilter + get() = when (unseenFilterRaw) { + EPISODE_SHOW_UNSEEN -> TriStateFilter.ENABLED_IS + EPISODE_SHOW_SEEN -> TriStateFilter.ENABLED_NOT + else -> TriStateFilter.DISABLED + } + + val bookmarkedFilter: TriStateFilter + get() = when (bookmarkedFilterRaw) { + EPISODE_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS + EPISODE_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT + else -> TriStateFilter.DISABLED + } + + fun sortDescending(): Boolean { + return episodeFlags and EPISODE_SORT_DIR_MASK == EPISODE_SORT_DESC + } + + private fun Long.removeHexZeros(zeros: Int): Long { + val hex = 16.0 + return this.div(hex.pow(zeros)).toLong() + } + + companion object { + // Generic filter that does not filter anything + const val SHOW_ALL = 0x00000000L + + const val EPISODE_SORT_DESC = 0x00000000L + const val EPISODE_SORT_ASC = 0x00000001L + const val EPISODE_SORT_DIR_MASK = 0x00000001L + + const val EPISODE_SHOW_UNSEEN = 0x00000002L + const val EPISODE_SHOW_SEEN = 0x00000004L + const val EPISODE_UNSEEN_MASK = 0x00000006L + + const val EPISODE_SHOW_DOWNLOADED = 0x00000008L + const val EPISODE_SHOW_NOT_DOWNLOADED = 0x00000010L + const val EPISODE_DOWNLOADED_MASK = 0x00000018L + + const val EPISODE_SHOW_BOOKMARKED = 0x00000020L + const val EPISODE_SHOW_NOT_BOOKMARKED = 0x00000040L + const val EPISODE_BOOKMARKED_MASK = 0x00000060L + + const val EPISODE_SORTING_SOURCE = 0x00000000L + const val EPISODE_SORTING_NUMBER = 0x00000100L + const val EPISODE_SORTING_UPLOAD_DATE = 0x00000200L + const val EPISODE_SORTING_MASK = 0x00000300L + + const val EPISODE_DISPLAY_NAME = 0x00000000L + const val EPISODE_DISPLAY_NUMBER = 0x00100000L + const val EPISODE_DISPLAY_MASK = 0x00100000L + + const val ANIME_INTRO_MASK = 0x000000000000FFL + const val ANIME_AIRING_EPISODE_MASK = 0x00000000FFFF00L + const val ANIME_AIRING_TIME_MASK = 0xFFFFFFFF000000L + + fun create() = Anime( + id = -1L, + url = "", + title = "", + source = -1L, + favorite = false, + lastUpdate = 0L, + nextUpdate = 0L, + calculateInterval = 0, + dateAdded = 0L, + viewerFlags = 0L, + episodeFlags = 0L, + coverLastModified = 0L, + artist = null, + author = null, + description = null, + genre = null, + status = 0L, + thumbnailUrl = null, + updateStrategy = UpdateStrategy.ALWAYS_UPDATE, + initialized = false, + ) + } +} diff --git a/app/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt b/app/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt new file mode 100644 index 00000000..354abffc --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt @@ -0,0 +1,115 @@ +package tachiyomi.domain.entries.manga.model + +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import tachiyomi.domain.entries.TriStateFilter +import java.io.Serializable + +data class Manga( + val id: Long, + val source: Long, + val favorite: Boolean, + val lastUpdate: Long, + val nextUpdate: Long, + val calculateInterval: Int, + val dateAdded: Long, + val viewerFlags: Long, + val chapterFlags: Long, + val coverLastModified: Long, + val url: String, + val title: String, + val artist: String?, + val author: String?, + val description: String?, + val genre: List?, + val status: Long, + val thumbnailUrl: String?, + val updateStrategy: UpdateStrategy, + val initialized: Boolean, +) : Serializable { + + val sorting: Long + get() = chapterFlags and CHAPTER_SORTING_MASK + + val displayMode: Long + get() = chapterFlags and CHAPTER_DISPLAY_MASK + + val unreadFilterRaw: Long + get() = chapterFlags and CHAPTER_UNREAD_MASK + + val downloadedFilterRaw: Long + get() = chapterFlags and CHAPTER_DOWNLOADED_MASK + + val bookmarkedFilterRaw: Long + get() = chapterFlags and CHAPTER_BOOKMARKED_MASK + + val unreadFilter: TriStateFilter + get() = when (unreadFilterRaw) { + CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS + CHAPTER_SHOW_READ -> TriStateFilter.ENABLED_NOT + else -> TriStateFilter.DISABLED + } + + val bookmarkedFilter: TriStateFilter + get() = when (bookmarkedFilterRaw) { + CHAPTER_SHOW_BOOKMARKED -> TriStateFilter.ENABLED_IS + CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateFilter.ENABLED_NOT + else -> TriStateFilter.DISABLED + } + + fun sortDescending(): Boolean { + return chapterFlags and CHAPTER_SORT_DIR_MASK == CHAPTER_SORT_DESC + } + + companion object { + // Generic filter that does not filter anything + const val SHOW_ALL = 0x00000000L + + const val CHAPTER_SORT_DESC = 0x00000000L + const val CHAPTER_SORT_ASC = 0x00000001L + const val CHAPTER_SORT_DIR_MASK = 0x00000001L + + const val CHAPTER_SHOW_UNREAD = 0x00000002L + const val CHAPTER_SHOW_READ = 0x00000004L + const val CHAPTER_UNREAD_MASK = 0x00000006L + + const val CHAPTER_SHOW_DOWNLOADED = 0x00000008L + const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010L + const val CHAPTER_DOWNLOADED_MASK = 0x00000018L + + const val CHAPTER_SHOW_BOOKMARKED = 0x00000020L + const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040L + const val CHAPTER_BOOKMARKED_MASK = 0x00000060L + + const val CHAPTER_SORTING_SOURCE = 0x00000000L + const val CHAPTER_SORTING_NUMBER = 0x00000100L + const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200L + const val CHAPTER_SORTING_MASK = 0x00000300L + + const val CHAPTER_DISPLAY_NAME = 0x00000000L + const val CHAPTER_DISPLAY_NUMBER = 0x00100000L + const val CHAPTER_DISPLAY_MASK = 0x00100000L + + fun create() = Manga( + id = -1L, + url = "", + title = "", + source = -1L, + favorite = false, + lastUpdate = 0L, + nextUpdate = 0L, + calculateInterval = 0, + dateAdded = 0L, + viewerFlags = 0L, + chapterFlags = 0L, + coverLastModified = 0L, + artist = null, + author = null, + description = null, + genre = null, + status = 0L, + thumbnailUrl = null, + updateStrategy = UpdateStrategy.ALWAYS_UPDATE, + initialized = false, + ) + } +} diff --git a/app/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt b/app/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt new file mode 100644 index 00000000..c40410a3 --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt @@ -0,0 +1,119 @@ +package tachiyomi.domain.items.episode.service + +/** + * -R> = regex conversion. + */ +object EpisodeRecognition { + + private const val NUMBER_PATTERN = """([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""" + + /** + * All cases with Ch.xx + * Mokushiroku Alice Vol.1 Ch. 4: Misrepresentation -R> 4 + */ + private val basic = Regex("""(?<=ep\.) *$NUMBER_PATTERN""") + + /** + * Example: Bleach 567: Down With Snowwhite -R> 567 + */ + private val number = Regex(NUMBER_PATTERN) + + /** + * Regex used to remove unwanted tags + * Example Prison School 12 v.1 vol004 version1243 volume64 -R> Prison School 12 + */ + private val unwanted = Regex("""\b(?:v|ver|vol|version|volume|season|s)[^a-z]?[0-9]+""") + + /** + * Regex used to remove unwanted whitespace + * Example One Piece 12 special -R> One Piece 12special + */ + private val unwantedWhiteSpace = Regex("""\s(?=extra|special|omake)""") + + fun parseEpisodeNumber(animeTitle: String, episodeName: String, episodeNumber: Float? = null): Float { + // If episode number is known return. + if (episodeNumber != null && (episodeNumber == -2f || episodeNumber > -1f)) { + return episodeNumber + } + + // Get chapter title with lower case + var name = episodeName.lowercase() + + // Remove anime title from episode title. + name = name.replace(animeTitle.lowercase(), "").trim() + + // Remove comma's or hyphens. + name = name.replace(',', '.').replace('-', '.') + + // Remove unwanted white spaces. + name = unwantedWhiteSpace.replace(name, "") + + // Remove unwanted tags. + name = unwanted.replace(name, "") + + // Check base case ch.xx + basic.find(name)?.let { return getEpisodeNumberFromMatch(it) } + + // Take the first number encountered. + number.find(name)?.let { return getEpisodeNumberFromMatch(it) } + + return episodeNumber ?: -1f + } + + /** + * Check if episode number is found and return it + * @param match result of regex + * @return chapter number if found else null + */ + private fun getEpisodeNumberFromMatch(match: MatchResult): Float { + return match.let { + val initial = it.groups[1]?.value?.toFloat()!! + val subChapterDecimal = it.groups[2]?.value + val subChapterAlpha = it.groups[3]?.value + val addition = checkForDecimal(subChapterDecimal, subChapterAlpha) + initial.plus(addition) + } + } + + /** + * Check for decimal in received strings + * @param decimal decimal value of regex + * @param alpha alpha value of regex + * @return decimal/alpha float value + */ + private fun checkForDecimal(decimal: String?, alpha: String?): Float { + if (!decimal.isNullOrEmpty()) { + return decimal.toFloat() + } + + if (!alpha.isNullOrEmpty()) { + if (alpha.contains("extra")) { + return .99f + } + + if (alpha.contains("omake")) { + return .98f + } + + if (alpha.contains("special")) { + return .97f + } + + val trimmedAlpha = alpha.trimStart('.') + if (trimmedAlpha.length == 1) { + return parseAlphaPostFix(trimmedAlpha[0]) + } + } + + return .0f + } + + /** + * x.a -> x.1, x.b -> x.2, etc + */ + private fun parseAlphaPostFix(alpha: Char): Float { + val number = alpha.code - ('a'.code - 1) + if (number >= 10) return 0f + return number / 10f + } +} diff --git a/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSource.kt b/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSource.kt new file mode 100644 index 00000000..182e9653 --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSource.kt @@ -0,0 +1,25 @@ +package tachiyomi.domain.source.anime.model + +data class AnimeSource( + val id: Long, + val lang: String, + val name: String, + val supportsLatest: Boolean, + val isStub: Boolean, + val pin: Pins = Pins.unpinned, + val isUsedLast: Boolean = false, +) { + + val visualName: String + get() = when { + lang.isEmpty() -> name + else -> "$name (${lang.uppercase()})" + } + + val key: () -> String = { + when { + isUsedLast -> "$id-lastused" + else -> "$id" + } + } +} diff --git a/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceWithCount.kt b/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceWithCount.kt new file mode 100644 index 00000000..65b928bc --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/source/anime/model/AnimeSourceWithCount.kt @@ -0,0 +1,13 @@ +package tachiyomi.domain.source.anime.model + +data class AnimeSourceWithCount( + val source: AnimeSource, + val count: Long, +) { + + val id: Long + get() = source.id + + val name: String + get() = source.name +} diff --git a/app/src/main/java/tachiyomi/domain/source/anime/model/Pin.kt b/app/src/main/java/tachiyomi/domain/source/anime/model/Pin.kt new file mode 100644 index 00000000..e4430d3a --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/source/anime/model/Pin.kt @@ -0,0 +1,42 @@ +package tachiyomi.domain.source.anime.model + +sealed class Pin(val code: Int) { + object Unpinned : Pin(0b00) + object Pinned : Pin(0b01) + object Actual : Pin(0b10) +} + +inline fun Pins(builder: Pins.PinsBuilder.() -> Unit = {}): Pins { + return Pins.PinsBuilder().apply(builder).flags() +} + +fun Pins(vararg pins: Pin) = Pins { + pins.forEach { +it } +} + +data class Pins(val code: Int = Pin.Unpinned.code) { + + operator fun contains(pin: Pin): Boolean = pin.code and code == pin.code + + operator fun plus(pin: Pin): Pins = Pins(code or pin.code) + + operator fun minus(pin: Pin): Pins = Pins(code xor pin.code) + + companion object { + val unpinned = Pins(Pin.Unpinned) + + val pinned = Pins(Pin.Pinned, Pin.Actual) + } + + class PinsBuilder(var code: Int = 0) { + operator fun Pin.unaryPlus() { + this@PinsBuilder.code = code or this@PinsBuilder.code + } + + operator fun Pin.unaryMinus() { + this@PinsBuilder.code = code or this@PinsBuilder.code + } + + fun flags(): Pins = Pins(code) + } +} diff --git a/app/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt b/app/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt new file mode 100644 index 00000000..7631c49c --- /dev/null +++ b/app/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt @@ -0,0 +1,33 @@ +package tachiyomi.domain.source.anime.model + +import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.Video + +@Suppress("OverridingDeprecatedMember") +class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource { + + override val id: Long = sourceData.id + + override val name: String = sourceData.name.ifBlank { id.toString() } + + override val lang: String = sourceData.lang + + override suspend fun getAnimeDetails(anime: SAnime): SAnime { + throw AnimeSourceNotInstalledException() + } + + override suspend fun getEpisodeList(anime: SAnime): List { + throw AnimeSourceNotInstalledException() + } + + override suspend fun getVideoList(episode: SEpisode): List diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml index 844524a2..a6b47cdf 100644 --- a/app/src/main/res/layout/activity_media.xml +++ b/app/src/main/res/layout/activity_media.xml @@ -293,4 +293,12 @@ tools:ignore="ContentDescription,ImageContrastCheck" tools:srcCompat="@tools:sample/backgrounds/scenic[2]" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_anime_watch.xml b/app/src/main/res/layout/fragment_anime_watch.xml index 54d0545b..2a65e1ea 100644 --- a/app/src/main/res/layout/fragment_anime_watch.xml +++ b/app/src/main/res/layout/fragment_anime_watch.xml @@ -39,4 +39,6 @@ android:paddingBottom="128dp" tools:itemCount="1" tools:listitem="@layout/item_anime_watch" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_anime_page.xml b/app/src/main/res/layout/item_anime_page.xml index 0c5ecc09..ad473996 100644 --- a/app/src/main/res/layout/item_anime_page.xml +++ b/app/src/main/res/layout/item_anime_page.xml @@ -34,7 +34,7 @@ android:layout_marginEnd="8dp" android:layout_weight="1" android:hint="@string/anime" - android:textColorHint="?attr/colorOnPrimaryContainer" + android:textColorHint="?attr/colorPrimary" android:transitionName="@string/search" app:boxBackgroundColor="?attr/colorPrimaryContainer" app:boxCornerRadiusBottomEnd="28dp" @@ -42,7 +42,7 @@ app:boxCornerRadiusTopEnd="28dp" app:boxCornerRadiusTopStart="28dp" app:endIconDrawable="@drawable/ic_round_search_24" - app:endIconTint="?attr/colorOnPrimaryContainer" + app:endIconTint="?attr/colorPrimary" app:boxStrokeColor="@color/text_input_layout_stroke_color" app:hintAnimationEnabled="true"> @@ -64,7 +64,7 @@ android:layout_width="52dp" android:layout_height="match_parent" android:layout_marginTop="4dp" - android:backgroundTint="?attr/colorPrimaryContainer" + app:cardBackgroundColor="?attr/colorPrimaryContainer" app:strokeColor="@color/text_input_layout_stroke_color" app:cardCornerRadius="26dp"> @@ -73,7 +73,7 @@ android:layout_width="52dp" android:layout_height="52dp" android:scaleType="center" - android:tint="?attr/colorOnPrimaryContainer" + android:tint="?attr/colorPrimary" app:srcCompat="@drawable/ic_round_settings_24" tools:ignore="ContentDescription,ImageContrastCheck" /> diff --git a/app/src/main/res/layout/item_anime_watch.xml b/app/src/main/res/layout/item_anime_watch.xml index 9be21673..bea04864 100644 --- a/app/src/main/res/layout/item_anime_watch.xml +++ b/app/src/main/res/layout/item_anime_watch.xml @@ -82,19 +82,70 @@ android:textAllCaps="true" android:textColor="?android:attr/textColorSecondary" android:textSize="14sp" + android:ellipsize="end" + android:maxLines="1" tools:ignore="LabelFor,TextContrastCheck,DuplicateSpeakableTextCheck" /> + + + + + + + + + + + + @@ -21,6 +22,17 @@ android:textSize="18sp" android:text="Extension Name" /> + + @@ -66,7 +66,7 @@ android:layout_width="52dp" android:layout_height="match_parent" android:layout_marginTop="4dp" - android:backgroundTint="?attr/colorPrimaryContainer" + app:cardBackgroundColor="?attr/colorPrimaryContainer" app:strokeColor="@color/text_input_layout_stroke_color" app:cardCornerRadius="26dp"> @@ -75,7 +75,7 @@ android:layout_width="52dp" android:layout_height="52dp" android:scaleType="center" - android:tint="?attr/colorOnPrimaryContainer" + android:tint="?attr/colorPrimary" app:srcCompat="@drawable/ic_round_settings_24" tools:ignore="ContentDescription,ImageContrastCheck" /> diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 636da759..bbd3e021 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,5 +2,4 @@ - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..feb696dad8ebbd2a48e39dd52053fe03ee4324aa GIT binary patch literal 3038 zcmV<43nBDUNk&H23jhFDMM6+kP&iD=3jhEwN5Byf^#(z)bzE9h z`YRkUEW?c3THDs-Cc)j^B0Et28Gs$==xh&a8IP7NkXzffYc&h={k`|>eZRqb?>&!@ z0!Yb-30()|seZOqq`|S5RVB6+iYflGm zBPmiceZ#N^bAO;~{r?&*`i$~_B{S%FGPsq{LJh4}?pMK~6gk$?un$ zJ4ly9ZLL?Yqw8DwqSd-(-CN%rNdN$rk(44$*0yciws+6A@7A_$+qS)D+qQo8-2jpR zfMuk9!Rf4R+cs{qH`}&t+dhrfwr$&XyAN2m)wVsyALLB+AC+SWm16>}HOycTol2Rr zrE+#TaNEX_tay{{-m8D$XIKGh$;^wExFoc->Pi$toY$qQu4p7`?^4&x-*-jR9^$+k z_XfX4Ph?DZqyrS@M+_86&F%6I@jGHj^vf=7Uw((V{@hdvo@pgBsm{c+#Ggfk2x}D; z1qqxJk-qtoe}roy(qFs8Ty8fK{!@0zi}+MTiXu;b@t;RTj{m`Ips_4@K)f21u1QL- z1%<6a_B|d0gITMUM1SO3LaB-2nh^i}$UkDnc$7uNdm_q^kjxH5i1**ils;j_X2efr zkc$EHf&AmKd4}{kcSYbC{}~NA5Td_bB`EdspRhKJ6Dh=)g%E*K6Zw;rp(>LA9gkpL zMPsy{);fV`vOEq+l~8`sc{;qAS>@+nD|~Ox8Df&DI`e5C=#cUy@--clx?AWhageB zE}1S*D-YOe)~`(9qW;O`~wl-KL;?n>o^|pl!jYG{Yj*B{Z>~9S218w1^nF zKPG(mr%ptTA#(v`+wL|?GI!2k*FX-#peYwkzdc!>;h7c>Cq|||{W}U*-E9F%w{&jJ zW%CPD+t5=s>NxrWj`sD47)AKVUo|lkDBf_l6{y@r@3Cx7w~DkPt%ylY&=G-gO|Jx; z7~1PsBzjD1zDx4~`cKezESt^qhoV9}aXw}WL+m)<_&}%cv9@@b;x(JlMsxd$bqtqN(5(7FEVW_=_RQkBPcd6SG3QS@L2Ldt= z34GMy(nz(|v}NjgsYod3*j9|H9dsX)x>GQ)Cesw+ii61IKD#@U5WSHscjjmSyQgKa zF+Xc_8v2e%h3%1bMDvgA(Yuy`a=6~!-MGoM46&3#ZDa`RA#N6d7ztoX3Y$+zdw2~+ ziOyX`9dk0w^G6;1&~QgPy)VyFpGTt3VA2Il>NHsms%SWR7fK;Yrgv?e9U1hlp0BCl4zn_8s zwnLmpU0nweFrUJV12Aurw^KEK_a{- zDFmz$rC?}--XlWv9UB}RPK3)v ziT9KG$^n>;`A*tAF+|sSOge&qh2r6*Tr)#g z*HHva0i_2A~P*P_n^wa|YzQ2$W&SBCz8aI`IMNxF|Gw}22hQ^EqFp;B9Aclkn;nXG17MJww5}B>i1h&CI==TWF z8?%Ez$>58#2yj0gyCGpZiBNuLD#|KSiyLBzm0UnXAp%hZZS90R!9@d5CmPn!{5s41 zxs1YWg}6-+H=9H#Q)CMWYcFY~m>ahWHg-f7%~)J|JEm<=1}^{w5P7sKMS6UGhiyS& zR$>uu-ci(6)x|+15vCY+N_ z7}Gk&MDHax5Do(j*&ZgnuqM_R)IC7aKa2l z0QWz-n>ys*H**8b;xMx~{5K23Jec`mW^TwkW~7g4;4t+ZpMCb3Y2;8bEj*dA8OhTJ z^eu7B%{%$S38aZgQ3SKvagllua6BXnfD&Gxo?z2pQQyZB{!@7MDsG*Ql18_sw2H(L zR{&+$Wkn+31!cp8@D`1Z1S=08qj>Ys9Y)aQ?oU9$(?(JMKMd z+Q~DA^2d`XX9y7xBSp|O7`y_joY)8eP>7v=8347pv%2Ho57_iat;c-*#N$%q2o!2tc z3vvm$idNSCRVRNhSNyQdMc#*HE+>m)BLvhYPft11&u?sfeu4G+fvopWNYpB?W@VSN zGRk~mrI)#6rIpCVWXm5)Fl0xlvX+)8iu~*;cwV;tG87Z~tBI?8 zuU>&7Et%6L3o=04(uufsoCx}vbU8JMIg$MAR@Dt zt_@_vmd>E%@b7xb>8-&cKou}+OZ~3$^?1gDUpoF-TW<(3zuBvqv!uJwR|X8G%mv!g zo<}egVw8HBUM0ilY-xYWT+s67zM3-%&>_nJ4Zw6xH`|dfc>-$&A;wiNar(8MR?!|g zet4;&mAkbDS$2oBTknH`;wyl~yxBBcT3MvG8$*$*Do9{WxaWI!si-QEp<+0{HD@jz zuy|#+-)cmF235HE3Sc^J-n${U+w$?Ec)6hoG(%kZ_|c8Zk&aHkvGvMk(Pv}!VI3$Wz*i%%TmSUy-+$P?nwM)bzE9h z`YRkUEW?c3THDs-Cc)j^B0Et28Gs$==xh&a8IP7NkXzffYc&h={k`|>eZRqb?>&!@ z0!Yb-30()|seZOqq`|S5RVB6+iYflGm zBPmiceZ#N^bAO;~{r?&*`i$~_B{S%FGPsq{LJh4}?pMK~6gk$?un$ zJ4ly9ZLL?Yqw8DwqSd-(-CN%rNdN$rk(44$*0yciws+6A@7A_$+qS)D+qQo8-2jpR zfMuk9!Rf4R+cs{qH`}&t+dhrfwr$&XyAN2m)wVsyALLB+AC+SWm16>}HOycTol2Rr zrE+#TaNEX_tay{{-m8D$XIKGh$;^wExFoc->Pi$toY$qQu4p7`?^4&x-*-jR9^$+k z_XfX4Ph?DZqyrS@M+_86&F%6I@jGHj^vf=7Uw((V{@hdvo@pgBsm{c+#Ggfk2x}D; z1qqxJk-qtoe}roy(qFs8Ty8fK{!@0zi}+MTiXu;b@t;RTj{m`Ips_4@K)f21u1QL- z1%<6a_B|d0gITMUM1SO3LaB-2nh^i}$UkDnc$7uNdm_q^kjxH5i1**ils;j_X2efr zkc$EHf&AmKd4}{kcSYbC{}~NA5Td_bB`EdspRhKJ6Dh=)g%E*K6Zw;rp(>LA9gkpL zMPsy{);fV`vOEq+l~8`sc{;qAS>@+nD|~Ox8Df&DI`e5C=#cUy@--clx?AWhageB zE}1S*D-YOe)~`(9qW;O`~wl-KL;?n>o^|pl!jYG{Yj*B{Z>~9S218w1^nF zKPG(mr%ptTA#(v`+wL|?GI!2k*FX-#peYwkzdc!>;h7c>Cq|||{W}U*-E9F%w{&jJ zW%CPD+t5=s>NxrWj`sD47)AKVUo|lkDBf_l6{y@r@3Cx7w~DkPt%ylY&=G-gO|Jx; z7~1PsBzjD1zDx4~`cKezESt^qhoV9}aXw}WL+m)<_&}%cv9@@b;x(JlMsxd$bqtqN(5(7FEVW_=_RQkBPcd6SG3QS@L2Ldt= z34GMy(nz(|v}NjgsYod3*j9|H9dsX)x>GQ)Cesw+ii61IKD#@U5WSHscjjmSyQgKa zF+Xc_8v2e%h3%1bMDvgA(Yuy`a=6~!-MGoM46&3#ZDa`RA#N6d7ztoX3Y$+zdw2~+ ziOyX`9dk0w^G6;1&~QgPy)VyFpGTt3VA2Il>NHsms%SWR7fK;Yrgv?e9U1hlp0BCl4zn_8s zwnLmpU0nweFrUJV12Aurw^KEK_a{- zDFmz$rC?}--XlWv9UB}RPK3)v ziT9KG$^n>;`A*tAF+|sSOge&qh2r6*Tr)#g z*HHva0i_2A~P*P_n^wa|YzQ2$W&SBCz8aI`IMNxF|Gw}22hQ^EqFp;B9Aclkn;nXG17MJww5}B>i1h&CI==TWF z8?%Ez$>58#2yj0gyCGpZiBNuLD#|KSiyLBzm0UnXAp%hZZS90R!9@d5CmPn!{5s41 zxs1YWg}6-+H=9H#Q)CMWYcFY~m>ahWHg-f7%~)J|JEm<=1}^{w5P7sKMS6UGhiyS& zR$>uu-ci(6)x|+15vCY+N_ z7}Gk&MDHax5Do(j*&ZgnuqM_R)IC7aKa2l z0QWz-n>ys*H**8b;xMx~{5K23Jec`mW^TwkW~7g4;4t+ZpMCb3Y2;8bEj*dA8OhTJ z^eu7B%{%$S38aZgQ3SKvagllua6BXnfD&Gxo?z2pQQyZB{!@7MDsG*Ql18_sw2H(L zR{&+$Wkn+31!cp8@D`1Z1S=08qj>Ys9Y)aQ?oU9$(?(JMKMd z+Q~DA^2d`XX9y7xBSp|O7`y_joY)8eP>7v=8347pv%2Ho57_iat;c-*#N$%q2o!2tc z3vvm$idNSCRVRNhSNyQdMc#*HE+>m)BLvhYPft11&u?sfeu4G+fvopWNYpB?W@VSN zGRk~mrI)#6rIpCVWXm5)Fl0xlvX+)8iu~*;cwV;tG87Z~tBI?8 zuU>&7Et%6L3o=04(uufsoCx}vbU8JMIg$MAR@Dt zt_@_vmd>E%@b7xb>8-&cKou}+OZ~3$^?1gDUpoF-TW<(3zuBvqv!uJwR|X8G%mv!g zo<}egVw8HBUM0ilY-xYWT+s67zM3-%&>_nJ4Zw6xH`|dfc>-$&A;wiNar(8MR?!|g zet4;&mAkbDS$2oBTknH`;wyl~yxBBcT3MvG8$*$*Do9{WxaWI!si-QEp<+0{HD@jz zuy|#+-)cmF235HE3Sc^J-n${U+w$?Ec)6hoG(%kZ_|c8Zk&aHkvGvMk(Pv}!VI3$Wz*i%%TmSUy-+$P?nwMZ&TaUAC+qP}jw%yLQ?PMQtRNJ5=(^grNOE574m1omRCm=6X3Xr= zj_nj9=Bu!rSkC}Q@Qmtv!%gofP&&;??l3QE5 zYAb91d;k02$Njm#hr2{h11=#gxD%0j<}~88Kuis^U~;tmQzYfLGCos(sj99TcUi;T z-QDlt5h2fJSmq9y#|&q1I078O4j>7(ZPRva+qP{Zwr$(CZL4M5c3P?5ik*DhI_KQU zz-`+&*1V>7ckd0`w$>!CUQNnv?iy1Vmn^bUbEOcs_Aq!Y7q^O~>QZx>pOxF(KDhnT z<%n>sVH`c^XQoC$esl9dmN$P0w1`<~XWb%sEi^BshJYi6f-;35_XUf3#&UNX45~>e)3!t zo-FEVjj}tASQ-mUXItIiJ4Mk<@Km~EUxIKsX@n~L#gI8_jHFds@vH?=Ip}#?2!|$8ln?{k+HDygwW8z zv4R4#lDL&cac%9Kz|J2!9&J^vTTBu$lQb}MrSJ~cc25}x_Lv8X0CbJ@bp=+_`@fPj zv68HA^tZ5WxnmF)n*KmmG@2vQMntS>x0qeW%fEBe@`2o{)!cd6=a(Dxid(G83VpXfIb!EZU zpYZrq6z-0{;Q9xN8z1oiZ!$udh~p!D3z^%<+9mMPr2IIRFbpqID>dW_kfSN7EVCaJ zV$YnBAH(ch;EjFZMck@-M6i)4LiVmAw~m(~)3xd|;0}_bt5Br|GrInknzw-gffvLajF(%?J%yh2Hb%vN;t)s9rpJ03;Y&)d`L51#6#`jOF;||nLAF^#ZTZ^ zl+Bc5s?ju)7PW75ti6Owc|{QRtaP#X!8Uzpoo^C-g`cEgKw zs>Yv&G?`OPAZHH^C*pXd3AGiiB%%qJOy3W67t*OMKDdv1Mf@n5`4nY`XgWpXi8v17 zhoXqgtbEQj*^m3$6>s9EJzOMBqM6Cx^Ms~TZQFP3lwoU-+Ckhqt!3&3xCv(8vZLGb zH#%Lr(dnXKEQT1D8PyoqIkC@%AcldAEyACuJJuG^aBNX4kJhZtkNqPq2GDCcyniT< zRk&7EPiKtlC&&GBE800fQK!_vAJM##HPu$j!VQa7^b!a>$)pmWx zh(=|ZXgNBucH}dg>rMsv3|#Cj*T;lw)pLeZ4nuHRMTV!X>or1=SF>lx6F`P}akzE0 z7Z=)Q0I;&k)}H|RbHwsgiaeJhY2&!)&j7P!`Akm_7PbKEWwOGa>^EF)+e(o$Ckn z$T5{EY9;Gu)7LapmmWzGwVP6!i&6+mJzCB%c7=@xHnK;Kp{yKkCUK}SLDt)*LMm_7 zSA2z7RPl(-onIm}Hy!6#xWmpPmjt$2M^hlhg)36P%~qW42Wy0PTBjC0lOopy&&f_8 zqA}fQIMTMXoMQYM8xNdwsdFU}u9GaUKLKn@0x)k~UOnySNQ0iTz`g1@T@?F$n;2DS zuQPCom3yv$tvgGWIYhXKOttR;tV#kfP3+rRpyN4u$O7kk%P|P{m2fK)6mZdbj`>?` zKbqV5b4SKJE~F%kp?VI$)b<0dUN$t~?ZAD7wTuZdT6?>N!&smxy@PB=zD2E@t!2KqQ>VA`nFuT$ZqmUtHP& z=mJ=`dFm&`5&4(LdzjZvnjhT;KY!z*PVSN%OO*%{tu8ux1YlEBRu6Xl*oZgVepM&w zKsse8_39;Vc54byGLD3mfax%u`>|aBh5#OgseO@j{M3pVXuo!ux=)`t#f zgQ^;0I%1>EVGLlG+BM&o*L7IakU!^S1X_+~_*x!kc$zX?O(njmWCYr#^p-~a-Ugx( z7VS%g!xC)XQGI0~5yg2})dhLz@|&?;;m3&Sko5b<9}}O)m=WS>hKWj;AtEzKaLcq~ zm|_@H1oQOiQ&R$?V#*PsP8NUWkUrxaTlZ3z3epnRsK2?SevZ5v6u@QF`#aO{=TAET z*nvS3{sCOSan0$aPdY9oP)P-)R{*a291d#$mM995`2Fh>sDHrVsT98ePF^&2+)6Pc zHK4T|qBRzVEOq8pV*t0&Pp?k|006+I=ssV+npWIOHDR%%yy+Y(78EGno2y#8C5Y*8 zc0|vUz~E7wmEE)KK5Edr*h?9g5$k#VTDgh^HzYKd=}4W`2s>T)_!vO_@5S|$13Z0T z|4D88vqw^oD#{6xFcOg*CKF1Vx2n`N0WbrCbbiIyo!_4W7~Isl&+8A)U)Afe{lHWr z4MfAe)+|u5z@YRL!2jc#yTTa279boy#%GrN<-P$xJb&ZrP4yvf{aPn?$(kZmj-`Ug ztq~QWU(i%s6+l1qq5|NI!6xC?&qDnFuc@hA0RWEh@PXrJ8*+)Qdsm`9o+P-{P$a$@Z zdnEqNQs)Xwmbu1U&R)NMU1f$*t%)WBc&@JbPz}Hwz$aA{x|>zCM#Kb`q7c#3ffR20N@S~(KN&vX0%lc0tPBtU}5Sqv9tvsa`$A_Bgh6|0^kG?RHv!*OjA9? zU{;evr0-$IRcZAL2F1Gx%M^4C?obW}fB}FtfY?HB*7k{mlY2vL^nMIr58w$9+*b!ulZ_7SDBU`P(&>WA3p40vOSpt#syU{9&6f-+ zu^fLZdVXtox{#r`u_cv8*J&zk|BusO{J1yNN?chA-~iz9J3z?%dJtO=66-XTngBTo zrt|(uFr8CJDzzRY^vxgnef@V!D{-iw7{Ku3AppBU0N3@?|NVFR`e^XYp^rC?g%T&V SxEH`+W2LfjUvC`vk^%tR;}ZA) diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c30ada7655379b8daf5b10db109cd0998b0f67ba GIT binary patch literal 1946 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 QS}m7X>XysZs%POC0IkIx0ssI2 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..3276f97bda40502aa6c7d39c9e67147e1cca891b GIT binary patch literal 6266 zcmV-=7=`CjNk&F;7ytlQMM6+kP&iCw7ytk-kH8}kRfmGMZKRk#?Cl;15itQlQTf@2 zW6^pj3aSmEDP?Uca!e(c;RVf`y69BdN}-gbD5wyj)hv%eOD$`1Pqo_qSGMv^JYmkc zGiQb~ck+PBsf3(^!hz4p=2!$ZWSwjdF3L}-VfQn^M zLI#-{pomNtsQ^5S1~L?YKShv36NAI%usa|~G%|xl-9Bb5GzuLh3WYue+y;m~+fis# z)GRD6IwE;Xvga6&0Z9A$NeyXjIP7z-i&OK-vo@0g@!sw&wai z+qP}nwr!sCY}>YN+y1s~+lDl1+s;OMXYJjnSlhO3+qP}nw#_k<)a|0SZI3haf8}V~ zcI=UD+qPAvvzCw@s7g8~Gt(Ojuv**Y?7-~ey#Fb|Z6isNtS}<#m}O0p9FEIibeIEPx{OFz%WRm&4T- zsOqY_2C~@$9rmpv^-Sl42QTdaBke;G8#vc=NbI$O8!5-Ez^{O=Q^g|2>|r{b$q990 zJpA$W7Wg%G6lFK{d&d~|8TQqeyG zra?^e7&kEOU?MTW*x>P7mU(x&r5>Gi*+b7wiM85lx;gcgY0X z`ETdwD2r)7rgi|pv}>kBdj+j2GXenjf6Z!{`vJ-X!jD+HCC2BNRsq0NAZ#cm0KEI( ze~<|r@OfB=)6wuhA4x%&N%4W7vUX>#@~j$Ymn>tuIi_C#U|Jvn7C*QSCJR+PDtlgo z8yyYh{tXZ@K{EKi;J;Q%E|3+_E}63SJWNXfI!HU+T z!Y`ENv0=sjet z3y8roVRgVBD$CMcxwyZvqtRIa6NthDI2xX#lBMX9PY5iSK0`zS!bn8`V|poD786gRT&55TQ(c`VR#qevMg40%dF1!sBHS; zQQ5?!yz$thypdvfR5$VHY~#`0&ZEDR$7nZ?$sQh~ovL&;^C_+17nv%^Hb4wVP_kIn z?cu^<{G!Oc7^A`^GXV(o7AOrUeGG)61c>KBh8QxXkpCCz^-yCV4QlipsrlTd2YA)j zs+O89O5{?g0;|r~Q0so^_9SC_#3Fnb0fOH9zD4|Sk4UGZSdX`c-XMCpefXAw( z<_a_Q7Q?eBP#6)A-!wT)mn^ts#_B-GtvpcrM0f&9LAn?UrGS960XMLS}Zf&k9@gi06^ByIviKhFcD zPK2gBG9*y0rYVRM{h?ZNHnGQ;X&+S~U<}=ZTuGb`gj^H63CThzRk}b^6ib;xx!*$} z_JQJn5~)LyUUi;^dipih5tTnEc2oXYROx7nW6vP@R95<5Ko$fL9<&Sb_KI2nLcbF_ zha>?M$!VG3f(n-_qLl3kl$rLaMg+{Eg|^iZQ@kK_?oL!_Xqf@kIwi4R1^Gu3s6zq- zhW3ZlHPR9KxdI6FKM#Zqypbw~N-Zr@pw_@vE7nkSJYE<{2&e_8P^OxAH7x)sxhimO zWn_w7pk)knT42P%(r4nAnIVor(PbvR@|AWE@l8i~-T+WK@NaWhWJ|rJWehYJVaP6* zv$g(Htu#+9tIxoXq=(yy4>c+PA@2(G4H>kCZmV1ZTdUknd`o-rkE)685t%cR2TDh1 zqx)Y-xT>Me48smdZtUoVZHcdmF0S4`Isn~0U zg8aj=($ijc z>Ru9p7PCDhgdv-Xpc`U%0^5>|d?hMLBOSgkO05yO|E-a^tP$D48j*uztIHp3_4|jg zVJTNSQr@UH!hl^aCsoa8EJI2Gzgl$aN>r8CQH3a=%SK}(!!((#N^hG=#|xZszZ@we z^jhVlaa8hzIQrw_8hW!w9@UU9W?AbL0TXL)fD}kiZG@(?AonmXuRH9S6e*+KEGLI6 z@_|||tZsN!8v-_}%V{-@6;=R*JSeXXu?^6`_7qF0T$<;-R92Q=J-_^HTv7{a7~M^i zz4-<;Ko>y=VhA(z;PSp}`jndzuO&-K?k`cwcDS5+(v5f38)T22z=f$3NO}q-M&$CU zuVc&M)3hn)$T9eI*WzL-hEMj|KtR{!j(unkgc0N(&Y6%YaTA5JAXkL8GcsnuA$;Tx zkE&_~oaFKOmK`fs9@mOSz3+@Y(>cDXbVk!$W_hjiapi~iZi@~4(gX5!ZQksxc^(up@ zk~k;jfd#@`b{?E{-8eBqOlg%q7f~w7&+-wI&jKIrIJm8<38?5(K@b56*m^SJ3}@;S zCkZ#ve$_)vbHHS6rY4y!HCp2d16sW2|w)xtVoc6$!o!nF1~M^x=x%R1i5In z<>+&aFl^(~TZ>u2P@3K1OhQ0fEI_v;af;D`1y`L1=iE0sz+&XG1%yVyh#gZNyMqh{ zJ?W9pV?xTX_>CAXSa5~&;wlT40@Uo`3N`!q{dmQm1M$d z!PU--OV5Kd_K6dPAXHl%i>VB}j?u9OL)7q!76jZX4R-co>?TPZ7o7*EuMBNnGz}qD8Va8vP;+&H#iWMg@>I93?D!RQd16>|nvy#xVVu8mtPbLZte~<5I&B!n6za18^~-D(Em~2Mhda zTTnJ#3Df$NGr_0>=G|~Q#5Ak?_hELhz%Mt(TJ?lfX~<(-CU4uugA)lY10pS*jBdoX z56*>|rs;FrirIlB5`1!HoK;ImqYW9>K0}^{CbE2;7Va+T2VH^_L1q%a-dxNM3V3mD zgr+W}F(A&;Flic-%rO{&&^`v09xfm-g4Fk6j61!FvtecmA)$eo9TZ^H_()I`5&}UM zy9m2Z4NYR}8jF$oIaOt~$NcHWqQ@;}8U{lYyqtnS9098qECd1pswV0>J|c;&H$3h# zscaHI$KS=iAI^uR8yCV1hN!`aW(2~)q6q~20sKnR{hR>^gf=lK>1@Y4%1fk77<0ge z*YlGv>1_%$!CF2ZrYe@ zZ#Gye0?tt&%hG@((@wFducW52$s8Bn7fw|to+_rYFk3H{4Lh6(F$rAduNe$cVC3jX zP;DsEs*=?P1fl`Vm*#Q$kOYE+M<#Rhg2Nn>AgZt!MQg6o*Gvk(?tD~2J?$2i%i;nL z)U|lthgYQ8*d-3PcIq@LnWH~Et_^5~%;k074(Ea>+ctH=TzxPB4ZYbZ9_QnlfHJc^ zEJmWIoqERMh9|MDz42FLkU2K$7>i<41!TsvZRl{lBKv_DfrUxPIqkNb*%`sd#Q@0k zK>co(<|KOJsdZ?j2R!bG-tf5gu&Cw$!DfomwC5b-<7_$Yow~kTH z8=MLNr2#C3C=f=3wY##WA50F%j93koF`9OZdIU1NW@)h0WdV3P0E)coEOCm`c2L&* z1L+lf&RbC#4JDx9VE0SN&LjN16M&uC#9?~tK-PQa0#ajLYmzv}Xc5+KsE`);Bw;fm z^T@@e2}xfV09gp+{MbrV7bA0dowl)UXqhZtj~%#}(X>bOAW$BNQ9;!MT4rzd$;%`u z%+ZUh=sBDXpzR!`Ek?!U=63#QGm-*DDx|~(1R@idJH{znQ@O}O3W0(zW7&2XfQ6W(OR(E&Td9R zuuhkjx-6vnq^%6i(OuSu?jP0-V0|CMVSNx}J#tVA>oEt*Ne`S45t~e02=h5? zRLcn?2_89JZZW-U@3N{YcL;?+`2eWI1^G4nu7bbJDgH7z@P~m%Nw^eYW(9{;6g;*s z+4UWq?A;*ZBC&#;^m+()%>Y0NQjd+|bMgrPYvC^wAq&5%;ZB^H6_gAWi_&*wa9F^| z?Qv$nw5^M}oYZBnfIz200My{R?mpd$_)-biV$3YSzN!+G9K&FHvy0`|BPN;=@w&EN z2dO?e04PJj*;#x_p5R{%wiWTYQWl)DoNx3e|&5n>wlX&dje-eM0AW-tVMcLO?Dl_f~C{9udfVM>)0NSvedjl^6whvjwU-6^L>Ufm&F|I^W_pV?)v?&=Rxk_K~YtI$t>g^iQ ziHrFZoa}ph&t3Ugs1)`;50p(x08kHrcI=Q(@g@xY{}Tsy}+GtjvG149dm{|`V3FlF@4b>|973>PC1429qh;2m7N z4S`|*-~a+vjfSq|JsScV)c~NW3}DzT`ps>st*<7dP-qvR2|ychBQqCI+aF}hY#1ZlH)Rz)*81M zt<=+V&#Lb{G$F-jakXqi9)ZlWnr`Sa3tyck<-jpaV^1e583ndYBS5P$-@xxs9;#*I zwM&tv(cUqUpZChu9U@Tzf<`p510z=_+AeprKWk}iT$;u~iZa|JDN6UjX{rwGT6!zew3?<*G>$NQ=648?>RMl9RSS2xQwb+4*D zG$Cn|`4pLfJ}%f%q4_!Zcdy*60X=FMVUHM59d@{_)BIVX`Sa1ffefERVyGJH&9ACM zn$GZan@@z({LRRsIA^V=9%UhYxODYV1Z<-8IwK z+t~|-kG}fRpeU9Ru1Hh?7P0ytcJ}h6)7Q-I9xQDwRg{6MiH3dp23s}^Pye^wIO%3} zUnwL((L-J`EUq+27QS*nyK$<_YBha$L(5>_J`H?MQlMHfb|?aYWpC|XKHO0bhBlvY zhk4J%2_A>cbo`fZ6DPYfMkJD8?SuCht_rsFYkWqve}2miW-#(qUxY; zux`Ve-1NO|)@ZwV%f%}hG4;a2qYZ#oy>a8IrkZNeSrROo2#|=;3ScB_UhNT6@4J45 z7F!O+({pP!)D8B{K6Hq%Q4*>ZhYsE6uB+;+Is5c2BP}lD^&E$J-@%JLg5r@&uP*HH z=%gVyPCvT2u$qxeZb5N~;%koAb4OZS*G}jB)%C0Hx(@*k7OE8!@m=%F8rM}XUz6jO z%E7F{$IoWVw%g7**)s?G<>u9H(diKb|6t^_SIl|&!UMf>?mdhc^$6Mro&mRGN9T$bNDo*;C@Lp3!!bm-RkWlP)YT0Yl4%v-mty}_@_z`6`<%D}b^ z?5_3rx0Hd^ugbw>?=8G{xczf2b!|(z$e}}7l^YQZJT>!a%8}7PGNT!=bwl%&yIM-nG=nQMzkqzIOJ&u7@^cV%eg~N2Vw!)i`T0 kn}`i%GVv;8GDERMw#a$;wJ8%yDb;vXRaL7LK&j+j06#^&)Bpeg literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 89d1411f53ace1ed85e9ce9c266e0f3261125b5e..3276f97bda40502aa6c7d39c9e67147e1cca891b 100644 GIT binary patch literal 6266 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 literal 6482 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 W*nW|VQ;_xXAlt+pudI6 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 diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 037fa6f4..d0c07b09 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -10,4 +10,6 @@ #54000000 #80000000 #29FF6B08 + + #E8222222 \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8c98f79e..7eaa3516 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -22,6 +22,8 @@ #999999 #030201 + #E8EDEDED + #00658e #00658E diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index a1e9864b..0058aae7 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -18,6 +18,7 @@ 1000 @drawable/anim_splash shortEdges + @color/bg_black