Update camera permission UI for usernames.
This commit is contained in:
parent
2744dec43a
commit
a99db2b16e
4 changed files with 79 additions and 17 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
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.components.settings.app.usernamelinks.main.UsernameLinkSettingsState.ActiveTab
|
||||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||||
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
@ -140,6 +142,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||||
onShareBadge = { shareQrBadge(requireActivity(), viewModel.generateQrCodeImage(helpText)) },
|
onShareBadge = { shareQrBadge(requireActivity(), viewModel.generateQrCodeImage(helpText)) },
|
||||||
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
|
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
|
||||||
onQrResultHandled = { viewModel.onQrResultHandled() },
|
onQrResultHandled = { viewModel.onQrResultHandled() },
|
||||||
|
onOpenCameraClicked = { askCameraPermissions() },
|
||||||
onOpenGalleryClicked = {
|
onOpenGalleryClicked = {
|
||||||
if (galleryPermissionState.allPermissionsGranted) {
|
if (galleryPermissionState.allPermissionsGranted) {
|
||||||
galleryLauncher.launch(Unit)
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
disposables.bindTo(viewLifecycleOwner)
|
disposables.bindTo(viewLifecycleOwner)
|
||||||
}
|
}
|
||||||
|
@ -161,6 +168,15 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
viewModel.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)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
|
@ -177,6 +193,7 @@ private fun MainScreen(
|
||||||
onShareBadge: () -> Unit = {},
|
onShareBadge: () -> Unit = {},
|
||||||
onQrCodeScanned: (String) -> Unit = {},
|
onQrCodeScanned: (String) -> Unit = {},
|
||||||
onQrResultHandled: () -> Unit = {},
|
onQrResultHandled: () -> Unit = {},
|
||||||
|
onOpenCameraClicked: () -> Unit = {},
|
||||||
onOpenGalleryClicked: () -> Unit = {},
|
onOpenGalleryClicked: () -> Unit = {},
|
||||||
onLinkReset: () -> Unit = {},
|
onLinkReset: () -> Unit = {},
|
||||||
onBackNavigationPressed: () -> Unit = {},
|
onBackNavigationPressed: () -> Unit = {},
|
||||||
|
@ -244,7 +261,9 @@ private fun MainScreen(
|
||||||
qrScanResult = state.qrScanResult,
|
qrScanResult = state.qrScanResult,
|
||||||
onQrCodeScanned = onQrCodeScanned,
|
onQrCodeScanned = onQrCodeScanned,
|
||||||
onQrResultHandled = onQrResultHandled,
|
onQrResultHandled = onQrResultHandled,
|
||||||
|
onOpenCameraClicked = onOpenCameraClicked,
|
||||||
onOpenGalleryClicked = onOpenGalleryClicked,
|
onOpenGalleryClicked = onOpenGalleryClicked,
|
||||||
|
hasCameraPermission = cameraPermissionState.status.isGranted,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
onRecipientFound = { recipient ->
|
onRecipientFound = { recipient ->
|
||||||
val taskStack = TaskStackBuilder
|
val taskStack = TaskStackBuilder
|
||||||
|
@ -296,13 +315,7 @@ private fun TopAppBarContent(
|
||||||
TabButton(
|
TabButton(
|
||||||
label = stringResource(R.string.UsernameLinkSettings_scan_tab_name),
|
label = stringResource(R.string.UsernameLinkSettings_scan_tab_name),
|
||||||
active = activeTab == ActiveTab.Scan,
|
active = activeTab == ActiveTab.Scan,
|
||||||
onClick = {
|
onClick = { onScanTabSelected() },
|
||||||
if (cameraPermissionState.status.isGranted) {
|
|
||||||
onScanTabSelected()
|
|
||||||
} else {
|
|
||||||
cameraPermissionState.launchPermissionRequest()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
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.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
import org.signal.core.ui.Dialogs
|
import org.signal.core.ui.Dialogs
|
||||||
import org.signal.core.ui.theme.SignalTheme
|
import org.signal.core.ui.theme.SignalTheme
|
||||||
import org.signal.qr.QrScannerView
|
import org.signal.qr.QrScannerView
|
||||||
|
@ -49,8 +52,10 @@ fun UsernameQrScanScreen(
|
||||||
qrScanResult: QrScanResult?,
|
qrScanResult: QrScanResult?,
|
||||||
onQrCodeScanned: (String) -> Unit,
|
onQrCodeScanned: (String) -> Unit,
|
||||||
onQrResultHandled: () -> Unit,
|
onQrResultHandled: () -> Unit,
|
||||||
|
onOpenCameraClicked: () -> Unit,
|
||||||
onOpenGalleryClicked: () -> Unit,
|
onOpenGalleryClicked: () -> Unit,
|
||||||
onRecipientFound: (Recipient) -> Unit,
|
onRecipientFound: (Recipient) -> Unit,
|
||||||
|
hasCameraPermission: Boolean,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val path = remember { Path() }
|
val path = remember { Path() }
|
||||||
|
@ -113,9 +118,34 @@ fun UsernameQrScanScreen(
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
drawContent()
|
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(
|
FloatingActionButton(
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -30,7 +32,10 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.MultiplePermissionsState
|
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.rememberMultiplePermissionsState
|
||||||
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import org.signal.core.ui.Dialogs
|
import org.signal.core.ui.Dialogs
|
||||||
import org.signal.core.ui.theme.SignalTheme
|
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.signal.core.util.getParcelableExtraCompat
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||||
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme
|
import org.thoughtcrime.securesms.util.DynamicTheme
|
||||||
|
@ -55,6 +61,11 @@ class UsernameQrScannerActivity : AppCompatActivity() {
|
||||||
private val viewModel: UsernameQrScannerViewModel by viewModels()
|
private val viewModel: UsernameQrScannerViewModel by viewModels()
|
||||||
private val disposables = LifecycleDisposable()
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
disposables.bindTo(this)
|
disposables.bindTo(this)
|
||||||
|
@ -74,6 +85,7 @@ class UsernameQrScannerActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cameraPermissionState: PermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
|
||||||
val state by viewModel.state
|
val state by viewModel.state
|
||||||
|
|
||||||
SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(LocalContext.current)) {
|
SignalTheme(isDarkMode = DynamicTheme.isDarkTheme(LocalContext.current)) {
|
||||||
|
@ -82,10 +94,12 @@ class UsernameQrScannerActivity : AppCompatActivity() {
|
||||||
diposables = disposables.disposables,
|
diposables = disposables.disposables,
|
||||||
state = state,
|
state = state,
|
||||||
galleryPermissionsState = galleryPermissionState,
|
galleryPermissionsState = galleryPermissionState,
|
||||||
|
cameraPermissionState = cameraPermissionState,
|
||||||
onQrScanned = { url -> viewModel.onQrScanned(url) },
|
onQrScanned = { url -> viewModel.onQrScanned(url) },
|
||||||
onQrResultHandled = {
|
onQrResultHandled = {
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
|
onOpenCameraClicked = { askCameraPermissions() },
|
||||||
onOpenGalleryClicked = {
|
onOpenGalleryClicked = {
|
||||||
if (galleryPermissionState.allPermissionsGranted) {
|
if (galleryPermissionState.allPermissionsGranted) {
|
||||||
galleryLauncher.launch(Unit)
|
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?>() {
|
class Contract : ActivityResultContract<Unit, RecipientId?>() {
|
||||||
override fun createIntent(context: Context, input: Unit): Intent {
|
override fun createIntent(context: Context, input: Unit): Intent {
|
||||||
return Intent(context, UsernameQrScannerActivity::class.java)
|
return Intent(context, UsernameQrScannerActivity::class.java)
|
||||||
|
@ -126,8 +149,10 @@ fun Content(
|
||||||
diposables: CompositeDisposable,
|
diposables: CompositeDisposable,
|
||||||
state: UsernameQrScannerViewModel.ScannerState,
|
state: UsernameQrScannerViewModel.ScannerState,
|
||||||
galleryPermissionsState: MultiplePermissionsState,
|
galleryPermissionsState: MultiplePermissionsState,
|
||||||
|
cameraPermissionState: PermissionState,
|
||||||
onQrScanned: (String) -> Unit,
|
onQrScanned: (String) -> Unit,
|
||||||
onQrResultHandled: () -> Unit,
|
onQrResultHandled: () -> Unit,
|
||||||
|
onOpenCameraClicked: () -> Unit,
|
||||||
onOpenGalleryClicked: () -> Unit,
|
onOpenGalleryClicked: () -> Unit,
|
||||||
onRecipientFound: (Recipient) -> Unit,
|
onRecipientFound: (Recipient) -> Unit,
|
||||||
onBackNavigationPressed: () -> Unit
|
onBackNavigationPressed: () -> Unit
|
||||||
|
@ -155,8 +180,10 @@ fun Content(
|
||||||
qrScanResult = state.qrScanResult,
|
qrScanResult = state.qrScanResult,
|
||||||
onQrCodeScanned = onQrScanned,
|
onQrCodeScanned = onQrScanned,
|
||||||
onQrResultHandled = onQrResultHandled,
|
onQrResultHandled = onQrResultHandled,
|
||||||
|
onOpenCameraClicked = onOpenCameraClicked,
|
||||||
onOpenGalleryClicked = onOpenGalleryClicked,
|
onOpenGalleryClicked = onOpenGalleryClicked,
|
||||||
onRecipientFound = onRecipientFound,
|
onRecipientFound = onRecipientFound,
|
||||||
|
hasCameraPermission = cameraPermissionState.status.isGranted,
|
||||||
modifier = Modifier.padding(contentPadding)
|
modifier = Modifier.padding(contentPadding)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameQrScannerActivity
|
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameQrScannerActivity
|
||||||
import org.thoughtcrime.securesms.invites.InviteActions
|
import org.thoughtcrime.securesms.invites.InviteActions
|
||||||
import org.thoughtcrime.securesms.permissions.compose.Permissions
|
|
||||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberVisualTransformation
|
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberVisualTransformation
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
import org.thoughtcrime.securesms.registration.util.CountryPrefix
|
||||||
|
@ -142,13 +141,6 @@ class FindByActivity : PassphraseRequiredActivity() {
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
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(
|
Content(
|
||||||
paddingValues = it,
|
paddingValues = it,
|
||||||
state = state,
|
state = state,
|
||||||
|
@ -171,7 +163,7 @@ class FindByActivity : PassphraseRequiredActivity() {
|
||||||
navController.navigate("select-country-prefix")
|
navController.navigate("select-country-prefix")
|
||||||
},
|
},
|
||||||
onQrCodeScanClicked = {
|
onQrCodeScanClicked = {
|
||||||
cameraPermissionController.request()
|
qrScanLauncher.launch(Unit)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue