Update permissions UI.

This commit is contained in:
Michelle Tang
2026-04-14 17:31:22 -04:00
committed by jeffrey-signal
parent 48cd1c1da0
commit d2243707b5

View File

@@ -10,13 +10,14 @@ package org.signal.registration.screens.permissions
import android.Manifest
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.SpaceAround
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
@@ -26,6 +27,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
@@ -34,11 +36,14 @@ import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import org.signal.core.ui.WindowBreakpoint
import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.rememberWindowBreakpoint
import org.signal.registration.R
import org.signal.registration.screens.RegistrationScreen
import org.signal.registration.screens.util.MockMultiplePermissionsState
import org.signal.registration.screens.util.MockPermissionsState
import org.signal.registration.test.TestTags
@@ -57,122 +62,258 @@ fun PermissionsScreen(
onProceed: () -> Unit = {},
modifier: Modifier = Modifier
) {
val windowBreakpoint = rememberWindowBreakpoint()
val permissions = permissionsState.permissions.map { it.permission }
Surface(modifier = modifier.testTag(TestTags.PERMISSIONS_SCREEN)) {
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState)
.weight(weight = 1f, fill = false)
.padding(bottom = 16.dp)
.horizontalGutters()
) {
Spacer(Modifier.height(40.dp))
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__allow_permissions),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.fillMaxWidth()
when (windowBreakpoint) {
WindowBreakpoint.SMALL -> {
Surface(modifier = modifier.testTag(TestTags.PERMISSIONS_SCREEN)) {
CompactLayout(
permissionsState = permissionsState,
permissions = permissions,
onProceed = onProceed,
modifier = modifier
)
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__to_help_you_message_people_you_know),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 16.dp)
)
Spacer(modifier = Modifier.height(40.dp))
if (permissions.any { it == Manifest.permission.POST_NOTIFICATIONS }) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_notification),
title = stringResource(id = R.string.GrantPermissionsFragment__notifications),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__get_notified_when)
)
}
if (permissions.any { it == Manifest.permission.READ_CONTACTS || it == Manifest.permission.WRITE_CONTACTS }) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_contact),
title = stringResource(id = R.string.GrantPermissionsFragment__contacts),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__find_people_you_know)
)
}
if (permissions.any {
it == Manifest.permission.READ_EXTERNAL_STORAGE ||
it == Manifest.permission.WRITE_EXTERNAL_STORAGE ||
it == Manifest.permission.READ_MEDIA_IMAGES ||
it == Manifest.permission.READ_MEDIA_VIDEO ||
it == Manifest.permission.READ_MEDIA_AUDIO
}
) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_file),
title = stringResource(id = R.string.GrantPermissionsFragment__storage),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__send_photos_videos_and_files)
)
}
if (permissions.any { it == Manifest.permission.READ_PHONE_STATE || it == Manifest.permission.READ_PHONE_NUMBERS }) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_phone),
title = stringResource(id = R.string.GrantPermissionsFragment__phone_calls),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__make_registering_easier)
)
}
}
}
Surface(
shadowElevation = if (scrollState.canScrollForward) 8.dp else 0.dp,
modifier = Modifier.fillMaxWidth()
WindowBreakpoint.MEDIUM -> {
Surface(modifier = modifier.testTag(TestTags.PERMISSIONS_SCREEN)) {
MediumLayout(
permissionsState = permissionsState,
permissions = permissions,
onProceed = onProceed,
modifier = modifier
)
}
}
WindowBreakpoint.LARGE -> {
Surface(modifier = modifier.testTag(TestTags.PERMISSIONS_SCREEN)) {
LargeLayout(
permissionsState = permissionsState,
permissions = permissions,
onProceed = onProceed,
modifier = modifier
)
}
}
}
}
@Composable
private fun CompactLayout(
modifier: Modifier,
permissions: List<String>,
permissionsState: MultiplePermissionsState,
onProceed: () -> Unit
) {
RegistrationScreen(
modifier = modifier.fillMaxSize(),
content = {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(top = 24.dp),
contentAlignment = Alignment.BottomCenter
) {
Box(
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.padding(top = 8.dp, bottom = 24.dp)
.fillMaxHeight()
.verticalScroll(scrollState)
.padding(top = 16.dp)
.horizontalGutters()
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__allow_permissions),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__to_help_you_message_people_you_know),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 16.dp, bottom = 40.dp)
)
PermissionList(permissions)
Spacer(modifier = Modifier.padding(48.dp))
}
PermissionButtons(onProceed, permissionsState, scrollState.canScrollForward)
}
}
)
}
@Composable
private fun MediumLayout(
modifier: Modifier,
permissions: List<String>,
permissionsState: MultiplePermissionsState,
onProceed: () -> Unit
) {
RegistrationScreen(
modifier = modifier.fillMaxSize(),
content = {
Row(
horizontalArrangement = SpaceAround,
modifier = Modifier.padding(top = 56.dp)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 24.dp)
) {
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__allow_permissions),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__to_help_you_message_people_you_know),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 16.dp)
)
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
contentAlignment = Alignment.BottomCenter
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(scrollState)
.padding(horizontal = 24.dp)
) {
TextButton(
modifier = Modifier
.weight(weight = 1f, fill = false)
.testTag(TestTags.PERMISSIONS_NOT_NOW_BUTTON),
onClick = onProceed
) {
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__not_now)
)
}
Spacer(modifier = Modifier.size(24.dp))
Buttons.LargeTonal(
modifier = Modifier.testTag(TestTags.PERMISSIONS_NEXT_BUTTON),
onClick = {
permissionsState.launchMultiplePermissionRequest()
onProceed()
}
) {
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__next)
)
}
PermissionList(permissions)
Spacer(modifier = Modifier.padding(48.dp))
}
PermissionButtons(
onProceed,
permissionsState,
scrollState.canScrollForward
)
}
}
}
)
}
@Composable
private fun LargeLayout(
modifier: Modifier,
permissions: List<String>,
permissionsState: MultiplePermissionsState,
onProceed: () -> Unit
) {
RegistrationScreen(
modifier = modifier.fillMaxSize(),
content = {
Row(
horizontalArrangement = SpaceAround,
modifier = Modifier.padding(top = 76.dp)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 24.dp)
) {
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__allow_permissions),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.fillMaxWidth()
)
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__to_help_you_message_people_you_know),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 16.dp)
)
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
contentAlignment = Alignment.BottomCenter
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxHeight()
.verticalScroll(scrollState)
.padding(horizontal = 24.dp)
) {
PermissionList(permissions)
Spacer(modifier = Modifier.padding(48.dp))
}
PermissionButtons(
onProceed,
permissionsState,
scrollState.canScrollForward
)
}
}
}
)
}
@Composable
private fun PermissionList(permissions: List<String>) {
if (permissions.any { it == Manifest.permission.POST_NOTIFICATIONS }) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_notification),
title = stringResource(id = R.string.GrantPermissionsFragment__notifications),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__get_notified_when)
)
}
if (permissions.any { it == Manifest.permission.READ_CONTACTS || it == Manifest.permission.WRITE_CONTACTS }) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_contact),
title = stringResource(id = R.string.GrantPermissionsFragment__contacts),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__find_people_you_know)
)
}
if (permissions.any {
it == Manifest.permission.READ_EXTERNAL_STORAGE ||
it == Manifest.permission.WRITE_EXTERNAL_STORAGE ||
it == Manifest.permission.READ_MEDIA_IMAGES ||
it == Manifest.permission.READ_MEDIA_VIDEO ||
it == Manifest.permission.READ_MEDIA_AUDIO
}
) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_file),
title = stringResource(id = R.string.GrantPermissionsFragment__storage),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__send_photos_videos_and_files)
)
}
if (permissions.any { it == Manifest.permission.READ_PHONE_STATE || it == Manifest.permission.READ_PHONE_NUMBERS }) {
PermissionRow(
imageVector = ImageVector.vectorResource(id = R.drawable.permission_phone),
title = stringResource(id = R.string.GrantPermissionsFragment__phone_calls),
subtitle = stringResource(id = R.string.GrantPermissionsFragment__make_registering_easier)
)
}
}
@@ -207,6 +348,47 @@ private fun PermissionRow(
}
}
@Composable
private fun PermissionButtons(onProceed: () -> Unit, permissionsState: MultiplePermissionsState, canScrollForward: Boolean) {
Surface(
modifier = Modifier.fillMaxWidth(),
shadowElevation = if (canScrollForward) 8.dp else 0.dp
) {
Row(
horizontalArrangement = Arrangement.Absolute.Right,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
.horizontalGutters()
) {
TextButton(
modifier = Modifier
.weight(weight = 1f, fill = false)
.testTag(TestTags.PERMISSIONS_NOT_NOW_BUTTON),
onClick = onProceed
) {
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__not_now)
)
}
Spacer(modifier = Modifier.size(24.dp))
Buttons.LargeTonal(
modifier = Modifier.testTag(TestTags.PERMISSIONS_NEXT_BUTTON),
onClick = {
permissionsState.launchMultiplePermissionRequest()
onProceed()
}
) {
Text(
text = stringResource(id = R.string.GrantPermissionsFragment__next)
)
}
}
}
}
@AllDevicePreviews
@Composable
private fun PermissionsScreenPreview() {