Improve UI

This commit is contained in:
BinTianqi
2025-05-17 14:03:17 +08:00
parent fdcb7c179f
commit b547c8add8
9 changed files with 112 additions and 68 deletions

View File

@@ -38,10 +38,6 @@ android {
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("defaultSignature")
composeCompiler {
includeSourceInformation = false
includeTraceMarkers = false
}
}
debug {
signingConfig = signingConfigs.getByName("defaultSignature")
@@ -64,6 +60,10 @@ android {
dependenciesInfo {
includeInApk = false
}
composeCompiler {
includeSourceInformation = false
includeTraceMarkers = false
}
}
kotlin {

View File

@@ -1,6 +1,5 @@
package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.os.Build.VERSION
import android.os.Bundle
import android.widget.Toast
@@ -237,13 +236,11 @@ import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import com.rosan.dhizuku.api.Dhizuku
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import org.lsposed.hiddenapibypass.HiddenApiBypass
import java.util.Locale
val backToHomeStateFlow = MutableStateFlow(false)
@ExperimentalMaterial3Api
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -291,12 +288,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
val context = LocalContext.current
val receiver = context.getReceiver()
val focusMgr = LocalFocusManager.current
val backToHome by backToHomeStateFlow.collectAsState()
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(backToHome) {
if(backToHome) { navController.navigateUp(); backToHomeStateFlow.value = false }
}
val userRestrictions by vm.userRestrictions.collectAsStateWithLifecycle()
fun navigateUp() { navController.navigateUp() }
fun navigate(destination: Any) { navController.navigate(destination) }
LaunchedEffect(Unit) {
@@ -320,7 +312,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
popEnterTransition = Animations.navHostPopEnterTransition,
popExitTransition = Animations.navHostPopExitTransition
) {
composable<Home> { HomeScreen { navController.navigate(it) } }
composable<Home> { HomeScreen(::navigate) }
composable<WorkModes> {
WorkModesScreen(it.toRoute(), ::navigateUp, {
navController.navigate(Home) {
@@ -330,9 +322,7 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
navController.navigate(WorkModes(false)) {
popUpTo<Home> { inclusive = true }
}
}, {
navController.navigate(it)
})
}, ::navigate)
}
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
@@ -369,14 +359,14 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<WipeData> { WipeDataScreen(::navigateUp) }
composable<Network> { NetworkScreen(::navigateUp, ::navigate) }
composable<WiFi> { WifiScreen(::navigateUp, { navController.navigate(it) }) { navController.navigate(AddNetwork, it)} }
composable<WiFi> { WifiScreen(::navigateUp, ::navigate) { navController.navigate(AddNetwork, it)} }
composable<NetworkOptions> { NetworkOptionsScreen(::navigateUp) }
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp, ::navigate) }
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
NetworkStatsViewerScreen(it.toRoute()) { navController.navigateUp() }
NetworkStatsViewerScreen(it.toRoute(), ::navigateUp)
}
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) }
@@ -433,25 +423,12 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
composable<SetDefaultDialer> { SetDefaultDialerScreen(::navigateUp) }
composable<UserRestriction> {
LaunchedEffect(Unit) {
vm.userRestrictions.value = context.getDPM().getUserRestrictions(receiver)
}
UserRestrictionScreen(::navigateUp) { title, items ->
navController.navigate(UserRestrictionOptions(title, items))
navigate(UserRestrictionOptions(title, items))
}
}
composable<UserRestrictionOptions>(mapOf(serializableNavTypePair<List<Restriction>>())) {
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)
}
}
UserRestrictionOptionsScreen(it.toRoute(), ::navigateUp)
}
composable<Users> { UsersScreen(::navigateUp, ::navigate) }

View File

