mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-21 15:05:19 +00:00
Defer setting display name until necessary in create flow in onboarding
This commit is contained in:
parent
9cf3a37a2b
commit
508547a013
@ -10,7 +10,7 @@ import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.onboarding.manager.LoadingManager
|
||||
import org.thoughtcrime.securesms.onboarding.manager.LoadAccountManager
|
||||
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsActivity
|
||||
import org.thoughtcrime.securesms.ui.setComposeContent
|
||||
import org.thoughtcrime.securesms.util.start
|
||||
@ -22,7 +22,7 @@ class LoadAccountActivity : BaseActionBarActivity() {
|
||||
@Inject
|
||||
internal lateinit var prefs: TextSecurePreferences
|
||||
@Inject
|
||||
internal lateinit var loadingManager: LoadingManager
|
||||
internal lateinit var loadAccountManager: LoadAccountManager
|
||||
|
||||
private val viewModel: LoadAccountViewModel by viewModels()
|
||||
|
||||
@ -35,7 +35,7 @@ class LoadAccountActivity : BaseActionBarActivity() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.events.collect {
|
||||
loadingManager.load(it.mnemonic)
|
||||
loadAccountManager.load(it.mnemonic)
|
||||
start<MessageNotificationsActivity>()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.onboarding.manager
|
||||
|
||||
import android.app.Application
|
||||
import org.session.libsession.snode.SnodeModule
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.utilities.KeyHelper
|
||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class CreateAccountManager @Inject constructor(
|
||||
private val application: Application,
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val configFactory: ConfigFactory,
|
||||
) {
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
|
||||
fun createAccount(displayName: String) {
|
||||
prefs.setProfileName(displayName)
|
||||
configFactory.user?.setName(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(application, seed, ed25519KeyPair, x25519KeyPair)
|
||||
configFactory.keyPairChanged()
|
||||
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
|
||||
val registrationID = KeyHelper.generateRegistrationId(false)
|
||||
prefs.setLocalRegistrationId(registrationID)
|
||||
prefs.setLocalNumber(userHexEncodedPublicKey)
|
||||
prefs.setRestorationTime(0)
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LoadingManager @Inject constructor(
|
||||
class LoadAccountManager @Inject constructor(
|
||||
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val prefs: TextSecurePreferences
|
@ -6,14 +6,14 @@ import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.home.startHomeActivity
|
||||
import org.thoughtcrime.securesms.notifications.PushRegistry
|
||||
import org.thoughtcrime.securesms.onboarding.loading.LoadingActivity
|
||||
import org.thoughtcrime.securesms.onboarding.manager.LoadingManager
|
||||
import org.thoughtcrime.securesms.onboarding.manager.LoadAccountManager
|
||||
import org.thoughtcrime.securesms.onboarding.messagenotifications.MessageNotificationsActivity.Companion.EXTRA_PROFILE_NAME
|
||||
import org.thoughtcrime.securesms.ui.setComposeContent
|
||||
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
|
||||
@ -30,9 +30,8 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
||||
@Inject
|
||||
internal lateinit var viewModelFactory: MessageNotificationsViewModel.AssistedFactory
|
||||
|
||||
@Inject lateinit var pushRegistry: PushRegistry
|
||||
@Inject lateinit var prefs: TextSecurePreferences
|
||||
@Inject lateinit var loadingManager: LoadingManager
|
||||
@Inject lateinit var loadAccountManager: LoadAccountManager
|
||||
|
||||
val profileName by lazy { intent.getStringExtra(EXTRA_PROFILE_NAME) }
|
||||
|
||||
@ -46,6 +45,15 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
||||
prefs.setHasSeenWelcomeScreen(true)
|
||||
|
||||
setComposeContent { MessageNotificationsScreen() }
|
||||
|
||||
lifecycleScope.launch {
|
||||
viewModel.events.collect {
|
||||
when (it) {
|
||||
Event.Loading -> start<LoadingActivity>()
|
||||
Event.OnboardingComplete -> startHomeActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@ -62,22 +70,11 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
||||
MessageNotificationsScreen(
|
||||
uiState,
|
||||
setEnabled = viewModel::setEnabled,
|
||||
onContinue = ::register,
|
||||
onContinue = viewModel::onContinue,
|
||||
quit = viewModel::quit,
|
||||
dismissDialog = viewModel::dismissDialog
|
||||
)
|
||||
}
|
||||
|
||||
private fun register() {
|
||||
prefs.setPushEnabled(viewModel.uiStates.value.pushEnabled)
|
||||
ApplicationContext.getInstance(this).startPollingIfNeeded()
|
||||
pushRegistry.refresh(true)
|
||||
|
||||
when {
|
||||
prefs.getHasViewedSeed() && !prefs.getConfigurationMessageSynced() -> start<LoadingActivity>()
|
||||
else -> startHomeActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Activity.startMessageNotificationsActivity(profileName: String) {
|
||||
|
@ -8,23 +8,50 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.notifications.PushRegistry
|
||||
import org.thoughtcrime.securesms.onboarding.manager.CreateAccountManager
|
||||
|
||||
internal class MessageNotificationsViewModel(
|
||||
private val state: State,
|
||||
private val application: Application
|
||||
private val application: Application,
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val pushRegistry: PushRegistry,
|
||||
private val createAccountManager: CreateAccountManager
|
||||
): AndroidViewModel(application) {
|
||||
private val _uiStates = MutableStateFlow(UiState())
|
||||
val uiStates = _uiStates.asStateFlow()
|
||||
|
||||
private val _events = MutableSharedFlow<Event>()
|
||||
val events = _events.asSharedFlow()
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
_uiStates.update { UiState(pushEnabled = enabled) }
|
||||
}
|
||||
|
||||
fun onContinue() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (state is State.CreateAccount) createAccountManager.createAccount(state.displayName)
|
||||
|
||||
prefs.setPushEnabled(uiStates.value.pushEnabled)
|
||||
pushRegistry.refresh(true)
|
||||
|
||||
_events.emit(
|
||||
when (state) {
|
||||
is State.CreateAccount -> Event.OnboardingComplete
|
||||
else -> Event.Loading
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [true] if the back press was handled.
|
||||
*/
|
||||
@ -70,14 +97,24 @@ internal class MessageNotificationsViewModel(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class Factory @AssistedInject constructor(
|
||||
@Assisted private val profileName: String?,
|
||||
private val application: Application
|
||||
private val application: Application,
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val pushRegistry: PushRegistry,
|
||||
private val createAccountManager: CreateAccountManager,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return MessageNotificationsViewModel(
|
||||
state = profileName?.let(State::CreateAccount) ?: State.LoadAccount,
|
||||
application = application
|
||||
application = application,
|
||||
prefs = prefs,
|
||||
pushRegistry = pushRegistry,
|
||||
createAccountManager = createAccountManager
|
||||
) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Event {
|
||||
OnboardingComplete, Loading
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
|
||||
@Composable
|
||||
private fun DisplayNameScreen(viewModel: PickDisplayNameViewModel) {
|
||||
val state = viewModel.states.collectAsState()
|
||||
DisplayName(state.value, viewModel::onChange) { viewModel.onContinue(this) }
|
||||
DisplayName(state.value, viewModel::onChange) { viewModel.onContinue() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
package org.thoughtcrime.securesms.onboarding.pickname
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@ -12,15 +10,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.snode.SnodeModule
|
||||
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol.Companion.NAME_PADDED_LENGTH
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.utilities.KeyHelper
|
||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
|
||||
internal class PickDisplayNameViewModel(
|
||||
@ -34,10 +26,7 @@ internal class PickDisplayNameViewModel(
|
||||
private val _events = MutableSharedFlow<Event>()
|
||||
val events = _events.asSharedFlow()
|
||||
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
|
||||
fun onContinue(context: Context) {
|
||||
fun onContinue() {
|
||||
_states.update { it.copy(displayName = it.displayName.trim()) }
|
||||
|
||||
val displayName = _states.value.displayName
|
||||
@ -46,37 +35,18 @@ internal class PickDisplayNameViewModel(
|
||||
displayName.isEmpty() -> { _states.update { it.copy(isTextErrorColor = true, error = R.string.displayNameErrorDescription) } }
|
||||
displayName.toByteArray().size > NAME_PADDED_LENGTH -> { _states.update { it.copy(isTextErrorColor = true, error = R.string.displayNameErrorDescriptionShorter) } }
|
||||
else -> {
|
||||
// success - clear the error as we can still see it during the transition to the
|
||||
// next screen.
|
||||
_states.update { it.copy(isTextErrorColor = false, error = null) }
|
||||
|
||||
prefs.setProfileName(displayName)
|
||||
configFactory.user?.setName(displayName)
|
||||
when {
|
||||
loadFailed -> {
|
||||
prefs.setProfileName(displayName)
|
||||
configFactory.user?.setName(displayName)
|
||||
|
||||
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 {
|
||||
if (loadFailed) {
|
||||
_events.emit(Event.LoadAccountComplete)
|
||||
} else {
|
||||
_events.emit(Event.CreateAccount(displayName))
|
||||
_events.tryEmit(Event.LoadAccountComplete)
|
||||
}
|
||||
else -> _events.tryEmit(Event.CreateAccount(displayName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
import android.view.View
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@ -108,5 +109,4 @@ data class ThemeState (
|
||||
|
||||
inline fun <reified T: Activity> Activity.show() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) }
|
||||
inline fun <reified T: Activity> Activity.push() = Intent(this, T::class.java).also(::startActivity).let { overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out) }
|
||||
inline fun <reified T: Activity> Context.start() = Intent(this, T::class.java).let(::startActivity)
|
||||
inline fun <reified T: Activity> Context.start(modify: Intent.() -> Unit) = Intent(this, T::class.java).also(modify).let(::startActivity)
|
||||
inline fun <reified T: Activity> Context.start(modify: Intent.() -> Unit = {}) = Intent(this, T::class.java).also(modify).apply { addFlags(FLAG_ACTIVITY_SINGLE_TOP) }.let(::startActivity)
|
||||
|
@ -28,7 +28,7 @@ class MnemonicCodecTest {
|
||||
|
||||
@Test
|
||||
fun `decode empty`() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
assertThrows(InputTooShort::class.java) {
|
||||
codec.decode("")
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,6 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
|
||||
}
|
||||
|
||||
fun sanitizeAndDecodeAsByteArray(mnemonic: String): ByteArray = sanitizeRecoveryPhrase(mnemonic).let(::decode).let(Hex::fromStringCondensed)
|
||||
fun decodeAsByteArray(mnemonic: String): ByteArray = decode(mnemonic = mnemonic).let(Hex::fromStringCondensed)
|
||||
|
||||
private fun sanitizeRecoveryPhrase(rawMnemonic: String): String = rawMnemonic
|
||||
.replace("[^\\w]+".toRegex(), " ") // replace any sequence of non-word characters with a space
|
||||
@ -125,27 +124,30 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
|
||||
.joinToString(" ") // reassemble
|
||||
|
||||
fun decodeMnemonicOrHexAsByteArray(mnemonicOrHex: String): ByteArray = try {
|
||||
// Try to use decode mnemonicOrHex as a mnemonic
|
||||
decode(mnemonic = mnemonicOrHex).let(Hex::fromStringCondensed)
|
||||
} catch (decodeException: Exception) {
|
||||
if (mnemonicOrHex.isHex()) throw decodeException
|
||||
// It's not a valid mnemonic, if it's pure-hexadecimal then we'll interpret it as a
|
||||
// hexadecimal-byte encoded mnemonic.
|
||||
if (!mnemonicOrHex.isHex()) throw decodeException
|
||||
try {
|
||||
Hex.fromStringCondensed(mnemonicOrHex)
|
||||
} catch (_: Exception) {
|
||||
throw decodeException
|
||||
}
|
||||
}
|
||||
|
||||
private fun swap(x: String): String {
|
||||
val p1 = x.substring(6 until 8)
|
||||
val p2 = x.substring(4 until 6)
|
||||
val p3 = x.substring(2 until 4)
|
||||
val p4 = x.substring(0 until 2)
|
||||
return p1 + p2 + p3 + p4
|
||||
}
|
||||
|
||||
private fun determineChecksumIndex(x: List<String>, prefixLength: Int): Int {
|
||||
val bytes = x.joinToString("") { it.substring(0 until prefixLength) }.toByteArray()
|
||||
val checksum = CRC32().apply { update(bytes) }.value
|
||||
return (checksum % x.size.toLong()).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun swap(x: String): String {
|
||||
val p1 = x.substring(6 until 8)
|
||||
val p2 = x.substring(4 until 6)
|
||||
val p3 = x.substring(2 until 4)
|
||||
val p4 = x.substring(0 until 2)
|
||||
return p1 + p2 + p3 + p4
|
||||
}
|
||||
|
||||
private fun determineChecksumIndex(x: List<String>, prefixLength: Int): Int {
|
||||
val bytes = x.joinToString("") { it.substring(0 until prefixLength) }.toByteArray()
|
||||
val checksum = CRC32().apply { update(bytes) }.value
|
||||
return (checksum % x.size.toLong()).toInt()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user