mirror of
https://github.com/CherretGit/zaprett-app.git
synced 2025-12-10 05:29:37 +05:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e5b96b562 | ||
|
|
acf579da89 | ||
|
|
180922699c | ||
|
|
3fdd68ca55 | ||
|
|
20bdcac931 | ||
|
|
76e7fb8310 | ||
|
|
4ed064ff9c | ||
|
|
b758decd6b | ||
|
|
4a74050ec6 | ||
|
|
d9aa02a436 | ||
|
|
18b3c0e3c6 | ||
|
|
1870edfb5c | ||
|
|
9b138d55ee | ||
|
|
03e1ea4f06 | ||
|
|
9e95ba9922 | ||
|
|
aac80da9a7 | ||
|
|
2b611ce8cd | ||
|
|
6967ee286c | ||
|
|
83737d5df9 | ||
|
|
a05c6af2c1 | ||
|
|
7b7f94f0e2 | ||
|
|
8367eac9bc | ||
|
|
e95792b8c9 | ||
|
|
7352943bb3 | ||
|
|
b4da6bda5a | ||
|
|
4ecc9a40d4 | ||
|
|
96dc70473e | ||
|
|
b05616d7ef | ||
|
|
316bdea986 | ||
|
|
de22bb048c | ||
|
|
56d3c95f07 | ||
|
|
df4e7f9658 | ||
|
|
3c44a449c3 | ||
|
|
e46b0e7d4f | ||
|
|
136340949d | ||
|
|
894899bfc9 |
66
.github/workflows/workflow.yml
vendored
Normal file
66
.github/workflows/workflow.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag for the release'
|
||||
required: true
|
||||
type: string
|
||||
release_name:
|
||||
description: 'Release Name'
|
||||
required: true
|
||||
type: string
|
||||
release_notes:
|
||||
description: 'Release Description'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v2
|
||||
|
||||
- name: Build APK
|
||||
run: ./gradlew assembleRelease
|
||||
|
||||
- name: Decode Keystore
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > keystore.jks
|
||||
|
||||
- name: Sign the APK
|
||||
run: |
|
||||
$ANDROID_HOME/build-tools/$(ls $ANDROID_HOME/build-tools | sort -V | tail -1)/apksigner sign \
|
||||
--ks keystore.jks \
|
||||
--ks-pass "pass:${{ secrets.KEY_STORE_PASSWORD }}" \
|
||||
--key-pass "pass:${{ secrets.KEY_PASSWORD }}" \
|
||||
--out app/build/outputs/apk/release/app-release.apk \
|
||||
app/build/outputs/apk/release/app-release-unsigned.apk
|
||||
|
||||
- name: Verify APK signature
|
||||
run: |
|
||||
$ANDROID_HOME/build-tools/$(ls $ANDROID_HOME/build-tools | sort -V | tail -1)/apksigner verify \
|
||||
app/build/outputs/apk/release/app-release.apk
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.tag }}
|
||||
name: ${{ github.event.inputs.release_name }}
|
||||
body: ${{ github.event.inputs.release_notes }}
|
||||
files: |
|
||||
app/build/outputs/apk/release/app-release.apk
|
||||
12
README.md
Normal file
12
README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# zaprett
|
||||
## О приложении
|
||||
Приложение разработано для работы с модулем [zaprett](https://github.com/egor-white/zaprett)
|
||||
### Данное приложение является ремейком [приложения](https://github.com/egor-white/zaprett-app) от [egor-white](https://github.com/egor-white)
|
||||
|
||||
На данный момент приложение умеет:
|
||||
* Включать, выключать и перезапускать модуль
|
||||
* Работа с листами (добавление, включение и выключение)
|
||||
* Авто обновление приложения
|
||||
|
||||
## Скриншоты:
|
||||
<img src="images/1.png" width="300"><img src="images/2.png" width="300"><img src="images/3.png" width="300">
|
||||
@@ -2,6 +2,8 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -12,8 +14,8 @@ android {
|
||||
applicationId = "com.cherret.zaprett"
|
||||
minSdk = 30
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
versionCode = 6
|
||||
versionName = "1.5"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -46,6 +48,11 @@ dependencies {
|
||||
implementation("androidx.navigation:navigation-compose:2.8.9")
|
||||
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
||||
implementation ("com.github.topjohnwu.libsu:core:6.0.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.14")
|
||||
implementation("com.squareup.moshi:moshi-kotlin:1.15.2")
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.12.0"))
|
||||
implementation("com.google.firebase:firebase-analytics")
|
||||
implementation("com.google.firebase:firebase-crashlytics")
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
|
||||
29
app/google-services.json
Normal file
29
app/google-services.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "1005804036856",
|
||||
"project_id": "zaprett-app",
|
||||
"storage_bucket": "zaprett-app.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1005804036856:android:e7db5546b8bb4daf91510d",
|
||||
"android_client_info": {
|
||||
"package_name": "com.cherret.zaprett"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyASt83pAMxMI4txNAXaHDpX1R9crfoZAMk"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
@@ -17,6 +20,15 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Zaprett"
|
||||
tools:targetApi="31">
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@@ -23,11 +23,14 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.edit
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
@@ -37,7 +40,9 @@ import com.cherret.zaprett.ui.screens.HomeScreen
|
||||
import com.cherret.zaprett.ui.screens.HostsScreen
|
||||
import com.cherret.zaprett.ui.screens.SettingsScreen
|
||||
import com.cherret.zaprett.ui.theme.ZaprettTheme
|
||||
import com.topjohnwu.superuser.Shell
|
||||
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: androidx.compose.ui.graphics.vector.ImageVector) {
|
||||
object home : Screen("home", R.string.title_home, Icons.Default.Home)
|
||||
@@ -46,20 +51,25 @@ sealed class Screen(val route: String, @StringRes val nameResId: Int, val icon:
|
||||
}
|
||||
val topLevelRoutes = listOf(Screen.home, Screen.hosts, Screen.settings)
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var firebaseAnalytics: FirebaseAnalytics
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE)
|
||||
Shell.setDefaultBuilder(Shell.Builder.create()
|
||||
.setTimeout(10))
|
||||
firebaseAnalytics = Firebase.analytics
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
ZaprettTheme {
|
||||
val sharedPreferences = remember { getSharedPreferences("settings", MODE_PRIVATE) }
|
||||
var showPermissionDialog by remember { mutableStateOf(!Environment.isExternalStorageManager()) }
|
||||
var showWelcomeDialog by remember { mutableStateOf(sharedPreferences.getBoolean("welcome_dialog", true)) }
|
||||
BottomBar()
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
permissionDialog()
|
||||
if (showPermissionDialog) {
|
||||
PermissionDialog { showPermissionDialog = false }
|
||||
}
|
||||
if (sharedPreferences.getBoolean("welcome_dialog", true)) {
|
||||
welcomeDialog()
|
||||
if (showWelcomeDialog) {
|
||||
WelcomeDialog {
|
||||
sharedPreferences.edit() { putBoolean("welcome_dialog", false) }
|
||||
showWelcomeDialog = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,69 +120,39 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun welcomeDialog() {
|
||||
val sharedPreferences =
|
||||
LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
val editor = sharedPreferences.edit()
|
||||
val openDialog = remember { mutableStateOf(true) }
|
||||
if (openDialog.value) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(R.string.app_name))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.text_welcome))
|
||||
},
|
||||
onDismissRequest = {
|
||||
editor.putBoolean("welcome_dialog", false).apply()
|
||||
openDialog.value = false
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
editor.putBoolean("welcome_dialog", false).apply()
|
||||
openDialog.value = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fun WelcomeDialog(onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
title = { Text(text = stringResource(R.string.app_name)) },
|
||||
text = { Text(text = stringResource(R.string.text_welcome)) },
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun permissionDialog() {
|
||||
val openDialog = remember { mutableStateOf(true) }
|
||||
if (openDialog.value) {
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = stringResource(R.string.error_no_storage_title))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.error_no_storage_message))
|
||||
},
|
||||
onDismissRequest = {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.fromParts("package", packageName, null)
|
||||
intent.setData(uri)
|
||||
startActivity(intent)
|
||||
openDialog.value = false
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.fromParts("package", packageName, null)
|
||||
intent.setData(uri)
|
||||
startActivity(intent)
|
||||
openDialog.value = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
fun PermissionDialog(onDismiss: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
AlertDialog(
|
||||
title = { Text(text = stringResource(R.string.error_no_storage_title)) },
|
||||
text = { Text(text = stringResource(R.string.error_no_storage_message)) },
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.fromParts("package", context.packageName, null)
|
||||
intent.data = uri
|
||||
context.startActivity(intent)
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.btn_continue))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,38 +9,40 @@ import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.Properties
|
||||
|
||||
fun checkRoot(): Boolean {
|
||||
val result = Shell.cmd("ls /").exec()
|
||||
Shell.getShell().close()
|
||||
return result.isSuccess
|
||||
fun checkRoot(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("ls /").submit { result ->
|
||||
callback(result.isSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkModuleInstallation(): Boolean {
|
||||
val result = Shell.cmd("zaprett").exec()
|
||||
Shell.getShell().close()
|
||||
return result.out.toString().contains("zaprett")
|
||||
fun checkModuleInstallation(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett").submit { result ->
|
||||
callback(result.out.toString().contains("zaprett"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getStatus(): Boolean {
|
||||
val result = Shell.cmd("zaprett status").exec()
|
||||
Shell.getShell().close()
|
||||
return result.out.toString().contains("working")
|
||||
fun getStatus(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett status").submit { result ->
|
||||
callback(result.out.toString().contains("working"))
|
||||
}
|
||||
}
|
||||
|
||||
fun startService() {
|
||||
Shell.cmd("zaprett start").exec()
|
||||
Shell.getShell().close()
|
||||
fun startService(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett start").submit { result ->
|
||||
callback(result.isSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopService() {
|
||||
Shell.cmd("zaprett stop").exec()
|
||||
Shell.getShell().close()
|
||||
fun stopService(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett stop").submit { result ->
|
||||
callback(result.isSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fun restartService() {
|
||||
Shell.cmd("zaprett restart").exec()
|
||||
Shell.getShell().close()
|
||||
fun restartService(callback: (Boolean) -> Unit) {
|
||||
Shell.cmd("zaprett restart").submit { result ->
|
||||
callback(result.isSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
fun getConfigFile(): File {
|
||||
|
||||
142
app/src/main/java/com/cherret/zaprett/Updater.kt
Normal file
142
app/src/main/java/com/cherret/zaprett/Updater.kt
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.cherret.zaprett
|
||||
|
||||
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 android.provider.Settings
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
import java.io.File
|
||||
|
||||
val client = OkHttpClient()
|
||||
|
||||
fun getUpdate(context: Context, callback: (UpdateInfo?) -> Unit) {
|
||||
val request = Request.Builder().url("https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json").build()
|
||||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
|
||||
val jsonAdapter = moshi.adapter(UpdateInfo::class.java)
|
||||
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 = jsonAdapter.fromJson(jsonString)
|
||||
if (updateInfo != null) {
|
||||
val packageVersionCode = context.packageManager.getPackageInfo(context.packageName, 0).longVersionCode
|
||||
updateInfo.versionCode?.let { versionCode ->
|
||||
if (versionCode > packageVersionCode)
|
||||
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
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
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 downloadManager = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager ?: return
|
||||
downloadManager.query(DownloadManager.Query().setFilterById(downloadId)).use { cursor ->
|
||||
if (cursor.moveToFirst() && cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) {
|
||||
context.unregisterReceiver(this)
|
||||
onDownloaded(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)).toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateInfo(
|
||||
val version: String?,
|
||||
val versionCode: Int?,
|
||||
val downloadUrl: String?,
|
||||
val changelogUrl: String?,
|
||||
)
|
||||
@@ -2,6 +2,11 @@ package com.cherret.zaprett.ui.screens
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -12,6 +17,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.RestartAlt
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
@@ -21,12 +27,16 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
@@ -40,7 +50,12 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.cherret.zaprett.R
|
||||
import com.cherret.zaprett.download
|
||||
import com.cherret.zaprett.getChangelog
|
||||
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
|
||||
@@ -51,17 +66,37 @@ import kotlinx.coroutines.launch
|
||||
fun HomeScreen() {
|
||||
val context = LocalContext.current
|
||||
val sharedPreferences = remember { context.getSharedPreferences("settings", MODE_PRIVATE) }
|
||||
val cardText = remember { mutableStateOf(R.string.status_not_availible) }
|
||||
val cardText = remember { mutableIntStateOf(R.string.status_not_availible) }
|
||||
val changeLog = remember { mutableStateOf<String?>(null) }
|
||||
val newVersion = remember { mutableStateOf<String?>(null) }
|
||||
val updateAvailable = remember {mutableStateOf(false)}
|
||||
val downloadUrl = remember { mutableStateOf<String?>(null) }
|
||||
var showUpdateDialog by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
LaunchedEffect(Unit) {
|
||||
if (sharedPreferences.getBoolean("auto_update", true)) {
|
||||
getUpdate(context) {
|
||||
if (it != null) {
|
||||
downloadUrl.value = it.downloadUrl.toString()
|
||||
getChangelog(it.changelogUrl.toString()) {
|
||||
changeLog.value = it
|
||||
}
|
||||
newVersion.value = it.version?.toString()
|
||||
updateAvailable.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sharedPreferences.getBoolean("use_module", false) && sharedPreferences.getBoolean("update_on_boot", false)) {
|
||||
if (getStatus()) {
|
||||
cardText.value = R.string.status_enabled
|
||||
}
|
||||
else {
|
||||
cardText.value = R.string.status_disabled
|
||||
getStatus {
|
||||
if (it) {
|
||||
cardText.value = R.string.status_enabled
|
||||
}
|
||||
else {
|
||||
cardText.value = R.string.status_disabled
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
@@ -111,6 +146,40 @@ fun HomeScreen() {
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = updateAvailable.value,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
ElevatedCard(
|
||||
elevation = CardDefaults.cardElevation(
|
||||
defaultElevation = 6.dp
|
||||
),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, top = 10.dp, end = 10.dp)
|
||||
.size(width = 140.dp, height = 70.dp),
|
||||
onClick = {
|
||||
showUpdateDialog = true
|
||||
}
|
||||
)
|
||||
{
|
||||
Text(
|
||||
text = stringResource(R.string.update_available),
|
||||
fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showUpdateDialog) {
|
||||
UpdateDialog(context, downloadUrl.value.toString(), changeLog.value.toString(), newVersion, onDismiss = { showUpdateDialog = false })
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = { onBtnStartService(context, snackbarHostState, scope) },
|
||||
modifier = Modifier
|
||||
@@ -164,12 +233,15 @@ fun HomeScreen() {
|
||||
fun onCardClick(context: Context, cardText: MutableState<Int>, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
val sharedPreferences = context.getSharedPreferences("settings", MODE_PRIVATE)
|
||||
if (sharedPreferences.getBoolean("use_module", false)) {
|
||||
if (getStatus()) {
|
||||
cardText.value = R.string.status_enabled
|
||||
}
|
||||
else {
|
||||
cardText.value = R.string.status_disabled
|
||||
getStatus {
|
||||
if (it) {
|
||||
cardText.value = R.string.status_enabled
|
||||
}
|
||||
else {
|
||||
cardText.value = R.string.status_disabled
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
scope.launch {
|
||||
@@ -180,14 +252,16 @@ fun onCardClick(context: Context, cardText: MutableState<Int>, snackbarHostState
|
||||
|
||||
fun onBtnStartService(context: Context, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
if (context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("use_module", false)) {
|
||||
if (getStatus()) {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_already_started))
|
||||
}
|
||||
} else {
|
||||
startService()
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.btn_start_service))
|
||||
getStatus {
|
||||
if (it) {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_already_started))
|
||||
}
|
||||
} else {
|
||||
startService {}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_starting_service))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -199,15 +273,17 @@ fun onBtnStartService(context: Context, snackbarHostState: SnackbarHostState, sc
|
||||
|
||||
fun onBtnStopService(context: Context, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
if (context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("use_module", false)) {
|
||||
if (getStatus()) {
|
||||
stopService()
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.btn_stop_service))
|
||||
getStatus {
|
||||
if (it) {
|
||||
stopService{}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_stopping_service))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_no_service))
|
||||
else {
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_no_service))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +296,7 @@ fun onBtnStopService(context: Context, snackbarHostState: SnackbarHostState, sco
|
||||
|
||||
fun onBtnRestart(context: Context, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
if (context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("use_module", false)) {
|
||||
restartService()
|
||||
restartService{}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_reload))
|
||||
}
|
||||
@@ -230,4 +306,29 @@ fun onBtnRestart(context: Context, snackbarHostState: SnackbarHostState, scope:
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_module_disabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateDialog(context: Context, downloadUrl: String, changeLog: String, newVersion: MutableState<String?>, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
title = { Text(text = stringResource(R.string.update_available)) },
|
||||
text = { Text(text = "${stringResource(R.string.alert_version)}: ${context.packageManager.getPackageInfo(context.packageName, 0).versionName} —> ${newVersion.value}\n${stringResource(R.string.alert_changelog)}:\n$changeLog") },
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDismiss()
|
||||
val downloadId = download(context, downloadUrl)
|
||||
registerDownloadListener(context, downloadId) { uri ->
|
||||
installApk(context, uri)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.btn_update))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.btn_dismiss))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -8,9 +8,12 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -18,15 +21,20 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.RestartAlt
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
@@ -37,6 +45,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.Snapshot
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@@ -47,6 +57,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.cherret.zaprett.R
|
||||
@@ -55,6 +66,7 @@ import com.cherret.zaprett.enableList
|
||||
import com.cherret.zaprett.getActiveLists
|
||||
import com.cherret.zaprett.getAllLists
|
||||
import com.cherret.zaprett.getZaprettPath
|
||||
import com.cherret.zaprett.restartService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
@@ -85,7 +97,6 @@ fun HostsScreen() {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
val primaryColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
@@ -125,14 +136,16 @@ fun HostsScreen() {
|
||||
},
|
||||
modifier = Modifier
|
||||
) {
|
||||
LazyColumn {
|
||||
LazyColumn (
|
||||
contentPadding = PaddingValues(bottom = 25.dp)
|
||||
){
|
||||
items(allLists) { item ->
|
||||
ElevatedCard(
|
||||
elevation = CardDefaults.cardElevation(
|
||||
defaultElevation = 6.dp
|
||||
),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -158,11 +171,74 @@ fun HostsScreen() {
|
||||
disableList(item)
|
||||
}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.pls_restart_snack))
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.pls_restart_snack),
|
||||
actionLabel = context.getString(R.string.btn_restart_service)
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.ActionPerformed -> {
|
||||
restartService {}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(
|
||||
R.string.snack_reload
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
SnackbarResult.Dismissed -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
HorizontalDivider(thickness = Dp.Hairline)
|
||||
Row (modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End) {
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
if (deleteHost(item)) {
|
||||
allLists = getAllLists()
|
||||
activeLists = getActiveLists()
|
||||
checked.clear()
|
||||
allLists.forEach { list ->
|
||||
checked[list] = activeLists.contains(list)
|
||||
}
|
||||
}
|
||||
scope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.pls_restart_snack),
|
||||
actionLabel = context.getString(R.string.btn_restart_service)
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.ActionPerformed -> {
|
||||
restartService {}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(
|
||||
R.string.snack_reload
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
SnackbarResult.Dismissed -> {}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(start = 5.dp, end = 5.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(R.string.btn_remove_host),
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.btn_remove_host)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,10 +257,10 @@ fun HostsScreen() {
|
||||
}
|
||||
|
||||
fun addHost(launcher: ActivityResultLauncher<Array<String>>) {
|
||||
launcher.launch(arrayOf("*/*"))
|
||||
launcher.launch(arrayOf("text/plain"))
|
||||
}
|
||||
|
||||
fun copySelectedFile(context: Context, uri: Uri?, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
|
||||
fun copySelectedFile(context: Context, uri: Uri?, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {// AI Generated
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
if (uri == null) return
|
||||
val contentResolver = context.contentResolver
|
||||
@@ -200,7 +276,16 @@ fun copySelectedFile(context: Context, uri: Uri?, snackbarHostState: SnackbarHos
|
||||
}
|
||||
}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.pls_restart_snack))
|
||||
val result = snackbarHostState.showSnackbar(context.getString(R.string.pls_restart_snack), actionLabel = context.getString(R.string.btn_restart_service))
|
||||
when (result) {
|
||||
SnackbarResult.ActionPerformed -> {
|
||||
restartService{}
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.snack_reload))
|
||||
}
|
||||
}
|
||||
SnackbarResult.Dismissed -> {}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
@@ -208,3 +293,12 @@ fun copySelectedFile(context: Context, uri: Uri?, snackbarHostState: SnackbarHos
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteHost(item: String): Boolean {
|
||||
val hostFile = File(item)
|
||||
if (hostFile.exists()) {
|
||||
hostFile.delete()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ fun SettingsScreen() {
|
||||
val useModule = remember { mutableStateOf(sharedPreferences.getBoolean("use_module", false)) }
|
||||
val updateOnBoot = remember { mutableStateOf(sharedPreferences.getBoolean("update_on_boot", false)) }
|
||||
val autoRestart = remember { mutableStateOf(getStartOnBoot()) }
|
||||
val autoUpdate = remember { mutableStateOf(sharedPreferences.getBoolean("auto_update", true)) }
|
||||
val openNoRootDialog = remember { mutableStateOf(false) }
|
||||
val openNoModuleDialog = remember { mutableStateOf(false) }
|
||||
showNoRootDialog(openNoRootDialog)
|
||||
@@ -98,7 +99,19 @@ fun SettingsScreen() {
|
||||
)
|
||||
Switch(
|
||||
checked = useModule.value,
|
||||
onCheckedChange = { if (useModule(context, it, updateOnBoot, openNoRootDialog, openNoModuleDialog)) useModule.value = it}
|
||||
onCheckedChange = { isChecked ->
|
||||
useModule(
|
||||
context,
|
||||
isChecked,
|
||||
updateOnBoot,
|
||||
openNoRootDialog,
|
||||
openNoModuleDialog
|
||||
) {
|
||||
if (it) {
|
||||
useModule.value = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Row(
|
||||
@@ -131,34 +144,53 @@ fun SettingsScreen() {
|
||||
onCheckedChange = { if (autoRestart(context, it)) autoRestart.value = it;}
|
||||
)
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.btn_autoupdate),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Switch(
|
||||
checked = autoUpdate.value,
|
||||
onCheckedChange = { autoUpdate.value = it; editor.putBoolean("auto_update", it).apply()}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun useModule(context: Context, checked: Boolean, updateOnBoot: MutableState<Boolean>, openNoRootDialog: MutableState<Boolean>, openNoModuleDialog: MutableState<Boolean>): Boolean {
|
||||
fun useModule(context: Context, checked: Boolean, updateOnBoot: MutableState<Boolean>, openNoRootDialog: MutableState<Boolean>, openNoModuleDialog: MutableState<Boolean>, callback: (Boolean) -> Unit): Boolean {
|
||||
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val editor = sharedPreferences.edit()
|
||||
if (checked) {
|
||||
if (checkRoot()) {
|
||||
if (checkModuleInstallation()) {
|
||||
editor.putBoolean("use_module", true).putBoolean("update_on_boot", true).apply()
|
||||
updateOnBoot.value = true
|
||||
return true
|
||||
|
||||
checkRoot {
|
||||
if (it) {
|
||||
checkModuleInstallation {
|
||||
if (it) {
|
||||
editor.putBoolean("use_module", true).putBoolean("update_on_boot", true).apply()
|
||||
updateOnBoot.value = true
|
||||
callback(true)
|
||||
}
|
||||
else {
|
||||
openNoModuleDialog.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
openNoModuleDialog.value = true
|
||||
openNoRootDialog.value = true
|
||||
}
|
||||
} else {
|
||||
openNoRootDialog.value = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
editor.putBoolean("use_module", false).putBoolean("update_on_boot", false)
|
||||
.apply()
|
||||
return true
|
||||
editor.putBoolean("use_module", false).putBoolean("update_on_boot", false).apply()
|
||||
updateOnBoot.value = false
|
||||
callback(true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cherret.zaprett.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<string name="title_hosts">Хосты</string>
|
||||
<string name="title_settings">Настройки</string>
|
||||
<string name="btn_continue">Продолжить</string>
|
||||
<string name="btn_update">Обновить</string>
|
||||
<string name="btn_dismiss">Отмена</string>
|
||||
<string name="text_welcome">Привет в zaprett! Это приложение предназначено для обхода цензуры и иных блокировок. Для полноценной работоспособности необходимо установить Magisk модуль.</string>
|
||||
<string name="btn_use_root">Использовать модуль</string>
|
||||
<string name="error_root_title">Root не получен</string>
|
||||
@@ -17,11 +19,16 @@
|
||||
<string name="status_enabled">Сервис zaprett работает. Нажми для обновления состояния</string>
|
||||
<string name="status_disabled">Сервис zaprett не работает. Нажми для обновления состояния</string>
|
||||
<string name="status_crashed">Сервис zaprett вероятно крашнулся. Нажмите кнопку перезапуска ниже</string>
|
||||
<string name="update_available">Доступно новое обновление!</string>
|
||||
<string name="btn_update_on_boot">Обновлять статус при запуске Главной страницы</string>
|
||||
<string name="btn_start_service">Запустить сервис</string>
|
||||
<string name="btn_stop_service">Остановить сервис</string>
|
||||
<string name="btn_restart_service">Перезапустить сервис</string>
|
||||
<string name="btn_remove_host">Удалить</string>
|
||||
<string name="btn_autorestart">Переодически перезапускать сервис</string>
|
||||
<string name="btn_autoupdate">Авто обновление</string>
|
||||
<string name="alert_version">Версия</string>
|
||||
<string name="alert_changelog">Список изменений</string>
|
||||
<string name="snack_already_started">Сервис уже запущен.</string>
|
||||
<string name="snack_starting_service">Запускаем сервис...</string>
|
||||
<string name="snack_no_service">Сервис не запущен</string>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<string name="title_hosts">Hosts</string>
|
||||
<string name="title_settings">Settings</string>
|
||||
<string name="btn_continue">Continue</string>
|
||||
<string name="btn_update">Update</string>
|
||||
<string name="btn_dismiss">Dismiss</string>
|
||||
<string name="text_welcome">Hello to zaprett! This application is designed to bypass censorship and other blockages. For full functionality you need to install Magisk module.</string>
|
||||
<string name="btn_use_root">Use module</string>
|
||||
<string name="error_root_title">Can\'t get root</string>
|
||||
@@ -17,11 +19,16 @@
|
||||
<string name="status_enabled">zaprett service is working. Tap to update</string>
|
||||
<string name="status_disabled">zaprett service disabled.</string>
|
||||
<string name="status_crashed">zaprett service crashed. Tap restart button below</string>
|
||||
<string name="update_available">New update available!</string>
|
||||
<string name="btn_update_on_boot">Update the status when the Home page is launched</string>
|
||||
<string name="btn_start_service">Start service</string>
|
||||
<string name="btn_stop_service">Stop service</string>
|
||||
<string name="btn_restart_service">Restart service</string>
|
||||
<string name="btn_remove_host">Remove</string>
|
||||
<string name="btn_autorestart">Restart service periodicaly</string>
|
||||
<string name="btn_autoupdate">Autoupdate</string>
|
||||
<string name="alert_version">Version</string>
|
||||
<string name="alert_changelog">Changelog</string>
|
||||
<string name="snack_already_started">Service already started.</string>
|
||||
<string name="snack_starting_service">Starting service...</string>
|
||||
<string name="snack_no_service">Service is not launched</string>
|
||||
|
||||
4
app/src/main/res/xml/file_paths.xml
Normal file
4
app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path name="downloads" path="Download/" />
|
||||
</paths>
|
||||
@@ -3,4 +3,6 @@ plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||
id("com.google.firebase.crashlytics") version "3.0.3" apply false
|
||||
}
|
||||
2
changelog.md
Normal file
2
changelog.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Исправление багов
|
||||
Добавление Firebase Analytics
|
||||
BIN
images/1.png
Normal file
BIN
images/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
images/2.png
Normal file
BIN
images/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
images/3.png
Normal file
BIN
images/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
6
update.json
Normal file
6
update.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1.4",
|
||||
"versionCode": 5,
|
||||
"downloadUrl": "https://github.com/CherretGit/zaprett-app/releases/download/1_4_0/app-release.apk",
|
||||
"changelogUrl": "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/changelog.md"
|
||||
}
|
||||
Reference in New Issue
Block a user