mirror of
https://github.com/CherretGit/zaprett-app.git
synced 2025-12-10 05:29:37 +05:00
add automatic strategy selection, remove phantom switch in settings, add ipset repository url setting, made some refactoring, disable analytics and autoupdate in debug releases by default, etc
This commit is contained in:
@@ -29,6 +29,12 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
buildConfigField("boolean", "send_firebase_analytics", "true")
|
||||
buildConfigField("boolean", "auto_update", "true")
|
||||
}
|
||||
debug {
|
||||
buildConfigField("boolean", "send_firebase_analytics", "false")
|
||||
buildConfigField("boolean", "auto_update", "false")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.cherret.zaprett.ui.screen.IpsetsScreen
|
||||
import com.cherret.zaprett.ui.screen.RepoScreen
|
||||
import com.cherret.zaprett.ui.screen.SettingsScreen
|
||||
import com.cherret.zaprett.ui.screen.StrategyScreen
|
||||
import com.cherret.zaprett.ui.screen.StrategySelectionScreen
|
||||
import com.cherret.zaprett.ui.theme.ZaprettTheme
|
||||
import com.cherret.zaprett.ui.viewmodel.HomeViewModel
|
||||
import com.cherret.zaprett.ui.viewmodel.HostRepoViewModel
|
||||
@@ -72,7 +73,7 @@ sealed class Screen(val route: String, @StringRes val nameResId: Int, val icon:
|
||||
object settings : Screen("settings", R.string.title_settings, Icons.Default.Settings)
|
||||
}
|
||||
val topLevelRoutes = listOf(Screen.home, Screen.hosts, Screen.strategies, Screen.ipsets, Screen.settings)
|
||||
val hideNavBar = listOf("repo?source={source}", "debugScreen")
|
||||
val hideNavBar = listOf("repo?source={source}", "debugScreen", "selectionScreen")
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val viewModel: HomeViewModel by viewModels()
|
||||
private lateinit var notificationPermissionLauncher: ActivityResultLauncher<String>
|
||||
@@ -127,7 +128,7 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
}
|
||||
var showWelcomeDialog by remember { mutableStateOf(sharedPreferences.getBoolean("welcome_dialog", true)) }
|
||||
firebaseAnalytics.setAnalyticsCollectionEnabled(sharedPreferences.getBoolean("send_firebase_analytics", true))
|
||||
firebaseAnalytics.setAnalyticsCollectionEnabled(sharedPreferences.getBoolean("send_firebase_analytics", BuildConfig.send_firebase_analytics))
|
||||
BottomBar()
|
||||
if (showStoragePermissionDialog) {
|
||||
PermissionDialog(
|
||||
@@ -239,6 +240,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
composable("debugScreen") { DebugScreen(navController) }
|
||||
composable("selectionScreen") { StrategySelectionScreen(navController) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.cherret.zaprett.MainActivity
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.ServiceStatus
|
||||
import com.cherret.zaprett.utils.disableList
|
||||
import com.cherret.zaprett.utils.getActiveByeDPIStrategyContent
|
||||
import com.cherret.zaprett.utils.getActiveExcludeIpsets
|
||||
import com.cherret.zaprett.utils.getActiveExcludeLists
|
||||
import com.cherret.zaprett.utils.getActiveIpsets
|
||||
@@ -191,7 +192,13 @@ class ByeDpiVpnService : VpnService() {
|
||||
val listSet = if (getHostListMode(sharedPreferences) == "whitelist") getActiveLists(sharedPreferences) else getActiveExcludeLists(sharedPreferences)
|
||||
val ipsetSet = if (getHostListMode(sharedPreferences) == "whitelist") getActiveIpsets(sharedPreferences) else getActiveExcludeIpsets(sharedPreferences)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val args = parseArgs(socksIp, socksPort, getActiveStrategy(sharedPreferences), prepareList(listSet), prepareIpset(ipsetSet), sharedPreferences)
|
||||
val args = parseArgs(
|
||||
socksIp,
|
||||
socksPort,
|
||||
getActiveByeDPIStrategyContent(sharedPreferences),
|
||||
prepareList(listSet),
|
||||
prepareIpset(ipsetSet),
|
||||
sharedPreferences)
|
||||
val result = NativeBridge().jniStartProxy(args)
|
||||
if (result < 0) {
|
||||
Log.d("proxy","Failed to start byedpi proxy")
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.cherret.zaprett.data
|
||||
|
||||
data class StrategyCheckResult (
|
||||
val path : String,
|
||||
val progress : Float,
|
||||
val status : Int
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.cherret.zaprett.ui.component
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -7,26 +9,37 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.InstallMobile
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.StrategyCheckResult
|
||||
import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel
|
||||
import com.cherret.zaprett.utils.RepoItemInfo
|
||||
import com.cherret.zaprett.utils.enableStrategy
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
|
||||
@@ -164,3 +177,80 @@ fun RepoItem(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferences, context : Context, snackbarHostState : SnackbarHostState) {
|
||||
val scope = rememberCoroutineScope()
|
||||
ElevatedCard (
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, top = 25.dp, bottom = 0.dp)
|
||||
) {
|
||||
Column (
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
{
|
||||
Row {
|
||||
Text(
|
||||
text = strategy.path,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
)
|
||||
FilledTonalIconButton(
|
||||
onClick = {
|
||||
enableStrategy(strategy.path, prefs)
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = context.getString(R.string.strategy_applied)
|
||||
)
|
||||
}
|
||||
},
|
||||
enabled = strategy.status == R.string.strategy_status_tested
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = "apply"
|
||||
)
|
||||
}
|
||||
}
|
||||
Row {
|
||||
Text(
|
||||
text = stringResource(strategy.status),
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
}
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
|
||||
progress = {
|
||||
strategy.progress
|
||||
},
|
||||
color = ProgressIndicatorDefaults.linearColor,
|
||||
trackColor = ProgressIndicatorDefaults.linearTrackColor,
|
||||
strokeCap = ProgressIndicatorDefaults.LinearStrokeCap,
|
||||
)
|
||||
Text(
|
||||
text = "${(strategy.progress*100).toInt()}%",
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp),
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,8 +100,8 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
val useModule = viewModel.useModule.collectAsState()
|
||||
val updateOnBoot = remember { mutableStateOf(sharedPreferences.getBoolean("update_on_boot", true)) }
|
||||
val autoRestart = viewModel.autoRestart.collectAsState()
|
||||
val autoUpdate = remember { mutableStateOf(sharedPreferences.getBoolean("auto_update", true)) }
|
||||
val sendFirebaseAnalytics = remember { mutableStateOf(sharedPreferences.getBoolean("send_firebase_analytics", true)) }
|
||||
val autoUpdate = remember { mutableStateOf(sharedPreferences.getBoolean("auto_update", BuildConfig.auto_update)) }
|
||||
val sendFirebaseAnalytics = remember { mutableStateOf(sharedPreferences.getBoolean("send_firebase_analytics", BuildConfig.send_firebase_analytics)) }
|
||||
val ipv6 = remember { mutableStateOf(sharedPreferences.getBoolean("ipv6",false)) }
|
||||
val openNoRootDialog = remember { mutableStateOf(false) }
|
||||
val openNoModuleDialog = remember { mutableStateOf(false) }
|
||||
@@ -117,6 +117,7 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
val showBlackDialog = remember { mutableStateOf(false) }
|
||||
val showAppsListsSheet = remember { mutableStateOf(false) }
|
||||
val showSystemApps = remember { mutableStateOf(sharedPreferences.getBoolean("show_system_apps", false)) }
|
||||
val showChangeProbeTimeout = remember { mutableStateOf(false) }
|
||||
|
||||
val settingsList = listOf(
|
||||
Setting.Section(stringResource(R.string.general_section)),
|
||||
@@ -155,13 +156,6 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
editor.putBoolean("send_firebase_analytics", it).apply()
|
||||
}
|
||||
),
|
||||
Setting.Toggle(
|
||||
title = "",
|
||||
checked = false,
|
||||
onToggle = {
|
||||
|
||||
}
|
||||
),
|
||||
Setting.Action(
|
||||
title = stringResource(R.string.btn_repository_url_lists),
|
||||
onClick = {
|
||||
@@ -183,9 +177,7 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
showStrategyRepoUrlDialog.value = true
|
||||
}
|
||||
),
|
||||
Setting.Section(
|
||||
title = stringResource(R.string.shared_section)
|
||||
),
|
||||
Setting.Section(title = stringResource(R.string.shared_section)),
|
||||
Setting.Action(
|
||||
title = stringResource(R.string.btn_applist),
|
||||
onClick = {
|
||||
@@ -204,6 +196,20 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
showBlackDialog.value = true
|
||||
}
|
||||
),
|
||||
Setting.Section(stringResource(R.string.title_selection)),
|
||||
Setting.Action(
|
||||
title = stringResource(R.string.begin_selection),
|
||||
onClick = {
|
||||
navController.navigate("selectionScreen")
|
||||
}
|
||||
),
|
||||
Setting.Action(
|
||||
title = stringResource(R.string.change_probe_timeout),
|
||||
onClick = {
|
||||
textDialogValue.value = sharedPreferences.getLong("probe_timeout", 1000L).toString()
|
||||
showChangeProbeTimeout.value = true
|
||||
}
|
||||
),
|
||||
Setting.Section(stringResource(R.string.byedpi_section)),
|
||||
Setting.Toggle(
|
||||
title = stringResource(R.string.btn_ipv6),
|
||||
@@ -234,9 +240,7 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
showDNSDialog.value = true
|
||||
}
|
||||
),
|
||||
Setting.Section(
|
||||
title = stringResource(R.string.zapret_section)
|
||||
),
|
||||
Setting.Section(title = stringResource(R.string.zapret_section)),
|
||||
Setting.Toggle(
|
||||
title = stringResource(R.string.btn_autorestart),
|
||||
checked = autoRestart.value,
|
||||
@@ -271,6 +275,11 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
editor.putString("hosts_repo_url", it).apply()
|
||||
}, onDismiss = { showHostsRepoUrlDialog.value = false })
|
||||
}
|
||||
if (showIpsetRepoUrlDialog.value) {
|
||||
TextDialog(stringResource(R.string.btn_repository_url_ipsets), stringResource(R.string.hint_enter_repository_url_ipsets), textDialogValue.value, onConfirm = {
|
||||
editor.putString("ipsets_repo_url", it).apply()
|
||||
}, onDismiss = { showIpsetRepoUrlDialog.value = false })
|
||||
}
|
||||
|
||||
if (showStrategyRepoUrlDialog.value) {
|
||||
TextDialog(stringResource(R.string.btn_repository_url_strategies), stringResource(R.string.hint_enter_repository_url_strategies), textDialogValue.value, onConfirm = {
|
||||
@@ -332,6 +341,12 @@ fun SettingsScreen(navController: NavController, viewModel : SettingsViewModel =
|
||||
)
|
||||
}
|
||||
|
||||
if (showChangeProbeTimeout.value) {
|
||||
TextDialog(stringResource(R.string.probe_timeout), stringResource(R.string.hint_enter_probe_timeout), textDialogValue.value, onConfirm = {
|
||||
editor.putLong("probe_timeout", it.toLong()).apply()
|
||||
}, onDismiss = { showChangeProbeTimeout.value = false })
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.cherret.zaprett.ui.screen
|
||||
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.ui.component.StrategySelectionItem
|
||||
import com.cherret.zaprett.ui.viewmodel.StrategySelectionViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun StrategySelectionScreen(navController: NavController, viewModel : StrategySelectionViewModel = viewModel()){
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val strategyStates = viewModel.strategyStates
|
||||
val context = LocalContext.current
|
||||
val prefs = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
var showDialog = remember { mutableStateOf(false) }
|
||||
|
||||
if (showDialog.value) {
|
||||
InfoAlert { showDialog.value = false }
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.title_selection),
|
||||
fontSize = 40.sp,
|
||||
fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal))
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.btn_back)
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
showDialog.value = true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = "info"
|
||||
)
|
||||
}
|
||||
},
|
||||
windowInsets = WindowInsets(0)
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
content = { paddingValues ->
|
||||
LazyColumn (
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
item {
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
)
|
||||
{
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
viewModel.viewModelScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.begin_selection_snack)
|
||||
)
|
||||
viewModel.performTest()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.begin_selection))
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
strategyStates.isEmpty() -> {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.empty_list),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
items(strategyStates, key = { it.path }) { item ->
|
||||
StrategySelectionItem(item, prefs, context, snackbarHostState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InfoAlert(onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
title = { Text(text = stringResource(R.string.strategy_selection_info_title)) },
|
||||
text = { Text(text = stringResource(R.string.strategy_selection_info_msg)) },
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.cherret.zaprett.BuildConfig
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.data.ServiceStatus
|
||||
@@ -71,7 +72,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var showUpdateDialog = mutableStateOf(false)
|
||||
|
||||
fun checkForUpdate() {
|
||||
if (prefs.getBoolean("auto_update", true)) {
|
||||
if (prefs.getBoolean("auto_update", BuildConfig.auto_update)) {
|
||||
getUpdate(prefs) {
|
||||
if (it != null) {
|
||||
downloadUrl.value = it.downloadUrl.toString()
|
||||
|
||||
@@ -42,7 +42,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
init {
|
||||
refreshApplications()
|
||||
_useModule.value = context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("use_module", false)
|
||||
getStartOnBoot { value ->
|
||||
getStartOnBoot(prefs) { value ->
|
||||
_autoRestart.value = value
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
||||
fun handleAutoRestart(context: Context) {
|
||||
val sharedPreferences = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||
setStartOnBoot{ value ->
|
||||
setStartOnBoot(prefs) { value ->
|
||||
_autoRestart.value = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.cherret.zaprett.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.data.ServiceStatus
|
||||
import com.cherret.zaprett.data.StrategyCheckResult
|
||||
import com.cherret.zaprett.utils.disableStrategy
|
||||
import com.cherret.zaprett.utils.enableStrategy
|
||||
import com.cherret.zaprett.utils.getActiveLists
|
||||
import com.cherret.zaprett.utils.getActiveStrategy
|
||||
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.utils.getAllStrategies
|
||||
import com.cherret.zaprett.utils.getStatus
|
||||
import com.cherret.zaprett.utils.startService
|
||||
import com.cherret.zaprett.utils.stopService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class StrategySelectionViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val prefs = application.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
val client = OkHttpClient.Builder()
|
||||
.callTimeout(prefs.getLong("probe_timeout", 1000L), TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
val context = application
|
||||
|
||||
val strategyStates = mutableStateListOf<StrategyCheckResult>()
|
||||
|
||||
init {
|
||||
loadStrategies()
|
||||
}
|
||||
|
||||
fun loadStrategies() {
|
||||
val strategyList = getAllStrategies(prefs)
|
||||
strategyStates.clear()
|
||||
strategyList.forEach { name ->
|
||||
strategyStates += StrategyCheckResult(
|
||||
path = name,
|
||||
status = R.string.strategy_status_waiting,
|
||||
progress = 0f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun testDomain(domain : String) : Boolean = withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url("https://${domain}")
|
||||
.build()
|
||||
|
||||
try {
|
||||
client.newCall(request).execute().use { response ->
|
||||
response.isSuccessful || (response.code in 300..399)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun countReachable(urls: List<String>): Float = coroutineScope {
|
||||
if (urls.isEmpty()) return@coroutineScope 0f
|
||||
val results: List<Boolean> = urls.map { url ->
|
||||
async { testDomain(url) }
|
||||
}.awaitAll()
|
||||
val successCount = results.count { it }
|
||||
(successCount.toFloat() / urls.size.toFloat()).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
suspend fun readActiveListsLines(): List<String> = withContext(Dispatchers.IO) {
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
getActiveLists(prefs).forEach { path ->
|
||||
runCatching {
|
||||
File(path).useLines { lines ->
|
||||
lines.forEach { line ->
|
||||
result += line
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("Error", "Occured error when creating list for check")
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
suspend fun performTest() {
|
||||
val targets = readActiveListsLines()
|
||||
|
||||
for (index in strategyStates.indices) {
|
||||
val current = strategyStates[index]
|
||||
strategyStates[index] = current.copy(status = R.string.strategy_status_testing)
|
||||
|
||||
enableStrategy(current.path, prefs)
|
||||
|
||||
if (prefs.getBoolean("use_module", false)) {
|
||||
getStatus { if (it) stopService {} }
|
||||
startService {}
|
||||
|
||||
try {
|
||||
val progress = countReachable(targets)
|
||||
|
||||
val old = strategyStates[index]
|
||||
strategyStates[index] = old.copy(
|
||||
progress = progress,
|
||||
status = R.string.strategy_status_tested
|
||||
)
|
||||
} finally {
|
||||
stopService {}
|
||||
disableStrategy(current.path, prefs)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ByeDpiVpnService.status == ServiceStatus.Connected) {
|
||||
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||
action = "STOP_VPN"
|
||||
})
|
||||
delay(300L)
|
||||
}
|
||||
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||
action = "START_VPN"
|
||||
})
|
||||
|
||||
val connected = withTimeoutOrNull(10_000L) {
|
||||
while (ByeDpiVpnService.status != ServiceStatus.Connected) {
|
||||
delay(100L)
|
||||
}
|
||||
true
|
||||
} ?: false
|
||||
|
||||
if (connected) delay(150L)
|
||||
try {
|
||||
val progress = countReachable(targets)
|
||||
|
||||
val old = strategyStates[index]
|
||||
strategyStates[index] = old.copy(
|
||||
progress = progress,
|
||||
status = R.string.strategy_status_tested
|
||||
)
|
||||
} finally {
|
||||
context.startService(Intent(context, ByeDpiVpnService::class.java).apply {
|
||||
action = "STOP_VPN"
|
||||
})
|
||||
delay(200L)
|
||||
disableStrategy(current.path, prefs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val sorted = strategyStates.sortedByDescending { it.progress }
|
||||
strategyStates.clear()
|
||||
strategyStates.addAll(sorted)
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.data.ServiceStatus
|
||||
import com.cherret.zaprett.utils.disableStrategy
|
||||
import com.cherret.zaprett.utils.enableStrategy
|
||||
import com.cherret.zaprett.utils.getActiveByeDPIStrategies
|
||||
import com.cherret.zaprett.utils.getActiveNfqwsStrategies
|
||||
import com.cherret.zaprett.utils.getActiveByeDPIStrategy
|
||||
import com.cherret.zaprett.utils.getActiveNfqwsStrategy
|
||||
import com.cherret.zaprett.utils.getAllByeDPIStrategies
|
||||
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.utils.getStatus
|
||||
@@ -84,10 +84,10 @@ interface StrategyProvider {
|
||||
|
||||
class NfqwsStrategyProvider : StrategyProvider {
|
||||
override fun getAll() = getAllNfqwsStrategies()
|
||||
override fun getActive() = getActiveNfqwsStrategies()
|
||||
override fun getActive() = getActiveNfqwsStrategy()
|
||||
}
|
||||
|
||||
class ByeDPIStrategyProvider(private val sharedPreferences: SharedPreferences) : StrategyProvider {
|
||||
override fun getAll() = getAllByeDPIStrategies()
|
||||
override fun getActive() = getActiveByeDPIStrategies(sharedPreferences)
|
||||
override fun getActive() = getActiveByeDPIStrategy(sharedPreferences)
|
||||
}
|
||||
@@ -64,16 +64,20 @@ fun getConfigFile(): File {
|
||||
return File(Environment.getExternalStorageDirectory(), "zaprett/config")
|
||||
}
|
||||
|
||||
fun setStartOnBoot(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett autostart").submit { result ->
|
||||
if (result.out.isNotEmpty() && result.out.toString().contains("true")) callback(true) else callback(false)
|
||||
fun setStartOnBoot(prefs : SharedPreferences, callback: (Boolean) -> Unit) {
|
||||
if (prefs.getBoolean("use_module", false)) {
|
||||
Shell.cmd("zaprett autostart").submit { result ->
|
||||
if (result.out.isNotEmpty() && result.out.toString().contains("true")) callback(true) else callback(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getStartOnBoot(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett get-autostart").submit { result ->
|
||||
if (result.out.isNotEmpty() && result.out.toString().contains("true")) callback(true) else callback(false)
|
||||
}
|
||||
fun getStartOnBoot(prefs : SharedPreferences, callback: (Boolean) -> Unit) {
|
||||
if (prefs.getBoolean("use_module", false)) {
|
||||
Shell.cmd("zaprett get-autostart").submit { result ->
|
||||
if (result.out.isNotEmpty() && result.out.toString().contains("true")) callback(true) else callback(false)
|
||||
}
|
||||
} else { callback(false) }
|
||||
}
|
||||
|
||||
fun getZaprettPath(): String {
|
||||
@@ -140,6 +144,11 @@ fun getAllByeDPIStrategies(): Array<String> {
|
||||
?: emptyArray()
|
||||
}
|
||||
|
||||
fun getAllStrategies(sharedPreferences : SharedPreferences) : Array<String> {
|
||||
return if (sharedPreferences.getBoolean("use_module", false)) getAllNfqwsStrategies()
|
||||
else getAllByeDPIStrategies()
|
||||
}
|
||||
|
||||
|
||||
fun getActiveLists(sharedPreferences: SharedPreferences): Array<String> {
|
||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||
@@ -230,7 +239,7 @@ fun getActiveExcludeIpsets(sharedPreferences: SharedPreferences): Array<String>
|
||||
else return emptyArray()
|
||||
}
|
||||
|
||||
fun getActiveNfqwsStrategies(): Array<String> {
|
||||
fun getActiveNfqwsStrategy(): Array<String> {
|
||||
val configFile = File("${getZaprettPath()}/config")
|
||||
if (configFile.exists()) {
|
||||
val props = Properties()
|
||||
@@ -248,7 +257,7 @@ fun getActiveNfqwsStrategies(): Array<String> {
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
fun getActiveByeDPIStrategies(sharedPreferences: SharedPreferences): Array<String> {
|
||||
fun getActiveByeDPIStrategy(sharedPreferences: SharedPreferences): Array<String> {
|
||||
val path = sharedPreferences.getString("active_strategy", "")
|
||||
if (!path.isNullOrBlank() && File(path).exists()) {
|
||||
return arrayOf(path)
|
||||
@@ -256,7 +265,7 @@ fun getActiveByeDPIStrategies(sharedPreferences: SharedPreferences): Array<Strin
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
fun getActiveStrategy(sharedPreferences: SharedPreferences): List<String> {
|
||||
fun getActiveByeDPIStrategyContent(sharedPreferences: SharedPreferences): List<String> {
|
||||
val path = sharedPreferences.getString("active_strategy", "")
|
||||
if (!path.isNullOrBlank() && File(path).exists()) {
|
||||
return File(path).readLines()
|
||||
@@ -264,6 +273,12 @@ fun getActiveStrategy(sharedPreferences: SharedPreferences): List<String> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun getActiveStrategy(sharedPreferences: SharedPreferences): Array<String> {
|
||||
return if (sharedPreferences.getBoolean("use_module", false)) getActiveNfqwsStrategy()
|
||||
else getActiveByeDPIStrategy(sharedPreferences)
|
||||
|
||||
}
|
||||
|
||||
fun enableList(path: String, sharedPreferences: SharedPreferences) {
|
||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||
val configFile = getConfigFile()
|
||||
|
||||
@@ -100,4 +100,18 @@
|
||||
<string name="log_copied">Лог скопирован</string>
|
||||
<string name="download_error">Произошла ошибка скачивания, пожалуйста, сообщите о ней разработчикам</string>
|
||||
<string name="ipset_repo_url">URL репозитория ipset</string>
|
||||
<string name="title_selection">Подбор стратегии</string>
|
||||
<string name="btn_repository_url_ipsets">URL репозитория ipset</string>
|
||||
<string name="hint_enter_repository_url_ipsets">Введите URL репозитория ipset</string>
|
||||
<string name="strategy_status_waiting">ожидание...</string>
|
||||
<string name="strategy_status_testing">проверка...</string>
|
||||
<string name="strategy_status_tested">проверка завершена</string>
|
||||
<string name="strategy_applied">Стратегия применена</string>
|
||||
<string name="begin_selection">Начать подбор</string>
|
||||
<string name="begin_selection_snack">Начат подбор стратегии. Не закрывайте эту вкладку</string>
|
||||
<string name="change_probe_timeout">Изменить таймаут пробы</string>
|
||||
<string name="probe_timeout">Таймаут пробы</string>
|
||||
<string name="hint_enter_probe_timeout">Введите таймаут пробы</string>
|
||||
<string name="strategy_selection_info_title">Информация</string>
|
||||
<string name="strategy_selection_info_msg">"В этом разделе настроек приложения представлен перебор стратегий\n Подбор проходит среди скачанных стратегий, поэтому заранее скачайте из репозитория или добавьте из файловой системы интересующие вас стратегии для сравнения. \n Перед началом так же выберете один или несколько листов доменов на вкладке \"Листы\", затем нажмите на \"Начать подбор\". Не используйте для перебора списки с большим количеством доменов."</string>
|
||||
</resources>
|
||||
@@ -105,4 +105,18 @@
|
||||
<string name="download_error">Occurred a file download error, please report to developers</string>
|
||||
<string name="ipset_repo_url">Ipset repository URL</string>
|
||||
<string name="title_ipset" translatable="false">Ipset</string>
|
||||
<string name="title_selection">Strategy selection</string>
|
||||
<string name="btn_repository_url_ipsets">Ipsets repository url</string>
|
||||
<string name="hint_enter_repository_url_ipsets">Enter ipsets repository URL</string>
|
||||
<string name="strategy_status_waiting">waiting...</string>
|
||||
<string name="strategy_status_testing">testing...</string>
|
||||
<string name="strategy_status_tested">testing ended</string>
|
||||
<string name="strategy_applied">Strategy applied</string>
|
||||
<string name="begin_selection">Begin selection</string>
|
||||
<string name="begin_selection_snack">Strategy selection has begun. Please keep this tab open.</string>
|
||||
<string name="change_probe_timeout">Change probe timeout</string>
|
||||
<string name="probe_timeout">Probe timeout</string>
|
||||
<string name="hint_enter_probe_timeout">Enter probe timeout</string>
|
||||
<string name="strategy_selection_info_title">Tip</string>
|
||||
<string name="strategy_selection_info_msg">This section of the application settings allows you to iterate through strategies.\n The selection is based on downloaded strategies, so download the strategies you\'re interested in from the repository or add them from the file system for comparison.\n Before starting, select one or more domain lists in the \"Lists\" tab, then click \"Start selection\". Avoid using lists with a large number of domains.</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.12.0"
|
||||
agp = "8.13.0"
|
||||
kotlin = "2.2.10"
|
||||
coreKtx = "1.17.0"
|
||||
junit = "4.13.2"
|
||||
|
||||
Reference in New Issue
Block a user