commit 12b176b7684d6c98ce595a4f0afb5d5a2bd1aaa3 Author: Cherret Date: Tue Mar 25 12:21:23 2025 +0700 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..7643783 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,123 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..cb8cd7c --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7061a0d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,61 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..c224ad5 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..a2ae4e0 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,64 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "com.cherret.zaprett" + compileSdk = 35 + + defaultConfig { + applicationId = "com.cherret.zaprett" + minSdk = 30 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + implementation("androidx.compose.material3:material3:1.3.1") + implementation("androidx.compose.material3:material3-window-size-class:1.3.1") + implementation("androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha10") + 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(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/cherret/zaprett/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/cherret/zaprett/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..ad946d8 --- /dev/null +++ b/app/src/androidTest/java/com/cherret/zaprett/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.cherret.zaprett + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.cherret.zaprett", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..27d38f2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/MainActivity.kt b/app/src/main/java/com/cherret/zaprett/MainActivity.kt new file mode 100644 index 0000000..10a2f6d --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/MainActivity.kt @@ -0,0 +1,178 @@ +package com.cherret.zaprett + + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.provider.Settings +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Dashboard +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +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 + +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) + object hosts : Screen("hosts", R.string.title_hosts, Icons.Default.Dashboard) + object settings : Screen("settings", R.string.title_settings, Icons.Default.Settings) +} +val topLevelRoutes = listOf(Screen.home, Screen.hosts, Screen.settings) +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) + Shell.setDefaultBuilder(Shell.Builder.create() + .setTimeout(10)) + enableEdgeToEdge() + setContent { + ZaprettTheme { + BottomBar() + if (!Environment.isExternalStorageManager()) { + permissionDialog() + } + if (sharedPreferences.getBoolean("welcome_dialog", true)) { + welcomeDialog() + } + } + } + } + + @Composable + fun BottomBar() { + val navController = rememberNavController() + Scaffold( + bottomBar = { + NavigationBar { + val navBackStackEntry = navController.currentBackStackEntryAsState().value + val currentDestination = navBackStackEntry?.destination + topLevelRoutes.forEach { topLevelRoute -> + NavigationBarItem( + icon = { + Icon( + topLevelRoute.icon, + contentDescription = stringResource(id = topLevelRoute.nameResId) + ) + }, + label = { Text(text = stringResource(id = topLevelRoute.nameResId)) }, + selected = currentDestination?.route == topLevelRoute.route, + onClick = { + navController.navigate(topLevelRoute.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + } + ) { innerPadding -> + NavHost( + navController, + startDestination = Screen.home.route, + Modifier.padding(innerPadding) + ) { + composable(Screen.home.route) { HomeScreen() } + composable(Screen.hosts.route) { HostsScreen() } + composable(Screen.settings.route) { SettingsScreen() } + } + } + } + + @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)) + } + }, + ) + } + } + + @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)) + } + }, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/Module.kt b/app/src/main/java/com/cherret/zaprett/Module.kt new file mode 100644 index 0000000..65252d6 --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/Module.kt @@ -0,0 +1,170 @@ +package com.cherret.zaprett + +import android.os.Environment +import android.util.Log +import com.topjohnwu.superuser.Shell +import java.io.File +import java.io.FileInputStream +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 checkModuleInstallation(): Boolean { + val result = Shell.cmd("zaprett").exec() + Shell.getShell().close() + return 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 startService() { + Shell.cmd("zaprett start").exec() + Shell.getShell().close() +} + +fun stopService() { + Shell.cmd("zaprett stop").exec() + Shell.getShell().close() +} + +fun restartService() { + Shell.cmd("zaprett restart").exec() + Shell.getShell().close() +} + +fun getConfigFile(): File { + return File(Environment.getExternalStorageDirectory(), "zaprett/config") +} + +fun setStartOnBoot(startOnBoot: Boolean) { + val configFile = getConfigFile() + if (configFile.exists()) { + val props = Properties() + try { + FileInputStream(configFile).use { input -> + props.load(input) + } + props.setProperty("autorestart", startOnBoot.toString()) + FileOutputStream(configFile).use { output -> + props.store(output, "Don't place '/' in end of directory! Example: /sdcard") + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} + +fun getStartOnBoot(): Boolean { + val configFile = getConfigFile() + val props = Properties() + return try { + if (configFile.exists()) { + FileInputStream(configFile).use { input -> + props.load(input) + } + props.getProperty("autorestart", "false").toBoolean() + } else { + false + } + } catch (e: IOException) { + false + } +} + +fun getZaprettPath(): String { + val props = Properties() + val configFile = getConfigFile() + if (configFile.exists()) { + return try { + FileInputStream(configFile).use { input -> + props.load(input) + } + props.getProperty("zaprettdir", "/sdcard/zaprett") + } catch (e: IOException) { + throw RuntimeException(e) + } + } + return "/sdcard/zaprett" +} + +fun getAllLists(): Array { + val listsDir = File("${getZaprettPath()}/lists/") + if (listsDir.exists() && listsDir.isDirectory) { + val onlyNames = listsDir.list() ?: return emptyArray() + return onlyNames.map { "$listsDir/$it" }.toTypedArray() + } + return emptyArray() +} + +fun getActiveLists(): Array { + val configFile = File("${getZaprettPath()}/config") + if (configFile.exists()) { + val props = Properties() + return try { + FileInputStream(configFile).use { input -> + props.load(input) + } + val activeLists = props.getProperty("activelists", "") + Log.d("Active lists", activeLists) + if (activeLists.isNotEmpty()) activeLists.split(",").toTypedArray() else emptyArray() + } catch (e: IOException) { + throw RuntimeException(e) + } + } + return emptyArray() +} + +fun enableList(path: String) { + val props = Properties() + val configFile = getConfigFile() + try { + if (configFile.exists()) { + FileInputStream(configFile).use { input -> + props.load(input) + } + } + val activeLists = props.getProperty("activelists", "").split(",").toMutableList() + if (path !in activeLists) { + activeLists.add(path) + } + props.setProperty("activelists", activeLists.joinToString(",")) + FileOutputStream(configFile).use { output -> + props.store(output, "Don't place '/' in end of directory! Example: /sdcard") + } + } catch (e: IOException) { + throw RuntimeException(e) + } +} + +fun disableList(path: String) { + val props = Properties() + val configFile = getConfigFile() + try { + if (configFile.exists()) { + FileInputStream(configFile).use { input -> + props.load(input) + } + } + val activeLists = props.getProperty("activelists", "").split(",").toMutableList() + if (path in activeLists) { + activeLists.remove(path) + } + props.setProperty("activelists", activeLists.joinToString(",")) + FileOutputStream(configFile).use { output -> + props.store(output, "Don't place '/' in end of directory! Example: /sdcard") + } + } catch (e: IOException) { + throw RuntimeException(e) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/ui/screens/HomeScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screens/HomeScreen.kt new file mode 100644 index 0000000..e4785f7 --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/ui/screens/HomeScreen.kt @@ -0,0 +1,233 @@ +package com.cherret.zaprett.ui.screens + +import android.content.Context +import android.content.Context.MODE_PRIVATE +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +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.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.FilledTonalButton +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.platform.LocalContext +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.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.cherret.zaprett.R +import com.cherret.zaprett.getStatus +import com.cherret.zaprett.restartService +import com.cherret.zaprett.startService +import com.cherret.zaprett.stopService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun HomeScreen() { + val context = LocalContext.current + val sharedPreferences = remember { context.getSharedPreferences("settings", MODE_PRIVATE) } + val cardText = remember { mutableStateOf(R.string.status_not_availible) } + val scope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + LaunchedEffect(Unit) { + 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 + } + } + } + Scaffold( + topBar = { + val primaryColor = MaterialTheme.colorScheme.surfaceVariant + Box(modifier = Modifier.padding(start = 20.dp, top = 10.dp)) { + Canvas(modifier = Modifier.size(100.dp, 50.dp)) { + rotate(degrees = -30f) { + drawOval( + color = primaryColor, + size = Size(200f, 140f), + topLeft = Offset(-20f, -20f) + ) + } + } + Text( + text = stringResource(R.string.app_name), + fontSize = 40.sp, + fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)) + ) + } + }, + content = { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + ) { + ElevatedCard( + elevation = CardDefaults.cardElevation( + defaultElevation = 6.dp + ), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.primary, + ), + modifier = Modifier + .fillMaxWidth() + .padding(start = 10.dp, top = 25.dp, end = 10.dp) + .size(width = 240.dp, height = 150.dp), + onClick = { onCardClick(context, cardText, snackbarHostState, scope) } + ) { + Text( + text = stringResource(cardText.value), + fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + textAlign = TextAlign.Center, + ) + } + FilledTonalButton( + onClick = { onBtnStartService(context, snackbarHostState, scope) }, + modifier = Modifier + .padding(start = 5.dp, top = 8.dp, end = 5.dp) + .fillMaxWidth() + ) { + Icon( + imageVector = Icons.Default.PlayArrow, + contentDescription = stringResource(R.string.btn_start_service), + modifier = Modifier.size(20.dp) + ) + Text( + stringResource(R.string.btn_start_service) + ) + } + FilledTonalButton( + onClick = { onBtnStopService(context, snackbarHostState, scope) }, + modifier = Modifier + .padding(start = 5.dp, top = 8.dp, end = 5.dp) + .fillMaxWidth() + ) { + Icon( + imageVector = Icons.Default.Stop, + contentDescription = stringResource(R.string.btn_stop_service), + modifier = Modifier.size(20.dp) + ) + Text( + stringResource(R.string.btn_stop_service) + ) + } + FilledTonalButton( + onClick = { onBtnRestart(context, snackbarHostState, scope) }, + modifier = Modifier + .padding(start = 5.dp, top = 8.dp, end = 5.dp) + .fillMaxWidth() + ) { + Icon( + imageVector = Icons.Default.RestartAlt, + contentDescription = stringResource(R.string.btn_restart_service), + modifier = Modifier.size(20.dp) + ) + Text( + stringResource(R.string.btn_restart_service) + ) + } + } + }, + snackbarHost = {SnackbarHost(hostState = snackbarHostState)} + ) +} +fun onCardClick(context: Context, cardText: MutableState, 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 + } + } + else { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.snack_module_disabled)) + } + } +} + +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)) + } + } + } else { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.snack_module_disabled)) + } + } +} + +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)) + } + } + else { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.snack_no_service)) + } + } + } + else { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.snack_module_disabled)) + } + } +} + +fun onBtnRestart(context: Context, snackbarHostState: SnackbarHostState, scope: CoroutineScope) { + if (context.getSharedPreferences("settings", MODE_PRIVATE).getBoolean("use_module", false)) { + restartService() + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.snack_reload)) + } + } + else { + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.snack_module_disabled)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/ui/screens/HostsScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screens/HostsScreen.kt new file mode 100644 index 0000000..8f7691a --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/ui/screens/HostsScreen.kt @@ -0,0 +1,210 @@ +package com.cherret.zaprett.ui.screens + +import android.content.Context +import android.net.Uri +import android.os.Environment +import android.provider.OpenableColumns +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.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +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.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +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.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.platform.LocalContext +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.sp +import com.cherret.zaprett.R +import com.cherret.zaprett.disableList +import com.cherret.zaprett.enableList +import com.cherret.zaprett.getActiveLists +import com.cherret.zaprett.getAllLists +import com.cherret.zaprett.getZaprettPath +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HostsScreen() { + val context = LocalContext.current + var allLists by remember { mutableStateOf(getAllLists()) } + var activeLists by remember { mutableStateOf(getActiveLists()) } + val scope = rememberCoroutineScope() + val snackbarHostState = remember { SnackbarHostState() } + var isRefreshing by remember { mutableStateOf(false) } + val checked = remember { + mutableStateMapOf().apply { + allLists.forEach { list -> + this[list] = activeLists.contains(list) + } + } + } + val filePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenDocument(), + onResult = { uri: Uri? -> + uri?.let { + copySelectedFile(context, it, snackbarHostState, scope) + } + } + ) + + Scaffold( + topBar = { + val primaryColor = MaterialTheme.colorScheme.surfaceVariant + Box(modifier = Modifier.padding(start = 20.dp, top = 10.dp)) { + Canvas(modifier = Modifier.size(100.dp, 50.dp)) { + rotate(degrees = -30f) { + drawOval( + color = primaryColor, + size = Size(200f, 140f), + topLeft = Offset(-30f, -30f) + ) + } + } + Text( + text = stringResource(R.string.title_hosts), + fontSize = 40.sp, + fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)) + ) + } + }, + content = { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + ) { + PullToRefreshBox( + isRefreshing = isRefreshing, + onRefresh = { + isRefreshing = true + allLists = getAllLists() + activeLists = getActiveLists() + checked.clear() + allLists.forEach { list -> + checked[list] = activeLists.contains(list) + } + isRefreshing = false + }, + modifier = Modifier + ) { + LazyColumn { + items(allLists) { item -> + ElevatedCard( + elevation = CardDefaults.cardElevation( + defaultElevation = 6.dp + ), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), + modifier = Modifier + .fillMaxWidth() + .padding(start = 10.dp, top = 25.dp, end = 10.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = item, + modifier = Modifier.weight(1f) + ) + Switch( + checked = checked[item] == true, + onCheckedChange = { isChecked -> + checked[item] = isChecked + if (isChecked) { + enableList(item) + } else { + disableList(item) + } + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.pls_restart_snack)) + } + } + ) + } + } + } + } + } + } + }, + floatingActionButton = { + FloatingActionButton(modifier = Modifier + .size(80.dp, 80.dp), + onClick = {addHost(filePickerLauncher)}) { + Icon(Icons.Default.Add, contentDescription = "Restart") + } + }, + snackbarHost = {SnackbarHost(hostState = snackbarHostState)} + ) +} + +fun addHost(launcher: ActivityResultLauncher>) { + launcher.launch(arrayOf("*/*")) +} + +fun copySelectedFile(context: Context, uri: Uri?, snackbarHostState: SnackbarHostState, scope: CoroutineScope) { + if (Environment.isExternalStorageManager()) { + if (uri == null) return + val contentResolver = context.contentResolver + val fileName = contentResolver.query(uri, null, null, null, null)?.use { cursor -> + val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (cursor.moveToFirst() && nameIndex != -1) cursor.getString(nameIndex) else "copied_file" + } ?: "copied_file" + val outputFile = File(getZaprettPath() + "/lists", fileName) + try { + contentResolver.openInputStream(uri)?.use { inputStream -> + FileOutputStream(outputFile).use { outputStream -> + inputStream.copyTo(outputStream) + } + } + scope.launch { + snackbarHostState.showSnackbar(context.getString(R.string.pls_restart_snack)) + } + } catch (e: IOException) { + e.printStackTrace() + } + } +} + diff --git a/app/src/main/java/com/cherret/zaprett/ui/screens/SettingsScreen.kt b/app/src/main/java/com/cherret/zaprett/ui/screens/SettingsScreen.kt new file mode 100644 index 0000000..1954919 --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/ui/screens/SettingsScreen.kt @@ -0,0 +1,224 @@ +package com.cherret.zaprett.ui.screens + +import android.content.Context +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.platform.LocalContext +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.sp +import com.cherret.zaprett.R +import com.cherret.zaprett.checkModuleInstallation +import com.cherret.zaprett.checkRoot +import com.cherret.zaprett.getStartOnBoot +import com.cherret.zaprett.setStartOnBoot + +@Composable +fun SettingsScreen() { + val context = LocalContext.current + val sharedPreferences = remember { context.getSharedPreferences("settings", Context.MODE_PRIVATE) } + val editor = remember { sharedPreferences.edit() } + 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 openNoRootDialog = remember { mutableStateOf(false) } + val openNoModuleDialog = remember { mutableStateOf(false) } + showNoRootDialog(openNoRootDialog) + showNoModuleDialog(openNoModuleDialog) + Scaffold( + topBar = { + val primaryColor = MaterialTheme.colorScheme.surfaceVariant + Box(modifier = Modifier.padding(start = 20.dp, top = 10.dp)) { + Canvas(modifier = Modifier.size(100.dp, 50.dp)) { + rotate(degrees = -30f) { + drawOval( + color = primaryColor, + size = Size(200f, 140f), + topLeft = Offset(-30f, -30f) + ) + } + } + Text( + text = stringResource(R.string.title_settings), + fontSize = 40.sp, + fontFamily = FontFamily(Font(R.font.unbounded, FontWeight.Normal)) + ) + } + }, + content = { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + ) { + ElevatedCard( + elevation = CardDefaults.cardElevation( + defaultElevation = 6.dp + ), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + ), + modifier = Modifier + .fillMaxWidth() + .padding(start = 10.dp, top = 25.dp, end = 10.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = stringResource(R.string.btn_use_root), + modifier = Modifier.weight(1f) + ) + Switch( + checked = useModule.value, + onCheckedChange = { if (useModule(context, it, updateOnBoot, openNoRootDialog, openNoModuleDialog)) useModule.value = it} + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = stringResource(R.string.btn_update_on_boot), + modifier = Modifier.weight(1f) + ) + Switch( + checked = updateOnBoot.value, + onCheckedChange = { updateOnBoot.value = it; editor.putBoolean("update_on_boot", it).apply()} + ) + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = stringResource(R.string.btn_autorestart), + modifier = Modifier.weight(1f) + ) + Switch( + checked = autoRestart.value, + onCheckedChange = { if (autoRestart(context, it)) autoRestart.value = it;} + ) + } + } + } + } + ) +} + +fun useModule(context: Context, checked: Boolean, updateOnBoot: MutableState, openNoRootDialog: MutableState, openNoModuleDialog: MutableState): 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 + + } + else { + openNoModuleDialog.value = true + } + } else { + openNoRootDialog.value = true + } + } + else { + editor.putBoolean("use_module", false).putBoolean("update_on_boot", false) + .apply() + return true + } + return false +} + +fun autoRestart(context: Context, checked: Boolean): Boolean { + if (context.getSharedPreferences("settings", Context.MODE_PRIVATE).getBoolean("use_module", false)) { + setStartOnBoot(checked) + return true + } + return false +} + +@Composable +fun showNoRootDialog(openDialog: MutableState) { + if (openDialog.value) { + AlertDialog( + title = { + Text(text = stringResource(R.string.error_root_title)) + }, + text = { + Text(text = stringResource(R.string.error_root_message)) + }, + onDismissRequest = { + openDialog.value = false + }, + confirmButton = { + TextButton( + onClick = { + openDialog.value = false + } + ) { + Text(stringResource(R.string.btn_continue)) + } + }, + ) + } +} + +@Composable +fun showNoModuleDialog(openDialog: MutableState) { + if (openDialog.value) { + AlertDialog( + title = { + Text(text = stringResource(R.string.error_no_module_title)) + }, + text = { + Text(text = stringResource(R.string.error_no_module_message)) + }, + onDismissRequest = { + openDialog.value = false + }, + confirmButton = { + TextButton( + onClick = { + openDialog.value = false + } + ) { + Text(stringResource(R.string.btn_continue)) + } + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/ui/theme/Color.kt b/app/src/main/java/com/cherret/zaprett/ui/theme/Color.kt new file mode 100644 index 0000000..aed5051 --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.cherret.zaprett.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/ui/theme/Theme.kt b/app/src/main/java/com/cherret/zaprett/ui/theme/Theme.kt new file mode 100644 index 0000000..868b96c --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.cherret.zaprett.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun ZaprettTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/cherret/zaprett/ui/theme/Type.kt b/app/src/main/java/com/cherret/zaprett/ui/theme/Type.kt new file mode 100644 index 0000000..135c209 --- /dev/null +++ b/app/src/main/java/com/cherret/zaprett/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.cherret.zaprett.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..23098e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..40db64c --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml new file mode 100644 index 0000000..6a862e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_monochrome.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/font/productsans.ttf b/app/src/main/res/font/productsans.ttf new file mode 100644 index 0000000..7a96d3b Binary files /dev/null and b/app/src/main/res/font/productsans.ttf differ diff --git a/app/src/main/res/font/unbounded.ttf b/app/src/main/res/font/unbounded.ttf new file mode 100644 index 0000000..d23ee18 Binary files /dev/null and b/app/src/main/res/font/unbounded.ttf differ diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..8fde456 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..8fde456 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..5f91051 --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,34 @@ + + + Главная + Хосты + Настройки + Продолжить + Привет в zaprett! Это приложение предназначено для обхода цензуры и иных блокировок. Для полноценной работоспособности необходимо установить Magisk модуль. + Использовать модуль + Root не получен + Не получилось получить root доступ. Дайте права root для использования модуля Magisk + Модуль не установлен + Magisk модуль zaprett не найден. Пожалуйста, установите его + Перезагружаем zaprett... + Модуль Magisk отключен! Не получилось выполнить действие + Стратегия + Состояние zaprett неизвестно. Нажми для обновления + Сервис zaprett работает. Нажми для обновления состояния + Сервис zaprett не работает. Нажми для обновления состояния + Сервис zaprett вероятно крашнулся. Нажмите кнопку перезапуска ниже + Обновлять статус при запуске Главной страницы + Запустить сервис + Остановить сервис + Перезапустить сервис + Переодически перезапускать сервис + Сервис уже запущен. + Запускаем сервис... + Сервис не запущен + Останавливаем сервис... + Нет разрешения + Для правильной работы приложения необходимо разрешение на доступ к хранилищу + Показывать полный путь к листу + Перезагрузите устройство для вступления изменений в силу + Перезапустите zaprett для вступления изменений в силу + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..499b7c4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,34 @@ + + zaprett + Home + Hosts + Settings + Continue + Hello to zaprett! This application is designed to bypass censorship and other blockages. For full functionality you need to install Magisk module. + Use module + Can\'t get root + Couldn\'t get root access. Give root access to use the Magisk module + Module is not installed + Magisk module zaprett wasn\'t found. Please install it + Reloading zaprett... + Magisk module disabled! Cant\'t execute action + Strategy + Status of zaprett is unknown. Tap to update + zaprett service is working. Tap to update + zaprett service disabled. + zaprett service crashed. Tap restart button below + Update the status when the Home page is launched + Start service + Stop service + Restart service + Restart service periodicaly + Service already started. + Starting service... + Service is not launched + Stopping service... + No permission + The application requires permission to acess the storage to work properly + Show full list\'s path + Reboot your device for the changes to take effect + Restart zaprett service for the changes to take effects + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..85f9d7b --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +