From 793db41bb7f214f225a667ae5243d2010e432ba5 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Sat, 17 May 2025 16:40:06 +0800 Subject: [PATCH] compose: details: Setup permissions screen Signed-off-by: Aayush Gupta --- .../aurora/store/compose/navigation/Screen.kt | 6 + .../compose/ui/details/AppDetailsScreen.kt | 13 ++- .../ui/details/DetailsPermissionScreen.kt | 104 ++++++++++++++++++ .../details/DetailsExodusViewModel.kt | 2 +- .../details/DetailsPermissionViewModel.kt | 49 +++++++++ 5 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/compose/ui/details/DetailsPermissionScreen.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/details/DetailsPermissionViewModel.kt diff --git a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt index dfa5b2abd..a709bbb93 100644 --- a/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt +++ b/app/src/main/java/com/aurora/store/compose/navigation/Screen.kt @@ -60,4 +60,10 @@ sealed class Screen( */ @Serializable data object DetailsReview : Screen() + + /** + * Child screen of [AppDetails]; Avoid navigating to this screen directly. + */ + @Serializable + data object DetailsPermission : Screen() } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt index 3d66d369a..4b17d7992 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt @@ -291,6 +291,7 @@ private fun ScreenContentApp( onNavigateToDetailsScreenshot = { showExtraPane(Screen.DetailsScreenshot(it)) }, onNavigateToDetailsReview = { showExtraPane(Screen.DetailsReview) }, onNavigateToDetailsExodus = { showExtraPane(Screen.DetailsExodus) }, + onNavigateToDetailsPermission = { showExtraPane(Screen.DetailsPermission) }, onDownload = onDownload, onManualDownload = onManualDownload, onCancelDownload = onCancelDownload, @@ -320,7 +321,9 @@ private fun ScreenContentApp( onNavigateUp = ::showMainPane, onNavigateToAppDetails = onNavigateToAppDetails ) - + is Screen.DetailsPermission -> DetailsPermissionScreen( + onNavigateUp = ::showMainPane + ) is Screen.DetailsScreenshot -> DetailsScreenshotScreen( index = screen.index, onNavigateUp = ::showMainPane @@ -359,6 +362,7 @@ private fun ScreenContentAppMainPane( onNavigateToDetailsScreenshot: (index: Int) -> Unit, onNavigateToDetailsReview: () -> Unit, onNavigateToDetailsExodus: () -> Unit, + onNavigateToDetailsPermission: () -> Unit, onDownload: () -> Unit, onManualDownload: () -> Unit, onCancelDownload: () -> Unit, @@ -480,7 +484,12 @@ private fun ScreenContentAppMainPane( HeaderComposable( title = stringResource(R.string.details_permission), - subtitle = stringResource(R.string.permissions, app.permissions.size) + subtitle = if (app.permissions.isNotEmpty()) { + stringResource(R.string.permissions, app.permissions.size) + } else { + stringResource(R.string.details_no_permission) + }, + onClick = if (app.permissions.isNotEmpty()) onNavigateToDetailsPermission else null ) if (dataSafetyReport != null) AppDataSafety(report = dataSafetyReport) diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/DetailsPermissionScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/DetailsPermissionScreen.kt new file mode 100644 index 000000000..0db568d2e --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/details/DetailsPermissionScreen.kt @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2025 The Calyx Institute + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.compose.ui.details + +import android.content.pm.PermissionInfo +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +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.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.PreviewParameter +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.window.core.layout.WindowWidthSizeClass +import com.aurora.extensions.adaptiveNavigationIcon +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.compose.composables.TopAppBarComposable +import com.aurora.store.compose.composables.preview.AppPreviewProvider +import com.aurora.store.viewmodel.details.AppDetailsViewModel +import com.aurora.store.viewmodel.details.DetailsPermissionViewModel + +@Composable +fun DetailsPermissionScreen( + onNavigateUp: () -> Unit, + appDetailsViewModel: AppDetailsViewModel = hiltViewModel(), + detailsPermissionViewModel: DetailsPermissionViewModel = hiltViewModel { factory: DetailsPermissionViewModel.Factory -> + factory.create(appDetailsViewModel.app.value!!.permissions) + }, + windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() +) { + val app by appDetailsViewModel.app.collectAsStateWithLifecycle() + val permissionsInfo by detailsPermissionViewModel.permissionsInfo.collectAsStateWithLifecycle() + + val topAppBarTitle = when (windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass) { + WindowWidthSizeClass.COMPACT -> app!!.displayName + else -> stringResource(R.string.details_permission) + } + + ScreenContent( + topAppBarTitle = topAppBarTitle, + onNavigateUp = onNavigateUp, + permissionsInfo = permissionsInfo + ) +} + +@Composable +private fun ScreenContent( + topAppBarTitle: String? = null, + permissionsInfo: Map = emptyMap(), + onNavigateUp: () -> Unit = {}, + windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() +) { + val packageManager = LocalContext.current.packageManager + + Scaffold( + topBar = { + TopAppBarComposable( + title = topAppBarTitle, + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, + onNavigateUp = onNavigateUp + ) + } + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = dimensionResource(R.dimen.padding_medium)) + ) { + items(items = permissionsInfo.keys.toList(), key = { it }) { permission -> + // Bail out if this is not a known permission for the OS + val permissionInfo = permissionsInfo.getValue(permission) ?: return@items + + Text( + text = permissionInfo.loadLabel(packageManager).toString(), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Preview +@Composable +private fun DetailsPermissionScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) { + ScreenContent( + topAppBarTitle = app.displayName + ) +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/DetailsExodusViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/DetailsExodusViewModel.kt index 9493e9280..6ea5a3345 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/details/DetailsExodusViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/details/DetailsExodusViewModel.kt @@ -37,7 +37,7 @@ class DetailsExodusViewModel @AssistedInject constructor( getExodusTrackersFromReport() } - fun getExodusTrackersFromReport() { + private fun getExodusTrackersFromReport() { viewModelScope.launch(Dispatchers.IO) { val trackerObjects = exodusReport.trackers.map { exodusTrackers.getJSONObject(it.toString()) diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/DetailsPermissionViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/DetailsPermissionViewModel.kt new file mode 100644 index 000000000..457d0a41a --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/details/DetailsPermissionViewModel.kt @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 The Calyx Institute + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.viewmodel.details + +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.PermissionInfo +import androidx.lifecycle.ViewModel +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +@HiltViewModel(assistedFactory = DetailsPermissionViewModel.Factory::class) +class DetailsPermissionViewModel @AssistedInject constructor( + @Assisted private val permissions: List, + @ApplicationContext private val context: Context +): ViewModel() { + + @AssistedFactory + interface Factory { + fun create(permissions: List): DetailsPermissionViewModel + } + + private val _permissionsInfo = MutableStateFlow>(emptyMap()) + val permissionsInfo = _permissionsInfo.asStateFlow() + + init { + fetchPermissions() + } + + private fun fetchPermissions() { + _permissionsInfo.value = permissions.associateWith { getPermissionInfo(it) } + } + + private fun getPermissionInfo(permissionName: String): PermissionInfo? { + return try { + context.packageManager.getPermissionInfo(permissionName, 0) + } catch (_: PackageManager.NameNotFoundException) { + null + } + } +}