@@ -1,7 +1,6 @@
package com.bintianqi.owndroid
import android.app.Application
import android.os.Bundle
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -9,7 +8,6 @@ import kotlinx.coroutines.launch
class MyViewModel(application: Application): AndroidViewModel(application) {
val theme = MutableStateFlow(ThemeSettings())
val userRestrictions = MutableStateFlow(Bundle())
init {
val sp = SharedPrefs(application)

View File

@@ -12,20 +12,29 @@ import androidx.compose.foundation.layout.Box
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.widthIn
import androidx.compose.foundation.rememberScrollState
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.MoreVert
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -35,6 +44,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
@@ -49,6 +59,7 @@ import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.Notes
import com.bintianqi.owndroid.ui.SwitchItem
import kotlinx.serialization.Serializable
@@ -59,6 +70,7 @@ import java.util.Locale
@Serializable object Settings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val context = LocalContext.current
@@ -66,19 +78,55 @@ fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val exportLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) {
if(it != null) exportLogs(context, it)
}
MyScaffold(R.string.settings, onNavigateUp, 0.dp) {
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) }
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) }
FunctionItem(R.string.app_lock, icon = R.drawable.lock_fill0) { onNavigate(AppLockSettings) }
if (privilege.device || privilege.profile)
FunctionItem(title = R.string.api, icon = R.drawable.code_fill0) { onNavigate(ApiSettings) }
if (privilege.device && !privilege.dhizuku)
FunctionItem(R.string.notifications, icon = R.drawable.notifications_fill0) { onNavigate(Notifications) }
FunctionItem(title = R.string.export_logs, icon = R.drawable.description_fill0) {
val time = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date(System.currentTimeMillis()))
exportLogsLauncher.launch("owndroid_log_$time")
val sb = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
var dropdown by remember { mutableStateOf(false) }
Scaffold(
Modifier.nestedScroll(sb.nestedScrollConnection),
topBar = {
LargeTopAppBar(
{ Text(stringResource(R.string.settings)) },
navigationIcon = { NavIcon(onNavigateUp) },
scrollBehavior = sb,
actions = {
Box {
IconButton({ dropdown = true }) {
Icon(Icons.Default.MoreVert, null)
}
DropdownMenu(dropdown, { dropdown = false }) {
DropdownMenuItem(
{ Text(stringResource(R.string.export_logs)) },
{
dropdown = false
val time = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
.format(Date(System.currentTimeMillis()))
exportLogsLauncher.launch("owndroid_log_$time")
},
leadingIcon = {
Icon(painterResource(R.drawable.description_fill0), null)
}
)
}
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(bottom = 80.dp)
) {
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) }
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) }
FunctionItem(R.string.app_lock, icon = R.drawable.lock_fill0) { onNavigate(AppLockSettings) }
if (privilege.device || privilege.profile)
FunctionItem(title = R.string.api, icon = R.drawable.code_fill0) { onNavigate(ApiSettings) }
if (privilege.device && !privilege.dhizuku)
FunctionItem(R.string.notifications, icon = R.drawable.notifications_fill0) { onNavigate(Notifications) }
FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { onNavigate(About) }
}
FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { onNavigate(About) }
}
}
@@ -111,6 +159,10 @@ fun SettingsOptionsScreen(onNavigateUp: () -> Unit) {
fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onThemeChange: (ThemeSettings) -> Unit) {
var darkThemeMenu by remember { mutableStateOf(false) }
var theme by remember { mutableStateOf(currentTheme) }
fun update(it: ThemeSettings) {
theme = it
onThemeChange(it)
}
val darkThemeTextID = when(theme.darkTheme) {
1 -> R.string.on
0 -> R.string.off
@@ -121,7 +173,7 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onTh
SwitchItem(
R.string.material_you_color,
state = theme.materialYou,
onCheckedChange = { theme = theme.copy(materialYou = it) }
onCheckedChange = { update(theme.copy(materialYou = it)) }
)
}
Box {
@@ -133,13 +185,14 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onTh
DropdownMenuItem(
text = { Text(stringResource(R.string.follow_system)) },
onClick = {
theme = theme.copy(darkTheme = -1)
update(theme.copy(darkTheme = -1))
darkThemeMenu = false
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.on)) },
onClick = {
update(theme.copy(darkTheme = 1))
theme = theme.copy(darkTheme = 1)
darkThemeMenu = false
}
@@ -147,19 +200,17 @@ fun AppearanceScreen(onNavigateUp: () -> Unit, currentTheme: ThemeSettings, onTh
DropdownMenuItem(
text = { Text(stringResource(R.string.off)) },
onClick = {
theme = theme.copy(darkTheme = 0)
update(theme.copy(darkTheme = 0))
darkThemeMenu = false
}
)
}
}
AnimatedVisibility(theme.darkTheme == 1 || (theme.darkTheme == -1 && isSystemInDarkTheme())) {
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))
}
SwitchItem(
R.string.black_theme, state = theme.blackTheme,
onCheckedChange = { update(theme.copy(blackTheme = it)) }
)
}
}
}

View File

