Delete ServerActivity.kt

This commit is contained in:
2dust committed 2026-09-21 19:13:13 +08:00
1 parent 0e80f6b431
commit 1bd1bceadb
1 file changed
-527
@@ -1,527 +0,0 @@
package com.v2ray.ang.ui.server
import android.os.Bundle
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.v2ray.ang.AppConfig.DEFAULT_PORT
import com.v2ray.ang.AppConfig.REALITY
import com.v2ray.ang.AppConfig.TLS
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_ADDRESS_V4
import com.v2ray.ang.AppConfig.WIREGUARD_LOCAL_MTU
import com.v2ray.ang.R
import com.v2ray.ang.dto.entities.ProfileItem
import com.v2ray.ang.enums.EConfigType
import com.v2ray.ang.enums.NetworkType
import com.v2ray.ang.extension.nullIfBlank
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.toastSuccess
import com.v2ray.ang.handler.AngConfigManager
import com.v2ray.ang.handler.CertificateFingerprintManager
import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.ui.base.BaseComponentActivity
import com.v2ray.ang.ui.compose.AppTopBar
import com.v2ray.ang.ui.compose.DeleteConfirmDialog
import com.v2ray.ang.ui.compose.FormDropdownField
import com.v2ray.ang.ui.compose.FormTextField
import com.v2ray.ang.ui.compose.SettingsSwitchItem
import com.v2ray.ang.ui.compose.verticalScrollbar
import com.v2ray.ang.util.JsonUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ServerActivity : BaseComponentActivity() {
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy {
intent.getBooleanExtra("isRunning", false)
&& editGuid.isNotEmpty()
&& editGuid == MmkvManager.getSelectServer()
}
private val createConfigType by lazy {
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value))
?: EConfigType.VMESS
}
private val subscriptionId by lazy { intent.getStringExtra("subscriptionId") }
private lateinit var initialConfig: ProfileItem
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val existingConfig = MmkvManager.decodeServerConfig(editGuid)
initialConfig = existingConfig ?: ProfileItem.create(createConfigType)
}
@Composable
override fun ScreenContent() {
ServerScreen(
guid = editGuid,
configType = initialConfig.configType,
initialConfig = initialConfig,
isRunning = isRunning,
onBackClick = { finish() },
onSave = { saveServer(it) },
onDelete = { deleteServer(editGuid, isRunning) }
)
}
private fun saveServer(config: ProfileItem): Boolean {
if (config.remarks.isBlank()) {
toast(R.string.server_lab_remarks)
return false
}
if (config.server.isNullOrBlank()) {
toast(R.string.server_lab_address)
return false
}
if (
config.configType != EConfigType.HYSTERIA2 &&
(config.serverPort?.toIntOrNull() ?: 0) <= 0
) {
toast(R.string.server_lab_port)
return false
}
if (
config.configType != EConfigType.SOCKS &&
config.configType != EConfigType.HTTP &&
config.password.isNullOrBlank()
) {
val message = when (config.configType) {
EConfigType.TROJAN,
EConfigType.SHADOWSOCKS,
EConfigType.HYSTERIA2 -> R.string.server_lab_id3
else -> R.string.server_lab_id
}
toast(message)
return false
}
if (
config.configType == EConfigType.TROJAN &&
config.security.isNullOrBlank()
) {
toast(R.string.server_lab_stream_security)
return false
}
if (
!config.xhttpExtra.isNullOrBlank() &&
JsonUtil.parseString(config.xhttpExtra) == null
) {
toast(R.string.server_lab_xhttp_extra)
return false
}
if (
!config.finalMask.isNullOrBlank() &&
JsonUtil.parseString(config.finalMask) == null
) {
toast(R.string.server_lab_final_mask)
return false
}
config.description =
AngConfigManager.generateDescription(config)
if (
config.subscriptionId.isEmpty() &&
!subscriptionId.isNullOrEmpty()
) {
config.subscriptionId = subscriptionId.orEmpty()
}
val savedGuid = MmkvManager.encodeServerConfig(
editGuid,
config
)
toastSuccess(R.string.toast_success)
ProfileEditorResult.run {
finishSaved(
guid = savedGuid,
restartService = isRunning
)
}
return true
}
private fun deleteServer(
guid: String,
isRunning: Boolean
) {
if (
guid.isEmpty() ||
guid == MmkvManager.getSelectServer()
) {
toast(R.string.toast_action_not_allowed)
return
}
MmkvManager.removeServer(guid)
ProfileEditorResult.run {
finishDeleted(guid)
}
}
}
@Composable
fun ServerScreen(
guid: String,
configType: EConfigType,
initialConfig: ProfileItem,
isRunning: Boolean,
onBackClick: () -> Unit,
onSave: (ProfileItem) -> Boolean,
onDelete: () -> Unit
) {
val context = LocalContext.current
val securityOptions = stringArrayResource(R.array.securitys).toList()
val ssSecurityOptions = stringArrayResource(R.array.ss_securitys).toList()
val flowOptions = stringArrayResource(R.array.flows).toList()
val networkOptions = stringArrayResource(R.array.networks).toList()
val tcpHeaderOptions = stringArrayResource(R.array.header_type_tcp).toList()
val kcpHeaderOptions = stringArrayResource(R.array.header_type_kcp_and_quic).toList()
val grpcModeOptions = stringArrayResource(R.array.mode_type_grpc).toList()
val xhttpModeOptions = stringArrayResource(R.array.xhttp_mode).toList()
val streamSecurityOptions = stringArrayResource(R.array.streamsecurityxs).toList()
val uTlsOptions = stringArrayResource(R.array.streamsecurity_utls).toList()
val alpnOptions = stringArrayResource(R.array.streamsecurity_alpn).toList()
val browserDialerOptions = stringArrayResource(R.array.browser_dialer_mode_value).toList()
var remarks by rememberSaveable { mutableStateOf(initialConfig.remarks) }
var address by rememberSaveable { mutableStateOf(initialConfig.server ?: "") }
var port by rememberSaveable { mutableStateOf(initialConfig.serverPort ?: DEFAULT_PORT.toString()) }
var password by rememberSaveable { mutableStateOf(initialConfig.password ?: "") }
var method by rememberSaveable { mutableStateOf(initialConfig.method ?: "") }
var flow by rememberSaveable { mutableStateOf(initialConfig.flow ?: "") }
var encryption by rememberSaveable { mutableStateOf(initialConfig.method ?: "") }
var username by rememberSaveable { mutableStateOf(initialConfig.username ?: "") }
var secretKey by rememberSaveable { mutableStateOf(initialConfig.secretKey ?: "") }
var publicKey by rememberSaveable { mutableStateOf(initialConfig.publicKey ?: "") }
var preSharedKey by rememberSaveable { mutableStateOf(initialConfig.preSharedKey ?: "") }
var reserved by rememberSaveable { mutableStateOf(initialConfig.reserved ?: "0,0,0") }
var localAddress by rememberSaveable { mutableStateOf(initialConfig.localAddress ?: WIREGUARD_LOCAL_ADDRESS_V4) }
var mtu by rememberSaveable { mutableStateOf(initialConfig.mtu?.toString() ?: WIREGUARD_LOCAL_MTU) }
var obfsPassword by rememberSaveable { mutableStateOf(initialConfig.obfsPassword ?: "") }
var portHopping by rememberSaveable { mutableStateOf(initialConfig.portHopping ?: "") }
var portHoppingInterval by rememberSaveable { mutableStateOf(initialConfig.portHoppingInterval ?: "") }
var bandwidthDown by rememberSaveable { mutableStateOf(initialConfig.bandwidthDown ?: "") }
var bandwidthUp by rememberSaveable { mutableStateOf(initialConfig.bandwidthUp ?: "") }
var network by rememberSaveable { mutableStateOf(initialConfig.network ?: NetworkType.TCP.type) }
var headerType by rememberSaveable { mutableStateOf(initialConfig.headerType ?: "none") }
var host by rememberSaveable { mutableStateOf(initialConfig.host ?: "") }
var path by rememberSaveable { mutableStateOf(initialConfig.path ?: "") }
var seed by rememberSaveable { mutableStateOf(initialConfig.seed ?: "") }
var quicSecurity by rememberSaveable { mutableStateOf(initialConfig.quicSecurity ?: "") }
var quicKey by rememberSaveable { mutableStateOf(initialConfig.quicKey ?: "") }
var mode by rememberSaveable { mutableStateOf(initialConfig.mode ?: "") }
var serviceName by rememberSaveable { mutableStateOf(initialConfig.serviceName ?: "") }
var authority by rememberSaveable { mutableStateOf(initialConfig.authority ?: "") }
var xhttpMode by rememberSaveable { mutableStateOf(initialConfig.xhttpMode ?: "") }
var xhttpExtra by rememberSaveable { mutableStateOf(initialConfig.xhttpExtra ?: "") }
var finalMask by rememberSaveable { mutableStateOf(initialConfig.finalMask ?: "") }
var kcpMtu by rememberSaveable { mutableStateOf(initialConfig.kcpMtu?.toString() ?: "") }
var kcpTti by rememberSaveable { mutableStateOf(initialConfig.kcpTti?.toString() ?: "") }
var browserDialerMode by rememberSaveable { mutableStateOf(initialConfig.browserDialerMode ?: "") }
var streamSecurity by rememberSaveable { mutableStateOf(initialConfig.security ?: "") }
var sni by rememberSaveable { mutableStateOf(initialConfig.sni ?: "") }
var allowInsecure by rememberSaveable { mutableStateOf(initialConfig.insecure == true) }
var fingerPrint by rememberSaveable { mutableStateOf(initialConfig.fingerPrint ?: "") }
var alpn by rememberSaveable { mutableStateOf(initialConfig.alpn ?: "") }
var publicKeyReality by rememberSaveable { mutableStateOf(initialConfig.publicKey ?: "") }
var shortId by rememberSaveable { mutableStateOf(initialConfig.shortId ?: "") }
var spiderX by rememberSaveable { mutableStateOf(initialConfig.spiderX ?: "") }
var mldsa65Verify by rememberSaveable { mutableStateOf(initialConfig.mldsa65Verify ?: "") }
var echConfigList by rememberSaveable { mutableStateOf(initialConfig.echConfigList ?: "") }
var verifyPeerCertByName by rememberSaveable { mutableStateOf(initialConfig.verifyPeerCertByName ?: "") }
var pinnedCA256 by rememberSaveable { mutableStateOf(initialConfig.pinnedCA256 ?: "") }
val isVmess = configType == EConfigType.VMESS
val isVless = configType == EConfigType.VLESS
val isShadowsocks = configType == EConfigType.SHADOWSOCKS
val isSocksOrHttp = configType == EConfigType.SOCKS || configType == EConfigType.HTTP
val isTrojan = configType == EConfigType.TROJAN
val isWireguard = configType == EConfigType.WIREGUARD
val isHysteria2 = configType == EConfigType.HYSTERIA2
var showDeleteDialog by rememberSaveable { mutableStateOf(false) }
var isFetchingCert by rememberSaveable { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
fun buildProfileItem(): ProfileItem = initialConfig.copy(
remarks = remarks,
server = address,
serverPort = port,
password = password,
method = when {
isVmess || isShadowsocks -> method
isVless -> encryption
else -> null
},
flow = if (isVless) flow else null,
username = if (isSocksOrHttp) username else null,
secretKey = if (isWireguard) secretKey else null,
publicKey = when {
isWireguard -> publicKey
streamSecurity == REALITY -> publicKeyReality
else -> null
},
preSharedKey = if (isWireguard) preSharedKey else null,
reserved = if (isWireguard) reserved else null,
localAddress = if (isWireguard) localAddress else null,
mtu = if (isWireguard) mtu.toIntOrNull() else null,
obfsPassword = if (isHysteria2) obfsPassword else null,
portHopping = if (isHysteria2) portHopping else null,
portHoppingInterval = if (isHysteria2) portHoppingInterval else null,
bandwidthDown = if (isHysteria2) bandwidthDown else null,
bandwidthUp = if (isHysteria2) bandwidthUp else null,
network = network,
headerType = headerType,
host = host,
path = path,
seed = seed,
quicSecurity = quicSecurity,
quicKey = quicKey,
mode = mode,
serviceName = serviceName,
authority = authority,
xhttpMode = xhttpMode,
xhttpExtra = xhttpExtra.nullIfBlank(),
finalMask = finalMask.nullIfBlank(),
kcpMtu = kcpMtu.toIntOrNull(),
kcpTti = kcpTti.toIntOrNull(),
browserDialerMode = if (network in listOf(NetworkType.WS.type, NetworkType.XHTTP.type)) browserDialerMode.nullIfBlank() else null,
security = streamSecurity,
sni = sni,
insecure = allowInsecure,
fingerPrint = fingerPrint,
alpn = alpn,
shortId = shortId,
spiderX = spiderX,
mldsa65Verify = mldsa65Verify,
echConfigList = echConfigList,
verifyPeerCertByName = verifyPeerCertByName,
pinnedCA256 = pinnedCA256
)
Scaffold(
contentWindowInsets = WindowInsets(0),
topBar = {
AppTopBar(
title = configType.toString(),
onBackClick = onBackClick,
actions = {
if (guid.isNotEmpty() && !isRunning) {
IconButton(onClick = { showDeleteDialog = true }) {
Icon(painterResource(R.drawable.ic_delete_24dp), stringResource(R.string.acc_delete))
}
}
IconButton(onClick = {
onSave(buildProfileItem())
}) {
Icon(painterResource(R.drawable.ic_fab_check), stringResource(R.string.acc_save))
}
}
)
}
) { innerPadding ->
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding()
.verticalScrollbar(listState),
contentPadding = PaddingValues(bottom = 36.dp + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
item { FormTextField(stringResource(R.string.server_lab_remarks), remarks, { remarks = it }) }
item { FormTextField(stringResource(R.string.server_lab_address), address, { address = it }) }
if (configType != EConfigType.HYSTERIA2) item { FormTextField(stringResource(R.string.server_lab_port), port, { port = it }, keyboardType = KeyboardType.Number) }
when {
isVmess || isTrojan || isShadowsocks || isHysteria2 -> item {
FormTextField(
stringResource(
when {
isTrojan || isShadowsocks || isHysteria2 -> R.string.server_lab_id3
else -> R.string.server_lab_id
}
), password, { password = it })
}
isVless -> {
item { FormTextField(stringResource(R.string.server_lab_id), password, { password = it }) }
item { FormTextField(stringResource(R.string.server_lab_encryption), encryption, { encryption = it }) }
item { FormDropdownField(stringResource(R.string.server_lab_flow), flow, flowOptions, { flow = it }) }
}
isSocksOrHttp -> {
item { FormTextField(stringResource(R.string.server_lab_security4), username, { username = it }) }
item { FormTextField(stringResource(R.string.server_lab_id4), password, { password = it }) }
}
}
if (isVmess) item { FormDropdownField(stringResource(R.string.server_lab_security), method, securityOptions, { method = it }) }
if (isShadowsocks) item { FormDropdownField(stringResource(R.string.server_lab_security), method, ssSecurityOptions, { method = it }) }
if (isWireguard) {
item { FormTextField(stringResource(R.string.server_lab_secret_key), secretKey, { secretKey = it }) }
item { FormTextField(stringResource(R.string.server_lab_public_key), publicKey, { publicKey = it }) }
item { FormTextField(stringResource(R.string.server_lab_preshared_key), preSharedKey, { preSharedKey = it }) }
item { FormTextField(stringResource(R.string.server_lab_reserved), reserved, { reserved = it }) }
item { FormTextField(stringResource(R.string.server_lab_local_address), localAddress, { localAddress = it }) }
item { FormTextField(stringResource(R.string.server_lab_local_mtu), mtu, { mtu = it }, keyboardType = KeyboardType.Number) }
}
if (isHysteria2) {
item { FormTextField(stringResource(R.string.server_obfs_password), obfsPassword, { obfsPassword = it }) }
item { FormTextField(stringResource(R.string.server_lab_port_hop), portHopping, { portHopping = it }) }
item { FormTextField(stringResource(R.string.server_lab_port_hop_interval), portHoppingInterval, { portHoppingInterval = it }) }
item { FormTextField(stringResource(R.string.server_lab_bandwidth_down), bandwidthDown, { bandwidthDown = it }) }
item { FormTextField(stringResource(R.string.server_lab_bandwidth_up), bandwidthUp, { bandwidthUp = it }) }
}
item { FormDropdownField(stringResource(R.string.server_lab_network), network, networkOptions, { network = it }) }
val headerOptions = when (network) {
NetworkType.TCP.type -> tcpHeaderOptions
NetworkType.KCP.type -> kcpHeaderOptions
NetworkType.GRPC.type -> grpcModeOptions
NetworkType.XHTTP.type -> xhttpModeOptions
else -> listOf("---")
}
if (headerOptions.size > 1) {
item {
FormDropdownField(
stringResource(
when (network) {
NetworkType.GRPC.type -> R.string.server_lab_mode_type; NetworkType.XHTTP.type -> R.string.server_lab_xhttp_mode; else -> R.string.server_lab_head_type
}
),
headerType, headerOptions, { headerType = it }
)
}
}
item {
FormTextField(
stringResource(
when (network) {
NetworkType.TCP.type, NetworkType.HTTP_UPGRADE.type, NetworkType.XHTTP.type, NetworkType.H2.type -> R.string.server_lab_request_host_http
NetworkType.WS.type -> R.string.server_lab_request_host_ws
NetworkType.GRPC.type -> R.string.server_lab_request_host_grpc
else -> R.string.server_lab_request_host6
}
), host, { host = it })
}
item {
FormTextField(
stringResource(
when (network) {
NetworkType.KCP.type -> R.string.server_lab_path_kcp
NetworkType.WS.type -> R.string.server_lab_path_ws
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_path_httpupgrade
NetworkType.XHTTP.type -> R.string.server_lab_path_xhttp
NetworkType.H2.type -> R.string.server_lab_path_h2
NetworkType.GRPC.type -> R.string.server_lab_path_grpc
else -> R.string.server_lab_path
}
), path, { path = it })
}
if (network == NetworkType.XHTTP.type) {
item { FormTextField(stringResource(R.string.server_lab_xhttp_extra), xhttpExtra, { xhttpExtra = it }) }
}
if (network == NetworkType.KCP.type) {
item { FormTextField(stringResource(R.string.server_lab_kcp_mtu), kcpMtu, { kcpMtu = it }, keyboardType = KeyboardType.Number) }
item { FormTextField(stringResource(R.string.server_lab_kcp_tti), kcpTti, { kcpTti = it }, keyboardType = KeyboardType.Number) }
}
item { FormTextField(stringResource(R.string.server_lab_final_mask), finalMask, { finalMask = it }) }
if (network == NetworkType.WS.type || network == NetworkType.XHTTP.type) {
item { FormDropdownField(stringResource(R.string.server_lab_browser_dialer), browserDialerMode, browserDialerOptions, { browserDialerMode = it }) }
}
item { FormDropdownField(stringResource(R.string.server_lab_stream_security), streamSecurity, streamSecurityOptions, { streamSecurity = it }) }
if (streamSecurity.isNotBlank()) {
item { FormTextField(stringResource(R.string.server_lab_sni), sni, { sni = it }) }
item { FormDropdownField(stringResource(R.string.server_lab_stream_fingerprint), fingerPrint, uTlsOptions, { fingerPrint = it }) }
if (streamSecurity == TLS) {
item { SettingsSwitchItem(title = stringResource(R.string.server_lab_allow_insecure), checked = allowInsecure, onCheckedChange = { allowInsecure = it }) }
item { FormDropdownField(stringResource(R.string.server_lab_stream_alpn), alpn, alpnOptions, { alpn = it }) }
item { FormTextField(stringResource(R.string.server_lab_ech_config_list), echConfigList, { echConfigList = it }) }
item { FormTextField(stringResource(R.string.server_lab_verify_peer_cert_by_name), verifyPeerCertByName, { verifyPeerCertByName = it }) }
item { FormTextField(stringResource(R.string.server_lab_pinned_ca256), pinnedCA256, { pinnedCA256 = it }) }
item {
Button(
onClick = {
if (address.isBlank()) {
context.toast(R.string.server_lab_address); return@Button
}
if (configType != EConfigType.HYSTERIA2 && (port.toIntOrNull() ?: 0) <= 0) {
context.toast(R.string.server_lab_port); return@Button
}
val temp = buildProfileItem()
scope.launch {
isFetchingCert = true
try {
val sha256 = withContext(Dispatchers.IO) { CertificateFingerprintManager.fetchForManualFill(temp) }
if (sha256.isNullOrBlank()) context.toast(R.string.toast_fetch_cert_sha256_failed) else {
pinnedCA256 = sha256
context.toastSuccess(R.string.toast_fetch_cert_sha256_success)
}
} finally {
isFetchingCert = false
}
}
},
enabled = !isFetchingCert,
modifier = Modifier.padding(start = 16.dp)
) { Text(stringResource(R.string.pinned_ca256_action_fetch)) }
}
} else if (streamSecurity == REALITY) {
item { FormTextField(stringResource(R.string.server_lab_public_key), publicKeyReality, { publicKeyReality = it }) }
item { FormTextField(stringResource(R.string.server_lab_short_id), shortId, { shortId = it }) }
item { FormTextField(stringResource(R.string.server_lab_spider_x), spiderX, { spiderX = it }) }
item { FormTextField(stringResource(R.string.server_lab_mldsa65_verify), mldsa65Verify, { mldsa65Verify = it }) }
}
}
}
}
if (showDeleteDialog) {
DeleteConfirmDialog(
message = stringResource(R.string.confirm_delete_profile),
itemName = initialConfig.remarks,
onConfirm = { showDeleteDialog = false; onDelete() },
onDismiss = { showDeleteDialog = false }
)
}
}