mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-26 03:56:00 +00:00
Optimize Applications and PackageChooser
This commit is contained in:
2
app/proguard-rules.pro
vendored
2
app/proguard-rules.pro
vendored
@@ -24,3 +24,5 @@
|
|||||||
-dontwarn android.app.ActivityThread
|
-dontwarn android.app.ActivityThread
|
||||||
-dontwarn android.app.ContextImpl
|
-dontwarn android.app.ContextImpl
|
||||||
-dontwarn android.app.LoadedApk
|
-dontwarn android.app.LoadedApk
|
||||||
|
|
||||||
|
-keep class com.bintianqi.owndroid.MyViewModel { *; }
|
||||||
|
|||||||
@@ -60,11 +60,6 @@
|
|||||||
<data android:mimeType="application/vnd.android.package-archive"/>
|
<data android:mimeType="application/vnd.android.package-archive"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".PackageChooserActivity"
|
|
||||||
android:label="@string/package_chooser"
|
|
||||||
android:exported="false"
|
|
||||||
android:launchMode="singleInstance"/>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ShortcutsReceiverActivity"
|
android:name=".ShortcutsReceiverActivity"
|
||||||
android:permission="com.bintianqi.owndroid.MyPermission"
|
android:permission="com.bintianqi.owndroid.MyPermission"
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import com.rosan.dhizuku.server_api.DhizukuService
|
|||||||
import com.rosan.dhizuku.shared.DhizukuVariables
|
import com.rosan.dhizuku.shared.DhizukuVariables
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
private const val TAG = "DhizukuServer"
|
private const val TAG = "DhizukuServer"
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ import com.bintianqi.owndroid.dpm.AutoTimePolicyScreen
|
|||||||
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy
|
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicy
|
||||||
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen
|
import com.bintianqi.owndroid.dpm.AutoTimeZonePolicyScreen
|
||||||
import com.bintianqi.owndroid.dpm.BlockUninstall
|
import com.bintianqi.owndroid.dpm.BlockUninstall
|
||||||
import com.bintianqi.owndroid.dpm.BlockUninstallScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.CaCert
|
import com.bintianqi.owndroid.dpm.CaCert
|
||||||
import com.bintianqi.owndroid.dpm.CaCertScreen
|
import com.bintianqi.owndroid.dpm.CaCertScreen
|
||||||
import com.bintianqi.owndroid.dpm.ChangeTime
|
import com.bintianqi.owndroid.dpm.ChangeTime
|
||||||
@@ -101,9 +100,7 @@ import com.bintianqi.owndroid.dpm.CredentialManagerPolicyScreen
|
|||||||
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter
|
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilter
|
||||||
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen
|
import com.bintianqi.owndroid.dpm.CrossProfileIntentFilterScreen
|
||||||
import com.bintianqi.owndroid.dpm.CrossProfilePackages
|
import com.bintianqi.owndroid.dpm.CrossProfilePackages
|
||||||
import com.bintianqi.owndroid.dpm.CrossProfilePackagesScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders
|
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProviders
|
||||||
import com.bintianqi.owndroid.dpm.CrossProfileWidgetProvidersScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.DelegatedAdmins
|
import com.bintianqi.owndroid.dpm.DelegatedAdmins
|
||||||
import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen
|
import com.bintianqi.owndroid.dpm.DelegatedAdminsScreen
|
||||||
import com.bintianqi.owndroid.dpm.DeleteWorkProfile
|
import com.bintianqi.owndroid.dpm.DeleteWorkProfile
|
||||||
@@ -115,9 +112,7 @@ import com.bintianqi.owndroid.dpm.DhizukuServerSettingsScreen
|
|||||||
import com.bintianqi.owndroid.dpm.DisableAccountManagement
|
import com.bintianqi.owndroid.dpm.DisableAccountManagement
|
||||||
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
|
import com.bintianqi.owndroid.dpm.DisableAccountManagementScreen
|
||||||
import com.bintianqi.owndroid.dpm.DisableMeteredData
|
import com.bintianqi.owndroid.dpm.DisableMeteredData
|
||||||
import com.bintianqi.owndroid.dpm.DisableMeteredDataScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.DisableUserControl
|
import com.bintianqi.owndroid.dpm.DisableUserControl
|
||||||
import com.bintianqi.owndroid.dpm.DisableUserControlScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.EnableSystemApp
|
import com.bintianqi.owndroid.dpm.EnableSystemApp
|
||||||
import com.bintianqi.owndroid.dpm.EnableSystemAppScreen
|
import com.bintianqi.owndroid.dpm.EnableSystemAppScreen
|
||||||
import com.bintianqi.owndroid.dpm.FrpPolicy
|
import com.bintianqi.owndroid.dpm.FrpPolicy
|
||||||
@@ -125,13 +120,11 @@ import com.bintianqi.owndroid.dpm.FrpPolicyScreen
|
|||||||
import com.bintianqi.owndroid.dpm.HardwareMonitor
|
import com.bintianqi.owndroid.dpm.HardwareMonitor
|
||||||
import com.bintianqi.owndroid.dpm.HardwareMonitorScreen
|
import com.bintianqi.owndroid.dpm.HardwareMonitorScreen
|
||||||
import com.bintianqi.owndroid.dpm.Hide
|
import com.bintianqi.owndroid.dpm.Hide
|
||||||
import com.bintianqi.owndroid.dpm.HideScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.InstallExistingApp
|
import com.bintianqi.owndroid.dpm.InstallExistingApp
|
||||||
import com.bintianqi.owndroid.dpm.InstallExistingAppScreen
|
import com.bintianqi.owndroid.dpm.InstallExistingAppScreen
|
||||||
import com.bintianqi.owndroid.dpm.InstallSystemUpdate
|
import com.bintianqi.owndroid.dpm.InstallSystemUpdate
|
||||||
import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen
|
import com.bintianqi.owndroid.dpm.InstallSystemUpdateScreen
|
||||||
import com.bintianqi.owndroid.dpm.KeepUninstalledPackages
|
import com.bintianqi.owndroid.dpm.KeepUninstalledPackages
|
||||||
import com.bintianqi.owndroid.dpm.KeepUninstalledPackagesScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.Keyguard
|
import com.bintianqi.owndroid.dpm.Keyguard
|
||||||
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures
|
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeatures
|
||||||
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeaturesScreen
|
import com.bintianqi.owndroid.dpm.KeyguardDisabledFeaturesScreen
|
||||||
@@ -157,6 +150,8 @@ import com.bintianqi.owndroid.dpm.OrganizationOwnedProfile
|
|||||||
import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen
|
import com.bintianqi.owndroid.dpm.OrganizationOwnedProfileScreen
|
||||||
import com.bintianqi.owndroid.dpm.OverrideApn
|
import com.bintianqi.owndroid.dpm.OverrideApn
|
||||||
import com.bintianqi.owndroid.dpm.OverrideApnScreen
|
import com.bintianqi.owndroid.dpm.OverrideApnScreen
|
||||||
|
import com.bintianqi.owndroid.dpm.PackageFunctionScreen
|
||||||
|
import com.bintianqi.owndroid.dpm.PackageFunctionScreenWithoutResult
|
||||||
import com.bintianqi.owndroid.dpm.Password
|
import com.bintianqi.owndroid.dpm.Password
|
||||||
import com.bintianqi.owndroid.dpm.PasswordInfo
|
import com.bintianqi.owndroid.dpm.PasswordInfo
|
||||||
import com.bintianqi.owndroid.dpm.PasswordInfoScreen
|
import com.bintianqi.owndroid.dpm.PasswordInfoScreen
|
||||||
@@ -166,9 +161,8 @@ import com.bintianqi.owndroid.dpm.PermissionPolicyScreen
|
|||||||
import com.bintianqi.owndroid.dpm.PermissionsManager
|
import com.bintianqi.owndroid.dpm.PermissionsManager
|
||||||
import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
|
import com.bintianqi.owndroid.dpm.PermissionsManagerScreen
|
||||||
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices
|
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServices
|
||||||
import com.bintianqi.owndroid.dpm.PermittedAccessibilityServicesScreen
|
import com.bintianqi.owndroid.dpm.PermittedAsAndImPackages
|
||||||
import com.bintianqi.owndroid.dpm.PermittedInputMethods
|
import com.bintianqi.owndroid.dpm.PermittedInputMethods
|
||||||
import com.bintianqi.owndroid.dpm.PermittedInputMethodsScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.PreferentialNetworkService
|
import com.bintianqi.owndroid.dpm.PreferentialNetworkService
|
||||||
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
|
import com.bintianqi.owndroid.dpm.PreferentialNetworkServiceScreen
|
||||||
import com.bintianqi.owndroid.dpm.PrivateDns
|
import com.bintianqi.owndroid.dpm.PrivateDns
|
||||||
@@ -195,7 +189,6 @@ import com.bintianqi.owndroid.dpm.SupportMessageScreen
|
|||||||
import com.bintianqi.owndroid.dpm.Suspend
|
import com.bintianqi.owndroid.dpm.Suspend
|
||||||
import com.bintianqi.owndroid.dpm.SuspendPersonalApp
|
import com.bintianqi.owndroid.dpm.SuspendPersonalApp
|
||||||
import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen
|
import com.bintianqi.owndroid.dpm.SuspendPersonalAppScreen
|
||||||
import com.bintianqi.owndroid.dpm.SuspendScreen
|
|
||||||
import com.bintianqi.owndroid.dpm.SystemManager
|
import com.bintianqi.owndroid.dpm.SystemManager
|
||||||
import com.bintianqi.owndroid.dpm.SystemManagerScreen
|
import com.bintianqi.owndroid.dpm.SystemManagerScreen
|
||||||
import com.bintianqi.owndroid.dpm.SystemOptions
|
import com.bintianqi.owndroid.dpm.SystemOptions
|
||||||
@@ -276,6 +269,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
fun navigateUp() { navController.navigateUp() }
|
fun navigateUp() { navController.navigateUp() }
|
||||||
fun navigate(destination: Any) { navController.navigate(destination) }
|
fun navigate(destination: Any) { navController.navigate(destination) }
|
||||||
|
fun choosePackage() {
|
||||||
|
navController.navigate(ApplicationsList(false))
|
||||||
|
}
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if(!Privilege.status.value.activated) {
|
if(!Privilege.status.value.activated) {
|
||||||
navController.navigate(WorkModes(false)) {
|
navController.navigate(WorkModes(false)) {
|
||||||
@@ -306,7 +302,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) }
|
composable<DhizukuServerSettings> { DhizukuServerSettingsScreen(::navigateUp) }
|
||||||
|
|
||||||
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
|
composable<DelegatedAdmins> { DelegatedAdminsScreen(::navigateUp, ::navigate) }
|
||||||
composable<AddDelegatedAdmin>{ AddDelegatedAdminScreen(it.toRoute(), ::navigateUp) }
|
composable<AddDelegatedAdmin>{
|
||||||
|
AddDelegatedAdminScreen(vm.chosenPackage, ::choosePackage, it.toRoute(), ::navigateUp)
|
||||||
|
}
|
||||||
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
|
composable<DeviceInfo> { DeviceInfoScreen(::navigateUp) }
|
||||||
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
|
composable<LockScreenInfo> { LockScreenInfoScreen(::navigateUp) }
|
||||||
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
|
composable<SupportMessage> { SupportMessageScreen(::navigateUp) }
|
||||||
@@ -331,7 +329,9 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
composable<PermissionPolicy> { PermissionPolicyScreen(::navigateUp) }
|
composable<PermissionPolicy> { PermissionPolicyScreen(::navigateUp) }
|
||||||
composable<MtePolicy> { MtePolicyScreen(::navigateUp) }
|
composable<MtePolicy> { MtePolicyScreen(::navigateUp) }
|
||||||
composable<NearbyStreamingPolicy> { NearbyStreamingPolicyScreen(::navigateUp) }
|
composable<NearbyStreamingPolicy> { NearbyStreamingPolicyScreen(::navigateUp) }
|
||||||
composable<LockTaskMode> { LockTaskModeScreen(::navigateUp) }
|
composable<LockTaskMode> {
|
||||||
|
LockTaskModeScreen(vm.chosenPackage, ::choosePackage, ::navigateUp)
|
||||||
|
}
|
||||||
composable<CaCert> { CaCertScreen(::navigateUp) }
|
composable<CaCert> { CaCertScreen(::navigateUp) }
|
||||||
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
|
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
|
||||||
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
|
composable<DisableAccountManagement> { DisableAccountManagementScreen(::navigateUp) }
|
||||||
@@ -346,12 +346,16 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
|
composable<AddNetwork> { AddNetworkScreen(it.arguments!!, ::navigateUp) }
|
||||||
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
|
composable<WifiSecurityLevel> { WifiSecurityLevelScreen(::navigateUp) }
|
||||||
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
|
composable<WifiSsidPolicyScreen> { WifiSsidPolicyScreen(::navigateUp) }
|
||||||
composable<QueryNetworkStats> { NetworkStatsScreen(::navigateUp, ::navigate) }
|
composable<QueryNetworkStats> {
|
||||||
|
NetworkStatsScreen(vm.chosenPackage, ::choosePackage, ::navigateUp, ::navigate)
|
||||||
|
}
|
||||||
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
|
composable<NetworkStatsViewer>(mapOf(serializableNavTypePair<List<NetworkStatsViewer.Data>>())) {
|
||||||
NetworkStatsViewerScreen(it.toRoute(), ::navigateUp)
|
NetworkStatsViewerScreen(it.toRoute(), ::navigateUp)
|
||||||
}
|
}
|
||||||
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
|
composable<PrivateDns> { PrivateDnsScreen(::navigateUp) }
|
||||||
composable<AlwaysOnVpnPackage> { AlwaysOnVpnPackageScreen(::navigateUp) }
|
composable<AlwaysOnVpnPackage> {
|
||||||
|
AlwaysOnVpnPackageScreen(vm.chosenPackage, ::choosePackage, ::navigateUp)
|
||||||
|
}
|
||||||
composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) }
|
composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) }
|
||||||
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
|
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
|
||||||
composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
||||||
@@ -368,14 +372,25 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) }
|
composable<DeleteWorkProfile> { DeleteWorkProfileScreen(::navigateUp) }
|
||||||
|
|
||||||
composable<ApplicationsList> {
|
composable<ApplicationsList> {
|
||||||
AppChooserScreen(it.toRoute(), { dest ->
|
val canSwitchView = (it.toRoute() as ApplicationsList).canSwitchView
|
||||||
if(dest == null) navigateUp() else navigate(ApplicationDetails(dest))
|
AppChooserScreen(
|
||||||
|
canSwitchView, vm.installedPackages, vm.refreshPackagesProgress, { name ->
|
||||||
|
if (canSwitchView) {
|
||||||
|
if (name == null) {
|
||||||
|
navigateUp()
|
||||||
|
} else {
|
||||||
|
navigate(ApplicationDetails(name))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (name != null) vm.chosenPackage.trySend(name)
|
||||||
|
navigateUp()
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
SP.applicationsListView = false
|
SP.applicationsListView = false
|
||||||
navController.navigate(ApplicationsFeatures) {
|
navController.navigate(ApplicationsFeatures) {
|
||||||
popUpTo(Home)
|
popUpTo(Home)
|
||||||
}
|
}
|
||||||
})
|
}, vm::refreshPackageList)
|
||||||
}
|
}
|
||||||
composable<ApplicationsFeatures> {
|
composable<ApplicationsFeatures> {
|
||||||
ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
|
ApplicationsFeaturesScreen(::navigateUp, ::navigate) {
|
||||||
@@ -385,24 +400,78 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composable<ApplicationDetails> { ApplicationDetailsScreen(it.toRoute(), ::navigateUp, ::navigate) }
|
composable<ApplicationDetails> {
|
||||||
composable<Suspend> { SuspendScreen(::navigateUp) }
|
ApplicationDetailsScreen(it.toRoute(), vm, ::navigateUp, ::navigate)
|
||||||
composable<Hide> { HideScreen(::navigateUp) }
|
}
|
||||||
composable<BlockUninstall> { BlockUninstallScreen(::navigateUp) }
|
composable<Suspend> {
|
||||||
composable<DisableUserControl> { DisableUserControlScreen(::navigateUp) }
|
PackageFunctionScreen(R.string.suspend, vm.suspendedPackages, vm::getSuspendedPackaged,
|
||||||
composable<PermissionsManager> { PermissionsManagerScreen(::navigateUp, it.toRoute()) }
|
vm::setPackageSuspended, ::navigateUp, vm.chosenPackage, ::choosePackage,
|
||||||
composable<DisableMeteredData> { DisableMeteredDataScreen(::navigateUp) }
|
R.string.info_suspend_app)
|
||||||
composable<ClearAppStorage> { ClearAppStorageScreen(::navigateUp) }
|
}
|
||||||
composable<UninstallApp> { UninstallAppScreen(::navigateUp) }
|
composable<Hide> {
|
||||||
composable<KeepUninstalledPackages> { KeepUninstalledPackagesScreen(::navigateUp) }
|
PackageFunctionScreen(R.string.hide, vm.hiddenPackages, vm::getHiddenPackages,
|
||||||
composable<InstallExistingApp> { InstallExistingAppScreen(::navigateUp) }
|
vm::setPackageHidden, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||||
composable<CrossProfilePackages> { CrossProfilePackagesScreen(::navigateUp) }
|
}
|
||||||
composable<CrossProfileWidgetProviders> { CrossProfileWidgetProvidersScreen(::navigateUp) }
|
composable<BlockUninstall> {
|
||||||
composable<CredentialManagerPolicy> { CredentialManagerPolicyScreen(::navigateUp) }
|
PackageFunctionScreenWithoutResult(R.string.block_uninstall, vm.ubPackages,
|
||||||
composable<PermittedAccessibilityServices> { PermittedAccessibilityServicesScreen(::navigateUp) }
|
vm::getUbPackages, vm::setPackageUb, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||||
composable<PermittedInputMethods> { PermittedInputMethodsScreen(::navigateUp) }
|
}
|
||||||
composable<EnableSystemApp> { EnableSystemAppScreen(::navigateUp) }
|
composable<DisableUserControl> {
|
||||||
composable<SetDefaultDialer> { SetDefaultDialerScreen(::navigateUp) }
|
PackageFunctionScreenWithoutResult(R.string.disable_user_control, vm.ucdPackages,
|
||||||
|
vm::getUcdPackages, vm::setPackageUcd, ::navigateUp, vm.chosenPackage,
|
||||||
|
::choosePackage, R.string.info_disable_user_control)
|
||||||
|
}
|
||||||
|
composable<PermissionsManager> {
|
||||||
|
PermissionsManagerScreen(vm.packagePermissions, vm::getPackagePermissions,
|
||||||
|
vm::setPackagePermission, ::navigateUp, it.toRoute(), vm.chosenPackage, ::choosePackage)
|
||||||
|
}
|
||||||
|
composable<DisableMeteredData> {
|
||||||
|
PackageFunctionScreen(R.string.disable_metered_data, vm.mddPackages,
|
||||||
|
vm::getMddPackages, vm::setPackageMdd, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||||
|
}
|
||||||
|
composable<ClearAppStorage> {
|
||||||
|
ClearAppStorageScreen(vm.chosenPackage, ::choosePackage, vm::clearAppData, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<UninstallApp> {
|
||||||
|
UninstallAppScreen(vm.chosenPackage, ::choosePackage, vm::uninstallPackage, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<KeepUninstalledPackages> {
|
||||||
|
PackageFunctionScreenWithoutResult(R.string.keep_uninstalled_packages, vm.kuPackages,
|
||||||
|
vm::getKuPackages, vm::setPackageKu, ::navigateUp, vm.chosenPackage,
|
||||||
|
::choosePackage, R.string.info_keep_uninstalled_apps)
|
||||||
|
}
|
||||||
|
composable<InstallExistingApp> {
|
||||||
|
InstallExistingAppScreen(vm.chosenPackage, ::choosePackage,
|
||||||
|
vm::installExistingApp, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<CrossProfilePackages> {
|
||||||
|
PackageFunctionScreenWithoutResult(R.string.cross_profile_apps, vm.cpPackages,
|
||||||
|
vm::getCpPackages, vm::setPackageCp, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||||
|
}
|
||||||
|
composable<CrossProfileWidgetProviders> {
|
||||||
|
PackageFunctionScreen(R.string.cross_profile_widget, vm.cpwProviders,
|
||||||
|
vm::getCpwProviders, vm::setCpwProvider, ::navigateUp, vm.chosenPackage, ::choosePackage)
|
||||||
|
}
|
||||||
|
composable<CredentialManagerPolicy> {
|
||||||
|
CredentialManagerPolicyScreen(vm.chosenPackage, ::choosePackage,
|
||||||
|
vm.cmPackages, vm::getCmPolicy, vm::setCmPackage, vm::setCmPolicy, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<PermittedAccessibilityServices> {
|
||||||
|
PermittedAsAndImPackages(R.string.permitted_accessibility_services,
|
||||||
|
R.string.system_accessibility_always_allowed, vm.chosenPackage, ::choosePackage,
|
||||||
|
vm.pasPackages, vm::getPasPackages, vm::setPasPackage, vm::setPasPolicy, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<PermittedInputMethods> {
|
||||||
|
PermittedAsAndImPackages(R.string.permitted_ime, R.string.system_ime_always_allowed,
|
||||||
|
vm.chosenPackage, ::choosePackage, vm.pimPackages, vm::getPimPackages,
|
||||||
|
vm::setPimPackage, vm::setPimPolicy, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<EnableSystemApp> {
|
||||||
|
EnableSystemAppScreen(vm.chosenPackage, ::choosePackage, vm::enableSystemApp, ::navigateUp)
|
||||||
|
}
|
||||||
|
composable<SetDefaultDialer> {
|
||||||
|
SetDefaultDialerScreen(vm.chosenPackage, ::choosePackage, vm::setDefaultDialer, ::navigateUp)
|
||||||
|
}
|
||||||
|
|
||||||
composable<UserRestriction> {
|
composable<UserRestriction> {
|
||||||
UserRestrictionScreen(::navigateUp) {
|
UserRestrictionScreen(::navigateUp) {
|
||||||
@@ -497,7 +566,10 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
|
|||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.ime
|
contentWindowInsets = WindowInsets.ime
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxSize().padding(it).verticalScroll(rememberScrollState())) {
|
Column(Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(it)
|
||||||
|
.verticalScroll(rememberScrollState())) {
|
||||||
if(privilege.device || privilege.profile) {
|
if(privilege.device || privilege.profile) {
|
||||||
HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
|
HomePageItem(R.string.system, R.drawable.android_fill0) { onNavigate(SystemManager) }
|
||||||
HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) }
|
HomePageItem(R.string.network, R.drawable.wifi_fill0) { onNavigate(Network) }
|
||||||
|
|||||||
@@ -1,10 +1,41 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.admin.PackagePolicy
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build.VERSION
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.application
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.bintianqi.owndroid.Privilege.DAR
|
||||||
|
import com.bintianqi.owndroid.Privilege.DPM
|
||||||
|
import com.bintianqi.owndroid.dpm.AppStatus
|
||||||
|
import com.bintianqi.owndroid.dpm.getPackageInstaller
|
||||||
|
import com.bintianqi.owndroid.dpm.isValidPackageName
|
||||||
|
import com.bintianqi.owndroid.dpm.parsePackageInstallerMessage
|
||||||
|
import com.bintianqi.owndroid.dpm.permissionList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class MyViewModel(application: Application): AndroidViewModel(application) {
|
class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||||
|
val PM = application.packageManager
|
||||||
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
val theme = MutableStateFlow(ThemeSettings(SP.materialYou, SP.darkTheme, SP.blackTheme))
|
||||||
fun changeTheme(newTheme: ThemeSettings) {
|
fun changeTheme(newTheme: ThemeSettings) {
|
||||||
theme.value = newTheme
|
theme.value = newTheme
|
||||||
@@ -12,6 +43,340 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
|||||||
SP.darkTheme = newTheme.darkTheme
|
SP.darkTheme = newTheme.darkTheme
|
||||||
SP.blackTheme = newTheme.blackTheme
|
SP.blackTheme = newTheme.blackTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val chosenPackage = Channel<String>(1, BufferOverflow.DROP_LATEST)
|
||||||
|
|
||||||
|
val installedPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
val refreshPackagesProgress = MutableStateFlow(0F)
|
||||||
|
fun refreshPackageList() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
installedPackages.value = emptyList()
|
||||||
|
val apps = PM.getInstalledApplications(getInstalledAppsFlags)
|
||||||
|
apps.forEachIndexed { index, info ->
|
||||||
|
installedPackages.update {
|
||||||
|
it + getAppInfo(info)
|
||||||
|
}
|
||||||
|
refreshPackagesProgress.value = (index + 1).toFloat() / apps.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun getAppInfo(info: ApplicationInfo) =
|
||||||
|
AppInfo(info.packageName, info.loadLabel(PM).toString(), info.loadIcon(PM), info.flags)
|
||||||
|
fun getAppInfo(name: String): AppInfo {
|
||||||
|
return try {
|
||||||
|
getAppInfo(PM.getApplicationInfo(name, getInstalledAppsFlags))
|
||||||
|
} catch (_: PackageManager.NameNotFoundException) {
|
||||||
|
AppInfo(name, "???", Color.Transparent.toArgb().toDrawable(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val suspendedPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun getSuspendedPackaged() {
|
||||||
|
val packages = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||||
|
DPM.isPackageSuspended(DAR, it.packageName)
|
||||||
|
}
|
||||||
|
suspendedPackages.value = packages.map { getAppInfo(it) }
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun setPackageSuspended(name: String, status: Boolean): Boolean {
|
||||||
|
val result = DPM.setPackagesSuspended(DAR, arrayOf(name), status)
|
||||||
|
getSuspendedPackaged()
|
||||||
|
return result.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val hiddenPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
fun getHiddenPackages() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
hiddenPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||||
|
DPM.isApplicationHidden(DAR, it.packageName)
|
||||||
|
}.map { getAppInfo(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setPackageHidden(name: String, status: Boolean): Boolean {
|
||||||
|
val result = DPM.setApplicationHidden(DAR, name, status)
|
||||||
|
getHiddenPackages()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstall blocked packages
|
||||||
|
val ubPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
fun getUbPackages() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
ubPackages.value = PM.getInstalledApplications(getInstalledAppsFlags).filter {
|
||||||
|
DPM.isUninstallBlocked(DAR, it.packageName)
|
||||||
|
}.map { getAppInfo(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setPackageUb(name: String, status: Boolean) {
|
||||||
|
DPM.setUninstallBlocked(DAR, name, status)
|
||||||
|
getUbPackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// User control disabled packages
|
||||||
|
val ucdPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
@RequiresApi(30)
|
||||||
|
fun getUcdPackages() {
|
||||||
|
ucdPackages.value = DPM.getUserControlDisabledPackages(DAR).map {
|
||||||
|
getAppInfo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(30)
|
||||||
|
fun setPackageUcd(name: String, status: Boolean) {
|
||||||
|
DPM.setUserControlDisabledPackages(
|
||||||
|
DAR,
|
||||||
|
ucdPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) }
|
||||||
|
)
|
||||||
|
getUcdPackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
val packagePermissions = MutableStateFlow(emptyMap<String, Int>())
|
||||||
|
@RequiresApi(23)
|
||||||
|
fun getPackagePermissions(name: String) {
|
||||||
|
if (name.isValidPackageName) {
|
||||||
|
packagePermissions.value = permissionList().associate {
|
||||||
|
it.permission to DPM.getPermissionGrantState(DAR, name, it.permission)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
packagePermissions.value = emptyMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(23)
|
||||||
|
fun setPackagePermission(name: String, permission: String, status: Int): Boolean {
|
||||||
|
val result = DPM.setPermissionGrantState(DAR, name, permission, status)
|
||||||
|
getPackagePermissions(name)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metered data disabled packages
|
||||||
|
val mddPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun getMddPackages() {
|
||||||
|
mddPackages.value = DPM.getMeteredDataDisabledPackages(DAR).map { getAppInfo(it) }
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun setPackageMdd(name: String, status: Boolean): Boolean {
|
||||||
|
val result = DPM.setMeteredDataDisabledPackages(
|
||||||
|
DAR, mddPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) }
|
||||||
|
)
|
||||||
|
getMddPackages()
|
||||||
|
return result.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep uninstalled packages
|
||||||
|
val kuPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun getKuPackages() {
|
||||||
|
kuPackages.value = DPM.getKeepUninstalledPackages(DAR)?.map { getAppInfo(it) } ?: emptyList()
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun setPackageKu(name: String, status: Boolean) {
|
||||||
|
DPM.setKeepUninstalledPackages(
|
||||||
|
DAR, kuPackages.value.map { it.name }.run { if (status) plus(name) else minus(name) }
|
||||||
|
)
|
||||||
|
getKuPackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross profile packages
|
||||||
|
val cpPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
@RequiresApi(30)
|
||||||
|
fun getCpPackages() {
|
||||||
|
cpPackages.value = DPM.getCrossProfilePackages(DAR).map { getAppInfo(it) }
|
||||||
|
}
|
||||||
|
@RequiresApi(30)
|
||||||
|
fun setPackageCp(name: String, status: Boolean) {
|
||||||
|
DPM.setCrossProfilePackages(
|
||||||
|
DAR,
|
||||||
|
cpPackages.value.map { it.name }.toSet().run { if (status) plus(name) else minus(name) }
|
||||||
|
)
|
||||||
|
getCpPackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cross-profile widget providers
|
||||||
|
val cpwProviders = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
fun getCpwProviders() {
|
||||||
|
cpwProviders.value = DPM.getCrossProfileWidgetProviders(DAR).map { getAppInfo(it) }
|
||||||
|
}
|
||||||
|
fun setCpwProvider(name: String, status: Boolean): Boolean {
|
||||||
|
val result = if (status) {
|
||||||
|
DPM.addCrossProfileWidgetProvider(DAR, name)
|
||||||
|
} else {
|
||||||
|
DPM.removeCrossProfileWidgetProvider(DAR, name)
|
||||||
|
}
|
||||||
|
getCpwProviders()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun clearAppData(name: String, callback: (Boolean) -> Unit) {
|
||||||
|
DPM.clearApplicationUserData(DAR, name, Executors.newSingleThreadExecutor()) { _, result ->
|
||||||
|
callback(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uninstallPackage(packageName: String, onComplete: (String?) -> Unit) {
|
||||||
|
val receiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val statusExtra = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 999)
|
||||||
|
if(statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||||
|
@SuppressWarnings("UnsafeIntentLaunch")
|
||||||
|
context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?)
|
||||||
|
} else {
|
||||||
|
context.unregisterReceiver(this)
|
||||||
|
if(statusExtra == PackageInstaller.STATUS_SUCCESS) {
|
||||||
|
onComplete(null)
|
||||||
|
} else {
|
||||||
|
onComplete(parsePackageInstallerMessage(context, intent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextCompat.registerReceiver(
|
||||||
|
application, receiver, IntentFilter(AppInstallerViewModel.ACTION), null,
|
||||||
|
null, ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
val pi = if(VERSION.SDK_INT >= 34) {
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
application, 0, Intent(AppInstallerViewModel.ACTION),
|
||||||
|
PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or PendingIntent.FLAG_MUTABLE
|
||||||
|
).intentSender
|
||||||
|
} else {
|
||||||
|
PendingIntent.getBroadcast(application, 0, Intent(AppInstallerViewModel.ACTION), PendingIntent.FLAG_MUTABLE).intentSender
|
||||||
|
}
|
||||||
|
application.getPackageInstaller().uninstall(packageName, pi)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun installExistingApp(name: String): Boolean {
|
||||||
|
return DPM.installExistingPackage(DAR, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credential manager policy
|
||||||
|
val cmPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
@RequiresApi(34)
|
||||||
|
fun getCmPolicy(): Int {
|
||||||
|
return DPM.credentialManagerPolicy?.let { policy ->
|
||||||
|
cmPackages.value = policy.packageNames.map { getAppInfo(it) }
|
||||||
|
policy.policyType
|
||||||
|
} ?: -1
|
||||||
|
}
|
||||||
|
fun setCmPackage(name: String, status: Boolean) {
|
||||||
|
cmPackages.update { list ->
|
||||||
|
if (status) list + getAppInfo(name) else list.dropWhile { it.name == name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(34)
|
||||||
|
fun setCmPolicy(type: Int) {
|
||||||
|
DPM.credentialManagerPolicy = if (type != -1 && cmPackages.value.isNotEmpty()) {
|
||||||
|
PackagePolicy(type, cmPackages.value.map { it.name }.toSet())
|
||||||
|
} else null
|
||||||
|
getCmPolicy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permitted input method
|
||||||
|
val pimPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
fun getPimPackages(): Boolean {
|
||||||
|
return DPM.getPermittedInputMethods(DAR).let { packages ->
|
||||||
|
pimPackages.value = packages?.map { getAppInfo(it) } ?: emptyList()
|
||||||
|
packages == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setPimPackage(name: String, status: Boolean) {
|
||||||
|
pimPackages.update { packages ->
|
||||||
|
if (status) packages + getAppInfo(name) else packages.dropWhile { it.name == name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setPimPolicy(allowAll: Boolean): Boolean {
|
||||||
|
val result = DPM.setPermittedInputMethods(
|
||||||
|
DAR, if (allowAll) null else pimPackages.value.map { it.name })
|
||||||
|
getPimPackages()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permitted accessibility services
|
||||||
|
val pasPackages = MutableStateFlow(emptyList<AppInfo>())
|
||||||
|
fun getPasPackages(): Boolean {
|
||||||
|
return DPM.getPermittedAccessibilityServices(DAR).let { packages ->
|
||||||
|
pasPackages.value = packages?.map { getAppInfo(it) } ?: emptyList()
|
||||||
|
packages == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setPasPackage(name: String, status: Boolean) {
|
||||||
|
pasPackages.update { packages ->
|
||||||
|
if (status) packages + getAppInfo(name) else packages.dropWhile { it.name == name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun setPasPolicy(allowAll: Boolean): Boolean {
|
||||||
|
val result = DPM.setPermittedAccessibilityServices(
|
||||||
|
DAR, if (allowAll) null else pasPackages.value.map { it.name })
|
||||||
|
getPasPackages()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enableSystemApp(name: String) {
|
||||||
|
DPM.enableSystemApp(DAR, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
val appStatus = MutableStateFlow(AppStatus(false, false, false, false, false, false))
|
||||||
|
fun getAppStatus(name: String) {
|
||||||
|
appStatus.value = AppStatus(
|
||||||
|
if (VERSION.SDK_INT >= 24) DPM.isPackageSuspended(DAR, name) else false,
|
||||||
|
DPM.isApplicationHidden(DAR, name),
|
||||||
|
DPM.isUninstallBlocked(DAR, name),
|
||||||
|
if (VERSION.SDK_INT >= 30) name in DPM.getUserControlDisabledPackages(DAR) else false,
|
||||||
|
if (VERSION.SDK_INT >= 28) name in DPM.getMeteredDataDisabledPackages(DAR) else false,
|
||||||
|
if (VERSION.SDK_INT >= 28) DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true else false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Application details
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun adSetPackageSuspended(name: String, status: Boolean) {
|
||||||
|
DPM.setPackagesSuspended(DAR, arrayOf(name), status)
|
||||||
|
appStatus.update { it.copy(suspend = DPM.isPackageSuspended(DAR, name)) }
|
||||||
|
}
|
||||||
|
fun adSetPackageHidden(name: String, status: Boolean) {
|
||||||
|
DPM.setApplicationHidden(DAR, name, status)
|
||||||
|
appStatus.update { it.copy(hide = DPM.isApplicationHidden(DAR, name)) }
|
||||||
|
}
|
||||||
|
fun adSetPackageUb(name: String, status: Boolean) {
|
||||||
|
DPM.setUninstallBlocked(DAR, name, status)
|
||||||
|
appStatus.update { it.copy(uninstallBlocked = DPM.isUninstallBlocked(DAR, name)) }
|
||||||
|
}
|
||||||
|
@RequiresApi(30)
|
||||||
|
fun adSetPackageUcd(name: String, status: Boolean) {
|
||||||
|
DPM.setUserControlDisabledPackages(DAR,
|
||||||
|
DPM.getUserControlDisabledPackages(DAR).run { if (status) plus(name) else minus(name) })
|
||||||
|
appStatus.update {
|
||||||
|
it.copy(userControlDisabled = name in DPM.getUserControlDisabledPackages(DAR))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun adSetPackageMdd(name: String, status: Boolean) {
|
||||||
|
DPM.setMeteredDataDisabledPackages(DAR,
|
||||||
|
DPM.getMeteredDataDisabledPackages(DAR).run { if (status) plus(name) else minus(name) })
|
||||||
|
appStatus.update {
|
||||||
|
it.copy(meteredDataDisabled = name in DPM.getMeteredDataDisabledPackages(DAR))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(28)
|
||||||
|
fun adSetPackageKu(name: String, status: Boolean) {
|
||||||
|
DPM.setKeepUninstalledPackages(DAR,
|
||||||
|
DPM.getKeepUninstalledPackages(DAR)?.run { if (status) plus(name) else minus(name) } ?: emptyList())
|
||||||
|
appStatus.update {
|
||||||
|
it.copy(keepUninstalled = DPM.getKeepUninstalledPackages(DAR)?.contains(name) == true )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(34)
|
||||||
|
fun setDefaultDialer(name: String): Boolean {
|
||||||
|
return try {
|
||||||
|
DPM.setDefaultDialerApplication(name)
|
||||||
|
true
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ThemeSettings(
|
data class ThemeSettings(
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
package com.bintianqi.owndroid
|
package com.bintianqi.owndroid
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -44,10 +37,8 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -62,35 +53,10 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.ui.theme.OwnDroidTheme
|
|
||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
class PackageChooserActivity: ComponentActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val vm by viewModels<MyViewModel>()
|
|
||||||
enableEdgeToEdge()
|
|
||||||
setContent {
|
|
||||||
val theme by vm.theme.collectAsStateWithLifecycle()
|
|
||||||
OwnDroidTheme(theme) {
|
|
||||||
AppChooserScreen(ApplicationsList(false), {
|
|
||||||
setResult(0, Intent().putExtra("package", it))
|
|
||||||
finish()
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val installedApps = MutableStateFlow(emptyList<AppInfo>())
|
|
||||||
|
|
||||||
data class AppInfo(
|
data class AppInfo(
|
||||||
val name: String,
|
val name: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
@@ -105,11 +71,14 @@ private fun searchInString(query: String, content: String)
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Unit, onSwitchView: () -> Unit) {
|
fun AppChooserScreen(
|
||||||
val packages by installedApps.collectAsStateWithLifecycle()
|
canSwitchView: Boolean, packageList: MutableStateFlow<List<AppInfo>>,
|
||||||
val coroutine = rememberCoroutineScope()
|
refreshProgress: MutableStateFlow<Float>, onChoosePackage: (String?) -> Unit,
|
||||||
|
onSwitchView: () -> Unit, onRefresh: () -> Unit
|
||||||
|
) {
|
||||||
|
val packages by packageList.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var progress by remember { mutableFloatStateOf(1F) }
|
val progress by refreshProgress.collectAsStateWithLifecycle()
|
||||||
var system by rememberSaveable { mutableStateOf(false) }
|
var system by rememberSaveable { mutableStateOf(false) }
|
||||||
var query by rememberSaveable { mutableStateOf("") }
|
var query by rememberSaveable { mutableStateOf("") }
|
||||||
var searchMode by rememberSaveable { mutableStateOf(false) }
|
var searchMode by rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -119,7 +88,7 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
|
|||||||
}
|
}
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if(packages.size <= 1) getInstalledApps(coroutine, context) { progress = it }
|
if(packages.size <= 1) onRefresh()
|
||||||
}
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -135,20 +104,17 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
|
|||||||
}) {
|
}) {
|
||||||
Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null)
|
Icon(painter = painterResource(R.drawable.filter_alt_fill0), contentDescription = null)
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(onRefresh, enabled = progress == 1F) {
|
||||||
{ getInstalledApps(coroutine, context) { progress = it } },
|
|
||||||
enabled = progress == 1F
|
|
||||||
) {
|
|
||||||
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null)
|
Icon(painter = painterResource(R.drawable.refresh_fill0), contentDescription = null)
|
||||||
}
|
}
|
||||||
if(params.canSwitchView) IconButton(onSwitchView) {
|
if (canSwitchView) IconButton(onSwitchView) {
|
||||||
Icon(Icons.AutoMirrored.Default.List, null)
|
Icon(Icons.AutoMirrored.Default.List, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
if(searchMode) {
|
if(searchMode) {
|
||||||
val fr = FocusRequester()
|
val fr = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { fr.requestFocus() }
|
LaunchedEffect(Unit) { fr.requestFocus() }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = query,
|
value = query,
|
||||||
@@ -213,23 +179,5 @@ fun AppChooserScreen(params: ApplicationsList, onChoosePackage: (String?) -> Uni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInstalledApps(scope: CoroutineScope, context: Context, onProgressUpdated: (Float) -> Unit) {
|
|
||||||
installedApps.value = emptyList()
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
val pm = context.packageManager
|
|
||||||
val apps = pm.getInstalledApplications(getInstalledAppsFlags)
|
|
||||||
for(pkg in apps) {
|
|
||||||
val label = pkg.loadLabel(pm).toString()
|
|
||||||
val icon = pkg.loadIcon(pm)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
installedApps.update {
|
|
||||||
it + AppInfo(pkg.packageName, label, icon, pkg.flags)
|
|
||||||
}
|
|
||||||
onProgressUpdated(installedApps.value.size.toFloat() / apps.size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val getInstalledAppsFlags =
|
val getInstalledAppsFlags =
|
||||||
if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
if(Build.VERSION.SDK_INT >= 24) PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_UNINSTALLED_PACKAGES else 0
|
||||||
|
|||||||
@@ -233,28 +233,38 @@ fun AppLockSettingsScreen(onNavigateUp: () -> Unit) = MyScaffold(R.string.app_lo
|
|||||||
var confirmPassword by remember { mutableStateOf("") }
|
var confirmPassword by remember { mutableStateOf("") }
|
||||||
var allowBiometrics by remember { mutableStateOf(SP.biometricsUnlock) }
|
var allowBiometrics by remember { mutableStateOf(SP.biometricsUnlock) }
|
||||||
var lockWhenLeaving by remember { mutableStateOf(SP.lockWhenLeaving) }
|
var lockWhenLeaving by remember { mutableStateOf(SP.lockWhenLeaving) }
|
||||||
val fr = FocusRequester()
|
val fr = remember { FocusRequester() }
|
||||||
val alreadySet = !SP.lockPasswordHash.isNullOrEmpty()
|
val alreadySet = !SP.lockPasswordHash.isNullOrEmpty()
|
||||||
val isInputLegal = password.length !in 1..3 && (alreadySet || (password.isNotEmpty() && password.isNotBlank()))
|
val isInputLegal = password.length !in 1..3 && (alreadySet || (password.isNotEmpty() && password.isNotBlank()))
|
||||||
Column(Modifier.widthIn(max = 300.dp).align(Alignment.CenterHorizontally)) {
|
Column(Modifier
|
||||||
|
.widthIn(max = 300.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
password, { password = it }, Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
password, { password = it }, Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
label = { Text(stringResource(R.string.password)) },
|
label = { Text(stringResource(R.string.password)) },
|
||||||
supportingText = { Text(stringResource(if(alreadySet) R.string.leave_empty_to_remain_unchanged else R.string.minimum_length_4)) },
|
supportingText = { Text(stringResource(if(alreadySet) R.string.leave_empty_to_remain_unchanged else R.string.minimum_length_4)) },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next),
|
||||||
keyboardActions = KeyboardActions { fr.requestFocus() }
|
keyboardActions = KeyboardActions { fr.requestFocus() }
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
confirmPassword, { confirmPassword = it }, Modifier.fillMaxWidth().focusRequester(fr),
|
confirmPassword, { confirmPassword = it }, Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(fr),
|
||||||
label = { Text(stringResource(R.string.confirm_password)) },
|
label = { Text(stringResource(R.string.confirm_password)) },
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
||||||
keyboardActions = KeyboardActions { fm.clearFocus() }
|
keyboardActions = KeyboardActions { fm.clearFocus() }
|
||||||
)
|
)
|
||||||
if(VERSION.SDK_INT >= 28) Row(Modifier.fillMaxWidth().padding(vertical = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
if(VERSION.SDK_INT >= 28) Row(Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
||||||
Text(stringResource(R.string.allow_biometrics))
|
Text(stringResource(R.string.allow_biometrics))
|
||||||
Switch(allowBiometrics, { allowBiometrics = it })
|
Switch(allowBiometrics, { allowBiometrics = it })
|
||||||
}
|
}
|
||||||
Row(Modifier.fillMaxWidth().padding(bottom = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
Row(Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 6.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
|
||||||
Text(stringResource(R.string.lock_when_leaving))
|
Text(stringResource(R.string.lock_when_leaving))
|
||||||
Switch(lockWhenLeaving, { lockWhenLeaving = it })
|
Switch(lockWhenLeaving, { lockWhenLeaving = it })
|
||||||
}
|
}
|
||||||
@@ -302,7 +312,9 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
|
|||||||
var key by remember { mutableStateOf("") }
|
var key by remember { mutableStateOf("") }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) },
|
value = key, onValueChange = { key = it }, label = { Text(stringResource(R.string.api_key)) },
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp), readOnly = true,
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 4.dp), readOnly = true,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -316,7 +328,9 @@ fun ApiSettings(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 10.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
SP.apiKey = key
|
SP.apiKey = key
|
||||||
context.showOperationResultToast(true)
|
context.showOperationResultToast(true)
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import android.content.ClipData
|
|||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
@@ -107,13 +105,6 @@ inline fun <reified T> serializableNavTypePair() =
|
|||||||
Json.encodeToString(value)
|
Json.encodeToString(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChoosePackageContract: ActivityResultContract<Nothing?, String?>() {
|
|
||||||
override fun createIntent(context: Context, input: Nothing?): Intent =
|
|
||||||
Intent(context, PackageChooserActivity::class.java)
|
|
||||||
override fun parseResult(resultCode: Int, intent: Intent?): String? =
|
|
||||||
intent?.getStringExtra("package")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportLogs(context: Context, uri: Uri) {
|
fun exportLogs(context: Context, uri: Uri) {
|
||||||
context.contentResolver.openOutputStream(uri)?.use { output ->
|
context.contentResolver.openOutputStream(uri)?.use { output ->
|
||||||
val proc = Runtime.getRuntime().exec("logcat -d")
|
val proc = Runtime.getRuntime().exec("logcat -d")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@ import com.bintianqi.owndroid.createShortcuts
|
|||||||
import com.rosan.dhizuku.api.Dhizuku
|
import com.rosan.dhizuku.api.Dhizuku
|
||||||
import com.rosan.dhizuku.api.DhizukuBinderWrapper
|
import com.rosan.dhizuku.api.DhizukuBinderWrapper
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.ChoosePackageContract
|
|
||||||
import com.bintianqi.owndroid.HorizontalPadding
|
import com.bintianqi.owndroid.HorizontalPadding
|
||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
import com.bintianqi.owndroid.R
|
import com.bintianqi.owndroid.R
|
||||||
@@ -153,6 +152,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -886,7 +886,10 @@ fun NetworkStats.toBucketList(): List<NetworkStats.Bucket> {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
@Composable
|
@Composable
|
||||||
fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit) {
|
fun NetworkStatsScreen(
|
||||||
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
||||||
|
onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkStatsViewer) -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
val privilege by Privilege.status.collectAsStateWithLifecycle()
|
||||||
val fm = LocalFocusManager.current
|
val fm = LocalFocusManager.current
|
||||||
@@ -1053,16 +1056,14 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
|
|||||||
) {
|
) {
|
||||||
var uidText by rememberSaveable { mutableStateOf(context.getString(NetworkStatsUID.All.strRes)) }
|
var uidText by rememberSaveable { mutableStateOf(context.getString(NetworkStatsUID.All.strRes)) }
|
||||||
var readOnly by rememberSaveable { mutableStateOf(true) }
|
var readOnly by rememberSaveable { mutableStateOf(true) }
|
||||||
if(!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt()
|
if (!readOnly && uidText.toIntOrNull() != null) uid = uidText.toInt()
|
||||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) {
|
if (VERSION.SDK_INT >= 24) LaunchedEffect(Unit) {
|
||||||
it ?: return@rememberLauncherForActivityResult
|
val pkg = chosenPackage.receive()
|
||||||
if(VERSION.SDK_INT >= 24 && readOnly) {
|
try {
|
||||||
try {
|
uid = context.packageManager.getPackageUid(pkg, 0)
|
||||||
uid = context.packageManager.getPackageUid(it, 0)
|
uidText = "$uid ($pkg)"
|
||||||
uidText = "$it ($uid)"
|
} catch(_: NameNotFoundException) {
|
||||||
} catch(_: NameNotFoundException) {
|
context.showOperationResultToast(false)
|
||||||
context.showOperationResultToast(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -1093,7 +1094,7 @@ fun NetworkStatsScreen(onNavigateUp: () -> Unit, onNavigateToViewer: (NetworkSta
|
|||||||
onClick = {
|
onClick = {
|
||||||
readOnly = true
|
readOnly = true
|
||||||
activeTextField = NetworkStatsActiveTextField.None
|
activeTextField = NetworkStatsActiveTextField.None
|
||||||
choosePackage.launch(null)
|
onChoosePackage()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
@@ -1457,15 +1458,18 @@ fun PrivateDnsScreen(onNavigateUp: () -> Unit) {
|
|||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
@Composable
|
@Composable
|
||||||
fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) {
|
fun AlwaysOnVpnPackageScreen(
|
||||||
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var lockdown by rememberSaveable { mutableStateOf(false) }
|
var lockdown by rememberSaveable { mutableStateOf(false) }
|
||||||
var pkgName by rememberSaveable { mutableStateOf("") }
|
var pkgName by rememberSaveable { mutableStateOf("") }
|
||||||
val focusMgr = LocalFocusManager.current
|
fun refresh() {
|
||||||
val refresh = { pkgName = Privilege.DPM.getAlwaysOnVpnPackage(Privilege.DAR) ?: "" }
|
pkgName = Privilege.DPM.getAlwaysOnVpnPackage(Privilege.DAR) ?: ""
|
||||||
LaunchedEffect(Unit) { refresh() }
|
}
|
||||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
LaunchedEffect(Unit) {
|
||||||
result?.let { pkgName = it }
|
refresh()
|
||||||
|
pkgName = chosenPackage.receive()
|
||||||
}
|
}
|
||||||
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
|
val setAlwaysOnVpn: (String?, Boolean)->Boolean = { vpnPkg: String?, lockdownEnabled: Boolean ->
|
||||||
try {
|
try {
|
||||||
@@ -1483,21 +1487,8 @@ fun AlwaysOnVpnPackageScreen(onNavigateUp: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MyScaffold(R.string.always_on_vpn, onNavigateUp) {
|
MyScaffold(R.string.always_on_vpn, onNavigateUp) {
|
||||||
OutlinedTextField(
|
PackageNameTextField(pkgName, onChoosePackage,
|
||||||
value = pkgName,
|
Modifier.padding(vertical = 4.dp)) { pkgName = it }
|
||||||
onValueChange = { pkgName = it },
|
|
||||||
label = { Text(stringResource(R.string.package_name)) },
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
|
||||||
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
|
|
||||||
trailingIcon = {
|
|
||||||
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(50))
|
|
||||||
.clickable { choosePackage.launch(null) }
|
|
||||||
.padding(3.dp))
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp)
|
|
||||||
)
|
|
||||||
SwitchItem(R.string.enable_lockdown, state = lockdown, onCheckedChange = { lockdown = it }, padding = false)
|
SwitchItem(R.string.enable_lockdown, state = lockdown, onCheckedChange = { lockdown = it }, padding = false)
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
Spacer(Modifier.padding(vertical = 5.dp))
|
||||||
Button(
|
Button(
|
||||||
@@ -2067,7 +2058,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
|
|||||||
keyboardActions = KeyboardActions { fm.clearFocus() }
|
keyboardActions = KeyboardActions { fm.clearFocus() }
|
||||||
)
|
)
|
||||||
if(VERSION.SDK_INT >= 33) Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) {
|
if(VERSION.SDK_INT >= 33) Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween) {
|
||||||
val fr = FocusRequester()
|
val fr = remember { FocusRequester() }
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
mtuV4, { mtuV4 = it }, Modifier.fillMaxWidth(0.49F),
|
mtuV4, { mtuV4 = it }, Modifier.fillMaxWidth(0.49F),
|
||||||
label = { Text("MTU (IPv4)") },
|
label = { Text("MTU (IPv4)") },
|
||||||
@@ -2206,7 +2197,7 @@ fun AddApnSettingScreen(origin: ApnSetting?, onNavigateUp: () -> Unit) {
|
|||||||
if(dialog != 0) {
|
if(dialog != 0) {
|
||||||
var address by remember { mutableStateOf((if(dialog == 1) proxyAddress else mmsProxyAddress)) }
|
var address by remember { mutableStateOf((if(dialog == 1) proxyAddress else mmsProxyAddress)) }
|
||||||
var port by remember { mutableStateOf((if(dialog == 1) proxyPort else mmsProxyPort)) }
|
var port by remember { mutableStateOf((if(dialog == 1) proxyPort else mmsProxyPort)) }
|
||||||
val fr = FocusRequester()
|
val fr = remember { FocusRequester() }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = { Text(if(dialog == 1) "Proxy" else "MMS proxy") },
|
title = { Text(if(dialog == 1) "Proxy" else "MMS proxy") },
|
||||||
text = {
|
text = {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.PersistableBundle
|
import android.os.PersistableBundle
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@@ -84,10 +83,8 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.state.ToggleableState
|
import androidx.compose.ui.state.ToggleableState
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.ChoosePackageContract
|
|
||||||
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
|
import com.bintianqi.owndroid.DHIZUKU_CLIENTS_FILE
|
||||||
import com.bintianqi.owndroid.DhizukuClientInfo
|
import com.bintianqi.owndroid.DhizukuClientInfo
|
||||||
import com.bintianqi.owndroid.DhizukuPermissions
|
import com.bintianqi.owndroid.DhizukuPermissions
|
||||||
@@ -113,6 +110,7 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
|||||||
import com.rosan.dhizuku.api.Dhizuku
|
import com.rosan.dhizuku.api.Dhizuku
|
||||||
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
|
import com.rosan.dhizuku.api.DhizukuRequestPermissionListener
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -731,30 +729,19 @@ fun DelegatedAdminsScreen(onNavigateUp: () -> Unit, onNavigate: (AddDelegatedAdm
|
|||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
@Composable
|
@Composable
|
||||||
fun AddDelegatedAdminScreen(data: AddDelegatedAdmin, onNavigateUp: () -> Unit) {
|
fun AddDelegatedAdminScreen(
|
||||||
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
|
||||||
|
data: AddDelegatedAdmin, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val updateMode = data.pkg.isNotEmpty()
|
val updateMode = data.pkg.isNotEmpty()
|
||||||
val fm = LocalFocusManager.current
|
|
||||||
var input by remember { mutableStateOf(data.pkg) }
|
var input by remember { mutableStateOf(data.pkg) }
|
||||||
val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) }
|
val scopes = remember { mutableStateListOf(*data.scopes.toTypedArray()) }
|
||||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
LaunchedEffect(Unit) {
|
||||||
result?.let { input = it }
|
input = chosenPackage.receive()
|
||||||
}
|
}
|
||||||
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) {
|
MySmallTitleScaffold(if(updateMode) R.string.place_holder else R.string.add_delegated_admin, onNavigateUp, 0.dp) {
|
||||||
OutlinedTextField(
|
PackageNameTextField(input, onChoosePackage,
|
||||||
value = input, onValueChange = { input = it },
|
Modifier.padding(HorizontalPadding, 8.dp)) { input = it }
|
||||||
label = { Text(stringResource(R.string.package_name)) },
|
|
||||||
trailingIcon = {
|
|
||||||
if(!updateMode) IconButton({ choosePackage.launch(null) }) {
|
|
||||||
Icon(painterResource(R.drawable.list_fill0), null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
|
||||||
keyboardActions = KeyboardActions { fm.clearFocus() },
|
|
||||||
readOnly = updateMode,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp, horizontal = HorizontalPadding)
|
|
||||||
)
|
|
||||||
DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope ->
|
DelegatedScope.entries.filter { VERSION.SDK_INT >= it.requiresApi }.forEach { scope ->
|
||||||
val checked = scope in scopes
|
val checked = scope in scopes
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -112,13 +112,11 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.bintianqi.owndroid.ChoosePackageContract
|
|
||||||
import com.bintianqi.owndroid.HorizontalPadding
|
import com.bintianqi.owndroid.HorizontalPadding
|
||||||
import com.bintianqi.owndroid.NotificationUtils
|
import com.bintianqi.owndroid.NotificationUtils
|
||||||
import com.bintianqi.owndroid.Privilege
|
import com.bintianqi.owndroid.Privilege
|
||||||
@@ -143,6 +141,7 @@ import com.bintianqi.owndroid.ui.Notes
|
|||||||
import com.bintianqi.owndroid.ui.SwitchItem
|
import com.bintianqi.owndroid.ui.SwitchItem
|
||||||
import com.bintianqi.owndroid.uriToStream
|
import com.bintianqi.owndroid.uriToStream
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -1134,7 +1133,9 @@ fun NearbyStreamingPolicyScreen(onNavigateUp: () -> Unit) {
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
@Composable
|
@Composable
|
||||||
fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
fun LockTaskModeScreen(
|
||||||
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit, onNavigateUp: () -> Unit
|
||||||
|
) {
|
||||||
val coroutine = rememberCoroutineScope()
|
val coroutine = rememberCoroutineScope()
|
||||||
val pagerState = rememberPagerState { 3 }
|
val pagerState = rememberPagerState { 3 }
|
||||||
var tabIndex by remember { mutableIntStateOf(0) }
|
var tabIndex by remember { mutableIntStateOf(0) }
|
||||||
@@ -1177,8 +1178,8 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
|||||||
.padding(horizontal = HorizontalPadding)
|
.padding(horizontal = HorizontalPadding)
|
||||||
.padding(bottom = 80.dp)
|
.padding(bottom = 80.dp)
|
||||||
) {
|
) {
|
||||||
if(page == 0) StartLockTaskMode()
|
if(page == 0) StartLockTaskMode(chosenPackage, onChoosePackage)
|
||||||
else LockTaskPackages()
|
else LockTaskPackages(chosenPackage, onChoosePackage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Column(
|
Column(
|
||||||
@@ -1197,33 +1198,20 @@ fun LockTaskModeScreen(onNavigateUp: () -> Unit) {
|
|||||||
|
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.StartLockTaskMode() {
|
private fun ColumnScope.StartLockTaskMode(
|
||||||
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val focusMgr = LocalFocusManager.current
|
val focusMgr = LocalFocusManager.current
|
||||||
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
|
var startLockTaskApp by rememberSaveable { mutableStateOf("") }
|
||||||
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
|
var startLockTaskActivity by rememberSaveable { mutableStateOf("") }
|
||||||
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
var specifyActivity by rememberSaveable { mutableStateOf(false) }
|
||||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
LaunchedEffect(Unit) {
|
||||||
result?.let { startLockTaskApp = it }
|
startLockTaskApp = chosenPackage.receive()
|
||||||
}
|
}
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
Spacer(Modifier.padding(vertical = 5.dp))
|
||||||
OutlinedTextField(
|
PackageNameTextField(startLockTaskApp, onChoosePackage,
|
||||||
value = startLockTaskApp,
|
Modifier.padding(vertical = 3.dp), { startLockTaskApp = it })
|
||||||
onValueChange = { startLockTaskApp = it },
|
|
||||||
label = { Text(stringResource(R.string.package_name)) },
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
|
||||||
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
|
|
||||||
trailingIcon = {
|
|
||||||
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(50))
|
|
||||||
.clickable { choosePackage.launch(null) }
|
|
||||||
.padding(3.dp))
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 3.dp)
|
|
||||||
)
|
|
||||||
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
|
CheckBoxItem(R.string.specify_activity, specifyActivity) { specifyActivity = it }
|
||||||
AnimatedVisibility(specifyActivity) {
|
AnimatedVisibility(specifyActivity) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
@@ -1264,37 +1252,23 @@ private fun ColumnScope.StartLockTaskMode() {
|
|||||||
|
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.LockTaskPackages() {
|
private fun ColumnScope.LockTaskPackages(
|
||||||
|
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val focusMgr = LocalFocusManager.current
|
|
||||||
val lockTaskPackages = remember { mutableStateListOf<String>() }
|
val lockTaskPackages = remember { mutableStateListOf<String>() }
|
||||||
var input by rememberSaveable { mutableStateOf("") }
|
var input by rememberSaveable { mutableStateOf("") }
|
||||||
val choosePackage = rememberLauncherForActivityResult(ChoosePackageContract()) { result ->
|
LaunchedEffect(Unit) {
|
||||||
result?.let { input = it }
|
lockTaskPackages.addAll(Privilege.DPM.getLockTaskPackages(Privilege.DAR))
|
||||||
|
input = chosenPackage.receive()
|
||||||
}
|
}
|
||||||
LaunchedEffect(Unit) { lockTaskPackages.addAll(Privilege.DPM.getLockTaskPackages(Privilege.DAR)) }
|
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
Spacer(Modifier.padding(vertical = 5.dp))
|
||||||
if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none))
|
if(lockTaskPackages.isEmpty()) Text(text = stringResource(R.string.none))
|
||||||
for(i in lockTaskPackages) {
|
for(i in lockTaskPackages) {
|
||||||
ListItem(i) { lockTaskPackages -= i }
|
ListItem(i) { lockTaskPackages -= i }
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
PackageNameTextField(input, onChoosePackage,
|
||||||
value = input,
|
Modifier.padding(vertical = 3.dp), { input = it })
|
||||||
onValueChange = { input = it },
|
|
||||||
label = { Text(stringResource(R.string.package_name)) },
|
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
|
||||||
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
|
|
||||||
trailingIcon = {
|
|
||||||
Icon(painter = painterResource(R.drawable.list_fill0), contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(50))
|
|
||||||
.clickable { choosePackage.launch(null) }
|
|
||||||
.padding(3.dp))
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 3.dp)
|
|
||||||
)
|
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|||||||
Reference in New Issue
Block a user