More options in app installer

Add download links to READMEs
This commit is contained in:
BinTianqi
2025-02-15 21:03:32 +08:00
parent 7995bfbdfe
commit d8044ee8ab
11 changed files with 204 additions and 52 deletions

View File

@@ -4,6 +4,11 @@
Use Android Device owner privilege to manage your device. Use Android Device owner privilege to manage your device.
## Download
[IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/com.bintianqi.owndroid)
[Releases on GitHub](https://github.com/BinTianqi/OwnDroid/releases)
## Features ## Features
- System - System

View File

@@ -4,6 +4,11 @@
使用安卓Device owner特权管理你的设备。 使用安卓Device owner特权管理你的设备。
## 下载
[IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/com.bintianqi.owndroid)
[Releases on GitHub](https://github.com/BinTianqi/OwnDroid/releases)
## 功能 ## 功能
- 系统 - 系统

View File

@@ -6,6 +6,7 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@@ -17,14 +18,21 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
@@ -38,11 +46,17 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -55,7 +69,8 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
import com.bintianqi.owndroid.ui.RadioButtonItem import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
@@ -76,13 +91,13 @@ class AppInstallerActivity:FragmentActivity() {
val theme by myVm.theme.collectAsStateWithLifecycle() val theme by myVm.theme.collectAsStateWithLifecycle()
OwnDroidTheme(theme) { OwnDroidTheme(theme) {
val installing by vm.installing.collectAsStateWithLifecycle() val installing by vm.installing.collectAsStateWithLifecycle()
val sessionMode by vm.sessionMode.collectAsStateWithLifecycle() val options by vm.options.collectAsStateWithLifecycle()
val packages by vm.packages.collectAsStateWithLifecycle() val packages by vm.packages.collectAsStateWithLifecycle()
val writtenPackages by vm.writtenPackages.collectAsStateWithLifecycle() val writtenPackages by vm.writtenPackages.collectAsStateWithLifecycle()
val writingPackage by vm.writingPackage.collectAsStateWithLifecycle() val writingPackage by vm.writingPackage.collectAsStateWithLifecycle()
val result by vm.result.collectAsStateWithLifecycle() val result by vm.result.collectAsStateWithLifecycle()
AppInstaller( AppInstaller(
installing, sessionMode, { vm.sessionMode.value = it }, installing, options, { if(!installing) vm.options.value = it },
packages, { uri -> vm.packages.update { it.minus(uri) } }, packages, { uri -> vm.packages.update { it.minus(uri) } },
{ uris -> vm.packages.update { it.plus(uris) } }, { uris -> vm.packages.update { it.plus(uris) } },
{ vm.startInstallationProcess(this) }, writtenPackages, writingPackage, { vm.startInstallationProcess(this) }, writtenPackages, writingPackage,
@@ -98,8 +113,8 @@ class AppInstallerActivity:FragmentActivity() {
@Composable @Composable
private fun AppInstaller( private fun AppInstaller(
installing: Boolean = false, installing: Boolean = false,
sessionMode: Int = PackageInstaller.SessionParams.MODE_INHERIT_EXISTING, options: SessionParamsOptions = SessionParamsOptions(),
onSessionModeChoose: (Int) -> Unit = {}, onOptionsChange: (SessionParamsOptions) -> Unit = {},
packages: Set<Uri> = setOf(Uri.parse("https://example.com")), packages: Set<Uri> = setOf(Uri.parse("https://example.com")),
onPackageRemove: (Uri) -> Unit = {}, onPackageRemove: (Uri) -> Unit = {},
onPackageChoose: (List<Uri>) -> Unit = {}, onPackageChoose: (List<Uri>) -> Unit = {},
@@ -109,6 +124,7 @@ private fun AppInstaller(
result: Intent? = null, result: Intent? = null,
onResultDialogClose: () -> Unit = {} onResultDialogClose: () -> Unit = {}
) { ) {
val coroutine = rememberCoroutineScope()
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -127,46 +143,57 @@ private fun AppInstaller(
) )
} }
) { paddingValues -> ) { paddingValues ->
var tab by remember { mutableIntStateOf(0) }
val pagerState = rememberPagerState { 2 }
val scrollState = rememberScrollState()
Column(modifier = Modifier.padding(paddingValues)) { Column(modifier = Modifier.padding(paddingValues)) {
SessionMode(sessionMode, onSessionModeChoose) TabRow(tab) {
Packages(installing, packages, onPackageRemove, onPackageChoose, writtenPackages, writingPackage) Tab(
tab == 0,
onClick = {
tab = 0
coroutine.launch { scrollState.animateScrollTo(0) }
coroutine.launch { pagerState.animateScrollToPage(0) }
},
text = { Text(stringResource(R.string.packages)) }
)
Tab(
tab == 1,
onClick = {
tab = 1
coroutine.launch { scrollState.animateScrollTo(0) }
coroutine.launch { pagerState.animateScrollToPage(1) }
},
text = { Text(stringResource(R.string.options)) }
)
}
HorizontalPager(pagerState) { page ->
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState).padding(top = 8.dp)) {
if(page == 0) Packages(installing, packages, onPackageRemove, onPackageChoose, writtenPackages, writingPackage)
else Options(options, onOptionsChange)
}
}
ResultDialog(result, onResultDialogClose) ResultDialog(result, onResultDialogClose)
} }
} }
} }
@Composable
private fun SessionMode(mode: Int, onChoose: (Int) -> Unit) {
Text(
stringResource(R.string.mode), modifier = Modifier.padding(top = 10.dp, start = 8.dp),
style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary
)
RadioButtonItem(R.string.full_install, mode == PackageInstaller.SessionParams.MODE_FULL_INSTALL) {
onChoose(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
}
RadioButtonItem(R.string.inherit_existing, mode == PackageInstaller.SessionParams.MODE_INHERIT_EXISTING) {
onChoose(PackageInstaller.SessionParams.MODE_INHERIT_EXISTING)
}
}
@Composable @Composable
private fun Packages( private fun ColumnScope.Packages(
installing: Boolean, installing: Boolean,
packages: Set<Uri>, onRemove: (Uri) -> Unit, onChoose: (List<Uri>) -> Unit, packages: Set<Uri>, onRemove: (Uri) -> Unit, onChoose: (List<Uri>) -> Unit,
writtenPackages: Set<Uri>, writingPackage: Uri? writtenPackages: Set<Uri>, writingPackage: Uri?
) { ) {
val chooseSplitPackage = rememberLauncherForActivityResult(ActivityResultContracts.GetMultipleContents(), onChoose) val chooseSplitPackage = rememberLauncherForActivityResult(ActivityResultContracts.GetMultipleContents(), onChoose)
Text(
stringResource(R.string.packages), modifier = Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp),
style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary
)
packages.forEach { packages.forEach {
PackageItem( PackageItem(
it, installing, it, installing,
{ onRemove(it) }, it in writtenPackages, it == writingPackage { onRemove(it) }, it in writtenPackages, it == writingPackage
) )
} }
if(!installing) Row( AnimatedVisibility(!installing) {
Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).clickable { modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp).clickable {
chooseSplitPackage.launch(APK_MIME) chooseSplitPackage.launch(APK_MIME)
@@ -175,6 +202,7 @@ private fun Packages(
Icon(Icons.Default.Add, null, modifier = Modifier.padding(horizontal = 10.dp)) Icon(Icons.Default.Add, null, modifier = Modifier.padding(horizontal = 10.dp))
Text(stringResource(R.string.add_packages), style = MaterialTheme.typography.titleMedium) Text(stringResource(R.string.add_packages), style = MaterialTheme.typography.titleMedium)
} }
}
} }
@@ -198,6 +226,50 @@ private fun PackageItem(uri: Uri, installing: Boolean, onRemove: () -> Unit, isW
} }
} }
data class SessionParamsOptions(
val mode: Int = PackageInstaller.SessionParams.MODE_FULL_INSTALL,
val keepOriginalEnabledSetting: Boolean = false,
val noKill: Boolean = false,
val location: Int = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY,
)
@Composable
private fun ColumnScope.Options(options: SessionParamsOptions, onChange: (SessionParamsOptions) -> Unit) {
Text(
stringResource(R.string.mode), modifier = Modifier.padding(top = 10.dp, start = 8.dp, bottom = 4.dp),
style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary
)
FullWidthRadioButtonItem(R.string.full_install, options.mode == PackageInstaller.SessionParams.MODE_FULL_INSTALL) {
onChange(options.copy(mode = PackageInstaller.SessionParams.MODE_FULL_INSTALL, noKill = false))
}
FullWidthRadioButtonItem(R.string.inherit_existing, options.mode == PackageInstaller.SessionParams.MODE_INHERIT_EXISTING) {
onChange(options.copy(mode = PackageInstaller.SessionParams.MODE_INHERIT_EXISTING))
}
if(Build.VERSION.SDK_INT >= 34) {
AnimatedVisibility(options.mode == PackageInstaller.SessionParams.MODE_INHERIT_EXISTING) {
FullWidthCheckBoxItem(R.string.dont_kill_app, options.noKill) {
onChange(options.copy(noKill = it))
}
}
FullWidthCheckBoxItem(R.string.keep_original_enabled_setting, options.keepOriginalEnabledSetting) {
onChange(options.copy(keepOriginalEnabledSetting = it))
}
}
Text(
stringResource(R.string.install_location), modifier = Modifier.padding(top = 10.dp, start = 8.dp, bottom = 4.dp),
style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary
)
FullWidthRadioButtonItem(R.string.auto, options.location == PackageInfo.INSTALL_LOCATION_AUTO) {
onChange(options.copy(location = PackageInfo.INSTALL_LOCATION_AUTO))
}
FullWidthRadioButtonItem(R.string.internal_only, options.location == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
onChange(options.copy(location = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY))
}
FullWidthRadioButtonItem(R.string.prefer_external, options.location == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
onChange(options.copy(location = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL))
}
}
@Composable @Composable
private fun ResultDialog(result: Intent?, onDialogClose: () -> Unit) { private fun ResultDialog(result: Intent?, onDialogClose: () -> Unit) {
if(result != null) { if(result != null) {
@@ -236,7 +308,7 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
val result = MutableStateFlow<Intent?>(null) val result = MutableStateFlow<Intent?>(null)
val packages = MutableStateFlow(setOf<Uri>()) val packages = MutableStateFlow(setOf<Uri>())
val sessionMode = MutableStateFlow(PackageInstaller.SessionParams.MODE_FULL_INSTALL) val options = MutableStateFlow(SessionParamsOptions())
val writtenPackages = MutableStateFlow(setOf<Uri>()) val writtenPackages = MutableStateFlow(setOf<Uri>())
val writingPackage = MutableStateFlow<Uri?>(null) val writingPackage = MutableStateFlow<Uri?>(null)
@@ -254,13 +326,22 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
}) })
else startInstall() else startInstall()
} }
private fun getSessionParams(): PackageInstaller.SessionParams {
return PackageInstaller.SessionParams(options.value.mode).apply {
if(Build.VERSION.SDK_INT >= 34) {
if(options.value.keepOriginalEnabledSetting) setApplicationEnabledSettingPersistent()
setDontKillApp(options.value.noKill)
}
setInstallLocation(options.value.location)
}
}
private fun startInstall() { private fun startInstall() {
if(installing.value) return if(installing.value) return
installing.value = true installing.value = true
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val context = getApplication<Application>() val context = getApplication<Application>()
val packageInstaller = context.packageManager.packageInstaller val packageInstaller = context.packageManager.packageInstaller
val sessionId = packageInstaller.createSession(PackageInstaller.SessionParams(sessionMode.value)) val sessionId = packageInstaller.createSession(getSessionParams())
val session = packageInstaller.openSession(sessionId) val session = packageInstaller.openSession(sessionId)
try { try {
packages.value.forEach { splitPackageUri -> packages.value.forEach { splitPackageUri ->

View File

@@ -40,11 +40,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -122,14 +125,12 @@ fun ApplicationsScreen(onNavigateUp: () -> Unit) {
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }), keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
trailingIcon = { trailingIcon = {
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null, IconButton({
modifier = Modifier
.clip(RoundedCornerShape(50))
.clickable(onClick = {
focusMgr.clearFocus() focusMgr.clearFocus()
choosePackage.launch(null) choosePackage.launch(null)
}) }) {
.padding(3.dp)) Icon(Icons.AutoMirrored.Default.List, stringResource(R.string.package_chooser))
}
}, },
textStyle = typography.bodyLarge, textStyle = typography.bodyLarge,
singleLine = true singleLine = true
@@ -173,13 +174,8 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
val deviceOwner = context.isDeviceOwner val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner val profileOwner = context.isProfileOwner
var suspend by remember { mutableStateOf(false) } var suspend by remember { mutableStateOf(false) }
suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false }
catch(_: NameNotFoundException) { false }
catch(_: IllegalArgumentException) { false }
var hide by remember { mutableStateOf(false) } var hide by remember { mutableStateOf(false) }
hide = dpm.isApplicationHidden(receiver, pkgName)
var blockUninstall by remember { mutableStateOf(false) } var blockUninstall by remember { mutableStateOf(false) }
blockUninstall = dpm.isUninstallBlocked(receiver,pkgName)
var appControlAction by remember { mutableIntStateOf(0) } var appControlAction by remember { mutableIntStateOf(0) }
val focusMgr = LocalFocusManager.current val focusMgr = LocalFocusManager.current
val appControl: (Boolean) -> Unit = { val appControl: (Boolean) -> Unit = {
@@ -198,6 +194,13 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
3 -> blockUninstall = dpm.isUninstallBlocked(receiver,pkgName) 3 -> blockUninstall = dpm.isUninstallBlocked(receiver,pkgName)
} }
} }
LaunchedEffect(pkgName) {
suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false }
catch(_: NameNotFoundException) { false }
catch(_: IllegalArgumentException) { false }
hide = dpm.isApplicationHidden(receiver, pkgName)
blockUninstall = dpm.isUninstallBlocked(receiver,pkgName)
}
Column( Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
) { ) {

View File

@@ -20,7 +20,6 @@ import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST
import android.app.usage.NetworkStats import android.app.usage.NetworkStats
import android.app.usage.NetworkStatsManager import android.app.usage.NetworkStatsManager
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.IpConfiguration import android.net.IpConfiguration
@@ -82,6 +81,8 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -343,10 +344,12 @@ private fun SavedNetworks(onNavigateToUpdateNetwork: (Bundle) -> Unit) {
val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val configuredNetworks = remember { mutableStateListOf<WifiConfiguration>() } val configuredNetworks = remember { mutableStateListOf<WifiConfiguration>() }
var networkDetailsDialog by remember { mutableIntStateOf(-1) } // -1:Hidden, 0+:Index of configuredNetworks var networkDetailsDialog by remember { mutableIntStateOf(-1) } // -1:Hidden, 0+:Index of configuredNetworks
val coroutine = rememberCoroutineScope()
fun refresh() { fun refresh() {
configuredNetworks.clear() configuredNetworks.clear()
wm.configuredNetworks.forEach { network -> coroutine.launch(Dispatchers.IO) {
if(configuredNetworks.none { it.networkId == network.networkId }) configuredNetworks += network val list = wm.configuredNetworks.distinctBy { it.networkId }
withContext(Dispatchers.Main) { configuredNetworks.addAll(list) }
} }
} }
LaunchedEffect(Unit) { refresh() } LaunchedEffect(Unit) { refresh() }
@@ -439,6 +442,7 @@ private fun SavedNetworks(onNavigateToUpdateNetwork: (Bundle) -> Unit) {
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Icon(Icons.Default.Edit, null)
Text(stringResource(R.string.edit)) Text(stringResource(R.string.edit))
} }
TextButton( TextButton(
@@ -450,6 +454,7 @@ private fun SavedNetworks(onNavigateToUpdateNetwork: (Bundle) -> Unit) {
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error), colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Icon(Icons.Outlined.Delete, null)
Text(stringResource(R.string.remove)) Text(stringResource(R.string.remove))
} }
} }

