Fix QR code scan & display

This commit is contained in:
Andrew 2024-06-26 19:25:44 +09:30
parent ff6c0fb6f5
commit 39c7f27c7d
5 changed files with 64 additions and 39 deletions

View File

@ -24,11 +24,11 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
internal class PickDisplayNameViewModel( internal class PickDisplayNameViewModel(
pickNewName: Boolean, private val loadFailed: Boolean,
private val prefs: TextSecurePreferences, private val prefs: TextSecurePreferences,
private val configFactory: ConfigFactory private val configFactory: ConfigFactory
): ViewModel() { ): ViewModel() {
private val _states = MutableStateFlow(if (pickNewName) pickNewNameState() else State()) private val _states = MutableStateFlow(if (loadFailed) pickNewNameState() else State())
val states = _states.asStateFlow() val states = _states.asStateFlow()
private val _events = MutableSharedFlow<Event>() private val _events = MutableSharedFlow<Event>()
@ -48,6 +48,7 @@ internal class PickDisplayNameViewModel(
else -> { else -> {
prefs.setProfileName(displayName) prefs.setProfileName(displayName)
if (!loadFailed) {
// This is here to resolve a case where the app restarts before a user completes onboarding // This is here to resolve a case where the app restarts before a user completes onboarding
// which can result in an invalid database state // which can result in an invalid database state
database.clearAllLastMessageHashes() database.clearAllLastMessageHashes()
@ -65,6 +66,7 @@ internal class PickDisplayNameViewModel(
prefs.setLocalRegistrationId(registrationID) prefs.setLocalRegistrationId(registrationID)
prefs.setLocalNumber(userHexEncodedPublicKey) prefs.setLocalNumber(userHexEncodedPublicKey)
prefs.setRestorationTime(0) prefs.setRestorationTime(0)
}
viewModelScope.launch { _events.emit(Event.DONE) } viewModelScope.launch { _events.emit(Event.DONE) }
} }
@ -82,18 +84,18 @@ internal class PickDisplayNameViewModel(
@dagger.assisted.AssistedFactory @dagger.assisted.AssistedFactory
interface AssistedFactory { interface AssistedFactory {
fun create(pickNewName: Boolean): Factory fun create(loadFailed: Boolean): Factory
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class Factory @AssistedInject constructor( class Factory @AssistedInject constructor(
@Assisted private val pickNewName: Boolean, @Assisted private val loadFailed: Boolean,
private val prefs: TextSecurePreferences, private val prefs: TextSecurePreferences,
private val configFactory: ConfigFactory private val configFactory: ConfigFactory
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PickDisplayNameViewModel(pickNewName, prefs, configFactory) as T return PickDisplayNameViewModel(loadFailed, prefs, configFactory) as T
} }
} }
} }

View File

@ -32,8 +32,6 @@ import org.thoughtcrime.securesms.ui.SessionShieldIcon
import org.thoughtcrime.securesms.ui.base import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.color.Colors import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.components.ButtonStyle
import org.thoughtcrime.securesms.ui.components.OutlineCopyButton
import org.thoughtcrime.securesms.ui.components.QrImage import org.thoughtcrime.securesms.ui.components.QrImage
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
@ -44,7 +42,8 @@ import org.thoughtcrime.securesms.ui.h8
@Composable @Composable
internal fun RecoveryPasswordScreen( internal fun RecoveryPasswordScreen(
seed: String = "", mnemonic: String,
seed: String? = null,
copySeed:() -> Unit = {}, copySeed:() -> Unit = {},
onHide:() -> Unit = {} onHide:() -> Unit = {}
) { ) {
@ -55,13 +54,17 @@ internal fun RecoveryPasswordScreen(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(bottom = LocalDimensions.current.xsMargin) .padding(bottom = LocalDimensions.current.xsMargin)
) { ) {
RecoveryPasswordCell(seed, copySeed) RecoveryPasswordCell(mnemonic, seed, copySeed)
HideRecoveryPasswordCell(onHide) HideRecoveryPasswordCell(onHide)
} }
} }
@Composable @Composable
private fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) { private fun RecoveryPasswordCell(
mnemonic: String,
seed: String?,
copySeed:() -> Unit = {}
) {
var showQr by remember { var showQr by remember {
mutableStateOf(false) mutableStateOf(false)
} }
@ -85,7 +88,7 @@ private fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) {
) )
AnimatedVisibility(!showQr) { AnimatedVisibility(!showQr) {
RecoveryPassword(seed) RecoveryPassword(mnemonic)
} }
AnimatedVisibility( AnimatedVisibility(
@ -128,9 +131,9 @@ private fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) {
} }
@Composable @Composable
private fun RecoveryPassword(seed: String) { private fun RecoveryPassword(mnemonic: String) {
Text( Text(
seed, mnemonic,
modifier = Modifier modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password_container) .contentDescription(R.string.AccessibilityId_recovery_password_container)
.padding(vertical = LocalDimensions.current.smallMargin) .padding(vertical = LocalDimensions.current.smallMargin)
@ -178,6 +181,6 @@ private fun PreviewRecoveryPasswordScreen(
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors @PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
) { ) {
PreviewTheme(colors) { PreviewTheme(colors) {
RecoveryPasswordScreen(seed = "Voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane") RecoveryPasswordScreen(mnemonic = "voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane")
} }
} }

View File

@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.recoverypassword
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.showSessionDialog
@ -16,10 +18,15 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
supportActionBar!!.title = resources.getString(R.string.sessionRecoveryPassword) supportActionBar!!.title = resources.getString(R.string.sessionRecoveryPassword)
setComposeContent { setComposeContent {
val mnemonic by viewModel.mnemonic.collectAsState("")
val seed by viewModel.seed.collectAsState(null)
RecoveryPasswordScreen( RecoveryPasswordScreen(
viewModel.seed, mnemonic = mnemonic,
{ viewModel.copySeed(this) } seed = seed,
) { onHide() } copySeed = { viewModel.copySeed(this) },
onHide = ::onHide
)
} }
} }

View File

@ -5,7 +5,15 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.session.libsession.utilities.AppTextSecurePreferences import org.session.libsession.utilities.AppTextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.crypto.MnemonicCodec
@ -20,21 +28,26 @@ class RecoveryPasswordViewModel @Inject constructor(
): AndroidViewModel(application) { ): AndroidViewModel(application) {
val prefs = AppTextSecurePreferences(application) val prefs = AppTextSecurePreferences(application)
val seed = MutableStateFlow<String?>(null)
val mnemonic = seed.filterNotNull()
.map { MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) }.encode(it, MnemonicCodec.Language.Configuration.english) }
fun permanentlyHidePassword() { fun permanentlyHidePassword() {
prefs.setHidePassword(true) prefs.setHidePassword(true)
} }
fun copySeed(context: Context) { fun copySeed(context: Context) {
val seed = seed.value ?: return
TextSecurePreferences.setHasViewedSeed(context, true) TextSecurePreferences.setHasViewedSeed(context, true)
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Seed", seed) val clip = ClipData.newPlainText("Seed", seed)
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)
} }
val seed by lazy { init {
val hexEncodedSeed = IdentityKeyUtil.retrieve(application, IdentityKeyUtil.LOKI_SEED) viewModelScope.launch(Dispatchers.IO) {
?: IdentityKeyUtil.getIdentityKeyPair(application).hexEncodedPrivateKey // Legacy account seed.emit(IdentityKeyUtil.retrieve(application, IdentityKeyUtil.LOKI_SEED)
MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) } ?: IdentityKeyUtil.getIdentityKeyPair(application).hexEncodedPrivateKey) // Legacy account
.encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english) }
} }
} }

View File

@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.util.QRCodeUtilities
@Composable @Composable
fun QrImage( fun QrImage(
string: String, string: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
icon: Int = R.drawable.session_shield icon: Int = R.drawable.session_shield
) { ) {
@ -47,7 +47,7 @@ fun QrImage(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(string) { LaunchedEffect(string) {
scope.launch(Dispatchers.IO) { if (string != null) scope.launch(Dispatchers.IO) {
bitmap = (300..500 step 100).firstNotNullOf { bitmap = (300..500 step 100).firstNotNullOf {
runCatching { QRCodeUtilities.encode(string, it) }.getOrNull() runCatching { QRCodeUtilities.encode(string, it) }.getOrNull()
} }