Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91e627ced6 | ||
|
|
0819cbab73 | ||
|
|
dddac71bc6 | ||
|
|
2d4850c0e8 | ||
|
|
a466ad8bcd | ||
|
|
1121365dfb | ||
|
|
212df7fa9b | ||
|
|
5e01ebae7d | ||
|
|
2e2b33ca55 | ||
|
|
2d1a25bbc3 | ||
|
|
4737e763ea | ||
|
|
8c93093b6b | ||
|
|
0977d30729 | ||
|
|
f02b22d8bd | ||
|
|
6cf4a067fb | ||
|
|
3fa009d3e9 | ||
|
|
0222ee5cfc | ||
|
|
e8c8a2a6c1 | ||
|
|
7c75174e26 | ||
|
|
c687d912b3 | ||
|
|
6b017d116f | ||
|
|
c8c3b6de96 | ||
|
|
b832be9556 | ||
|
|
817f3a0e7d | ||
|
|
7d25f7e600 | ||
|
|
8dcb4d1997 | ||
|
|
ba2cf523bd | ||
|
|
c268ee8ccd | ||
|
|
72905f4180 | ||
|
|
a7bb250a64 | ||
|
|
c153b582e7 |
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-06-22T07:39:53.690794081Z">
|
||||
<DropdownSelection timestamp="2025-07-08T13:21:50.180843436Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="Default" identifier="serial=10.0.0.189:40463;connection=ffec9c3f" />
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/white/.android/avd/Medium_Phone.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
@@ -16,3 +16,4 @@
|
||||
## Скриншоты:
|
||||
<img src="images/1.png" width="300"><img src="images/2.png" width="300"><img src="images/3.png" width="300"><img src="images/4.png" width="300"><img src="images/5.png" width="300">
|
||||
<img src="images/6.png" width="300">
|
||||
<img src="images/7.png" width="300">
|
||||
|
||||
@@ -13,10 +13,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.cherret.zaprett"
|
||||
minSdk = 30
|
||||
minSdk = 29
|
||||
targetSdk = 35
|
||||
versionCode = 13
|
||||
versionName = "2.1"
|
||||
versionCode = 15
|
||||
versionName = "2.3"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
ndk {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<application
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
@@ -45,7 +46,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".QSTileService"
|
||||
android:name=".utils.QSTileService"
|
||||
android:exported="true"
|
||||
android:label="@string/qs_name"
|
||||
android:icon="@drawable/ic_launcher_monochrome"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.cherret.zaprett
|
||||
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
@@ -18,9 +18,9 @@ import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Dashboard
|
||||
import androidx.compose.material.icons.filled.Dns
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Lan
|
||||
import androidx.compose.material.icons.filled.MultipleStop
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -30,13 +30,13 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
@@ -55,14 +55,15 @@ import com.cherret.zaprett.ui.theme.ZaprettTheme
|
||||
import com.cherret.zaprett.ui.viewmodel.HomeViewModel
|
||||
import com.cherret.zaprett.ui.viewmodel.HostRepoViewModel
|
||||
import com.cherret.zaprett.ui.viewmodel.StrategyRepoViewModel
|
||||
import com.cherret.zaprett.utils.checkModuleInstallation
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.analytics
|
||||
|
||||
sealed class Screen(val route: String, @StringRes val nameResId: Int, val icon: ImageVector) {
|
||||
object home : Screen("home", R.string.title_home, Icons.Default.Home)
|
||||
object hosts : Screen("hosts", R.string.title_hosts, Icons.Default.Dashboard)
|
||||
object strategies : Screen("strategies", R.string.title_strategies, Icons.Default.Dns)
|
||||
object hosts : Screen("hosts", R.string.title_hosts, Icons.Default.Lan)
|
||||
object strategies : Screen("strategies", R.string.title_strategies, Icons.Default.MultipleStop)
|
||||
object settings : Screen("settings", R.string.title_settings, Icons.Default.Settings)
|
||||
}
|
||||
val topLevelRoutes = listOf(Screen.home, Screen.hosts, Screen.strategies, Screen.settings)
|
||||
@@ -86,7 +87,29 @@ class MainActivity : ComponentActivity() {
|
||||
setContent {
|
||||
ZaprettTheme {
|
||||
val sharedPreferences = remember { getSharedPreferences("settings", MODE_PRIVATE) }
|
||||
var showStoragePermissionDialog by remember { mutableStateOf(!Environment.isExternalStorageManager()) }
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
checkModuleInstallation { result ->
|
||||
if (getSharedPreferences("settings", Context.MODE_PRIVATE).getBoolean("use_module", false) && !result) sharedPreferences.edit {
|
||||
putBoolean(
|
||||
"use_module",
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
var showStoragePermissionDialog by remember {
|
||||
mutableStateOf(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
!Environment.isExternalStorageManager()
|
||||
} else {
|
||||
ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
)
|
||||
}
|
||||
var showNotificationPermissionDialog by remember {
|
||||
mutableStateOf(
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||
@@ -101,10 +124,20 @@ class MainActivity : ComponentActivity() {
|
||||
title = stringResource(R.string.error_no_storage_title),
|
||||
message = stringResource(R.string.error_no_storage_message),
|
||||
onConfirm = {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.fromParts("package", packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.fromParts("package", applicationContext.packageName, null)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
} else {
|
||||
requestPermissions(
|
||||
arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
),
|
||||
100
|
||||
)
|
||||
}
|
||||
showStoragePermissionDialog = false
|
||||
},
|
||||
onDismiss = { showStoragePermissionDialog = false }
|
||||
@@ -133,6 +166,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun BottomBar() {
|
||||
val navController = rememberNavController()
|
||||
|
||||
@@ -14,7 +14,7 @@ import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.cherret.zaprett.MainActivity
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.getActiveStrategy
|
||||
import com.cherret.zaprett.utils.getActiveStrategy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -40,6 +40,7 @@ class ByeDpiVpnService : VpnService() {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
return when (intent?.action) {
|
||||
"START_VPN" -> {
|
||||
startForeground(NOTIFICATION_ID, createNotification())
|
||||
setupProxy()
|
||||
START_STICKY
|
||||
}
|
||||
@@ -98,23 +99,14 @@ class ByeDpiVpnService : VpnService() {
|
||||
}
|
||||
|
||||
private fun setupProxy() {
|
||||
if (getActiveStrategy(sharedPreferences).isNotEmpty()) {
|
||||
startForeground(NOTIFICATION_ID, createNotification())
|
||||
try {
|
||||
startSocksProxy()
|
||||
startByeDpi()
|
||||
status = ServiceStatus.Connected
|
||||
} catch (e: Exception) {
|
||||
Log.e("proxy", "Failed to start")
|
||||
status = ServiceStatus.Failed
|
||||
}
|
||||
}
|
||||
else {
|
||||
Toast.makeText(
|
||||
this@ByeDpiVpnService,
|
||||
getString(R.string.toast_no_strategy_selected),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
try {
|
||||
startSocksProxy()
|
||||
startByeDpi()
|
||||
status = ServiceStatus.Connected
|
||||
} catch (e: Exception) {
|
||||
Log.e("proxy", "Failed to start")
|
||||
status = ServiceStatus.Failed
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,17 @@ import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -49,6 +52,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
@@ -58,6 +62,7 @@ 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.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -75,6 +80,7 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel(), vpnLauncher: ActivityResu
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val cardText = viewModel.cardText
|
||||
val cardIcon = viewModel.cardIcon;
|
||||
val changeLog = viewModel.changeLog
|
||||
val newVersion = viewModel.newVersion
|
||||
val updateAvailable = viewModel.updateAvailable
|
||||
@@ -119,7 +125,7 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel(), vpnLauncher: ActivityResu
|
||||
Column(modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.verticalScroll(rememberScrollState())) {
|
||||
ServiceStatusCard(viewModel, cardText, snackbarHostState, scope)
|
||||
ServiceStatusCard(viewModel, cardText, cardIcon, snackbarHostState, scope)
|
||||
UpdateCard(updateAvailable) { viewModel.showUpdateDialog() }
|
||||
if (showUpdateDialog) {
|
||||
UpdateDialog(viewModel, changeLog.value.orEmpty(), newVersion) { viewModel.dismissUpdateDialog() }
|
||||
@@ -137,25 +143,44 @@ fun HomeScreen(viewModel: HomeViewModel = viewModel(), vpnLauncher: ActivityResu
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ServiceStatusCard(viewModel: HomeViewModel, cardText: MutableState<Int>, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
private fun ServiceStatusCard(viewModel: HomeViewModel, cardText: MutableState<Int>, cardIcon : MutableState<ImageVector>, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
ElevatedCard(
|
||||
elevation = CardDefaults.cardElevation(6.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, top = 25.dp, end = 10.dp)
|
||||
.width(240.dp)
|
||||
.height(150.dp),
|
||||
//.height(150.dp)
|
||||
.wrapContentHeight(),
|
||||
onClick = { viewModel.onCardClick() }
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(cardText.value),
|
||||
fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)),
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
textAlign = TextAlign.Center
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
painter = rememberVectorPainter(cardIcon.value),
|
||||
modifier = Modifier
|
||||
.width(60.dp)
|
||||
.height(60.dp),
|
||||
contentDescription = "icon"
|
||||
)
|
||||
Text(
|
||||
text = stringResource(cardText.value),
|
||||
fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)),
|
||||
fontSize = 16.sp,
|
||||
//maxLines = 3,
|
||||
//overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ import com.cherret.zaprett.BuildConfig
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.byedpi.ServiceStatus
|
||||
import com.cherret.zaprett.checkModuleInstallation
|
||||
import com.cherret.zaprett.checkRoot
|
||||
import com.cherret.zaprett.getStartOnBoot
|
||||
import com.cherret.zaprett.setStartOnBoot
|
||||
import com.cherret.zaprett.utils.checkModuleInstallation
|
||||
import com.cherret.zaprett.utils.checkRoot
|
||||
import com.cherret.zaprett.utils.getStartOnBoot
|
||||
import com.cherret.zaprett.utils.setStartOnBoot
|
||||
import com.cherret.zaprett.utils.stopService
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -66,7 +67,10 @@ fun SettingsScreen() {
|
||||
openNoRootDialog = openNoRootDialog,
|
||||
openNoModuleDialog = openNoModuleDialog
|
||||
) { success ->
|
||||
if (success) useModule.value = isChecked
|
||||
if (success) {
|
||||
useModule.value = isChecked
|
||||
if (!isChecked) stopService { }
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
@@ -11,15 +13,17 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.getZaprettPath
|
||||
import com.cherret.zaprett.restartService
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
import com.cherret.zaprett.utils.restartService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.jar.Manifest
|
||||
|
||||
abstract class BaseListsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val context = application
|
||||
@@ -61,7 +65,11 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
|
||||
}
|
||||
|
||||
fun copySelectedFile(context: Context, path: String, uri: Uri) {
|
||||
if (!Environment.isExternalStorageManager()) return
|
||||
//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)
|
||||
@@ -72,9 +80,29 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
|
||||
if (!directory.exists()) {
|
||||
directory.mkdirs()
|
||||
}
|
||||
val outputFile = File(getZaprettPath() + path, fileName)
|
||||
|
||||
try {
|
||||
val outputFile = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
val outputDir = File(getZaprettPath() + 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)
|
||||
}
|
||||
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
FileOutputStream(outputFile).use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
|
||||
@@ -9,13 +9,13 @@ import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.cherret.zaprett.RepoItemInfo
|
||||
import com.cherret.zaprett.utils.RepoItemInfo
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.download
|
||||
import com.cherret.zaprett.getFileSha256
|
||||
import com.cherret.zaprett.getZaprettPath
|
||||
import com.cherret.zaprett.registerDownloadListenerHost
|
||||
import com.cherret.zaprett.restartService
|
||||
import com.cherret.zaprett.utils.download
|
||||
import com.cherret.zaprett.utils.getFileSha256
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
import com.cherret.zaprett.utils.registerDownloadListenerHost
|
||||
import com.cherret.zaprett.utils.restartService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
@@ -3,6 +3,16 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Debug
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Help
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -11,17 +21,18 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.byedpi.ServiceStatus
|
||||
import com.cherret.zaprett.download
|
||||
import com.cherret.zaprett.getBinVersion
|
||||
import com.cherret.zaprett.getChangelog
|
||||
import com.cherret.zaprett.getModuleVersion
|
||||
import com.cherret.zaprett.getStatus
|
||||
import com.cherret.zaprett.getUpdate
|
||||
import com.cherret.zaprett.installApk
|
||||
import com.cherret.zaprett.registerDownloadListener
|
||||
import com.cherret.zaprett.restartService
|
||||
import com.cherret.zaprett.startService
|
||||
import com.cherret.zaprett.stopService
|
||||
import com.cherret.zaprett.utils.download
|
||||
import com.cherret.zaprett.utils.getActiveStrategy
|
||||
import com.cherret.zaprett.utils.getBinVersion
|
||||
import com.cherret.zaprett.utils.getChangelog
|
||||
import com.cherret.zaprett.utils.getModuleVersion
|
||||
import com.cherret.zaprett.utils.getStatus
|
||||
import com.cherret.zaprett.utils.getUpdate
|
||||
import com.cherret.zaprett.utils.installApk
|
||||
import com.cherret.zaprett.utils.registerDownloadListener
|
||||
import com.cherret.zaprett.utils.restartService
|
||||
import com.cherret.zaprett.utils.startService
|
||||
import com.cherret.zaprett.utils.stopService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -34,6 +45,8 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val requestVpnPermission = _requestVpnPermission.asStateFlow()
|
||||
var cardText = mutableIntStateOf(R.string.status_not_availible) // MVP temporarily(maybe)
|
||||
private set
|
||||
var cardIcon = mutableStateOf(Icons.AutoMirrored.Filled.Help)
|
||||
private set
|
||||
|
||||
var moduleVer = mutableStateOf(context.getString(R.string.unknown_text))
|
||||
private set
|
||||
@@ -77,21 +90,49 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
fun checkServiceStatus() {
|
||||
if (prefs.getBoolean("use_module", false) && prefs.getBoolean("update_on_boot", false)) {
|
||||
getStatus { isEnabled ->
|
||||
cardText.intValue = if (isEnabled) R.string.status_enabled else R.string.status_disabled
|
||||
if (isEnabled){
|
||||
cardText.intValue = R.string.status_enabled
|
||||
cardIcon.value = Icons.Filled.CheckCircle
|
||||
}
|
||||
else {
|
||||
cardText.intValue = R.string.status_disabled
|
||||
cardIcon.value = Icons.Filled.Cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
cardText.value = if (ByeDpiVpnService.status == ServiceStatus.Connected) R.string.status_enabled else R.string.status_disabled
|
||||
if (ByeDpiVpnService.status == ServiceStatus.Connected){
|
||||
cardText.intValue = R.string.status_enabled
|
||||
cardIcon.value = Icons.Filled.CheckCircle
|
||||
}
|
||||
else {
|
||||
cardText.intValue = R.string.status_disabled
|
||||
cardIcon.value = Icons.Filled.Cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCardClick() {
|
||||
if (prefs.getBoolean("use_module", false)) {
|
||||
getStatus { isEnabled ->
|
||||
cardText.value = if (isEnabled) R.string.status_enabled else R.string.status_disabled
|
||||
if (isEnabled){
|
||||
cardText.intValue = R.string.status_enabled
|
||||
cardIcon.value = Icons.Filled.CheckCircle
|
||||
}
|
||||
else {
|
||||
cardText.value = R.string.status_disabled
|
||||
cardIcon.value = Icons.Filled.Cancel
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cardText.value = if (ByeDpiVpnService.status == ServiceStatus.Connected) R.string.status_enabled else R.string.status_disabled
|
||||
if (ByeDpiVpnService.status == ServiceStatus.Connected){
|
||||
cardText.value = R.string.status_enabled
|
||||
cardIcon.value = Icons.Filled.CheckCircle
|
||||
}
|
||||
else {
|
||||
cardText.value = R.string.status_disabled
|
||||
cardIcon.value = Icons.Filled.Cancel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +154,19 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
} else {
|
||||
if (ByeDpiVpnService.status == ServiceStatus.Disconnected || ByeDpiVpnService.status == ServiceStatus.Failed) {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_starting_service))
|
||||
if (getActiveStrategy(prefs).isNotEmpty()) {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_starting_service))
|
||||
}
|
||||
_requestVpnPermission.value = true
|
||||
}
|
||||
else {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.toast_no_strategy_selected),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
_requestVpnPermission.value = true
|
||||
}
|
||||
else {
|
||||
scope.launch {
|
||||
@@ -196,9 +246,17 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
fun onUpdateConfirm() {
|
||||
showUpdateDialog.value = false
|
||||
val id = download(context, downloadUrl.value.orEmpty())
|
||||
registerDownloadListener(context, id) { uri ->
|
||||
installApk(context, uri)
|
||||
if (context.packageManager.canRequestPackageInstalls()){
|
||||
val id = download(context, downloadUrl.value.orEmpty())
|
||||
registerDownloadListener(context, id) { uri ->
|
||||
installApk(context, uri)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val packageUri = Uri.fromParts("package", context.packageName, null)
|
||||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageUri).addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
package com.cherret.zaprett.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.cherret.zaprett.RepoItemInfo
|
||||
import com.cherret.zaprett.download
|
||||
import com.cherret.zaprett.getAllLists
|
||||
import com.cherret.zaprett.getHostList
|
||||
import com.cherret.zaprett.getZaprettPath
|
||||
import com.cherret.zaprett.registerDownloadListenerHost
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import com.cherret.zaprett.utils.RepoItemInfo
|
||||
import com.cherret.zaprett.utils.getAllLists
|
||||
import com.cherret.zaprett.utils.getHostList
|
||||
|
||||
class HostRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||
override fun getInstalledLists(): Array<String> = getAllLists()
|
||||
|
||||
@@ -3,11 +3,11 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import com.cherret.zaprett.disableList
|
||||
import com.cherret.zaprett.enableList
|
||||
import com.cherret.zaprett.getActiveLists
|
||||
import com.cherret.zaprett.getAllLists
|
||||
import com.cherret.zaprett.getStatus
|
||||
import com.cherret.zaprett.utils.disableList
|
||||
import com.cherret.zaprett.utils.enableList
|
||||
import com.cherret.zaprett.utils.getActiveLists
|
||||
import com.cherret.zaprett.utils.getAllLists
|
||||
import com.cherret.zaprett.utils.getStatus
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.io.File
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.cherret.zaprett.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import com.cherret.zaprett.RepoItemInfo
|
||||
import com.cherret.zaprett.getAllByeDPIStrategies
|
||||
import com.cherret.zaprett.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.getStrategiesList
|
||||
import com.cherret.zaprett.utils.RepoItemInfo
|
||||
import com.cherret.zaprett.utils.getAllByeDPIStrategies
|
||||
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.utils.getStrategiesList
|
||||
|
||||
class StrategyRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||
override fun getInstalledLists(): Array<String> =
|
||||
|
||||
@@ -6,13 +6,13 @@ import android.content.SharedPreferences
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.byedpi.ServiceStatus
|
||||
import com.cherret.zaprett.disableStrategy
|
||||
import com.cherret.zaprett.enableStrategy
|
||||
import com.cherret.zaprett.getActiveByeDPIStrategies
|
||||
import com.cherret.zaprett.getActiveNfqwsStrategies
|
||||
import com.cherret.zaprett.getAllByeDPIStrategies
|
||||
import com.cherret.zaprett.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.getStatus
|
||||
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.getAllByeDPIStrategies
|
||||
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.utils.getStatus
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.io.File
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.cherret.zaprett
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Environment
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.cherret.zaprett
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import com.cherret.zaprett.R
|
||||
|
||||
class QSTileService: TileService() {
|
||||
override fun onTileAdded() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.cherret.zaprett
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
@@ -1,18 +1,22 @@
|
||||
package com.cherret.zaprett
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import com.cherret.zaprett.BuildConfig
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Call
|
||||
@@ -77,7 +81,22 @@ fun download(context: Context, url: String): Long {
|
||||
setTitle(fileName)
|
||||
setDescription(fileName)
|
||||
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.Downloads.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
val uri = context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
if (uri != null) {
|
||||
setDestinationUri(uri)
|
||||
} else {
|
||||
Log.e("Updater", "Failed to create MediaStore URI")
|
||||
return -1L
|
||||
}
|
||||
} else {
|
||||
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
||||
}
|
||||
|
||||
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
|
||||
}
|
||||
return downloadManager.enqueue(request)
|
||||
@@ -93,11 +112,6 @@ fun installApk(context: Context, uri: Uri) {
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
else {
|
||||
val packageUri = Uri.fromParts("package", context.packageName, null)
|
||||
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageUri)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun registerDownloadListener(context: Context, downloadId: Long, onDownloaded: (Uri) -> Unit) {// AI Generated
|
||||
@@ -1,3 +1 @@
|
||||
Исправление багов в фрагменте хостов
|
||||
Исправление сплющивания карточки информации о модуле
|
||||
Перенос версии byedpi в соответствующее место
|
||||
Поддержка Android 10(спасибо @Likhach42)
|
||||
|
||||
BIN
images/1.png
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 123 KiB |
BIN
images/2.png
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 118 KiB |
BIN
images/3.png
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 130 KiB |
BIN
images/4.png
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 89 KiB |
BIN
images/5.png
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 142 KiB |
BIN
images/6.png
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 113 KiB |
BIN
images/7.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "2.1",
|
||||
"versionCode": 13,
|
||||
"downloadUrl": "https://github.com/CherretGit/zaprett-app/releases/download/2_1/app-release.apk",
|
||||
"version": "2.3",
|
||||
"versionCode": 15,
|
||||
"downloadUrl": "https://github.com/CherretGit/zaprett-app/releases/download/2_3/app-release.apk",
|
||||
"changelogUrl": "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/changelog.md"
|
||||
}
|
||||
|
||||