Fix crash caused by DelegatedScope

Use LargeTopAppBar in most screens
Optimize Package Chooser
Install existing app
This commit is contained in:
BinTianqi
2025-02-23 11:50:24 +08:00
parent fa81a2f30e
commit b734522171
15 changed files with 182 additions and 157 deletions

View File

@@ -1,6 +1,5 @@
package com.bintianqi.owndroid
import android.app.Application
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -32,7 +31,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
@@ -56,9 +55,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.lifecycleScope
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.Dispatchers
@@ -70,46 +68,41 @@ import kotlinx.coroutines.withContext
class PackageChooserActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val myVm by viewModels<MyViewModel>()
val vm by viewModels<PackageChooserViewModel>()
vm.initialize()
val vm by viewModels<MyViewModel>()
if(getPackagesProgress.value < 1F) getPackages()
setContent {
val theme by myVm.theme.collectAsStateWithLifecycle()
val theme by vm.theme.collectAsStateWithLifecycle()
OwnDroidTheme(theme) {
val packages by vm.packages.collectAsStateWithLifecycle()
val progress by vm.progress.collectAsStateWithLifecycle()
PackageChooserScreen(packages, progress, vm::getPackages) {
val packages by installedPackages.collectAsStateWithLifecycle()
val progress by getPackagesProgress.collectAsStateWithLifecycle()
PackageChooserScreen(packages, progress, ::getPackages) {
setResult(0, Intent().putExtra("package", it))
finish()
}
}
}
}
}
class PackageChooserViewModel(application: Application): AndroidViewModel(application) {
val packages = MutableStateFlow(emptyList<PackageInfo>())
val progress = MutableStateFlow(0F)
val flags = if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
fun initialize() {
if(progress.value < 1F) getPackages()
}
fun getPackages() {
packages.value = emptyList()
viewModelScope.launch(Dispatchers.IO) {
val pm = getApplication<Application>().packageManager
installedPackages.value = emptyList()
lifecycleScope.launch(Dispatchers.IO) {
val pm = packageManager
val apps = pm.getInstalledApplications(flags)
for(pkg in apps) {
packages.update {
installedPackages.update {
it + PackageInfo(
pkg.packageName, pkg.loadLabel(pm).toString(), pkg.loadIcon(pm),
(pkg.flags and ApplicationInfo.FLAG_SYSTEM) != 0
)
}
withContext(Dispatchers.Main) { progress.value = packages.value.size.toFloat() / apps.size }
withContext(Dispatchers.Main) { getPackagesProgress.value = installedPackages.value.size.toFloat() / apps.size }
}
}
}
companion object {
val installedPackages = MutableStateFlow(emptyList<PackageInfo>())
val getPackagesProgress = MutableStateFlow(0F)
}
}
data class PackageInfo(
@@ -186,7 +179,7 @@ private fun PackageChooserScreen(
Icon(Icons.AutoMirrored.Default.ArrowBack, null)
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.background)
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceContainer)
)
}
) { paddingValues->

View File

@@ -10,7 +10,6 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContract
@@ -97,6 +96,9 @@ fun parseDate(date: Date)
val Long.humanReadableDate: String
get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this))
val Long.humanReadableDateTime: String
get() = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(Date(this))
fun Context.showOperationResultToast(success: Boolean) {
Toast.makeText(this, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
}

View File

@@ -2,7 +2,6 @@ package com.bintianqi.owndroid.dpm
import android.app.AlertDialog
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
import android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
@@ -15,7 +14,6 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager.NameNotFoundException
import android.net.Uri
import android.os.Build.VERSION
import android.os.Looper
@@ -50,11 +48,11 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
@@ -88,8 +86,8 @@ import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.APK_MIME
import com.bintianqi.owndroid.AppInstallerActivity
import com.bintianqi.owndroid.AppInstallerViewModel
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.ChoosePackageContract
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.Animations
import com.bintianqi.owndroid.ui.FunctionItem
@@ -117,13 +115,13 @@ fun ApplicationsScreen(onNavigateUp: () -> Unit) {
topBar = {
TopAppBar(
title = {
TextField(
OutlinedTextField(
value = pkgName,
onValueChange = { pkgName = it },
label = { Text(stringResource(R.string.package_name)) },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
trailingIcon = {
IconButton({
focusMgr.clearFocus()
@@ -137,7 +135,7 @@ fun ApplicationsScreen(onNavigateUp: () -> Unit) {
)
},
navigationIcon = { NavIcon(onNavigateUp) },
colors = TopAppBarDefaults.topAppBarColors(containerColor = colorScheme.background)
colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer)
)
}
) { paddingValues->
@@ -167,6 +165,7 @@ fun ApplicationsScreen(onNavigateUp: () -> Unit) {
@Composable
private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
/** 1:Enable system app, 2:Clear app storage, 3:Set default dialer, 4:App control, 5:Install existing app */
var dialogStatus by remember { mutableIntStateOf(0) }
val context = LocalContext.current
val dpm = context.getDPM()
@@ -176,31 +175,26 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
var suspend by remember { mutableStateOf(false) }
var hide by remember { mutableStateOf(false) }
var blockUninstall by remember { mutableStateOf(false) }
var appControlAction by remember { mutableIntStateOf(0) }
var appControlAction by remember { mutableIntStateOf(0) } // 1:Suspend, 2:Hide, 3:Block uninstall
val focusMgr = LocalFocusManager.current
val appControl: (Boolean) -> Unit = {
when(appControlAction) {
1 -> if(VERSION.SDK_INT >= 24) dpm.setPackagesSuspended(receiver, arrayOf(pkgName), it)
2 -> dpm.setApplicationHidden(receiver, pkgName, it)
3 -> dpm.setUninstallBlocked(receiver, pkgName, it)
fun refresh() {
if(VERSION.SDK_INT >= 24) {
try {
suspend = dpm.isPackageSuspended(receiver, pkgName)
} catch(_: Exception) {}
}
when(appControlAction) {
1 -> {
suspend = try{ if(VERSION.SDK_INT >= 24) dpm.isPackageSuspended(receiver, pkgName) else false }
catch(_: NameNotFoundException) { false }
catch(_: IllegalArgumentException) { false }
}
2 -> hide = dpm.isApplicationHidden(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)
blockUninstall = dpm.isUninstallBlocked(receiver, pkgName)
}
fun appControl(status: Boolean) {
when(appControlAction) {
1 -> if(VERSION.SDK_INT >= 24) dpm.setPackagesSuspended(receiver, arrayOf(pkgName), status)
2 -> dpm.setApplicationHidden(receiver, pkgName, status)
3 -> dpm.setUninstallBlocked(receiver, pkgName, status)
}
refresh()
}
LaunchedEffect(pkgName) { refresh() }
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
) {
@@ -222,7 +216,7 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
)
}
SwitchItem(
title = R.string.hide, desc = stringResource(R.string.isapphidden_desc), icon = R.drawable.visibility_off_fill0,
title = R.string.hide, icon = R.drawable.visibility_off_fill0,
state = hide,
onCheckedChange = { appControlAction = 2; appControl(it) },
onClickBlank = { appControlAction = 2; dialogStatus = 4 }
@@ -270,8 +264,10 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
startActivity(context, intent, null)
}
FunctionItem(title = R.string.install_app, icon = R.drawable.install_mobile_fill0) {
Toast.makeText(context, R.string.choose_apk_file, Toast.LENGTH_SHORT).show()
chooseApks.launch(APK_MIME)
}
if(VERSION.SDK_INT >= 28) FunctionItem(R.string.install_existing_app, icon = R.drawable.install_mobile_fill0) { dialogStatus = 5 }
FunctionItem(title = R.string.uninstall_app, icon = R.drawable.delete_fill0) { onNavigate(UninstallPackage) }
if(VERSION.SDK_INT >= 34 && (deviceOwner || dpm.isOrgProfile(receiver))) {
FunctionItem(title = R.string.set_default_dialer, icon = R.drawable.call_fill0) {
@@ -287,22 +283,20 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
},
onDismissRequest = { dialogStatus = 0 },
dismissButton = {
TextButton(onClick = { dialogStatus = 0 }) {
TextButton({ dialogStatus = 0 }) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
try {
dpm.enableSystemApp(receiver, pkgName)
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
dialogStatus = 0
TextButton({
try {
dpm.enableSystemApp(receiver, pkgName)
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
) {
dialogStatus = 0
}) {
Text(stringResource(R.string.confirm))
}
},
@@ -317,16 +311,15 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
TextButton(
onClick = {
val executor = Executors.newCachedThreadPool()
val onClear = DevicePolicyManager.OnClearApplicationUserDataListener { pkg: String, succeed: Boolean ->
dpm.clearApplicationUserData(receiver, pkgName, executor) { pkg: String, succeed: Boolean ->
Looper.prepare()
val toastText =
if(pkg!="") { "$pkg\n" }else{ "" } +
if(pkg != "") { "$pkg\n" } else { "" } +
context.getString(R.string.clear_data) +
context.getString(if(succeed) R.string.success else R.string.failed )
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show()
Looper.loop()
}
dpm.clearApplicationUserData(receiver, pkgName, executor, onClear)
dialogStatus = 0
},
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
@@ -335,9 +328,7 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
}
},
dismissButton = {
TextButton(
onClick = { dialogStatus = 0 }
) {
TextButton({ dialogStatus = 0 }) {
Text(text = stringResource(R.string.cancel))
}
},
@@ -351,22 +342,20 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
},
onDismissRequest = { dialogStatus = 0 },
dismissButton = {
TextButton(onClick = { dialogStatus = 0 }) {
TextButton({ dialogStatus = 0 }) {
Text(stringResource(R.string.cancel))
}
},
confirmButton = {
TextButton(
onClick = {
try{
dpm.setDefaultDialerApplication(pkgName)
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
dialogStatus = 0
TextButton({
try {
dpm.setDefaultDialerApplication(pkgName)
context.showOperationResultToast(true)
} catch(_: IllegalArgumentException) {
context.showOperationResultToast(false)
}
) {
dialogStatus = 0
}) {
Text(stringResource(R.string.confirm))
}
},
@@ -379,18 +368,15 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
AlertDialog(
onDismissRequest = { dialogStatus = 0 },
title = {
Text(
text = stringResource(
when(appControlAction) {
1 -> R.string.suspend
2 -> R.string.hide
3 -> R.string.block_uninstall
4 -> R.string.always_on_vpn
else -> R.string.unknown
}
),
style = typography.headlineMedium
)
Text(stringResource(
when(appControlAction) {
1 -> R.string.suspend
2 -> R.string.hide
3 -> R.string.block_uninstall
4 -> R.string.always_on_vpn
else -> R.string.unknown
}
))
},
text = {
val enabled = when(appControlAction){
@@ -406,27 +392,38 @@ private fun HomeScreen(pkgName: String, onNavigate: (Any) -> Unit) {
}
},
confirmButton = {
TextButton(
onClick = {
appControl(true)
dialogStatus = 0
}
) {
TextButton({
appControl(true)
dialogStatus = 0
}) {
Text(text = stringResource(R.string.enable))
}
},
dismissButton = {
TextButton(
onClick = {
appControl(false)
dialogStatus = 0
}
) {
TextButton({
appControl(false)
dialogStatus = 0
}) {
Text(text = stringResource(R.string.disable))
}
}
)
}
if(dialogStatus == 5 && VERSION.SDK_INT >= 28) AlertDialog(
text = { Text(stringResource(R.string.info_install_existing_app)) },
confirmButton = {
TextButton({
context.showOperationResultToast(dpm.installExistingPackage(receiver, pkgName))
dialogStatus = 0
}) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton({ dialogStatus = 0 }) { Text(stringResource(R.string.cancel)) }
},
onDismissRequest = { dialogStatus = 0 }
)
LaunchedEffect(dialogStatus) { focusMgr.clearFocus() }
}

View File

@@ -98,6 +98,7 @@ import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -129,6 +130,7 @@ import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SharedPrefs
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.humanReadableDate
import com.bintianqi.owndroid.humanReadableDateTime
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.ExpandExposedTextFieldIcon
@@ -136,6 +138,7 @@ import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.ListItem
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.MySmallTitleScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.RadioButtonItem
import com.bintianqi.owndroid.ui.SwitchItem
@@ -231,7 +234,8 @@ fun WifiScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit, onNavigateTo
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.wifi)) },
navigationIcon = { NavIcon(onNavigateUp) }
navigationIcon = { NavIcon(onNavigateUp) },
colors = TopAppBarDefaults.topAppBarColors(MaterialTheme.colorScheme.surfaceContainer)
)
}
) { paddingValues ->
@@ -466,7 +470,7 @@ object AddNetwork
@Composable
fun AddNetworkScreen(data: Bundle, onNavigateUp: () -> Unit) {
MyScaffold(R.string.update_network, 0.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.update_network, 0.dp, onNavigateUp) {
AddNetworkScreen(data.getParcelable("wifi_configuration"), onNavigateUp)
}
}
@@ -1213,8 +1217,6 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
context.showOperationResultToast(false)
}
} else {
val bundle = Bundle()
bundle.putInt("size", buckets.size)
val stats = buckets.map {
NetworkStatsViewer.Data(
it.rxBytes, it.rxPackets, it.txBytes, it.txPackets,
@@ -1289,7 +1291,7 @@ data class NetworkStatsViewer(
fun NetworkStatsViewerScreen(nsv: NetworkStatsViewer, onNavigateUp: () -> Unit) {
var index by remember { mutableIntStateOf(0) }
val size = nsv.stats.size
MyScaffold(R.string.place_holder, 8.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.place_holder, 8.dp, onNavigateUp) {
if(size > 1) Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.align(Alignment.CenterHorizontally).padding(bottom = 8.dp)
@@ -1310,7 +1312,7 @@ fun NetworkStatsViewerScreen(nsv: NetworkStatsViewer, onNavigateUp: () -> Unit)
}
val data = nsv.stats[index]
Text(
data.startTime.humanReadableDate + " ~ " + data.endTime.humanReadableDate,
data.startTime.humanReadableDateTime + " ~ " + data.endTime.humanReadableDateTime,
modifier = Modifier.align(Alignment.CenterHorizontally).padding(bottom = 8.dp)
)
val txBytes = data.txBytes
@@ -1556,10 +1558,11 @@ fun RecommendedGlobalProxyScreen(onNavigateUp: () -> Unit) {
label = { Text(stringResource(R.string.excluded_hosts)) },
maxLines = 5,
minLines = 2,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { focusMgr.clearFocus() },
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)
)
}
Spacer(Modifier.padding(vertical = 4.dp))
Button(
onClick = {
if(proxyType == 0) {
@@ -1597,7 +1600,7 @@ fun RecommendedGlobalProxyScreen(onNavigateUp: () -> Unit) {
dpm.setRecommendedGlobalProxy(receiver, proxyInfo)
context.showOperationResultToast(true)
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
) {
Text(stringResource(R.string.apply))
}
@@ -1716,7 +1719,7 @@ fun PreferentialNetworkServiceScreen(onNavigateUp: () -> Unit, onNavigate: (AddP
configs.addAll(dpm.preferentialNetworkServiceConfigs)
}
LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.preferential_network_service, 0.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.preferential_network_service, 0.dp, onNavigateUp) {
SwitchItem(R.string.enabled, state = masterEnabled, onCheckedChange = {
dpm.isPreferentialNetworkServiceEnabled = it
refresh()
@@ -1780,7 +1783,7 @@ fun AddPreferentialNetworkServiceConfigScreen(route: AddPreferentialNetworkServi
var blockNonMatching by remember { mutableStateOf(route.blockNonMatching) }
var excludedUids by remember { mutableStateOf(route.excludedUids.joinToString("\n")) }
var includedUids by remember { mutableStateOf(route.includedUids.joinToString("\n")) }
MyScaffold(R.string.preferential_network_service, 8.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.preferential_network_service, 8.dp, onNavigateUp) {
SwitchItem(title = R.string.enabled, state = enabled, onCheckedChange = { enabled = it }, padding = false)
AnimatedVisibility(enabled) {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -1882,7 +1885,7 @@ fun OverrideApnScreen(onNavigateUp: () -> Unit, onNavigateToAddSetting: (Bundle)
settings.addAll(dpm.getOverrideApns(receiver))
}
LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.override_apn, 0.dp, onNavigateUp, false) {
MyScaffold(R.string.override_apn, 0.dp, onNavigateUp) {
SwitchItem(
R.string.enable, state = enabled,
onCheckedChange = {
@@ -1966,7 +1969,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
var persistent by remember { mutableStateOf(if(VERSION.SDK_INT >= 33) origin?.isPersistent == true else false) }
var alwaysOn by remember { mutableStateOf(VERSION.SDK_INT >= 35 && origin?.isAlwaysOn == true) }
var errorMessage: String? by remember { mutableStateOf(null) }
MyScaffold(R.string.apn_setting, 8.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.apn_setting, 8.dp, onNavigateUp) {
val protocolMap = mapOf(
ApnSetting.PROTOCOL_IP to "IPv4", ApnSetting.PROTOCOL_IPV6 to "IPv6",
ApnSetting.PROTOCOL_IPV4V6 to "IPv4/v6", ApnSetting.PROTOCOL_PPP to "PPP"

View File

@@ -15,6 +15,7 @@ import android.os.RemoteException
import android.os.UserManager
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.annotation.Keep
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
@@ -495,6 +496,7 @@ fun DeviceOwnerScreen(onNavigateUp: () -> Unit) {
}
}
@Keep
@Suppress("InlinedApi")
enum class DelegatedScope(val id: String, @StringRes val string: Int, val requiresApi: Int = 0) {
AppRestrictions(DevicePolicyManager.DELEGATION_APP_RESTRICTIONS, R.string.manage_application_restrictions),
@@ -585,7 +587,7 @@ fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) {
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
result?.let { input = it }
}
MyScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, 0.dp, onNavigateUp, !updateMode) {
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, 0.dp, onNavigateUp,) {
OutlinedTextField(
value = input, onValueChange = { input = it },
label = { Text(stringResource(R.string.package_name)) },

View File

@@ -38,7 +38,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.bintianqi.owndroid.IUserService
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.MySmallTitleScaffold
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
@@ -66,7 +66,7 @@ fun ShizukuScreen(navArgs: Bundle, onNavigateUp: () -> Unit, onNavigateToAccount
null
}
}
MyScaffold(R.string.shizuku, 0.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.shizuku, 0.dp, onNavigateUp) {
Button(
onClick = {
@@ -185,7 +185,7 @@ data class Accounts(
@Composable
fun AccountsScreen(accounts: Accounts, onNavigateUp: () -> Unit) {
MyScaffold(R.string.accounts, 8.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.accounts, 8.dp, onNavigateUp) {
accounts.list.forEach {
Column(
modifier = Modifier

View File

@@ -99,6 +99,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TimePicker
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
@@ -138,6 +139,7 @@ import com.bintianqi.owndroid.ui.FunctionItem
import com.bintianqi.owndroid.ui.InfoCard
import com.bintianqi.owndroid.ui.ListItem
import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.MySmallTitleScaffold
import com.bintianqi.owndroid.ui.NavIcon
import com.bintianqi.owndroid.ui.RadioButtonItem
import com.bintianqi.owndroid.ui.SwitchItem
@@ -431,7 +433,7 @@ fun HardwareMonitorScreen(onNavigateUp: () -> Unit) {
delay(refreshIntervalMs)
}
}
MyScaffold(R.string.hardware_monitor, 8.dp, onNavigateUp, false) {
MySmallTitleScaffold(R.string.hardware_monitor, 8.dp, onNavigateUp) {
Text(stringResource(R.string.refresh_interval), style = typography.titleLarge, modifier = Modifier.padding(vertical = 4.dp))
Slider(refreshInterval, { refreshInterval = it }, valueRange = 0.5F..2F, steps = 14)
Text("${refreshIntervalMs}ms")
@@ -943,7 +945,7 @@ fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
var appPolicy by remember { mutableIntStateOf(dpm.nearbyAppStreamingPolicy) }
MyScaffold(R.string.nearby_streaming_policy, 0.dp, onNavigateUp, false) {
MyScaffold(R.string.nearby_streaming_policy, 0.dp, onNavigateUp) {
Text(
stringResource(R.string.nearby_app_streaming),
Modifier.padding(start = 8.dp, top = 10.dp, bottom = 4.dp), style = typography.titleLarge
@@ -1018,7 +1020,8 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.lock_task_mode)) },
navigationIcon = { NavIcon(onNavigateUp) }
navigationIcon = { NavIcon(onNavigateUp) },
colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer)
)
}
) { paddingValues ->
@@ -1108,7 +1111,8 @@ private fun ColumnScope.StartLockTaskMode() {
} else {
Toast.makeText(context, R.string.failed, Toast.LENGTH_SHORT).show()
}
}
},
enabled = startLockTaskApp.isNotBlank() && (!specifyActivity || startLockTaskActivity.isNotBlank())
) {
Text(stringResource(R.string.start))
}

View File

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

View File

@@ -171,7 +171,7 @@ fun CreateWorkProfileScreen(onNavigateUp: () -> Unit) {
fun OrganizationOwnedProfileScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val dpm = context.getDPM()
MyScaffold(R.string.org_owned_work_profile, 8.dp, onNavigateUp, false) {
MyScaffold(R.string.org_owned_work_profile, 8.dp, onNavigateUp) {
CardItem(R.string.org_owned_work_profile, dpm.isOrganizationOwnedDeviceWithManagedProfile.yesOrNo)
Spacer(Modifier.padding(vertical = 5.dp))
if(!dpm.isOrganizationOwnedDeviceWithManagedProfile) {

View File

@@ -23,9 +23,9 @@ import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
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.draw.rotate
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -292,20 +292,16 @@ fun MyScaffold(
@StringRes title: Int,
horizonPadding: Dp,
onNavIconClicked: () -> Unit,
displayTitle: Boolean = true,
content: @Composable ColumnScope.() -> Unit
) {
val scrollState = rememberScrollState()
val sb = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
Modifier.nestedScroll(sb.nestedScrollConnection),
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(title),
modifier = if(displayTitle) Modifier.alpha((maxOf(scrollState.value-90,0)).toFloat()/50) else Modifier
)
},
navigationIcon = { NavIcon (onNavIconClicked) }
LargeTopAppBar(
{ Text(stringResource(title)) },
navigationIcon = { NavIcon(onNavIconClicked) },
scrollBehavior = sb
)
}
) { paddingValues ->
@@ -314,14 +310,39 @@ fun MyScaffold(
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = horizonPadding)
.verticalScroll(scrollState)
.verticalScroll(rememberScrollState())
.padding(bottom = 80.dp)
) {
if(displayTitle) Text(
text = stringResource(title),
style = typography.headlineLarge,
modifier = Modifier.padding(start = if(horizonPadding == 0.dp) 16.dp else 0.dp,top = 10.dp, bottom = 5.dp)
content()
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MySmallTitleScaffold(
@StringRes title: Int,
horizonPadding: Dp,
onNavIconClicked: () -> Unit,
content: @Composable ColumnScope.() -> Unit
) {
Scaffold(
topBar = {
TopAppBar(
{ Text(stringResource(title)) },
navigationIcon = { NavIcon(onNavIconClicked) },
colors = TopAppBarDefaults.topAppBarColors(colorScheme.surfaceContainer)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = horizonPadding)
.verticalScroll(rememberScrollState())
.padding(bottom = 80.dp)
) {
content()
}
}

View File

@@ -6,19 +6,11 @@ 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,
@@ -111,8 +103,6 @@ fun OwnDroidTheme(
else -> lightScheme
}.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 {