diff --git a/app/src/main/java/ani/dantotsu/others/Xpandable.kt b/app/src/main/java/ani/dantotsu/others/Xpandable.kt index c4025c58..53518b40 100644 --- a/app/src/main/java/ani/dantotsu/others/Xpandable.kt +++ b/app/src/main/java/ani/dantotsu/others/Xpandable.kt @@ -12,8 +12,8 @@ import ani.dantotsu.R class Xpandable @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : LinearLayout(context, attrs) { - var expanded: Boolean = false - private var listener: OnChangeListener? = null + private var expanded: Boolean = false + private var listeners: ArrayList = arrayListOf() init { context.withStyledAttributes(attrs, R.styleable.Xpandable) { @@ -50,7 +50,9 @@ class Xpandable @JvmOverloads constructor( } } postDelayed({ - listener?.onRetract() + listeners.forEach{ + it.onRetract() + } }, 300) } @@ -64,13 +66,19 @@ class Xpandable @JvmOverloads constructor( } } postDelayed({ - listener?.onExpand() + listeners.forEach{ + it.onExpand() + } }, 300) } @Suppress("unused") - fun setOnChangeListener(listener: OnChangeListener) { - this.listener = listener + fun addOnChangeListener(listener: OnChangeListener) { + listeners.add(listener) + } + + fun removeListener(listener: OnChangeListener) { + listeners.remove(listener) } interface OnChangeListener { diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt index ff18527d..ec431643 100644 --- a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt @@ -137,11 +137,6 @@ class PlayerSettingsActivity : AppCompatActivity() { binding.playerSettingsAutoSkipOpEd.isEnabled = isChecked } - binding.playerSettingsTimeStampsAutoHide.isChecked = PrefManager.getVal(PrefName.AutoHideTimeStamps) - binding.playerSettingsTimeStampsAutoHide.setOnCheckedChangeListener { _, isChecked -> - PrefManager.setVal(PrefName.AutoHideTimeStamps, isChecked) - } - binding.playerSettingsTimeStampsProxy.isChecked = PrefManager.getVal(PrefName.UseProxyForTimeStamps) binding.playerSettingsTimeStampsProxy.setOnCheckedChangeListener { _, isChecked -> @@ -152,6 +147,13 @@ class PlayerSettingsActivity : AppCompatActivity() { PrefManager.getVal(PrefName.ShowTimeStampButton) binding.playerSettingsShowTimeStamp.setOnCheckedChangeListener { _, isChecked -> PrefManager.setVal(PrefName.ShowTimeStampButton, isChecked) + binding.playerSettingsTimeStampsAutoHide.isEnabled = isChecked + } + + binding.playerSettingsTimeStampsAutoHide.isChecked = PrefManager.getVal(PrefName.AutoHideTimeStamps) + binding.playerSettingsTimeStampsAutoHide.isEnabled = binding.playerSettingsShowTimeStamp.isChecked + binding.playerSettingsTimeStampsAutoHide.setOnCheckedChangeListener { _, isChecked -> + PrefManager.setVal(PrefName.AutoHideTimeStamps, isChecked) } // Auto @@ -475,7 +477,7 @@ class PlayerSettingsActivity : AppCompatActivity() { updateSubPreview() } } - binding.subtitleTest.setOnChangeListener(object: Xpandable.OnChangeListener { + binding.subtitleTest.addOnChangeListener(object: Xpandable.OnChangeListener { override fun onExpand() { updateSubPreview() } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 6f2d7a08..34b29ac5 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -14,12 +14,15 @@ import android.os.Build.VERSION.CODENAME import android.os.Build.VERSION.RELEASE import android.os.Build.VERSION.SDK_INT import android.os.Bundle +import android.view.HapticFeedbackConstants +import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AnimationUtils import android.view.inputmethod.EditorInfo import android.widget.ArrayAdapter +import android.widget.EditText import android.widget.RadioButton import android.widget.RadioGroup import android.widget.TextView @@ -30,6 +33,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import android.view.HapticFeedbackConstants import androidx.core.view.ViewCompat.performHapticFeedback +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.lifecycleScope @@ -52,6 +56,7 @@ import ani.dantotsu.databinding.ActivitySettingsExtensionsBinding import ani.dantotsu.databinding.ActivitySettingsMangaBinding import ani.dantotsu.databinding.ActivitySettingsNotificationsBinding import ani.dantotsu.databinding.ActivitySettingsThemeBinding +import ani.dantotsu.databinding.ItemRepositoryBinding import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity import ani.dantotsu.loadImage @@ -91,15 +96,19 @@ import eltos.simpledialogfragment.SimpleDialog import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener.BUTTON_POSITIVE import eltos.simpledialogfragment.color.SimpleColorDialog import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import kotlin.random.Random @@ -119,7 +128,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene private lateinit var bindingAbout: ActivitySettingsAboutBinding private val extensionInstaller = Injekt.get().extensionInstaller() private var cursedCounter = 0 - + private val animeExtensionManager: AnimeExtensionManager by injectLazy() + private val mangaExtensionManager: MangaExtensionManager by injectLazy() + @kotlin.OptIn(DelicateCoroutinesApi::class) @OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) { @@ -586,6 +597,142 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } bindingExtensions = ActivitySettingsExtensionsBinding.bind(binding.root).apply { + + fun setExtensionOutput() { + animeRepoInventory.removeAllViews() + PrefManager.getVal>(PrefName.AnimeExtensionRepos).forEach { item -> + val view = ItemRepositoryBinding.inflate( + LayoutInflater.from(animeRepoInventory.context), animeRepoInventory, true + ) + view.repositoryItem.text = item + view.repositoryItem.setOnClickListener { + snackString(getString(R.string.long_click_delete)) + } + view.repositoryItem.setOnLongClickListener { + val anime = PrefManager.getVal>(PrefName.AnimeExtensionRepos) + .minus(item) + PrefManager.setVal(PrefName.AnimeExtensionRepos, anime) + it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + setExtensionOutput() + CoroutineScope(Dispatchers.IO).launch { + animeExtensionManager.findAvailableExtensions() + } + true + } + } + animeRepoInventory.isVisible = animeRepoInventory.childCount > 0 + mangaRepoInventory.removeAllViews() + PrefManager.getVal>(PrefName.MangaExtensionRepos).forEach { item -> + val view = ItemRepositoryBinding.inflate( + LayoutInflater.from(mangaRepoInventory.context), mangaRepoInventory, true + ) + view.repositoryItem.text = item + view.repositoryItem.setOnClickListener { + snackString(getString(R.string.long_click_delete)) + } + view.repositoryItem.setOnLongClickListener { + val anime = PrefManager.getVal>(PrefName.MangaExtensionRepos) + .minus(item) + PrefManager.setVal(PrefName.MangaExtensionRepos, anime) + it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + setExtensionOutput() + CoroutineScope(Dispatchers.IO).launch { + mangaExtensionManager.findAvailableExtensions() + } + true + } + } + mangaRepoInventory.isVisible = mangaRepoInventory.childCount > 0 + } + + fun processUserInput(input: String, mediaType: MediaType) { + val entry = if (input.endsWith("/") || input.endsWith("index.min.json")) + input.substring(0, input.lastIndexOf("/")) else input + if (mediaType == MediaType.ANIME) { + val anime = PrefManager.getVal>(PrefName.AnimeExtensionRepos).plus(entry) + PrefManager.setVal(PrefName.AnimeExtensionRepos, anime) + CoroutineScope(Dispatchers.IO).launch { + animeExtensionManager.findAvailableExtensions() + } + } + if (mediaType == MediaType.MANGA) { + val manga = PrefManager.getVal>(PrefName.MangaExtensionRepos).plus(entry) + PrefManager.setVal(PrefName.MangaExtensionRepos, manga) + CoroutineScope(Dispatchers.IO).launch { + mangaExtensionManager.findAvailableExtensions() + } + } + setExtensionOutput() + } + + fun processEditorAction(dialog: AlertDialog, editText: EditText, mediaType: MediaType) { + editText.setOnEditorActionListener { textView, action, keyEvent -> + if (action == EditorInfo.IME_ACTION_SEARCH || action == EditorInfo.IME_ACTION_DONE || + (keyEvent?.action == KeyEvent.ACTION_UP + && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER)) { + processUserInput(textView.text.toString(), mediaType) + dialog.dismiss() + return@setOnEditorActionListener true + } + false + } + } + + setExtensionOutput() + animeAddRepository.setOnClickListener { + val dialogView = layoutInflater.inflate(R.layout.dialog_user_agent, null) + val editText = dialogView.findViewById(R.id.userAgentTextBox).apply { + hint = getString(R.string.anime_add_repository) + } + val alertDialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.anime_add_repository) + .setView(dialogView) + .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + processUserInput(editText.text.toString(), MediaType.ANIME) + dialog.dismiss() + } + .setNeutralButton(getString(R.string.reset)) { dialog, _ -> + PrefManager.removeVal(PrefName.DefaultUserAgent) + editText.setText("") + dialog.dismiss() + } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> + dialog.dismiss() + } + .create() + + processEditorAction(alertDialog, editText, MediaType.ANIME) + alertDialog.show() + alertDialog.window?.setDimAmount(0.8f) + } + + mangaAddRepository.setOnClickListener { + val dialogView = layoutInflater.inflate(R.layout.dialog_user_agent, null) + val editText = dialogView.findViewById(R.id.userAgentTextBox).apply { + hint = getString(R.string.manga_add_repository) + } + val alertDialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.manga_add_repository) + .setView(dialogView) + .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + processUserInput(editText.text.toString(), MediaType.MANGA) + dialog.dismiss() + } + .setNeutralButton(getString(R.string.reset)) { dialog, _ -> + PrefManager.removeVal(PrefName.DefaultUserAgent) + editText.setText("") + dialog.dismiss() + } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> + dialog.dismiss() + } + .create() + + processEditorAction(alertDialog, editText, MediaType.MANGA) + alertDialog.show() + alertDialog.window?.setDimAmount(0.8f) + } + settingsForceLegacyInstall.isChecked = extensionInstaller.get() == BasePreferences.ExtensionInstaller.LEGACY settingsForceLegacyInstall.setOnCheckedChangeListener { _, isChecked -> @@ -1196,4 +1343,4 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene ?: "Unknown Architecture" } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 414a45cb..fb59a98c 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -28,6 +28,9 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files "Mozilla/5.0 (Linux; Android 13; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36" ) ), + AnimeExtensionRepos(Pref(Location.General, Set::class, setOf())), + MangaExtensionRepos(Pref(Location.General, Set::class, setOf())), + SharedRepositories(Pref(Location.General, Boolean::class, false)), AnimeSourcesOrder(Pref(Location.General, List::class, listOf())), AnimeSearchHistory(Pref(Location.General, Set::class, setOf())), MangaSourcesOrder(Pref(Location.General, List::class, listOf())), diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt index 556ec343..6215daed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.extension.anime.api import android.content.Context +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager @@ -32,42 +34,36 @@ internal class AnimeExtensionGithubApi { preferenceStore.getLong("last_ext_check", 0) } - private var requiresFallbackSource = false - suspend fun findExtensions(): List { return withIOContext { - val githubResponse = if (requiresFallbackSource) { - null - } else { + + val extensions: ArrayList = arrayListOf() + + PrefManager.getVal>(PrefName.AnimeExtensionRepos).forEach { try { - networkService.client - .newCall(GET("${REPO_URL_PREFIX}index.min.json")) - .awaitSuccess() + val githubResponse = + networkService.client + .newCall(GET("${it}/index.min.json")) + .awaitSuccess() + + val repoExtensions = with(json) { + githubResponse + .parseAs>() + .toExtensions(it) + } + + // Sanity check - a small number of extensions probably means something broke + // with the repo generator + if (repoExtensions.size < 10) { + throw Exception() + } + + extensions.addAll(repoExtensions) } catch (e: Throwable) { Logger.log("Failed to get extensions from GitHub") - requiresFallbackSource = true - null } } - val response = githubResponse ?: run { - networkService.client - .newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")) - .awaitSuccess() - } - - val extensions = with(json) { - response - .parseAs>() - .toExtensions() - } - - // Sanity check - a small number of extensions probably means something broke - // with the repo generator - if (extensions.size < 10) { - throw Exception() - } - extensions } } @@ -111,7 +107,7 @@ internal class AnimeExtensionGithubApi { return extensionsWithUpdate } - private fun List.toExtensions(): List { + private fun List.toExtensions(repository: String): List { return this .filter { val libVersion = it.extractLibVersion() @@ -130,7 +126,8 @@ internal class AnimeExtensionGithubApi { hasChangelog = it.hasChangelog == 1, sources = it.sources?.toAnimeExtensionSources().orEmpty(), apkName = it.apk, - iconUrl = "${getUrlPrefix()}icon/${it.pkg}.png", + repository = repository, + iconUrl = "${repository}/icon/${it.pkg}.png", ) } } @@ -147,15 +144,7 @@ internal class AnimeExtensionGithubApi { } fun getApkUrl(extension: AnimeExtension.Available): String { - return "${getUrlPrefix()}apk/${extension.apkName}" - } - - private fun getUrlPrefix(): String { - return if (requiresFallbackSource) { - FALLBACK_REPO_URL_PREFIX - } else { - REPO_URL_PREFIX - } + return "${extension.repository}/apk/${extension.apkName}" } } @@ -163,11 +152,6 @@ private fun AnimeExtensionJsonObject.extractLibVersion(): Double { return version.substringBeforeLast('.').toDouble() } -private const val REPO_URL_PREFIX = - "https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo/" -private const val FALLBACK_REPO_URL_PREFIX = - "https://gcore.jsdelivr.net/gh/aniyomiorg/aniyomi-extensions@repo/" - @Serializable private data class AnimeExtensionJsonObject( val name: String, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt index 4f552879..ad6d6b4f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/model/AnimeExtension.kt @@ -47,6 +47,7 @@ sealed class AnimeExtension { val sources: List, val apkName: String, val iconUrl: String, + val repository: String ) : AnimeExtension() data class Untrusted( diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt index d3d162a1..39322259 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.extension.manga.api import android.content.Context +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.util.Logger import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager @@ -32,42 +34,37 @@ internal class MangaExtensionGithubApi { preferenceStore.getLong("last_ext_check", 0) } - private var requiresFallbackSource = false - suspend fun findExtensions(): List { return withIOContext { - val githubResponse = if (requiresFallbackSource) { - null - } else { + + val extensions: ArrayList = arrayListOf() + + + PrefManager.getVal>(PrefName.MangaExtensionRepos).forEach { try { - networkService.client - .newCall(GET("${REPO_URL_PREFIX}index.min.json")) - .awaitSuccess() + val githubResponse = + networkService.client + .newCall(GET("${it}/index.min.json")) + .awaitSuccess() + + val repoExtensions = with(json) { + githubResponse + .parseAs>() + .toExtensions(it) + } + + // Sanity check - a small number of extensions probably means something broke + // with the repo generator + if (repoExtensions.size < 10) { + throw Exception() + } + + extensions.addAll(repoExtensions) } catch (e: Throwable) { Logger.log("Failed to get extensions from GitHub") - requiresFallbackSource = true - null } } - val response = githubResponse ?: run { - networkService.client - .newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")) - .awaitSuccess() - } - - val extensions = with(json) { - response - .parseAs>() - .toExtensions() - } - - // Sanity check - a small number of extensions probably means something broke - // with the repo generator - if (extensions.size < 100) { - throw Exception() - } - extensions } } @@ -110,7 +107,7 @@ internal class MangaExtensionGithubApi { return extensionsWithUpdate } - private fun List.toExtensions(): List { + private fun List.toExtensions(repository: String): List { return this .filter { val libVersion = it.extractLibVersion() @@ -129,7 +126,8 @@ internal class MangaExtensionGithubApi { hasChangelog = it.hasChangelog == 1, sources = it.sources?.toExtensionSources().orEmpty(), apkName = it.apk, - iconUrl = "${getUrlPrefix()}icon/${it.pkg}.png", + repository = repository, + iconUrl = "${repository}/icon/${it.pkg}.png", ) } } @@ -146,15 +144,7 @@ internal class MangaExtensionGithubApi { } fun getApkUrl(extension: MangaExtension.Available): String { - return "${getUrlPrefix()}apk/${extension.apkName}" - } - - private fun getUrlPrefix(): String { - return if (requiresFallbackSource) { - FALLBACK_REPO_URL_PREFIX - } else { - REPO_URL_PREFIX - } + return "${extension.repository}/apk/${extension.apkName}" } private fun ExtensionJsonObject.extractLibVersion(): Double { @@ -162,10 +152,6 @@ internal class MangaExtensionGithubApi { } } -private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/keiyoushi/extensions/main/" -private const val FALLBACK_REPO_URL_PREFIX = - "https://gcore.jsdelivr.net/gh/keiyoushi/extensions@main/" - @Serializable private data class ExtensionJsonObject( val name: String, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt index d0b6d448..2ae5a165 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/model/MangaExtension.kt @@ -47,6 +47,7 @@ sealed class MangaExtension { val sources: List, val apkName: String, val iconUrl: String, + val repository: String ) : MangaExtension() data class Untrusted( diff --git a/app/src/main/res/layout/activity_player_settings.xml b/app/src/main/res/layout/activity_player_settings.xml index adcd25b9..393e50e4 100644 --- a/app/src/main/res/layout/activity_player_settings.xml +++ b/app/src/main/res/layout/activity_player_settings.xml @@ -177,8 +177,7 @@ android:layout_height="64dp" android:fontFamily="@font/poppins_bold" android:gravity="center_vertical" - android:paddingStart="48dp" - android:paddingEnd="32dp" + android:paddingHorizontal="32dp" android:text="@string/sub_text_example" android:textColor="?attr/colorSecondary" app:drawableEndCompat="@drawable/ic_round_arrow_drop_down_24" @@ -492,37 +491,6 @@ app:showText="false" app:thumbTint="@color/button_switch_track" /> - - - - - - - + + android:text="@string/hide_skip_button" + android:textSize="14sp" /> + +