diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt index 7e7f7c592..b93181dee 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt @@ -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) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerHysteria2Activity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerHysteria2Activity.kt index 85b8886cc..5d5692853 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerHysteria2Activity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerHysteria2Activity.kt @@ -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), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerShadowsocksActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerShadowsocksActivity.kt index ee8127643..a06f225a5 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerShadowsocksActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerShadowsocksActivity.kt @@ -52,9 +52,10 @@ class ServerShadowsocksActivity : BaseServerActivity() { methodOptions: List ) { 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), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerTrojanActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerTrojanActivity.kt index 9713c3d20..62c376665 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerTrojanActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerTrojanActivity.kt @@ -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 ) } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt index 767399f86..eb9bf1ca7 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerUiState.kt @@ -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 diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVlessActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVlessActivity.kt index 19595c393..8099d28c6 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVlessActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVlessActivity.kt @@ -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 ) { 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), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVmessActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVmessActivity.kt index 7eec9f46b..9217bcb83 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVmessActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerVmessActivity.kt @@ -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 ) { 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), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt index ca0c691da..793f44697 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt @@ -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(