@@ -32,7 +32,6 @@ import com.bintianqi.owndroid.backToHomeStateFlow
import com.bintianqi.owndroid.createShortcuts
import com.bintianqi.owndroid.myPrivilege
import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.Dhizuku.binderWrapper
import com.rosan.dhizuku.api.DhizukuBinderWrapper
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.encodeToString
@@ -73,7 +72,7 @@ fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager?
val oldInterface = field[manager] as IDevicePolicyManager
if (oldInterface is DhizukuBinderWrapper) return manager
val oldBinder = oldInterface.asBinder()
val newBinder = binderWrapper(oldBinder)
val newBinder = Dhizuku.binderWrapper(oldBinder)
val newInterface = IDevicePolicyManager.Stub.asInterface(newBinder)
field[manager] = newInterface
return manager
@@ -93,7 +92,7 @@ private fun binderWrapperPackageInstaller(appContext: Context): PackageInstaller
val oldInterface = field[installer] as IPackageInstaller
if (oldInterface is DhizukuBinderWrapper) return installer
val oldBinder = oldInterface.asBinder()
val newBinder = binderWrapper(oldBinder)
val newBinder = Dhizuku.binderWrapper(oldBinder)
val newInterface = IPackageInstaller.Stub.asInterface(newBinder)
field[installer] = newInterface
return installer
@@ -107,7 +106,6 @@ fun Context.getPackageInstaller(): PackageInstaller {
if(SharedPrefs(this).dhizuku) {
if (!dhizukuPermissionGranted()) {
dhizukuErrorStatus.value = 2
backToHomeStateFlow.value = true
return this.packageManager.packageInstaller
}
return binderWrapperPackageInstaller(this) ?: this.packageManager.packageInstaller
@@ -120,7 +118,6 @@ fun Context.getDPM(): DevicePolicyManager {
if(SharedPrefs(this).dhizuku) {
if (!dhizukuPermissionGranted()) {
dhizukuErrorStatus.value = 2
backToHomeStateFlow.value = true
return this.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
}
return binderWrapperDevicePolicyManager(this) ?: this.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager

View File

@@ -295,7 +295,7 @@ fun WorkModesScreen(
Spacer(Modifier.padding(horizontal = 2.dp))
Button({ dialog = 5 }) { Text(stringResource(R.string.adb_command)) }
Spacer(Modifier.padding(horizontal = 2.dp))
if (VERSION.SDK_INT == 35) Button({
if (VERSION.SDK_INT >= 33) Button({
dialog = 6
}) {
Text(stringResource(R.string.root_force_activate))
@@ -575,6 +575,7 @@ fun LockScreenInfoScreen(onNavigateUp: () -> Unit) {
onClick = {
focusMgr.clearFocus()
dpm.setDeviceOwnerLockScreenInfo(receiver, null)
infoText = ""
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()

View File

@@ -10,8 +10,12 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -73,14 +77,30 @@ data class UserRestrictionOptions(
@RequiresApi(24)
@Composable
fun UserRestrictionOptionsScreen(
data: UserRestrictionOptions, restrictions: Bundle,
onNavigateUp: () -> Unit, onRestrictionChange: (String, Boolean) -> Unit
data: UserRestrictionOptions, onNavigateUp: () -> Unit
) {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val status = remember { mutableStateMapOf<String, Boolean>() }
LaunchedEffect(Unit) {
val restrictions = dpm.getUserRestrictions(receiver)
data.items.forEach {
status.put(it.id, restrictions.getBoolean(it.id))
}
}
MyScaffold(data.title, onNavigateUp, 0.dp) {
data.items.filter { Build.VERSION.SDK_INT >= it.requiresApi }.forEach { restriction ->
SwitchItem(
restriction.name, restriction.id, restriction.icon,
restrictions.getBoolean(restriction.id), { onRestrictionChange(restriction.id, it) }, padding = true
restriction.name, restriction.id, restriction.icon, status[restriction.id] == true,
{
if (it) {
dpm.addUserRestriction(receiver, restriction.id)
} else {
dpm.clearUserRestriction(receiver, restriction.id)
}
status[restriction.id] = dpm.getUserRestrictions(receiver).getBoolean(restriction.id)
}, padding = true
)
}
}

View File

@@ -102,7 +102,7 @@ fun OwnDroidTheme(
darkTheme -> darkScheme
else -> lightScheme
}.let {
if(darkTheme && theme.blackTheme) it.copy(background = Color.Black) else it
if(darkTheme && theme.blackTheme) it.copy(background = Color.Black, surface = Color.Black) else it
}
val view = LocalView.current
SideEffect {