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
// the pollers
if (TextSecurePreferences.getLocalNumber(this) == null || !TextSecurePreferences.hasSeenWelcomeScreen(this)) {
if (textSecurePreferences.getLocalNumber() == null || !textSecurePreferences.hasSeenWelcomeScreen()) {
return;
}

View File

@ -4,6 +4,7 @@ import android.Manifest
import android.app.NotificationManager
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
@ -789,3 +790,10 @@ private fun EmptyView(newAccount: Boolean) {
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.PreviewParameter
import androidx.compose.ui.unit.dp
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import network.loki.messenger.R
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.showOpenUrlDialog
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.color.Colors
import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.ui.components.BorderlessHtmlButton
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.util.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.util.start
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
private data class TextData(
@StringRes val stringId: Int,
val isOutgoing: Boolean = false
)
@AndroidEntryPoint
class LandingActivity: BaseActionBarActivity() {
private val MESSAGES = listOf(
TextData(R.string.onboardingBubbleWelcomeToSession),
TextData(R.string.onboardingBubbleSessionIsEngineered, isOutgoing = true),
TextData(R.string.onboardingBubbleNoPhoneNumber),
TextData(R.string.onboardingBubbleCreatingAnAccountIsEasy, isOutgoing = true)
)
class LandingActivity : BaseActionBarActivity() {
@Inject lateinit var prefs: TextSecurePreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -85,7 +77,14 @@ class LandingActivity : BaseActionBarActivity() {
setUpActionBarSessionLogo(true)
setComposeContent { LandingScreen() }
setComposeContent {
LandingScreen(
createAccount = {
prefs.setHasViewedSeed(false)
startPickDisplayNameActivity()
}
)
}
IdentityKeyUtil.generateIdentityKeyPair(this)
TextSecurePreferences.setPasswordDisabled(this, true)
@ -99,12 +98,12 @@ class LandingActivity : BaseActionBarActivity() {
@PreviewParameter(SessionColorsParameterProvider::class) colors: Colors
) {
PreviewTheme(colors) {
LandingScreen()
LandingScreen {}
}
}
@Composable
private fun LandingScreen() {
private fun LandingScreen(createAccount: () -> Unit) {
var count by remember { mutableStateOf(0) }
val listState = rememberLazyListState()
@ -159,7 +158,7 @@ class LandingActivity : BaseActionBarActivity() {
.fillMaxWidth()
.align(Alignment.CenterHorizontally)
.contentDescription(R.string.AccessibilityId_create_account_button),
onClick = ::startPickDisplayNameActivity
onClick = createAccount
)
Spacer(modifier = Modifier.height(LocalDimensions.current.itemSpacingExtraSmall))
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 org.session.libsession.utilities.TextSecurePreferences
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.ui.LocalDimensions
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)
class LinkDeviceActivity : BaseActionBarActivity() {
@Inject
lateinit var prefs: TextSecurePreferences
@Inject lateinit var prefs: TextSecurePreferences
@Inject lateinit var loadingManager: LoadingManager
val viewModel: LinkDeviceViewModel by viewModels()
@ -67,8 +67,8 @@ class LinkDeviceActivity : BaseActionBarActivity() {
lifecycleScope.launch {
viewModel.eventFlow.collect {
loadingManager.load(it.mnemonic)
startMessageNotificationsActivity()
startLoadingActivity(it.mnemonic)
finish()
}
}

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.onboarding.loading
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
@ -13,14 +12,12 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
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.ui.setComposeContent
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
import javax.inject.Inject
private const val EXTRA_MNEMONIC = "mnemonic"
@AndroidEntryPoint
class LoadingActivity: BaseActionBarActivity() {
@ -40,11 +37,12 @@ class LoadingActivity: BaseActionBarActivity() {
private fun register(skipped: Boolean) {
prefs.setLastConfigurationSyncTime(System.currentTimeMillis())
val flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
when {
skipped -> startPickDisplayNameActivity(true, flags)
else -> startMessageNotificationsActivity(flags)
skipped -> startPickDisplayNameActivity(
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)
viewModel.restore(application, intent.getByteArrayExtra(EXTRA_MNEMONIC)!!)
lifecycleScope.launch {
viewModel.eventFlow.collect {
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
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.timeout
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.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 kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@ -31,66 +26,43 @@ data class State(val duration: Duration)
private val ANIMATE_TO_DONE_TIME = 500.milliseconds
private val IDLE_DONE_TIME = 1.seconds
private val TIMEOUT_TIME = 15.seconds
private val TOTAL_ANIMATION_TIME = TIMEOUT_TIME - IDLE_DONE_TIME
@OptIn(FlowPreview::class)
@HiltViewModel
class LoadingViewModel @Inject constructor(
private val configFactory: ConfigFactory,
private val prefs: TextSecurePreferences,
) : ViewModel() {
val prefs: TextSecurePreferences
): ViewModel() {
private val state = MutableStateFlow(State(TOTAL_ANIMATION_TIME))
private val state = MutableStateFlow(State(TIMEOUT_TIME))
val stateFlow = state.asStateFlow()
private val event = Channel<Event>()
val eventFlow = event.receiveAsFlow()
private var restoreJob: Job? = null
internal val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage
fun restore(context: Context, 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 = viewModelScope.launch(Dispatchers.IO) {
// 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 = 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)
init {
viewModelScope.launch(Dispatchers.IO) {
try {
TextSecurePreferences.events
.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }
.timeout(TIMEOUT_TIME)
.onStart { emit(TextSecurePreferences.CONFIGURATION_SYNCED) }
.collectLatest {
if (prefs.getConfigurationMessageSynced()) onSuccess()
}
} catch (e: Exception) {
onFail()
}
}
}
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()
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)
}
}

View File

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

View File

@ -22,8 +22,10 @@ import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import network.loki.messenger.R
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.onboarding.messagenotifications.startMessageNotificationsActivity
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.base
@ -35,17 +37,18 @@ import org.thoughtcrime.securesms.ui.setComposeContent
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
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
class PickDisplayNameActivity : BaseActionBarActivity() {
@Inject
lateinit var viewModelFactory: PickDisplayNameViewModel.AssistedFactory
@Inject 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 {
val pickNewName = intent.getBooleanExtra(EXTRA_PICK_NEW_NAME, false)
viewModelFactory.create(pickNewName)
viewModelFactory.create(failedToLoad)
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -54,9 +57,11 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
setComposeContent { DisplayNameScreen(viewModel) }
if (!failedToLoad) prefs.setHasViewedSeed(false)
lifecycleScope.launch {
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
Intent(this, PickDisplayNameActivity::class.java)
.apply { putExtra(EXTRA_PICK_NEW_NAME, failedToLoad) }
.apply { putExtra(EXTRA_FAILED_TO_LOAD, failedToLoad) }
.also { it.flags = flags }
.also(::startActivity)
}

View File

@ -65,7 +65,6 @@ class PickDisplayNameViewModel(
prefs.setLocalRegistrationId(registrationID)
prefs.setLocalNumber(userHexEncodedPublicKey)
prefs.setRestorationTime(0)
prefs.setHasViewedSeed(false)
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 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 org.thoughtcrime.securesms.BaseActionBarActivity
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
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
fun TextSecurePreferences.colors(): Colors = lightDarkColors().colors()
fun TextSecurePreferences.lightDarkColors() = LightDarkColors(isClassic(), isLight(), getFollowSystemSettings(), primaryColor())
fun TextSecurePreferences.isLight(): Boolean = getThemeStyle() in setOf(CLASSIC_LIGHT, OCEAN_LIGHT)
fun TextSecurePreferences.isClassic(): Boolean = getThemeStyle() in setOf(CLASSIC_DARK, CLASSIC_LIGHT)
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
private fun TextSecurePreferences.lightDarkColors() = LightDarkColors(isClassic(), isLight(), getFollowSystemSettings(), primaryColor())
private fun TextSecurePreferences.isLight(): Boolean = getThemeStyle() in setOf(CLASSIC_LIGHT, OCEAN_LIGHT)
private fun TextSecurePreferences.isClassic(): Boolean = getThemeStyle() in setOf(CLASSIC_DARK, CLASSIC_LIGHT)
private fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
GREEN_ACCENT -> primaryGreen
BLUE_ACCENT -> primaryBlue
PURPLE_ACCENT -> primaryPurple