compose: details: Setup permissions screen

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-05-17 16:40:06 +08:00
parent 5d0bb8b634
commit 793db41bb7
5 changed files with 171 additions and 3 deletions

View File

@@ -60,4 +60,10 @@ sealed class Screen(
*/ */
@Serializable @Serializable
data object DetailsReview : Screen() data object DetailsReview : Screen()
/**
* Child screen of [AppDetails]; Avoid navigating to this screen directly.
*/
@Serializable
data object DetailsPermission : Screen()
} }

View File

@@ -291,6 +291,7 @@ private fun ScreenContentApp(
onNavigateToDetailsScreenshot = { showExtraPane(Screen.DetailsScreenshot(it)) }, onNavigateToDetailsScreenshot = { showExtraPane(Screen.DetailsScreenshot(it)) },
onNavigateToDetailsReview = { showExtraPane(Screen.DetailsReview) }, onNavigateToDetailsReview = { showExtraPane(Screen.DetailsReview) },
onNavigateToDetailsExodus = { showExtraPane(Screen.DetailsExodus) }, onNavigateToDetailsExodus = { showExtraPane(Screen.DetailsExodus) },
onNavigateToDetailsPermission = { showExtraPane(Screen.DetailsPermission) },
onDownload = onDownload, onDownload = onDownload,
onManualDownload = onManualDownload, onManualDownload = onManualDownload,
onCancelDownload = onCancelDownload, onCancelDownload = onCancelDownload,
@@ -320,7 +321,9 @@ private fun ScreenContentApp(
onNavigateUp = ::showMainPane, onNavigateUp = ::showMainPane,
onNavigateToAppDetails = onNavigateToAppDetails onNavigateToAppDetails = onNavigateToAppDetails
) )
is Screen.DetailsPermission -> DetailsPermissionScreen(
onNavigateUp = ::showMainPane
)
is Screen.DetailsScreenshot -> DetailsScreenshotScreen( is Screen.DetailsScreenshot -> DetailsScreenshotScreen(
index = screen.index, index = screen.index,
onNavigateUp = ::showMainPane onNavigateUp = ::showMainPane
@@ -359,6 +362,7 @@ private fun ScreenContentAppMainPane(
onNavigateToDetailsScreenshot: (index: Int) -> Unit, onNavigateToDetailsScreenshot: (index: Int) -> Unit,
onNavigateToDetailsReview: () -> Unit, onNavigateToDetailsReview: () -> Unit,
onNavigateToDetailsExodus: () -> Unit, onNavigateToDetailsExodus: () -> Unit,
onNavigateToDetailsPermission: () -> Unit,
onDownload: () -> Unit, onDownload: () -> Unit,
onManualDownload: () -> Unit, onManualDownload: () -> Unit,
onCancelDownload: () -> Unit, onCancelDownload: () -> Unit,
@@ -480,7 +484,12 @@ private fun ScreenContentAppMainPane(
HeaderComposable( HeaderComposable(
title = stringResource(R.string.details_permission), 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) if (dataSafetyReport != null) AppDataSafety(report = dataSafetyReport)

View File

@@ -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<String, PermissionInfo?> = 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
)
}

View File

@@ -37,7 +37,7 @@ class DetailsExodusViewModel @AssistedInject constructor(
getExodusTrackersFromReport() getExodusTrackersFromReport()
} }
fun getExodusTrackersFromReport() { private fun getExodusTrackersFromReport() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val trackerObjects = exodusReport.trackers.map { val trackerObjects = exodusReport.trackers.map {
exodusTrackers.getJSONObject(it.toString()) exodusTrackers.getJSONObject(it.toString())

View File

@@ -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<String>,
@ApplicationContext private val context: Context
): ViewModel() {
@AssistedFactory
interface Factory {
fun create(permissions: List<String>): DetailsPermissionViewModel
}
private val _permissionsInfo = MutableStateFlow<Map<String, PermissionInfo?>>(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
}
}
}