mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-25 07:42:15 +00:00
Added initial implementation of the Intro, Create profile, Conditions screens
This commit is contained in:
35
apps/multiplatform/.idea/codeStyles/Project.xml
generated
35
apps/multiplatform/.idea/codeStyles/Project.xml
generated
@@ -3,6 +3,41 @@
|
||||
<ComposeCustomCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_FORMATTING_FOR_MODIFIERS" value="false" />
|
||||
</ComposeCustomCodeStyleSettings>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" module="true" />
|
||||
<package name="android" withSubpackages="true" static="true" />
|
||||
<package name="androidx" withSubpackages="true" static="true" />
|
||||
<package name="com" withSubpackages="true" static="true" />
|
||||
<package name="junit" withSubpackages="true" static="true" />
|
||||
<package name="net" withSubpackages="true" static="true" />
|
||||
<package name="org" withSubpackages="true" static="true" />
|
||||
<package name="java" withSubpackages="true" static="true" />
|
||||
<package name="javax" withSubpackages="true" static="true" />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="androidx" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="SPACE_BEFORE_EXTEND_COLON" value="false" />
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="3" />
|
||||
|
||||
@@ -139,7 +139,7 @@ fun MainScreen() {
|
||||
when {
|
||||
onboarding == OnboardingStage.Step1_SimpleXInfo && chatModel.migrationState.value != null -> {
|
||||
// In migration process. Nothing should interrupt it, that's why it's the first branch in when()
|
||||
SimpleXInfo(chatModel, onboarding = true)
|
||||
IntroCarouselView(chatModel)
|
||||
if (appPlatform.isDesktop) {
|
||||
ModalManager.fullscreen.showInView()
|
||||
}
|
||||
@@ -184,7 +184,7 @@ fun MainScreen() {
|
||||
when (state) {
|
||||
OnboardingStage.OnboardingComplete -> { /* handled out of AnimatedContent block */}
|
||||
OnboardingStage.Step1_SimpleXInfo -> {
|
||||
SimpleXInfo(chatModel, onboarding = true)
|
||||
IntroCarouselView(chatModel)
|
||||
if (appPlatform.isDesktop) {
|
||||
ModalManager.fullscreen.showInView()
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ import androidx.compose.ui.graphics.SolidColor
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.*
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.BuildConfigCommon
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
@@ -30,6 +32,12 @@ import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.onboarding.*
|
||||
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||
import chat.simplex.res.MR
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -143,20 +151,68 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
val displayName = rememberSaveable { mutableStateOf("") }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, end = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||
AppBarTitle(stringResource(MR.strings.create_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false)
|
||||
// Isometric illustration (conditional on USE_BRANDED_IMAGES)
|
||||
if (BuildConfigCommon.USE_BRANDED_IMAGES) {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = DEFAULT_PADDING),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(MR.images.intro_2),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(200.dp),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
}
|
||||
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
|
||||
|
||||
// Title: "Create profile"
|
||||
Text(
|
||||
text = stringResource(MR.strings.create_profile),
|
||||
style = MaterialTheme.typography.h1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester)
|
||||
|
||||
// Subtitle: "Your profile is stored on your device and only shared with your contacts."
|
||||
Text(
|
||||
text = stringResource(MR.strings.create_profile_subtitle),
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2))
|
||||
|
||||
// Name input field with placeholder: "Enter your name..."
|
||||
ProfileNameField(displayName, stringResource(MR.strings.enter_your_name_placeholder), { it.trim() == mkValidName(it) }, focusRequester)
|
||||
}
|
||||
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
|
||||
// Calculate bottom padding - ColumnWithScrollBar already applies imePadding automatically
|
||||
val imePadding = WindowInsets.ime.asPaddingValues().calculateBottomPadding()
|
||||
val isKeyboardOpen = appPlatform.isAndroid && keyboardState == KeyboardState.Opened && imePadding > 0.dp
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = DEFAULT_PADDING * 2),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
OnboardingActionButton(
|
||||
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.create_profile_button,
|
||||
labelId = MR.strings.create_profile,
|
||||
onboarding = null,
|
||||
enabled = canCreateProfile(displayName.value),
|
||||
onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) }
|
||||
@@ -164,6 +220,14 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
// Reserve space
|
||||
TextButtonBelowOnboardingButton("", null)
|
||||
}
|
||||
|
||||
// Add spacer at bottom when keyboard is open to ensure button can be scrolled above keyboard
|
||||
// This provides extra scrollable space so the button remains visible
|
||||
if (isKeyboardOpen) {
|
||||
Spacer(Modifier.height(imePadding + DEFAULT_PADDING))
|
||||
} else {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(300)
|
||||
@@ -173,13 +237,15 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
setLastVersionDefault(chatModel)
|
||||
}
|
||||
if (savedKeyboardState != keyboardState) {
|
||||
LaunchedEffect(keyboardState) {
|
||||
// Auto-scroll when keyboard opens to ensure button is visible
|
||||
LaunchedEffect(keyboardState) {
|
||||
if (appPlatform.isAndroid && keyboardState == KeyboardState.Opened) {
|
||||
scope.launch {
|
||||
savedKeyboardState = keyboardState
|
||||
delay(100) // Small delay to ensure layout is updated
|
||||
scrollState.animateScrollTo(scrollState.maxValue)
|
||||
}
|
||||
}
|
||||
savedKeyboardState = keyboardState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.BuildConfigCommon
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.platform.*
|
||||
@@ -27,6 +28,13 @@ import chat.simplex.common.views.usersettings.networkAndServers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.material.Icon
|
||||
import chat.simplex.common.views.helpers.BoltFilled
|
||||
import chat.simplex.common.views.onboarding.SetNotificationsMode
|
||||
|
||||
@Composable
|
||||
fun ModalData.OnboardingConditionsView() {
|
||||
@@ -43,30 +51,83 @@ fun ModalData.OnboardingConditionsView() {
|
||||
.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer),
|
||||
maxIntrinsicSize = true
|
||||
) {
|
||||
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||
AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), bottomPadding = DEFAULT_PADDING)
|
||||
Column(
|
||||
(if (appPlatform.isDesktop) Modifier.width(450.dp).align(Alignment.CenterHorizontally) else Modifier)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Isometric illustration (conditional on USE_BRANDED_IMAGES)
|
||||
if (BuildConfigCommon.USE_BRANDED_IMAGES) {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = DEFAULT_PADDING),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(MR.images.intro_2),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(200.dp),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
}
|
||||
|
||||
// Title: "Conditions of use" (centered, bold)
|
||||
Text(
|
||||
text = stringResource(MR.strings.conditions_of_use_title),
|
||||
style = MaterialTheme.typography.h1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2))
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Column(
|
||||
(if (appPlatform.isDesktop) Modifier.width(450.dp).align(Alignment.CenterHorizontally) else Modifier)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING),
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
// Body text (left-aligned)
|
||||
Text(
|
||||
stringResource(MR.strings.onboarding_conditions_private_chats_not_accessible),
|
||||
style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp)
|
||||
style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp),
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
Text(
|
||||
stringResource(MR.strings.onboarding_conditions_by_using_you_agree),
|
||||
style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp)
|
||||
"By using SimpleX Chat you agree to:",
|
||||
style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp),
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
||||
Text(
|
||||
"• send only legal content in public groups.",
|
||||
style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
modifier = Modifier.padding(start = DEFAULT_PADDING)
|
||||
)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
||||
Text(
|
||||
"• respect other users – no spam.",
|
||||
style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp),
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
modifier = Modifier.padding(start = DEFAULT_PADDING)
|
||||
)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
// Privacy policy link (blue, underlined)
|
||||
Text(
|
||||
stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use),
|
||||
style = TextStyle(fontSize = 17.sp),
|
||||
style = TextStyle(fontSize = 17.sp, textDecoration = TextDecoration.Underline),
|
||||
color = MaterialTheme.colors.primary,
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
@@ -83,10 +144,65 @@ fun ModalData.OnboardingConditionsView() {
|
||||
|
||||
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds)
|
||||
TextButtonBelowOnboardingButton(stringResource(MR.strings.onboarding_conditions_configure_server_operators)) {
|
||||
ModalManager.fullscreen.showModalCloseable { close ->
|
||||
ChooseServerOperators(serverOperators, selectedOperatorIds, close)
|
||||
}
|
||||
|
||||
// Configure server operators link with icon
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
) {
|
||||
ModalManager.fullscreen.showModalCloseable { close ->
|
||||
ChooseServerOperators(serverOperators, selectedOperatorIds, close)
|
||||
}
|
||||
}
|
||||
.padding(vertical = DEFAULT_PADDING_HALF),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(MR.strings.onboarding_conditions_configure_server_operators),
|
||||
color = MaterialTheme.colors.primary,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
// Server operator icon - using network icon if available, otherwise a simple circle
|
||||
Icon(
|
||||
painterResource(MR.images.ic_info),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Configure notifications link with icon
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null
|
||||
) {
|
||||
ModalManager.fullscreen.showModalCloseable { close ->
|
||||
SetNotificationsMode(chatModel)
|
||||
}
|
||||
}
|
||||
.padding(vertical = DEFAULT_PADDING_HALF),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(MR.strings.configure_notifications),
|
||||
color = MaterialTheme.colors.primary,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
// Notification icon - using bolt/lightning icon
|
||||
Icon(
|
||||
BoltFilled,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.BuildConfigCommon
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.platform.appPlatform
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.migration.MigrateToDeviceView
|
||||
import chat.simplex.common.views.migration.MigrationToState
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
fun IntroCarouselView(chatModel: ChatModel) {
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||
ModalView({}, showClose = false, showAppBar = false) {
|
||||
IntroCarouselContent(chatModel)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (chatModel.migrationState.value != null && !ModalManager.fullscreen.hasModalsOpen()) {
|
||||
ModalManager.fullscreen.showCustomModal(animated = false) { close -> MigrateToDeviceView(close) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IntroCarouselContent(chatModel: ChatModel) {
|
||||
val pagerState = rememberPagerState(initialPage = 0, initialPageOffsetFraction = 0f) { 3 }
|
||||
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = DEFAULT_PADDING + 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
SimpleXLogo(modifier = Modifier.widthIn(max = if (appPlatform.isAndroid) 250.dp else 500.dp))
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth(),
|
||||
pageNestedScrollConnection = LocalAppBarHandler.current!!.connection,
|
||||
verticalAlignment = Alignment.Top,
|
||||
userScrollEnabled = appPlatform.isAndroid,
|
||||
) { page ->
|
||||
val headline = when (page) {
|
||||
0 -> stringResource(MR.strings.intro_headline_1)
|
||||
1 -> stringResource(MR.strings.intro_headline_2)
|
||||
else -> stringResource(MR.strings.intro_headline_3)
|
||||
}
|
||||
val subtitle = when (page) {
|
||||
0 -> stringResource(MR.strings.intro_subtitle_1)
|
||||
1 -> stringResource(MR.strings.intro_subtitle_2)
|
||||
else -> stringResource(MR.strings.intro_subtitle_3)
|
||||
}
|
||||
val showButtons = page == 2
|
||||
val introImage = when (page) {
|
||||
0 -> MR.images.intro_2
|
||||
1 -> MR.images.intro_2
|
||||
else -> MR.images.intro_2
|
||||
}
|
||||
IntroPage(
|
||||
headline = headline,
|
||||
subtitle = subtitle,
|
||||
centralContent = {
|
||||
if (BuildConfigCommon.USE_BRANDED_IMAGES) {
|
||||
Icon(
|
||||
painter = painterResource(introImage),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(200.dp),
|
||||
)
|
||||
}
|
||||
},
|
||||
showButtons = showButtons,
|
||||
onCreateProfile = if (showButtons) {
|
||||
{}
|
||||
} else null,
|
||||
onMigrate = if (showButtons) {
|
||||
{
|
||||
chatModel.migrationState.value = MigrationToState.PasteOrScanLink
|
||||
ModalManager.fullscreen.showCustomModal { close -> MigrateToDeviceView(close) }
|
||||
}
|
||||
} else null,
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = DEFAULT_PADDING),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
PageIndicator(pageCount = 3, currentPage = pagerState.currentPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextFallback() {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
painter = painterResource(MR.images.ic_chat),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(120.dp),
|
||||
tint = MaterialTheme.colors.primary.copy(alpha = 0.6f),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.platform.appPlatform
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
fun IntroPage(
|
||||
headline: String,
|
||||
subtitle: String,
|
||||
centralContent: @Composable BoxScope.() -> Unit,
|
||||
showButtons: Boolean = false,
|
||||
onCreateProfile: (() -> Unit)? = null,
|
||||
onMigrate: (() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(0.5f)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
centralContent()
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(32.dp))
|
||||
|
||||
Text(
|
||||
text = headline,
|
||||
style = MaterialTheme.typography.h1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 38.sp,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = MaterialTheme.colors.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 24.sp,
|
||||
)
|
||||
|
||||
if (showButtons && (onCreateProfile != null || onMigrate != null)) {
|
||||
Spacer(Modifier.height(DEFAULT_PADDING * 2))
|
||||
Column(
|
||||
Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
if (onCreateProfile != null) {
|
||||
OnboardingActionButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
labelId = MR.strings.create_your_profile,
|
||||
onboarding = OnboardingStage.Step2_CreateProfile,
|
||||
onclick = onCreateProfile,
|
||||
)
|
||||
}
|
||||
if (onMigrate != null) {
|
||||
TextButtonBelowOnboardingButton(
|
||||
text = stringResource(MR.strings.migrate_from_another_device),
|
||||
onClick = onMigrate,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||
import chat.simplex.common.ui.theme.isInDarkTheme
|
||||
|
||||
@Composable
|
||||
fun PageIndicator(
|
||||
pageCount: Int,
|
||||
currentPage: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
dotSize: Dp = 8.dp,
|
||||
spacing: Dp = 8.dp,
|
||||
) {
|
||||
val activeColor = MaterialTheme.colors.primary
|
||||
val inactiveColor = if (isInDarkTheme()) {
|
||||
MaterialTheme.colors.secondary.copy(alpha = 0.5f)
|
||||
} else {
|
||||
MaterialTheme.colors.secondary.copy(alpha = 0.4f)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier.padding(vertical = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.spacedBy(spacing, Alignment.CenterHorizontally),
|
||||
) {
|
||||
repeat(pageCount) { index ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(dotSize)
|
||||
.clip(CircleShape)
|
||||
.background(if (index == currentPage) activeColor else inactiveColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,12 +95,12 @@ fun SimpleXInfoLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimpleXLogo() {
|
||||
fun SimpleXLogo(modifier: Modifier = Modifier) {
|
||||
Image(
|
||||
painter = painterResource(if (isInDarkTheme()) MR.images.logo_light else MR.images.logo),
|
||||
contentDescription = stringResource(MR.strings.image_descr_simplex_logo),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.padding(vertical = DEFAULT_PADDING)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 10.25H11.52C10.7179 10.2498 9.93692 10.5068 9.29167 10.9832C8.64641 11.4596 8.17092 12.1304 7.935 12.897L3.915 21.961C3.80533 22.3184 3.74971 22.6902 3.75 23.064V30C3.75 30.9946 4.14509 31.9484 4.84835 32.6516C5.55161 33.3549 6.50544 33.75 7.5 33.75H32.5C33.4946 33.75 34.4484 33.3549 35.1517 32.6516C35.8549 31.9484 36.25 30.9946 36.25 30V23.064C36.25 22.6901 36.194 22.3183 36.084 21.961L32.064 12.897C31.8281 12.1306 31.3528 11.4599 30.7077 10.9835C30.0627 10.5071 29.2819 10.25 28.48 10.25H25M3.75 22.5H10.182C10.8784 22.5 11.5611 22.694 12.1534 23.0601C12.7458 23.4262 13.2246 23.9501 13.536 24.573L13.964 25.427C14.2754 26.0499 14.7542 26.5738 15.3466 26.9399C15.9389 27.306 16.6216 27.5 17.318 27.5H22.682C23.3786 27.5002 24.0614 27.3063 24.654 26.9402C25.2466 26.574 25.7255 26.05 26.037 25.427L26.464 24.573C26.7754 23.9501 27.2542 23.4262 27.8466 23.0601C28.439 22.694 29.1216 22.5 29.818 22.5H36.25M20 5V18.75M20 18.75L15 13.75M20 18.75L25 13.75" stroke="#E6A800" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -40,4 +40,4 @@ compose.version=1.8.2
|
||||
database.backend=sqlite
|
||||
|
||||
# Set to true for branded builds (includes proprietary illustrations). Default: false (AGPL-compliant build).
|
||||
# USE_BRANDED_IMAGES=true
|
||||
USE_BRANDED_IMAGES=true
|
||||
|
||||
@@ -624,11 +624,25 @@ If critical issues arise post-release:
|
||||
## Open Questions
|
||||
|
||||
1. **Asset repository structure** — Final decision on sibling folder vs separate repo?
|
||||
|
||||
Let's do folder with readme that it's proprietory and not licensed.
|
||||
|
||||
2. **Intro content** — Final copy for headlines and subtitles?
|
||||
|
||||
Yes, it's on me, start from invitations flow - it's more interesting and ready.
|
||||
|
||||
3. **Animated fallback design** — Specific animation/text layout for AGPL build?
|
||||
|
||||
Just show opened card I think in the first iteration, we'll see how it looks
|
||||
|
||||
4. **Migration flow entry** — Should "Migrate from another device" also appear elsewhere?
|
||||
|
||||
Don't understand the question.
|
||||
|
||||
5. **Analytics** — Any onboarding completion tracking requirements?
|
||||
|
||||
We don't do it yet
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
Reference in New Issue
Block a user