compose: onboarding: Initial permissions screen work
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -29,14 +29,12 @@ import com.aurora.store.data.model.PermissionType
|
|||||||
* Composable to display permission details in a list
|
* Composable to display permission details in a list
|
||||||
* @param modifier The modifier to be applied to the composable
|
* @param modifier The modifier to be applied to the composable
|
||||||
* @param permission [Permission] to display
|
* @param permission [Permission] to display
|
||||||
* @param isGranted If the permission has been granted
|
|
||||||
* @param onAction Callback when the user clicks the action button
|
* @param onAction Callback when the user clicks the action button
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun PermissionComposable(
|
fun PermissionComposable(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
permission: Permission,
|
permission: Permission,
|
||||||
isGranted: Boolean = false,
|
|
||||||
onAction: () -> Unit = {}
|
onAction: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -58,9 +56,9 @@ fun PermissionComposable(
|
|||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TextButton(onClick = onAction, enabled = !isGranted) {
|
TextButton(onClick = onAction, enabled = !permission.isGranted) {
|
||||||
Text(
|
Text(
|
||||||
text = if (isGranted) {
|
text = if (permission.isGranted) {
|
||||||
stringResource(R.string.action_granted)
|
stringResource(R.string.action_granted)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.action_grant)
|
stringResource(R.string.action_grant)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
package com.aurora.store.compose.composables
|
package com.aurora.store.compose.composables
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -27,24 +25,21 @@ import com.aurora.store.R
|
|||||||
* @see ActionHeaderComposable
|
* @see ActionHeaderComposable
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun TextDividerComposable(modifier: Modifier = Modifier, @StringRes title: Int) {
|
fun TextDividerComposable(modifier: Modifier = Modifier, title: String) {
|
||||||
Row(
|
Text(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(dimensionResource(R.dimen.padding_small))
|
.padding(dimensionResource(R.dimen.padding_small)),
|
||||||
) {
|
text = title.uppercase(),
|
||||||
Text(
|
style = MaterialTheme.typography.bodySmall,
|
||||||
text = stringResource(title).uppercase(),
|
color = MaterialTheme.colorScheme.primary,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
maxLines = 1,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
overflow = TextOverflow.Ellipsis
|
||||||
maxLines = 1,
|
)
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun TextDividerComposablePreview() {
|
private fun TextDividerComposablePreview() {
|
||||||
TextDividerComposable(title = R.string.item_optional)
|
TextDividerComposable(title = stringResource(R.string.item_optional))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator
|
|||||||
import com.aurora.store.compose.ui.commons.BlacklistScreen
|
import com.aurora.store.compose.ui.commons.BlacklistScreen
|
||||||
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
import com.aurora.store.compose.ui.details.AppDetailsScreen
|
||||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||||
|
import com.aurora.store.compose.ui.onboarding.PermissionsScreen
|
||||||
import com.aurora.store.compose.ui.search.SearchScreen
|
import com.aurora.store.compose.ui.search.SearchScreen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +70,12 @@ fun NavDisplay(startDestination: NavKey) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry<Screen.Permissions> { screen ->
|
||||||
|
PermissionsScreen(
|
||||||
|
onNavigateUp = { onNavigateUp() },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,7 @@ sealed class Screen : NavKey, Parcelable {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Search : Screen()
|
data object Search : Screen()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object Permissions : Screen()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,10 +77,12 @@ import com.aurora.store.compose.ui.details.components.Tags
|
|||||||
import com.aurora.store.compose.ui.details.components.Testing
|
import com.aurora.store.compose.ui.details.components.Testing
|
||||||
import com.aurora.store.compose.ui.details.navigation.ExtraScreen
|
import com.aurora.store.compose.ui.details.navigation.ExtraScreen
|
||||||
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
import com.aurora.store.compose.ui.dev.DevProfileScreen
|
||||||
|
import com.aurora.store.compose.ui.onboarding.PermissionsScreen
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.model.AppState
|
import com.aurora.store.data.model.AppState
|
||||||
import com.aurora.store.data.model.Report
|
import com.aurora.store.data.model.Report
|
||||||
import com.aurora.store.data.model.Scores
|
import com.aurora.store.data.model.Scores
|
||||||
|
import com.aurora.store.data.providers.PermissionProvider.Companion.isPermittedToInstall
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
import com.aurora.store.util.ShortcutManagerUtil
|
import com.aurora.store.util.ShortcutManagerUtil
|
||||||
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
import com.aurora.store.viewmodel.details.AppDetailsViewModel
|
||||||
@@ -237,6 +239,16 @@ private fun ScreenContentApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onInstall() {
|
||||||
|
if (isPermittedToInstall(context, app)) {
|
||||||
|
onDownload()
|
||||||
|
onNavigateBack()
|
||||||
|
} else {
|
||||||
|
context.toast(R.string.permissions_denied)
|
||||||
|
showExtraPane(Screen.Permissions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SetupMenu() {
|
fun SetupMenu() {
|
||||||
AppDetailsMenu(isInstalled = app.isInstalled, isFavorite = isFavorite) { menuItem ->
|
AppDetailsMenu(isInstalled = app.isInstalled, isFavorite = isFavorite) { menuItem ->
|
||||||
@@ -271,7 +283,7 @@ private fun ScreenContentApp(
|
|||||||
Actions(
|
Actions(
|
||||||
primaryActionDisplayName = stringResource(R.string.action_update),
|
primaryActionDisplayName = stringResource(R.string.action_update),
|
||||||
secondaryActionDisplayName = stringResource(R.string.action_uninstall),
|
secondaryActionDisplayName = stringResource(R.string.action_uninstall),
|
||||||
onPrimaryAction = onDownload,
|
onPrimaryAction = ::onInstall,
|
||||||
onSecondaryAction = onUninstall
|
onSecondaryAction = onUninstall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -295,7 +307,7 @@ private fun ScreenContentApp(
|
|||||||
Actions(
|
Actions(
|
||||||
primaryActionDisplayName = primaryActionName,
|
primaryActionDisplayName = primaryActionName,
|
||||||
secondaryActionDisplayName = stringResource(R.string.title_manual_download),
|
secondaryActionDisplayName = stringResource(R.string.title_manual_download),
|
||||||
onPrimaryAction = onDownload,
|
onPrimaryAction = ::onInstall,
|
||||||
onSecondaryAction = { showExtraPane(ExtraScreen.ManualDownload) }
|
onSecondaryAction = { showExtraPane(ExtraScreen.ManualDownload) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -476,6 +488,11 @@ private fun ScreenContentApp(
|
|||||||
onNavigateToAppDetails = { onNavigateToAppDetails(it) }
|
onNavigateToAppDetails = { onNavigateToAppDetails(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is Screen.Permissions -> PermissionsScreen(
|
||||||
|
onNavigateUp = ::onNavigateBack,
|
||||||
|
onPermissionCallback = { onInstall() }
|
||||||
|
)
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.compose.ui.onboarding
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||||
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.aurora.extensions.adaptiveNavigationIcon
|
||||||
|
import com.aurora.extensions.isTAndAbove
|
||||||
|
import com.aurora.extensions.toast
|
||||||
|
import com.aurora.store.BuildConfig
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.compose.composables.PermissionComposable
|
||||||
|
import com.aurora.store.compose.composables.TextDividerComposable
|
||||||
|
import com.aurora.store.compose.composables.TopAppBarComposable
|
||||||
|
import com.aurora.store.data.model.Permission
|
||||||
|
import com.aurora.store.data.model.PermissionType
|
||||||
|
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
|
||||||
|
import com.aurora.store.util.PackageUtil
|
||||||
|
import com.aurora.store.viewmodel.onboarding.PermissionsViewModel
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
private const val TAG = "PermissionsScreen"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PermissionsScreen(
|
||||||
|
isOnboarding: Boolean = false,
|
||||||
|
onNavigateUp: () -> Unit,
|
||||||
|
onPermissionCallback: (type: PermissionType) -> Unit = {},
|
||||||
|
viewModel: PermissionsViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val permissions by viewModel.permissions.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
ScreenContent(
|
||||||
|
isOnboarding = isOnboarding,
|
||||||
|
permissions = permissions,
|
||||||
|
onNavigateUp = onNavigateUp,
|
||||||
|
onPermissionCallback = { type ->
|
||||||
|
viewModel.refreshPermissionsList()
|
||||||
|
onPermissionCallback(type)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScreenContent(
|
||||||
|
isOnboarding: Boolean = false,
|
||||||
|
permissions: List<Permission> = emptyList(),
|
||||||
|
onNavigateUp: () -> Unit = {},
|
||||||
|
onPermissionCallback: (type: PermissionType) -> Unit = {},
|
||||||
|
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo()
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
var permissionRequested by rememberSaveable { mutableStateOf<PermissionType?>(null) }
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi", "BatteryLife")
|
||||||
|
val intentMap = mapOf(
|
||||||
|
PermissionType.STORAGE_MANAGER to PackageUtil.getStorageManagerIntent(context),
|
||||||
|
PermissionType.INSTALL_UNKNOWN_APPS to PackageUtil.getInstallUnknownAppsIntent(),
|
||||||
|
PermissionType.DOZE_WHITELIST to Intent(
|
||||||
|
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
|
||||||
|
"package:${BuildConfig.APPLICATION_ID}".toUri()
|
||||||
|
),
|
||||||
|
PermissionType.APP_LINKS to Intent(
|
||||||
|
ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||||
|
"package:${BuildConfig.APPLICATION_ID}".toUri()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val intentLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.StartActivityForResult(),
|
||||||
|
onResult = { permissionRequested?.let { onPermissionCallback(it) } }
|
||||||
|
)
|
||||||
|
val permissionLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.RequestPermission(),
|
||||||
|
onResult = {
|
||||||
|
when (permissionRequested) {
|
||||||
|
PermissionType.STORAGE_MANAGER -> {
|
||||||
|
intentLauncher.launch(intentMap[PermissionType.STORAGE_MANAGER]!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> permissionRequested?.let { onPermissionCallback(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fun requestPermission(type: PermissionType) {
|
||||||
|
try {
|
||||||
|
permissionRequested = type
|
||||||
|
when (type) {
|
||||||
|
PermissionType.EXTERNAL_STORAGE -> {
|
||||||
|
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionType.POST_NOTIFICATIONS -> {
|
||||||
|
if (isTAndAbove) {
|
||||||
|
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionType.STORAGE_MANAGER -> {
|
||||||
|
if (!isGranted(context, PermissionType.INSTALL_UNKNOWN_APPS)) {
|
||||||
|
context.toast(R.string.toast_permission_installer_required)
|
||||||
|
} else {
|
||||||
|
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val intent = intentMap[type] ?: return
|
||||||
|
intentLauncher.launch(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.e(TAG, "Error requesting permission", exception)
|
||||||
|
permissionRequested = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBarComposable(
|
||||||
|
title = stringResource(R.string.onboarding_title_permissions),
|
||||||
|
navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon,
|
||||||
|
onNavigateUp = if (isOnboarding) null else onNavigateUp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(paddingValues),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_xxsmall))
|
||||||
|
) {
|
||||||
|
permissions.sortedBy { it.optional }
|
||||||
|
.groupBy { permission -> permission.optional }
|
||||||
|
.forEach { (key, value) ->
|
||||||
|
stickyHeader {
|
||||||
|
TextDividerComposable(
|
||||||
|
title = if (!key) {
|
||||||
|
stringResource(R.string.item_required)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.item_optional)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(items = value, key = { p -> p.type.name }) { permission ->
|
||||||
|
PermissionComposable(
|
||||||
|
permission = permission,
|
||||||
|
onAction = { requestPermission(permission.type) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PermissionsScreenPreview() {
|
||||||
|
val permissions = PermissionType.entries.map { type ->
|
||||||
|
Permission(
|
||||||
|
type = type,
|
||||||
|
title = LoremIpsum(3).values.first(),
|
||||||
|
subtitle = LoremIpsum(7).values.first(),
|
||||||
|
optional = Random.nextBoolean(),
|
||||||
|
isGranted = Random.nextBoolean()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ScreenContent(permissions = permissions)
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ data class Permission(
|
|||||||
val title: String,
|
val title: String,
|
||||||
val subtitle: String,
|
val subtitle: String,
|
||||||
val optional: Boolean = false,
|
val optional: Boolean = false,
|
||||||
|
val isGranted: Boolean = false
|
||||||
) {
|
) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return when (other) {
|
return when (other) {
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import com.aurora.extensions.isDomainVerified
|
|||||||
import com.aurora.extensions.isExternalStorageAccessible
|
import com.aurora.extensions.isExternalStorageAccessible
|
||||||
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
||||||
import com.aurora.extensions.isTAndAbove
|
import com.aurora.extensions.isTAndAbove
|
||||||
|
import com.aurora.extensions.requiresObbDir
|
||||||
import com.aurora.extensions.toast
|
import com.aurora.extensions.toast
|
||||||
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.BuildConfig
|
import com.aurora.store.BuildConfig
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.model.PermissionType
|
import com.aurora.store.data.model.PermissionType
|
||||||
@@ -28,6 +30,52 @@ import com.aurora.store.util.PackageUtil
|
|||||||
class PermissionProvider(private val fragment: Fragment) :
|
class PermissionProvider(private val fragment: Fragment) :
|
||||||
ActivityResultCallback<ActivityResult> {
|
ActivityResultCallback<ActivityResult> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Aurora Store has permissions to install the given app
|
||||||
|
*/
|
||||||
|
fun isPermittedToInstall(context: Context, app: App): Boolean {
|
||||||
|
if (!isGranted(context, PermissionType.INSTALL_UNKNOWN_APPS)) return false
|
||||||
|
return when {
|
||||||
|
app.fileList.requiresObbDir() -> {
|
||||||
|
return isGranted(context, PermissionType.STORAGE_MANAGER)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a known permission has been granted
|
||||||
|
*/
|
||||||
|
fun isGranted(context: Context, permissionType: PermissionType): Boolean {
|
||||||
|
return when (permissionType) {
|
||||||
|
PermissionType.EXTERNAL_STORAGE,
|
||||||
|
PermissionType.STORAGE_MANAGER -> {
|
||||||
|
context.isExternalStorageAccessible()
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionType.INSTALL_UNKNOWN_APPS -> {
|
||||||
|
PackageUtil.canRequestPackageInstalls(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionType.POST_NOTIFICATIONS -> {
|
||||||
|
if (isTAndAbove) {
|
||||||
|
context.checkManifestPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionType.DOZE_WHITELIST -> context.isIgnoringBatteryOptimizations()
|
||||||
|
|
||||||
|
PermissionType.APP_LINKS -> context.isDomainVerified("play.google.com") &&
|
||||||
|
context.isDomainVerified("market.android.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val TAG = PermissionProvider::class.java.simpleName
|
private val TAG = PermissionProvider::class.java.simpleName
|
||||||
|
|
||||||
private val context: Context
|
private val context: Context
|
||||||
@@ -47,7 +95,7 @@ class PermissionProvider(private val fragment: Fragment) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(result: ActivityResult) {
|
override fun onActivityResult(result: ActivityResult) {
|
||||||
permissionRequested?.let { permissionCallback(isGranted(it)) }
|
permissionRequested?.let { permissionCallback(isGranted(context, it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun request(permissionType: PermissionType, callback: (Boolean) -> Unit = {}) {
|
fun request(permissionType: PermissionType, callback: (Boolean) -> Unit = {}) {
|
||||||
@@ -67,7 +115,7 @@ class PermissionProvider(private val fragment: Fragment) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
PermissionType.STORAGE_MANAGER -> {
|
PermissionType.STORAGE_MANAGER -> {
|
||||||
if (!isGranted(PermissionType.INSTALL_UNKNOWN_APPS)) {
|
if (!isGranted(context, PermissionType.INSTALL_UNKNOWN_APPS)) {
|
||||||
context.toast(R.string.toast_permission_installer_required)
|
context.toast(R.string.toast_permission_installer_required)
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
@@ -100,30 +148,6 @@ class PermissionProvider(private val fragment: Fragment) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isGranted(permissionType: PermissionType): Boolean {
|
|
||||||
return when (permissionType) {
|
|
||||||
PermissionType.EXTERNAL_STORAGE,
|
|
||||||
PermissionType.STORAGE_MANAGER -> {
|
|
||||||
context.isExternalStorageAccessible()
|
|
||||||
}
|
|
||||||
|
|
||||||
PermissionType.INSTALL_UNKNOWN_APPS -> PackageUtil.canRequestPackageInstalls(context)
|
|
||||||
|
|
||||||
PermissionType.POST_NOTIFICATIONS -> {
|
|
||||||
if (isTAndAbove) {
|
|
||||||
context.checkManifestPermission(Manifest.permission.POST_NOTIFICATIONS)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PermissionType.DOZE_WHITELIST -> context.isIgnoringBatteryOptimizations()
|
|
||||||
|
|
||||||
PermissionType.APP_LINKS -> context.isDomainVerified("play.google.com") &&
|
|
||||||
context.isDomainVerified("market.android.com")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unregister() {
|
fun unregister() {
|
||||||
intentLauncher.unregister()
|
intentLauncher.unregister()
|
||||||
permissionLauncher.unregister()
|
permissionLauncher.unregister()
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.aurora.extensions.isTAndAbove
|
|||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.model.Permission
|
import com.aurora.store.data.model.Permission
|
||||||
import com.aurora.store.data.model.PermissionType
|
import com.aurora.store.data.model.PermissionType
|
||||||
|
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
|
||||||
import com.aurora.store.databinding.FragmentOnboardingPermissionsBinding
|
import com.aurora.store.databinding.FragmentOnboardingPermissionsBinding
|
||||||
import com.aurora.store.view.epoxy.views.TextDividerViewModel_
|
import com.aurora.store.view.epoxy.views.TextDividerViewModel_
|
||||||
import com.aurora.store.view.epoxy.views.preference.PermissionViewModel_
|
import com.aurora.store.view.epoxy.views.preference.PermissionViewModel_
|
||||||
@@ -160,7 +161,7 @@ class PermissionsFragment : BaseFragment<FragmentOnboardingPermissionsBinding>()
|
|||||||
return PermissionViewModel_()
|
return PermissionViewModel_()
|
||||||
.id(permission.type.name)
|
.id(permission.type.name)
|
||||||
.permission(permission)
|
.permission(permission)
|
||||||
.isGranted(permissionProvider.isGranted(permission.type))
|
.isGranted(isGranted(requireContext(), permission.type))
|
||||||
.click { _ ->
|
.click { _ ->
|
||||||
permissionProvider.request(permission.type) {
|
permissionProvider.request(permission.type) {
|
||||||
if (it) updateController()
|
if (it) updateController()
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.preference.SwitchPreferenceCompat
|
|||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.model.PermissionType
|
import com.aurora.store.data.model.PermissionType
|
||||||
import com.aurora.store.data.model.UpdateMode
|
import com.aurora.store.data.model.UpdateMode
|
||||||
|
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS
|
import com.aurora.store.util.Preferences.PREFERENCES_UPDATES_RESTRICTIONS
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
import com.aurora.store.util.Preferences.PREFERENCE_FILTER_AURORA_ONLY
|
||||||
@@ -63,7 +64,7 @@ class UpdatesPreference : BasePreferenceFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateMode.CHECK_AND_NOTIFY -> {
|
UpdateMode.CHECK_AND_NOTIFY -> {
|
||||||
if (permissionProvider.isGranted(PermissionType.POST_NOTIFICATIONS)) {
|
if (isGranted(requireContext(), PermissionType.POST_NOTIFICATIONS)) {
|
||||||
handleAutoUpdateDependentPrefs(true)
|
handleAutoUpdateDependentPrefs(true)
|
||||||
viewModel.updateHelper.scheduleAutomatedCheck()
|
viewModel.updateHelper.scheduleAutomatedCheck()
|
||||||
true
|
true
|
||||||
@@ -81,7 +82,7 @@ class UpdatesPreference : BasePreferenceFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateMode.CHECK_AND_INSTALL -> {
|
UpdateMode.CHECK_AND_INSTALL -> {
|
||||||
if (permissionProvider.isGranted(PermissionType.DOZE_WHITELIST)) {
|
if (isGranted(requireContext(), PermissionType.DOZE_WHITELIST)) {
|
||||||
handleAutoUpdateDependentPrefs(true)
|
handleAutoUpdateDependentPrefs(true)
|
||||||
viewModel.updateHelper.scheduleAutomatedCheck()
|
viewModel.updateHelper.scheduleAutomatedCheck()
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import com.aurora.store.R
|
|||||||
import com.aurora.store.compose.navigation.Screen
|
import com.aurora.store.compose.navigation.Screen
|
||||||
import com.aurora.store.data.model.MinimalApp
|
import com.aurora.store.data.model.MinimalApp
|
||||||
import com.aurora.store.data.model.PermissionType
|
import com.aurora.store.data.model.PermissionType
|
||||||
|
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
import com.aurora.store.data.room.update.Update
|
import com.aurora.store.data.room.update.Update
|
||||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||||
@@ -192,7 +193,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
|
|
||||||
private fun updateSingle(update: Update) {
|
private fun updateSingle(update: Update) {
|
||||||
if (update.fileList.requiresObbDir()) {
|
if (update.fileList.requiresObbDir()) {
|
||||||
if (permissionProvider.isGranted(PermissionType.STORAGE_MANAGER)) {
|
if (isGranted(requireContext(), PermissionType.STORAGE_MANAGER)) {
|
||||||
viewModel.download(update)
|
viewModel.download(update)
|
||||||
} else {
|
} else {
|
||||||
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
|
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
|
||||||
@@ -207,7 +208,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
private fun updateAll() {
|
private fun updateAll() {
|
||||||
viewModel.updateAllEnqueued = true
|
viewModel.updateAllEnqueued = true
|
||||||
if (viewModel.updates.value?.any { it.fileList.requiresObbDir() } == true) {
|
if (viewModel.updates.value?.any { it.fileList.requiresObbDir() } == true) {
|
||||||
if (permissionProvider.isGranted(PermissionType.STORAGE_MANAGER)) {
|
if (isGranted(requireContext(), PermissionType.STORAGE_MANAGER)) {
|
||||||
viewModel.downloadAll()
|
viewModel.downloadAll()
|
||||||
} else {
|
} else {
|
||||||
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
|
permissionProvider.request(PermissionType.STORAGE_MANAGER) {
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.viewmodel.onboarding
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.aurora.extensions.isOAndAbove
|
||||||
|
import com.aurora.extensions.isRAndAbove
|
||||||
|
import com.aurora.extensions.isSAndAbove
|
||||||
|
import com.aurora.extensions.isTAndAbove
|
||||||
|
import com.aurora.store.R
|
||||||
|
import com.aurora.store.data.model.Permission
|
||||||
|
import com.aurora.store.data.model.PermissionType
|
||||||
|
import com.aurora.store.data.providers.PermissionProvider.Companion.isGranted
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class PermissionsViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _permissions = MutableStateFlow(getPermissions(context))
|
||||||
|
val permissions = _permissions.asStateFlow()
|
||||||
|
|
||||||
|
private fun getPermissions(context: Context): List<Permission> {
|
||||||
|
val list = mutableListOf(
|
||||||
|
Permission(
|
||||||
|
type = PermissionType.INSTALL_UNKNOWN_APPS,
|
||||||
|
title = context.getString(R.string.onboarding_permission_installer),
|
||||||
|
subtitle = if (isOAndAbove) {
|
||||||
|
context.getString(R.string.onboarding_permission_installer_desc)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.onboarding_permission_installer_legacy_desc)
|
||||||
|
},
|
||||||
|
optional = false,
|
||||||
|
isGranted = isGranted(context, PermissionType.INSTALL_UNKNOWN_APPS)
|
||||||
|
),
|
||||||
|
Permission(
|
||||||
|
type = PermissionType.DOZE_WHITELIST,
|
||||||
|
title = context.getString(R.string.onboarding_permission_doze),
|
||||||
|
subtitle = context.getString(R.string.onboarding_permission_doze_desc),
|
||||||
|
optional = true,
|
||||||
|
isGranted = isGranted(context, PermissionType.DOZE_WHITELIST)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isRAndAbove) {
|
||||||
|
list.add(
|
||||||
|
Permission(
|
||||||
|
type = PermissionType.STORAGE_MANAGER,
|
||||||
|
title = context.getString(R.string.onboarding_permission_esm),
|
||||||
|
subtitle = context.getString(R.string.onboarding_permission_esa_desc),
|
||||||
|
optional = false,
|
||||||
|
isGranted = isGranted(context, PermissionType.STORAGE_MANAGER)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
list.add(
|
||||||
|
Permission(
|
||||||
|
type = PermissionType.EXTERNAL_STORAGE,
|
||||||
|
title = context.getString(R.string.onboarding_permission_esa),
|
||||||
|
subtitle = context.getString(R.string.onboarding_permission_esa_desc),
|
||||||
|
optional = false,
|
||||||
|
isGranted = isGranted(context, PermissionType.EXTERNAL_STORAGE)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTAndAbove) {
|
||||||
|
list.add(
|
||||||
|
Permission(
|
||||||
|
type = PermissionType.POST_NOTIFICATIONS,
|
||||||
|
title = context.getString(R.string.onboarding_permission_notifications),
|
||||||
|
subtitle = context.getString(R.string.onboarding_permission_notifications_desc),
|
||||||
|
optional = true,
|
||||||
|
isGranted = isGranted(context, PermissionType.POST_NOTIFICATIONS)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSAndAbove) {
|
||||||
|
list.add(
|
||||||
|
Permission(
|
||||||
|
type = PermissionType.APP_LINKS,
|
||||||
|
title = context.getString(R.string.app_links_title),
|
||||||
|
subtitle = context.getString(R.string.app_links_desc),
|
||||||
|
optional = true,
|
||||||
|
isGranted = isGranted(context, PermissionType.APP_LINKS)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshPermissionsList() {
|
||||||
|
_permissions.value = _permissions.value.map { permission ->
|
||||||
|
permission.copy(isGranted = isGranted(context, permission.type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user