Start loading account while user chooses notifications

This commit is contained in:
Andrew 2024-06-16 23:12:29 +09:30
parent b18561acb4
commit 3e8701d10f
13 changed files with 352 additions and 296 deletions

View File

@ -264,7 +264,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
// If the user account hasn't been created or onboarding wasn't finished then don't start // If the user account hasn't been created or onboarding wasn't finished then don't start
// the pollers // the pollers
if (TextSecurePreferences.getLocalNumber(this) == null || !TextSecurePreferences.hasSeenWelcomeScreen(this)) { if (textSecurePreferences.getLocalNumber() == null || !textSecurePreferences.hasSeenWelcomeScreen()) {
return; return;
} }

View File

@ -4,6 +4,7 @@ import android.Manifest
import android.app.NotificationManager import android.app.NotificationManager
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -789,3 +790,10 @@ private fun EmptyView(newAccount: Boolean) {
Spacer(modifier = Modifier.weight(2f)) Spacer(modifier = Modifier.weight(2f))
} }
} }
fun Context.startHomeActivity() {
Intent(this, HomeActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(HomeActivity.FROM_ONBOARDING, true)
}.also(::startActivity)
}

View File

@ -37,6 +37,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -46,10 +47,9 @@ import org.thoughtcrime.securesms.onboarding.pickname.startPickDisplayNameActivi
import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.showOpenUrlDialog import org.thoughtcrime.securesms.showOpenUrlDialog
import org.thoughtcrime.securesms.ui.LocalDimensions import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.PreviewTheme import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
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.BorderlessHtmlButton import org.thoughtcrime.securesms.ui.components.BorderlessHtmlButton
import org.thoughtcrime.securesms.ui.components.PrimaryFillButton import org.thoughtcrime.securesms.ui.components.PrimaryFillButton
@ -60,21 +60,13 @@ import org.thoughtcrime.securesms.ui.large
import org.thoughtcrime.securesms.ui.setComposeContent import org.thoughtcrime.securesms.ui.setComposeContent
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.start import org.thoughtcrime.securesms.util.start
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
private data class TextData( @AndroidEntryPoint
@StringRes val stringId: Int, class LandingActivity: BaseActionBarActivity() {
val isOutgoing: Boolean = false
)
private val MESSAGES = listOf( @Inject lateinit var prefs: TextSecurePreferences
TextData(R.string.onboardingBubbleWelcomeToSession),
TextData(R.string.onboardingBubbleSessionIsEngineered, isOutgoing = true),
TextData(R.string.onboardingBubbleNoPhoneNumber),
TextData(R.string.onboardingBubbleCreatingAnAccountIsEasy, isOutgoing = true)
)
class LandingActivity : BaseActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -85,7 +77,14 @@ class LandingActivity : BaseActionBarActivity() {
setUpActionBarSessionLogo(true) setUpActionBarSessionLogo(true)
setComposeContent { LandingScreen() } setComposeContent {
LandingScreen(
createAccount = {
prefs.setHasViewedSeed(false)
startPickDisplayNameActivity()
}
)
}
IdentityKeyUtil.generateIdentityKeyPair(this) IdentityKeyUtil.generateIdentityKeyPair(this)
TextSecurePreferences.setPasswordDisabled(this, true) TextSecurePreferences.setPasswordDisabled(this, true)
@ -99,12 +98,12 @@ class LandingActivity : BaseActionBarActivity() {
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors @PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
) { ) {
PreviewTheme(colors) { PreviewTheme(colors) {
LandingScreen() LandingScreen {}
} }
} }
@Composable @Composable
private fun LandingScreen() { private fun LandingScreen(createAccount: () -> Unit) {
var count by remember { mutableStateOf(0) } var count by remember { mutableStateOf(0) }
val listState = rememberLazyListState() val listState = rememberLazyListState()
@ -159,7 +158,7 @@ class LandingActivity : BaseActionBarActivity() {
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.CenterHorizontally) .align(Alignment.CenterHorizontally)
.contentDescription(R.string.AccessibilityId_create_account_button), .contentDescription(R.string.AccessibilityId_create_account_button),
onClick = ::startPickDisplayNameActivity onClick = createAccount
) )
Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall)) Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall))
PrimaryOutlineButton( PrimaryOutlineButton(
@ -252,3 +251,15 @@ private fun MessageText(
) )
} }
} }
private data class TextData(
@StringRes val stringId: Int,
val isOutgoing: Boolean = false
)
private val MESSAGES = listOf(
TextData(R.string.onboardingBubbleWelcomeToSession),
TextData(R.string.onboardingBubbleSessionIsEngineered, isOutgoing = true),
TextData(R.string.onboardingBubbleNoPhoneNumber),
TextData(R.string.onboardingBubbleCreatingAnAccountIsEasy, isOutgoing = true)
)

