Confirm changing user icon in dialog

Disable API on deactivate
Fix changing theme
Fix auth in app installer
This commit is contained in:
BinTianqi
2025-02-15 19:14:38 +08:00
parent 44d2ab7e2e
commit 7995bfbdfe
11 changed files with 131 additions and 196 deletions

View File

@@ -241,7 +241,8 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
val writtenPackages = MutableStateFlow(setOf<Uri>())
val writingPackage = MutableStateFlow<Uri?>(null)
fun startInstallationProcess(activity: FragmentActivity) {
startAuth(activity, object : BiometricPrompt.AuthenticationCallback() {
val sp = SharedPrefs(getApplication<Application>())
if(sp.auth) startAuth(activity, object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
startInstall()
@@ -251,6 +252,7 @@ class AppInstallerViewModel(application: Application): AndroidViewModel(applicat
Toast.makeText(activity, R.string.failed_to_authenticate, Toast.LENGTH_SHORT).show()
}
})
else startInstall()
}
private fun startInstall() {
if(installing.value) return

View File

@@ -76,8 +76,6 @@ import com.bintianqi.owndroid.dpm.ChangeTime
import com.bintianqi.owndroid.dpm.ChangeTimeScreen
import com.bintianqi.owndroid.dpm.ChangeTimeZone
import com.bintianqi.owndroid.dpm.ChangeTimeZoneScreen
import com.bintianqi.owndroid.dpm.ChangeUserIcon
import com.bintianqi.owndroid.dpm.ChangeUserIconScreen
import com.bintianqi.owndroid.dpm.ChangeUsername
import com.bintianqi.owndroid.dpm.ChangeUsernameScreen
import com.bintianqi.owndroid.dpm.ContentProtectionPolicy
@@ -255,31 +253,19 @@ class MainActivity : FragmentActivity() {
@ExperimentalMaterial3Api
@Composable
fun Home(activity: FragmentActivity, vm: MyViewModel) {
val navCtrl = rememberNavController()
val navController = rememberNavController()
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val sp = SharedPrefs(context)
val focusMgr = LocalFocusManager.current
val backToHome by backToHomeStateFlow.collectAsState()
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(backToHome) {
if(backToHome) { navCtrl.navigateUp(); backToHomeStateFlow.value = false }
if(backToHome) { navController.navigateUp(); backToHomeStateFlow.value = false }
}
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
fun onUserRestrictionsChange(id: String, status: Boolean) {
try {
if(status) dpm.addUserRestriction(receiver, id)
else dpm.clearUserRestriction(receiver, id)
@SuppressLint("NewApi")
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
} catch(_: Exception) {
context.showOperationResultToast(false)
}
}
fun navigateUp() { navCtrl.navigateUp() }
fun navigateUp() { navController.navigateUp() }
@Suppress("NewApi") NavHost(
navController = navCtrl,
navController = navController,
startDestination = Home,
modifier = Modifier
.fillMaxSize()
@@ -291,15 +277,15 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
popEnterTransition = Animations.navHostPopEnterTransition,
popExitTransition = Animations.navHostPopExitTransition
) {
composable<Home> { HomeScreen { navCtrl.navigate(it) } }
composable<Home> { HomeScreen { navController.navigate(it) } }
composable<Permissions> {
PermissionsScreen(::navigateUp, { navCtrl.navigate(it) }) {
val dest = navCtrl.graph.findNode(ShizukuScreen)!!.id
navCtrl.navigate(dest, it)
PermissionsScreen(::navigateUp, { navController.navigate(it) }) {
val dest = navController.graph.findNode(ShizukuScreen)!!.id
navController.navigate(dest, it)
}
}
composable<ShizukuScreen> { ShizukuScreen(it.arguments!!, ::navigateUp) { navCtrl.navigate(it) } }
composable<ShizukuScreen> { ShizukuScreen(it.arguments!!, ::navigateUp) { navController.navigate(it) } }
composable<Accounts>(mapOf(serializableNavTypePair<List<Accounts.Account>>())) { AccountsScreen(it.toRoute(), ::navigateUp) }
composable<DeviceAdmin> { DeviceAdminScreen(::navigateUp) }
composable<ProfileOwner> { ProfileOwnerScreen(::navigateUp) }
@@ -310,7 +296,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
composable<TransferOwnership> { TransferOwnershipScreen(::navigateUp) }
composable<SystemManager> { SystemManagerScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<SystemManager> { SystemManagerScreen(::navigateUp) { navController.navigate(it) } }
composable<SystemOptions> { SystemOptionsScreen(::navigateUp) }
composable<Keyguard> { KeyguardScreen(::navigateUp) }
composable<HardwareMonitor> { HardwareMonitorScreen(::navigateUp) }
@@ -330,20 +316,20 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<FrpPolicy> { FrpPolicyScreen(::navigateUp) }
composable<WipeData> { WipeDataScreen(::navigateUp) }
composable<Network> { NetworkScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<Network> { NetworkScreen(::navigateUp) { navController.navigate(it) } }
composable<WiFi> {
WifiScreen(::navigateUp, { navCtrl.navigate(it) }) {
val dest = navCtrl.graph.findNode(AddNetwork)!!.id
navCtrl.navigate(dest, it)
WifiScreen(::navigateUp, { navController.navigate(it) }) {
val dest = navController.graph.findNode(AddNetwork)!!.id
navController.navigate(dest, it)
}
}
composable<NetworkOptions> { NetworkOptionsScreen(::navigateUp) }
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp) { navController.navigate(it) } }
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
NetworkStatsViewerScreen(it.toRoute()) { navCtrl.navigateUp() }
NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() }
}
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) }
@@ -353,7 +339,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) }
composable<OverrideApn> { OverrideApnScreen(::navigateUp) }
composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<WorkProfile> { WorkProfileScreen(::navigateUp) { navController.navigate(it) } }
composable<OrganizationOwnedProfile> { OrganizationOwnedProfileScreen(::navigateUp) }
composable<CreateWorkProfile> { CreateWorkProfileScreen(::navigateUp) }
composable<SuspendPersonalApp> { SuspendPersonalAppScreen(::navigateUp) }
@@ -364,27 +350,36 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<UserRestriction> {
LaunchedEffect(Unit) {
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
vm.userRestrictions.value = context.getDPM().getUserRestrictions(receiver)
}
UserRestrictionScreen(::navigateUp) { title, items ->
navCtrl.navigate(UserRestrictionOptions(title, items))
navController.navigate(UserRestrictionOptions(title, items))
}
}
composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) {
UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::onUserRestrictionsChange, ::navigateUp)
UserRestrictionOptionsScreen(it.toRoute(), userRestrictions, ::navigateUp) { id, status ->
try {
val dpm = context.getDPM()
if(status) dpm.addUserRestriction(receiver, id)
else dpm.clearUserRestriction(receiver, id)
@SuppressLint("NewApi")
vm.userRestrictions.value = dpm.getUserRestrictions(receiver)
} catch(_: Exception) {
context.showOperationResultToast(false)
}
}
}
composable<Users> { UsersScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<Users> { UsersScreen(::navigateUp) { navController.navigate(it) } }
composable<UserInfo> { UserInfoScreen(::navigateUp) }
composable<UsersOptions> { UsersOptionsScreen(::navigateUp) }
composable<UserOperation> { UserOperationScreen(::navigateUp) }
composable<CreateUser> { CreateUserScreen(::navigateUp) }
composable<ChangeUsername> { ChangeUsernameScreen(::navigateUp) }
composable<ChangeUserIcon> { ChangeUserIconScreen(::navigateUp) }
composable<UserSessionMessage> { UserSessionMessageScreen(::navigateUp) }
composable<AffiliationId> { AffiliationIdScreen(::navigateUp) }
composable<Password> { PasswordScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<Password> { PasswordScreen(::navigateUp) { navController.navigate(it) } }
composable<PasswordInfo> { PasswordInfoScreen(::navigateUp) }
composable<ResetPasswordToken> { ResetPasswordTokenScreen(::navigateUp) }
composable<ResetPassword> { ResetPasswordScreen(::navigateUp) }
@@ -392,7 +387,7 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<KeyguardDisabledFeatures> { KeyguardDisabledFeaturesScreen(::navigateUp) }
composable<RequiredPasswordQuality> { RequiredPasswordQualityScreen(::navigateUp) }
composable<Settings> { SettingsScreen(::navigateUp) { navCtrl.navigate(it) } }
composable<Settings> { SettingsScreen(::navigateUp) { navController.navigate(it) } }
composable<SettingsOptions> { SettingsOptionsScreen(::navigateUp) }
composable<Appearance> {
val theme by vm.theme.collectAsStateWithLifecycle()
@@ -409,11 +404,12 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
}
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
val sp = SharedPrefs(context)
if(
(event == Lifecycle.Event.ON_RESUME && sp.auth && sp.lockInBackground) ||
(event == Lifecycle.Event.ON_CREATE && sp.auth)
) {
navCtrl.navigate(Authenticate) { launchSingleTop = true }
navController.navigate(Authenticate) { launchSingleTop = true }
}
}
lifecycleOwner.lifecycle.addObserver(observer)
@@ -422,6 +418,8 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
}
}
LaunchedEffect(Unit) {
val dpm = context.getDPM()
val sp = SharedPrefs(context)
val profileNotActivated = !sp.managedProfileActivated && context.isProfileOwner && (VERSION.SDK_INT < 24 || dpm.isManagedProfile(receiver))
if(profileNotActivated) {
dpm.setProfileEnabled(receiver)

View File

@@ -11,7 +11,6 @@ import android.os.PersistableBundle
import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.isDeviceAdmin
import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner
import com.bintianqi.owndroid.dpm.processSecurityLogs
@@ -29,11 +28,12 @@ class Receiver : DeviceAdminReceiver() {
dpm.setLockTaskPackages(receiver, arrayOf())
dpm.setLockTaskPackages(receiver, packages)
}
if(!context.isDeviceOwner && !context.isProfileOwner) SharedPrefs(context).isApiEnabled = false
}
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
if(context.isDeviceAdmin || context.isProfileOwner || context.isDeviceOwner){
if(context.isProfileOwner || context.isDeviceOwner){
Toast.makeText(context, context.getString(R.string.onEnabled), Toast.LENGTH_SHORT).show()
}
}

View File

@@ -69,8 +69,9 @@ fun SettingsOptionsScreen(onNavigateUp: () -> Unit) {
@Serializable object Appearance
@Composable
fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) {
fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) {
var darkThemeMenu by remember { mutableStateOf(false) }
var theme by remember { mutableStateOf(currentTheme) }
val darkThemeTextID = when(theme.darkTheme) {
1 -> R.string.on
0 -> R.string.off
@@ -78,7 +79,11 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChan
}
MyScaffold(R.string.appearance, 0.dp, onNavigateUp) {
if(VERSION.SDK_INT >= 31) {
SwitchItem(R.string.material_you_color, state = theme.materialYou, onCheckedChange = { onThemeChange(theme.copy(materialYou = it)) })
SwitchItem(
R.string.material_you_color,
state = theme.materialYou,
onCheckedChange = { theme = theme.copy(materialYou = it) }
)
}
Box {
FunctionItem(R.string.dark_theme, stringResource(darkThemeTextID)) { darkThemeMenu = true }
@@ -89,28 +94,33 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, theme: ThemeSettings, onThemeChan
DropdownMenuItem(
text = { Text(stringResource(R.string.follow_system)) },
onClick = {
onThemeChange(theme.copy(darkTheme = -1))
theme = theme.copy(darkTheme = -1)
darkThemeMenu = false
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.on)) },
onClick = {
onThemeChange(theme.copy(darkTheme = 1))
theme = theme.copy(darkTheme = 1)
darkThemeMenu = false
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.off)) },
onClick = {
onThemeChange(theme.copy(darkTheme = 0))
theme = theme.copy(darkTheme = 0)
darkThemeMenu = false
}
)
}
}
AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) {
SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { onThemeChange(theme.copy(blackTheme = it)) })
SwitchItem(R.string.black_theme, state = theme.blackTheme, onCheckedChange = { theme = theme.copy(blackTheme = it) })
}
AnimatedVisibility(theme != currentTheme, Modifier.fillMaxWidth().padding(8.dp)) {
Button({onThemeChange(theme)}) {
Text(stringResource(R.string.apply))
}
}
}
}

