mirror of
https://github.com/CherretGit/zaprett-app.git
synced 2026-03-22 00:28:15 +05:00
Compare commits
7 Commits
0ada5ff50a
...
c387cd13b8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c387cd13b8 | ||
|
|
a06989033e | ||
|
|
da2b53ace7 | ||
|
|
a145ae4349 | ||
|
|
1f2df5bdfc | ||
|
|
47c3b24733 | ||
|
|
eb082a6072 |
@@ -199,8 +199,8 @@ class ByeDpiVpnService : VpnService() {
|
||||
socksIp,
|
||||
socksPort,
|
||||
getActiveByeDPIStrategyContent(sharedPreferences),
|
||||
prepareList(listSet),
|
||||
prepareIpset(ipsetSet),
|
||||
prepareList(listSet.map { it.file }.toTypedArray()),
|
||||
prepareIpset(ipsetSet.map { it.file }.toTypedArray()),
|
||||
sharedPreferences)
|
||||
val result = NativeBridge().jniStartProxy(args)
|
||||
if (result < 0) {
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package com.cherret.zaprett.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
@Serializable
|
||||
data class StorageData(
|
||||
val schema: Int,
|
||||
val id: String,
|
||||
val name: String,
|
||||
val version: String,
|
||||
val author: String,
|
||||
val description: String,
|
||||
val dependencies: List<String> = emptyList(),
|
||||
val file: String
|
||||
)
|
||||
val file: String,
|
||||
|
||||
){
|
||||
@Transient
|
||||
var manifestPath: String = ""
|
||||
}
|
||||
16
app/src/main/java/com/cherret/zaprett/data/UpdateInfo.kt
Normal file
16
app/src/main/java/com/cherret/zaprett/data/UpdateInfo.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.cherret.zaprett.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UpdateInfo(
|
||||
val version: String,
|
||||
val versionCode: Int,
|
||||
val downloadUrl: String,
|
||||
val changelogUrl: String
|
||||
)
|
||||
|
||||
data class UpdateData(
|
||||
val updateInfo: UpdateInfo,
|
||||
val changelog: String
|
||||
)
|
||||
@@ -51,6 +51,7 @@ import androidx.compose.ui.unit.sp
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.data.DependencyUI
|
||||
import com.cherret.zaprett.data.RepoItemUI
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.data.StrategyCheckResult
|
||||
import com.cherret.zaprett.data.StrategyTestingStatus
|
||||
import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel
|
||||
@@ -60,7 +61,7 @@ import com.cherret.zaprett.utils.getActiveStrategy
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
|
||||
fun ListSwitchItem(item: StorageData, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
|
||||
ElevatedCard(
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
|
||||
@@ -74,7 +75,14 @@ fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(text = item, modifier = Modifier.weight(1f))
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(text = item.name)
|
||||
Text(text = stringResource(R.string.title_author, item.author))
|
||||
Text(text = item.description)
|
||||
}
|
||||
Switch(
|
||||
checked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
@@ -255,7 +263,9 @@ fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferen
|
||||
)
|
||||
FilledTonalIconButton(
|
||||
onClick = {
|
||||
if (getActiveStrategy(prefs).isNotEmpty()) disableStrategy(getActiveStrategy(prefs)[0], prefs)
|
||||
getActiveStrategy(prefs).getOrNull()?.file
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let { disableStrategy(it, prefs) }
|
||||
enableStrategy(strategy.path, prefs)
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.ContextCompat
|
||||
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
|
||||
@@ -30,11 +31,11 @@ import java.io.IOException
|
||||
|
||||
abstract class BaseListsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val context = application
|
||||
var allItems by mutableStateOf<List<String>>(emptyList())
|
||||
var allItems by mutableStateOf<List<StorageData>>(emptyList())
|
||||
private set
|
||||
var activeItems by mutableStateOf<List<String>>(emptyList())
|
||||
var activeItems by mutableStateOf<List<StorageData>>(emptyList())
|
||||
private set
|
||||
val checked = mutableStateMapOf<String, Boolean>()
|
||||
val checked = mutableStateMapOf<StorageData, Boolean>()
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
@@ -44,10 +45,10 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
|
||||
private var _showNoPermissionDialog = MutableStateFlow(false)
|
||||
val showNoPermissionDialog: StateFlow<Boolean> = _showNoPermissionDialog
|
||||
|
||||
abstract fun loadAllItems(): Array<String>
|
||||
abstract fun loadActiveItems(): Array<String>
|
||||
abstract fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
|
||||
abstract fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
|
||||
abstract fun loadAllItems(): Array<StorageData>
|
||||
abstract fun loadActiveItems(): Array<StorageData>
|
||||
abstract fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
|
||||
abstract fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
|
||||
|
||||
fun refresh() {
|
||||
when (checkStoragePermission(context)) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.cherret.zaprett.R
|
||||
@@ -20,18 +19,18 @@ import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.data.RepoItemFull
|
||||
import com.cherret.zaprett.data.RepoItemUI
|
||||
import com.cherret.zaprett.data.RepoManifest
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.RepoTab
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.DownloadUtils.download
|
||||
import com.cherret.zaprett.utils.DownloadUtils.getFileSha256
|
||||
import com.cherret.zaprett.utils.DownloadUtils.registerDownloadListener
|
||||
import com.cherret.zaprett.utils.NetworkUtils.getRepo
|
||||
import com.cherret.zaprett.utils.NetworkUtils.resolveDependencies
|
||||
import com.cherret.zaprett.utils.checkStoragePermission
|
||||
import com.cherret.zaprett.utils.download
|
||||
import com.cherret.zaprett.utils.getFileSha256
|
||||
import com.cherret.zaprett.utils.getHostListMode
|
||||
import com.cherret.zaprett.utils.getRepo
|
||||
import com.cherret.zaprett.utils.getServiceType
|
||||
import com.cherret.zaprett.utils.getZaprettPath
|
||||
import com.cherret.zaprett.utils.registerDownloadListener
|
||||
import com.cherret.zaprett.utils.resolveDependencies
|
||||
import com.cherret.zaprett.utils.restartService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -78,7 +77,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
||||
val isInstalling = mutableStateMapOf<String, Boolean>()
|
||||
val isUpdateInstalling = mutableStateMapOf<String, Boolean>()
|
||||
|
||||
abstract fun getInstalledLists(): Array<String>
|
||||
abstract fun getInstalledLists(): Array<StorageData>
|
||||
abstract val repoTab: RepoTab
|
||||
val repoUrl = sharedPreferences.getString("repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/index.json") ?: "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/index.json"
|
||||
private val Json = Json {
|
||||
@@ -156,7 +155,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
||||
}
|
||||
|
||||
fun isItemInstalled(item: RepoItemUI): Boolean {
|
||||
return getInstalledLists().any { File(it).name == item.name }
|
||||
return getInstalledLists().any { it.id == item.id }
|
||||
}
|
||||
|
||||
fun install(item: RepoItemUI) {
|
||||
@@ -204,6 +203,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
||||
}
|
||||
|
||||
val targetFile = baseDir
|
||||
.resolve("files")
|
||||
.resolve(targetDirSuffix)
|
||||
.resolve(uri.lastPathSegment!!
|
||||
.replace(Regex("""-\d+(?=\.|$)"""), ""))
|
||||
@@ -220,6 +220,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
|
||||
item.schema,
|
||||
item.id,
|
||||
item.name,
|
||||
item.version,
|
||||
item.author,
|
||||
item.description,
|
||||
item.dependencies,
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import com.cherret.zaprett.data.RepoTab
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.getAllBin
|
||||
|
||||
class BinRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||
override fun getInstalledLists(): Array<String> = emptyArray()
|
||||
override fun getInstalledLists(): Array<StorageData> = getAllBin()
|
||||
override val repoTab = RepoTab.bins
|
||||
}
|
||||
@@ -20,17 +20,16 @@ import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.data.ServiceStatus
|
||||
import com.cherret.zaprett.data.ServiceStatusUI
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.utils.download
|
||||
import com.cherret.zaprett.utils.DownloadUtils.download
|
||||
import com.cherret.zaprett.utils.DownloadUtils.installApk
|
||||
import com.cherret.zaprett.utils.DownloadUtils.registerDownloadListener
|
||||
import com.cherret.zaprett.utils.NetworkUtils.getUpdate
|
||||
import com.cherret.zaprett.utils.getActiveStrategy
|
||||
import com.cherret.zaprett.utils.getChangelog
|
||||
import com.cherret.zaprett.utils.getModuleVersion
|
||||
import com.cherret.zaprett.utils.getNfqws2Version
|
||||
import com.cherret.zaprett.utils.getNfqwsVersion
|
||||
import com.cherret.zaprett.utils.getServiceType
|
||||
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
|
||||
@@ -79,16 +78,18 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
var showUpdateDialog = mutableStateOf(false)
|
||||
|
||||
fun checkForUpdate() {
|
||||
suspend fun checkForUpdate() {
|
||||
if (prefs.getBoolean("auto_update", BuildConfig.auto_update)) {
|
||||
getUpdate(prefs) {
|
||||
if (it != null) {
|
||||
downloadUrl.value = it.downloadUrl.toString()
|
||||
getChangelog(it.changelogUrl.toString()) { log -> changeLog.value = log }
|
||||
newVersion.value = it.version
|
||||
getUpdate(prefs)
|
||||
.onSuccess { updateData ->
|
||||
downloadUrl.value = updateData.updateInfo.downloadUrl
|
||||
changeLog.value = updateData.changelog
|
||||
newVersion.value = updateData.updateInfo.version
|
||||
updateAvailable.value = true
|
||||
}
|
||||
}
|
||||
.onFailure { exception ->
|
||||
_errorFlow.value = exception.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
} else {
|
||||
if (ByeDpiVpnService.status == ServiceStatus.Disconnected || ByeDpiVpnService.status == ServiceStatus.Failed) {
|
||||
if (getActiveStrategy(prefs).isNotEmpty()) {
|
||||
if (getActiveStrategy(prefs).isSuccess) {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_starting_service))
|
||||
}
|
||||
@@ -255,7 +256,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||
installApk(context, uri)
|
||||
},
|
||||
onError = {
|
||||
|
||||
_errorFlow.value = it
|
||||
})
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
import android.app.Application
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.data.RepoTab
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.getAllExcludeLists
|
||||
import com.cherret.zaprett.utils.getAllLists
|
||||
import com.cherret.zaprett.utils.getHostListMode
|
||||
|
||||
class HostRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||
override fun getInstalledLists(): Array<String> =
|
||||
override fun getInstalledLists(): Array<StorageData> =
|
||||
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllLists() else getAllExcludeLists()
|
||||
override val repoTab = RepoTab.lists
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.content.Context
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.disableList
|
||||
import com.cherret.zaprett.utils.enableList
|
||||
import com.cherret.zaprett.utils.getActiveExcludeLists
|
||||
@@ -20,18 +21,19 @@ import java.io.File
|
||||
|
||||
class HostsViewModel(application: Application): BaseListsViewModel(application) {
|
||||
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
override fun loadAllItems(): Array<String> =
|
||||
override fun loadAllItems(): Array<StorageData> =
|
||||
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllLists()
|
||||
else getAllExcludeLists()
|
||||
override fun loadActiveItems(): Array<String> =
|
||||
override fun loadActiveItems(): Array<StorageData> =
|
||||
if (getHostListMode(sharedPreferences) == ListType.whitelist) getActiveLists(sharedPreferences)
|
||||
else getActiveExcludeLists(sharedPreferences)
|
||||
|
||||
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
override fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
val wasChecked = checked[item] == true
|
||||
disableList(item, sharedPreferences)
|
||||
val success = File(item).delete()
|
||||
if (success) refresh()
|
||||
disableList(item.manifestPath, sharedPreferences)
|
||||
val successArtifact = File(item.file).delete()
|
||||
val successManifest = File(item.manifestPath).delete()
|
||||
if (successArtifact && successManifest) refresh()
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getStatus { isEnabled ->
|
||||
if (isEnabled && wasChecked) {
|
||||
@@ -42,9 +44,9 @@ class HostsViewModel(application: Application): BaseListsViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
checked[item] = isChecked
|
||||
if (isChecked) enableList(item, sharedPreferences) else disableList(item, sharedPreferences)
|
||||
if (isChecked) enableList(item.manifestPath, sharedPreferences) else disableList(item.manifestPath, sharedPreferences)
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getStatus { isEnabled ->
|
||||
if (isEnabled) {
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
import android.app.Application
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.data.RepoTab
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.getAllExcludeIpsets
|
||||
import com.cherret.zaprett.utils.getAllIpsets
|
||||
import com.cherret.zaprett.utils.getHostListMode
|
||||
|
||||
class IpsetRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||
override fun getInstalledLists(): Array<String> =
|
||||
override fun getInstalledLists(): Array<StorageData> =
|
||||
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllIpsets() else getAllExcludeIpsets()
|
||||
override val repoTab = RepoTab.ipsets
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.content.Context
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.disableIpset
|
||||
import com.cherret.zaprett.utils.enableIpset
|
||||
import com.cherret.zaprett.utils.getActiveExcludeIpsets
|
||||
@@ -20,18 +21,19 @@ import java.io.File
|
||||
|
||||
class IpsetViewModel(application: Application): BaseListsViewModel(application) {
|
||||
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
override fun loadAllItems(): Array<String> =
|
||||
override fun loadAllItems(): Array<StorageData> =
|
||||
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllIpsets()
|
||||
else getAllExcludeIpsets()
|
||||
override fun loadActiveItems(): Array<String> =
|
||||
override fun loadActiveItems(): Array<StorageData> =
|
||||
if (getHostListMode(sharedPreferences) == ListType.whitelist) getActiveIpsets(sharedPreferences)
|
||||
else getActiveExcludeIpsets(sharedPreferences)
|
||||
|
||||
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
override fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
val wasChecked = checked[item] == true
|
||||
disableIpset(item, sharedPreferences)
|
||||
val success = File(item).delete()
|
||||
if (success) refresh()
|
||||
disableIpset(item.manifestPath, sharedPreferences)
|
||||
val successArtifact = File(item.file).delete()
|
||||
val successManifest = File(item.manifestPath).delete()
|
||||
if (successArtifact && successManifest) refresh()
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getStatus { isEnabled ->
|
||||
if (isEnabled && wasChecked) {
|
||||
@@ -42,9 +44,9 @@ class IpsetViewModel(application: Application): BaseListsViewModel(application)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
checked[item] = isChecked
|
||||
if (isChecked) enableIpset(item, sharedPreferences) else disableIpset(item, sharedPreferences)
|
||||
if (isChecked) enableIpset(item.manifestPath, sharedPreferences) else disableIpset(item.manifestPath, sharedPreferences)
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getStatus { isEnabled ->
|
||||
if (isEnabled) {
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.cherret.zaprett.ui.viewmodel
|
||||
import android.app.Application
|
||||
import com.cherret.zaprett.data.RepoTab
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.getAllByeDPIStrategies
|
||||
import com.cherret.zaprett.utils.getAllNfqwsStrategies
|
||||
import com.cherret.zaprett.utils.getServiceType
|
||||
|
||||
class StrategyRepoViewModel(application: Application): BaseRepoViewModel(application) {
|
||||
override fun getInstalledLists(): Array<String> =
|
||||
override fun getInstalledLists(): Array<StorageData> =
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getAllNfqwsStrategies()
|
||||
} else {
|
||||
|
||||
@@ -70,9 +70,9 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
|
||||
fun loadStrategies() {
|
||||
val strategyList = getAllStrategies(prefs)
|
||||
strategyStates.clear()
|
||||
strategyList.forEach { name ->
|
||||
strategyList.forEach { manifest ->
|
||||
strategyStates += StrategyCheckResult(
|
||||
path = name,
|
||||
path = manifest.name,
|
||||
status = StrategyTestingStatus.Waiting,
|
||||
progress = 0f,
|
||||
domains = emptyList()
|
||||
@@ -108,7 +108,7 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
|
||||
val result = mutableListOf<String>()
|
||||
getActiveLists(prefs).forEach { path ->
|
||||
runCatching {
|
||||
File(path).useLines { lines ->
|
||||
File(path.file).useLines { lines ->
|
||||
lines.forEach { line ->
|
||||
result += line
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.material3.SnackbarHostState
|
||||
import com.cherret.zaprett.byedpi.ByeDpiVpnService
|
||||
import com.cherret.zaprett.data.ServiceStatus
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.utils.disableStrategy
|
||||
import com.cherret.zaprett.utils.enableStrategy
|
||||
import com.cherret.zaprett.utils.getActiveByeDPIStrategy
|
||||
@@ -19,6 +20,7 @@ import com.cherret.zaprett.utils.getServiceType
|
||||
import com.cherret.zaprett.utils.getStatus
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.io.File
|
||||
import kotlin.emptyArray
|
||||
|
||||
class StrategyViewModel(application: Application): BaseListsViewModel(application) {
|
||||
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
@@ -28,14 +30,20 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
|
||||
ServiceType.nfqws2 -> Nfqws2StrategyProvider()
|
||||
ServiceType.byedpi -> ByeDPIStrategyProvider(sharedPreferences)
|
||||
}
|
||||
override fun loadAllItems(): Array<String> = strategyProvider.getAll()
|
||||
override fun loadActiveItems(): Array<String> = strategyProvider.getActive()
|
||||
override fun loadAllItems(): Array<StorageData> = strategyProvider.getAll()
|
||||
// костыль, желательно переделать
|
||||
override fun loadActiveItems(): Array<StorageData> {
|
||||
val activeItem = strategyProvider.getActive().getOrNull()
|
||||
return if (activeItem != null) arrayOf(activeItem)
|
||||
else emptyArray()
|
||||
}
|
||||
|
||||
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
override fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
val wasChecked = checked[item] == true
|
||||
disableStrategy(item, sharedPreferences)
|
||||
val success = File(item).delete()
|
||||
if (success) refresh()
|
||||
disableStrategy(item.manifestPath, sharedPreferences)
|
||||
val successArtifact = File(item.file).delete()
|
||||
val successManifest = File(item.manifestPath).delete()
|
||||
if (successArtifact && successManifest) refresh()
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getStatus { isEnabled ->
|
||||
if (isEnabled && wasChecked) {
|
||||
@@ -51,19 +59,19 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
checked[item] = isChecked
|
||||
if (isChecked) {
|
||||
checked.keys.forEach { key ->
|
||||
checked[key] = false
|
||||
disableStrategy(key, sharedPreferences)
|
||||
disableStrategy(key.manifestPath, sharedPreferences)
|
||||
}
|
||||
checked[item] = true
|
||||
enableStrategy(item, sharedPreferences)
|
||||
enableStrategy(item.manifestPath, sharedPreferences)
|
||||
}
|
||||
else {
|
||||
checked[item] = false
|
||||
disableStrategy(item, sharedPreferences)
|
||||
disableStrategy(item.manifestPath, sharedPreferences)
|
||||
}
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
getStatus { isEnabled ->
|
||||
@@ -81,8 +89,8 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
|
||||
}
|
||||
|
||||
interface StrategyProvider {
|
||||
fun getAll(): Array<String>
|
||||
fun getActive(): Array<String>
|
||||
fun getAll(): Array<StorageData>
|
||||
fun getActive(): Result<StorageData>
|
||||
}
|
||||
|
||||
class NfqwsStrategyProvider : StrategyProvider {
|
||||
|
||||
106
app/src/main/java/com/cherret/zaprett/utils/DownloadUtils.kt
Normal file
106
app/src/main/java/com/cherret/zaprett/utils/DownloadUtils.kt
Normal file
@@ -0,0 +1,106 @@
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
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 androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
|
||||
object DownloadUtils {
|
||||
fun registerDownloadListener(context: Context, downloadId: Long, onDownloaded: (Uri) -> Unit, onError: (String) -> Unit) {// AI Generated
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
@SuppressLint("Range")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action != DownloadManager.ACTION_DOWNLOAD_COMPLETE) return
|
||||
if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) != downloadId) return
|
||||
val dm = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager ?: return
|
||||
val query = DownloadManager.Query().setFilterById(downloadId)
|
||||
dm.query(query)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||
val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
|
||||
when (status) {
|
||||
DownloadManager.STATUS_SUCCESSFUL -> {
|
||||
val uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
|
||||
if (uriString != null) {
|
||||
val uri = uriString.toUri()
|
||||
context.unregisterReceiver(this)
|
||||
onDownloaded(uri)
|
||||
}
|
||||
}
|
||||
|
||||
DownloadManager.STATUS_FAILED -> {
|
||||
context.unregisterReceiver(this)
|
||||
val errorMessage = when (reason) {
|
||||
DownloadManager.ERROR_CANNOT_RESUME -> "Cannot resume download"
|
||||
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "Device not found"
|
||||
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "File already exists"
|
||||
DownloadManager.ERROR_FILE_ERROR -> "File error"
|
||||
DownloadManager.ERROR_HTTP_DATA_ERROR -> "HTTP data error"
|
||||
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "Insufficient space"
|
||||
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "Too many redirects"
|
||||
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "Unhandled HTTP code"
|
||||
DownloadManager.ERROR_UNKNOWN -> "Unknown error"
|
||||
else -> "Download failed: reason=$reason"
|
||||
}
|
||||
onError(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
ContextCompat.registerReceiver(context, receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), ContextCompat.RECEIVER_EXPORTED)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileSha256(file: File): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
file.inputStream().use { input ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (input.read(buffer).also { bytesRead = it } != -1) {
|
||||
digest.update(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
return digest.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
fun download(context: Context, url: String): Long {
|
||||
val fileName = url.substringAfterLast("/")
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(url.toUri()).apply {
|
||||
setTitle(fileName)
|
||||
setDescription(fileName)
|
||||
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
||||
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
|
||||
}
|
||||
return downloadManager.enqueue(request)
|
||||
}
|
||||
|
||||
fun installApk(context: Context, uri: Uri) {
|
||||
val file = File(uri.path!!)
|
||||
val apkUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
|
||||
if (context.packageManager.canRequestPackageInstalls()) {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
app/src/main/java/com/cherret/zaprett/utils/NetworkUtils.kt
Normal file
93
app/src/main/java/com/cherret/zaprett/utils/NetworkUtils.kt
Normal file
@@ -0,0 +1,93 @@
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.cherret.zaprett.data.DependencyEntry
|
||||
import com.cherret.zaprett.data.RepoIndex
|
||||
import com.cherret.zaprett.data.RepoIndexItem
|
||||
import com.cherret.zaprett.data.RepoItemFull
|
||||
import com.cherret.zaprett.data.RepoManifest
|
||||
import com.cherret.zaprett.data.ResolveResult
|
||||
import com.cherret.zaprett.data.UpdateData
|
||||
import com.cherret.zaprett.data.UpdateInfo
|
||||
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.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.collections.forEach
|
||||
|
||||
object NetworkUtils {
|
||||
private val client = HttpClient(OkHttp)
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
fun getRepo(url: String, filter: (RepoIndexItem) -> Boolean): Flow<List<RepoItemFull>> = flow {
|
||||
val index = client.get(url).bodyAsText()
|
||||
val indexJson = json.decodeFromString<RepoIndex>(index)
|
||||
val filtered = indexJson.items.filter(filter)
|
||||
val semaphore = Semaphore(15)
|
||||
val manifest = coroutineScope {
|
||||
filtered.map { item ->
|
||||
async {
|
||||
semaphore.withPermit {
|
||||
val manifest =
|
||||
json.decodeFromString<RepoManifest>(client.get(item.manifest).bodyAsText())
|
||||
RepoItemFull(item, manifest)
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
emit(manifest)
|
||||
}
|
||||
|
||||
fun resolveDependencies(items: List<RepoItemFull>): Flow<ResolveResult> = flow {
|
||||
val resolved = mutableSetOf<String>()
|
||||
val depsMap = mutableMapOf<String, DependencyEntry>()
|
||||
val manifestCache = mutableMapOf<String, RepoManifest>()
|
||||
suspend fun collect(manifest: RepoManifest, rootName: String) {
|
||||
manifest.dependencies.forEach { depUrl ->
|
||||
val dep = manifestCache.getOrPut(depUrl) {
|
||||
json.decodeFromString<RepoManifest>(
|
||||
client.get(depUrl).bodyAsText()
|
||||
)
|
||||
}
|
||||
val entry = depsMap.getOrPut(dep.name) {
|
||||
DependencyEntry(dep)
|
||||
}
|
||||
entry.dependencies += rootName
|
||||
if (resolved.add(dep.name)) {
|
||||
collect(dep, dep.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
items.forEach { item ->
|
||||
collect(item.manifest, item.index.id)
|
||||
}
|
||||
emit(
|
||||
ResolveResult(
|
||||
roots = items,
|
||||
dependencies = depsMap.values.toList()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getUpdate(sharedPreferences: SharedPreferences): Result<UpdateData> {
|
||||
val url = sharedPreferences.getString("update_repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json")?: "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json"
|
||||
return runCatching {
|
||||
val update = client.get(url).bodyAsText()
|
||||
val updateInfo = json.decodeFromString<UpdateInfo>(update)
|
||||
val changeLog = client.get(updateInfo.changelogUrl).bodyAsText()
|
||||
UpdateData(
|
||||
updateInfo,
|
||||
changeLog
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.DownloadManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import com.cherret.zaprett.data.DependencyEntry
|
||||
import com.cherret.zaprett.data.RepoIndex
|
||||
import com.cherret.zaprett.data.RepoIndexItem
|
||||
import com.cherret.zaprett.data.RepoItemFull
|
||||
import com.cherret.zaprett.data.RepoManifest
|
||||
import com.cherret.zaprett.data.ResolveResult
|
||||
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.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
|
||||
private val client = HttpClient(OkHttp)
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
fun getRepo(url: String, filter: (RepoIndexItem) -> Boolean): Flow<List<RepoItemFull>> = flow {
|
||||
val index = client.get(url).bodyAsText()
|
||||
val indexJson = json.decodeFromString<RepoIndex>(index)
|
||||
val filtered = indexJson.items.filter(filter)
|
||||
val manifest = coroutineScope {
|
||||
filtered.map { item ->
|
||||
async {
|
||||
Semaphore(15).withPermit {
|
||||
val manifest =
|
||||
json.decodeFromString<RepoManifest>(client.get(item.manifest).bodyAsText())
|
||||
RepoItemFull(item, manifest)
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
emit(manifest)
|
||||
}
|
||||
|
||||
fun resolveDependencies(items: List<RepoItemFull>): Flow<ResolveResult> = flow {
|
||||
val resolved = mutableSetOf<String>()
|
||||
val depsMap = mutableMapOf<String, DependencyEntry>()
|
||||
val manifestCache = mutableMapOf<String, RepoManifest>()
|
||||
suspend fun collect(manifest: RepoManifest, rootName: String) {
|
||||
manifest.dependencies.forEach { depUrl ->
|
||||
val dep = manifestCache.getOrPut(depUrl) {
|
||||
json.decodeFromString<RepoManifest>(
|
||||
client.get(depUrl).bodyAsText()
|
||||
)
|
||||
}
|
||||
val entry = depsMap.getOrPut(dep.name) {
|
||||
DependencyEntry(dep)
|
||||
}
|
||||
entry.dependencies += rootName
|
||||
if (resolved.add(dep.name)) {
|
||||
collect(dep, dep.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
items.forEach { item ->
|
||||
collect(item.manifest, item.index.id)
|
||||
}
|
||||
emit(
|
||||
ResolveResult(
|
||||
roots = items,
|
||||
dependencies = depsMap.values.toList()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun registerDownloadListener(context: Context, downloadId: Long, onDownloaded: (Uri) -> Unit, onError: (String) -> Unit) {// AI Generated
|
||||
val receiver = object : BroadcastReceiver() {
|
||||
@SuppressLint("Range")
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action != DownloadManager.ACTION_DOWNLOAD_COMPLETE) return
|
||||
if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) != downloadId) return
|
||||
val dm = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager ?: return
|
||||
val query = DownloadManager.Query().setFilterById(downloadId)
|
||||
dm.query(query)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||
val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
|
||||
when (status) {
|
||||
DownloadManager.STATUS_SUCCESSFUL -> {
|
||||
val uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
|
||||
if (uriString != null) {
|
||||
val uri = uriString.toUri()
|
||||
context.unregisterReceiver(this)
|
||||
onDownloaded(uri)
|
||||
}
|
||||
}
|
||||
|
||||
DownloadManager.STATUS_FAILED -> {
|
||||
context.unregisterReceiver(this)
|
||||
val errorMessage = when (reason) {
|
||||
DownloadManager.ERROR_CANNOT_RESUME -> "Cannot resume download"
|
||||
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "Device not found"
|
||||
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "File already exists"
|
||||
DownloadManager.ERROR_FILE_ERROR -> "File error"
|
||||
DownloadManager.ERROR_HTTP_DATA_ERROR -> "HTTP data error"
|
||||
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "Insufficient space"
|
||||
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "Too many redirects"
|
||||
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "Unhandled HTTP code"
|
||||
DownloadManager.ERROR_UNKNOWN -> "Unknown error"
|
||||
else -> "Download failed: reason=$reason"
|
||||
}
|
||||
onError(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
|
||||
} else {
|
||||
ContextCompat.registerReceiver(context, receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), ContextCompat.RECEIVER_EXPORTED)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFileSha256(file: File): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
file.inputStream().use { input ->
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
while (input.read(buffer).also { bytesRead = it } != -1) {
|
||||
digest.update(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
return digest.digest().joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package com.cherret.zaprett.utils
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
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
|
||||
import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
import java.io.File
|
||||
|
||||
private val client = OkHttpClient()
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
// PLS EGOR-WHITE REFACTOR THIS
|
||||
fun getUpdate(sharedPreferences: SharedPreferences, callback: (UpdateInfo?) -> Unit) {
|
||||
val request = Request.Builder().url(sharedPreferences.getString("update_repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json")?: "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json").build()
|
||||
client.newCall(request).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
e.printStackTrace()
|
||||
callback(null)
|
||||
}
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
response.use {
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException()
|
||||
callback(null)
|
||||
}
|
||||
val jsonString = response.body.string()
|
||||
val updateInfo = json.decodeFromString<UpdateInfo>(jsonString)
|
||||
updateInfo?.versionCode?.let { versionCode ->
|
||||
if (versionCode > BuildConfig.VERSION_CODE)
|
||||
callback(updateInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun getChangelog(changelogUrl: String, callback: (String?) -> Unit) {
|
||||
val request = Request.Builder().url(changelogUrl).build()
|
||||
client.newCall(request).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
e.printStackTrace()
|
||||
callback(null)
|
||||
}
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
response.use {
|
||||
if (!response.isSuccessful) {
|
||||
callback(null)
|
||||
return
|
||||
}
|
||||
val changelogText = response.body.string()
|
||||
callback(changelogText)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun download(context: Context, url: String): Long {
|
||||
val fileName = url.substringAfterLast("/")
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(url.toUri()).apply {
|
||||
setTitle(fileName)
|
||||
setDescription(fileName)
|
||||
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
|
||||
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
|
||||
}
|
||||
return downloadManager.enqueue(request)
|
||||
}
|
||||
|
||||
fun installApk(context: Context, uri: Uri) {
|
||||
val file = File(uri.path!!)
|
||||
val apkUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
|
||||
if (context.packageManager.canRequestPackageInstalls()) {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(apkUri, "application/vnd.android.package-archive")
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@Serializable
|
||||
data class UpdateInfo(
|
||||
val version: String?,
|
||||
val versionCode: Int?,
|
||||
val downloadUrl: String?,
|
||||
val changelogUrl: String?
|
||||
)
|
||||
@@ -12,9 +12,13 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import com.cherret.zaprett.data.AppListType
|
||||
import com.cherret.zaprett.data.ListType
|
||||
import com.cherret.zaprett.data.RepoManifest
|
||||
import com.cherret.zaprett.data.ServiceType
|
||||
import com.cherret.zaprett.data.StorageData
|
||||
import com.cherret.zaprett.data.ZaprettConfig
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import io.ktor.client.plugins.cache.storage.FileStorage
|
||||
import kotlinx.io.files.FileNotFoundException
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
@@ -124,7 +128,7 @@ fun getNfqws2Version(callback: (String) -> Unit) {
|
||||
}
|
||||
|
||||
fun getConfigFile(): File {
|
||||
return getZaprettPath().resolve("/config.json")
|
||||
return getZaprettPath().resolve("config.json")
|
||||
}
|
||||
|
||||
fun setStartOnBoot(prefs: SharedPreferences, callback: (Boolean) -> Unit) {
|
||||
@@ -149,63 +153,57 @@ fun getZaprettPath(): File {
|
||||
return Environment.getExternalStorageDirectory().resolve("zaprett")
|
||||
}
|
||||
|
||||
fun getAllLists(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("lists/include")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
fun getManifestsPath(): File {
|
||||
return getZaprettPath().resolve("manifests")
|
||||
}
|
||||
fun getValidManifests(listsDir: File): Array<StorageData> {
|
||||
return (listsDir.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
if (!file.isFile || file.extension.lowercase() != "json") return@mapNotNull null
|
||||
parseManifestFromFile(file).getOrNull()?.takeIf {
|
||||
File(it.file).exists()
|
||||
}
|
||||
}
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
?: emptyArray())
|
||||
}
|
||||
|
||||
fun getAllIpsets(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("ipset/include")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
fun getAllLists(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("lists/include")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllExcludeLists(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("lists/include")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
fun getAllIpsets(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("ipset/include")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllExcludeIpsets(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("ipset/exclude/")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
fun getAllExcludeLists(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("lists/exclude")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllNfqwsStrategies(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("strategies/nfqws")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
fun getAllExcludeIpsets(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("ipset/exclude")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllNfqws2Strategies(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("strategies/nfqws2")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
fun getAllNfqwsStrategies(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("strategies/nfqws")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllByeDPIStrategies(): Array<String> {
|
||||
val listsDir = getZaprettPath().resolve("strategies/byedpi")
|
||||
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
|
||||
?.map { it.absolutePath }
|
||||
?.toTypedArray()
|
||||
?: emptyArray()
|
||||
fun getAllNfqws2Strategies(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("strategies/nfqws2")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllStrategies(sharedPreferences: SharedPreferences): Array<String> {
|
||||
fun getAllByeDPIStrategies(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("strategies/byedpi")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getAllStrategies(sharedPreferences: SharedPreferences): Array<StorageData> {
|
||||
return when (getServiceType(sharedPreferences)) {
|
||||
ServiceType.nfqws -> getAllNfqwsStrategies()
|
||||
ServiceType.nfqws2 -> getAllNfqws2Strategies()
|
||||
@@ -213,11 +211,16 @@ fun getAllStrategies(sharedPreferences: SharedPreferences): Array<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fun getActiveLists(sharedPreferences: SharedPreferences): Array<String> {
|
||||
fun getAllBin(): Array<StorageData> {
|
||||
val listsDir = getManifestsPath().resolve("bin")
|
||||
return getValidManifests(listsDir)
|
||||
}
|
||||
|
||||
fun getActiveLists(sharedPreferences: SharedPreferences): Array<StorageData> {
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
return readConfig().activeLists.toTypedArray()
|
||||
return readConfig().activeLists.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
} else {
|
||||
return sharedPreferences.getStringSet("lists", emptySet())?.toTypedArray() ?: emptyArray()
|
||||
return sharedPreferences.getStringSet("lists", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,55 +245,59 @@ fun setServiceType(sharedPreferences: SharedPreferences, serviceType: ServiceTyp
|
||||
}
|
||||
}
|
||||
|
||||
fun getActiveIpsets(sharedPreferences: SharedPreferences): Array<String> {
|
||||
fun getActiveIpsets(sharedPreferences: SharedPreferences): Array<StorageData> {
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
return readConfig().activeIpsets.toTypedArray()
|
||||
} else return sharedPreferences.getStringSet("ipsets", emptySet())?.toTypedArray() ?: emptyArray()
|
||||
return readConfig().activeIpsets.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
} else return sharedPreferences.getStringSet("ipsets", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
}
|
||||
|
||||
fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<String> {
|
||||
fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<StorageData> {
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
return readConfig().activeExcludeLists.toTypedArray()
|
||||
return readConfig().activeExcludeLists.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
} else {
|
||||
return sharedPreferences.getStringSet("exclude_lists", emptySet())?.toTypedArray() ?: emptyArray()
|
||||
return sharedPreferences.getStringSet("exclude_lists", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
fun getActiveExcludeIpsets(sharedPreferences: SharedPreferences): Array<String> {
|
||||
fun getActiveExcludeIpsets(sharedPreferences: SharedPreferences): Array<StorageData> {
|
||||
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
|
||||
return readConfig().activeExcludeIpsets.toTypedArray()
|
||||
} else return sharedPreferences.getStringSet("exclude_ipsets", emptySet())?.toTypedArray() ?: emptyArray()
|
||||
return readConfig().activeExcludeIpsets.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
} else return sharedPreferences.getStringSet("exclude_ipsets", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
|
||||
}
|
||||
|
||||
fun getActiveNfqwsStrategy(): Array<String> {
|
||||
val strategy = readConfig().strategy
|
||||
return if (strategy.isNotBlank()) arrayOf(strategy) else emptyArray()
|
||||
fun getActiveNfqwsStrategy(): Result<StorageData> {
|
||||
return parseManifestFromFile(File(readConfig().strategy))
|
||||
}
|
||||
|
||||
fun getActiveNfqws2Strategy(): Array<String> {
|
||||
val strategy = readConfig().strategyNfqws2
|
||||
return if (strategy.isNotBlank()) arrayOf(strategy) else emptyArray()
|
||||
fun getActiveNfqws2Strategy(): Result<StorageData> {
|
||||
return parseManifestFromFile(File(readConfig().strategyNfqws2))
|
||||
}
|
||||
|
||||
fun getActiveByeDPIStrategy(sharedPreferences: SharedPreferences): Array<String> {
|
||||
val path = sharedPreferences.getString("active_strategy", "")
|
||||
if (!path.isNullOrBlank() && File(path).exists()) {
|
||||
return arrayOf(path)
|
||||
}
|
||||
return emptyArray()
|
||||
fun getActiveByeDPIStrategy(sharedPreferences: SharedPreferences): Result<StorageData> {
|
||||
val path = sharedPreferences.getString("active_strategy", "")?: ""
|
||||
// if (!path.isNullOrBlank() && File(path).exists()) {
|
||||
// return arrayOf(path)
|
||||
// }
|
||||
// return emptyArray()
|
||||
return parseManifestFromFile(File(path))
|
||||
|
||||
}
|
||||
|
||||
fun getActiveByeDPIStrategyContent(sharedPreferences: SharedPreferences): List<String> {
|
||||
val path = sharedPreferences.getString("active_strategy", "")
|
||||
if (!path.isNullOrBlank() && File(path).exists()) {
|
||||
return File(path).readLines()
|
||||
}
|
||||
return emptyList()
|
||||
val path = sharedPreferences.getString("active_strategy", "")?: ""
|
||||
if (path.isBlank()) return emptyList()
|
||||
val manifest = parseManifestFromFile(File(path)).getOrNull()
|
||||
val file = manifest?.file?.let { File(it) }
|
||||
return if (file?.exists() == true) file.readLines()
|
||||
else emptyList()
|
||||
}
|
||||
|
||||
fun getActiveStrategy(sharedPreferences: SharedPreferences): Array<String> {
|
||||
return if (getServiceType(sharedPreferences) != ServiceType.byedpi) getActiveNfqwsStrategy()
|
||||
else getActiveByeDPIStrategy(sharedPreferences)
|
||||
fun getActiveStrategy(sharedPreferences: SharedPreferences): Result<StorageData> {
|
||||
return when(getServiceType(sharedPreferences)) {
|
||||
ServiceType.byedpi -> getActiveByeDPIStrategy(sharedPreferences)
|
||||
ServiceType.nfqws -> getActiveNfqwsStrategy()
|
||||
ServiceType.nfqws2 -> getActiveNfqws2Strategy()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableList(path: String, sharedPreferences: SharedPreferences) {
|
||||
@@ -563,4 +570,13 @@ fun getHostListMode(prefs: SharedPreferences): ListType {
|
||||
} else {
|
||||
return runCatching { enumValueOf<ListType>(prefs.getString("list_type", ListType.whitelist.name) ?: ListType.whitelist.name) }.getOrDefault(ListType.whitelist)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseManifestFromFile(file: File): Result<StorageData> {
|
||||
return runCatching {
|
||||
if (!file.isFile || !file.canRead()) throw IllegalArgumentException("can't find manifest file")
|
||||
val manifest = Json.decodeFromString<StorageData>(file.readText())
|
||||
manifest.manifestPath = file.path
|
||||
manifest
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user