mirror of
https://github.com/CherretGit/zaprett-app.git
synced 2026-03-22 00:28:15 +05:00
Compare commits
3 Commits
09cdb53791
...
154d8214bb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
154d8214bb | ||
|
|
490709384e | ||
|
|
b65b725ae5 |
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -6,7 +6,6 @@
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="/usr/share/java/gradle" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
@@ -16,6 +15,5 @@
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
<option name="parallelModelFetch" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
155
app/src/main/java/com/cherret/zaprett/ui/component/Dialogs.kt
Normal file
155
app/src/main/java/com/cherret/zaprett/ui/component/Dialogs.kt
Normal file
@@ -0,0 +1,155 @@
|
||||
package com.cherret.zaprett.ui.component
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
|
||||
@Composable
|
||||
fun InfoDialog(title: String, message: String, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
title = { Text(text = title) },
|
||||
text = { Text(text = message) },
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextDialog(title: String, message: String, initialText: String, onConfirm: (String) -> Unit, onDismiss: () -> Unit) {
|
||||
var inputText by remember { mutableStateOf(initialText) }
|
||||
AlertDialog(
|
||||
title = { Text(text = title) },
|
||||
text = {
|
||||
TextField(
|
||||
value = inputText,
|
||||
onValueChange = { inputText = it },
|
||||
placeholder = { Text(message) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)},
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (inputText.isNotEmpty()) {
|
||||
onConfirm(inputText)
|
||||
onDismiss()
|
||||
}
|
||||
else {
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { onDismiss() }
|
||||
) {
|
||||
Text(stringResource(R.string.btn_dismiss))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@Composable
|
||||
fun GenerateManifestDialog(path: String, onConfirm: (StorageData) -> Unit, onDismiss: () -> Unit) {
|
||||
var id by remember { mutableStateOf("") }
|
||||
var name by remember { mutableStateOf("") }
|
||||
var version by remember { mutableStateOf("") }
|
||||
var author by remember { mutableStateOf("") }
|
||||
var description by remember { mutableStateOf("") }
|
||||
AlertDialog(
|
||||
title = { Text(text = stringResource(R.string.title_generate_manifest)) },
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
TextField(
|
||||
value = id,
|
||||
onValueChange = { id = it },
|
||||
placeholder = { Text(stringResource(R.string.hint_manifest_id)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
TextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
placeholder = { Text(stringResource(R.string.hint_manifest_name)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
TextField(
|
||||
value = version,
|
||||
onValueChange = { version = it },
|
||||
placeholder = { Text(stringResource(R.string.hint_manifest_version)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
TextField(
|
||||
value = author,
|
||||
onValueChange = { author = it },
|
||||
placeholder = { Text(stringResource(R.string.hint_manifest_author)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
TextField(
|
||||
value = description,
|
||||
onValueChange = { description = it },
|
||||
placeholder = { Text(stringResource(R.string.hint_manifest_description)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onConfirm(
|
||||
StorageData(
|
||||
schema = 1,
|
||||
id = id,
|
||||
name = name,
|
||||
version = version,
|
||||
author = author,
|
||||
description = description,
|
||||
dependencies = emptyList(),
|
||||
file = path
|
||||
)
|
||||
) }
|
||||
) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = {
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.btn_dismiss))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -127,7 +127,7 @@ fun SettingDropDown(title: String, selected: String, items: List<DropdownItem>)
|
||||
.fillMaxWidth()
|
||||
.height(80.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||
onClick = { expanded = true },
|
||||
onClick = { expanded = !expanded },
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
Row(
|
||||
@@ -181,56 +181,4 @@ fun SettingsSection(title: String) {
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(vertical = 8.dp, horizontal = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InfoDialog(title: String, message: String, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
title = { Text(text = title) },
|
||||
text = { Text(text = message) },
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextDialog(title: String, message: String, initialText: String, onConfirm: (String) -> Unit, onDismiss: () -> Unit) {
|
||||
var inputText by remember { mutableStateOf(initialText) }
|
||||
AlertDialog(
|
||||
title = { Text(text = title) },
|
||||
text = {
|
||||
TextField(
|
||||
value = inputText,
|
||||
onValueChange = { inputText = it },
|
||||
placeholder = { Text(message) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)},
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (inputText.isNotEmpty()) {
|
||||
onConfirm(inputText)
|
||||
onDismiss()
|
||||
}
|
||||
else {
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = { onDismiss() }
|
||||
) {
|
||||
Text(stringResource(R.string.btn_dismiss))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -82,6 +82,7 @@ import com.cherret.zaprett.ui.viewmodel.HomeViewModel
|
||||
import com.cherret.zaprett.utils.getServiceType
|
||||
import dev.jeziellago.compose.markdowntext.MarkdownText
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -103,9 +104,9 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel(), vpnLauncher: ActivityResu
|
||||
val serviceMode = viewModel.serviceMode
|
||||
val error by viewModel.errorFlow.collectAsState()
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.checkForUpdate()
|
||||
viewModel.checkServiceStatus()
|
||||
viewModel.checkModuleInfo()
|
||||
launch { viewModel.checkForUpdate() }
|
||||
launch { viewModel.checkServiceStatus() }
|
||||
launch { viewModel.checkModuleInfo() }
|
||||
}
|
||||
|
||||
LaunchedEffect(requestVpnPermission) {
|
||||
|
||||
@@ -63,9 +63,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.ui.component.GenerateManifestDialog
|
||||
import com.cherret.zaprett.ui.component.ListSwitchItem
|
||||
import com.cherret.zaprett.ui.viewmodel.HostsViewModel
|
||||
import com.cherret.zaprett.utils.getHostListMode
|
||||
import com.cherret.zaprett.utils.getManifestsPath
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -82,10 +85,17 @@ fun HostsScreen(navController: NavController, viewModel: HostsViewModel = viewMo
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
if (getHostListMode(prefs) == ListType.whitelist) viewModel.copySelectedFile(context, "/lists/include", it)
|
||||
else viewModel.copySelectedFile(context, "/lists/exclude", it) }
|
||||
val path = when (getHostListMode(prefs)) {
|
||||
ListType.whitelist -> getZaprettPath().resolve("files/lists/include")
|
||||
ListType.blacklist -> getZaprettPath().resolve("files/lists/exclude")
|
||||
}
|
||||
viewModel.prepareImport(context, path, uri)
|
||||
}
|
||||
}
|
||||
val error by viewModel.errorFlow.collectAsState()
|
||||
val showGenerateManifestDialog by viewModel.showGenerateManifestDialog.collectAsState()
|
||||
val pendingName by viewModel.pendingFileName.collectAsState()
|
||||
val pendingUri by viewModel.pendingFileUri.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.refresh()
|
||||
@@ -208,6 +218,17 @@ fun HostsScreen(navController: NavController, viewModel: HostsViewModel = viewMo
|
||||
}
|
||||
)
|
||||
}
|
||||
if (showGenerateManifestDialog && pendingName != null && pendingUri != null) {
|
||||
GenerateManifestDialog(
|
||||
path = pendingName!!,
|
||||
onConfirm = { manifest ->
|
||||
val manifestPath = when (getHostListMode(prefs)) {
|
||||
ListType.whitelist -> getManifestsPath().resolve("lists/include")
|
||||
ListType.blacklist -> getManifestsPath().resolve("lists/exclude")
|
||||
}
|
||||
viewModel.import(context, manifestPath, manifest)
|
||||
}, onDismiss = {viewModel.cancelImport() })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -63,9 +63,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.ui.component.GenerateManifestDialog
|
||||
import com.cherret.zaprett.ui.component.ListSwitchItem
|
||||
import com.cherret.zaprett.ui.viewmodel.IpsetViewModel
|
||||
import com.cherret.zaprett.utils.getHostListMode
|
||||
import com.cherret.zaprett.utils.getManifestsPath
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -82,10 +85,17 @@ fun IpsetsScreen(navController: NavController, viewModel: IpsetViewModel = viewM
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
if (getHostListMode(prefs) == ListType.whitelist) viewModel.copySelectedFile(context, "/ipset/include", it)
|
||||
else viewModel.copySelectedFile(context, "/ipset/exclude", it) }
|
||||
val path = when (getHostListMode(prefs)) {
|
||||
ListType.whitelist -> getZaprettPath().resolve("files/ipset/include")
|
||||
ListType.blacklist -> getZaprettPath().resolve("files/ipset/exclude")
|
||||
}
|
||||
viewModel.prepareImport(context, path, uri)
|
||||
}
|
||||
}
|
||||
val error by viewModel.errorFlow.collectAsState()
|
||||
val showGenerateManifestDialog by viewModel.showGenerateManifestDialog.collectAsState()
|
||||
val pendingName by viewModel.pendingFileName.collectAsState()
|
||||
val pendingUri by viewModel.pendingFileUri.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.refresh()
|
||||
@@ -208,6 +218,17 @@ fun IpsetsScreen(navController: NavController, viewModel: IpsetViewModel = viewM
|
||||
}
|
||||
)
|
||||
}
|
||||
if (showGenerateManifestDialog && pendingName != null && pendingUri != null) {
|
||||
GenerateManifestDialog(
|
||||
path = pendingName!!,
|
||||
onConfirm = { manifest ->
|
||||
val manifestPath = when (getHostListMode(prefs)) {
|
||||
ListType.whitelist -> getManifestsPath().resolve("ipset/include")
|
||||
ListType.blacklist -> getManifestsPath().resolve("ipset/exclude")
|
||||
}
|
||||
viewModel.import(context, manifestPath, manifest)
|
||||
}, onDismiss = {viewModel.cancelImport() })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -57,9 +57,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.ui.component.GenerateManifestDialog
|
||||
import com.cherret.zaprett.ui.component.ListSwitchItem
|
||||
import com.cherret.zaprett.ui.viewmodel.StrategyViewModel
|
||||
import com.cherret.zaprett.utils.getManifestsPath
|
||||
import com.cherret.zaprett.utils.getServiceType
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -75,17 +78,19 @@ fun StrategyScreen(navController: NavController, viewModel: StrategyViewModel =
|
||||
val filePickerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
uri?.let { viewModel.copySelectedFile(
|
||||
context,
|
||||
when(getServiceType(sharedPreferences)) {
|
||||
ServiceType.nfqws -> "/strategies/nfqws"
|
||||
ServiceType.nfqws2 -> "/strategies/nfqws2"
|
||||
ServiceType.byedpi -> "/strategies/byedpi"
|
||||
},
|
||||
it
|
||||
) }
|
||||
uri?.let {
|
||||
val path = when (getServiceType(sharedPreferences)) {
|
||||
ServiceType.byedpi -> getZaprettPath().resolve("files/strategies/byedpi")
|
||||
ServiceType.nfqws -> getZaprettPath().resolve("files/strategies/nfqws")
|
||||
ServiceType.nfqws2 -> getZaprettPath().resolve("files/strategies/nfqws2")
|
||||
}
|
||||
viewModel.prepareImport(context, path, uri)
|
||||
}
|
||||
}
|
||||
val error by viewModel.errorFlow.collectAsState()
|
||||
val showGenerateManifestDialog by viewModel.showGenerateManifestDialog.collectAsState()
|
||||
val pendingName by viewModel.pendingFileName.collectAsState()
|
||||
val pendingUri by viewModel.pendingFileUri.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.refresh()
|
||||
@@ -205,6 +210,18 @@ fun StrategyScreen(navController: NavController, viewModel: StrategyViewModel =
|
||||
}
|
||||
)
|
||||
}
|
||||
if (showGenerateManifestDialog && pendingName != null && pendingUri != null) {
|
||||
GenerateManifestDialog(
|
||||
path = pendingName!!,
|
||||
onConfirm = { manifest ->
|
||||
val manifestPath = when (getServiceType(sharedPreferences)) {
|
||||
ServiceType.byedpi -> getManifestsPath().resolve("strategies/byedpi")
|
||||
ServiceType.nfqws -> getManifestsPath().resolve("strategies/nfqws")
|
||||
ServiceType.nfqws2 -> getManifestsPath().resolve("strategies/nfqws2")
|
||||
}
|
||||
viewModel.import(context, manifestPath, manifest)
|
||||
}, onDismiss = {viewModel.cancelImport() })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -18,19 +18,23 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.checkStoragePermission
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
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 kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
abstract class BaseListsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val context = application
|
||||
val json = Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
var allItems by mutableStateOf<List<StorageData>>(emptyList())
|
||||
private set
|
||||
var activeItems by mutableStateOf<List<StorageData>>(emptyList())
|
||||
@@ -38,12 +42,18 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
|
||||
val checked = mutableStateMapOf<StorageData, Boolean>()
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
private val _pendingFileName = MutableStateFlow<String?>(null)
|
||||
val pendingFileName: StateFlow<String?> = _pendingFileName.asStateFlow()
|
||||
private val _pendingFileUri = MutableStateFlow<Uri?>(null)
|
||||
val pendingFileUri: StateFlow<Uri?> = _pendingFileUri.asStateFlow()
|
||||
|
||||
private val _errorFlow = MutableStateFlow("")
|
||||
val errorFlow = _errorFlow.asStateFlow()
|
||||
|
||||
private var _showNoPermissionDialog = MutableStateFlow(false)
|
||||
val showNoPermissionDialog: StateFlow<Boolean> = _showNoPermissionDialog
|
||||
private var _showGenerateManifestDialog = MutableStateFlow(false)
|
||||
val showGenerateManifestDialog: StateFlow<Boolean> = _showGenerateManifestDialog
|
||||
|
||||
abstract fun loadAllItems(): Array<StorageData>
|
||||
abstract fun loadActiveItems(): Array<StorageData>
|
||||
@@ -69,7 +79,7 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
|
||||
fun hideNoPermissionDialog() {
|
||||
_showNoPermissionDialog.value = false
|
||||
}
|
||||
|
||||
|
||||
fun showRestartSnackbar(context: Context, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
scope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
@@ -89,51 +99,48 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
|
||||
_errorFlow.value = ""
|
||||
}
|
||||
|
||||
fun prepareImport(context: Context, path: File, uri: Uri) {
|
||||
val name = context.contentResolver.query(uri, null, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (cursor.moveToFirst() && nameIndex != -1) cursor.getString(nameIndex) else null
|
||||
} ?: "copied_file"
|
||||
_pendingFileName.value = path.resolve(name).absolutePath
|
||||
_pendingFileUri.value = uri
|
||||
_showGenerateManifestDialog.value = true
|
||||
}
|
||||
|
||||
fun cancelImport() {
|
||||
_pendingFileName.value = null
|
||||
_pendingFileUri.value = null
|
||||
_showGenerateManifestDialog.value = true
|
||||
}
|
||||
|
||||
fun import(context: Context, manifestPath: File, manifest: StorageData) {
|
||||
val uri = _pendingFileUri.value ?: return
|
||||
val manifestFile = manifestPath.resolve("${manifest.id}.json")
|
||||
manifestFile.parentFile!!.mkdirs()
|
||||
manifestFile.writeText(json.encodeToString(manifest))
|
||||
copySelectedFile(context, manifest.file, uri)
|
||||
_pendingFileName.value = null
|
||||
_pendingFileUri.value = null
|
||||
_showGenerateManifestDialog.value = false
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun copySelectedFile(context: Context, path: String, uri: Uri) {
|
||||
//if (!Environment.isExternalStorageManager()) return
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
|
||||
if (!Environment.isExternalStorageManager()) return
|
||||
}
|
||||
else if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) return
|
||||
val contentResolver = context.contentResolver
|
||||
val fileName = contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (cursor.moveToFirst() && nameIndex != -1) cursor.getString(nameIndex) else "copied_file"
|
||||
} ?: "copied_file"
|
||||
|
||||
val directory = getZaprettPath().resolve(path)
|
||||
if (!directory.exists()) {
|
||||
directory.mkdirs()
|
||||
}
|
||||
|
||||
val directory = File(path)
|
||||
try {
|
||||
val outputFile = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
val outputDir = getZaprettPath().resolve(path)
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs()
|
||||
}
|
||||
File(outputDir, fileName)
|
||||
} else {
|
||||
val outputDir = File(context.filesDir, path)
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs()
|
||||
}
|
||||
File(outputDir, fileName)
|
||||
}
|
||||
} else {
|
||||
val outputDir = File(context.filesDir, path)
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs()
|
||||
}
|
||||
File(outputDir, fileName)
|
||||
}
|
||||
directory.parentFile?.mkdirs()
|
||||
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
FileOutputStream(outputFile).use { outputStream ->
|
||||
FileOutputStream(directory).use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class HostsViewModel(application: Application): BaseListsViewModel(application)
|
||||
}
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
|
||||
@@ -42,6 +42,7 @@ class IpsetViewModel(application: Application): BaseListsViewModel(application)
|
||||
}
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
|
||||
@@ -57,6 +57,7 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
|
||||
showRestartSnackbar(context, snackbarHostState, scope)
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
@@ -88,6 +89,6 @@ object NetworkUtils {
|
||||
updateInfo,
|
||||
changeLog
|
||||
)
|
||||
}
|
||||
}.onFailure { if (it is CancellationException) throw it }
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,8 @@ package com.cherret.zaprett.utils
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
@@ -19,19 +17,16 @@ class QSTileService: TileService() {
|
||||
prefs = applicationContext.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
override fun onTileAdded() {
|
||||
super.onTileAdded()
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||
@@ -59,7 +54,6 @@ class QSTileService: TileService() {
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun updateStatus() {
|
||||
if (getServiceType(prefs) != ServiceType.byedpi) {
|
||||
getStatus {
|
||||
|
||||
@@ -118,4 +118,10 @@
|
||||
<string name="reset_settings_title">Сброс настроек</string>
|
||||
<string name="reset_settings_message">Вы действительно хотите сбросить настройки?</string>
|
||||
<string name="error_hash_mismatch">Не совпала контрольная сумма файла, попробуйте позже</string>
|
||||
<string name="title_generate_manifest">Генерация манифеста</string>
|
||||
<string name="hint_manifest_id">Введите ID манифеста</string>
|
||||
<string name="hint_manifest_name">Введите имя манифеста</string>
|
||||
<string name="hint_manifest_version">Введите версию манифеста</string>
|
||||
<string name="hint_manifest_author">Введите автора манифеста</string>
|
||||
<string name="hint_manifest_description">Введите описание манифеста</string>
|
||||
</resources>
|
||||
@@ -124,4 +124,10 @@
|
||||
<string name="reset_settings_title">Reset settings</string>
|
||||
<string name="reset_settings_message">Are you sure you want to reset the settings?</string>
|
||||
<string name="error_hash_mismatch">File checksum mismatch, please try again later</string>
|
||||
<string name="title_generate_manifest">Generate manifest</string>
|
||||
<string name="hint_manifest_id">Enter manifest ID</string>
|
||||
<string name="hint_manifest_name">Enter manifest name</string>
|
||||
<string name="hint_manifest_version">Enter manifest version</string>
|
||||
<string name="hint_manifest_author">Enter manifest author</string>
|
||||
<string name="hint_manifest_description">Enter manifest description</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user