mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 02:56:01 +00:00
App grouping (#195)
This commit is contained in:
@@ -66,7 +66,6 @@ class ApiReceiver: BroadcastReceiver() {
|
||||
}
|
||||
else -> {
|
||||
log += "\nInvalid action"
|
||||
false
|
||||
}
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -114,6 +113,8 @@ import com.bintianqi.owndroid.dpm.DisableAccountManagement
|
||||
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
|
||||
import com.bintianqi.owndroid.dpm.DisableMeteredData
|
||||
import com.bintianqi.owndroid.dpm.DisableUserControl
|
||||
import com.bintianqi.owndroid.dpm.EditAppGroup
|
||||
import com.bintianqi.owndroid.dpm.EditAppGroupScreen
|
||||
import com.bintianqi.owndroid.dpm.EnableSystemApp
|
||||
import com.bintianqi.owndroid.dpm.EnableSystemAppScreen
|
||||
import com.bintianqi.owndroid.dpm.FrpPolicy
|
||||
@@ -134,6 +135,8 @@ import com.bintianqi.owndroid.dpm.LockScreenInfo
|
||||
import com.bintianqi.owndroid.dpm.LockScreenInfoScreen
|
||||
import com.bintianqi.owndroid.dpm.LockTaskMode
|
||||
import com.bintianqi.owndroid.dpm.LockTaskModeScreen
|
||||
import com.bintianqi.owndroid.dpm.ManageAppGroups
|
||||
import com.bintianqi.owndroid.dpm.ManageAppGroupsScreen
|
||||
import com.bintianqi.owndroid.dpm.MtePolicy
|
||||
import com.bintianqi.owndroid.dpm.MtePolicyScreen
|
||||
import com.bintianqi.owndroid.dpm.NearbyStreamingPolicy
|
||||
@@ -284,6 +287,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
fun choosePackage() {
|
||||
navController.navigate(ApplicationsList(false))
|
||||
}
|
||||
fun navigateToAppGroups() {
|
||||
navController.navigate(ManageAppGroups)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if(!Privilege.status.value.activated) {
|
||||
navController.navigate(WorkModes(false)) {
|
||||
@@ -522,20 +528,20 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<Suspend> {
|
||||
PackageFunctionScreen(R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
|
||||
vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||
R.string.info_suspend_app)
|
||||
::navigateToAppGroups, vm.appGroups, R.string.info_suspend_app)
|
||||
}
|
||||
composable<Hide> {
|
||||
PackageFunctionScreen(R.string.hide, vm.hiddenPackages, vm::getHiddenPackages,
|
||||
vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
}
|
||||
composable<BlockUninstall> {
|
||||
PackageFunctionScreenWithoutResult(R.string.block_uninstall, vm.ubPackages,
|
||||
vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
}
|
||||
composable<DisableUserControl> {
|
||||
PackageFunctionScreenWithoutResult(R.string.disable_user_control, vm.ucdPackages,
|
||||
vm::getUcdPackages, vm::setPackageUcd, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, R.string.info_disable_user_control)
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups, R.string.info_disable_user_control)
|
||||
}
|
||||
composable<PermissionsManager> {
|
||||
PermissionsManagerScreen(vm.packagePermissions, vm::getPackagePermissions,
|
||||
@@ -543,7 +549,8 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
}
|
||||
composable<DisableMeteredData> {
|
||||
PackageFunctionScreen(R.string.disable_metered_data, vm.mddPackages,
|
||||
vm::getMddPackages, vm::setPackageMdd, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
vm::getMddPackages, vm::setPackageMdd, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
}
|
||||
composable<ClearAppStorage> {
|
||||
ClearAppStorageScreen(vm.chosenPackage, ::choosePackage, vm::clearAppData, ::navigateUp)
|
||||
@@ -554,7 +561,8 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<KeepUninstalledPackages> {
|
||||
PackageFunctionScreenWithoutResult(R.string.keep_uninstalled_packages, vm.kuPackages,
|
||||
vm::getKuPackages, vm::setPackageKu, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, R.string.info_keep_uninstalled_apps)
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups,
|
||||
R.string.info_keep_uninstalled_apps)
|
||||
}
|
||||
composable<InstallExistingApp> {
|
||||
InstallExistingAppScreen(vm.chosenPackage, ::choosePackage,
|
||||
@@ -562,11 +570,13 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
}
|
||||
composable<CrossProfilePackages> {
|
||||
PackageFunctionScreenWithoutResult(R.string.cross_profile_apps, vm.cpPackages,
|
||||
vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
}
|
||||
composable<CrossProfileWidgetProviders> {
|
||||
PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders,
|
||||
vm::getCpwProviders, vm::setCpwProvider, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||
vm::getCpwProviders, vm::setCpwProvider, ::navigateUp, vm.chosenPackage,
|
||||
::choosePackage, ::navigateToAppGroups, vm.appGroups)
|
||||
}
|
||||
composable<CredentialManagerPolicy> {
|
||||
CredentialManagerPolicyScreen(vm.chosenPackage, ::choosePackage,
|
||||
@@ -588,6 +598,19 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<SetDefaultDialer> {
|
||||
SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp)
|
||||
}
|
||||
composable<ManageAppGroups> {
|
||||
ManageAppGroupsScreen(
|
||||
vm.appGroups,
|
||||
{ id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) },
|
||||
::navigateUp
|
||||
)
|
||||
}
|
||||
composable<EditAppGroup> {
|
||||
EditAppGroupScreen(
|
||||
it.toRoute(), vm::getAppInfo, ::navigateUp, vm::setAppGroup,
|
||||
vm::deleteAppGroup, ::choosePackage, vm.chosenPackage
|
||||
)
|
||||
}
|
||||
|
||||
composable<UserRestriction> {
|
||||
UserRestrictionScreen(vm::getUserRestrictions, ::navigateUp, ::navigate)
|
||||
|
||||
@@ -4,11 +4,12 @@ import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
|
||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 3) {
|
||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 4) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(DHIZUKU_CLIENTS_TABLE)
|
||||
db.execSQL(SECURITY_LOGS_TABLE)
|
||||
db.execSQL(NETWORK_LOGS_TABLE)
|
||||
db.execSQL(APP_GROUPS_TABLE)
|
||||
}
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion < 2) {
|
||||
@@ -17,6 +18,9 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 3) {
|
||||
if (oldVersion < 3) {
|
||||
db.execSQL(NETWORK_LOGS_TABLE)
|
||||
}
|
||||
if (oldVersion < 4) {
|
||||
db.execSQL(APP_GROUPS_TABLE)
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
const val DHIZUKU_CLIENTS_TABLE = "CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
|
||||
@@ -26,5 +30,8 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 3) {
|
||||
const val NETWORK_LOGS_TABLE = "CREATE TABLE network_logs (id INTEGER, package INTEGER," +
|
||||
"time INTEGER, type TEXT, host TEXT, count INTEGER, addresses TEXT," +
|
||||
"address TEXT, port INTEGER)"
|
||||
const val APP_GROUPS_TABLE = "CREATE TABLE app_groups(" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"name TEXT, apps TEXT)"
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import com.bintianqi.owndroid.dpm.AppGroup
|
||||
import com.bintianqi.owndroid.dpm.NetworkLog
|
||||
import com.bintianqi.owndroid.dpm.SecurityEvent
|
||||
import com.bintianqi.owndroid.dpm.SecurityEventWithData
|
||||
@@ -224,4 +225,27 @@ class MyRepository(val dbHelper: MyDbHelper) {
|
||||
fun deleteNetworkLogs() {
|
||||
dbHelper.writableDatabase.execSQL("DELETE FROM network_logs")
|
||||
}
|
||||
|
||||
fun getAppGroups(): List<AppGroup> {
|
||||
val list = mutableListOf<AppGroup>()
|
||||
dbHelper.readableDatabase.rawQuery("SELECT * FROM app_groups", null).use {
|
||||
while (it.moveToNext()) {
|
||||
list += AppGroup(it.getInt(0), it.getString(1), it.getString(2).split(','))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
fun setAppGroup(id: Int?, name: String, apps: List<String>) {
|
||||
val cv = ContentValues()
|
||||
cv.put("name", name)
|
||||
cv.put("apps", apps.joinToString(","))
|
||||
if (id == null) {
|
||||
dbHelper.writableDatabase.insert("app_groups", null, cv)
|
||||
} else {
|
||||
dbHelper.writableDatabase.update("app_groups", cv, "id = ?", arrayOf(id.toString()))
|
||||
}
|
||||
}
|
||||
fun deleteAppGroup(id: Int) {
|
||||
dbHelper.writableDatabase.delete("app_groups", "id = ?", arrayOf(id.toString()))
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import com.bintianqi.owndroid.dpm.ApnAuthType
|
||||
import com.bintianqi.owndroid.dpm.ApnConfig
|
||||
import com.bintianqi.owndroid.dpm.ApnMvnoType
|
||||
import com.bintianqi.owndroid.dpm.ApnProtocol
|
||||
import com.bintianqi.owndroid.dpm.AppGroup
|
||||
import com.bintianqi.owndroid.dpm.AppStatus
|
||||
import com.bintianqi.owndroid.dpm.CaCertInfo
|
||||
import com.bintianqi.owndroid.dpm.CreateUserResult
|
||||
@@ -509,6 +510,24 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
val appGroups = MutableStateFlow(emptyList<AppGroup>())
|
||||
init {
|
||||
getAppGroups()
|
||||
}
|
||||
fun getAppGroups() {
|
||||
appGroups.value = myRepo.getAppGroups()
|
||||
}
|
||||
fun setAppGroup(id: Int?, name: String, apps: List<String>) {
|
||||
myRepo.setAppGroup(id, name, apps)
|
||||
getAppGroups()
|
||||
}
|
||||
fun deleteAppGroup(id: Int) {
|
||||
myRepo.deleteAppGroup(id)
|
||||
appGroups.update { group ->
|
||||
group.filter { it.id != id }
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(24)
|
||||
fun reboot() {
|
||||
DPM.reboot(DAR)
|
||||
|
||||
@@ -23,8 +23,8 @@ object Privilege {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch(_: Exception) {
|
||||
false
|
||||
} catch(e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
dhizukuErrorStatus.value = 2
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class SerializableSaver<T>(val serializer: KSerializer<T>) : Saver<T, String> {
|
||||
override fun restore(value: String): T? {
|
||||
return Json.decodeFromString(serializer, value)
|
||||
}
|
||||
override fun SaverScope.save(value: T): String? {
|
||||
override fun SaverScope.save(value: T): String {
|
||||
return Json.encodeToString(serializer, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -20,6 +21,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
@@ -30,12 +32,20 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.outlined.CheckCircle
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
@@ -46,12 +56,15 @@ import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -89,6 +102,7 @@ import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
val String.isValidPackageName
|
||||
@@ -729,27 +743,69 @@ fun SetDefaultDialerScreen(
|
||||
fun PackageFunctionScreenWithoutResult(
|
||||
title: Int, packagesState: MutableStateFlow<List<AppInfo>>, onGet: () -> Unit,
|
||||
onSet: (String, Boolean) -> Unit, onNavigateUp: () -> Unit,
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, notes: Int? = null
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
||||
navigateToGroups: () -> Unit, appGroups: StateFlow<List<AppGroup>>, notes: Int? = null
|
||||
) {
|
||||
PackageFunctionScreen(
|
||||
title, packagesState, onGet, { name, status -> onSet(name, status); null },
|
||||
onNavigateUp, chosenPackage, onChoosePackage, notes
|
||||
onNavigateUp, chosenPackage, onChoosePackage, navigateToGroups, appGroups, notes
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PackageFunctionScreen(
|
||||
title: Int, packagesState: MutableStateFlow<List<AppInfo>>, onGet: () -> Unit,
|
||||
onSet: (String, Boolean) -> Boolean?, onNavigateUp: () -> Unit,
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, notes: Int? = null
|
||||
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
||||
navigateToGroups: () -> Unit, appGroups: StateFlow<List<AppGroup>>, notes: Int? = null
|
||||
) {
|
||||
val groups by appGroups.collectAsStateWithLifecycle()
|
||||
val packages by packagesState.collectAsStateWithLifecycle()
|
||||
var packageName by rememberSaveable { mutableStateOf("") }
|
||||
var selectedGroup by remember { mutableStateOf<AppGroup?>(null) }
|
||||
LaunchedEffect(Unit) {
|
||||
onGet()
|
||||
packageName = chosenPackage.receive()
|
||||
}
|
||||
MyLazyScaffold(title, onNavigateUp) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
{ Text(stringResource(title)) },
|
||||
navigationIcon = { NavIcon(onNavigateUp) },
|
||||
actions = {
|
||||
var expand by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
IconButton({
|
||||
expand = true
|
||||
}) {
|
||||
Icon(Icons.Default.MoreVert, null)
|
||||
}
|
||||
DropdownMenu(expand, { expand = false }) {
|
||||
groups.forEach {
|
||||
DropdownMenuItem(
|
||||
{ Text("(${it.apps.size}) ${it.name}") },
|
||||
{
|
||||
selectedGroup = it
|
||||
expand = false
|
||||
}
|
||||
)
|
||||
}
|
||||
if (groups.isNotEmpty()) HorizontalDivider()
|
||||
DropdownMenuItem(
|
||||
{ Text(stringResource(R.string.manage_app_groups)) },
|
||||
{
|
||||
navigateToGroups()
|
||||
expand = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(Modifier.padding(paddingValues)) {
|
||||
items(packages, { it.name }) {
|
||||
ApplicationItem(it) {
|
||||
onSet(it.name, false)
|
||||
@@ -761,7 +817,6 @@ fun PackageFunctionScreen(
|
||||
Button(
|
||||
{
|
||||
if (onSet(packageName, true) != false) {
|
||||
println("reset")
|
||||
packageName = ""
|
||||
}
|
||||
},
|
||||
@@ -774,4 +829,152 @@ fun PackageFunctionScreen(
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectedGroup != null) AlertDialog(
|
||||
text = {
|
||||
Column {
|
||||
Button({
|
||||
selectedGroup!!.apps.forEach {
|
||||
onSet(it, true)
|
||||
}
|
||||
selectedGroup = null
|
||||
}) {
|
||||
Text(stringResource(R.string.add_to_list))
|
||||
}
|
||||
Button({
|
||||
selectedGroup!!.apps.forEach {
|
||||
onSet(it, false)
|
||||
}
|
||||
selectedGroup = null
|
||||
}) {
|
||||
Text(stringResource(R.string.remove_from_list))
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton({ selectedGroup = null }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { selectedGroup = null }
|
||||
)
|
||||
}
|
||||
|
||||
class AppGroup(val id: Int, val name: String, val apps: List<String>)
|
||||
|
||||
@Serializable object ManageAppGroups
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ManageAppGroupsScreen(
|
||||
appGroups: StateFlow<List<AppGroup>>,
|
||||
navigateToEditScreen: (Int?, String, List<String>) -> Unit, navigateUp: () -> Unit
|
||||
) {
|
||||
val groups by appGroups.collectAsStateWithLifecycle()
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
{ Text(stringResource(R.string.app_group)) },
|
||||
navigationIcon = { NavIcon(navigateUp) }
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton({
|
||||
navigateToEditScreen(null, "", emptyList())
|
||||
}) {
|
||||
Icon(Icons.Default.Add, null)
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(Modifier.padding(paddingValues)) {
|
||||
items(groups, { it.id }) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth().clickable {
|
||||
navigateToEditScreen(it.id, it.name, it.apps)
|
||||
}.padding(HorizontalPadding, 8.dp)
|
||||
) {
|
||||
Text(it.name)
|
||||
Text(
|
||||
it.apps.size.toString() + " apps", Modifier.alpha(0.7F),
|
||||
style = typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable class EditAppGroup(val id: Int?, val name: String, val apps: List<String>)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EditAppGroupScreen(
|
||||
params: EditAppGroup, getAppInfo: (String) -> AppInfo, navigateUp: () -> Unit,
|
||||
setGroup: (Int?, String, List<String>) -> Unit, deleteGroup: (Int) -> Unit,
|
||||
onChoosePackage: () -> Unit, chosenPackage: Channel<String>
|
||||
) {
|
||||
var name by rememberSaveable { mutableStateOf(params.name) }
|
||||
val list = rememberSaveable { mutableStateListOf(*params.apps.toTypedArray()) }
|
||||
val appInfoList = list.map { getAppInfo(it) }
|
||||
var packageName by rememberSaveable { mutableStateOf("") }
|
||||
LaunchedEffect(Unit) {
|
||||
packageName = chosenPackage.receive()
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
{ Text(stringResource(R.string.edit_app_group)) },
|
||||
navigationIcon = {
|
||||
NavIcon(navigateUp)
|
||||
},
|
||||
actions = {
|
||||
if (params.id != null) IconButton({
|
||||
deleteGroup(params.id)
|
||||
navigateUp()
|
||||
}) {
|
||||
Icon(Icons.Outlined.Delete, null)
|
||||
}
|
||||
IconButton(
|
||||
{
|
||||
setGroup(params.id, name, list)
|
||||
navigateUp()
|
||||
},
|
||||
enabled = name.isNotBlank() && list.isNotEmpty()
|
||||
) {
|
||||
Icon(Icons.Default.Check, null)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
contentWindowInsets = adaptiveInsets()
|
||||
) { paddingValues ->
|
||||
LazyColumn(Modifier.padding(paddingValues)) {
|
||||
item {
|
||||
OutlinedTextField(
|
||||
name, { name = it }, Modifier.fillMaxWidth().padding(HorizontalPadding, 8.dp),
|
||||
label = { Text(stringResource(R.string.name)) }
|
||||
)
|
||||
}
|
||||
items(appInfoList, { it.name }) {
|
||||
ApplicationItem(it) {
|
||||
list -= it.name
|
||||
}
|
||||
}
|
||||
item {
|
||||
PackageNameTextField(packageName, onChoosePackage,
|
||||
Modifier.padding(HorizontalPadding, 8.dp)) { packageName = it }
|
||||
Button(
|
||||
{
|
||||
list += packageName
|
||||
packageName = ""
|
||||
},
|
||||
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding).padding(bottom = 10.dp),
|
||||
packageName.isValidPackageName
|
||||
) {
|
||||
Text(stringResource(R.string.add))
|
||||
}
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,6 @@
|
||||
<!--Dhizuku-->
|
||||
<string name="failed_to_init_dhizuku">Не удалось инициализировать Dhizuku</string>
|
||||
<string name="dhizuku_permission_not_granted">Разрешение Dhizuku не предоставлено</string>
|
||||
<string name="dhizuku_mode_disabled">Режим Dhizuku отключен</string>
|
||||
|
||||
<!--Системные-->
|
||||
<string name="system">Система</string>
|
||||
|
||||
@@ -118,7 +118,6 @@
|
||||
<!--Dhizuku-->
|
||||
<string name="failed_to_init_dhizuku">Dhizuku Başlatılamadı</string>
|
||||
<string name="dhizuku_permission_not_granted">Dhizuku İzni Verilmedi</string>
|
||||
<string name="dhizuku_mode_disabled">Dhizuku Modu Devre Dışı</string>
|
||||
|
||||
<!--System-->
|
||||
<string name="system">Sistem</string>
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
<!--Dhizuku-->
|
||||
<string name="failed_to_init_dhizuku">Dhizuku初始化失败</string>
|
||||
<string name="dhizuku_permission_not_granted">Dhizuku未授权</string>
|
||||
<string name="dhizuku_mode_disabled">Dhizuku模式已禁用</string>
|
||||
|
||||
<!--System-->
|
||||
<string name="system">系统</string>
|
||||
@@ -370,6 +369,11 @@
|
||||
<string name="enable_system_app">启用系统应用</string>
|
||||
<string name="keep_after_uninstall">卸载后保留</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="app_group">应用组</string>
|
||||
<string name="manage_app_groups">管理组</string>
|
||||
<string name="edit_app_group">编辑组</string>
|
||||
<string name="add_to_list">添加到列表</string>
|
||||
<string name="remove_from_list">从列表中移除</string>
|
||||
|
||||
<!--UserRestriction-->
|
||||
<string name="user_restriction">用户限制</string>
|
||||
|
||||
@@ -122,7 +122,6 @@
|
||||
<string name="dhizuku" translatable="false">Dhizuku</string>
|
||||
<string name="failed_to_init_dhizuku">Failed to initialize Dhizuku</string>
|
||||
<string name="dhizuku_permission_not_granted">Dhizuku permission not granted</string>
|
||||
<string name="dhizuku_mode_disabled">Dhizuku mode disabled</string>
|
||||
|
||||
<string name="shizuku" translatable="false">Shizuku</string>
|
||||
|
||||
@@ -404,6 +403,11 @@
|
||||
<string name="install_existing_app">Install existing app</string>
|
||||
<string name="keep_after_uninstall">Keep after uninstall</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="app_group">App group</string>
|
||||
<string name="manage_app_groups">Manage groups</string>
|
||||
<string name="edit_app_group">Edit group</string>
|
||||
<string name="add_to_list">Add to list</string>
|
||||
<string name="remove_from_list">Remove from list</string>
|
||||
|
||||
<!--UserRestriction-->
|
||||
<string name="user_restriction">User restriction</string>
|
||||
|
||||
@@ -7,3 +7,4 @@ kotlin.code.style=official
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx1536M"
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
Reference in New Issue
Block a user