feat: manual repository entries

Closes Dantotsu#298
This commit is contained in:
TwistedUmbrellaX 2024-03-27 14:50:05 -04:00
parent ba1725224a
commit ff3131d988
12 changed files with 369 additions and 109 deletions

View file

@ -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<OnChangeListener> = 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 {

View file

@ -475,7 +475,7 @@ class PlayerSettingsActivity : AppCompatActivity() {
updateSubPreview()
}
}
binding.subtitleTest.setOnChangeListener(object: Xpandable.OnChangeListener {
binding.subtitleTest.addOnChangeListener(object: Xpandable.OnChangeListener {
override fun onExpand() {
updateSubPreview()
}

View file

@ -13,12 +13,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
@ -29,6 +32,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
@ -51,6 +55,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.download.video.ExoplayerDownloadService
import ani.dantotsu.downloadsPermission
@ -89,13 +94,17 @@ 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.CoroutineScope
import kotlinx.coroutines.Dispatchers
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
@ -115,6 +124,9 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
private var cursedCounter = 0
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
private val mangaExtensionManager: MangaExtensionManager by injectLazy()
@OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -583,6 +595,142 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
}
bindingExtensions = ActivitySettingsExtensionsBinding.bind(binding.root).apply {
fun setExtensionOutput() {
animeRepoInventory.removeAllViews()
PrefManager.getVal<Set<String>>(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<Set<String>>(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<Set<String>>(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<Set<String>>(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<Set<String>>(PrefName.AnimeExtensionRepos).plus(entry)
PrefManager.setVal(PrefName.AnimeExtensionRepos, anime)
CoroutineScope(Dispatchers.IO).launch {
animeExtensionManager.findAvailableExtensions()
}
}
if (mediaType == MediaType.MANGA) {
val manga = PrefManager.getVal<Set<String>>(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<TextInputEditText>(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<TextInputEditText>(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 ->
@ -1169,4 +1317,4 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
?: "Unknown Architecture"
}
}
}
}

View file

@ -29,6 +29,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<String>())),
MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
SharedRepositories(Pref(Location.General, Boolean::class, false)),
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
AnimeSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),

View file

@ -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<AnimeExtension.Available> {
return withIOContext {
val githubResponse = if (requiresFallbackSource) {
null
} else {
val extensions: ArrayList<AnimeExtension.Available> = arrayListOf()
PrefManager.getVal<Set<String>>(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<List<AnimeExtensionJsonObject>>()
.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<List<AnimeExtensionJsonObject>>()
.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<AnimeExtensionJsonObject>.toExtensions(): List<AnimeExtension.Available> {
private fun List<AnimeExtensionJsonObject>.toExtensions(repository: String): List<AnimeExtension.Available> {
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,

View file

@ -47,6 +47,7 @@ sealed class AnimeExtension {
val sources: List<AvailableAnimeSources>,
val apkName: String,
val iconUrl: String,
val repository: String
) : AnimeExtension()
data class Untrusted(

View file

@ -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<MangaExtension.Available> {
return withIOContext {
val githubResponse = if (requiresFallbackSource) {
null
} else {
val extensions: ArrayList<MangaExtension.Available> = arrayListOf()
PrefManager.getVal<Set<String>>(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<List<ExtensionJsonObject>>()
.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<List<ExtensionJsonObject>>()
.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<ExtensionJsonObject>.toExtensions(): List<MangaExtension.Available> {
private fun List<ExtensionJsonObject>.toExtensions(repository: String): List<MangaExtension.Available> {
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,

View file

@ -47,6 +47,7 @@ sealed class MangaExtension {
val sources: List<AvailableMangaSources>,
val apkName: String,
val iconUrl: String,
val repository: String
) : MangaExtension()
data class Untrusted(

View file

@ -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"

View file

@ -2,7 +2,9 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<ani.dantotsu.others.Xpandable
android:id="@+id/extensionSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@ -18,14 +20,6 @@
app:drawableEndCompat="@drawable/ic_round_arrow_drop_down_24"
tools:ignore="TextContrastCheck" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="-16dp"
android:background="?android:attr/listDivider" />
<Button
android:id="@+id/userAgent"
android:layout_width="match_parent"
@ -38,8 +32,7 @@
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingStart="31dp"
android:paddingEnd="31dp"
android:paddingHorizontal="31dp"
android:text="@string/user_agent"
android:textAlignment="viewStart"
android:textAllCaps="false"
@ -55,7 +48,118 @@
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="-16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/animeRepoHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:fontFamily="@font/poppins_bold"
android:text="@string/anime_repo_listing"
android:textSize="14sp"
tools:ignore="RtlSymmetry" />
<LinearLayout
android:id="@+id/animeRepoInventory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="36dp"
android:visibility="gone"
tools:ignore="RtlSymmetry"
android:orientation="vertical" />
<Button
android:id="@+id/animeAddRepository"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="-31dp"
android:layout_marginEnd="-31dp"
android:background="@drawable/ui_bg"
android:backgroundTint="?attr/colorSecondary"
android:backgroundTintMode="src_atop"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingHorizontal="31dp"
android:text="@string/anime_add_repository"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:icon="@drawable/ic_github"
app:iconPadding="16dp"
app:iconSize="24dp"
app:iconTint="?attr/colorPrimary" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="-16dp"
android:background="?android:attr/listDivider" />
<View
android:id="@+id/mangaRepoHeadingDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="-16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/mangaRepoHeading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:fontFamily="@font/poppins_bold"
android:text="@string/manga_repo_listing"
android:textSize="14sp"
tools:ignore="RtlSymmetry" />
<LinearLayout
android:id="@+id/mangaRepoInventory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="36dp"
android:visibility="gone"
tools:ignore="RtlSymmetry"
android:orientation="vertical" />
<Button
android:id="@+id/mangaAddRepository"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="-31dp"
android:layout_marginEnd="-31dp"
android:background="@drawable/ui_bg"
android:backgroundTint="?attr/colorSecondary"
android:backgroundTintMode="src_atop"
android:fontFamily="@font/poppins_bold"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingHorizontal="31dp"
android:text="@string/manga_add_repository"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="0dp"
app:icon="@drawable/ic_github"
app:iconPadding="16dp"
app:iconSize="24dp"
app:iconTint="?attr/colorPrimary" />
<View
android:id="@+id/mangaRepoDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="-16dp"
android:layout_marginEnd="-16dp"
android:layout_marginBottom="16dp"
android:background="?android:attr/listDivider" />
<com.google.android.material.materialswitch.MaterialSwitch
@ -113,4 +217,4 @@
app:thumbTint="@color/button_switch_track" />
</ani.dantotsu.others.Xpandable>
</merge>
</merge>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/repositoryItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semi_bold"
android:textSize="12sp"
android:textAlignment="viewStart"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>

View file

@ -252,7 +252,7 @@
<string name="sub_window_color_info">"The subtitle window is the part left and right from them. (where the background isn\'t)"</string>
<string name="sub_color_info"><b>Note:</b> Changing above settings only affects Soft-Subtitles!</string>
<string name="sub_alpha">Subtitle Transparency</string>
<string name="sub_text_example">Example Subtitle</string>
<string name="sub_text_example">Example Sub</string>
<string name="sub_font_select">Subtitle Font</string>
<string name="subtitle_font_size">Subtitle Size</string>
@ -417,6 +417,7 @@
<string name="crop_borders">Crop Borders</string>
<string name="note">NOTE</string>
<string name="manage_extension_repos">Manage Extension Repos</string>
<string name="installing_extension">Installing extension</string>
<string name="installation_failed">Installation failed: %1$s</string>
<string name="installation_complete">Installation complete</string>
@ -859,6 +860,13 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
<string name="title_color">Title Color</string>
<string name="stat_text_color">Stats Text Color</string>
<string name="placeholder">Placeholder</string>
<string name="anime_repo_listing">Anime Extension Repos</string>
<string name="anime_add_repository">Add Anime Repo</string>
<string name="manga_repo_listing">Manga Extension Repos</string>
<string name="manga_add_repository">Add Manga Repo</string>
<string name="long_click_delete">Long click to delete</string>
<string name="trending_movies">Trending Movies</string>
<string name="include_list">Include list</string>
<string name="top_rated">Top rated</string>