android, desktop: fix onboarding layout (#4734)

This commit is contained in:
Stanislav Dmitrenko
2024-08-21 22:03:49 +01:00
committed by GitHub
parent 94218a1a7e
commit 519dd9e219
4 changed files with 220 additions and 194 deletions
@@ -111,53 +111,59 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
val scrollState = rememberScrollState()
val keyboardState by getKeyboardState()
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Column(
modifier = Modifier
.fillMaxSize()
.themedBackground(),
horizontalAlignment = Alignment.CenterHorizontally
) {
CloseSheetBar(close = {
if (chatModel.users.none { !it.user.hidden }) {
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
} else {
close()
}
})
BackHandler(onBack = {
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
})
ColumnWithScrollBar(
modifier = if (appPlatform.isAndroid) Modifier.fillMaxSize() else Modifier.widthIn(max = 600.dp).fillMaxHeight(),
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Column(
modifier = Modifier
.fillMaxSize()
.themedBackground(),
horizontalAlignment = Alignment.CenterHorizontally
) {
val displayName = rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING)
ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester)
Spacer(Modifier.height(DEFAULT_PADDING))
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_profile_button,
onboarding = null,
enabled = canCreateProfile(displayName.value),
onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) }
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
CloseSheetBar(close = {
if (chatModel.users.none { !it.user.hidden }) {
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
} else {
close()
}
})
BackHandler(onBack = {
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
})
LaunchedEffect(Unit) {
delay(300)
focusRequester.requestFocus()
ColumnWithScrollBar(
modifier = Modifier.fillMaxSize()
) {
val displayName = rememberSaveable { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) {
Box(Modifier.align(Alignment.CenterHorizontally)) {
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING, withPadding = false)
}
ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester)
Spacer(Modifier.height(DEFAULT_PADDING))
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Start, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_profile_button,
onboarding = null,
enabled = canCreateProfile(displayName.value),
onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) }
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
LaunchedEffect(Unit) {
delay(300)
focusRequester.requestFocus()
}
}
}
LaunchedEffect(Unit) {
@@ -76,56 +76,62 @@ private fun CreateSimpleXAddressLayout(
createAddress: () -> Unit,
nextStep: () -> Unit,
) {
ColumnWithScrollBar(
Modifier
.fillMaxSize()
.themedBackground(),
horizontalAlignment = Alignment.CenterHorizontally,
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
CloseSheetBar(showClose = false, close = {})
AppBarTitle(stringResource(MR.strings.simplex_address))
ModalView({}, showClose = false) {
ColumnWithScrollBar(
Modifier
.fillMaxSize()
.themedBackground(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarTitle(stringResource(MR.strings.simplex_address))
Spacer(Modifier.weight(1f))
Spacer(Modifier.weight(1f))
if (userAddress != null) {
SimpleXLinkQRCode(userAddress.connReqContact)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
Row {
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
Spacer(Modifier.width(DEFAULT_PADDING * 2))
ShareViaEmailButton { sendEmail(userAddress) }
}
Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.continue_to_next_step,
onboarding = null,
onclick = nextStep
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
} else {
Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) {
Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White)
}
Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f))
Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold)
TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings))
Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f))
if (userAddress != null) {
SimpleXLinkQRCode(userAddress.connReqContact)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
Row {
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
Spacer(Modifier.width(DEFAULT_PADDING * 2))
ShareViaEmailButton { sendEmail(userAddress) }
}
Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.continue_to_next_step,
onboarding = null,
onclick = nextStep
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
} else {
Button(createAddress, Modifier, shape = CircleShape, contentPadding = PaddingValues()) {
Icon(painterResource(MR.images.ic_mail_filled), null, Modifier.size(100.dp).background(MaterialTheme.colors.primary, CircleShape).padding(25.dp), tint = Color.White)
}
Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f))
Text(stringResource(MR.strings.create_simplex_address), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold)
TextBelowButton(stringResource(MR.strings.you_can_make_address_visible_via_settings))
Spacer(Modifier.height(DEFAULT_PADDING))
Spacer(Modifier.weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_address_button,
onboarding = null,
onclick = createAddress
)
TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep)
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
labelId = MR.strings.create_address_button,
onboarding = null,
onclick = createAddress
)
TextButtonBelowOnboardingButton(stringResource(MR.strings.dont_create_address), nextStep)
}
}
}
}
}
@@ -25,39 +25,47 @@ import chat.simplex.res.MR
@Composable
fun SetNotificationsMode(m: ChatModel) {
ColumnWithScrollBar(
modifier = Modifier
.fillMaxSize()
.themedBackground()
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
CloseSheetBar(showClose = false, close = {})
AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title))
val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) }
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
Spacer(Modifier.height(DEFAULT_PADDING * 2f))
SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) {
currentMode.value = NotificationsMode.OFF
}
SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)){
currentMode.value = NotificationsMode.PERIODIC
}
SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)){
currentMode.value = NotificationsMode.SERVICE
}
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.use_chat,
onboarding = OnboardingStage.OnboardingComplete,
onclick = {
changeNotificationsMode(currentMode.value, m)
ModalView({}, showClose = false) {
ColumnWithScrollBar(
modifier = Modifier
.fillMaxSize()
.themedBackground()
) {
Box(Modifier.align(Alignment.CenterHorizontally)) {
AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title))
}
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) }
Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) {
Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
Spacer(Modifier.height(DEFAULT_PADDING * 2f))
SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) {
currentMode.value = NotificationsMode.OFF
}
SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)) {
currentMode.value = NotificationsMode.PERIODIC
}
SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)) {
currentMode.value = NotificationsMode.SERVICE
}
}
Spacer(Modifier.fillMaxHeight().weight(1f))
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
OnboardingActionButton(
modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_PADDING * 2).fillMaxWidth() else Modifier,
labelId = MR.strings.use_chat,
onboarding = OnboardingStage.OnboardingComplete,
onclick = {
changeNotificationsMode(currentMode.value, m)
}
)
// Reserve space
TextButtonBelowOnboardingButton("", null)
}
}
}
}
SetNotificationsModeAdditions()
@@ -104,84 +104,90 @@ private fun SetupDatabasePassphraseLayout(
onConfirmEncrypt: () -> Unit,
nextStep: () -> Unit,
) {
ColumnWithScrollBar(
Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2),
horizontalAlignment = Alignment.CenterHorizontally,
val handler = remember { AppBarHandler() }
CompositionLocalProvider(
LocalAppBarHandler provides handler
) {
CloseSheetBar(showClose = false, close = {})
AppBarTitle(stringResource(MR.strings.setup_database_passphrase))
ModalView({}, showClose = false) {
ColumnWithScrollBar(
Modifier.fillMaxSize().themedBackground().padding(bottom = DEFAULT_PADDING * 2),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AppBarTitle(stringResource(MR.strings.setup_database_passphrase))
Spacer(Modifier.weight(1f))
Spacer(Modifier.weight(1f))
Column(Modifier.width(600.dp)) {
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
delay(100L)
focusRequester.requestFocus()
}
PassphraseField(
newKey,
generalGetString(MR.strings.new_passphrase),
modifier = Modifier
.padding(horizontal = DEFAULT_PADDING)
.focusRequester(focusRequester)
.onPreviewKeyEvent {
if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
focusManager.moveFocus(FocusDirection.Down)
true
} else {
false
Column(Modifier.width(600.dp)) {
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
delay(100L)
focusRequester.requestFocus()
}
PassphraseField(
newKey,
generalGetString(MR.strings.new_passphrase),
modifier = Modifier
.padding(horizontal = DEFAULT_PADDING)
.focusRequester(focusRequester)
.onPreviewKeyEvent {
if ((it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
focusManager.moveFocus(FocusDirection.Down)
true
} else {
false
}
},
showStrength = true,
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)
}
},
showStrength = true,
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,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier
.padding(horizontal = DEFAULT_PADDING)
.onPreviewKeyEvent {
if (!disabled && (it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
onClickUpdate()
true
} else {
false
}
},
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()
}
}
val disabled = currentKey.value == newKey.value ||
newKey.value != confirmNewKey.value ||
newKey.value.isEmpty() ||
!validKey(currentKey.value) ||
!validKey(newKey.value) ||
progressIndicator.value
PassphraseField(
confirmNewKey,
generalGetString(MR.strings.confirm_new_passphrase),
modifier = Modifier
.padding(horizontal = DEFAULT_PADDING)
.onPreviewKeyEvent {
if (!disabled && (it.key == Key.Enter || it.key == Key.NumPadEnter) && it.type == KeyEventType.KeyUp) {
onClickUpdate()
true
} else {
false
}
},
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()
}
}
}