From d9c42a41359cb19ffc253bf8b275ca5ae173b05f Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Thu, 6 Jun 2024 10:24:42 -0700 Subject: [PATCH] Add ability to scan linked device qr code from gallery. --- app/src/main/AndroidManifest.xml | 2 +- ...ctivity.kt => QrImageSelectionActivity.kt} | 6 ++--- .../main/UsernameLinkSettingsFragment.kt | 2 +- .../main/UsernameQrScannerActivity.kt | 3 ++- .../linkdevice/AddLinkDeviceFragment.kt | 15 ++++++++++-- .../linkdevice/LinkDeviceQrScanScreen.kt | 4 +++- .../linkdevice/LinkDeviceViewModel.kt | 24 +++++++++++++++++++ .../securesms/qr/QrScanScreens.kt | 21 +++++++++++++++- 8 files changed, 67 insertions(+), 10 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/{UsernameQrImageSelectionActivity.kt => QrImageSelectionActivity.kt} (87%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9f7a4905a1..f6f54a1260 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1105,7 +1105,7 @@ android:theme="@style/Theme.Signal.WallpaperCropper" android:exported="false"/> - diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrImageSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/QrImageSelectionActivity.kt similarity index 87% rename from app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrImageSelectionActivity.kt rename to app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/QrImageSelectionActivity.kt index 773d957e65..4ae8ed625b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrImageSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/QrImageSelectionActivity.kt @@ -19,9 +19,9 @@ import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.v2.gallery.MediaGalleryFragment /** - * Select username qr code from gallery instead of using camera. + * Select qr code from gallery instead of using camera. Used in usernames and when linking devices */ -class UsernameQrImageSelectionActivity : AppCompatActivity(), MediaGalleryFragment.Callbacks { +class QrImageSelectionActivity : AppCompatActivity(), MediaGalleryFragment.Callbacks { override fun attachBaseContext(newBase: Context) { delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES @@ -50,7 +50,7 @@ class UsernameQrImageSelectionActivity : AppCompatActivity(), MediaGalleryFragme class Contract : ActivityResultContract() { override fun createIntent(context: Context, input: Unit): Intent { - return Intent(context, UsernameQrImageSelectionActivity::class.java) + return Intent(context, QrImageSelectionActivity::class.java) } override fun parseResult(resultCode: Int, intent: Intent?): Uri? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsFragment.kt index f499396d1e..9236d49158 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameLinkSettingsFragment.kt @@ -95,7 +95,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - galleryLauncher = registerForActivityResult(UsernameQrImageSelectionActivity.Contract()) { uri -> + galleryLauncher = registerForActivityResult(QrImageSelectionActivity.Contract()) { uri -> if (uri != null) { viewModel.scanImage(requireContext(), uri) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScannerActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScannerActivity.kt index ba17bd07d4..5746bd0c20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScannerActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/usernamelinks/main/UsernameQrScannerActivity.kt @@ -42,6 +42,7 @@ import org.signal.core.ui.theme.SignalTheme import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.getParcelableExtraCompat import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.UsernameQrScannerActivity.Contract import org.thoughtcrime.securesms.permissions.PermissionCompat import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.recipients.Recipient @@ -70,7 +71,7 @@ class UsernameQrScannerActivity : AppCompatActivity() { super.onCreate(savedInstanceState) disposables.bindTo(this) - val galleryLauncher = registerForActivityResult(UsernameQrImageSelectionActivity.Contract()) { uri -> + val galleryLauncher = registerForActivityResult(QrImageSelectionActivity.Contract()) { uri -> if (uri != null) { viewModel.onQrImageSelected(this, uri) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt index 672b089829..70a20d21ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt @@ -34,6 +34,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.BiometricDeviceAuthentication import org.thoughtcrime.securesms.BiometricDeviceLockContract import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.QrImageSelectionActivity import org.thoughtcrime.securesms.compose.ComposeFragment import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.util.navigation.safeNavigate @@ -50,6 +51,7 @@ class AddLinkDeviceFragment : ComposeFragment() { private val viewModel: LinkDeviceViewModel by activityViewModels() private lateinit var biometricAuth: BiometricDeviceAuthentication private lateinit var biometricDeviceLockLauncher: ActivityResultLauncher + private lateinit var galleryLauncher: ActivityResultLauncher override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -72,6 +74,12 @@ class AddLinkDeviceFragment : ComposeFragment() { BiometricPrompt(requireActivity(), BiometricAuthenticationListener()), promptInfo ) + + galleryLauncher = registerForActivityResult(QrImageSelectionActivity.Contract()) { uri -> + if (uri != null) { + viewModel.scanImage(requireContext(), uri) + } + } } override fun onPause() { @@ -113,7 +121,8 @@ class AddLinkDeviceFragment : ComposeFragment() { viewModel.onLinkDeviceResult(true) navController.popBackStack() }, - onLinkDeviceFailure = { viewModel.onLinkDeviceResult(false) } + onLinkDeviceFailure = { viewModel.onLinkDeviceResult(false) }, + onGalleryOpened = { galleryLauncher.launch(Unit) } ) } @@ -161,7 +170,8 @@ private fun MainScreen( onQrCodeDismissed: () -> Unit = {}, onQrCodeRetry: () -> Unit = {}, onLinkDeviceSuccess: () -> Unit = {}, - onLinkDeviceFailure: () -> Unit = {} + onLinkDeviceFailure: () -> Unit = {}, + onGalleryOpened: () -> Unit = {} ) { Scaffolds.Settings( title = "", @@ -188,6 +198,7 @@ private fun MainScreen( linkDeviceResult = state.linkDeviceResult, onLinkDeviceSuccess = onLinkDeviceSuccess, onLinkDeviceFailure = onLinkDeviceFailure, + onGalleryOpened = onGalleryOpened, modifier = Modifier.padding(contentPadding) ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt index 386421648e..9708739780 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt @@ -37,6 +37,7 @@ fun LinkDeviceQrScanScreen( linkDeviceResult: LinkDeviceRepository.LinkDeviceResult, onLinkDeviceSuccess: () -> Unit, onLinkDeviceFailure: () -> Unit, + onGalleryOpened: () -> Unit, modifier: Modifier = Modifier ) { val lifecycleOwner = LocalLifecycleOwner.current @@ -106,7 +107,8 @@ fun LinkDeviceQrScanScreen( }, hasPermission = hasPermission, onRequestPermissions = onRequestPermissions, - qrString = stringResource(R.string.AddLinkDeviceFragment__scan_the_qr_code) + qrString = stringResource(R.string.AddLinkDeviceFragment__scan_the_qr_code), + onGalleryOpened = onGalleryOpened ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt index dfbeb6a403..310d848028 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt @@ -4,11 +4,15 @@ import android.content.Context import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DecodeFormat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import org.signal.core.util.toOptional +import org.signal.qr.QrProcessor import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob @@ -168,4 +172,24 @@ class LinkDeviceViewModel : ViewModel() { ) } } + + fun scanImage(context: Context, uri: Uri) { + viewModelScope.launch(Dispatchers.IO) { + val loadBitmap = Glide.with(context) + .asBitmap() + .format(DecodeFormat.PREFER_ARGB_8888) + .load(uri) + .submit() + + val result = QrProcessor().getScannedData(loadBitmap.get()).toOptional() + if (result.isPresent) { + onQrCodeScanned(result.get()) + } else { + _state.value = _state.value.copy( + qrCodeInvalid = true, + showFrontCamera = null + ) + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/qr/QrScanScreens.kt b/app/src/main/java/org/thoughtcrime/securesms/qr/QrScanScreens.kt index 7332ca9b4a..bc907983db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/qr/QrScanScreens.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/qr/QrScanScreens.kt @@ -1,13 +1,16 @@ package org.thoughtcrime.securesms.qr import android.content.Context +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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 import androidx.compose.runtime.Composable @@ -17,16 +20,19 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.PathEffect 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.compose.ui.viewinterop.NoOpUpdate import org.signal.core.ui.Buttons +import org.signal.core.ui.theme.SignalTheme import org.signal.qr.QrScannerView import org.thoughtcrime.securesms.R @@ -40,7 +46,8 @@ object QrScanScreens { update: (QrScannerView) -> Unit = NoOpUpdate, hasPermission: Boolean, onRequestPermissions: () -> Unit = {}, - qrString: String + qrString: String, + onGalleryOpened: () -> Unit = {} ) { val path = remember { Path() } @@ -97,6 +104,18 @@ object QrScanScreens { modifier = Modifier.padding(top = 24.dp).fillMaxWidth() ) } + FloatingActionButton( + shape = CircleShape, + containerColor = SignalTheme.colors.colorSurface1, + modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 24.dp), + onClick = onGalleryOpened + ) { + Image( + painter = painterResource(id = R.drawable.symbol_album_24), + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface) + ) + } } } }