Add ability to scan username qr from gallery.
This commit is contained in:
parent
6104ef62df
commit
86afa988a0
10 changed files with 255 additions and 21 deletions
|
@ -1084,6 +1084,11 @@
|
|||
android:theme="@style/Theme.Signal.WallpaperCropper"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrImageSelectionActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity android:name=".reactions.edit.EditReactionsActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||
|
|
|
@ -13,4 +13,6 @@ sealed class QrScanResult {
|
|||
object InvalidData : QrScanResult()
|
||||
|
||||
object NetworkError : QrScanResult()
|
||||
|
||||
object QrNotFound : QrScanResult()
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import android.content.res.Configuration
|
|||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
|
@ -52,9 +54,11 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.MultiplePermissionsState
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.PermissionStatus
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
|
@ -69,6 +73,7 @@ import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeSt
|
|||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
||||
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.providers.BlobProvider
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.UUID
|
||||
|
@ -79,6 +84,18 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
private val viewModel: UsernameLinkSettingsViewModel by viewModels()
|
||||
private val disposables: LifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
private lateinit var galleryLauncher: ActivityResultLauncher<Unit>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
galleryLauncher = registerForActivityResult(UsernameQrImageSelectionActivity.Contract()) { uri ->
|
||||
if (uri != null) {
|
||||
viewModel.scanImage(requireContext(), uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setFragmentResultListener(UsernameLinkShareBottomSheet.REQUEST_KEY) { key, bundle ->
|
||||
|
@ -99,18 +116,28 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
viewModel.onTabSelected(ActiveTab.Scan)
|
||||
}
|
||||
|
||||
val galleryPermissionState: MultiplePermissionsState = rememberMultiplePermissionsState(permissions = PermissionCompat.forImages().toList()) { grants ->
|
||||
if (grants.values.all { it }) {
|
||||
galleryLauncher.launch(Unit)
|
||||
} else {
|
||||
Toast.makeText(requireContext(), R.string.ChatWallpaperPreviewActivity__viewing_your_gallery_requires_the_storage_permission, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
MainScreen(
|
||||
state = state,
|
||||
navController = navController,
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
disposables = disposables.disposables,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
galleryPermissionState = galleryPermissionState,
|
||||
onCodeTabSelected = { viewModel.onTabSelected(ActiveTab.Code) },
|
||||
onScanTabSelected = { viewModel.onTabSelected(ActiveTab.Scan) },
|
||||
onUsernameLinkResetResultHandled = { viewModel.onUsernameLinkResetResultHandled() },
|
||||
onShareBadge = { shareQrBadge(requireActivity(), viewModel.generateQrCodeImage(helpText)) },
|
||||
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
|
||||
onQrResultHandled = { viewModel.onQrResultHandled() },
|
||||
onOpenGalleryClicked = { galleryLauncher.launch(Unit) },
|
||||
onLinkReset = { viewModel.onUsernameLinkReset() },
|
||||
onBackNavigationPressed = { requireActivity().onBackPressed() },
|
||||
linkCopiedEvent = linkCopiedEvent
|
||||
|
@ -127,6 +154,7 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
private fun MainScreen(
|
||||
state: UsernameLinkSettingsState,
|
||||
|
@ -134,12 +162,14 @@ private fun MainScreen(
|
|||
lifecycleOwner: LifecycleOwner = previewLifecycleOwner,
|
||||
disposables: CompositeDisposable = CompositeDisposable(),
|
||||
cameraPermissionState: PermissionState = previewPermissionState(),
|
||||
galleryPermissionState: MultiplePermissionsState = previewMultiplePermissionState(),
|
||||
onCodeTabSelected: () -> Unit = {},
|
||||
onScanTabSelected: () -> Unit = {},
|
||||
onUsernameLinkResetResultHandled: () -> Unit = {},
|
||||
onShareBadge: () -> Unit = {},
|
||||
onQrCodeScanned: (String) -> Unit = {},
|
||||
onQrResultHandled: () -> Unit = {},
|
||||
onOpenGalleryClicked: () -> Unit = {},
|
||||
onLinkReset: () -> Unit = {},
|
||||
onBackNavigationPressed: () -> Unit = {},
|
||||
linkCopiedEvent: UUID? = null
|
||||
|
@ -201,9 +231,11 @@ private fun MainScreen(
|
|||
UsernameQrScanScreen(
|
||||
lifecycleOwner = lifecycleOwner,
|
||||
disposables = disposables,
|
||||
galleryPermissionState = galleryPermissionState,
|
||||
qrScanResult = state.qrScanResult,
|
||||
onQrCodeScanned = onQrCodeScanned,
|
||||
onQrResultHandled = onQrResultHandled,
|
||||
onOpenGalleryClicked = onOpenGalleryClicked,
|
||||
modifier = Modifier.padding(contentPadding)
|
||||
)
|
||||
}
|
||||
|
@ -355,6 +387,16 @@ private fun previewPermissionState(): PermissionState {
|
|||
}
|
||||
}
|
||||
|
||||
private fun previewMultiplePermissionState(): MultiplePermissionsState {
|
||||
return object : MultiplePermissionsState {
|
||||
override val allPermissionsGranted: Boolean = true
|
||||
override val permissions: List<PermissionState> = emptyList()
|
||||
override val revokedPermissions: List<PermissionState> = emptyList()
|
||||
override val shouldShowRationale: Boolean = false
|
||||
override fun launchMultiplePermissionRequest() = Unit
|
||||
}
|
||||
}
|
||||
|
||||
private val previewLifecycleOwner: LifecycleOwner = object : LifecycleOwner {
|
||||
override val lifecycle: Lifecycle
|
||||
get() = throw UnsupportedOperationException("Only for tests")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
|
@ -9,6 +10,7 @@ import android.graphics.PorterDuffColorFilter
|
|||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.Layout
|
||||
import android.text.StaticLayout
|
||||
|
@ -25,13 +27,18 @@ import androidx.compose.ui.unit.Density
|
|||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DecodeFormat
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.toOptional
|
||||
import org.signal.qr.QrProcessor
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeData
|
||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.QrCodeState
|
||||
|
@ -193,6 +200,29 @@ class UsernameLinkSettingsViewModel : ViewModel() {
|
|||
_linkCopiedEvent.value = UUID.randomUUID()
|
||||
}
|
||||
|
||||
fun scanImage(context: Context, uri: Uri) {
|
||||
val loadBitmap = Glide.with(context)
|
||||
.asBitmap()
|
||||
.format(DecodeFormat.PREFER_ARGB_8888)
|
||||
.load(uri)
|
||||
.submit()
|
||||
|
||||
disposable += Single.fromFuture(loadBitmap)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map { QrProcessor().getScannedData(it).toOptional() }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy {
|
||||
if (it.isPresent) {
|
||||
onQrCodeScanned(it.get())
|
||||
} else {
|
||||
_state.value = _state.value.copy(
|
||||
qrScanResult = QrScanResult.QrNotFound,
|
||||
indeterminateProgress = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateQrCodeData(url: Optional<String>): Single<Optional<QrCodeData>> {
|
||||
return Single.fromCallable {
|
||||
url.map { QrCodeData.forData(it, 64) }
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.v2.gallery.MediaGalleryFragment
|
||||
|
||||
/**
|
||||
* Select username qr code from gallery instead of using camera.
|
||||
*/
|
||||
class UsernameQrImageSelectionActivity : AppCompatActivity(), MediaGalleryFragment.Callbacks {
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
|
||||
super.attachBaseContext(newBase)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
|
||||
setContentView(R.layout.username_qr_image_selection_activity)
|
||||
}
|
||||
|
||||
@SuppressLint("LogTagInlined")
|
||||
override fun onMediaSelected(media: Media) {
|
||||
setResult(RESULT_OK, Intent().setData(media.uri))
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onToolbarNavigationClicked() {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun isCameraEnabled() = false
|
||||
override fun isMultiselectEnabled() = false
|
||||
|
||||
class Contract : ActivityResultContract<Unit, Uri?>() {
|
||||
override fun createIntent(context: Context, input: Unit): Intent {
|
||||
return Intent(context, UsernameQrImageSelectionActivity::class.java)
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||
return if (resultCode == RESULT_OK) {
|
||||
intent?.data
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.usernamelinks.main
|
||||
|
||||
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.Row
|
||||
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.FloatingActionButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -15,19 +19,26 @@ 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.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.MultiplePermissionsState
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.signal.core.ui.Dialogs
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.qr.QrScannerView
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXModelBlocklist
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
|
@ -36,36 +47,51 @@ import java.util.concurrent.TimeUnit
|
|||
/**
|
||||
* A screen that allows you to scan a QR code to start a chat.
|
||||
*/
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun UsernameQrScanScreen(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
disposables: CompositeDisposable,
|
||||
galleryPermissionState: MultiplePermissionsState,
|
||||
qrScanResult: QrScanResult?,
|
||||
onQrCodeScanned: (String) -> Unit,
|
||||
onQrResultHandled: () -> Unit,
|
||||
onOpenGalleryClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val path = remember { Path() }
|
||||
|
||||
when (qrScanResult) {
|
||||
QrScanResult.InvalidData -> {
|
||||
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_invalid), onDismiss = onQrResultHandled)
|
||||
QrScanResultDialog(message = stringResource(R.string.UsernameLinkSettings_qr_result_invalid), onDismiss = onQrResultHandled)
|
||||
}
|
||||
|
||||
QrScanResult.NetworkError -> {
|
||||
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_network_error), onDismiss = onQrResultHandled)
|
||||
QrScanResultDialog(message = stringResource(R.string.UsernameLinkSettings_qr_result_network_error), onDismiss = onQrResultHandled)
|
||||
}
|
||||
|
||||
QrScanResult.QrNotFound -> {
|
||||
QrScanResultDialog(
|
||||
title = stringResource(R.string.UsernameLinkSettings_qr_code_not_found),
|
||||
message = stringResource(R.string.UsernameLinkSettings_try_scanning_another_image_containing_a_signal_qr_code),
|
||||
onDismiss = onQrResultHandled
|
||||
)
|
||||
}
|
||||
|
||||
is QrScanResult.NotFound -> {
|
||||
if (qrScanResult.username != null) {
|
||||
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_not_found, qrScanResult.username), onDismiss = onQrResultHandled)
|
||||
QrScanResultDialog(message = stringResource(R.string.UsernameLinkSettings_qr_result_not_found, qrScanResult.username), onDismiss = onQrResultHandled)
|
||||
} else {
|
||||
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_not_found_no_username), onDismiss = onQrResultHandled)
|
||||
QrScanResultDialog(message = stringResource(R.string.UsernameLinkSettings_qr_result_not_found_no_username), onDismiss = onQrResultHandled)
|
||||
}
|
||||
}
|
||||
|
||||
is QrScanResult.Success -> {
|
||||
CommunicationActions.startConversation(LocalContext.current, qrScanResult.recipient, null)
|
||||
val taskStack = TaskStackBuilder
|
||||
.create(LocalContext.current)
|
||||
.addNextIntent(MainActivity.clearTop(LocalContext.current))
|
||||
|
||||
CommunicationActions.startConversation(LocalContext.current, qrScanResult.recipient, null, taskStack)
|
||||
onQrResultHandled()
|
||||
}
|
||||
|
||||
|
@ -77,25 +103,52 @@ fun UsernameQrScanScreen(
|
|||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
val view = QrScannerView(context)
|
||||
disposables += view.qrData.throttleFirst(3000, TimeUnit.MILLISECONDS).subscribe { data ->
|
||||
onQrCodeScanned(data)
|
||||
}
|
||||
view
|
||||
},
|
||||
update = { view ->
|
||||
view.start(lifecycleOwner = lifecycleOwner, forceLegacy = CameraXModelBlocklist.isBlocklisted())
|
||||
},
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f, true)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawQrCrosshair(path)
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
val view = QrScannerView(context)
|
||||
disposables += view.qrData.throttleFirst(3000, TimeUnit.MILLISECONDS).subscribe { data ->
|
||||
onQrCodeScanned(data)
|
||||
}
|
||||
view
|
||||
},
|
||||
update = { view ->
|
||||
view.start(lifecycleOwner = lifecycleOwner, forceLegacy = CameraXModelBlocklist.isBlocklisted())
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawQrCrosshair(path)
|
||||
}
|
||||
)
|
||||
|
||||
FloatingActionButton(
|
||||
shape = CircleShape,
|
||||
containerColor = SignalTheme.colors.colorSurface1,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 24.dp),
|
||||
onClick = {
|
||||
if (galleryPermissionState.allPermissionsGranted) {
|
||||
onOpenGalleryClicked()
|
||||
} else {
|
||||
galleryPermissionState.launchMultiplePermissionRequest()
|
||||
}
|
||||
}
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.symbol_album_24),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
@ -114,8 +167,9 @@ fun UsernameQrScanScreen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun QrScanResultDialog(message: String, onDismiss: () -> Unit) {
|
||||
private fun QrScanResultDialog(title: String? = null, message: String, onDismiss: () -> Unit) {
|
||||
Dialogs.SimpleMessageDialog(
|
||||
title = title,
|
||||
message = message,
|
||||
dismiss = stringResource(id = android.R.string.ok),
|
||||
onDismiss = onDismiss
|
||||
|
|
12
app/src/main/res/drawable/symbol_album_24.xml
Normal file
12
app/src/main/res/drawable/symbol_album_24.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9 11.88c0-0.83 0.67-1.5 1.5-1.5s1.5 0.67 1.5 1.5c0 0.82-0.67 1.5-1.5 1.5S9 12.7 9 11.88Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18.87 7.13c0-0.52 0-0.97-0.03-1.34-0.04-0.47-0.12-0.91-0.33-1.32-0.33-0.64-0.84-1.15-1.48-1.48-0.4-0.2-0.85-0.29-1.32-0.33-0.45-0.04-1-0.04-1.67-0.04H5.96c-0.67 0-1.22 0-1.67 0.04C3.82 2.7 3.38 2.78 2.97 3 2.33 3.32 1.82 3.83 1.49 4.47 1.3 4.87 1.2 5.32 1.16 5.79c-0.04 0.45-0.04 1-0.03 1.67v4.08c0 0.67 0 1.22 0.03 1.67 0.04 0.47 0.12 0.91 0.33 1.32 0.33 0.64 0.84 1.15 1.48 1.48 0.4 0.2 0.85 0.29 1.32 0.33 0.24 0.02 0.52 0.03 0.83 0.03v0.17c0 0.67 0 1.22 0.04 1.67 0.04 0.47 0.12 0.91 0.33 1.32 0.33 0.64 0.84 1.15 1.48 1.48 0.4 0.2 0.85 0.29 1.32 0.33 0.45 0.04 1 0.04 1.67 0.04h8.08c0.67 0 1.22 0 1.67-0.04 0.47-0.04 0.91-0.12 1.32-0.33 0.64-0.33 1.15-0.84 1.48-1.48 0.2-0.4 0.29-0.85 0.33-1.32 0.04-0.45 0.04-1 0.04-1.67v-4.58c0-0.67 0-1.22-0.04-1.67-0.04-0.47-0.12-0.91-0.33-1.32-0.33-0.64-0.84-1.15-1.48-1.48-0.4-0.2-0.85-0.29-1.32-0.33-0.24-0.02-0.52-0.03-0.84-0.03Zm-1.75 0H9.96c-0.67 0-1.22 0-1.67 0.03C7.82 7.2 7.38 7.28 6.97 7.5 6.33 7.82 5.82 8.33 5.49 8.97c-0.2 0.4-0.29 0.85-0.33 1.32-0.04 0.45-0.04 1-0.04 1.67v2.66c-0.27 0-0.5-0.01-0.7-0.03-0.35-0.03-0.53-0.08-0.66-0.14-0.3-0.16-0.55-0.4-0.7-0.71-0.07-0.13-0.12-0.3-0.15-0.67-0.03-0.37-0.04-0.86-0.04-1.57v-4c0-0.71 0-1.2 0.04-1.57 0.03-0.36 0.08-0.54 0.14-0.67 0.16-0.3 0.4-0.55 0.71-0.7 0.13-0.07 0.3-0.12 0.67-0.15C4.8 4.38 5.29 4.37 6 4.37h8c0.71 0 1.2 0 1.57 0.04 0.36 0.03 0.54 0.08 0.67 0.14 0.3 0.16 0.55 0.4 0.7 0.71 0.07 0.13 0.12 0.3 0.15 0.67 0.03 0.3 0.03 0.68 0.03 1.2Zm3.83 2.63c0.06 0.13 0.11 0.3 0.14 0.67 0.03 0.37 0.04 0.86 0.04 1.57v3.39l-1.95-1.95c-0.93-0.93-2.43-0.93-3.36 0L13 16.26l-0.82-0.82c-0.93-0.93-2.43-0.93-3.36 0l-1.94 1.94V16.5 12c0-0.71 0-1.2 0.03-1.57 0.03-0.36 0.08-0.54 0.14-0.67 0.16-0.3 0.4-0.55 0.71-0.7 0.13-0.07 0.3-0.12 0.67-0.15C8.8 8.88 9.29 8.87 10 8.87h8c0.71 0 1.2 0 1.57 0.04 0.36 0.03 0.54 0.08 0.67 0.14 0.3 0.16 0.55 0.4 0.7 0.71Zm-3 4.92l3.16 3.17-0.02 0.22c-0.03 0.36-0.08 0.54-0.14 0.67-0.16 0.3-0.4 0.55-0.71 0.7-0.13 0.07-0.3 0.12-0.67 0.15-0.37 0.03-0.86 0.04-1.57 0.04h-8c-0.71 0-1.2 0-1.57-0.04-0.36-0.03-0.54-0.08-0.67-0.14-0.1-0.05-0.2-0.12-0.28-0.19l2.58-2.58c0.24-0.24 0.64-0.24 0.88 0l1.44 1.44c0.34 0.34 0.9 0.34 1.24 0l3.44-3.44c0.24-0.24 0.64-0.24 0.88 0Z"/>
|
||||
</vector>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:name="org.thoughtcrime.securesms.mediasend.v2.gallery.MediaGalleryFragment"
|
||||
tools:viewBindingIgnore="true" />
|
|
@ -6526,6 +6526,10 @@
|
|||
<string name="UsernameLinkSettings_reset_link_result_success">Your QR code and link have been reset and a new QR code and link has been created.</string>
|
||||
<!-- Shown on the generated username qr code image to explain how to use it. -->
|
||||
<string name="UsernameLinkSettings_scan_this_qr_code">Scan this QR code with your phone to chat with me on Signal.</string>
|
||||
<!-- Dialog title shown when scanning an image from the gallery for a username QR code and there is no qr code in the image. -->
|
||||
<string name="UsernameLinkSettings_qr_code_not_found">QR code not found</string>
|
||||
<!-- Dialog message shown when scanning an image from the gallery for a username QR code and there is no qr code in the image. -->
|
||||
<string name="UsernameLinkSettings_try_scanning_another_image_containing_a_signal_qr_code">Try scanning another image containing a Signal QR code.</string>
|
||||
|
||||
<!-- Explanatory text at the top of a bottom sheet describing how username links work -->
|
||||
<string name="UsernameLinkShareBottomSheet_title">Anyone with this link can view your username and start a chat with you. Only share it with people you trust.</string>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.signal.qr
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.zxing.BinaryBitmap
|
||||
import com.google.zxing.ChecksumException
|
||||
|
@ -8,10 +9,12 @@ import com.google.zxing.FormatException
|
|||
import com.google.zxing.LuminanceSource
|
||||
import com.google.zxing.NotFoundException
|
||||
import com.google.zxing.PlanarYUVLuminanceSource
|
||||
import com.google.zxing.RGBLuminanceSource
|
||||
import com.google.zxing.Result
|
||||
import com.google.zxing.common.HybridBinarizer
|
||||
import com.google.zxing.qrcode.QRCodeReader
|
||||
import org.signal.core.util.logging.Log
|
||||
import java.nio.IntBuffer
|
||||
|
||||
/**
|
||||
* Wraps [QRCodeReader] for use from API19 or API21+.
|
||||
|
@ -35,6 +38,16 @@ class QrProcessor {
|
|||
return getScannedData(PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false))
|
||||
}
|
||||
|
||||
fun getScannedData(bitmap: Bitmap?): String? {
|
||||
if (bitmap == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val buffer = IntBuffer.allocate((bitmap.byteCount / 4) + 1)
|
||||
bitmap.copyPixelsToBuffer(buffer)
|
||||
return getScannedData(RGBLuminanceSource(bitmap.width, bitmap.height, buffer.array()))
|
||||
}
|
||||
|
||||
private fun getScannedData(source: LuminanceSource): String? {
|
||||
try {
|
||||
if (source.width != previousWidth || source.height != previousHeight) {
|
||||
|
|
Loading…
Add table
Reference in a new issue