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