Update camera permission UI for usernames.

This commit is contained in:
mtang-signal 2024-05-17 10:48:40 -07:00 committed by Cody Henthorne
parent 2744dec43a
commit a99db2b16e
4 changed files with 79 additions and 17 deletions

View file

@ -2,6 +2,7 @@
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
@ -77,6 +78,7 @@ import org.thoughtcrime.securesms.components.settings.app.usernamelinks.Username
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameLinkSettingsState.ActiveTab
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.permissions.PermissionCompat
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.util.CommunicationActions
import java.io.ByteArrayOutputStream
@ -140,6 +142,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
onShareBadge = { shareQrBadge(requireActivity(), viewModel.generateQrCodeImage(helpText)) },
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
onQrResultHandled = { viewModel.onQrResultHandled() },
onOpenCameraClicked = { askCameraPermissions() },
onOpenGalleryClicked = {
if (galleryPermissionState.allPermissionsGranted) {
galleryLauncher.launch(Unit)
@ -153,6 +156,10 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
}
@ -161,6 +168,15 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
super.onResume()
viewModel.onResume()
}
private fun askCameraPermissions() {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, parentFragmentManager)
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
.execute()
}
}
@OptIn(ExperimentalPermissionsApi::class)
@ -177,6 +193,7 @@ private fun MainScreen(
onShareBadge: () -> Unit = {},
onQrCodeScanned: (String) -> Unit = {},
onQrResultHandled: () -> Unit = {},
onOpenCameraClicked: () -> Unit = {},
onOpenGalleryClicked: () -> Unit = {},
onLinkReset: () -> Unit = {},
onBackNavigationPressed: () -> Unit = {},
@ -244,7 +261,9 @@ private fun MainScreen(
qrScanResult = state.qrScanResult,
onQrCodeScanned = onQrCodeScanned,
onQrResultHandled = onQrResultHandled,
onOpenCameraClicked = onOpenCameraClicked,
onOpenGalleryClicked = onOpenGalleryClicked,
hasCameraPermission = cameraPermissionState.status.isGranted,
modifier = Modifier.padding(contentPadding),
onRecipientFound = { recipient ->
val taskStack = TaskStackBuilder
@ -296,13 +315,7 @@ private fun TopAppBarContent(
TabButton(
label = stringResource(R.string.UsernameLinkSettings_scan_tab_name),
active = activeTab == ActiveTab.Scan,
onClick = {
if (cameraPermissionState.status.isGranted) {
onScanTabSelected()
} else {
cameraPermissionState.launchPermissionRequest()
}
},
onClick = { onScanTabSelected() },
modifier = Modifier.padding(end = 8.dp)
)
}

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@ -26,11 +27,13 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.LifecycleOwner
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.signal.core.ui.Buttons
import org.signal.core.ui.Dialogs
import org.signal.core.ui.theme.SignalTheme
import org.signal.qr.QrScannerView
@ -49,8 +52,10 @@ fun UsernameQrScanScreen(
qrScanResult: QrScanResult?,
onQrCodeScanned: (String) -> Unit,
onQrResultHandled: () -> Unit,
onOpenCameraClicked: () -> Unit,
onOpenGalleryClicked: () -> Unit,
onRecipientFound: (Recipient) -> Unit,
hasCameraPermission: Boolean,
modifier: Modifier = Modifier
) {
val path = remember { Path() }
@ -113,9 +118,34 @@ fun UsernameQrScanScreen(
.fillMaxHeight()
.drawWithContent {
drawContent()
drawQrCrosshair(path)
if (hasCameraPermission) {
drawQrCrosshair(path)
}
}
)
if (!hasCameraPermission) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.align(Alignment.Center)
.padding(50.dp)
) {
Text(
text = stringResource(R.string.CameraXFragment_to_scan_qr_code_allow_camera),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
color = Color.White,
modifier = Modifier.padding(10.dp)
)
Buttons.MediumTonal(
colors = ButtonDefaults.filledTonalButtonColors(),
onClick = onOpenCameraClicked
) {
Text(stringResource(R.string.CameraXFragment_allow_access))
}
}
}
FloatingActionButton(
shape = CircleShape,

View file

@ -7,6 +7,8 @@
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
@ -30,7 +32,10 @@ import androidx.compose.ui.res.stringResource
import androidx.lifecycle.LifecycleOwner
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.permissions.rememberPermissionState
import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.signal.core.ui.Dialogs
import org.signal.core.ui.theme.SignalTheme
@ -38,6 +43,7 @@ import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.getParcelableExtraCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.permissions.PermissionCompat
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.DynamicTheme
@ -55,6 +61,11 @@ class UsernameQrScannerActivity : AppCompatActivity() {
private val viewModel: UsernameQrScannerViewModel by viewModels()
private val disposables = LifecycleDisposable()
@SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
disposables.bindTo(this)
@ -74,6 +85,7 @@ class UsernameQrScannerActivity : AppCompatActivity() {
}
}
val cameraPermissionState: PermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
val state by viewModel.state
SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(LocalContext.current)) {
@ -82,10 +94,12 @@ class UsernameQrScannerActivity : AppCompatActivity() {
diposables = disposables.disposables,
state = state,
galleryPermissionsState = galleryPermissionState,
cameraPermissionState = cameraPermissionState,
onQrScanned = { url -> viewModel.onQrScanned(url) },
onQrResultHandled = {
finish()
},
onOpenCameraClicked = { askCameraPermissions() },
onOpenGalleryClicked = {
if (galleryPermissionState.allPermissionsGranted) {
galleryLauncher.launch(Unit)
@ -108,6 +122,15 @@ class UsernameQrScannerActivity : AppCompatActivity() {
}
}
private fun askCameraPermissions() {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, supportFragmentManager)
.onAnyDenied { Toast.makeText(this, R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
.execute()
}
class Contract : ActivityResultContract<Unit, RecipientId?>() {
override fun createIntent(context: Context, input: Unit): Intent {
return Intent(context, UsernameQrScannerActivity::class.java)
@ -126,8 +149,10 @@ fun Content(
diposables: CompositeDisposable,
state: UsernameQrScannerViewModel.ScannerState,
galleryPermissionsState: MultiplePermissionsState,
cameraPermissionState: PermissionState,
onQrScanned: (String) -> Unit,
onQrResultHandled: () -> Unit,
onOpenCameraClicked: () -> Unit,
onOpenGalleryClicked: () -> Unit,
onRecipientFound: (Recipient) -> Unit,
onBackNavigationPressed: () -> Unit
@ -155,8 +180,10 @@ fun Content(
qrScanResult = state.qrScanResult,
onQrCodeScanned = onQrScanned,
onQrResultHandled = onQrResultHandled,
onOpenCameraClicked = onOpenCameraClicked,
onOpenGalleryClicked = onOpenGalleryClicked,
onRecipientFound = onRecipientFound,
hasCameraPermission = cameraPermissionState.status.isGranted,
modifier = Modifier.padding(contentPadding)
)

View file

@ -81,7 +81,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameQrScannerActivity
import org.thoughtcrime.securesms.invites.InviteActions
import org.thoughtcrime.securesms.permissions.compose.Permissions
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberVisualTransformation
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.registration.util.CountryPrefix
@ -142,13 +141,6 @@ class FindByActivity : PassphraseRequiredActivity() {
) {
val context = LocalContext.current
val cameraPermissionController = Permissions.cameraPermissionHandler(
rationale = stringResource(id = R.string.PaymentsTransferFragment__to_scan_a_qr_code_signal_needs_access_to_the_camera),
onPermissionGranted = {
qrScanLauncher.launch(Unit)
}
)
Content(
paddingValues = it,
state = state,
@ -171,7 +163,7 @@ class FindByActivity : PassphraseRequiredActivity() {
navController.navigate("select-country-prefix")
},
onQrCodeScanClicked = {
cameraPermissionController.request()
qrScanLauncher.launch(Unit)
}
)
}