@@ -8,6 +8,8 @@ import kotlinx.coroutines.Dispatchers
88import kotlinx.coroutines.async
99import kotlinx.coroutines.awaitAll
1010import kotlinx.coroutines.launch
11+ import kotlinx.coroutines.sync.Semaphore
12+ import kotlinx.coroutines.sync.withPermit
1113import kotlinx.coroutines.withContext
1214import top.uwu.mikubox.R
1315import top.uwu.mikubox.core.MihomoCore
@@ -28,6 +30,7 @@ class ProxiesActivity : EdgeToEdgeActivity() {
2830 private var allProxies: Map <String , MihomoCore .Proxy > = emptyMap()
2931 private var groups: List <MihomoCore .Proxy > = emptyList()
3032 private val delayCache = mutableMapOf<String , Int >()
33+ private var sortByDelay = false
3134
3235 override fun onCreate (savedInstanceState : Bundle ? ) {
3336 super .onCreate(savedInstanceState)
@@ -37,11 +40,18 @@ class ProxiesActivity : EdgeToEdgeActivity() {
3740
3841 binding.toolbar.setNavigationOnClickListener { finish() }
3942 binding.toolbar.setOnMenuItemClickListener { item ->
40- if (item.itemId == R .id.action_test_delay) {
41- testCurrentGroup()
42- true
43- } else {
44- false
43+ when (item.itemId) {
44+ R .id.action_test_delay -> {
45+ testCurrentGroup()
46+ true
47+ }
48+ R .id.action_sort -> {
49+ sortByDelay = ! sortByDelay
50+ item.setTitle(if (sortByDelay) R .string.sort_by_name else R .string.proxies_sort_delay)
51+ renderGroup(binding.groupTab.selectedTabPosition)
52+ true
53+ }
54+ else -> false
4555 }
4656 }
4757
@@ -94,7 +104,13 @@ class ProxiesActivity : EdgeToEdgeActivity() {
94104 selected = memberName == group.now,
95105 )
96106 }
97- adapter.submit(nodes)
107+ // Reachable nodes first (ascending latency); untested/testing/timeout sink to the bottom.
108+ val ordered = if (sortByDelay) {
109+ nodes.sortedBy { if (it.delay >= 0 ) it.delay else Int .MAX_VALUE }
110+ } else {
111+ nodes
112+ }
113+ adapter.submit(ordered)
98114 }
99115
100116 private fun selectNode (nodeName : String ) {
@@ -118,16 +134,24 @@ class ProxiesActivity : EdgeToEdgeActivity() {
118134 val group = groups.getOrNull(index) ? : return
119135 val members = group.all
120136 if (members.isEmpty()) return
121- toast(getString(R .string.proxies_testing))
137+ val testUrl = MihomoCoreSettings .testUrl(this )
138+ val timeout = MihomoCoreSettings .testTimeout(this )
139+ // Show a per-node testing state, then stream results in as each probe returns.
140+ members.forEach { delayCache[it] = - 3 }
141+ renderGroup(index)
122142 lifecycleScope.launch {
123- withContext(Dispatchers .IO ) {
124- val testUrl = MihomoCoreSettings .testUrl(this @ProxiesActivity)
125- val timeout = MihomoCoreSettings .testTimeout(this @ProxiesActivity)
126- members.map { name -> async { name to MihomoCore .delay(name, testUrl, timeout) } }
127- .awaitAll()
128- .forEach { (name, result) -> delayCache[name] = if (result < 0 ) - 1 else result }
129- }
130- renderGroup(index)
143+ val gate = Semaphore (16 )
144+ members.map { name ->
145+ async(Dispatchers .IO ) {
146+ gate.withPermit {
147+ val result = MihomoCore .delay(name, testUrl, timeout)
148+ val value = if (result < 0 ) - 1 else result
149+ delayCache[name] = value
150+ withContext(Dispatchers .Main ) { adapter.updateDelay(name, value) }
151+ }
152+ }
153+ }.awaitAll()
154+ if (sortByDelay) renderGroup(index)
131155 }
132156 }
133157
0 commit comments