View File

@ -30,7 +30,7 @@ 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
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.onboarding.loading.startLoadingActivity import org.thoughtcrime.securesms.onboarding.loading.LoadingManager
import org.thoughtcrime.securesms.onboarding.messagenotifications.startMessageNotificationsActivity import org.thoughtcrime.securesms.onboarding.messagenotifications.startMessageNotificationsActivity
import org.thoughtcrime.securesms.ui.LocalDimensions import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme import org.thoughtcrime.securesms.ui.PreviewTheme
@ -52,8 +52,8 @@ private val TITLES = listOf(R.string.sessionRecoveryPassword, R.string.qrScan)
@androidx.annotation.OptIn(ExperimentalGetImage::class) @androidx.annotation.OptIn(ExperimentalGetImage::class)
class LinkDeviceActivity : BaseActionBarActivity() { class LinkDeviceActivity : BaseActionBarActivity() {
@Inject @Inject lateinit var prefs: TextSecurePreferences
lateinit var prefs: TextSecurePreferences @Inject lateinit var loadingManager: LoadingManager
val viewModel: LinkDeviceViewModel by viewModels() val viewModel: LinkDeviceViewModel by viewModels()
@ -67,8 +67,8 @@ class LinkDeviceActivity : BaseActionBarActivity() {
lifecycleScope.launch { lifecycleScope.launch {
viewModel.eventFlow.collect { viewModel.eventFlow.collect {
loadingManager.load(it.mnemonic)
startMessageNotificationsActivity() startMessageNotificationsActivity()
startLoadingActivity(it.mnemonic)
finish() finish()
} }
} }

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.onboarding.loading package org.thoughtcrime.securesms.onboarding.loading
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
@ -13,14 +12,12 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.onboarding.messagenotifications.startMessageNotificationsActivity import org.thoughtcrime.securesms.home.startHomeActivity
import org.thoughtcrime.securesms.onboarding.pickname.startPickDisplayNameActivity import org.thoughtcrime.securesms.onboarding.pickname.startPickDisplayNameActivity
import org.thoughtcrime.securesms.ui.setComposeContent import org.thoughtcrime.securesms.ui.setComposeContent
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import javax.inject.Inject import javax.inject.Inject
private const val EXTRA_MNEMONIC = "mnemonic"
@AndroidEntryPoint @AndroidEntryPoint
class LoadingActivity: BaseActionBarActivity() { class LoadingActivity: BaseActionBarActivity() {
@ -40,11 +37,12 @@ class LoadingActivity: BaseActionBarActivity() {
private fun register(skipped: Boolean) { private fun register(skipped: Boolean) {
prefs.setLastConfigurationSyncTime(System.currentTimeMillis()) prefs.setLastConfigurationSyncTime(System.currentTimeMillis())
val flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
when { when {
skipped -> startPickDisplayNameActivity(true, flags) skipped -> startPickDisplayNameActivity(
else -> startMessageNotificationsActivity(flags) failedToLoad = true,
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
)
else -> startHomeActivity()
} }
} }
@ -60,8 +58,6 @@ class LoadingActivity: BaseActionBarActivity() {
setUpActionBarSessionLogo(true) setUpActionBarSessionLogo(true)
viewModel.restore(application, intent.getByteArrayExtra(EXTRA_MNEMONIC)!!)
lifecycleScope.launch { lifecycleScope.launch {
viewModel.eventFlow.collect { viewModel.eventFlow.collect {
when (it) { when (it) {
@ -72,9 +68,3 @@ class LoadingActivity: BaseActionBarActivity() {
} }
} }
} }
fun Context.startLoadingActivity(mnemonic: ByteArray) {
Intent(this, LoadingActivity::class.java)
.apply { putExtra(EXTRA_MNEMONIC, mnemonic) }
.also(::startActivity)
}

View File

@ -0,0 +1,59 @@
package org.thoughtcrime.securesms.onboarding.loading
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.database.LokiAPIDatabaseProtocol
import org.session.libsignal.utilities.hexEncodedPublicKey
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class LoadingManager @Inject constructor(
@dagger.hilt.android.qualifiers.ApplicationContext val context: Context,
val configFactory: ConfigFactory,
val prefs: TextSecurePreferences
) {
val isLoading: Boolean get() = restoreJob?.isActive == true
private val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage
private var restoreJob: Job? = null
private val scope = CoroutineScope(Dispatchers.IO)
fun load(seed: ByteArray) {
// only have one sync job running at a time (prevent QR from trying to spawn a new job)
if (restoreJob?.isActive == true) return
restoreJob = scope.launch {
// 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()
// RestoreActivity handles seed this way
val keyPairGenerationResult = org.thoughtcrime.securesms.crypto.KeyPairUtilities.generate(seed)
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
org.thoughtcrime.securesms.crypto.KeyPairUtilities.store(context, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair)
configFactory.keyPairChanged()
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
val registrationID = org.session.libsignal.utilities.KeyHelper.generateRegistrationId(false)
prefs.apply {
setLocalRegistrationId(registrationID)
setLocalNumber(userHexEncodedPublicKey)
setRestorationTime(System.currentTimeMillis())
setHasViewedSeed(true)
}
ApplicationContext.getInstance(context).apply { startPollingIfNeeded() }
}
}
}

View File

@ -1,26 +1,21 @@
package org.thoughtcrime.securesms.onboarding.loading package org.thoughtcrime.securesms.onboarding.loading
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.TextSecurePreferences 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.ApplicationContext
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -31,67 +26,44 @@ data class State(val duration: Duration)
private val ANIMATE_TO_DONE_TIME = 500.milliseconds private val ANIMATE_TO_DONE_TIME = 500.milliseconds
private val IDLE_DONE_TIME = 1.seconds private val IDLE_DONE_TIME = 1.seconds
private val TIMEOUT_TIME = 15.seconds private val TIMEOUT_TIME = 15.seconds
private val TOTAL_ANIMATION_TIME = TIMEOUT_TIME - IDLE_DONE_TIME
@OptIn(FlowPreview::class)
@HiltViewModel @HiltViewModel
class LoadingViewModel @Inject constructor( class LoadingViewModel @Inject constructor(
private val configFactory: ConfigFactory, val prefs: TextSecurePreferences
private val prefs: TextSecurePreferences, ): ViewModel() {
) : ViewModel() {
private val state = MutableStateFlow(State(TOTAL_ANIMATION_TIME)) private val state = MutableStateFlow(State(TIMEOUT_TIME))
val stateFlow = state.asStateFlow() val stateFlow = state.asStateFlow()
private val event = Channel<Event>() private val event = Channel<Event>()
val eventFlow = event.receiveAsFlow() val eventFlow = event.receiveAsFlow()
private var restoreJob: Job? = null init {
viewModelScope.launch(Dispatchers.IO) {
internal val database: LokiAPIDatabaseProtocol try {
get() = SnodeModule.shared.storage TextSecurePreferences.events
.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }
fun restore(context: Context, seed: ByteArray) { .timeout(TIMEOUT_TIME)
.onStart { emit(TextSecurePreferences.CONFIGURATION_SYNCED) }
// only have one sync job running at a time (prevent QR from trying to spawn a new job) .collectLatest {
if (restoreJob?.isActive == true) return if (prefs.getConfigurationMessageSynced()) onSuccess()
}
restoreJob = viewModelScope.launch(Dispatchers.IO) { } catch (e: Exception) {
// This is here to resolve a case where the app restarts before a user completes onboarding onFail()
// which can result in an invalid database state
database.clearAllLastMessageHashes()
database.clearReceivedMessageHashValues()
// RestoreActivity handles seed this way
val keyPairGenerationResult = KeyPairUtilities.generate(seed)
val x25519KeyPair = keyPairGenerationResult.x25519KeyPair
KeyPairUtilities.store(context, seed, keyPairGenerationResult.ed25519KeyPair, x25519KeyPair)
configFactory.keyPairChanged()
val userHexEncodedPublicKey = x25519KeyPair.hexEncodedPublicKey
val registrationID = KeyHelper.generateRegistrationId(false)
prefs.apply {
setLocalRegistrationId(registrationID)
setLocalNumber(userHexEncodedPublicKey)
setRestorationTime(System.currentTimeMillis())
setHasViewedSeed(true)
}
val skipJob = launch(Dispatchers.IO) {
delay(TIMEOUT_TIME)
event.send(Event.TIMEOUT)
}
// start polling and wait for updated message
ApplicationContext.getInstance(context).apply { startPollingIfNeeded() }
TextSecurePreferences.events.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }.collect {
// handle we've synced
skipJob.cancel()
state.value = State(ANIMATE_TO_DONE_TIME)
delay(IDLE_DONE_TIME)
event.send(Event.SUCCESS)
} }
} }
} }
private suspend fun onSuccess() {
state.value = State(ANIMATE_TO_DONE_TIME)
delay(IDLE_DONE_TIME)
event.send(Event.SUCCESS)
}
private fun onFail() {
event.trySend(Event.TIMEOUT)
}
} }
sealed interface Event { sealed interface Event {

View File

@ -12,22 +12,28 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.home.HomeActivity
import org.thoughtcrime.securesms.home.startHomeActivity
import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.notifications.PushRegistry
import org.thoughtcrime.securesms.onboarding.loading.LoadingActivity
import org.thoughtcrime.securesms.onboarding.loading.LoadingManager
import org.thoughtcrime.securesms.ui.setComposeContent import org.thoughtcrime.securesms.ui.setComposeContent
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.start
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MessageNotificationsActivity : BaseActionBarActivity() { class MessageNotificationsActivity : BaseActionBarActivity() {
@Inject lateinit var pushRegistry: PushRegistry @Inject lateinit var pushRegistry: PushRegistry
@Inject lateinit var prefs: TextSecurePreferences
@Inject lateinit var loadingManager: LoadingManager
private val viewModel: MessageNotificationsViewModel by viewModels() private val viewModel: MessageNotificationsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo(true) setUpActionBarSessionLogo(true)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true) prefs.setHasSeenWelcomeScreen(true)
setComposeContent { MessageNotificationsScreen() } setComposeContent { MessageNotificationsScreen() }
} }
@ -39,13 +45,15 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
} }
private fun register() { private fun register() {
TextSecurePreferences.setPushEnabled(this, viewModel.stateFlow.value.pushEnabled) prefs.setPushEnabled(viewModel.stateFlow.value.pushEnabled)
ApplicationContext.getInstance(this).startPollingIfNeeded() ApplicationContext.getInstance(this).startPollingIfNeeded()
pushRegistry.refresh(true) pushRegistry.refresh(true)
Intent(this, HomeActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK when {
putExtra(HomeActivity.FROM_ONBOARDING, true) prefs.getHasViewedSeed() && !prefs.getConfigurationMessageSynced() -> start<LoadingActivity>()
}.also(::startActivity) prefs.getProfileName() != null -> startHomeActivity()
else -> startHomeActivity()
}
} }
} }

View File

@ -22,8 +22,10 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
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.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
import org.thoughtcrime.securesms.home.startHomeActivity
import org.thoughtcrime.securesms.onboarding.messagenotifications.startMessageNotificationsActivity import org.thoughtcrime.securesms.onboarding.messagenotifications.startMessageNotificationsActivity
import org.thoughtcrime.securesms.ui.PreviewTheme import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.base import org.thoughtcrime.securesms.ui.base
@ -35,17 +37,18 @@ import org.thoughtcrime.securesms.ui.setComposeContent
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import javax.inject.Inject import javax.inject.Inject
private const val EXTRA_PICK_NEW_NAME = "extra_pick_new_name" private const val EXTRA_FAILED_TO_LOAD = "extra_failed_to_load"
@AndroidEntryPoint @AndroidEntryPoint
class PickDisplayNameActivity : BaseActionBarActivity() { class PickDisplayNameActivity : BaseActionBarActivity() {
@Inject @Inject lateinit var viewModelFactory: PickDisplayNameViewModel.AssistedFactory
lateinit var viewModelFactory: PickDisplayNameViewModel.AssistedFactory @Inject lateinit var prefs: TextSecurePreferences
val failedToLoad get() = intent.getBooleanExtra(EXTRA_FAILED_TO_LOAD, false)
private val viewModel: PickDisplayNameViewModel by viewModels { private val viewModel: PickDisplayNameViewModel by viewModels {
val pickNewName = intent.getBooleanExtra(EXTRA_PICK_NEW_NAME, false) viewModelFactory.create(failedToLoad)
viewModelFactory.create(pickNewName)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -54,9 +57,11 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
setComposeContent { DisplayNameScreen(viewModel) } setComposeContent { DisplayNameScreen(viewModel) }
if (!failedToLoad) prefs.setHasViewedSeed(false)
lifecycleScope.launch { lifecycleScope.launch {
viewModel.eventFlow.collect { viewModel.eventFlow.collect {
startMessageNotificationsActivity() if (failedToLoad) startHomeActivity() else startMessageNotificationsActivity()
} }
} }
} }
@ -119,7 +124,7 @@ fun Context.startPickDisplayNameActivity(failedToLoad: Boolean = false, flags: I
ApplicationContext.getInstance(this).newAccount = !failedToLoad ApplicationContext.getInstance(this).newAccount = !failedToLoad
Intent(this, PickDisplayNameActivity::class.java) Intent(this, PickDisplayNameActivity::class.java)
.apply { putExtra(EXTRA_PICK_NEW_NAME, failedToLoad) } .apply { putExtra(EXTRA_FAILED_TO_LOAD, failedToLoad) }
.also { it.flags = flags } .also { it.flags = flags }
.also(::startActivity) .also(::startActivity)
} }

View File

@ -65,7 +65,6 @@ class PickDisplayNameViewModel(
prefs.setLocalRegistrationId(registrationID) prefs.setLocalRegistrationId(registrationID)
prefs.setLocalNumber(userHexEncodedPublicKey) prefs.setLocalNumber(userHexEncodedPublicKey)
prefs.setRestorationTime(0) prefs.setRestorationTime(0)
prefs.setHasViewedSeed(false)
viewModelScope.launch { event.send(Event.DONE) } viewModelScope.launch { event.send(Event.DONE) }
} }

View File

@ -0,0 +1,185 @@
package org.thoughtcrime.securesms.onboarding.recoverypassword
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
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.QrImage
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.extraSmallMonospace
import org.thoughtcrime.securesms.ui.h8
@Preview
@Composable
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")
}
}
@Composable
fun RecoveryPasswordScreen(
seed: String = "",
copySeed:() -> Unit = {},
onHide:() -> Unit = {}
) {
Column(
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.marginExtraSmall),
modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password)
.verticalScroll(rememberScrollState())
.padding(bottom = LocalDimensions.current.marginExtraSmall)
) {
RecoveryPasswordCell(seed, copySeed)
HideRecoveryPasswordCell(onHide)
}
}
@Composable
fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) {
var showQr by remember {
mutableStateOf(false)
}
CellWithPaddingAndMargin {
Column {
Row {
Text(
stringResource(R.string.sessionRecoveryPassword),
style = h8
)
Spacer(Modifier.width(LocalDimensions.current.itemSpacingExtraSmall))
SessionShieldIcon()
}
Spacer(modifier = Modifier.height(LocalDimensions.current.marginTiny))
Text(
stringResource(R.string.recoveryPasswordDescription),
style = base
)
AnimatedVisibility(!showQr) {
RecoveryPassword(seed)
}
AnimatedVisibility(
showQr,
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
QrImage(
seed,
modifier = Modifier
.padding(vertical = LocalDimensions.current.marginSmall)
.contentDescription(R.string.AccessibilityId_qr_code),
icon = R.drawable.session_shield
)
}
AnimatedVisibility(!showQr) {
Row(horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.marginMedium)) {
SlimOutlineCopyButton(
Modifier.weight(1f),
onClick = copySeed
)
SlimOutlineButton(
stringResource(R.string.qrView),
Modifier.weight(1f),
) { showQr = !showQr }
}
}
AnimatedVisibility(showQr, modifier = Modifier.align(Alignment.CenterHorizontally)) {
SlimOutlineButton(
stringResource(R.string.recoveryPasswordView),
onClick = { showQr = !showQr }
)
}
}
}
}
@Composable
private fun RecoveryPassword(seed: String) {
Text(
seed,
modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password_container)
.padding(vertical = LocalDimensions.current.marginSmall)
.border(
width = 1.dp,
color = LocalColors.current.borders,
shape = RoundedCornerShape(11.dp)
)
.padding(LocalDimensions.current.marginSmall),
textAlign = TextAlign.Center,
style = extraSmallMonospace,
color = LocalColors.current.run { if (isLight) text else primary },
)
}
@Composable
private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
CellWithPaddingAndMargin {
Row {
Column(
Modifier.weight(1f)
) {
Text(
stringResource(R.string.recoveryPasswordHideRecoveryPassword),
style = h8
)
Text(
stringResource(R.string.recoveryPasswordHideRecoveryPasswordDescription),
style = base
)
}
Spacer(modifier = Modifier.width(LocalDimensions.current.marginExtraExtraSmall))
SlimOutlineButton(
text = stringResource(R.string.hide),
modifier = Modifier
.wrapContentWidth()
.align(Alignment.CenterVertically)
.contentDescription(R.string.AccessibilityId_hide_recovery_password_button),
color = LocalColors.current.danger,
onClick = onHide
)
}
}
}

