mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-28 01:20:46 +00:00
Handle QR errors
This commit is contained in:
parent
1445d56d08
commit
c32a5b6bba
@ -31,6 +31,8 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
@ -78,6 +80,7 @@ class NewMessageFragment : Fragment() {
|
|||||||
val uiState by viewModel.state.collectAsState(State())
|
val uiState by viewModel.state.collectAsState(State())
|
||||||
NewMessage(
|
NewMessage(
|
||||||
uiState,
|
uiState,
|
||||||
|
viewModel.qrErrors,
|
||||||
viewModel,
|
viewModel,
|
||||||
onClose = { delegate.onDialogClosePressed() },
|
onClose = { delegate.onDialogClosePressed() },
|
||||||
onBack = { delegate.onDialogBackPressed() },
|
onBack = { delegate.onDialogBackPressed() },
|
||||||
@ -104,7 +107,7 @@ private fun PreviewNewMessage(
|
|||||||
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
|
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
|
||||||
) {
|
) {
|
||||||
PreviewTheme(themeResId) {
|
PreviewTheme(themeResId) {
|
||||||
NewMessage(State(), object: Callbacks {})
|
NewMessage(State())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +117,8 @@ private val TITLES = listOf(R.string.enter_account_id, R.string.qrScan)
|
|||||||
@Composable
|
@Composable
|
||||||
private fun NewMessage(
|
private fun NewMessage(
|
||||||
state: State,
|
state: State,
|
||||||
callbacks: Callbacks,
|
errors: Flow<String> = emptyFlow(),
|
||||||
|
callbacks: Callbacks = object: Callbacks {},
|
||||||
onClose: () -> Unit = {},
|
onClose: () -> Unit = {},
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onHelp: () -> Unit = {},
|
onHelp: () -> Unit = {},
|
||||||
@ -127,7 +131,7 @@ private fun NewMessage(
|
|||||||
HorizontalPager(pagerState) {
|
HorizontalPager(pagerState) {
|
||||||
when (TITLES[it]) {
|
when (TITLES[it]) {
|
||||||
R.string.enter_account_id -> EnterAccountId(state, callbacks, onHelp)
|
R.string.enter_account_id -> EnterAccountId(state, callbacks, onHelp)
|
||||||
R.string.qrScan -> MaybeScanQrCode(onScan = callbacks::onScan)
|
R.string.qrScan -> MaybeScanQrCode(errors, onScan = callbacks::onScan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
@ -23,26 +24,33 @@ class NewMessageViewModel @Inject constructor(
|
|||||||
private val application: Application
|
private val application: Application
|
||||||
): AndroidViewModel(application), Callbacks {
|
): AndroidViewModel(application), Callbacks {
|
||||||
|
|
||||||
private val _state = MutableStateFlow(
|
|
||||||
State()
|
private val _state = MutableStateFlow(State())
|
||||||
)
|
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
private val _event = Channel<Event>()
|
private val _event = Channel<Event>()
|
||||||
val event = _event.receiveAsFlow()
|
val event = _event.receiveAsFlow()
|
||||||
|
|
||||||
|
private val _qrErrors = Channel<String>()
|
||||||
|
val qrErrors: Flow<String> = _qrErrors.receiveAsFlow()
|
||||||
|
|
||||||
override fun onChange(value: String) {
|
override fun onChange(value: String) {
|
||||||
_state.update { it.copy(
|
_state.update { it.copy(
|
||||||
newMessageIdOrOns = value,
|
newMessageIdOrOns = value,
|
||||||
error = null
|
error = null
|
||||||
) }
|
) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onContinue() {
|
override fun onContinue() {
|
||||||
createPrivateChatIfPossible(state.value.newMessageIdOrOns)
|
createPrivateChatIfPossible(state.value.newMessageIdOrOns)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScan(value: String) {
|
override fun onScan(value: String) {
|
||||||
createPrivateChatIfPossible(value)
|
if (PublicKeyValidation.isValid(value, isPrefixRequired = false) && PublicKeyValidation.hasValidPrefix(value)) {
|
||||||
|
onPublicKey(value)
|
||||||
|
} else {
|
||||||
|
_qrErrors.trySend(application.getString(R.string.this_qr_code_does_not_contain_an_account_id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPrivateChatIfPossible(onsNameOrPublicKey: String) {
|
private fun createPrivateChatIfPossible(onsNameOrPublicKey: String) {
|
||||||
|
@ -137,8 +137,7 @@ fun RecoveryPassword(state: LinkDeviceState, onChange: (String) -> Unit = {}, on
|
|||||||
text = state.recoveryPhrase,
|
text = state.recoveryPhrase,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.contentDescription(R.string.AccessibilityId_recovery_phrase_input)
|
.contentDescription(R.string.AccessibilityId_recovery_phrase_input),
|
||||||
.padding(horizontal = 64.dp),
|
|
||||||
placeholder = stringResource(R.string.recoveryPasswordEnter),
|
placeholder = stringResource(R.string.recoveryPasswordEnter),
|
||||||
onChange = onChange,
|
onChange = onChange,
|
||||||
onContinue = onContinue,
|
onContinue = onContinue,
|
||||||
@ -165,24 +164,3 @@ fun RecoveryPassword(state: LinkDeviceState, onChange: (String) -> Unit = {}, on
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Analyzer(
|
|
||||||
private val scanner: BarcodeScanner,
|
|
||||||
private val onBarcodeScanned: (String) -> Unit
|
|
||||||
): Analyzer {
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
|
||||||
override fun analyze(image: ImageProxy) {
|
|
||||||
InputImage.fromMediaImage(
|
|
||||||
image.image!!,
|
|
||||||
image.imageInfo.rotationDegrees
|
|
||||||
).let(scanner::process).apply {
|
|
||||||
addOnSuccessListener { barcodes ->
|
|
||||||
barcodes.filter { it.valueType == Barcode.TYPE_TEXT }.forEach {
|
|
||||||
it.rawValue?.let(onBarcodeScanned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addOnCompleteListener {
|
|
||||||
image.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,6 +4,8 @@ import android.app.Application
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -38,9 +40,12 @@ class LinkDeviceViewModel @Inject constructor(
|
|||||||
|
|
||||||
private val event = Channel<LinkDeviceEvent>()
|
private val event = Channel<LinkDeviceEvent>()
|
||||||
val eventFlow = event.receiveAsFlow().take(1)
|
val eventFlow = event.receiveAsFlow().take(1)
|
||||||
|
|
||||||
private val qrErrors = Channel<Throwable>()
|
private val qrErrors = Channel<Throwable>()
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||||
val qrErrorsFlow = qrErrors.receiveAsFlow()
|
val qrErrorsFlow = qrErrors.receiveAsFlow()
|
||||||
.debounce(QR_ERROR_TIME)
|
// .debounce(QR_ERROR_TIME)
|
||||||
.takeWhile { event.isEmpty }
|
.takeWhile { event.isEmpty }
|
||||||
.mapNotNull { application.getString(R.string.qrNotRecoveryPassword) }
|
.mapNotNull { application.getString(R.string.qrNotRecoveryPassword) }
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.preferences
|
package org.thoughtcrime.securesms.preferences
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -15,6 +14,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -34,33 +36,36 @@ private val TITLES = listOf(R.string.view, R.string.scan)
|
|||||||
|
|
||||||
class QRCodeActivity : PassphraseRequiredActionBarActivity() {
|
class QRCodeActivity : PassphraseRequiredActionBarActivity() {
|
||||||
|
|
||||||
|
private val errors = Channel<String>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||||
super.onCreate(savedInstanceState, isReady)
|
super.onCreate(savedInstanceState, isReady)
|
||||||
supportActionBar!!.title = resources.getString(R.string.activity_qr_code_title)
|
supportActionBar!!.title = resources.getString(R.string.activity_qr_code_title)
|
||||||
|
|
||||||
setComposeContent {
|
setComposeContent {
|
||||||
Tabs(TextSecurePreferences.getLocalNumber(this)!!, onScan = ::handleQRCodeScanned)
|
Tabs(TextSecurePreferences.getLocalNumber(this)!!, errors.receiveAsFlow(), onScan = ::onScan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleQRCodeScanned(string: String) {
|
fun onScan(string: String) {
|
||||||
if (!PublicKeyValidation.isValid(string)) {
|
if (!PublicKeyValidation.isValid(string)) {
|
||||||
return Toast.makeText(this, R.string.invalid_session_id, Toast.LENGTH_SHORT).show()
|
errors.trySend(getString(R.string.this_qr_code_does_not_contain_an_account_id))
|
||||||
|
} else if (!isFinishing) {
|
||||||
|
val recipient = Recipient.from(this, Address.fromSerialized(string), false)
|
||||||
|
start<ConversationActivityV2> {
|
||||||
|
putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
|
setDataAndType(intent.data, intent.type)
|
||||||
|
val existingThread = threadDatabase().getThreadIdIfExistsFor(recipient)
|
||||||
|
putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
}
|
}
|
||||||
val recipient = Recipient.from(this, Address.fromSerialized(string), false)
|
|
||||||
start<ConversationActivityV2> {
|
|
||||||
putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
|
||||||
setDataAndType(intent.data, intent.type)
|
|
||||||
val existingThread = threadDatabase().getThreadIdIfExistsFor(recipient)
|
|
||||||
putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
|
||||||
}
|
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Tabs(sessionId: String, onScan: (String) -> Unit) {
|
private fun Tabs(sessionId: String, errors: Flow<String>, onScan: (String) -> Unit) {
|
||||||
val pagerState = rememberPagerState { TITLES.size }
|
val pagerState = rememberPagerState { TITLES.size }
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@ -71,7 +76,7 @@ fun Tabs(sessionId: String, onScan: (String) -> Unit) {
|
|||||||
) { page ->
|
) { page ->
|
||||||
when (TITLES[page]) {
|
when (TITLES[page]) {
|
||||||
R.string.view -> QrPage(sessionId)
|
R.string.view -> QrPage(sessionId)
|
||||||
R.string.scan -> MaybeScanQrCode(onScan = onScan)
|
R.string.scan -> MaybeScanQrCode(errors, onScan = onScan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,9 +84,11 @@ fun Tabs(sessionId: String, onScan: (String) -> Unit) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QrPage(string: String) {
|
fun QrPage(string: String) {
|
||||||
Column(modifier = Modifier
|
Column(
|
||||||
.padding(horizontal = 32.dp)
|
modifier = Modifier
|
||||||
.fillMaxSize()) {
|
.padding(horizontal = 32.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
QrImage(
|
QrImage(
|
||||||
string = string,
|
string = string,
|
||||||
contentDescription = "Your session id",
|
contentDescription = "Your session id",
|
||||||
|
@ -7,6 +7,7 @@ import android.net.Uri
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@ -47,12 +48,18 @@ import com.google.mlkit.vision.barcode.BarcodeScanner
|
|||||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||||
import com.google.mlkit.vision.barcode.common.Barcode
|
import com.google.mlkit.vision.barcode.common.Barcode
|
||||||
|
import com.google.mlkit.vision.common.InputImage
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.onboarding.Analyzer
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
typealias CameraPreview = androidx.camera.core.Preview
|
typealias CameraPreview = androidx.camera.core.Preview
|
||||||
|
|
||||||
@ -61,7 +68,7 @@ private const val TAG = "NewMessageFragment"
|
|||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MaybeScanQrCode(
|
fun MaybeScanQrCode(
|
||||||
errors: Flow<String> = emptyFlow(),
|
errors: Flow<String>,
|
||||||
onClickSettings: () -> Unit = LocalContext.current.run { {
|
onClickSettings: () -> Unit = LocalContext.current.run { {
|
||||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||||
data = Uri.fromParts("package", packageName, null)
|
data = Uri.fromParts("package", packageName, null)
|
||||||
@ -75,7 +82,10 @@ fun MaybeScanQrCode(
|
|||||||
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
|
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
|
||||||
|
|
||||||
if (cameraPermissionState.status.isGranted) {
|
if (cameraPermissionState.status.isGranted) {
|
||||||
ScanQrCode(errors, onScan)
|
ScanQrCode(errors) {
|
||||||
|
Log.d("QR", "scan: $it")
|
||||||
|
onScan(it)
|
||||||
|
}
|
||||||
} else if (cameraPermissionState.status.shouldShowRationale) {
|
} else if (cameraPermissionState.status.shouldShowRationale) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -105,6 +115,7 @@ fun MaybeScanQrCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
||||||
val localContext = LocalContext.current
|
val localContext = LocalContext.current
|
||||||
@ -140,9 +151,11 @@ fun ScanQrCode(errors: Flow<String>, onScan: (String) -> Unit) {
|
|||||||
val scaffoldState = rememberScaffoldState()
|
val scaffoldState = rememberScaffoldState()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
errors.collect { error ->
|
errors.filter { scaffoldState.snackbarHostState.currentSnackbarData == null }
|
||||||
scaffoldState.snackbarHostState.showSnackbar(message = error)
|
.buffer(0, BufferOverflow.DROP_OLDEST)
|
||||||
}
|
.collect { error ->
|
||||||
|
scaffoldState.snackbarHostState.showSnackbar(message = error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -185,4 +198,26 @@ private fun buildAnalysisUseCase(
|
|||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
.build().apply {
|
.build().apply {
|
||||||
setAnalyzer(Executors.newSingleThreadExecutor(), Analyzer(scanner, onBarcodeScanned))
|
setAnalyzer(Executors.newSingleThreadExecutor(), Analyzer(scanner, onBarcodeScanned))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Analyzer(
|
||||||
|
private val scanner: BarcodeScanner,
|
||||||
|
private val onBarcodeScanned: (String) -> Unit
|
||||||
|
): ImageAnalysis.Analyzer {
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
override fun analyze(image: ImageProxy) {
|
||||||
|
InputImage.fromMediaImage(
|
||||||
|
image.image!!,
|
||||||
|
image.imageInfo.rotationDegrees
|
||||||
|
).let(scanner::process).apply {
|
||||||
|
addOnSuccessListener { barcodes ->
|
||||||
|
barcodes.forEach {
|
||||||
|
it.rawValue?.let(onBarcodeScanned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOnCompleteListener {
|
||||||
|
image.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -45,9 +45,12 @@ fun QrImage(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
LaunchedEffect(string) {
|
LaunchedEffect(string) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
bitmap = QRCodeUtilities.encode(string, 400).also {
|
val c = 150
|
||||||
for (y in 150 until 250) {
|
val w = c * 2
|
||||||
for (x in 150 until 250) {
|
bitmap = QRCodeUtilities.encode(string, w).also {
|
||||||
|
val hw = 30
|
||||||
|
for (y in c - hw until c + hw) {
|
||||||
|
for (x in c - hw until c + hw) {
|
||||||
it.setPixel(x, y, 0x00000000)
|
it.setPixel(x, y, 0x00000000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1132,4 +1132,5 @@
|
|||||||
<string name="onsErrorNotRecognized">We couldn\'t recognize this ONS. Please check it and try again.</string>
|
<string name="onsErrorNotRecognized">We couldn\'t recognize this ONS. Please check it and try again.</string>
|
||||||
<string name="this_is_your_account_id_other_users_can_scan_it_to_start_a_conversation_with_you">This is your Account ID. Other users can scan it to start a conversation with you.</string>
|
<string name="this_is_your_account_id_other_users_can_scan_it_to_start_a_conversation_with_you">This is your Account ID. Other users can scan it to start a conversation with you.</string>
|
||||||
<string name="accountIdShare">Hey, I\'ve been using Session to chat with complete privacy and security. Come join me! My Account ID is \n\n%1$s\n\nDownload it at https://getsession.org/</string>
|
<string name="accountIdShare">Hey, I\'ve been using Session to chat with complete privacy and security. Come join me! My Account ID is \n\n%1$s\n\nDownload it at https://getsession.org/</string>
|
||||||
|
<string name="this_qr_code_does_not_contain_an_account_id">This QR code does not contain an Account ID.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user