mirror of
https://github.com/2dust/v2rayNG.git
synced 2026-10-29 06:31:54 +00:00
Show inline validation errors in forms
Move several save-time validation failures from toast messages to field error states in the server and subscription editors. Password validation now marks the relevant server form field, and subscription editing highlights invalid URLs and update intervals before save instead of relying on transient toasts.
This commit is contained in:
1 parent
e4c0b09e1b
commit
1ad5f30ede
8 files changed
+50
-44
No files matched your search
@@ -382,13 +382,13 @@ abstract class BaseServerActivity : BaseComponentActivity() {
|
||||
|
||||
protected open fun validateProtocolConfig(config: ProfileItem): Boolean = true
|
||||
|
||||
protected open fun validateCommonConfig(config: ProfileItem): Boolean {
|
||||
protected open fun validateCommonConfig(state: ServerUiState, config: ProfileItem): Boolean {
|
||||
|
||||
if (config.password.isNullOrBlank()) {
|
||||
state.isPasswordError = true
|
||||
if (config.configType == EConfigType.VMESS ||
|
||||
config.configType == EConfigType.VLESS
|
||||
) {
|
||||
toast(R.string.server_lab_id)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -396,7 +396,6 @@ abstract class BaseServerActivity : BaseComponentActivity() {
|
||||
config.configType == EConfigType.SHADOWSOCKS ||
|
||||
config.configType == EConfigType.HYSTERIA2
|
||||
) {
|
||||
toast(R.string.server_lab_id3)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -422,7 +421,7 @@ abstract class BaseServerActivity : BaseComponentActivity() {
|
||||
protected fun saveServer(state: ServerUiState): Boolean {
|
||||
if (!validateBasicConfig(state)) return false
|
||||
val config = state.toProfileItem(initialConfig)
|
||||
if (!validateCommonConfig(config)) return false
|
||||
if (!validateCommonConfig(state, config)) return false
|
||||
if (!validateProtocolConfig(config)) return false
|
||||
|
||||
config.description = AngConfigManager.generateDescription(config)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package com.v2ray.ang.ui.server
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.ui.compose.FormTextField
|
||||
import com.v2ray.ang.ui.compose.SettingsSwitchItem
|
||||
|
||||
@@ -18,7 +16,6 @@ class ServerHysteria2Activity : BaseServerActivity() {
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val uiState = rememberSaveable(saver = ServerUiState.Saver) {
|
||||
ServerUiState.from(
|
||||
initialConfig = initialConfig
|
||||
@@ -39,7 +36,6 @@ class ServerHysteria2Activity : BaseServerActivity() {
|
||||
|
||||
override fun validateProtocolConfig(config: ProfileItem): Boolean {
|
||||
if (config.password.isNullOrBlank()) {
|
||||
toast(R.string.server_lab_id3)
|
||||
return false
|
||||
}
|
||||
if (config.security.isNullOrBlank()) {
|
||||
@@ -51,9 +47,10 @@ class ServerHysteria2Activity : BaseServerActivity() {
|
||||
@Composable
|
||||
private fun Hysteria2ProtocolFields(state: ServerUiState) {
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_id3),
|
||||
state.password,
|
||||
{ state.password = it }
|
||||
label = stringResource(R.string.server_lab_id3),
|
||||
value = state.password,
|
||||
onValueChange = { state.password = it },
|
||||
isError = state.isPasswordError
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_obfs_password),
|
||||
|
||||
@@ -52,9 +52,10 @@ class ServerShadowsocksActivity : BaseServerActivity() {
|
||||
methodOptions: List<String>
|
||||
) {
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_id3),
|
||||
state.password,
|
||||
{ state.password = it }
|
||||
label = stringResource(R.string.server_lab_id3),
|
||||
value = state.password,
|
||||
onValueChange = { state.password = it },
|
||||
isError = state.isPasswordError
|
||||
)
|
||||
FormDropdownField(
|
||||
stringResource(R.string.server_lab_security),
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.ui.compose.FormTextField
|
||||
|
||||
class ServerTrojanActivity : BaseServerActivity() {
|
||||
@@ -44,11 +43,9 @@ class ServerTrojanActivity : BaseServerActivity() {
|
||||
|
||||
override fun validateProtocolConfig(config: ProfileItem): Boolean {
|
||||
if (config.password.isNullOrBlank()) {
|
||||
toast(R.string.server_lab_id3)
|
||||
return false
|
||||
}
|
||||
if (config.security.isNullOrBlank()) {
|
||||
toast(R.string.server_lab_stream_security)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -57,9 +54,10 @@ class ServerTrojanActivity : BaseServerActivity() {
|
||||
@Composable
|
||||
private fun TrojanProtocolFields(state: ServerUiState) {
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_id3),
|
||||
state.password,
|
||||
{ state.password = it }
|
||||
label = stringResource(R.string.server_lab_id3),
|
||||
value = state.password,
|
||||
onValueChange = { state.password = it },
|
||||
isError = state.isPasswordError
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ class ServerUiState(
|
||||
var isRemarksError by mutableStateOf(false)
|
||||
var isAddressError by mutableStateOf(false)
|
||||
var isPortError by mutableStateOf(false)
|
||||
var isPasswordError by mutableStateOf(false)
|
||||
|
||||
fun toProfileItem(initialConfig: ProfileItem): ProfileItem {
|
||||
val isVmess = configType == EConfigType.VMESS
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.ui.compose.FormDropdownField
|
||||
import com.v2ray.ang.ui.compose.FormTextField
|
||||
|
||||
@@ -47,7 +46,6 @@ class ServerVlessActivity : BaseServerActivity() {
|
||||
|
||||
override fun validateProtocolConfig(config: ProfileItem): Boolean {
|
||||
if (config.password.isNullOrBlank()) {
|
||||
toast(R.string.server_lab_id)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -59,9 +57,10 @@ class ServerVlessActivity : BaseServerActivity() {
|
||||
flowOptions: List<String>
|
||||
) {
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_id),
|
||||
state.password,
|
||||
{ state.password = it }
|
||||
label = stringResource(R.string.server_lab_id),
|
||||
value = state.password,
|
||||
onValueChange = { state.password = it },
|
||||
isError = state.isPasswordError
|
||||
)
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_encryption),
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.ui.compose.FormDropdownField
|
||||
import com.v2ray.ang.ui.compose.FormTextField
|
||||
|
||||
@@ -47,7 +46,6 @@ class ServerVmessActivity : BaseServerActivity() {
|
||||
|
||||
override fun validateProtocolConfig(config: ProfileItem): Boolean {
|
||||
if (config.password.isNullOrBlank()) {
|
||||
toast(R.string.server_lab_id)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -59,9 +57,10 @@ class ServerVmessActivity : BaseServerActivity() {
|
||||
methodOptions: List<String>
|
||||
) {
|
||||
FormTextField(
|
||||
stringResource(R.string.server_lab_id),
|
||||
state.password,
|
||||
{ state.password = it }
|
||||
label = stringResource(R.string.server_lab_id),
|
||||
value = state.password,
|
||||
onValueChange = { state.password = it },
|
||||
isError = state.isPasswordError
|
||||
)
|
||||
FormDropdownField(
|
||||
stringResource(R.string.server_lab_security),
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.entities.SubscriptionItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.toLongEx
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsChangeManager
|
||||
@@ -78,25 +77,19 @@ class SubEditActivity : BaseComponentActivity() {
|
||||
}
|
||||
|
||||
private fun saveServer(subItem: SubscriptionItem): Boolean {
|
||||
|
||||
if (TextUtils.isEmpty(subItem.remarks)) {
|
||||
return false
|
||||
}
|
||||
if (subItem.url.isNotEmpty()) {
|
||||
if (!Utils.isValidUrl(subItem.url)) {
|
||||
toast(R.string.toast_invalid_url)
|
||||
return false
|
||||
}
|
||||
if (!Utils.isValidSubUrl(subItem.url)) {
|
||||
toast(R.string.toast_insecure_url_protocol)
|
||||
if (!subItem.allowInsecureUrl) {
|
||||
return false
|
||||
}
|
||||
if (!Utils.isValidSubUrl(subItem.url) && !subItem.allowInsecureUrl) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (subItem.autoUpdate && subItem.updateInterval < AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES) {
|
||||
toast(R.string.toast_invalid_update_interval)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -133,12 +126,14 @@ fun SubEditScreen(
|
||||
var remarks by rememberSaveable { mutableStateOf(initial.remarks.orEmpty()) }
|
||||
var isRemarksError by rememberSaveable { mutableStateOf(false) }
|
||||
var url by rememberSaveable { mutableStateOf(initial.url.orEmpty()) }
|
||||
var isUrlError by rememberSaveable { mutableStateOf(false) }
|
||||
var userAgent by rememberSaveable { mutableStateOf(initial.userAgent.orEmpty()) }
|
||||
var requestHeaders by rememberSaveable { mutableStateOf(initial.requestHeaders.orEmpty()) }
|
||||
var filter by rememberSaveable { mutableStateOf(initial.filter ?: "") }
|
||||
var enabled by rememberSaveable { mutableStateOf(initial.enabled) }
|
||||
var autoUpdate by rememberSaveable { mutableStateOf(initial.autoUpdate) }
|
||||
var updateInterval by rememberSaveable { mutableStateOf(initial.updateInterval.toString()) }
|
||||
var isUpdateIntervalError by rememberSaveable { mutableStateOf(false) }
|
||||
var allowInsecureUrl by rememberSaveable { mutableStateOf(initial.allowInsecureUrl) }
|
||||
var prevProfile by rememberSaveable { mutableStateOf(initial.prevProfile ?: "") }
|
||||
var nextProfile by rememberSaveable { mutableStateOf(initial.nextProfile ?: "") }
|
||||
@@ -179,9 +174,16 @@ fun SubEditScreen(
|
||||
}
|
||||
IconButton(onClick = {
|
||||
val remarksErr = remarks.isBlank()
|
||||
isRemarksError = remarksErr
|
||||
val urlErr = url.isNotEmpty() && (
|
||||
!Utils.isValidUrl(url) || (!Utils.isValidSubUrl(url) && !allowInsecureUrl)
|
||||
)
|
||||
val intervalErr = autoUpdate && updateInterval.toLongEx() < AppConfig.SUBSCRIPTION_MIN_INTERVAL_MINUTES
|
||||
|
||||
val hasError = remarksErr
|
||||
isRemarksError = remarksErr
|
||||
isUrlError = urlErr
|
||||
isUpdateIntervalError = intervalErr
|
||||
|
||||
val hasError = remarksErr || urlErr || intervalErr
|
||||
if (!hasError) {
|
||||
onSave(buildSubItem())
|
||||
}
|
||||
@@ -209,7 +211,13 @@ fun SubEditScreen(
|
||||
onValueChange = { remarks = it },
|
||||
isError = isRemarksError
|
||||
)
|
||||
FormTextField(stringResource(R.string.sub_setting_url), url, { url = it })
|
||||
FormTextField(
|
||||
label = stringResource(R.string.sub_setting_url),
|
||||
value = url,
|
||||
onValueChange = { url = it },
|
||||
isError = isUrlError,
|
||||
supportingText = if (isUrlError) stringResource(R.string.toast_invalid_url) else null
|
||||
)
|
||||
FormTextField(stringResource(R.string.sub_setting_user_agent), userAgent, { userAgent = it })
|
||||
FormTextField(stringResource(R.string.sub_setting_request_headers), requestHeaders, { requestHeaders = it })
|
||||
FormTextField(stringResource(R.string.sub_setting_filter), filter, { filter = it })
|
||||
@@ -226,8 +234,12 @@ fun SubEditScreen(
|
||||
)
|
||||
|
||||
FormTextField(
|
||||
stringResource(R.string.title_pref_auto_update_interval),
|
||||
updateInterval, { updateInterval = it }, keyboardType = KeyboardType.Number
|
||||
label = stringResource(R.string.title_pref_auto_update_interval),
|
||||
value = updateInterval,
|
||||
onValueChange = { updateInterval = it },
|
||||
keyboardType = KeyboardType.Number,
|
||||
isError = isUpdateIntervalError,
|
||||
supportingText = if (isUpdateIntervalError) stringResource(R.string.toast_invalid_update_interval) else null
|
||||
)
|
||||
|
||||
SettingsSwitchItem(
|
||||
|
||||
Reference in new issue
Block a user