Update Readme files

Change version code and version name
Update some strings
This commit is contained in:
BinTianqi
2024-11-30 17:07:02 +08:00
parent 690ac876ab
commit c90325e1c3
14 changed files with 180 additions and 152 deletions

View File

@@ -0,0 +1,672 @@
package com.bintianqi.owndroid.dpm
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Binder
import android.os.Build.VERSION
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
import android.provider.MediaStore
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.fileUriFlow
import com.bintianqi.owndroid.getFile
import com.bintianqi.owndroid.parseTimestamp
import com.bintianqi.owndroid.toggle
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.CardItem
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.ListItem
import com.bintianqi.owndroid.ui.SubPageItem
import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar
import com.bintianqi.owndroid.uriToStream
import com.bintianqi.owndroid.yesOrNo
@Composable
fun UserManage(navCtrl: NavHostController) {
val localNavCtrl = rememberNavController()
val backStackEntry by localNavCtrl.currentBackStackEntryAsState()
val scrollState = rememberScrollState()
Scaffold(
topBar = {
TopBar(backStackEntry, navCtrl, localNavCtrl) {
if(backStackEntry?.destination?.route == "Home" && scrollState.maxValue > 100) {
Text(
text = stringResource(R.string.users),
modifier = Modifier.alpha((maxOf(scrollState.value-30, 0)).toFloat() / 80)
)
}
}
}
) {
NavHost(
navController = localNavCtrl, startDestination = "Home",
enterTransition = Animations.navHostEnterTransition,
exitTransition = Animations.navHostExitTransition,
popEnterTransition = Animations.navHostPopEnterTransition,
popExitTransition = Animations.navHostPopExitTransition,
modifier = Modifier.padding(top = it.calculateTopPadding())
) {
composable(route = "Home") { Home(localNavCtrl, scrollState) }
composable(route = "UserInfo") { CurrentUserInfo() }
composable(route = "Options") { Options() }
composable(route = "UserOperation") { UserOperation() }
composable(route = "CreateUser") { CreateUser() }
composable(route = "EditUsername") { Username() }
composable(route = "ChangeUserIcon") { UserIcon() }
composable(route = "UserSessionMessage") { UserSessionMessage() }
composable(route = "AffiliationID") { AffiliationID() }
}
}
}
@Composable
private fun Home(navCtrl: NavHostController,scrollState: ScrollState) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner
//var logoutDialog by remember { mutableStateOf(false) }
var dialog by remember { mutableIntStateOf(0) }
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
Text(
text = stringResource(R.string.users),
style = typography.headlineLarge,
modifier = Modifier.padding(top = 8.dp, bottom = 5.dp, start = 16.dp)
)
SubPageItem(R.string.user_info, "", R.drawable.person_fill0) { navCtrl.navigate("UserInfo") }
if(deviceOwner && VERSION.SDK_INT >= 28) {
SubPageItem(R.string.secondary_users, "", R.drawable.list_fill0) { dialog = 1 }
SubPageItem(R.string.options, "", R.drawable.tune_fill0) { navCtrl.navigate("Options") }
}
if(deviceOwner) {
SubPageItem(R.string.user_operation, "", R.drawable.sync_alt_fill0) { navCtrl.navigate("UserOperation") }
}
if(VERSION.SDK_INT >= 24 && deviceOwner) {
SubPageItem(R.string.create_user, "", R.drawable.person_add_fill0) { navCtrl.navigate("CreateUser") }
}
if(VERSION.SDK_INT >= 28 && profileOwner && dpm.isAffiliatedUser) {
SubPageItem(R.string.logout_current_user, "", R.drawable.logout_fill0) { dialog = 2 }
}
if(deviceOwner || profileOwner) {
SubPageItem(R.string.change_username, "", R.drawable.edit_fill0) { navCtrl.navigate("EditUsername") }
}
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
SubPageItem(R.string.change_user_icon, "", R.drawable.account_circle_fill0) { navCtrl.navigate("ChangeUserIcon") }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
SubPageItem(R.string.user_session_msg, "", R.drawable.notifications_fill0) { navCtrl.navigate("UserSessionMessage") }
}
if(VERSION.SDK_INT >= 26 && (deviceOwner || profileOwner)) {
SubPageItem(R.string.affiliation_id, "", R.drawable.id_card_fill0) { navCtrl.navigate("AffiliationID") }
}
Spacer(Modifier.padding(vertical = 30.dp))
LaunchedEffect(Unit) { fileUriFlow.value = Uri.parse("") }
}
if(dialog != 0 && VERSION.SDK_INT >= 28) AlertDialog(
title = { Text(stringResource(if(dialog == 1) R.string.secondary_users else R.string.logout_current_user)) },
text = {
if(dialog == 1) {
val um = context.getSystemService(Context.USER_SERVICE) as UserManager
val list = dpm.getSecondaryUsers(receiver)
Column {
if(list.isEmpty()) {
Text(stringResource(R.string.no_secondary_users))
} else {
Text("(" + stringResource(R.string.serial_number) + ")")
list.forEach {
Text(um.getSerialNumberForUser(it).toString())
}
}
}
}
},
confirmButton = {
TextButton(
onClick = {
if(dialog == 2) {
val result = dpm.logoutUser(receiver)
Toast.makeText(context, userOperationResultCode(result), Toast.LENGTH_SHORT).show()
}
dialog = 0
}
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
if(dialog != 1) TextButton(onClick = { dialog = 0 }) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = { dialog = 0 }
)
}
@Composable
private fun Options() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) {
if(VERSION.SDK_INT >= 28) {
SwitchItem(R.string.enable_logout, "", null, { dpm.isLogoutEnabled }, { dpm.setLogoutEnabled(receiver, it) })
}
}
}
@Composable
private fun CurrentUserInfo() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
val user = Process.myUserHandle()
var infoDialog by remember { mutableIntStateOf(0) }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.user_info), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
if(VERSION.SDK_INT >= 24) CardItem(R.string.support_multiuser, UserManager.supportsMultipleUsers().yesOrNo)
if(VERSION.SDK_INT >= 31) CardItem(R.string.headless_system_user_mode, UserManager.isHeadlessSystemUserMode().yesOrNo) { infoDialog = 1 }
Spacer(Modifier.padding(vertical = 8.dp))
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 >= 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 >= 28) {
CardItem(R.string.logout_enabled, dpm.isLogoutEnabled.yesOrNo)
if(context.isDeviceOwner || context.isProfileOwner) {
CardItem(R.string.ephemeral_user, dpm.isEphemeralUser(receiver).yesOrNo)
}
CardItem(R.string.affiliated_user, dpm.isAffiliatedUser.yesOrNo)
}
CardItem(R.string.user_id, (Binder.getCallingUid() / 100000).toString())
CardItem(R.string.user_serial_number, userManager.getSerialNumberForUser(Process.myUserHandle()).toString())
Spacer(Modifier.padding(vertical = 30.dp))
}
if(infoDialog != 0) AlertDialog(
text = { Text(stringResource(R.string.info_headless_system_user_mode)) },
confirmButton = {
TextButton(onClick = { infoDialog = 0 }) {
Text(stringResource(R.string.confirm))
}
},
onDismissRequest = { infoDialog = 0 }
)
}
@Composable
private fun UserOperation() {
val context = LocalContext.current
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
val dpm = context.getDPM()
val receiver = context.getReceiver()
var idInput by remember { mutableStateOf("") }
var useUid by remember { mutableStateOf(false) }
val focusMgr = LocalFocusManager.current
fun withUserHandle(operation: (UserHandle) -> Unit) {
val userHandle = if(useUid && VERSION.SDK_INT >= 24) {
UserHandle.getUserHandleForUid(idInput.toInt())
} else {
userManager.getUserForSerialNumber(idInput.toLong())
}
if(userHandle == null) {
Toast.makeText(context, R.string.user_not_exist, Toast.LENGTH_SHORT).show()
} else {
operation(userHandle)
}
}
val legalInput = try {
idInput.toInt()
true
} catch(_: Exception) {
false
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.user_operation), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = idInput,
onValueChange = {
idInput = it
},
label = { Text(if(useUid) "UID" else stringResource(R.string.serial_number)) },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() })
)
Spacer(Modifier.padding(vertical = 3.dp))
if(VERSION.SDK_INT >= 24) {
CheckBoxItem(text = R.string.use_uid, checked = useUid, operation = { idInput=""; useUid = it })
}
if(VERSION.SDK_INT >= 28) {
Button(
onClick = {
focusMgr.clearFocus()
withUserHandle {
val result = dpm.startUserInBackground(receiver, it)
Toast.makeText(context, userOperationResultCode(result), Toast.LENGTH_SHORT).show()
}
},
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.start_in_background))
}
}
Button(
onClick = {
focusMgr.clearFocus()
withUserHandle {
Toast.makeText(context, if(dpm.switchUser(receiver, it)) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
}
},
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.user_operation_switch))
}
if(VERSION.SDK_INT >= 28) {
Button(
onClick = {
focusMgr.clearFocus()
withUserHandle {
val result = dpm.stopUser(receiver, it)
Toast.makeText(context, userOperationResultCode(result), Toast.LENGTH_SHORT).show()
}
},
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.stop))
}
}
Button(
onClick = {
focusMgr.clearFocus()
withUserHandle {
if(dpm.removeUser(receiver, it)) {
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
idInput = ""
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
}
},
enabled = legalInput,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.delete))
}
InfoCard(R.string.info_user_operation)
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun CreateUser() {
val context = LocalContext.current
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var userName by remember { mutableStateOf("") }
val flags = remember { mutableStateListOf<Int>() }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.create_user), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = userName,
onValueChange = { userName= it },
label = { Text(stringResource(R.string.username)) },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() })
)
Spacer(Modifier.padding(vertical = 5.dp))
CheckBoxItem(
R.string.create_user_skip_wizard,
DevicePolicyManager.SKIP_SETUP_WIZARD in flags,
{ flags.toggle(it, DevicePolicyManager.SKIP_SETUP_WIZARD) }
)
if(VERSION.SDK_INT >= 28) {
CheckBoxItem(
R.string.create_user_ephemeral_user,
DevicePolicyManager.MAKE_USER_EPHEMERAL in flags,
{ flags.toggle(it, DevicePolicyManager.MAKE_USER_EPHEMERAL) }
)
CheckBoxItem(
R.string.create_user_enable_all_system_app,
DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED in flags,
{ flags.toggle(it, DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED) }
)
}
var newUserHandle: UserHandle? by remember { mutableStateOf(null) }
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
focusMgr.clearFocus()
newUserHandle = dpm.createAndManageUser(receiver, userName, receiver, null, flags.sum())
Toast.makeText(context, if(newUserHandle!=null) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.create))
}
Spacer(Modifier.padding(vertical = 5.dp))
if(newUserHandle != null) { Text(text = stringResource(R.string.serial_number_of_new_user_is, userManager.getSerialNumberForUser(newUserHandle))) }
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun AffiliationID() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var input by remember { mutableStateOf("") }
val list = remember { mutableStateListOf<String>() }
val refreshIds = {
list.clear()
list.addAll(dpm.getAffiliationIds(receiver))
}
LaunchedEffect(Unit) { refreshIds() }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.affiliation_id), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
Column(modifier = Modifier.animateContentSize()) {
if(list.isEmpty()) Text(stringResource(R.string.none))
for(i in list) {
ListItem(i) { list -= i }
}
}
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = input,
onValueChange = { input = it },
label = { Text("ID") },
trailingIcon = {
IconButton(
onClick = {
list += input
input = ""
}
) {
Icon(imageVector = Icons.Default.Add, contentDescription = stringResource(R.string.add))
}
},
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() })
)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
list.removeAll(listOf(""))
dpm.setAffiliationIds(receiver, list.toSet())
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
refreshIds()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
InfoCard(R.string.info_affiliated_id)
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@Composable
private fun Username() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var inputUsername by remember { mutableStateOf("") }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.change_username), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = inputUsername,
onValueChange = { inputUsername= it },
label = { Text(stringResource(R.string.username)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
dpm.setProfileName(receiver, inputUsername)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.apply))
}
Button(
onClick = { dpm.setProfileName(receiver,null) },
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.reset))
}
}
}
@SuppressLint("NewApi")
@Composable
private fun UserSessionMessage() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
var start by remember { mutableStateOf("") }
var end by remember { mutableStateOf("") }
val refreshMsg = {
start = dpm.getStartUserSessionMessage(receiver)?.toString() ?: ""
end = dpm.getEndUserSessionMessage(receiver)?.toString() ?: ""
}
LaunchedEffect(Unit) { refreshMsg() }
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.user_session_msg), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
OutlinedTextField(
value = start,
onValueChange = { start= it },
label = { Text(stringResource(R.string.start_user_session_msg)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = {focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
dpm.setStartUserSessionMessage(receiver,start)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.apply))
}
Button(
onClick = {
dpm.setStartUserSessionMessage(receiver,null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.reset))
}
}
Spacer(Modifier.padding(vertical = 8.dp))
OutlinedTextField(
value = end,
onValueChange = { end= it },
label = { Text(stringResource(R.string.end_user_session_msg)) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth().padding(bottom = 2.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = {
dpm.setEndUserSessionMessage(receiver,end)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.apply))
}
Button(
onClick = {
dpm.setEndUserSessionMessage(receiver,null)
refreshMsg()
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.reset))
}
}
Spacer(Modifier.padding(vertical = 30.dp))
}
}
@SuppressLint("NewApi")
@Composable
private fun UserIcon() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
var getContent by remember { mutableStateOf(false) }
var bitmap by remember { mutableStateOf<Bitmap?>(null) }
val uriState by fileUriFlow.collectAsState()
LaunchedEffect(uriState) {
if(uriState == Uri.parse("")) return@LaunchedEffect
uriToStream(context, fileUriFlow.value) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
}
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.change_user_icon), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
CheckBoxItem(R.string.file_picker_instead_gallery, getContent, { getContent = it })
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val intent = Intent(if(getContent) Intent.ACTION_GET_CONTENT else Intent.ACTION_PICK)
if(getContent) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*")
getFile.launch(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.select_picture))
}
AnimatedVisibility(visible = bitmap != null, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Card(modifier = Modifier.padding(top = 8.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(10.dp)) {
Image(
bitmap = bitmap!!.asImageBitmap(), contentDescription = "User icon",
modifier = Modifier.padding(end = 12.dp).size(80.dp).clip(RoundedCornerShape(50))
)
Button(
onClick = {
dpm.setUserIcon(receiver, bitmap)
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}
) {
Text(stringResource(R.string.apply))
}
}
}
}
}
}
@StringRes
private fun userOperationResultCode(result:Int): Int =
when(result) {
UserManager.USER_OPERATION_SUCCESS -> R.string.success
UserManager.USER_OPERATION_ERROR_UNKNOWN -> R.string.unknown_error
UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE-> R.string.fail_managed_profile
UserManager.USER_OPERATION_ERROR_CURRENT_USER-> R.string.fail_current_user
else -> R.string.unknown
}