mirror of
https://github.com/wgtunnel/android.git
synced 2026-06-02 00:29:08 +02:00
feat: support for filtering by endpoint latency (#1155)
This commit is contained in:
+2
-1
@@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@@ -17,7 +18,7 @@ import androidx.compose.ui.unit.dp
|
||||
@Composable
|
||||
fun ExpandingRowListItem(
|
||||
leading: @Composable () -> Unit,
|
||||
text: String,
|
||||
text: AnnotatedString,
|
||||
trailing: @Composable () -> Unit,
|
||||
isSelected: Boolean,
|
||||
expanded: @Composable () -> Unit,
|
||||
|
||||
+10
@@ -218,6 +218,16 @@ fun currentRouteAsNavbarState(
|
||||
topTitle = context.getString(R.string.sort),
|
||||
topTrailing = {
|
||||
Row {
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.SortByLatency)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.NetworkCheck,
|
||||
stringResource(R.string.sort_by_latency),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
sharedViewModel.postSideEffect(LocalSideEffect.Sort)
|
||||
|
||||
+32
-1
@@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -24,12 +25,18 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalIsAndroidTV
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.ExpandingRowListItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.AlertRed
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isSortedBy
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.SharedAppViewModel
|
||||
import org.koin.compose.viewmodel.koinActivityViewModel
|
||||
@@ -46,6 +53,7 @@ fun SortScreen(sharedViewModel: SharedAppViewModel = koinActivityViewModel()) {
|
||||
|
||||
var sortAscending by rememberSaveable { mutableStateOf<Boolean?>(null) }
|
||||
var editableTunnels by rememberSaveable { mutableStateOf(tunnelsUiState.tunnels) }
|
||||
var latencies by rememberSaveable { mutableStateOf<Map<Int, Double>>(emptyMap()) }
|
||||
|
||||
sharedViewModel.collectSideEffect { sideEffect ->
|
||||
when (sideEffect) {
|
||||
@@ -66,6 +74,13 @@ fun SortScreen(sharedViewModel: SharedAppViewModel = koinActivityViewModel()) {
|
||||
null -> tunnelsUiState.tunnels
|
||||
}
|
||||
}
|
||||
LocalSideEffect.SortByLatency -> {
|
||||
sharedViewModel.sortByLatency(editableTunnels)
|
||||
}
|
||||
is LocalSideEffect.LatencySortFinished -> {
|
||||
editableTunnels = sideEffect.tunnels
|
||||
latencies = sideEffect.latencies
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
@@ -97,9 +112,25 @@ fun SortScreen(sharedViewModel: SharedAppViewModel = koinActivityViewModel()) {
|
||||
) {
|
||||
itemsIndexed(editableTunnels, key = { _, tunnel -> tunnel.id }) { index, tunnel ->
|
||||
ReorderableItem(reorderableLazyListState, tunnel.id) { isDragging ->
|
||||
val latency = latencies[tunnel.id]
|
||||
val text = buildAnnotatedString {
|
||||
append(tunnel.name)
|
||||
if (latency != null && latency != Double.MAX_VALUE) {
|
||||
append(" - ")
|
||||
val color =
|
||||
when (latency) {
|
||||
in 0.0..50.0 -> SilverTree
|
||||
in 50.0..150.0 -> Straw
|
||||
else -> AlertRed
|
||||
}
|
||||
withStyle(style = SpanStyle(color = color)) {
|
||||
append("${latency.toInt()}ms")
|
||||
}
|
||||
}
|
||||
}
|
||||
ExpandingRowListItem(
|
||||
leading = {},
|
||||
text = tunnel.name,
|
||||
text = text,
|
||||
trailing = {
|
||||
if (!isTv)
|
||||
Icon(Icons.Default.DragHandle, stringResource(R.string.drag_handle))
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.sideeffect
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConfig
|
||||
|
||||
sealed class LocalSideEffect {
|
||||
data object Sort : LocalSideEffect()
|
||||
data object SortByLatency : LocalSideEffect()
|
||||
data class LatencySortFinished(
|
||||
val tunnels: List<TunnelConfig>,
|
||||
val latencies: Map<Int, Double>,
|
||||
) : LocalSideEffect()
|
||||
|
||||
data object SaveChanges : LocalSideEffect()
|
||||
|
||||
|
||||
@@ -29,12 +29,16 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.QuickConfig
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelName
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asStringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.saveTunnelsUniquely
|
||||
import com.zaneschepke.wireguardautotunnel.util.network.NetworkUtils
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.prepareGet
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.time.Instant
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -42,6 +46,7 @@ import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.amnezia.awg.config.BadConfigException
|
||||
import org.orbitmvi.orbit.ContainerHost
|
||||
import org.orbitmvi.orbit.viewmodel.container
|
||||
@@ -60,6 +65,7 @@ class SharedAppViewModel(
|
||||
private val rootShellUtils: RootShellUtils,
|
||||
private val httpClient: HttpClient,
|
||||
private val fileUtils: FileUtils,
|
||||
private val networkUtils: NetworkUtils,
|
||||
) : ContainerHost<GlobalAppUiState, LocalSideEffect>, ViewModel() {
|
||||
|
||||
val globalSideEffect = globalEffectRepository.flow
|
||||
@@ -235,6 +241,45 @@ class SharedAppViewModel(
|
||||
postSideEffect(GlobalSideEffect.PopBackStack)
|
||||
}
|
||||
|
||||
fun sortByLatency(tunnels: List<TunnelConfig>) = intent {
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Snackbar(StringValue.StringResource(R.string.pinging_servers))
|
||||
)
|
||||
val sortedResult =
|
||||
withContext(Dispatchers.IO) {
|
||||
tunnels
|
||||
.map { tunnel ->
|
||||
async {
|
||||
val config =
|
||||
try {
|
||||
tunnel.toAmConfig()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
val endpoint =
|
||||
config?.peers?.firstOrNull()?.endpoint?.orElse(null)?.host
|
||||
if (endpoint != null) {
|
||||
val latency =
|
||||
try {
|
||||
val stats = networkUtils.pingWithStats(endpoint, 3)
|
||||
if (stats.isReachable) stats.rttAvg else Double.MAX_VALUE
|
||||
} catch (_: Exception) {
|
||||
Double.MAX_VALUE
|
||||
}
|
||||
tunnel to latency
|
||||
} else {
|
||||
tunnel to Double.MAX_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.sortedBy { it.second }
|
||||
}
|
||||
val sortedTunnels = sortedResult.map { it.first }
|
||||
val latencies = sortedResult.associate { it.first.id to it.second }
|
||||
postSideEffect(LocalSideEffect.LatencySortFinished(sortedTunnels, latencies))
|
||||
}
|
||||
|
||||
fun importTunnelConfigs(configs: Map<QuickConfig, TunnelName>) = intent {
|
||||
try {
|
||||
val tunnelConfigs =
|
||||
|
||||
@@ -242,6 +242,8 @@
|
||||
<string name="release_notes">Release notes</string>
|
||||
<string name="shizuku_not_detected">Shizuku not detected</string>
|
||||
<string name="sort">Sort</string>
|
||||
<string name="sort_by_latency">Sort by latency</string>
|
||||
<string name="pinging_servers">Pinging servers…</string>
|
||||
<string name="drag_handle">Drag Handle</string>
|
||||
<string name="move_up">Move Up</string>
|
||||
<string name="move_down">Move Down</string>
|
||||
|
||||
Reference in New Issue
Block a user