android/ui: prompt for permissions and show list of permissions with statuses
Updates #ENG-2948 Signed-off-by: Percy Wegmann <percy@tailscale.com>pull/251/head
parent
8e063051b6
commit
44ba20a24e
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package com.tailscale.ipn.ui.model
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.os.Build
|
||||||
|
import com.tailscale.ipn.R
|
||||||
|
|
||||||
|
object Permissions {
|
||||||
|
/**
|
||||||
|
* All permissions that Tailscale requires. MainView takes care of prompting for permissions, and
|
||||||
|
* PermissionsView provides a list of permissions with corresponding statuses and a link to the
|
||||||
|
* application settings.
|
||||||
|
*
|
||||||
|
* When new permissions are needed, just add them to this list and the necessary strings to
|
||||||
|
* strings.xml and the rest should take care of itself.
|
||||||
|
*/
|
||||||
|
val all: List<Permission>
|
||||||
|
get() {
|
||||||
|
val result = mutableListOf<Permission>()
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
result.add(
|
||||||
|
Permission(
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
R.string.permission_write_external_storage,
|
||||||
|
R.string.permission_write_external_storage_needed,
|
||||||
|
R.string.permission_write_external_storage_granted,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
result.add(
|
||||||
|
Permission(
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS,
|
||||||
|
R.string.permission_post_notifications,
|
||||||
|
R.string.permission_post_notifications_needed,
|
||||||
|
R.string.permission_post_notifications_granted))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Permission(
|
||||||
|
val name: String,
|
||||||
|
val title: Int,
|
||||||
|
val neededDescription: Int,
|
||||||
|
val grantedDescription: Int
|
||||||
|
)
|
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package com.tailscale.ipn.ui.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight
|
||||||
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
|
import com.tailscale.ipn.R
|
||||||
|
import com.tailscale.ipn.ui.model.Permissions
|
||||||
|
import com.tailscale.ipn.ui.util.itemsWithDividers
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
@Composable
|
||||||
|
fun PermissionsView(nav: BackNavigation, openApplicationSettings: () -> Unit) {
|
||||||
|
Scaffold(topBar = { Header(title = R.string.permissions, onBack = nav.onBack) }) { innerPadding ->
|
||||||
|
val permissions = Permissions.all
|
||||||
|
val permissionStates =
|
||||||
|
rememberMultiplePermissionsState(permissions = permissions.map { it.name })
|
||||||
|
val permissionsWithStates = permissions.zip(permissionStates.permissions)
|
||||||
|
|
||||||
|
LazyColumn(modifier = Modifier.padding(innerPadding)) {
|
||||||
|
itemsWithDividers(permissionsWithStates) { (permission, state) ->
|
||||||
|
var modifier: Modifier = Modifier
|
||||||
|
if (!state.status.isGranted) {
|
||||||
|
modifier = modifier.clickable { openApplicationSettings() }
|
||||||
|
}
|
||||||
|
ListItem(
|
||||||
|
modifier = modifier,
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
if (state.status.isGranted) Icons.Filled.CheckCircle else Icons.Filled.Warning,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
contentDescription =
|
||||||
|
stringResource(if (state.status.isGranted) R.string.ok else R.string.warning))
|
||||||
|
},
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(permission.title), style = MaterialTheme.typography.titleMedium)
|
||||||
|
},
|
||||||
|
supportingContent = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (state.status.isGranted) permission.grantedDescription
|
||||||
|
else permission.neededDescription))
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
if (!state.status.isGranted) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Outlined.KeyboardArrowRight,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
contentDescription = stringResource(R.string.more))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue