3 Commits

Author SHA1 Message Date
CherretGit
bd1bdf8298 Merge remote-tracking branch 'origin/main' 2025-10-18 01:56:19 +07:00
CherretGit
adc98db3f1 add info in strategy selection 2025-10-18 01:56:11 +07:00
CherretGit
9bbc3b771f filter list/ipset/strategy files to only .txt 2025-10-17 15:37:59 +07:00
5 changed files with 79 additions and 22 deletions

View File

@@ -3,5 +3,6 @@ package com.cherret.zaprett.data
data class StrategyCheckResult ( data class StrategyCheckResult (
val path : String, val path : String,
val progress : Float, val progress : Float,
val status : Int var domains: List<String>,
val status : StrategyTestingStatus,
) )

View File

@@ -0,0 +1,7 @@
package com.cherret.zaprett.data
import com.cherret.zaprett.R
enum class StrategyTestingStatus(val resId: Int) {
Waiting(R.string.strategy_status_waiting), Testing(R.string.strategy_status_testing), Completed(R.string.strategy_status_tested)
}

View File

@@ -2,17 +2,24 @@ package com.cherret.zaprett.ui.component
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.InstallMobile import androidx.compose.material.icons.filled.InstallMobile
import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Update
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
@@ -26,8 +33,11 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -36,6 +46,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.cherret.zaprett.R import com.cherret.zaprett.R
import com.cherret.zaprett.data.StrategyCheckResult import com.cherret.zaprett.data.StrategyCheckResult
import com.cherret.zaprett.data.StrategyTestingStatus
import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel
import com.cherret.zaprett.utils.RepoItemInfo import com.cherret.zaprett.utils.RepoItemInfo
import com.cherret.zaprett.utils.disableStrategy import com.cherret.zaprett.utils.disableStrategy
@@ -184,9 +195,15 @@ fun RepoItem(
@Composable @Composable
fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferences, context : Context, snackbarHostState : SnackbarHostState) { fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferences, context : Context, snackbarHostState : SnackbarHostState) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var expanded by remember { mutableStateOf(false) }
ElevatedCard ( ElevatedCard (
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp), elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
onClick = {
if (strategy.status == StrategyTestingStatus.Completed) {
expanded = !expanded
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, top = 25.dp, bottom = 0.dp) .padding(start = 10.dp, end = 10.dp, top = 25.dp, bottom = 0.dp)
@@ -213,7 +230,7 @@ fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferen
) )
} }
}, },
enabled = strategy.status == R.string.strategy_status_tested enabled = strategy.status == StrategyTestingStatus.Completed
) { ) {
Icon( Icon(
imageVector = Icons.Default.Check, imageVector = Icons.Default.Check,
@@ -223,7 +240,7 @@ fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferen
} }
Row { Row {
Text( Text(
text = stringResource(strategy.status), text = stringResource(strategy.status.resId),
modifier = Modifier modifier = Modifier
.weight(1f), .weight(1f),
fontSize = 12.sp, fontSize = 12.sp,
@@ -255,5 +272,34 @@ fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferen
) )
} }
} }
AnimatedVisibility(
visible = expanded,
enter = expandVertically(),
exit = shrinkVertically()
) {
Card (
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
modifier = Modifier
.padding(horizontal = 8.dp)
.fillMaxWidth()
) {
LazyColumn(modifier = Modifier.heightIn(max = 300.dp)) {
items(strategy.domains) { item ->
Card(
elevation = CardDefaults.cardElevation(4.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
) {
Text(
text = item,
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.bodyLarge
)
}
}
}
}
}
} }
} }

View File

