Twelve: Ensure server is proper
diff --git a/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt b/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt
index 50f62b0..b406700 100644
--- a/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt
+++ b/app/src/main/java/org/lineageos/twelve/datasources/SubsonicDataSource.kt
@@ -11,6 +11,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapLatest
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.lineageos.twelve.R
import org.lineageos.twelve.datasources.subsonic.SubsonicClient
import org.lineageos.twelve.datasources.subsonic.models.AlbumID3
@@ -325,6 +326,16 @@
R.string.provider_argument_server,
required = true,
hidden = false,
+ validate = {
+ when (it.toHttpUrlOrNull()) {
+ null -> ProviderArgument.ValidationError(
+ "Invalid URL",
+ R.string.provider_argument_validation_error_malformed_http_uri,
+ )
+
+ else -> null
+ }
+ }
)
val ARG_USERNAME = ProviderArgument(
diff --git a/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt b/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
index bdedfe0..1b59fef 100644
--- a/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
+++ b/app/src/main/java/org/lineageos/twelve/fragments/ManageProviderFragment.kt
@@ -33,7 +33,7 @@
import org.lineageos.twelve.ext.getViewProperty
import org.lineageos.twelve.ext.selectItem
import org.lineageos.twelve.models.ProviderArgument
-import org.lineageos.twelve.models.ProviderArgument.Companion.getArgument
+import org.lineageos.twelve.models.ProviderArgument.Companion.validateArgument
import org.lineageos.twelve.models.ProviderType
import org.lineageos.twelve.models.RequestStatus
import org.lineageos.twelve.ui.recyclerview.SimpleListAdapter
@@ -169,14 +169,14 @@
return@setOnClickListener
}
- val wrongArguments = providerType.arguments.filter { argument ->
- val value = providerArguments.getArgument(argument)
-
- (value ?: argument.defaultValue) == null && argument.required
+ val wrongArguments = providerType.arguments.mapNotNull { argument ->
+ providerArguments.validateArgument(argument)?.let {
+ argument to it
+ }
}
if (wrongArguments.isNotEmpty()) {
- showMissingArgumentsDialog(wrongArguments)
+ showArgumentValidationErrorDialog(wrongArguments)
return@setOnClickListener
}
@@ -303,13 +303,20 @@
super.onDestroyView()
}
- private fun showMissingArgumentsDialog(wrongArguments: List<ProviderArgument<*>>) {
+ private fun showArgumentValidationErrorDialog(
+ wrongArguments: List<Pair<ProviderArgument<*>, ProviderArgument.ValidationError>>
+ ) {
MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.argument_validation_error_title)
.setMessage(
getString(
- R.string.missing_provider_arguments,
- wrongArguments.joinToString {
- getString(it.nameStringResId)
+ R.string.argument_validation_error_message,
+ wrongArguments.joinToString(separator = "\n") {
+ getString(
+ R.string.argument_validation_error_item,
+ getString(it.first.nameStringResId),
+ getString(it.second.messageStringResId),
+ )
}
)
)
diff --git a/app/src/main/java/org/lineageos/twelve/models/ProviderArgument.kt b/app/src/main/java/org/lineageos/twelve/models/ProviderArgument.kt
index 1709255..e48b35e 100644
--- a/app/src/main/java/org/lineageos/twelve/models/ProviderArgument.kt
+++ b/app/src/main/java/org/lineageos/twelve/models/ProviderArgument.kt
@@ -7,6 +7,7 @@
import android.os.Bundle
import androidx.annotation.StringRes
+import org.lineageos.twelve.R
import kotlin.reflect.KClass
import kotlin.reflect.cast
@@ -20,6 +21,8 @@
* @param required Whether this argument is required
* @param hidden Whether the value of this argument should be hidden
* @param defaultValue The default value of the argument
+ * @param validate A lambda to validate the value of the argument, returning a
+ * [ProviderArgument.ValidationError] if the value is invalid, null otherwise
*/
data class ProviderArgument<T : Any>(
val key: String,
@@ -28,28 +31,76 @@
val required: Boolean,
val hidden: Boolean,
val defaultValue: T? = null,
+ val validate: ((T) -> ValidationError?) = { null },
) {
- fun getValue(value: T?) = value ?: defaultValue
+ /**
+ * Validation error of a [ProviderArgument].
+ *
+ * @param message The error message
+ * @param messageStringResId The localized error message string resource ID
+ */
+ data class ValidationError(
+ val message: String,
+ @StringRes val messageStringResId: Int,
+ )
companion object {
+ private val requiredValidationError = ValidationError(
+ "A value is required",
+ R.string.provider_argument_validation_error_required,
+ )
+
/**
- * Get the optional argument from a [Bundle].
+ * Get the argument value from a [Bundle] or the default value if it is not present.
*/
- fun <T : Any> Bundle.getArgument(
+ private fun <T : Any> Bundle.getArgumentValue(
providerArguments: ProviderArgument<T>
- ) = when (providerArguments.type) {
- String::class -> getString(providerArguments.key)
- Boolean::class -> getBoolean(providerArguments.key)
- else -> throw Exception("Unsupported type")
- }?.let {
- providerArguments.getValue(providerArguments.type.cast(it))
+ ) = when (containsKey(providerArguments.key)) {
+ true -> when (providerArguments.type) {
+ String::class -> getString(providerArguments.key)
+ Boolean::class -> getBoolean(providerArguments.key)
+ else -> throw Exception("Unsupported type")
+ }?.let {
+ providerArguments.type.cast(it)
+ }
+
+ false -> providerArguments.defaultValue
}
/**
- * Get the required argument from a [Bundle].
+ * Get the optional argument from a [Bundle]. This will also validate the value and throw
+ * an exception if the value is invalid.
+ */
+ fun <T : Any> Bundle.getArgument(
+ providerArguments: ProviderArgument<T>
+ ) = getArgumentValue(providerArguments)?.also { argumentValue ->
+ providerArguments.validate(argumentValue)?.let {
+ throw Exception(
+ "Validation error for argument ${providerArguments.key}: ${it.message}"
+ )
+ }
+ }
+
+ /**
+ * Get the required argument from a [Bundle]. This will also validate the value and throw
+ * an exception if the value is invalid.
*/
fun <T : Any> Bundle.requireArgument(
providerArguments: ProviderArgument<T>
) = getArgument(providerArguments) ?: throw Exception("Argument not found")
+
+ /**
+ * Validate the argument in this [Bundle] and return a [ValidationError] if it is invalid.
+ * Will also check if the argument is required.
+ */
+ fun <T : Any> Bundle.validateArgument(
+ providerArguments: ProviderArgument<T>
+ ) = getArgumentValue(providerArguments).let { argumentValue ->
+ argumentValue?.let {
+ providerArguments.validate(it)
+ } ?: requiredValidationError.takeIf {
+ providerArguments.required && argumentValue == null
+ }
+ }
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 929ddd6..2b4f3df 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -116,6 +116,10 @@
<string name="provider_argument_password">Password</string>
<string name="provider_argument_use_legacy_authentication">Use legacy authentication</string>
+ <!-- Provider arguments validation errors -->
+ <string name="provider_argument_validation_error_required">A value is required</string>
+ <string name="provider_argument_validation_error_malformed_http_uri">Must be a valid HTTP or HTTPS URL (e.g. https://google.com)</string>
+
<!-- Provider selector dialog fragment -->
<string name="providers">Providers</string>
<string name="add_provider_action">Add</string>
@@ -130,7 +134,9 @@
<string name="provider_name_error">Enter a name</string>
<string name="provider_type">Provider type</string>
<string name="provider_type_error">Select a provider type</string>
- <string name="missing_provider_arguments">The following arguments are required: %1$s</string>
+ <string name="argument_validation_error_title">Error</string>
+ <string name="argument_validation_error_message">Error while parsing the following arguments:\n%1$s</string>
+ <string name="argument_validation_error_item">%1$s: %2$s</string>
<!-- Now playing widget -->
<string name="now_playing_widget_description">Now playing</string>