mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 13:38:26 +00:00
Add QR errors
This commit is contained in:
parent
8fc83213ba
commit
ca0206409c
@ -12,7 +12,6 @@ import androidx.camera.core.ExperimentalGetImage
|
|||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.camera.core.ImageAnalysis
|
||||||
import androidx.camera.core.ImageAnalysis.Analyzer
|
import androidx.camera.core.ImageAnalysis.Analyzer
|
||||||
import androidx.camera.core.ImageProxy
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.camera.core.Preview
|
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
@ -36,9 +35,14 @@ import androidx.compose.material.LocalContentAlpha
|
|||||||
import androidx.compose.material.LocalContentColor
|
import androidx.compose.material.LocalContentColor
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.OutlinedTextField
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Snackbar
|
||||||
|
import androidx.compose.material.SnackbarHost
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextFieldDefaults
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.material.rememberScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -53,6 +57,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@ -66,7 +71,7 @@ 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 com.google.mlkit.vision.common.InputImage
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
@ -79,7 +84,6 @@ import org.thoughtcrime.securesms.ui.colorDestructive
|
|||||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
|
|
||||||
private const val TAG = "LinkDeviceActivity"
|
private const val TAG = "LinkDeviceActivity"
|
||||||
|
|
||||||
@ -94,7 +98,7 @@ class LinkDeviceActivity : BaseActionBarActivity() {
|
|||||||
|
|
||||||
val viewModel: LinkDeviceViewModel by viewModels()
|
val viewModel: LinkDeviceViewModel by viewModels()
|
||||||
|
|
||||||
val preview = Preview.Builder().build()
|
val preview = androidx.camera.core.Preview.Builder().build()
|
||||||
val selector = CameraSelector.Builder()
|
val selector = CameraSelector.Builder()
|
||||||
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
||||||
.build()
|
.build()
|
||||||
@ -147,15 +151,15 @@ class LinkDeviceActivity : BaseActionBarActivity() {
|
|||||||
val scanner = BarcodeScanning.getClient(options)
|
val scanner = BarcodeScanning.getClient(options)
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
when (title) {
|
cameraProvider.get().unbindAll()
|
||||||
R.string.activity_link_device_scan_qr_code -> {
|
if (title == R.string.activity_link_device_scan_qr_code) {
|
||||||
LocalSoftwareKeyboardController.current?.hide()
|
LocalSoftwareKeyboardController.current?.hide()
|
||||||
cameraProvider.get().apply {
|
cameraProvider.get().bindToLifecycle(
|
||||||
unbindAll()
|
LocalLifecycleOwner.current,
|
||||||
bindToLifecycle(LocalLifecycleOwner.current, selector, preview, buildAnalysisUseCase(scanner, viewModel::scan))
|
selector,
|
||||||
}
|
preview,
|
||||||
}
|
buildAnalysisUseCase(scanner, viewModel::scan)
|
||||||
else -> cameraProvider.get().unbindAll()
|
)
|
||||||
}
|
}
|
||||||
}.onFailure { Log.e(TAG, "error binding camera", it) }
|
}.onFailure { Log.e(TAG, "error binding camera", it) }
|
||||||
when (title) {
|
when (title) {
|
||||||
@ -166,6 +170,7 @@ class LinkDeviceActivity : BaseActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MaybeScanQrCode() {
|
fun MaybeScanQrCode() {
|
||||||
@ -173,7 +178,7 @@ class LinkDeviceActivity : BaseActionBarActivity() {
|
|||||||
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
|
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
|
||||||
|
|
||||||
if (cameraPermissionState.status.isGranted) {
|
if (cameraPermissionState.status.isGranted) {
|
||||||
ScanQrCode(preview)
|
ScanQrCode(preview, viewModel.qrErrorsFlow)
|
||||||
} else if (cameraPermissionState.status.shouldShowRationale) {
|
} else if (cameraPermissionState.status.shouldShowRationale) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -204,27 +209,59 @@ class LinkDeviceActivity : BaseActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScanQrCode(preview: Preview) {
|
fun ScanQrCode(preview: androidx.camera.core.Preview, errors: Flow<String>) {
|
||||||
Box {
|
val scaffoldState = rememberScaffoldState()
|
||||||
AndroidView(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
factory = { PreviewView(it).apply { preview.setSurfaceProvider(surfaceProvider) } }
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
LaunchedEffect(Unit) {
|
||||||
Modifier
|
errors.collect { error ->
|
||||||
.aspectRatio(1f)
|
lifecycleScope.launch {
|
||||||
.padding(20.dp)
|
scaffoldState.snackbarHostState.showSnackbar(
|
||||||
.clip(shape = RoundedCornerShape(20.dp))
|
message = error,
|
||||||
.background(Color(0x33ffffff))
|
actionLabel = "Dismiss"
|
||||||
.align(Alignment.Center)
|
)
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
scaffoldState = scaffoldState,
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(
|
||||||
|
hostState = scaffoldState.snackbarHostState,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) { data ->
|
||||||
|
Snackbar(
|
||||||
|
snackbarData = data,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Box(modifier = Modifier.padding(padding)) {
|
||||||
|
AndroidView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
factory = { PreviewView(it).apply { preview.setSurfaceProvider(surfaceProvider) } }
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(20.dp)
|
||||||
|
.clip(shape = RoundedCornerShape(20.dp))
|
||||||
|
.background(Color(0x33ffffff))
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewRecoveryPassword() = RecoveryPassword(state = LinkDeviceState())
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecoveryPassword(state: LinkDeviceState, onChange: (String) -> Unit = {}, onContinue: () -> Unit = {}) {
|
fun RecoveryPassword(state: LinkDeviceState, onChange: (String) -> Unit = {}, onContinue: () -> Unit = {}) {
|
||||||
Column(
|
Column(
|
||||||
@ -271,9 +308,9 @@ fun RecoveryPassword(state: LinkDeviceState, onChange: (String) -> Unit = {}, on
|
|||||||
OutlineButton(
|
OutlineButton(
|
||||||
text = stringResource(id = R.string.continue_2),
|
text = stringResource(id = R.string.continue_2),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
.padding(horizontal = 64.dp, vertical = 20.dp)
|
.padding(horizontal = 64.dp, vertical = 20.dp)
|
||||||
.width(200.dp)
|
.width(200.dp)
|
||||||
) { onContinue() }
|
) { onContinue() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,15 @@ import kotlinx.coroutines.flow.collect
|
|||||||
import kotlinx.coroutines.flow.consumeAsFlow
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.take
|
import kotlinx.coroutines.flow.take
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.session.libsignal.crypto.MnemonicCodec
|
import org.session.libsignal.crypto.MnemonicCodec
|
||||||
|
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort
|
||||||
|
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -35,7 +38,9 @@ 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>()
|
||||||
private val qrErrorsFlow = qrErrors.receiveAsFlow().debounce(QR_ERROR_TIME).takeWhile { event.isEmpty }
|
val qrErrorsFlow = qrErrors.receiveAsFlow().debounce(QR_ERROR_TIME).takeWhile { event.isEmpty }.mapNotNull {
|
||||||
|
"This QR code does not contain a Recovery Password."
|
||||||
|
}
|
||||||
|
|
||||||
private val codec by lazy { MnemonicCodec { MnemonicUtilities.loadFileContents(getApplication(), it) } }
|
private val codec by lazy { MnemonicCodec { MnemonicUtilities.loadFileContents(getApplication(), it) } }
|
||||||
|
|
||||||
@ -64,11 +69,19 @@ class LinkDeviceViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onFailure(error: Throwable) {
|
private fun onFailure(error: Throwable) {
|
||||||
state.update { it.copy(error = error.message) }
|
state.update {
|
||||||
|
it.copy(
|
||||||
|
error = when (error) {
|
||||||
|
is InputTooShort -> "The Recovery Password you entered is not long enough. Please check and try again."
|
||||||
|
is InvalidWord -> "Some of the words in your Recovery Password are incorrect. Please check and try again."
|
||||||
|
else -> "Please check your Recovery Password and try again."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onScanFailure(error: Throwable) {
|
private fun onScanFailure(error: Throwable) {
|
||||||
state.update { it.copy(error = error.message) }
|
viewModelScope.launch { qrErrors.send(error) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runDecodeCatching(mnemonic: String) = runCatching {
|
private fun runDecodeCatching(mnemonic: String) = runCatching {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user