@@ -12,6 +12,7 @@ import com.cherret.zaprett.R
import com.cherret.zaprett.byedpi.ByeDpiVpnService import com.cherret.zaprett.byedpi.ByeDpiVpnService
import com.cherret.zaprett.data.ServiceStatus import com.cherret.zaprett.data.ServiceStatus
import com.cherret.zaprett.data.StrategyCheckResult import com.cherret.zaprett.data.StrategyCheckResult
import com.cherret.zaprett.data.StrategyTestingStatus
import com.cherret.zaprett.utils.disableStrategy import com.cherret.zaprett.utils.disableStrategy
import com.cherret.zaprett.utils.enableStrategy import com.cherret.zaprett.utils.enableStrategy
import com.cherret.zaprett.utils.getActiveLists import com.cherret.zaprett.utils.getActiveLists
@@ -26,6 +27,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
@@ -57,8 +59,9 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
strategyList.forEach { name -> strategyList.forEach { name ->
strategyStates += StrategyCheckResult( strategyStates += StrategyCheckResult(
path = name, path = name,
status = R.string.strategy_status_waiting, status = StrategyTestingStatus.Waiting,
progress = 0f progress = 0f,
domains = emptyList()
) )
} }
} }
@@ -76,13 +79,13 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
} }
} }
suspend fun countReachable(urls: List<String>): Float = coroutineScope { suspend fun countReachable(index: Int, urls: List<String>): Float = coroutineScope {
if (urls.isEmpty()) return@coroutineScope 0f if (urls.isEmpty()) return@coroutineScope 0f
val results: List<Boolean> = urls.map { url -> val results: List<String> = urls.map { url ->
async { testDomain(url) } async { if (testDomain(url)) url else null }
}.awaitAll() }.awaitAll().filterNotNull()
val successCount = results.count { it } strategyStates[index].domains = results
(successCount.toFloat() / urls.size.toFloat()).coerceIn(0f, 1f) (results.size.toFloat() / urls.size.toFloat()).coerceIn(0f, 1f)
} }
suspend fun readActiveListsLines(): List<String> = withContext(Dispatchers.IO) { suspend fun readActiveListsLines(): List<String> = withContext(Dispatchers.IO) {
@@ -104,17 +107,17 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
val targets = readActiveListsLines() val targets = readActiveListsLines()
for (index in strategyStates.indices) { for (index in strategyStates.indices) {
val current = strategyStates[index] val current = strategyStates[index]
strategyStates[index] = current.copy(status = R.string.strategy_status_testing) strategyStates[index] = current.copy(status = StrategyTestingStatus.Testing)
enableStrategy(current.path, prefs) enableStrategy(current.path, prefs)
if (prefs.getBoolean("use_module", false)) { if (prefs.getBoolean("use_module", false)) {
getStatus { if (it) stopService {} } getStatus { if (it) stopService {} }
startService {} startService {}
try { try {
val progress = countReachable(targets) val progress = countReachable(index, targets)
val old = strategyStates[index] val old = strategyStates[index]
strategyStates[index] = old.copy( strategyStates[index] = old.copy(
progress = progress, progress = progress,
status = R.string.strategy_status_tested status = StrategyTestingStatus.Completed
) )
} finally { } finally {
stopService {} stopService {}
@@ -140,12 +143,12 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
} ?: false } ?: false
if (connected) delay(150L) if (connected) delay(150L)
try { try {
val progress = countReachable(targets) val progress = countReachable(index,targets)
val old = strategyStates[index] val old = strategyStates[index]
strategyStates[index] = old.copy( strategyStates[index] = old.copy(
progress = progress, progress = progress,
status = R.string.strategy_status_tested status = StrategyTestingStatus.Completed
) )
} finally { } finally {
context.startService(Intent(context, ByeDpiVpnService::class.java).apply { context.startService(Intent(context, ByeDpiVpnService::class.java).apply {

View File

@@ -98,7 +98,7 @@ fun getZaprettPath(): String {
fun getAllLists(): Array<String> { fun getAllLists(): Array<String> {
val listsDir = File("${getZaprettPath()}/lists/include") val listsDir = File("${getZaprettPath()}/lists/include")
return listsDir.listFiles { file -> file.isFile } return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath } ?.map { it.absolutePath }
?.toTypedArray() ?.toTypedArray()
?: emptyArray() ?: emptyArray()
@@ -106,7 +106,7 @@ fun getAllLists(): Array<String> {
fun getAllIpsets(): Array<String> { fun getAllIpsets(): Array<String> {
val listsDir = File("${getZaprettPath()}/ipset/include") val listsDir = File("${getZaprettPath()}/ipset/include")
return listsDir.listFiles { file -> file.isFile } return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath } ?.map { it.absolutePath }
?.toTypedArray() ?.toTypedArray()
?: emptyArray() ?: emptyArray()
@@ -114,7 +114,7 @@ fun getAllIpsets(): Array<String> {
fun getAllExcludeLists(): Array<String> { fun getAllExcludeLists(): Array<String> {
val listsDir = File("${getZaprettPath()}/lists/exclude/") val listsDir = File("${getZaprettPath()}/lists/exclude/")
return listsDir.listFiles { file -> file.isFile } return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath } ?.map { it.absolutePath }
?.toTypedArray() ?.toTypedArray()
?: emptyArray() ?: emptyArray()
@@ -122,7 +122,7 @@ fun getAllExcludeLists(): Array<String> {
fun getAllExcludeIpsets(): Array<String> { fun getAllExcludeIpsets(): Array<String> {
val listsDir = File("${getZaprettPath()}/ipset/exclude/") val listsDir = File("${getZaprettPath()}/ipset/exclude/")
return listsDir.listFiles { file -> file.isFile } return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath } ?.map { it.absolutePath }
?.toTypedArray() ?.toTypedArray()
?: emptyArray() ?: emptyArray()
@@ -130,7 +130,7 @@ fun getAllExcludeIpsets(): Array<String> {
fun getAllNfqwsStrategies(): Array<String> { fun getAllNfqwsStrategies(): Array<String> {
val listsDir = File("${getZaprettPath()}/strategies/nfqws") val listsDir = File("${getZaprettPath()}/strategies/nfqws")
return listsDir.listFiles { file -> file.isFile } return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath } ?.map { it.absolutePath }
?.toTypedArray() ?.toTypedArray()
?: emptyArray() ?: emptyArray()
@@ -138,7 +138,7 @@ fun getAllNfqwsStrategies(): Array<String> {
fun getAllByeDPIStrategies(): Array<String> { fun getAllByeDPIStrategies(): Array<String> {
val listsDir = File("${getZaprettPath()}/strategies/byedpi") val listsDir = File("${getZaprettPath()}/strategies/byedpi")
return listsDir.listFiles { file -> file.isFile } return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath } ?.map { it.absolutePath }
?.toTypedArray() ?.toTypedArray()
?: emptyArray() ?: emptyArray()