Add ability to scan linked device qr code from gallery.

This commit is contained in:
Michelle Tang
2024-06-06 10:24:42 -07:00
committed by Alex Hart
parent 644b93e5a3
commit d9c42a4135
8 changed files with 67 additions and 10 deletions

View File

@@ -1105,7 +1105,7 @@
android:theme="@style/Theme.Signal.WallpaperCropper"
android:exported="false"/>
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrImageSelectionActivity"
<activity android:name=".components.settings.app.usernamelinks.main.QrImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar"
android:exported="false"/>

View File

@@ -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<Unit, Uri?>() {
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? {

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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<String>
private lateinit var galleryLauncher: ActivityResultLauncher<Unit>
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)
)
}

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}
}
}

View File

@@ -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)
)
}
}
}
}