Stabilize profile editor IME scrolling (#5980)

Profile editors are bounded forms whose focused child must remain stable while the IME changes the available height. Lazy list item boundaries can be remeasured or detached during these viewport changes, causing visible jitter and leaving lower or multiline fields obscured.

Compose the form eagerly in one vertically scrollable Column. Preserve the existing spacing, scrollbar, IME padding, and bottom padding, and update only protocol-editor call sites to the ColumnScope API.
This commit is contained in:
eliotcougar authored and GitHub committed 2026-08-01 10:16:14 +08:00
1 parent 389e55d72a
commit 257c4bf21b
9 files changed
+53 -62

No files matched your search

@@ -3,14 +3,13 @@ package com.v2ray.ang.ui.server
import android.os.Bundle
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -431,10 +430,10 @@ abstract class BaseServerActivity : BaseComponentActivity() {
protected fun ServerEditorScaffold(
title: String,
onSaveClick: () -> Unit,
content: LazyListScope.() -> Unit
content: @Composable ColumnScope.() -> Unit
) {
var showDeleteDialog by rememberSaveable { mutableStateOf(false) }
val listState = rememberLazyListState()
val scrollState = rememberScrollState()
Scaffold(
contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
topBar = {
@@ -460,15 +459,15 @@ abstract class BaseServerActivity : BaseComponentActivity() {
)
}
) { innerPadding ->
LazyColumn(
state = listState,
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding()
.verticalScrollbar(listState),
contentPadding = PaddingValues(bottom = 36.dp),
.verticalScroll(scrollState)
.verticalScrollbar(scrollState)
.padding(bottom = 36.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
content = content
)
@@ -27,8 +27,8 @@ class ServerHttpActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { HttpProtocolFields(uiState) }
CommonBasicFields(uiState)
HttpProtocolFields(uiState)
}
}
@@ -31,8 +31,8 @@ class ServerHysteria2Activity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState, showPort = false) }
item { Hysteria2ProtocolFields(uiState) }
CommonBasicFields(uiState, showPort = false)
Hysteria2ProtocolFields(uiState)
}
}
@@ -32,17 +32,15 @@ class ServerShadowsocksActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { ShadowsocksProtocolFields(uiState, securityOptions) }
item { CommonNetworkFields(uiState, options) }
item {
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
CommonBasicFields(uiState)
ShadowsocksProtocolFields(uiState, securityOptions)
CommonNetworkFields(uiState, options)
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
}
@@ -27,8 +27,8 @@ class ServerSocksActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { SocksProtocolFields(uiState) }
CommonBasicFields(uiState)
SocksProtocolFields(uiState)
}
}
@@ -30,17 +30,15 @@ class ServerTrojanActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { TrojanProtocolFields(uiState) }
item { CommonNetworkFields(uiState, options) }
item {
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
CommonBasicFields(uiState)
TrojanProtocolFields(uiState)
CommonNetworkFields(uiState, options)
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
}
@@ -33,17 +33,15 @@ class ServerVlessActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { VlessProtocolFields(uiState, flowOptions) }
item { CommonNetworkFields(uiState, options) }
item {
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
CommonBasicFields(uiState)
VlessProtocolFields(uiState, flowOptions)
CommonNetworkFields(uiState, options)
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
}
@@ -33,17 +33,15 @@ class ServerVmessActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { VmessProtocolFields(uiState, securityOptions) }
item { CommonNetworkFields(uiState, options) }
item {
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
CommonBasicFields(uiState)
VmessProtocolFields(uiState, securityOptions)
CommonNetworkFields(uiState, options)
CommonStreamSecurityFields(
state = uiState,
options = options,
scope = scope,
buildProfileItem = { uiState.toProfileItem(initialConfig) }
)
}
}
@@ -28,8 +28,8 @@ class ServerWireguardActivity : BaseServerActivity() {
title = serverConfigType.toString(),
onSaveClick = { saveServer(uiState) }
) {
item { CommonBasicFields(uiState) }
item { WireguardProtocolFields(uiState) }
CommonBasicFields(uiState)
WireguardProtocolFields(uiState)
}
}