diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt index 7054797416..a6fb8fc35f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/pickname/PickDisplayNameViewModel.kt @@ -24,11 +24,11 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.dependencies.ConfigFactory internal class PickDisplayNameViewModel( - pickNewName: Boolean, + private val loadFailed: Boolean, private val prefs: TextSecurePreferences, private val configFactory: ConfigFactory ): ViewModel() { - private val _states = MutableStateFlow(if (pickNewName) pickNewNameState() else State()) + private val _states = MutableStateFlow(if (loadFailed) pickNewNameState() else State()) val states = _states.asStateFlow() private val _events = MutableSharedFlow() @@ -48,23 +48,25 @@ internal class PickDisplayNameViewModel( else -> { prefs.setProfileName(displayName) - // This is here to resolve a case where the app restarts before a user completes onboarding - // which can result in an invalid database state - database.clearAllLastMessageHashes() - database.clearReceivedMessageHashValues() - - val keyPairGenerationResult = KeyPairUtilities.generate() - val seed = keyPairGenerationResult.seed - val ed25519KeyPair = keyPairGenerationResult.ed25519KeyPair - val x25519KeyPair = keyPairGenerationResult.x25519KeyPair - - KeyPairUtilities.store(context, seed, ed25519KeyPair, x25519KeyPair) - configFactory.keyPairChanged() - val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey - val registrationID = KeyHelper.generateRegistrationId(false) - prefs.setLocalRegistrationId(registrationID) - prefs.setLocalNumber(userHexEncodedPublicKey) - prefs.setRestorationTime(0) + if (!loadFailed) { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + + val keyPairGenerationResult = KeyPairUtilities.generate() + val seed = keyPairGenerationResult.seed + val ed25519KeyPair = keyPairGenerationResult.ed25519KeyPair + val x25519KeyPair = keyPairGenerationResult.x25519KeyPair + + KeyPairUtilities.store(context, seed, ed25519KeyPair, x25519KeyPair) + configFactory.keyPairChanged() + val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey + val registrationID = KeyHelper.generateRegistrationId(false) + prefs.setLocalRegistrationId(registrationID) + prefs.setLocalNumber(userHexEncodedPublicKey) + prefs.setRestorationTime(0) + } viewModelScope.launch { _events.emit(Event.DONE) } } @@ -82,18 +84,18 @@ internal class PickDisplayNameViewModel( @dagger.assisted.AssistedFactory interface AssistedFactory { - fun create(pickNewName: Boolean): Factory + fun create(loadFailed: Boolean): Factory } @Suppress("UNCHECKED_CAST") class Factory @AssistedInject constructor( - @Assisted private val pickNewName: Boolean, + @Assisted private val loadFailed: Boolean, private val prefs: TextSecurePreferences, private val configFactory: ConfigFactory ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return PickDisplayNameViewModel(pickNewName, prefs, configFactory) as T + return PickDisplayNameViewModel(loadFailed, prefs, configFactory) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt index 6bb0cd9baf..524cc2a98c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPassword.kt @@ -32,8 +32,6 @@ import org.thoughtcrime.securesms.ui.SessionShieldIcon import org.thoughtcrime.securesms.ui.base import org.thoughtcrime.securesms.ui.color.Colors 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.SlimOutlineButton import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton @@ -44,7 +42,8 @@ import org.thoughtcrime.securesms.ui.h8 @Composable internal fun RecoveryPasswordScreen( - seed: String = "", + mnemonic: String, + seed: String? = null, copySeed:() -> Unit = {}, onHide:() -> Unit = {} ) { @@ -55,13 +54,17 @@ internal fun RecoveryPasswordScreen( .verticalScroll(rememberScrollState()) .padding(bottom = LocalDimensions.current.xsMargin) ) { - RecoveryPasswordCell(seed, copySeed) + RecoveryPasswordCell(mnemonic, seed, copySeed) HideRecoveryPasswordCell(onHide) } } @Composable -private fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) { +private fun RecoveryPasswordCell( + mnemonic: String, + seed: String?, + copySeed:() -> Unit = {} +) { var showQr by remember { mutableStateOf(false) } @@ -85,7 +88,7 @@ private fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) { ) AnimatedVisibility(!showQr) { - RecoveryPassword(seed) + RecoveryPassword(mnemonic) } AnimatedVisibility( @@ -128,9 +131,9 @@ private fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) { } @Composable -private fun RecoveryPassword(seed: String) { +private fun RecoveryPassword(mnemonic: String) { Text( - seed, + mnemonic, modifier = Modifier .contentDescription(R.string.AccessibilityId_recovery_password_container) .padding(vertical = LocalDimensions.current.smallMargin) @@ -178,6 +181,6 @@ private fun PreviewRecoveryPasswordScreen( @PreviewParameter(SessionColorsParameterProvider::class) colors: 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") } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt index 53532522d6..a4272fd877 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.recoverypassword import android.os.Bundle import androidx.activity.viewModels +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import network.loki.messenger.R import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.showSessionDialog @@ -16,10 +18,15 @@ class RecoveryPasswordActivity : BaseActionBarActivity() { supportActionBar!!.title = resources.getString(R.string.sessionRecoveryPassword) setComposeContent { + val mnemonic by viewModel.mnemonic.collectAsState("") + val seed by viewModel.seed.collectAsState(null) + RecoveryPasswordScreen( - viewModel.seed, - { viewModel.copySeed(this) } - ) { onHide() } + mnemonic = mnemonic, + seed = seed, + copySeed = { viewModel.copySeed(this) }, + onHide = ::onHide + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt index 32c2a7cf2c..59f3eb745f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt @@ -5,7 +5,15 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope 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.TextSecurePreferences import org.session.libsignal.crypto.MnemonicCodec @@ -20,21 +28,26 @@ class RecoveryPasswordViewModel @Inject constructor( ): AndroidViewModel(application) { val prefs = AppTextSecurePreferences(application) + val seed = MutableStateFlow(null) + val mnemonic = seed.filterNotNull() + .map { MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) }.encode(it, MnemonicCodec.Language.Configuration.english) } + fun permanentlyHidePassword() { prefs.setHidePassword(true) } fun copySeed(context: Context) { + val seed = seed.value ?: return TextSecurePreferences.setHasViewedSeed(context, true) val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("Seed", seed) clipboard.setPrimaryClip(clip) } - val seed by lazy { - val hexEncodedSeed = IdentityKeyUtil.retrieve(application, IdentityKeyUtil.LOKI_SEED) - ?: IdentityKeyUtil.getIdentityKeyPair(application).hexEncodedPrivateKey // Legacy account - MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) } - .encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english) + init { + viewModelScope.launch(Dispatchers.IO) { + seed.emit(IdentityKeyUtil.retrieve(application, IdentityKeyUtil.LOKI_SEED) + ?: IdentityKeyUtil.getIdentityKeyPair(application).hexEncodedPrivateKey) // Legacy account + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/QrImage.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/QrImage.kt index 997b44afe4..530aebd457 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/QrImage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/QrImage.kt @@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.util.QRCodeUtilities @Composable fun QrImage( - string: String, + string: String?, modifier: Modifier = Modifier, icon: Int = R.drawable.session_shield ) { @@ -47,7 +47,7 @@ fun QrImage( val scope = rememberCoroutineScope() LaunchedEffect(string) { - scope.launch(Dispatchers.IO) { + if (string != null) scope.launch(Dispatchers.IO) { bitmap = (300..500 step 100).firstNotNullOf { runCatching { QRCodeUtilities.encode(string, it) }.getOrNull() }