View File

@@ -196,7 +196,9 @@ fun UserInfoScreen(onNavigateUp: () -> Unit) {
if(VERSION.SDK_INT >= 23) CardItem(R.string.system_user, userManager.isSystemUser.yesOrNo) if(VERSION.SDK_INT >= 23) CardItem(R.string.system_user, userManager.isSystemUser.yesOrNo)
if(VERSION.SDK_INT >= 34) CardItem(R.string.admin_user, userManager.isAdminUser.yesOrNo) if(VERSION.SDK_INT >= 34) CardItem(R.string.admin_user, userManager.isAdminUser.yesOrNo)
if(VERSION.SDK_INT >= 25) CardItem(R.string.demo_user, userManager.isDemoUser.yesOrNo) if(VERSION.SDK_INT >= 25) CardItem(R.string.demo_user, userManager.isDemoUser.yesOrNo)
if(VERSION.SDK_INT >= 26) CardItem(R.string.creation_time, parseTimestamp(userManager.getUserCreationTime(user))) if(VERSION.SDK_INT >= 26) userManager.getUserCreationTime(user).let {
if(it != 0L) CardItem(R.string.creation_time, parseTimestamp(it))
}
if (VERSION.SDK_INT >= 28) { if (VERSION.SDK_INT >= 28) {
CardItem(R.string.logout_enabled, dpm.isLogoutEnabled.yesOrNo) CardItem(R.string.logout_enabled, dpm.isLogoutEnabled.yesOrNo)
if(context.isDeviceOwner || context.isProfileOwner) { if(context.isDeviceOwner || context.isProfileOwner) {

View File

@@ -99,6 +99,21 @@ fun RadioButtonItem(
} }
} }
@Composable
fun FullWidthRadioButtonItem(
text: Int,
selected: Boolean,
operation: () -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable(onClick = operation)
) {
RadioButton(selected = selected, onClick = operation, modifier = Modifier.padding(horizontal = 4.dp))
Text(text = stringResource(text), modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp))
}
}
@Composable @Composable
fun CheckBoxItem( fun CheckBoxItem(
@StringRes text: Int, @StringRes text: Int,
@@ -118,6 +133,20 @@ fun CheckBoxItem(
} }
} }
@Composable
fun FullWidthCheckBoxItem(
@StringRes text: Int,
checked: Boolean,
operation: (Boolean) -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().clickable { operation(!checked) }
) {
Checkbox(checked = checked, onCheckedChange = operation, modifier = Modifier.padding(horizontal = 4.dp))
Text(text = stringResource(text), modifier = Modifier.padding(bottom = if(zhCN) { 2 } else { 0 }.dp))
}
}
@Composable @Composable
fun SwitchItem( fun SwitchItem(

View File

@@ -649,6 +649,12 @@
<string name="mode">Режим</string> <string name="mode">Режим</string>
<string name="full_install">Полная установка</string> <string name="full_install">Полная установка</string>
<string name="inherit_existing">Наследовать существующие</string> <string name="inherit_existing">Наследовать существующие</string>
<!--TODO 5 new strings-->
<string name="keep_original_enabled_setting">Keep original enabled setting</string>
<string name="dont_kill_app">Don\'t kill app</string>
<string name="install_location">Install location</string>
<string name="internal_only">Internal only</string>
<string name="prefer_external">Prefer external</string>
<string name="packages">Пакеты</string> <string name="packages">Пакеты</string>
<string name="add_packages">Добавить пакет(ы)</string> <string name="add_packages">Добавить пакет(ы)</string>
<string name="status_failure_blocked">Операция была заблокирована: %1$s</string> <string name="status_failure_blocked">Операция была заблокирована: %1$s</string>

View File

@@ -652,6 +652,11 @@
<string name="mode">Mode</string> <string name="mode">Mode</string>
<string name="full_install">Full install</string> <string name="full_install">Full install</string>
<string name="inherit_existing">Inherit existing</string> <string name="inherit_existing">Inherit existing</string>
<string name="keep_original_enabled_setting">Keep original enabled setting</string>
<string name="dont_kill_app">Don\'t kill app</string>
<string name="install_location">Install location</string>
<string name="internal_only">Internal only</string>
<string name="prefer_external">Prefer external</string>
<string name="packages">Packages</string> <string name="packages">Packages</string>
<string name="add_packages">Add package(s)</string> <string name="add_packages">Add package(s)</string>
<string name="status_failure_blocked">The operation was blocked by: %1$s</string> <string name="status_failure_blocked">The operation was blocked by: %1$s</string>

View File

@@ -631,10 +631,16 @@
<string name="permission_BODY_SENSORS_BACKGROUND">后台使用身体传感器</string> <string name="permission_BODY_SENSORS_BACKGROUND">后台使用身体传感器</string>
<string name="permission_ACTIVITY_RECOGNITION">查看使用情况</string> <string name="permission_ACTIVITY_RECOGNITION">查看使用情况</string>
<string name="package_chooser">包选择器</string>
<string name="app_installer">App安装器</string> <string name="app_installer">App安装器</string>
<string name="mode">模式</string> <string name="mode">模式</string>
<string name="full_install">完整安装</string> <string name="full_install">完整安装</string>
<string name="inherit_existing">继承已有</string> <string name="inherit_existing">继承已有</string>
<string name="keep_original_enabled_setting">保持原始启用设置</string>
<string name="dont_kill_app">不要杀死app</string>
<string name="install_location">安装位置</string>
<string name="internal_only">仅内部</string>
<string name="prefer_external">外部优先</string>
<string name="packages">软件包</string> <string name="packages">软件包</string>
<string name="add_packages">添加包</string> <string name="add_packages">添加包</string>
<string name="status_failure_blocked">操作被 %1$s 阻止。</string> <string name="status_failure_blocked">操作被 %1$s 阻止。</string>

View File

@@ -676,6 +676,11 @@
<string name="mode">Mode</string> <string name="mode">Mode</string>
<string name="full_install">Full install</string> <string name="full_install">Full install</string>
<string name="inherit_existing">Inherit existing</string> <string name="inherit_existing">Inherit existing</string>
<string name="keep_original_enabled_setting">Keep original enabled setting</string>
<string name="dont_kill_app">Don\'t kill app</string>
<string name="install_location">Install location</string>
<string name="internal_only">Internal only</string>
<string name="prefer_external">Prefer external</string>
<string name="packages">Packages</string> <string name="packages">Packages</string>
<string name="add_packages">Add package(s)</string> <string name="add_packages">Add package(s)</string>
<string name="status_failure_blocked">The operation was blocked by: %1$s</string> <string name="status_failure_blocked">The operation was blocked by: %1$s</string>