View File

@ -2,49 +2,9 @@ package org.thoughtcrime.securesms.onboarding.recoverypassword
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
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
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.color.Colors
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.SessionColorsParameterProvider
import org.thoughtcrime.securesms.ui.SessionShieldIcon
import org.thoughtcrime.securesms.ui.base
import org.thoughtcrime.securesms.ui.components.QrImage
import org.thoughtcrime.securesms.ui.components.SlimOutlineButton
import org.thoughtcrime.securesms.ui.components.SlimOutlineCopyButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.extraSmallMonospace
import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.setComposeContent import org.thoughtcrime.securesms.ui.setComposeContent
class RecoveryPasswordActivity : BaseActionBarActivity() { class RecoveryPasswordActivity : BaseActionBarActivity() {
@ -87,144 +47,3 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
} }
} }
} }
@Preview
@Composable
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")
}
}
@Composable
fun RecoveryPasswordScreen(
seed: String = "",
copySeed:() -> Unit = {},
onHide:() -> Unit = {}
) {
Column(
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.marginExtraSmall),
modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password)
.verticalScroll(rememberScrollState())
.padding(bottom = LocalDimensions.current.marginExtraSmall)
) {
RecoveryPasswordCell(seed, copySeed)
HideRecoveryPasswordCell(onHide)
}
}
@Composable
fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) {
var showQr by remember {
mutableStateOf(false)
}
CellWithPaddingAndMargin {
Column {
Row {
Text(
stringResource(R.string.sessionRecoveryPassword),
style = h8
)
Spacer(Modifier.width(LocalDimensions.current.itemSpacingExtraSmall))
SessionShieldIcon()
}
Spacer(modifier = Modifier.height(LocalDimensions.current.marginTiny))
Text(
stringResource(R.string.recoveryPasswordDescription),
style = base
)
AnimatedVisibility(!showQr) {
RecoveryPassword(seed)
}
AnimatedVisibility(
showQr,
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
QrImage(
seed,
modifier = Modifier
.padding(vertical = LocalDimensions.current.marginSmall)
.contentDescription(R.string.AccessibilityId_qr_code),
icon = R.drawable.session_shield
)
}
AnimatedVisibility(!showQr) {
Row(horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.marginMedium)) {
SlimOutlineCopyButton(
Modifier.weight(1f),
onClick = copySeed
)
SlimOutlineButton(
stringResource(R.string.qrView),
Modifier.weight(1f),
) { showQr = !showQr }
}
}
AnimatedVisibility(showQr, modifier = Modifier.align(Alignment.CenterHorizontally)) {
SlimOutlineButton(
stringResource(R.string.recoveryPasswordView),
onClick = { showQr = !showQr }
)
}
}
}
}
@Composable
private fun RecoveryPassword(seed: String) {
Text(
seed,
modifier = Modifier
.contentDescription(R.string.AccessibilityId_recovery_password_container)
.padding(vertical = LocalDimensions.current.marginSmall)
.border(
width = 1.dp,
color = LocalColors.current.borders,
shape = RoundedCornerShape(11.dp)
)
.padding(LocalDimensions.current.marginSmall),
textAlign = TextAlign.Center,
style = extraSmallMonospace,
color = LocalColors.current.run { if (isLight) text else primary },
)
}
@Composable
private fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
CellWithPaddingAndMargin {
Row {
Column(
Modifier.weight(1f)
) {
Text(
stringResource(R.string.recoveryPasswordHideRecoveryPassword),
style = h8
)
Text(
stringResource(R.string.recoveryPasswordHideRecoveryPasswordDescription),
style = base
)
}
Spacer(modifier = Modifier.width(LocalDimensions.current.marginExtraExtraSmall))
SlimOutlineButton(
text = stringResource(R.string.hide),
modifier = Modifier
.wrapContentWidth()
.align(Alignment.CenterVertically)
.contentDescription(R.string.AccessibilityId_hide_recovery_password_button),
color = LocalColors.current.danger,
onClick = onHide
)
}
}
}