View File

@@ -1598,14 +1598,12 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
val logFile = context.filesDir.resolve("NetworkLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) { fileSize = logFile.length() }
val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".encodeToByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".encodeToByteArray())
context.showOperationResultToast(true)
}
val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".encodeToByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".encodeToByteArray())
context.showOperationResultToast(true)
}
}
MyScaffold(R.string.network_logging, 8.dp, onNavigateUp) {
@@ -1619,11 +1617,7 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button(
onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
exportNetworkLogsLauncher.launch(intent)
exportNetworkLogsLauncher.launch("NetworkLogs.json")
},
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)

View File

@@ -1299,21 +1299,17 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) { fileSize = logFile.length() }
var preRebootSecurityLogs by remember { mutableStateOf(byteArrayOf()) }
val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
preRebootSecurityLogs.inputStream().copyTo(outStream)
}
val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
preRebootSecurityLogs.inputStream().copyTo(outStream)
}
}
val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
result.data?.data?.let { uri ->
context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".toByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".toByteArray())
context.showOperationResultToast(true)
}
val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".toByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".toByteArray())
context.showOperationResultToast(true)
}
}
MyScaffold(R.string.security_logging, 8.dp, onNavigateUp) {
@@ -1326,11 +1322,7 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button(
onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "SecurityLogs.json")
exportSecurityLogs.launch(intent)
exportSecurityLogs.launch("SecurityLogs.json")
},
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
@@ -1362,11 +1354,7 @@ fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
processSecurityLogs(logs, outputStream)
outputStream.write("]".encodeToByteArray())
preRebootSecurityLogs = outputStream.toByteArray()
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "PreRebootSecurityLogs.json")
exportPreRebootSecurityLogs.launch(intent)
exportPreRebootSecurityLogs.launch("PreRebootSecurityLogs.json")
}
},
modifier = Modifier.fillMaxWidth()
@@ -1742,16 +1730,11 @@ fun InstallSystemUpdateScreen(onNavigateUp: () -> Unit) {
}
}
var uri by remember { mutableStateOf<Uri?>(null) }
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
uri = it.data?.data
}
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri = it }
MyScaffold(R.string.install_system_update, 8.dp, onNavigateUp) {
Button(
onClick = {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.setType("application/zip")
intent.addCategory(Intent.CATEGORY_OPENABLE)
getFileLauncher.launch(intent)
getFileLauncher.launch("application/zip")
},
modifier = Modifier.fillMaxWidth()
) {

View File

@@ -73,7 +73,7 @@ data class UserRestrictionOptions(
@Composable
fun UserRestrictionOptionsScreen(
data: UserRestrictionOptions, restrictions: Bundle,
onRestrictionChange: (String, Boolean) -> Unit, onNavigateUp: () -> Unit
onNavigateUp: () -> Unit, onRestrictionChange: (String, Boolean) -> Unit
) {
MyScaffold(data.title, 0.dp, onNavigateUp, false) {
data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction ->

View File

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Binder
@@ -10,13 +9,11 @@ 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.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -36,7 +33,6 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -56,7 +52,6 @@ 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.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
@@ -115,7 +110,16 @@ fun UsersScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
FunctionItem(R.string.change_username, icon = R.drawable.edit_fill0) { onNavigate(ChangeUsername) }
}
if(VERSION.SDK_INT >= 23 && (deviceOwner || profileOwner)) {
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { onNavigate(ChangeUserIcon) }
var changeUserIconDialog by remember { mutableStateOf(false) }
var bitmap: Bitmap? by remember { mutableStateOf(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
if(it != null) uriToStream(context, it) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
if(bitmap != null) changeUserIconDialog = true
}
}
FunctionItem(R.string.change_user_icon, icon = R.drawable.account_circle_fill0) { launcher.launch("image/*") }
if(changeUserIconDialog == true) ChangeUserIconDialog(bitmap!!) { changeUserIconDialog = false }
}
if(VERSION.SDK_INT >= 28 && deviceOwner) {
FunctionItem(R.string.user_session_msg, icon = R.drawable.notifications_fill0) { onNavigate(UserSessionMessage) }
@@ -568,56 +572,36 @@ fun UserSessionMessageScreen(onNavigateUp: () -> Unit) {
}
}
@Serializable object ChangeUserIcon
@RequiresApi(23)
@Composable
fun ChangeUserIconScreen(onNavigateUp: () -> Unit) {
private fun ChangeUserIconDialog(bitmap: Bitmap, onClose: () -> Unit) {
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 getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
it.data?.data?.let {
uriToStream(context, it) { stream ->
bitmap = BitmapFactory.decodeStream(stream)
AlertDialog(
title = { Text(stringResource(R.string.change_user_icon)) },
text = {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Image(
bitmap = bitmap.asImageBitmap(), contentDescription = null,
modifier = Modifier.size(80.dp).clip(RoundedCornerShape(50))
)
}
}
}
MyScaffold(R.string.change_user_icon, 8.dp, onNavigateUp) {
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/*")
getFileLauncher.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)
context.showOperationResultToast(true)
}
) {
Text(stringResource(R.string.apply))
}
}
},
confirmButton = {
TextButton({
context.getDPM().setUserIcon(context.getReceiver(), bitmap)
context.showOperationResultToast(true)
onClose()
}) {
Text(stringResource(R.string.confirm))
}
}
}
},
dismissButton = {
TextButton(onClose) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = onClose
)
}
@StringRes

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.*
@@ -67,16 +68,10 @@ fun FunctionItem(
}
@Composable
fun NavIcon(operation: () -> Unit) {
Icon(
painter = painterResource(R.drawable.arrow_back_fill0),
contentDescription = "Back arrow",
modifier = Modifier
.padding(horizontal = 6.dp)
.clip(RoundedCornerShape(50))
.clickable(onClick = operation)
.padding(5.dp)
)
fun NavIcon(onClick: () -> Unit) {
IconButton(onClick) {
Icon(Icons.AutoMirrored.Default.ArrowBack, null)
}
}
@Composable

View File

@@ -6,12 +6,19 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.graphics.Color
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.ThemeSettings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
private val lightScheme = lightColorScheme(
primary = primaryLight,
@@ -96,28 +103,24 @@ fun OwnDroidTheme(
) {
val darkTheme = theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())
val context = LocalContext.current
var colorScheme = when {
val colorScheme = when {
theme.materialYou && VERSION.SDK_INT >= 31 -> {
if(darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> darkScheme
else -> lightScheme
}
if(darkTheme && theme.blackTheme) {
colorScheme = colorScheme.copy(background = Color.Black)
}
if(!darkTheme) {
colorScheme = colorScheme.copy(background = colorScheme.primary.copy(alpha = 0.05f))
}.let {
if(darkTheme && theme.blackTheme) it.copy(background = Color.Black) else it
}.let {
if(!darkTheme) it.copy(background = it.primary.copy(alpha = 0.05f)) else it
}
val view = LocalView.current
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -1,34 +0,0 @@
package com.bintianqi.owndroid.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
)
*/
)