mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
desktop: onboarding improvements (#5294)
* consistent space to bottom on future of messaging * consistent button suze on server operators * updated setup database passphrase screen * ability to cancel random passphrase * reduce conditions padding to header * show scrollbar in desktop * EOLs * EOL * fix random passphrase param when deleting database and recreating new one --------- Co-authored-by: Evgeny <evgeny@poberezkin.com> Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
package chat.simplex.common.views.usersettings.networkAndServers
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
actual fun ConditionsBox(modifier: Modifier, scrollState: ScrollState, content: @Composable() (BoxScope.() -> Unit)){
|
||||
Box(
|
||||
modifier = modifier
|
||||
.verticalScroll(scrollState)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
+1
@@ -525,6 +525,7 @@ fun deleteChatDatabaseFilesAndState() {
|
||||
wallpapersDir.mkdirs()
|
||||
DatabaseUtils.ksDatabasePassword.remove()
|
||||
appPrefs.newDatabaseInitialized.set(false)
|
||||
chatModel.desktopOnboardingRandomPassword.value = false
|
||||
controller.appPrefs.storeDBPassphrase.set(true)
|
||||
controller.ctrl = null
|
||||
|
||||
|
||||
+4
-4
@@ -169,7 +169,7 @@ private fun ReviewConditionsButton(
|
||||
modalManager: ModalManager
|
||||
) {
|
||||
OnboardingActionButton(
|
||||
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
|
||||
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.operator_review_conditions,
|
||||
onboarding = null,
|
||||
enabled = enabled,
|
||||
@@ -184,7 +184,7 @@ private fun ReviewConditionsButton(
|
||||
@Composable
|
||||
private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOperators: State<List<ServerOperator>>, selectedOperatorIds: State<Set<Long>>, close: () -> Unit) {
|
||||
OnboardingActionButton(
|
||||
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
|
||||
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.onboarding_network_operators_update,
|
||||
onboarding = null,
|
||||
enabled = enabled,
|
||||
@@ -206,7 +206,7 @@ private fun SetOperatorsButton(enabled: Boolean, onboarding: Boolean, serverOper
|
||||
@Composable
|
||||
private fun ContinueButton(enabled: Boolean, onboarding: Boolean, close: () -> Unit) {
|
||||
OnboardingActionButton(
|
||||
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
|
||||
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.onboarding_network_operators_continue,
|
||||
onboarding = null,
|
||||
enabled = enabled,
|
||||
@@ -235,7 +235,7 @@ private fun ReviewConditionsView(
|
||||
val operatorsWithConditionsAccepted = remember { chatModel.conditions.value.serverOperators.filter { it.conditionsAcceptance.conditionsAccepted } }
|
||||
val acceptForOperators = remember { selectedOperators.value.filter { !it.conditionsAcceptance.conditionsAccepted } }
|
||||
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) {
|
||||
AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false)
|
||||
AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), withPadding = false, enableAlphaChanges = false, bottomPadding = DEFAULT_PADDING)
|
||||
if (operatorsWithConditionsAccepted.isNotEmpty()) {
|
||||
ReadableText(MR.strings.operator_conditions_accepted_for_some, args = operatorsWithConditionsAccepted.joinToString(", ") { it.legalName_ })
|
||||
ReadableText(MR.strings.operator_same_conditions_will_apply_to_operators, args = acceptForOperators.joinToString(", ") { it.legalName_ })
|
||||
|
||||
+5
-3
@@ -1,6 +1,7 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
@@ -23,7 +24,7 @@ import dev.icerock.moko.resources.StringResource
|
||||
|
||||
@Composable
|
||||
fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) {
|
||||
ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) {
|
||||
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||
AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false)
|
||||
ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues)
|
||||
ReadableText(MR.strings.only_client_devices_store_contacts_groups_e2e_encrypted_messages)
|
||||
@@ -35,11 +36,12 @@ fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>?
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
|
||||
if (onboardingStage != null) {
|
||||
Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING), contentAlignment = Alignment.Center) {
|
||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.fullscreen.closeModal() })
|
||||
// Reserve space
|
||||
TextButtonBelowOnboardingButton("", null)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-49
@@ -1,6 +1,5 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import SectionTextFooter
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material.*
|
||||
@@ -12,7 +11,6 @@ import androidx.compose.ui.focus.*
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -106,14 +104,31 @@ private fun SetupDatabasePassphraseLayout(
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||
ModalView({}, showClose = false) {
|
||||
ColumnWithScrollBar(
|
||||
Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(bottom = DEFAULT_PADDING * 2),
|
||||
Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AppBarTitle(stringResource(MR.strings.setup_database_passphrase))
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
val onClickUpdate = {
|
||||
// Don't do things concurrently. Shouldn't be here concurrently, just in case
|
||||
if (!progressIndicator.value) {
|
||||
encryptDatabaseAlert(onConfirmEncrypt)
|
||||
}
|
||||
}
|
||||
val disabled = currentKey.value == newKey.value ||
|
||||
newKey.value != confirmNewKey.value ||
|
||||
newKey.value.isEmpty() ||
|
||||
!validKey(currentKey.value) ||
|
||||
!validKey(newKey.value) ||
|
||||
progressIndicator.value
|
||||
|
||||
Column(Modifier.width(600.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)
|
||||
ReadableText(MR.strings.you_have_to_enter_passphrase_every_time, TextAlign.Center, padding = PaddingValues(), style = textStyle )
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ReadableText(MR.strings.impossible_to_recover_passphrase, TextAlign.Center, padding = PaddingValues(), style = textStyle)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
|
||||
Column(Modifier.width(600.dp)) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -138,18 +153,6 @@ private fun SetupDatabasePassphraseLayout(
|
||||
isValid = ::validKey,
|
||||
keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
|
||||
)
|
||||
val onClickUpdate = {
|
||||
// Don't do things concurrently. Shouldn't be here concurrently, just in case
|
||||
if (!progressIndicator.value) {
|
||||
encryptDatabaseAlert(onConfirmEncrypt)
|
||||
}
|
||||
}
|
||||
val disabled = currentKey.value == newKey.value ||
|
||||
newKey.value != confirmNewKey.value ||
|
||||
newKey.value.isEmpty() ||
|
||||
!validKey(currentKey.value) ||
|
||||
!validKey(newKey.value) ||
|
||||
progressIndicator.value
|
||||
|
||||
PassphraseField(
|
||||
confirmNewKey,
|
||||
@@ -167,21 +170,17 @@ private fun SetupDatabasePassphraseLayout(
|
||||
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
|
||||
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }),
|
||||
)
|
||||
|
||||
Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) {
|
||||
SetPassphraseButton(disabled, onClickUpdate)
|
||||
}
|
||||
|
||||
Column {
|
||||
SectionTextFooter(generalGetString(MR.strings.you_have_to_enter_passphrase_every_time))
|
||||
SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
SkipButton(progressIndicator.value) {
|
||||
chatModel.desktopOnboardingRandomPassword.value = true
|
||||
nextStep()
|
||||
|
||||
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
SetPassphraseButton(disabled, onClickUpdate)
|
||||
SkipButton(progressIndicator.value) {
|
||||
randomPassphraseAlert {
|
||||
chatModel.desktopOnboardingRandomPassword.value = true
|
||||
nextStep()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,30 +189,18 @@ private fun SetupDatabasePassphraseLayout(
|
||||
|
||||
@Composable
|
||||
private fun SetPassphraseButton(disabled: Boolean, onClick: () -> Unit) {
|
||||
SimpleButtonIconEnded(
|
||||
stringResource(MR.strings.set_database_passphrase),
|
||||
painterResource(MR.images.ic_check),
|
||||
style = MaterialTheme.typography.h2,
|
||||
color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary,
|
||||
disabled = disabled,
|
||||
click = onClick
|
||||
OnboardingActionButton(
|
||||
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.set_database_passphrase,
|
||||
onboarding = null,
|
||||
onclick = onClick,
|
||||
enabled = !disabled
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SkipButton(disabled: Boolean, onClick: () -> Unit) {
|
||||
SimpleButtonIconEnded(stringResource(MR.strings.use_random_passphrase), painterResource(MR.images.ic_chevron_right), color =
|
||||
if (disabled) MaterialTheme.colors.secondary else WarningOrange, disabled = disabled, click = onClick)
|
||||
Text(
|
||||
stringResource(MR.strings.you_can_change_it_later),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING * 3)
|
||||
.padding(top = DEFAULT_PADDING, bottom = DEFAULT_PADDING - 5.dp),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
color = MaterialTheme.colors.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
TextButtonBelowOnboardingButton(stringResource(MR.strings.use_random_passphrase), onClick = if (disabled) null else onClick)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -238,3 +225,12 @@ private suspend fun startChat(key: String?) {
|
||||
m.chatDbChanged.value = false
|
||||
m.chatRunning.value = true
|
||||
}
|
||||
|
||||
private fun randomPassphraseAlert(onConfirm: () -> Unit) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.use_random_passphrase),
|
||||
text = generalGetString(MR.strings.you_can_change_it_later),
|
||||
confirmText = generalGetString(MR.strings.ok),
|
||||
onConfirm = onConfirm,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -734,7 +734,7 @@ fun UsageConditionsView(
|
||||
}
|
||||
|
||||
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) {
|
||||
AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false)
|
||||
AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false, bottomPadding = DEFAULT_PADDING)
|
||||
when (val conditionsAction = chatModel.conditions.value.conditionsAction) {
|
||||
is UsageConditionsAction.Review -> {
|
||||
if (conditionsAction.operators.isNotEmpty()) {
|
||||
|
||||
+15
-12
@@ -14,6 +14,7 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.platform.UriHandler
|
||||
@@ -615,7 +616,6 @@ fun ConditionsTextView(
|
||||
val failedToLoad = remember { mutableStateOf(false) }
|
||||
val defaultConditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/stable/PRIVACY.md"
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// can show conditions when animation between modals finishes to prevent glitches
|
||||
val canShowConditionsAt = remember { System.currentTimeMillis() + 300 }
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -645,18 +645,18 @@ fun ConditionsTextView(
|
||||
|
||||
if (conditionsText != null) {
|
||||
val scrollState = rememberScrollState()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.border(border = BorderStroke(1.dp, CurrentColors.value.colors.secondary.copy(alpha = 0.6f)), shape = RoundedCornerShape(12.dp))
|
||||
.verticalScroll(scrollState)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
val parentUriHandler = LocalUriHandler.current
|
||||
CompositionLocalProvider(LocalUriHandler provides remember { internalUriHandler(parentUriHandler) }) {
|
||||
ConditionsMarkdown(conditionsText)
|
||||
}
|
||||
ConditionsBox(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.border(border = BorderStroke(1.dp, CurrentColors.value.colors.secondary.copy(alpha = 0.6f)), shape = RoundedCornerShape(12.dp))
|
||||
.clip(shape = RoundedCornerShape(12.dp)),
|
||||
scrollState
|
||||
) {
|
||||
val parentUriHandler = LocalUriHandler.current
|
||||
CompositionLocalProvider(LocalUriHandler provides remember { internalUriHandler(parentUriHandler) }) {
|
||||
ConditionsMarkdown(conditionsText)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val conditionsLink = "https://github.com/simplex-chat/simplex-chat/blob/${usageConditions.conditionsCommit}/PRIVACY.md"
|
||||
ConditionsLinkView(conditionsLink)
|
||||
@@ -668,6 +668,9 @@ fun ConditionsTextView(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
expect fun ConditionsBox(modifier: Modifier, scrollState: ScrollState, content: @Composable() (BoxScope.() -> Unit))
|
||||
|
||||
@Composable
|
||||
private fun ConditionsMarkdown(text: String) {
|
||||
Markdown(text,
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package chat.simplex.common.views.usersettings.networkAndServers
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.platform.DesktopScrollBar
|
||||
import chat.simplex.common.views.helpers.detectCursorMove
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@Composable
|
||||
actual fun ConditionsBox(modifier: Modifier, scrollState: ScrollState, content: @Composable() (BoxScope.() -> Unit)) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollBarAlpha = remember { Animatable(0f) }
|
||||
val scrollJob: MutableState<Job> = remember { mutableStateOf(Job()) }
|
||||
val scrollBarDraggingState = remember { mutableStateOf(false) }
|
||||
val scrollModifier = remember {
|
||||
Modifier
|
||||
.pointerInput(Unit) {
|
||||
detectCursorMove {
|
||||
scope.launch {
|
||||
scrollBarAlpha.animateTo(1f)
|
||||
}
|
||||
scrollJob.value.cancel()
|
||||
scrollJob.value = scope.launch {
|
||||
delay(1000L)
|
||||
scrollBarAlpha.animateTo(0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(8.dp)
|
||||
.then(scrollModifier)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) {
|
||||
DesktopScrollBar(rememberScrollbarAdapter(scrollState), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, false, scrollBarDraggingState)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user