View File

@ -19,10 +19,10 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.YELLOW_A
*/ */
@Composable @Composable
fun TextSecurePreferences.colors(): Colors = lightDarkColors().colors() fun TextSecurePreferences.colors(): Colors = lightDarkColors().colors()
fun TextSecurePreferences.lightDarkColors() = LightDarkColors(isClassic(), isLight(), getFollowSystemSettings(), primaryColor()) private fun TextSecurePreferences.lightDarkColors() = LightDarkColors(isClassic(), isLight(), getFollowSystemSettings(), primaryColor())
fun TextSecurePreferences.isLight(): Boolean = getThemeStyle() in setOf(CLASSIC_LIGHT, OCEAN_LIGHT) private fun TextSecurePreferences.isLight(): Boolean = getThemeStyle() in setOf(CLASSIC_LIGHT, OCEAN_LIGHT)
fun TextSecurePreferences.isClassic(): Boolean = getThemeStyle() in setOf(CLASSIC_DARK, CLASSIC_LIGHT) private fun TextSecurePreferences.isClassic(): Boolean = getThemeStyle() in setOf(CLASSIC_DARK, CLASSIC_LIGHT)
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) { private fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
GREEN_ACCENT -> primaryGreen GREEN_ACCENT -> primaryGreen
BLUE_ACCENT -> primaryBlue BLUE_ACCENT -> primaryBlue
PURPLE_ACCENT -> primaryPurple PURPLE_ACCENT -> primaryPurple