From ff02280239968ba09bb4fcfef3f2387069f871a2 Mon Sep 17 00:00:00 2001 From: rebelonion <87634197+rebelonion@users.noreply.github.com> Date: Thu, 18 Jan 2024 01:09:11 -0600 Subject: [PATCH] webview for extensions --- app/src/main/AndroidManifest.xml | 18 +++++ app/src/main/java/ani/dantotsu/Functions.kt | 15 ++-- .../main/java/ani/dantotsu/MainActivity.kt | 43 ++++++----- .../dantotsu/media/MediaDetailsActivity.kt | 1 - .../dantotsu/media/anime/AnimeNameAdapter.kt | 6 +- .../dantotsu/media/anime/AnimeWatchAdapter.kt | 36 ++++++++-- .../dantotsu/media/anime/EpisodeAdapters.kt | 8 ++- .../dantotsu/media/manga/MangaReadAdapter.kt | 41 +++++++++-- .../dantotsu/others/webview/CookieCatcher.kt | 60 ++++++++++++++++ .../others/webview/WebViewBottomDialog.kt | 6 +- .../source/service/SourcePreferences.kt | 32 ++++++--- .../animesource/model/AnimeFilter.kt | 11 ++- .../animesource/online/AnimeHttpSource.kt | 22 ++++-- .../PackageInstallerInstallerAnime.kt | 25 +++++-- .../util/AnimeExtensionInstallService.kt | 15 ++-- .../anime/util/AnimeExtensionLoader.kt | 24 +++++-- .../extension/manga/MangaExtensionManager.kt | 23 ++++-- .../manga/util/MangaExtensionInstaller.kt | 7 +- .../tachiyomi/network/AndroidCookieJar.kt | 2 +- .../tachiyomi/network/NetworkPreferences.kt | 5 +- .../interceptor/CloudflareInterceptor.kt | 11 ++- .../tachiyomi/source/online/HttpSource.kt | 15 +++- .../util/system/NotificationExtensions.kt | 23 ++++-- .../tachiyomi/core/preference/Preference.kt | 3 +- .../core/util/lang/CoroutinesExtensions.kt | 6 +- .../source/anime/model/StubAnimeSource.kt | 1 + .../anime/repository/AnimeSourceRepository.kt | 6 +- app/src/main/res/drawable/ic_internet.xml | 10 +++ app/src/main/res/layout/dialog_layout.xml | 72 +++++++++++++++++-- 29 files changed, 447 insertions(+), 100 deletions(-) create mode 100644 app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt create mode 100644 app/src/main/res/drawable/ic_internet.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 39c04cbe..c61e37dd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -190,6 +190,24 @@ + + + + + + + + + + + + + 1 } } else { - uiSettings.defaultStartUpTab - } + uiSettings.defaultStartUpTab + } binding.includedNavbar.navbarContainer.updateLayoutParams { bottomMargin = navBarHeight } } val offline = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - .getBoolean("offlineMode", false) + .getBoolean("offlineMode", false) if (!isOnline(this)) { snackString(this@MainActivity.getString(R.string.no_internet_connection)) startActivity(Intent(this, NoInternet::class.java)) } else { - if (offline){ + if (offline) { snackString(this@MainActivity.getString(R.string.no_internet_connection)) startActivity(Intent(this, NoInternet::class.java)) - } - else { + } else { val model: AnilistHomeViewModel by viewModels() model.genres.observe(this) { it -> if (it != null) { @@ -226,15 +225,16 @@ class MainActivity : AppCompatActivity() { binding.mainProgressBar.visibility = View.GONE val mainViewPager = binding.viewpager mainViewPager.isUserInputEnabled = false - mainViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) + mainViewPager.adapter = + ViewPagerAdapter(supportFragmentManager, lifecycle) mainViewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings)) navbar.setOnTabSelectListener(object : - AnimatedBottomBar.OnTabSelectListener { + AnimatedBottomBar.OnTabSelectListener { override fun onTabSelected( - lastIndex: Int, - lastTab: AnimatedBottomBar.Tab?, - newIndex: Int, - newTab: AnimatedBottomBar.Tab + lastIndex: Int, + lastTab: AnimatedBottomBar.Tab?, + newIndex: Int, + newTab: AnimatedBottomBar.Tab ) { navbar.animate().translationZ(12f).setDuration(200).start() selectedOption = newIndex @@ -242,7 +242,12 @@ class MainActivity : AppCompatActivity() { } }) navbar.selectTabAt(selectedOption) - mainViewPager.post { mainViewPager.setCurrentItem(selectedOption, false) } + mainViewPager.post { + mainViewPager.setCurrentItem( + selectedOption, + false + ) + } } else { binding.mainProgressBar.visibility = View.GONE } @@ -262,8 +267,8 @@ class MainActivity : AppCompatActivity() { if (media != null) { media.cameFromContinue = cont startActivity( - Intent(this@MainActivity, MediaDetailsActivity::class.java) - .putExtra("media", media as Serializable) + Intent(this@MainActivity, MediaDetailsActivity::class.java) + .putExtra("media", media as Serializable) ) } else { snackString(this@MainActivity.getString(R.string.anilist_not_found)) @@ -282,8 +287,8 @@ class MainActivity : AppCompatActivity() { val md = "Open settings & click +Add Links & select Anilist & Mal urls" addView(TextView(this@MainActivity).apply { val markWon = - Markwon.builder(this@MainActivity) - .usePlugin(SoftBreakAddsNewLinePlugin.create()).build() + Markwon.builder(this@MainActivity) + .usePlugin(SoftBreakAddsNewLinePlugin.create()).build() markWon.setMarkdown(this, md) }) @@ -296,8 +301,8 @@ class MainActivity : AppCompatActivity() { saveData("allow_opening_links", true, this@MainActivity) tryWith(true) { startActivity( - Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS) - .setData(Uri.parse("package:$packageName")) + Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS) + .setData(Uri.parse("package:$packageName")) ) } } diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 3952e2cc..6a693ce1 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -10,7 +10,6 @@ import android.view.GestureDetector import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.animation.AccelerateDecelerateInterpolator import android.widget.ImageView import androidx.activity.viewModels diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt index 6e69a17c..1a46e6c3 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeNameAdapter.kt @@ -43,7 +43,8 @@ class AnimeNameAdapter { text } return if (removedNumber.equals(text, true)) { - val failedEpisodeNumberPattern: Regex = Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE) + val failedEpisodeNumberPattern: Regex = + Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE) failedEpisodeNumberPattern.replace(removedNumber) { mr -> mr.value.replaceFirst(mr.groupValues[1], "") }.ifEmpty { removedNumber } @@ -58,7 +59,8 @@ class AnimeNameAdapter { text } return if (removedNumber.equals(text, true)) { - val failedEpisodeNumberPattern: Regex = Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE) + val failedEpisodeNumberPattern: Regex = + Regex(failedEpisodeNumberRegex, RegexOption.IGNORE_CASE) failedEpisodeNumberPattern.replace(removedNumber) { mr -> mr.value.replaceFirst(mr.groupValues[1], "") }.ifEmpty { removedNumber } 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 cf605c4e..b7d9c016 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -12,6 +12,7 @@ import android.widget.ImageButton import android.widget.LinearLayout import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.startActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* @@ -22,12 +23,14 @@ import ani.dantotsu.media.Media import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.SourceSearchDialogFragment import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.DynamicAnimeParser import ani.dantotsu.parsers.WatchSources import ani.dantotsu.subcriptions.Notifications.Companion.openSettings import ani.dantotsu.subcriptions.Subscription.Companion.getChannelId import com.google.android.material.chip.Chip +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch @@ -86,8 +89,12 @@ class AnimeWatchAdapter( null ) } - val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("offlineMode", false) == true) View.GONE else View.VISIBLE + val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences( + "Dantotsu", + Context.MODE_PRIVATE + ) + ?.getBoolean("offlineMode", false) == true + ) View.GONE else View.VISIBLE binding.animeSourceNameContainer.visibility = offline binding.animeSourceSettings.visibility = offline @@ -188,7 +195,7 @@ class AnimeWatchAdapter( val dialogView = LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) val dialogBinding = DialogLayoutBinding.bind(dialogView) - + var refresh = false var run = false var reversed = media.selected!!.recyclerReversed var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView @@ -237,6 +244,21 @@ class AnimeWatchAdapter( dialogBinding.layoutText.text = "Compact" run = true } + dialogBinding.animeWebviewContainer.setOnClickListener { + //start CookieCatcher activity + if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { + val sourceAHH = watchSources[source] as? DynamicAnimeParser + val sourceHttp = + sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource + val url = sourceHttp?.baseUrl + url?.let { + refresh = true + val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) + .putExtra("url", url) + startActivity(fragment.requireContext(), intent, null) + } + } + } //hidden dialogBinding.animeScanlatorContainer.visibility = View.GONE @@ -247,8 +269,13 @@ class AnimeWatchAdapter( .setView(dialogView) .setPositiveButton("OK") { _, _ -> if (run) fragment.onIconPressed(style, reversed) + if (refresh) fragment.loadEpisodes(source, true) } .setNegativeButton("Cancel") { _, _ -> + if (refresh) fragment.loadEpisodes(source, true) + } + .setOnCancelListener { + if (refresh) fragment.loadEpisodes(source, true) } .create() nestedDialog?.show() @@ -410,7 +437,8 @@ class AnimeWatchAdapter( ) val items = adapter.count - binding?.animeSourceLanguageContainer?.visibility = if (items > 1) View.VISIBLE else View.GONE + binding?.animeSourceLanguageContainer?.visibility = + if (items > 1) View.VISIBLE else View.GONE binding?.animeSourceLanguage?.setAdapter(adapter) } diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 1cb514ce..686a2c8b 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -11,7 +11,6 @@ import androidx.annotation.OptIn import androidx.core.content.ContextCompat import androidx.lifecycle.coroutineScope import androidx.media3.common.util.UnstableApi -import androidx.media3.exoplayer.offline.Download import androidx.media3.exoplayer.offline.DownloadIndex import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.* @@ -63,6 +62,7 @@ class EpisodeAdapter( index = Helper.downloadManager(fragment.requireContext()).downloadIndex } } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return (when (viewType) { 0 -> EpisodeListViewHolder( @@ -248,7 +248,10 @@ class EpisodeAdapter( // Find the position of the chapter and notify only that item val position = arr.indexOfFirst { it.number == episodeNumber } if (position != -1) { - val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), episodeNumber) + val taskName = AnimeDownloaderService.AnimeDownloadTask.getTaskName( + media.mainName(), + episodeNumber + ) val id = fragment.requireContext().getSharedPreferences( ContextCompat.getString(fragment.requireContext(), R.string.anime_downloads), Context.MODE_PRIVATE @@ -323,6 +326,7 @@ class EpisodeAdapter( inner class EpisodeListViewHolder(val binding: ItemEpisodeListBinding) : RecyclerView.ViewHolder(binding.root) { private val activeCoroutines = mutableSetOf() + init { itemView.setOnClickListener { if (bindingAdapterPosition < arr.size && bindingAdapterPosition >= 0) 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 290491ec..b1e8ee0a 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -3,6 +3,7 @@ package ani.dantotsu.media.manga import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Context +import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -23,12 +24,14 @@ import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.media.SourceSearchDialogFragment import ani.dantotsu.media.anime.handleProgress import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.others.webview.CookieCatcher 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 eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch @@ -65,8 +68,12 @@ class MangaReadAdapter( null ) } - val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE) - ?.getBoolean("offlineMode", false) == true) View.GONE else View.VISIBLE + val offline = if (!isOnline(binding.root.context) || currContext()?.getSharedPreferences( + "Dantotsu", + Context.MODE_PRIVATE + ) + ?.getBoolean("offlineMode", false) == true + ) View.GONE else View.VISIBLE binding.animeSourceNameContainer.visibility = offline binding.animeSourceSettings.visibility = offline @@ -149,9 +156,10 @@ class MangaReadAdapter( binding.animeNestedButton.setOnClickListener { - val dialogView = LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) + val dialogView = + LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) val dialogBinding = DialogLayoutBinding.bind(dialogView) - + var refresh = false var run = false var reversed = media.selected!!.recyclerReversed var style = media.selected!!.recyclerStyle ?: fragment.uiSettings.animeDefaultView @@ -194,6 +202,20 @@ class MangaReadAdapter( dialogBinding.layoutText.text = "Compact" run = true } + dialogBinding.animeWebviewContainer.setOnClickListener { + //start CookieCatcher activity + if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { + val sourceAHH = mangaReadSources[source] as? DynamicMangaParser + val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource + val url = sourceHttp?.baseUrl + url?.let { + refresh = true + val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) + .putExtra("url", url) + ContextCompat.startActivity(fragment.requireContext(), intent, null) + } + } + } //Multi download dialogBinding.downloadNo.text = "0" @@ -216,7 +238,8 @@ class MangaReadAdapter( } //Scanlator - dialogBinding.animeScanlatorContainer.visibility = if (options.count() > 1) View.VISIBLE else View.GONE + dialogBinding.animeScanlatorContainer.visibility = + if (options.count() > 1) View.VISIBLE else View.GONE dialogBinding.scanlatorNo.text = "${options.count()}" dialogBinding.animeScanlatorTop.setOnClickListener { val dialogView2 = @@ -267,8 +290,13 @@ class MangaReadAdapter( if (dialogBinding.downloadNo.text != "0") { fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt()) } + if (refresh) fragment.loadChapters(source, true) } .setNegativeButton("Cancel") { _, _ -> + if (refresh) fragment.loadChapters(source, true) + } + .setOnCancelListener { + if (refresh) fragment.loadChapters(source, true) } .create() nestedDialog?.show() @@ -437,7 +465,8 @@ class MangaReadAdapter( parser.extension.sources.map { LanguageMapper.mapLanguageCodeToName(it.lang) } ) val items = adapter.count - binding?.animeSourceLanguageContainer?.visibility = if (items > 1) View.VISIBLE else View.GONE + binding?.animeSourceLanguageContainer?.visibility = + if (items > 1) View.VISIBLE else View.GONE binding?.animeSourceLanguage?.setAdapter(adapter) diff --git a/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt b/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt new file mode 100644 index 00000000..d1f3635e --- /dev/null +++ b/app/src/main/java/ani/dantotsu/others/webview/CookieCatcher.kt @@ -0,0 +1,60 @@ +package ani.dantotsu.others.webview + +import android.annotation.SuppressLint +import android.app.Application +import android.os.Build +import android.os.Bundle +import android.webkit.CookieManager +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.appcompat.app.AppCompatActivity +import ani.dantotsu.R +import ani.dantotsu.themes.ThemeManager +import eu.kanade.tachiyomi.network.NetworkHelper +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class CookieCatcher : AppCompatActivity() { + @SuppressLint("SetJavaScriptEnabled") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThemeManager(this).applyTheme() + + //get url from intent + val url = intent.getStringExtra("url") ?: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val process = Application.getProcessName() + if (packageName != process) WebView.setDataDirectorySuffix(process) + } + setContentView(R.layout.activity_discord) + + val webView = findViewById(R.id.discordWebview) + + val cookies: CookieManager = Injekt.get().cookieJar.manager + cookies.setAcceptThirdPartyCookies(webView, true) + + webView.apply { + settings.javaScriptEnabled = true + settings.databaseEnabled = true + settings.domStorageEnabled = true + } + WebView.setWebContentsDebuggingEnabled(true) + webView.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest? + ): Boolean { + return super.shouldOverrideUrlLoading(view, request) + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + } + } + + webView.loadUrl(url) + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/others/webview/WebViewBottomDialog.kt b/app/src/main/java/ani/dantotsu/others/webview/WebViewBottomDialog.kt index 257a703c..489c68fc 100644 --- a/app/src/main/java/ani/dantotsu/others/webview/WebViewBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/others/webview/WebViewBottomDialog.kt @@ -11,6 +11,9 @@ import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.FileUrl import ani.dantotsu.databinding.BottomSheetWebviewBinding import ani.dantotsu.defaultHeaders +import eu.kanade.tachiyomi.network.NetworkHelper +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get abstract class WebViewBottomDialog : BottomSheetDialogFragment() { @@ -30,7 +33,8 @@ abstract class WebViewBottomDialog : BottomSheetDialogFragment() { dismiss() } - val cookies: CookieManager = CookieManager.getInstance() + val cookies: CookieManager = Injekt.get().cookieJar.manager + //CookieManager.getInstance() override fun onCreateView( inflater: LayoutInflater, diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index 22a24479..a22ee27b 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -11,15 +11,23 @@ class SourcePreferences( // Common options - fun sourceDisplayMode() = preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize) + fun sourceDisplayMode() = preferenceStore.getObject( + "pref_display_mode_catalogue", + LibraryDisplayMode.default, + LibraryDisplayMode.Serializer::serialize, + LibraryDisplayMode.Serializer::deserialize + ) - fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages()) + fun enabledLanguages() = + preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages()) fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true) - fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL) + fun migrationSortingMode() = + preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL) - fun migrationSortingDirection() = preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING) + fun migrationSortingDirection() = + preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING) fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet()) @@ -37,12 +45,17 @@ class SourcePreferences( fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0) fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0) - fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false) - fun searchPinnedMangaSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false) + fun searchPinnedAnimeSourcesOnly() = + preferenceStore.getBoolean("search_pinned_anime_sources_only", false) - fun hideInAnimeLibraryItems() = preferenceStore.getBoolean("browse_hide_in_anime_library_items", false) + fun searchPinnedMangaSourcesOnly() = + preferenceStore.getBoolean("search_pinned_sources_only", false) - fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false) + fun hideInAnimeLibraryItems() = + preferenceStore.getBoolean("browse_hide_in_anime_library_items", false) + + fun hideInMangaLibraryItems() = + preferenceStore.getBoolean("browse_hide_in_library_items", false) // SY --> @@ -62,7 +75,8 @@ class SourcePreferences( fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80) - fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean("data_saver_image_format_jpeg", false) + fun dataSaverImageFormatJpeg() = + preferenceStore.getBoolean("data_saver_image_format_jpeg", false) fun dataSaverServer() = preferenceStore.getString("data_saver_server", "") diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt index e19c2b79..b3cf58d5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/model/AnimeFilter.kt @@ -3,10 +3,15 @@ package eu.kanade.tachiyomi.animesource.model sealed class AnimeFilter(val name: String, var state: T) { open class Header(name: String) : AnimeFilter(name, 0) open class Separator(name: String = "") : AnimeFilter(name, 0) - abstract class Select(name: String, val values: Array, state: Int = 0) : AnimeFilter(name, state) + abstract class Select(name: String, val values: Array, state: Int = 0) : + AnimeFilter(name, state) + abstract class Text(name: String, state: String = "") : AnimeFilter(name, state) - abstract class CheckBox(name: String, state: Boolean = false) : AnimeFilter(name, state) - abstract class TriState(name: String, state: Int = STATE_IGNORE) : AnimeFilter(name, state) { + abstract class CheckBox(name: String, state: Boolean = false) : + AnimeFilter(name, state) + + abstract class TriState(name: String, state: Int = STATE_IGNORE) : + AnimeFilter(name, state) { fun isIgnored() = state == STATE_IGNORE fun isIncluded() = state == STATE_INCLUDE fun isExcluded() = state == STATE_EXCLUDE diff --git a/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt index f8a38196..197be008 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSource.kt @@ -50,7 +50,8 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { override val id by lazy { val key = "${name.lowercase()}/$lang/$versionId" val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) - (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE + (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) } + .reduce(Long::or) and Long.MAX_VALUE } /** @@ -112,7 +113,11 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { + override fun fetchSearchAnime( + page: Int, + query: String, + filters: AnimeFilterList + ): Observable { return Observable.defer { try { client.newCall(searchAnimeRequest(page, query, filters)).asObservableSuccess() @@ -134,7 +139,11 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - protected abstract fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request + protected abstract fun searchAnimeRequest( + page: Int, + query: String, + filters: AnimeFilterList + ): Request /** * Parses the response from the site and returns a [AnimesPage] object. @@ -311,7 +320,12 @@ abstract class AnimeHttpSource : AnimeCatalogueSource { val animeDownloadClient = client.newBuilder() .callTimeout(30, TimeUnit.MINUTES) .build() - return animeDownloadClient.newCachelessCallWithProgress(videoRequest(video, video.totalBytesDownloaded), video) + return animeDownloadClient.newCachelessCallWithProgress( + videoRequest( + video, + video.totalBytesDownloaded + ), video + ) .asObservableSuccess() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt index b1fd050b..ffa12c70 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt @@ -23,7 +23,10 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn private val packageActionReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) { + when (intent.getIntExtra( + PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE + )) { PackageInstaller.STATUS_PENDING_USER_ACTION -> { val userAction = intent.getParcelableExtraCompat(Intent.EXTRA_INTENT) if (userAction == null) { @@ -34,9 +37,11 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) service.startActivity(userAction) } + PackageInstaller.STATUS_FAILURE_ABORTED -> { continueQueue(InstallStep.Idle) } + PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed) else -> continueQueue(InstallStep.Error) } @@ -52,7 +57,8 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn super.processEntry(entry) activeSession = null try { - val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + val installParams = + PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED) } @@ -60,7 +66,8 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn val fileSize = service.getUriSize(entry.uri) ?: throw IllegalStateException() installParams.setSize(fileSize) - val inputStream = service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException() + val inputStream = + service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException() val session = packageInstaller.openSession(activeSession!!.second) val outputStream = session.openWrite(entry.downloadId.toString(), 0, fileSize) session.use { @@ -82,7 +89,10 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn session.commit(intentSender) } } catch (e: Exception) { - logcat(LogPriority.ERROR, e) { "Failed to install extension ${entry.downloadId} ${entry.uri}" } + logcat( + LogPriority.ERROR, + e + ) { "Failed to install extension ${entry.downloadId} ${entry.uri}" } logcat(LogPriority.ERROR) { "Exception: $e" } snackString("Failed to install extension ${entry.downloadId} ${entry.uri}") activeSession?.let { (_, sessionId) -> @@ -108,7 +118,12 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn } init { - ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_EXPORTED) + ContextCompat.registerReceiver( + service, + packageActionReceiver, + IntentFilter(INSTALL_ACTION), + ContextCompat.RECEIVER_EXPORTED + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt index fd5353f0..e70cd5e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt @@ -7,8 +7,8 @@ import android.content.pm.ServiceInfo import android.net.Uri import android.os.Build import android.os.IBinder -import eu.kanade.domain.base.BasePreferences import ani.dantotsu.R +import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAnime @@ -32,8 +32,12 @@ class AnimeExtensionInstallService : Service() { setProgress(100, 100, true) }.build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground(Notifications.ID_EXTENSION_INSTALLER, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) - }else{ + startForeground( + Notifications.ID_EXTENSION_INSTALLER, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + ) + } else { startForeground(Notifications.ID_EXTENSION_INSTALLER, notification) } } @@ -51,7 +55,10 @@ class AnimeExtensionInstallService : Service() { if (installer == null) { installer = when (installerUsed) { - BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime(this) + BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime( + this + ) + else -> { logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" } stopSelf() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt index 745366ed..cce7b7e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt @@ -41,15 +41,18 @@ internal object AnimeExtensionLoader { const val LIB_VERSION_MIN = 12 const val LIB_VERSION_MAX = 15 - private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES + private const val PACKAGE_FLAGS = + PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES // jmir1's key - private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c" + private const val officialSignature = + "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c" /** * List of the trusted signatures. */ - var trustedSignatures = mutableSetOf() + preferences.trustedSignatures().get() + officialSignature + var trustedSignatures = + mutableSetOf() + preferences.trustedSignatures().get() + officialSignature /** * Return a list of all the installed extensions initialized concurrently. @@ -105,7 +108,11 @@ internal object AnimeExtensionLoader { * @param pkgName The package name of the extension to load. * @param pkgInfo The package info of the extension. */ - private fun loadExtension(context: Context, pkgName: String, pkgInfo: PackageInfo): AnimeLoadResult { + private fun loadExtension( + context: Context, + pkgName: String, + pkgInfo: PackageInfo + ): AnimeLoadResult { val pkgManager = context.packageManager val appInfo = try { @@ -141,7 +148,14 @@ internal object AnimeExtensionLoader { logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } return AnimeLoadResult.Error } else if (signatureHash !in trustedSignatures) { - val extension = AnimeExtension.Untrusted(extName, pkgName, versionName, versionCode, libVersion, signatureHash) + val extension = AnimeExtension.Untrusted( + extName, + pkgName, + versionName, + versionCode, + libVersion, + signatureHash + ) logcat(LogPriority.WARN, message = { "Extension $pkgName isn't trusted" }) return AnimeLoadResult.Untrusted(extension) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt index 260af47c..fe098455 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt @@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader import eu.kanade.tachiyomi.util.preference.plusAssign -import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -63,9 +62,11 @@ class MangaExtensionManager( private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() fun getAppIconForSource(sourceId: Long): Drawable? { - val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName + val pkgName = + _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName if (pkgName != null) { - return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) } + return iconMap[pkgName] + ?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) } } return null } @@ -257,14 +258,20 @@ class MangaExtensionManager( MangaExtensionLoader.trustedSignatures += signature preferences.trustedSignatures() += signature - val nowTrustedExtensions = _untrustedExtensionsFlow.value.filter { it.signatureHash == signature } + val nowTrustedExtensions = + _untrustedExtensionsFlow.value.filter { it.signatureHash == signature } _untrustedExtensionsFlow.value -= nowTrustedExtensions val ctx = context launchNow { nowTrustedExtensions .map { extension -> - async { MangaExtensionLoader.loadMangaExtensionFromPkgName(ctx, extension.pkgName) } + async { + MangaExtensionLoader.loadMangaExtensionFromPkgName( + ctx, + extension.pkgName + ) + } } .map { it.await() } .forEach { result -> @@ -354,13 +361,15 @@ class MangaExtensionManager( } private fun MangaExtension.Installed.updateExists(availableExtension: MangaExtension.Available? = null): Boolean { - val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } + val availableExt = + availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } if (isUnofficial || availableExt == null) return false return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) } private fun updatePendingUpdatesCount() { - preferences.mangaExtensionUpdatesCount().set(_installedExtensionsFlow.value.count { it.hasUpdate }) + preferences.mangaExtensionUpdatesCount() + .set(_installedExtensionsFlow.value.count { it.hasUpdate }) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt index 4109f9f5..f05db16c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt @@ -77,7 +77,11 @@ internal class MangaExtensionInstaller(private val context: Context) { val request = DownloadManager.Request(downloadUri) .setTitle(extension.name) .setMimeType(APK_MIME) - .setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment) + .setDestinationInExternalFilesDir( + context, + Environment.DIRECTORY_DOWNLOADS, + downloadUri.lastPathSegment + ) .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) val id = downloadManager.enqueue(request) @@ -141,6 +145,7 @@ internal class MangaExtensionInstaller(private val context: Context) { context.startActivity(intent) } + else -> { val intent = MangaExtensionInstallService.getIntent(context, downloadId, uri, installer) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt index f9322e84..82b04be2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/AndroidCookieJar.kt @@ -7,7 +7,7 @@ import okhttp3.HttpUrl class AndroidCookieJar : CookieJar { - private val manager = CookieManager.getInstance() + val manager = CookieManager.getInstance() override fun saveFromResponse(url: HttpUrl, cookies: List) { val urlString = url.toString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt index fa0eab88..1dce7afd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkPreferences.kt @@ -17,6 +17,9 @@ class NetworkPreferences( } fun defaultUserAgent(): Preference { - return preferenceStore.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0") + return preferenceStore.getString( + "default_user_agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0" + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 2d8202ea..0beba7cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -30,7 +30,11 @@ class CloudflareInterceptor( return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK } - override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response { + override fun intercept( + chain: Interceptor.Chain, + request: Request, + response: Response + ): Response { try { response.close() cookieManager.remove(request.url, COOKIE_NAMES, 0) @@ -125,7 +129,10 @@ class CloudflareInterceptor( if (!cloudflareBypassed) { // Prompt user to update WebView if it seems too outdated if (isWebViewOutdated) { - context.toast("Please update the webview app for better compatibility", Toast.LENGTH_LONG) + context.toast( + "Please update the webview app for better compatibility", + Toast.LENGTH_LONG + ) } throw CloudflareBypassException() diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt index d0aa5b2d..16fd9935 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -50,7 +50,8 @@ abstract class HttpSource : CatalogueSource { override val id by lazy { val key = "${name.lowercase()}/$lang/$versionId" val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) - (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE + (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) } + .reduce(Long::or) and Long.MAX_VALUE } /** @@ -112,7 +113,11 @@ abstract class HttpSource : CatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList + ): Observable { return Observable.defer { try { client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess() @@ -134,7 +139,11 @@ abstract class HttpSource : CatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request + protected abstract fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList + ): Request /** * Parses the response from the site and returns a [MangasPage] object. diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt index d43d7045..67ce5a03 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt @@ -16,13 +16,21 @@ import androidx.core.content.getSystemService val Context.notificationManager: NotificationManager get() = getSystemService()!! -fun Context.notify(id: Int, channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null) { +fun Context.notify( + id: Int, + channelId: String, + block: (NotificationCompat.Builder.() -> Unit)? = null +) { val notification = notificationBuilder(channelId, block).build() this.notify(id, notification) } fun Context.notify(id: Int, notification: Notification) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PermissionChecker.PERMISSION_GRANTED + ) { return } @@ -30,7 +38,11 @@ fun Context.notify(id: Int, notification: Notification) { } fun Context.notify(notificationWithIdAndTags: List) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PermissionChecker.PERMISSION_GRANTED + ) { return } @@ -48,7 +60,10 @@ fun Context.cancelNotification(id: Int) { * @param block the function that will execute inside the builder. * @return a notification to be displayed or updated. */ -fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder { +fun Context.notificationBuilder( + channelId: String, + block: (NotificationCompat.Builder.() -> Unit)? = null +): NotificationCompat.Builder { val builder = NotificationCompat.Builder(this, channelId) .setColor(getColor(android.R.color.holo_blue_dark)) if (block != null) { diff --git a/app/src/main/java/tachiyomi/core/preference/Preference.kt b/app/src/main/java/tachiyomi/core/preference/Preference.kt index c276141e..f7c428ac 100644 --- a/app/src/main/java/tachiyomi/core/preference/Preference.kt +++ b/app/src/main/java/tachiyomi/core/preference/Preference.kt @@ -23,4 +23,5 @@ interface Preference { fun stateIn(scope: CoroutineScope): StateFlow } -inline fun Preference.getAndSet(crossinline block: (T) -> R) = set(block(get())) +inline fun Preference.getAndSet(crossinline block: (T) -> R) = + set(block(get())) diff --git a/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt b/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt index 4573b2d8..d99b7eb1 100644 --- a/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt +++ b/app/src/main/java/tachiyomi/core/util/lang/CoroutinesExtensions.kt @@ -52,9 +52,11 @@ fun CoroutineScope.launchIO(block: suspend CoroutineScope.() -> Unit): Job = fun CoroutineScope.launchNonCancellable(block: suspend CoroutineScope.() -> Unit): Job = launchIO { withContext(NonCancellable, block) } -suspend fun withUIContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.Main, block) +suspend fun withUIContext(block: suspend CoroutineScope.() -> T) = + withContext(Dispatchers.Main, block) -suspend fun withIOContext(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block) +suspend fun withIOContext(block: suspend CoroutineScope.() -> T) = + withContext(Dispatchers.IO, block) suspend fun withNonCancellableContext(block: suspend CoroutineScope.() -> T) = withContext(NonCancellable, block) 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 index 7631c49c..4bc3e65a 100644 --- a/app/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt +++ b/app/src/main/java/tachiyomi/domain/source/anime/model/StubAnimeSource.kt @@ -30,4 +30,5 @@ class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource { return if (sourceData.isMissingInfo.not()) "$name (${lang.uppercase()})" else id.toString() } } + class AnimeSourceNotInstalledException : Exception() diff --git a/app/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceRepository.kt b/app/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceRepository.kt index e8b2d372..c23fac97 100644 --- a/app/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceRepository.kt +++ b/app/src/main/java/tachiyomi/domain/source/anime/repository/AnimeSourceRepository.kt @@ -19,7 +19,11 @@ interface AnimeSourceRepository { fun getSourcesWithNonLibraryAnime(): Flow> - fun searchAnime(sourceId: Long, query: String, filterList: AnimeFilterList): AnimeSourcePagingSourceType + fun searchAnime( + sourceId: Long, + query: String, + filterList: AnimeFilterList + ): AnimeSourcePagingSourceType fun getPopularAnime(sourceId: Long): AnimeSourcePagingSourceType diff --git a/app/src/main/res/drawable/ic_internet.xml b/app/src/main/res/drawable/ic_internet.xml new file mode 100644 index 00000000..e7b4b2cc --- /dev/null +++ b/app/src/main/res/drawable/ic_internet.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_layout.xml b/app/src/main/res/layout/dialog_layout.xml index aa1d661f..e90ac449 100644 --- a/app/src/main/res/layout/dialog_layout.xml +++ b/app/src/main/res/layout/dialog_layout.xml @@ -6,10 +6,12 @@ android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> + + + + android:layout_weight="1" /> + + + + android:layout_gravity="center_vertical"> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file