diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/LocateTarget.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/LocateTarget.kt index ec3359a9..97e50312 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/LocateTarget.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/LocateTarget.kt @@ -2,6 +2,5 @@ package com.v2ray.ang.dto data class LocateTarget( val groupId: String, - val groupIndex: Int, - val itemPosition: Int -) \ No newline at end of file + val serverGuid: String, +) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt index 8df9ece8..6511221b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainContract.kt @@ -66,5 +66,5 @@ sealed interface MainAction { data class ImportBatchConfig(val configText: String) : MainAction - data class LocateHandled(val target: LocateTarget) : MainAction + data object LocateHandled : MainAction } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt index d7c9678e..e979cbd6 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt @@ -30,7 +30,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.v2ray.ang.dto.entities.ProfileItem import com.v2ray.ang.ui.compose.LocalDarkTheme import com.v2ray.ang.ui.compose.QRCodeDialog -import com.v2ray.ang.extension.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -73,16 +72,12 @@ fun MainScreen( val lazyListStates = remember { mutableStateMapOf() } val lazyGridStates = remember { mutableStateMapOf() } - var locateInProgress by remember { mutableStateOf(false) } - LaunchedEffect(groups) { val validGroupIds = groups.map { it.id }.toSet() lazyListStates.keys.retainAll(validGroupIds) lazyGridStates.keys.retainAll(validGroupIds) } - val latestDoubleColumnDisplay by rememberUpdatedState(doubleColumnDisplay) - LaunchedEffect(groups, uiState.selectedGroupId) { if (groups.isEmpty()) return@LaunchedEffect val selectedIndex = groups.indexOfFirst { it.id == uiState.selectedGroupId } @@ -93,68 +88,18 @@ fun MainScreen( } val latestGroups by rememberUpdatedState(groups) - val latestLocateInProgress by rememberUpdatedState(locateInProgress) LaunchedEffect(pagerState) { snapshotFlow { pagerState.settledPage } .distinctUntilChanged() .collect { page -> val currentGroups = latestGroups - if (!latestLocateInProgress && page in currentGroups.indices) { + if (page in currentGroups.indices) { onAction(MainAction.SelectGroup(currentGroups[page].id)) } } } - LaunchedEffect(uiState.locateTarget) { - val target = uiState.locateTarget ?: return@LaunchedEffect - if (target.groupIndex !in 0 until pagerState.pageCount) { - mainViewModel.onAction(MainAction.LocateHandled(target)) - return@LaunchedEffect - } - - locateInProgress = true - try { - if (pagerState.settledPage != target.groupIndex) { - pagerState.navigateToPageOptimized( - targetPage = target.groupIndex, - animateAdjacentPage = false - ) - } - onAction(MainAction.SelectGroup(target.groupId)) - - repeat(10) { - val ready = if (latestDoubleColumnDisplay) { - lazyGridStates[target.groupId] != null - } else { - lazyListStates[target.groupId] != null - } - if (ready) return@repeat - delay(16) - } - - if (latestDoubleColumnDisplay) { - lazyGridStates[target.groupId]?.let { gridState -> - gridState.scrollToItem( - index = target.itemPosition, - scrollOffset = -gridState.layoutInfo.viewportSize.height / 3 - ) - } - } else { - lazyListStates[target.groupId]?.let { listState -> - listState.scrollToItem( - index = target.itemPosition, - scrollOffset = -listState.layoutInfo.viewportSize.height / 3 - ) - } - } - } finally { - delay(32) - locateInProgress = false - mainViewModel.onAction(MainAction.LocateHandled(target)) - } - } - MainDialogs( showDelAllConfirm = showDelAllConfirm, onDismissDelAll = { showDelAllConfirm = false }, @@ -279,6 +224,7 @@ fun MainScreen( groupId = group.id, mainViewModel = mainViewModel, selectedGuid = selectedGuid, + locateTarget = uiState.locateTarget, doubleColumnDisplay = doubleColumnDisplay, confirmRemove = confirmRemove, searchQuery = searchQuery, diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt index de19f430..26a473cb 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt @@ -30,6 +30,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -46,6 +47,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.v2ray.ang.R +import com.v2ray.ang.dto.LocateTarget import com.v2ray.ang.dto.entities.ProfileItem import com.v2ray.ang.dto.entities.ServersCache import com.v2ray.ang.extension.isComplexType @@ -69,6 +71,7 @@ fun GroupPagerPage( groupId: String, mainViewModel: MainViewModel, selectedGuid: String?, + locateTarget: LocateTarget?, doubleColumnDisplay: Boolean, confirmRemove: Boolean, searchQuery: String, @@ -89,6 +92,7 @@ fun GroupPagerPage( ServerListPage( servers = servers, selectedGuid = selectedGuid, + locateTarget = locateTarget?.takeIf { it.groupId == groupId }, canReorder = canReorder, doubleColumnDisplay = doubleColumnDisplay, subscriptionId = groupId, @@ -101,6 +105,7 @@ fun GroupPagerPage( onShareServer = onShareServer, onMoreServer = onMoreServer, onRemoveServer = onRemoveServer, + onLocateHandled = { mainViewModel.onAction(MainAction.LocateHandled) }, onMoveServer = { fromIndex, toIndex -> mainViewModel.moveServer(groupId, fromIndex, toIndex) }, contentPadding = contentPadding ) @@ -110,6 +115,7 @@ fun GroupPagerPage( private fun ServerListPage( servers: List, selectedGuid: String?, + locateTarget: LocateTarget?, canReorder: Boolean, doubleColumnDisplay: Boolean, subscriptionId: String, @@ -122,6 +128,7 @@ private fun ServerListPage( onShareServer: (String, ProfileItem) -> Unit, onMoreServer: (String, ProfileItem) -> Unit, onRemoveServer: (String) -> Unit, + onLocateHandled: () -> Unit, onMoveServer: (Int, Int) -> Unit, contentPadding: PaddingValues ) { @@ -135,6 +142,8 @@ private fun ServerListPage( } } else null + LocateTargetEffect(locateTarget, servers, gridState, onLocateHandled) + LazyVerticalGrid( columns = GridCells.Fixed(2), state = gridState, @@ -182,6 +191,8 @@ private fun ServerListPage( } } else null + LocateTargetEffect(locateTarget, servers, listState, onLocateHandled) + LazyColumn( state = listState, modifier = Modifier @@ -230,6 +241,38 @@ private fun ServerListPage( } } +@Composable +private fun LocateTargetEffect( + target: LocateTarget?, + servers: List, + state: LazyListState, + onHandled: () -> Unit, +) { + if (target == null) return + LaunchedEffect(target, servers) { + val index = servers.indexOfFirst { it.guid == target.serverGuid } + if (index < 0) return@LaunchedEffect + state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3) + onHandled() + } +} + +@Composable +private fun LocateTargetEffect( + target: LocateTarget?, + servers: List, + state: LazyGridState, + onHandled: () -> Unit, +) { + if (target == null) return + LaunchedEffect(target, servers) { + val index = servers.indexOfFirst { it.guid == target.serverGuid } + if (index < 0) return@LaunchedEffect + state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3) + onHandled() + } +} + @Composable private fun ServerItemRow( serverCache: ServersCache, diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt index 0704f781..ac6a3165 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainViewModel.kt @@ -191,7 +191,7 @@ class MainViewModel( is MainAction.RemoveServer -> removeServerAndRefresh(action.guid) is MainAction.Search -> filterConfig(action.query) is MainAction.ImportBatchConfig -> importBatchConfig(action.configText) - is MainAction.LocateHandled -> consumeLocateTarget(action.target) + MainAction.LocateHandled -> consumeLocateTarget() is MainAction.ShareQRCode -> { val bitmap = dataSource.share2QRCode(action.guid) _uiState.update { it.copy(shareQRCodeBitmap = bitmap) } @@ -744,24 +744,21 @@ class MainViewModel( val selected = dataSource.getSelectServer() ?: return val profile = dataSource.decodeServerConfig(selected) ?: return val groupId = profile.subscriptionId - val groupIndex = - _uiState.value.groups.indexOfFirst { it.id == groupId }.takeIf { it >= 0 } ?: return + if (_uiState.value.groups.none { it.id == groupId }) return viewModelScope.launch(ioDispatcher) { - val position = - loadGroup(groupId).indexOfFirst { it.guid == selected }.takeIf { it >= 0 } - ?: return@launch + updateGroupUi(groupId, loadGroup(groupId)) + if (_uiState.value.selectedGroupId != groupId) { + dataSource.setSelectedSubscriptionId(groupId) + } + val target = LocateTarget(groupId, selected) _uiState.update { - it.copy(locateTarget = LocateTarget(groupId, groupIndex, position)) + it.copy(selectedGroupId = groupId, locateTarget = target) } } } - fun getPosition(guid: String): Int = currentServers().indexOfFirst { it.guid == guid } - - private fun consumeLocateTarget(target: LocateTarget) { - _uiState.update { state -> - if (state.locateTarget == target) state.copy(locateTarget = null) else state - } + private fun consumeLocateTarget() { + _uiState.update { it.copy(locateTarget = null) } } // ---------- Running state ----------