feat: app groups importing and exporting (#222)

Show group name in app group operation dialog (#224)
This commit is contained in:
BinTianqi
2026-01-15 13:04:52 +08:00
parent 2b58e56bbf
commit 3da523051b
7 changed files with 94 additions and 4 deletions

View File

@@ -642,7 +642,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
}
composable<ManageAppGroups> {
ManageAppGroupsScreen(
vm.appGroups,
vm.appGroups, vm::exportAppGroups, vm::importAppGroups,
{ id, name, apps -> navController.navigate(EditAppGroup(id, name, apps)) },
::navigateUp
)

View File

@@ -65,6 +65,7 @@ import com.bintianqi.owndroid.dpm.ApnProtocol
import com.bintianqi.owndroid.dpm.AppGroup
import com.bintianqi.owndroid.dpm.AppRestriction
import com.bintianqi.owndroid.dpm.AppStatus
import com.bintianqi.owndroid.dpm.BasicAppGroup
import com.bintianqi.owndroid.dpm.CaCertInfo
import com.bintianqi.owndroid.dpm.CreateUserResult
import com.bintianqi.owndroid.dpm.CreateWorkProfileOptions
@@ -119,6 +120,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.put
import java.net.InetAddress
import java.security.MessageDigest
import java.security.cert.CertificateException
@@ -632,6 +637,20 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
group.filter { it.id != id }
}
}
fun exportAppGroups(uri: Uri) {
application.contentResolver.openOutputStream(uri)!!.use {
val list: List<BasicAppGroup> = appGroups.value
it.write(Json.encodeToString(list).encodeToByteArray())
}
}
fun importAppGroups(uri: Uri) {
application.contentResolver.openInputStream(uri)!!.use {
Json.decodeFromString<List<BasicAppGroup>>(it.readBytes().decodeToString())
}.forEach {
myRepo.setAppGroup(null, it.name, it.apps)
}
getAppGroups()
}
@RequiresApi(24)
fun reboot() {

View File

@@ -5,8 +5,11 @@ import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
import android.app.admin.PackagePolicy
import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import android.os.Looper
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -904,6 +907,8 @@ fun PackageFunctionScreen(
if (dialog) AlertDialog(
text = {
Column {
Text(selectedGroup!!.name, style = typography.titleLarge)
Spacer(Modifier.height(6.dp))
Button({
onSet(selectedGroup!!.apps, true)
dialog = false
@@ -927,22 +932,68 @@ fun PackageFunctionScreen(
)
}
class AppGroup(val id: Int, val name: String, val apps: List<String>)
@Serializable
open class BasicAppGroup(open val name: String, open val apps: List<String>)
class AppGroup(
val id: Int, override val name: String, override val apps: List<String>
) : BasicAppGroup(name, apps)
@Serializable object ManageAppGroups
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ManageAppGroupsScreen(
appGroups: StateFlow<List<AppGroup>>,
appGroups: StateFlow<List<AppGroup>>, exportData: (Uri) -> Unit, importData: (Uri) -> Unit,
navigateToEditScreen: (Int?, String, List<String>) -> Unit, navigateUp: () -> Unit
) {
val groups by appGroups.collectAsStateWithLifecycle()
val exportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/json")
) {
if (it != null) exportData(it)
}
val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it != null) importData(it)
}
Scaffold(
topBar = {
TopAppBar(
{ Text(stringResource(R.string.app_group)) },
navigationIcon = { NavIcon(navigateUp) }
navigationIcon = { NavIcon(navigateUp) },
actions = {
var dropdown by remember { mutableStateOf(false) }
Box {
IconButton({
dropdown = true
}) {
Icon(Icons.Default.MoreVert, null)
}
DropdownMenu(dropdown, { dropdown = false }) {
DropdownMenuItem(
{ Text(stringResource(R.string.export)) },
{
exportLauncher.launch("owndroid_app_groups")
dropdown = false
},
leadingIcon = {
Icon(painterResource(R.drawable.file_export_fill0), null)
}
)
DropdownMenuItem(
{ Text(stringResource(R.string.import_str)) },
{
importLauncher.launch(arrayOf("application/json"))
dropdown = false
},
leadingIcon = {
Icon(painterResource(R.drawable.file_open_fill0), null)
}
)
}
}
}
)
},
floatingActionButton = {