diff --git a/compiler/src/main/kotlin/keiyoushi/processor/SourceProcessor.kt b/compiler/src/main/kotlin/keiyoushi/processor/SourceProcessor.kt index 50176a0b97e..2a859cd6000 100644 --- a/compiler/src/main/kotlin/keiyoushi/processor/SourceProcessor.kt +++ b/compiler/src/main/kotlin/keiyoushi/processor/SourceProcessor.kt @@ -46,12 +46,14 @@ data class BaseUrlSpecData( val type: String, val defaultUrl: String, val mirrors: List = emptyList(), + val withCustom: Boolean = false, ) @Serializable data class MirrorData( val url: String, val label: String = "", + val value: String = "", ) @Serializable @@ -69,8 +71,7 @@ private fun KSClassDeclaration.derivesFromHttpSource(): Boolean = private val configurable = ClassName("eu.kanade.tachiyomi.source", "ConfigurableSource") private val preferenceScreen = ClassName("androidx.preference", "PreferenceScreen") -private val mirrorPrefsClass = ClassName("keiyoushi.source", "MirrorPreferences") -private val customUrlPrefsClass = ClassName("keiyoushi.source", "CustomUrlPreferences") +private val customAndMirrorPrefsClass = ClassName("keiyoushi.source", "CustomAndMirrorPreferences") private val getPreferencesFn = MemberName("keiyoushi.utils", "getPreferences") class SourceProcessor( @@ -284,7 +285,7 @@ class SourceProcessor( if ("name" !in ctorParams && "name" in overridden) { logger.warn("name is provided by $className; the DSL name is used for metadata only.", node) } - + if ("baseUrl" !in ctorParams && "baseUrl" in overridden) { logger.warn("baseUrl is provided by $className; the DSL baseUrl is used for metadata/hosts only.", node) } @@ -362,52 +363,53 @@ class SourceProcessor( .build(), ) } - urlSpec.type == "mirrors" -> { + urlSpec.type == "mirrors" || urlSpec.type == "custom" -> { val strings = stringsForLang(source.lang) - val prefsName = "mirrorPrefs$suffix" + val prefsName = "baseUrlPrefs$suffix" val initializer = CodeBlock.builder() - .add("%T(\n", mirrorPrefsClass) + .add("%T(\n", customAndMirrorPrefsClass) .indent() .add("preferences = %M(%LL),\n", getPreferencesFn, source.id) - .add("mirrors = arrayOf(\n") + .add("mirrors = listOf(\n") .indent() .apply { urlSpec.mirrors.forEach { mirror -> - add("%S to %S,\n", mirror.label, mirror.url) + add("%S,\n", mirror.url) } } .unindent() .add("),\n") - .add("title = %S,\n", strings.mirrorTitle) - .unindent() - .add(")") - .build() - fileProps += PropertySpec.builder(prefsName, mirrorPrefsClass) - .addModifiers(KModifier.PRIVATE) - .initializer(initializer) - .build() - addProperty( - PropertySpec.builder("baseUrl", String::class.asClassName(), KModifier.OVERRIDE) - .getter(FunSpec.getterBuilder().addStatement("return %N.baseUrl", prefsName).build()) - .build(), - ) - addPreferenceScreen(isConfigurable) { addStatement("%N.setupPreferenceScreen(screen)", prefsName) } - if (!isConfigurable) addSuperinterface(configurable) - } - urlSpec.type == "custom" -> { - val strings = stringsForLang(source.lang) - val prefsName = "customUrlPrefs$suffix" - val initializer = CodeBlock.builder() - .add("%T(\n", customUrlPrefsClass) - .indent() - .add("preferences = %M(%LL),\n", getPreferencesFn, source.id) .add("defaultUrl = %S,\n", urlSpec.defaultUrl) - .add("title = %S,\n", strings.customUrlTitle) - .add("dialogMessage = %S,\n", strings.customUrlDialogMessage) + .add("withCustom = %L,\n", urlSpec.withCustom) + .add("mirrorTitle = %S,\n", strings.mirrorTitle) + .add("customTitle = %S,\n", strings.customUrlTitle) + .add("customDialogMessage = %S,\n", strings.customUrlDialogMessage) + .apply { + if (urlSpec.mirrors.any { it.label.isNotBlank() }) { + add("mirrorEntries = listOf(\n") + indent() + urlSpec.mirrors.forEach { mirror -> + val label = mirror.label.takeIf { it.isNotBlank() } ?: mirror.url + add("%S,\n", label) + } + unindent() + add("),\n") + } + if (urlSpec.mirrors.any { it.value.isNotBlank() }) { + add("mirrorEntryValues = listOf(\n") + indent() + urlSpec.mirrors.forEach { mirror -> + val value = mirror.value.takeIf { it.isNotBlank() } ?: urlSpec.mirrors.indexOf(mirror).toString() + add("%S,\n", value) + } + unindent() + add("),\n") + } + } .unindent() .add(")") .build() - fileProps += PropertySpec.builder(prefsName, customUrlPrefsClass) + fileProps += PropertySpec.builder(prefsName, customAndMirrorPrefsClass) .addModifiers(KModifier.PRIVATE) .initializer(initializer) .build() diff --git a/core/src/main/kotlin/keiyoushi/source/CustomAndMirrorPreferences.kt b/core/src/main/kotlin/keiyoushi/source/CustomAndMirrorPreferences.kt new file mode 100644 index 00000000000..c6594c53756 --- /dev/null +++ b/core/src/main/kotlin/keiyoushi/source/CustomAndMirrorPreferences.kt @@ -0,0 +1,141 @@ +package keiyoushi.source + +import android.content.SharedPreferences +import android.text.Editable +import android.text.TextWatcher +import android.widget.Button +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import java.net.URI + +class CustomAndMirrorPreferences( + private val preferences: SharedPreferences, + private val mirrors: List, + val defaultUrl: String, + private val withCustom: Boolean, + private val mirrorTitle: String, + private val customTitle: String, + private val customDialogMessage: String, + mirrorEntries: List? = null, + mirrorEntryValues: List? = null, +) { + private val prefKey: String = "preferred_mirror" + private val prefBaseKey: String = "overrideBaseUrl" + private val prefDefaultKey: String = "defaultBaseUrl" + + private val entries = ( + mirrorEntries ?: mirrors.map { url -> + runCatching { URI(url).host }.getOrDefault(url) + } + ).run { if (this@CustomAndMirrorPreferences.withCustom) plus("Custom") else this } + + private val entryValues = ( + mirrorEntryValues ?: mirrors.indices.map { it.toString() } + ).run { if (this@CustomAndMirrorPreferences.withCustom) plus("custom") else this } + + init { + if (withCustom) { + val storedDefault = preferences.getString(prefDefaultKey, null) + if (storedDefault != defaultUrl) { + preferences.edit() + .putString(prefBaseKey, defaultUrl) + .putString(prefDefaultKey, defaultUrl) + .apply() + } + } + } + + val baseUrl: String + get() { + val selected = preferences.getString(prefKey, entryValues.firstOrNull() ?: "0") + val url = if (selected == "custom") { + preferences.getString(prefBaseKey, defaultUrl) ?: defaultUrl + } else { + val index = entryValues.indexOf(selected).coerceIn(0, mirrors.size - 1) + mirrors.getOrNull(index) ?: defaultUrl + } + return url.removeSuffix("/") + } + + fun setupPreferenceScreen(screen: PreferenceScreen) { + val customUrlPref = if (withCustom) { + EditTextPreference(screen.context).apply { + key = prefBaseKey + title = customTitle + dialogTitle = customTitle + dialogMessage = customDialogMessage + + val currentValue = preferences.getString(prefBaseKey, null) + summary = currentValue?.takeIf { it.isNotBlank() } ?: defaultUrl + setDefaultValue(defaultUrl) + + setEnabled(preferences.getString(prefKey, entryValues.firstOrNull()) == "custom") + + setOnBindEditTextListener { editText -> + editText.hint = defaultUrl + editText.setHorizontallyScrolling(true) + editText.post { editText.selectAll() } + editText.addTextChangedListener( + object : TextWatcher { + override fun afterTextChanged(editable: Editable?) { + val text = editable?.toString() ?: "" + val isValid = text.isBlank() || text.toHttpUrlOrNull() != null + editText.rootView.findViewById