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:
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)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user