diff --git a/app/src/main/java/com/cherret/zaprett/ui/screen/HostsScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screen/HostsScreen.kt index ac2c95e..f369d02 100644 --- a/app/src/main/java/com/cherret/zaprett/ui/screen/HostsScreen.kt +++ b/app/src/main/java/com/cherret/zaprett/ui/screen/HostsScreen.kt @@ -1,7 +1,11 @@ package com.cherret.zaprett.ui.screen +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.SharedPreferences +import android.os.Build +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -79,11 +83,43 @@ fun HostsScreen(navController: NavController, viewModel: HostsViewModel = viewMo if (getHostListMode(prefs) == "whitelist") viewModel.copySelectedFile(context, "/lists/include", it) else viewModel.copySelectedFile(context, "/lists/exclude", it) } } + val error by viewModel.errorFlow.collectAsState() LaunchedEffect(Unit) { viewModel.refresh() } + if (error.isNotEmpty()) { + AlertDialog( + onDismissRequest = { + viewModel.clearError() + }, + title = { Text(stringResource(R.string.error_text)) }, + text = { + Text(stringResource(R.string.error_unknown)) + }, + dismissButton = { + TextButton(onClick = { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip: ClipData = ClipData.newPlainText("Error log", error) + clipboard.setPrimaryClip(clip) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { + Toast.makeText(context, context.getString(R.string.log_copied), Toast.LENGTH_SHORT).show() + } + }) { + Text(stringResource(R.string.btn_copy_log)) + } + }, + confirmButton = { + TextButton(onClick = { + viewModel.clearError() + }) { + Text(stringResource(R.string.btn_continue)) + } + } + ) + } + Scaffold( topBar = { TopAppBar( diff --git a/app/src/main/java/com/cherret/zaprett/ui/screen/IpsetsScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screen/IpsetsScreen.kt index d5e80ad..097020a 100644 --- a/app/src/main/java/com/cherret/zaprett/ui/screen/IpsetsScreen.kt +++ b/app/src/main/java/com/cherret/zaprett/ui/screen/IpsetsScreen.kt @@ -1,7 +1,11 @@ package com.cherret.zaprett.ui.screen +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.SharedPreferences +import android.os.Build +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -79,11 +83,43 @@ fun IpsetsScreen(navController: NavController, viewModel: IpsetViewModel = viewM if (getHostListMode(prefs) == "whitelist") viewModel.copySelectedFile(context, "/ipset/include", it) else viewModel.copySelectedFile(context, "/ipset/exclude", it) } } + val error by viewModel.errorFlow.collectAsState() LaunchedEffect(Unit) { viewModel.refresh() } + if (error.isNotEmpty()) { + AlertDialog( + onDismissRequest = { + viewModel.clearError() + }, + title = { Text(stringResource(R.string.error_text)) }, + text = { + Text(stringResource(R.string.error_unknown)) + }, + dismissButton = { + TextButton(onClick = { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip: ClipData = ClipData.newPlainText("Error log", error) + clipboard.setPrimaryClip(clip) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { + Toast.makeText(context, context.getString(R.string.log_copied), Toast.LENGTH_SHORT).show() + } + }) { + Text(stringResource(R.string.btn_copy_log)) + } + }, + confirmButton = { + TextButton(onClick = { + viewModel.clearError() + }) { + Text(stringResource(R.string.btn_continue)) + } + } + ) + } + Scaffold( topBar = { TopAppBar( diff --git a/app/src/main/java/com/cherret/zaprett/ui/screen/StrategyScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screen/StrategyScreen.kt index bc28e38..d460b7c 100644 --- a/app/src/main/java/com/cherret/zaprett/ui/screen/StrategyScreen.kt +++ b/app/src/main/java/com/cherret/zaprett/ui/screen/StrategyScreen.kt @@ -1,6 +1,10 @@ package com.cherret.zaprett.ui.screen +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context +import android.os.Build +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -73,11 +77,43 @@ fun StrategyScreen(navController: NavController, viewModel: StrategyViewModel = it ) } } + val error by viewModel.errorFlow.collectAsState() LaunchedEffect(Unit) { viewModel.refresh() } + if (error.isNotEmpty()) { + AlertDialog( + onDismissRequest = { + viewModel.clearError() + }, + title = { Text(stringResource(R.string.error_text)) }, + text = { + Text(stringResource(R.string.error_unknown)) + }, + dismissButton = { + TextButton(onClick = { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip: ClipData = ClipData.newPlainText("Error log", error) + clipboard.setPrimaryClip(clip) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { + Toast.makeText(context, context.getString(R.string.log_copied), Toast.LENGTH_SHORT).show() + } + }) { + Text(stringResource(R.string.btn_copy_log)) + } + }, + confirmButton = { + TextButton(onClick = { + viewModel.clearError() + }) { + Text(stringResource(R.string.btn_continue)) + } + } + ) + } + Scaffold( topBar = { TopAppBar( diff --git a/app/src/main/java/com/cherret/zaprett/ui/screen/StrategySelectionScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screen/StrategySelectionScreen.kt index 0bc189b..d971fbb 100644 --- a/app/src/main/java/com/cherret/zaprett/ui/screen/StrategySelectionScreen.kt +++ b/app/src/main/java/com/cherret/zaprett/ui/screen/StrategySelectionScreen.kt @@ -1,8 +1,13 @@ package com.cherret.zaprett.ui.screen +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.Intent import android.net.VpnService +import android.os.Build +import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -59,6 +64,7 @@ fun StrategySelectionScreen(navController: NavController, vpnLauncher: ActivityR val prefs = context.getSharedPreferences("settings", MODE_PRIVATE) var showDialog = remember { mutableStateOf(false) } val requestVpnPermission by viewModel.requestVpnPermission.collectAsState() + val error by viewModel.errorFlow.collectAsState() if (showDialog.value) { InfoAlert { showDialog.value = false } @@ -76,6 +82,37 @@ fun StrategySelectionScreen(navController: NavController, vpnLauncher: ActivityR } } + if (error.isNotEmpty()) { + AlertDialog( + onDismissRequest = { + viewModel.clearError() + }, + title = { Text(stringResource(R.string.error_text)) }, + text = { + Text(stringResource(R.string.error_unknown)) + }, + dismissButton = { + TextButton(onClick = { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip: ClipData = ClipData.newPlainText("Error log", error) + clipboard.setPrimaryClip(clip) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) { + Toast.makeText(context, context.getString(R.string.log_copied), Toast.LENGTH_SHORT).show() + } + }) { + Text(stringResource(R.string.btn_copy_log)) + } + }, + confirmButton = { + TextButton(onClick = { + viewModel.clearError() + }) { + Text(stringResource(R.string.btn_continue)) + } + } + ) + } + Scaffold( topBar = { TopAppBar( diff --git a/app/src/main/java/com/cherret/zaprett/ui/viewmodel/BaseListsViewModel.kt b/app/src/main/java/com/cherret/zaprett/ui/viewmodel/BaseListsViewModel.kt index d1691b8..5aa82ab 100644 --- a/app/src/main/java/com/cherret/zaprett/ui/viewmodel/BaseListsViewModel.kt +++ b/app/src/main/java/com/cherret/zaprett/ui/viewmodel/BaseListsViewModel.kt @@ -22,6 +22,7 @@ import com.cherret.zaprett.utils.restartService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import java.io.File import java.io.FileOutputStream @@ -37,6 +38,9 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a var isRefreshing by mutableStateOf(false) private set + private val _errorFlow = MutableStateFlow("") + val errorFlow = _errorFlow.asStateFlow() + private var _showNoPermissionDialog = MutableStateFlow(false) val showNoPermissionDialog: StateFlow = _showNoPermissionDialog @@ -72,12 +76,18 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a actionLabel = context.getString(R.string.btn_restart_service) ) if (result == SnackbarResult.ActionPerformed) { - restartService {} + restartService { error -> + _errorFlow.value = error + } snackbarHostState.showSnackbar(context.getString(R.string.snack_reload)) } } } + fun clearError() { + _errorFlow.value = "" + } + fun copySelectedFile(context: Context, path: String, uri: Uri) { //if (!Environment.isExternalStorageManager()) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){ diff --git a/app/src/main/java/com/cherret/zaprett/ui/viewmodel/StrategySelectionViewModel.kt b/app/src/main/java/com/cherret/zaprett/ui/viewmodel/StrategySelectionViewModel.kt index 20ee48c..688c047 100644 --- a/app/src/main/java/com/cherret/zaprett/ui/viewmodel/StrategySelectionViewModel.kt +++ b/app/src/main/java/com/cherret/zaprett/ui/viewmodel/StrategySelectionViewModel.kt @@ -43,6 +43,8 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap val context = application private val _requestVpnPermission = MutableStateFlow(false) val requestVpnPermission = _requestVpnPermission.asStateFlow() + private val _errorFlow = MutableStateFlow("") + val errorFlow = _errorFlow.asStateFlow() val strategyStates = mutableStateListOf() var noHostsCard = mutableStateOf(false) private set @@ -125,8 +127,12 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap strategyStates[index] = current.copy(status = StrategyTestingStatus.Testing) enableStrategy(current.path, prefs) if (prefs.getBoolean("use_module", false)) { - getStatus { if (it) stopService {} } - startService {} + getStatus { if (it) stopService { error -> + _errorFlow.value = error + } } + startService { error -> + _errorFlow.value = error + } try { val progress = countReachable(index, targets) val old = strategyStates[index] @@ -135,7 +141,9 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap status = StrategyTestingStatus.Completed ) } finally { - stopService {} + stopService { error -> + _errorFlow.value = error + } disableStrategy(current.path, prefs) } } @@ -189,4 +197,7 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap fun clearVpnPermissionRequest() { _requestVpnPermission.value = false } + fun clearError() { + _errorFlow.value = "" + } } \ No newline at end of file