From 735177ac46ab49ee3bd97cd7425ef4bebcbb9f0e Mon Sep 17 00:00:00 2001 From: BinTianqi Date: Mon, 2 Mar 2026 23:44:58 +0800 Subject: [PATCH] feat: improve intent filter presets Fix app installer and uninstall app bug --- .../owndroid/AppInstallerActivity.kt | 12 ++- .../CrossProfileIntentFilterModel.kt | 23 ++++- .../CrossProfileIntentFilterScreen.kt | 95 +++++++++++++++---- .../CrossProfileIntentFilterViewModel.kt | 4 + .../owndroid/ui/navigation/Destination.kt | 1 + .../owndroid/ui/navigation/EntryProvider.kt | 10 +- .../com/bintianqi/owndroid/utils/Utils.kt | 6 +- app/src/main/res/values-zh-rCN/strings.xml | 7 +- app/src/main/res/values/strings.xml | 7 +- 9 files changed, 135 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt index 8234826..47161e9 100644 --- a/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt +++ b/app/src/main/java/com/bintianqi/owndroid/AppInstallerActivity.kt @@ -10,14 +10,20 @@ import androidx.fragment.app.FragmentActivity import com.bintianqi.owndroid.feature.applications.AppInstaller import com.bintianqi.owndroid.feature.applications.AppInstallerViewModel import com.bintianqi.owndroid.ui.theme.OwnDroidTheme +import com.bintianqi.owndroid.utils.viewModelFactory -class AppInstallerActivity:FragmentActivity() { +class AppInstallerActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) - val vm by viewModels() + val myApp = application as MyApplication + val vm by viewModels { + viewModelFactory { + AppInstallerViewModel(myApp, myApp.container.settingsRepo) + } + } vm.initialize(intent) - val themeState = (application as MyApplication).container.themeState + val themeState = myApp.container.themeState setContent { val theme by themeState.collectAsState() OwnDroidTheme(theme) { diff --git a/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterModel.kt b/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterModel.kt index f3e66d5..e50db92 100644 --- a/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterModel.kt @@ -10,7 +10,24 @@ data class IntentFilterOptions( val direction: Int // 1: private to work, 2: work to private, 3: both ) -val crossProfileIntentFilterPresets = mapOf( - R.string.allow_file_sharing to - IntentFilterOptions(Intent.ACTION_SEND, Intent.CATEGORY_DEFAULT, "*/*", 3) +val directionTextMap = mapOf( + 1 to R.string.personal_to_work, + 2 to R.string.work_to_personal, + 3 to R.string.both_direction +) + +class IntentFilterPreset( + val name: Int, val action: String, val category: String = "", val mimeType: String = "" +) + +val crossProfileIntentFilterPresets = listOf( + IntentFilterPreset(R.string.open_file, Intent.ACTION_VIEW, Intent.CATEGORY_DEFAULT, "*/*"), + IntentFilterPreset(R.string.share, Intent.ACTION_SEND, Intent.CATEGORY_DEFAULT, "*/*"), + IntentFilterPreset(R.string.share_multiple, Intent.ACTION_SEND_MULTIPLE), + IntentFilterPreset(R.string.edit, Intent.ACTION_EDIT, Intent.CATEGORY_DEFAULT, "*/*"), + IntentFilterPreset(R.string.get_content, Intent.ACTION_GET_CONTENT, Intent.CATEGORY_DEFAULT, "*/*"), + IntentFilterPreset(R.string.install_app, Intent.ACTION_INSTALL_PACKAGE), + IntentFilterPreset(R.string.uninstall_app, Intent.ACTION_UNINSTALL_PACKAGE), + IntentFilterPreset(R.string.choose_file, Intent.ACTION_OPEN_DOCUMENT, Intent.CATEGORY_DEFAULT, "*/*"), + IntentFilterPreset(R.string.choose_folder, Intent.ACTION_OPEN_DOCUMENT_TREE) ) diff --git a/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterScreen.kt b/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterScreen.kt index a3932de..5bc38e8 100644 --- a/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterScreen.kt +++ b/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterScreen.kt @@ -2,6 +2,7 @@ package com.bintianqi.owndroid.feature.work_profile import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -9,11 +10,13 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button @@ -26,6 +29,7 @@ import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -39,6 +43,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -46,17 +51,17 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import com.bintianqi.owndroid.R +import com.bintianqi.owndroid.ui.MyLazyScaffold import com.bintianqi.owndroid.ui.NavIcon import com.bintianqi.owndroid.ui.Notes import com.bintianqi.owndroid.utils.BottomPadding import com.bintianqi.owndroid.utils.HorizontalPadding import com.bintianqi.owndroid.utils.adaptiveInsets - @OptIn(ExperimentalMaterial3Api::class) @Composable fun CrossProfileIntentFilterScreen( - vm: CrossProfileIntentFilterViewModel, onNavigateUp: () -> Unit + vm: CrossProfileIntentFilterViewModel, onNavigateUp: () -> Unit, navigateToPresets: () -> Unit ) { val focusMgr = LocalFocusManager.current var action by remember { mutableStateOf("") } @@ -66,7 +71,6 @@ fun CrossProfileIntentFilterScreen( var mimeType by remember { mutableStateOf("") } var dropdown by remember { mutableStateOf(false) } var direction by remember { mutableIntStateOf(3) } - var dialog by remember { mutableStateOf(false) } val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { if (it != null) vm.importFilters(it) } @@ -90,7 +94,7 @@ fun CrossProfileIntentFilterScreen( DropdownMenuItem( { Text(stringResource(R.string.presets)) }, { - dialog = true + navigateToPresets() menu = false }, leadingIcon = { @@ -124,11 +128,6 @@ fun CrossProfileIntentFilterScreen( }, contentWindowInsets = adaptiveInsets() ) { paddingValues -> - val directionTextMap = mapOf( - 1 to R.string.personal_to_work, - 2 to R.string.work_to_personal, - 3 to R.string.both_direction - ) Column( Modifier .padding(paddingValues) @@ -205,24 +204,82 @@ fun CrossProfileIntentFilterScreen( Notes(R.string.info_cross_profile_intent_filter) Spacer(Modifier.height(BottomPadding)) } - if (dialog) AlertDialog( - title = { Text(stringResource(R.string.presets)) }, + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CrossProfileIntentFilterPresetsScreen( + vm: CrossProfileIntentFilterViewModel, navigateUp: () -> Unit +) { + var dialog by remember { mutableStateOf(null) } + MyLazyScaffold(R.string.presets, navigateUp) { + items(crossProfileIntentFilterPresets) { + Row( + Modifier.padding(start = HorizontalPadding, end = 8.dp, bottom = 2.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column(Modifier.weight(1F)) { + Text(stringResource(it.name)) + Text( + it.action, + Modifier.alpha(0.7F), + style = MaterialTheme.typography.bodyMedium + ) + } + IconButton({ dialog = it }) { + Icon(Icons.Default.Add, null) + } + } + } + } + if (dialog != null) { + var direction by remember { mutableIntStateOf(3) } + AlertDialog( + title = { + Text(stringResource(dialog!!.name)) + }, text = { - crossProfileIntentFilterPresets.forEach { - Button({ - vm.addFilter(it.value) - dialog = false - }) { - Text(stringResource(it.key)) + Column { + var dropdown by remember { mutableStateOf(false) } + Text(dialog!!.action) + ExposedDropdownMenuBox( + dropdown, { dropdown = it }, Modifier.padding(top = 5.dp) + ) { + OutlinedTextField( + stringResource(directionTextMap[direction]!!), {}, + Modifier + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) + .fillMaxWidth(), + textStyle = MaterialTheme.typography.bodyLarge, + label = { Text(stringResource(R.string.direction)) }, readOnly = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(dropdown) } + ) + ExposedDropdownMenu(dropdown, { dropdown = false }) { + directionTextMap.forEach { + DropdownMenuItem({ Text(stringResource(it.value)) }, { + direction = it.key + dropdown = false + }) + } + } } } }, confirmButton = { - TextButton({ dialog = false }) { + TextButton({ + vm.addPreset(dialog!!, direction) + dialog = null + }) { + Text(stringResource(R.string.confirm)) + } + }, + dismissButton = { + TextButton({ dialog = null }) { Text(stringResource(R.string.cancel)) } }, - onDismissRequest = { dialog = false } + onDismissRequest = { dialog = null } ) } } diff --git a/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterViewModel.kt b/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterViewModel.kt index d47edb1..bf4e0ed 100644 --- a/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterViewModel.kt +++ b/app/src/main/java/com/bintianqi/owndroid/feature/work_profile/CrossProfileIntentFilterViewModel.kt @@ -25,6 +25,10 @@ class CrossProfileIntentFilterViewModel( toastChannel.sendStatus(true) } + fun addPreset(preset: IntentFilterPreset, direction: Int) { + addFilter(IntentFilterOptions(preset.action, preset.category, preset.mimeType, direction)) + } + fun clearFilters() = ph.safeDpmCall { dpm.clearCrossProfileIntentFilters(dar) repo.deleteAllCrossProfileIntentFilters() diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/navigation/Destination.kt b/app/src/main/java/com/bintianqi/owndroid/ui/navigation/Destination.kt index 62ec71b..df08f20 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/navigation/Destination.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/navigation/Destination.kt @@ -60,6 +60,7 @@ sealed class Destination : NavKey { @Serializable object CreateWorkProfile : Destination() @Serializable object SuspendPersonalApp : Destination() @Serializable object CrossProfileIntentFilter : Destination() + @Serializable object CrossProfileIntentFilterPresets: Destination() @Serializable object DeleteWorkProfile : Destination() @Serializable object ApplicationFeatures : Destination() diff --git a/app/src/main/java/com/bintianqi/owndroid/ui/navigation/EntryProvider.kt b/app/src/main/java/com/bintianqi/owndroid/ui/navigation/EntryProvider.kt index 3896626..9e2d614 100644 --- a/app/src/main/java/com/bintianqi/owndroid/ui/navigation/EntryProvider.kt +++ b/app/src/main/java/com/bintianqi/owndroid/ui/navigation/EntryProvider.kt @@ -95,6 +95,7 @@ import com.bintianqi.owndroid.feature.users.UserSessionMessageScreen import com.bintianqi.owndroid.feature.users.UsersOptionsScreen import com.bintianqi.owndroid.feature.users.UsersScreen import com.bintianqi.owndroid.feature.work_profile.CreateWorkProfileScreen +import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterPresetsScreen import com.bintianqi.owndroid.feature.work_profile.CrossProfileIntentFilterScreen import com.bintianqi.owndroid.feature.work_profile.DeleteWorkProfileScreen import com.bintianqi.owndroid.feature.work_profile.SuspendPersonalAppScreen @@ -360,7 +361,14 @@ fun myEntryProvider( entry { CrossProfileIntentFilterScreen( viewModel(factory = container.viewModelFactory), ::navigateUp - ) + ) { + navigate(Destination.CrossProfileIntentFilterPresets) + } + } + entry( + metadata = navParentKey(Destination.CrossProfileIntentFilter) + ) { + CrossProfileIntentFilterPresetsScreen(viewModel(), ::navigateUp) } entry( metadata = navParentKey(Destination.WorkProfile) diff --git a/app/src/main/java/com/bintianqi/owndroid/utils/Utils.kt b/app/src/main/java/com/bintianqi/owndroid/utils/Utils.kt index efea5e0..2f11151 100644 --- a/app/src/main/java/com/bintianqi/owndroid/utils/Utils.kt +++ b/app/src/main/java/com/bintianqi/owndroid/utils/Utils.kt @@ -233,9 +233,11 @@ fun uninstallPackage( 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) { + if (statusExtra == PackageInstaller.STATUS_PENDING_USER_ACTION) { @SuppressWarnings("UnsafeIntentLaunch") - context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT) as Intent?) + val confirmIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + confirmIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(confirmIntent) } else { context.unregisterReceiver(this) if (statusExtra == PackageInstaller.STATUS_SUCCESS) { diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index bbd6c51..da04e48 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -338,7 +338,12 @@ 个人应用已经因此挂起:%1$s Intent过滤器 预设 - 允许分享文件 + 打开文件 + 分享 + 分享(多个) + 获取内容 + 选择文件 + 选择文件夹 方向 双向 工作到个人 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 534bbad..4391081 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -372,7 +372,12 @@ Personal app suspended because of this: %1$s Intent filter Presets - Allow file sharing + Open file + Share + Share (multiple) + Get content + Choose file + Choose folder Direction Both direction Work to personal