Added initial implementation of the Intro, Create profile, Conditions screens

This commit is contained in:
hayk888997
2026-02-09 20:00:07 +04:00
parent a1d45c2de5
commit 43ebf99e71
11 changed files with 514 additions and 26 deletions

View File

@@ -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" />

View File

@@ -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()
}

View File

@@ -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
}
}
}

View File

@@ -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)
)
}
}
}

View File

@@ -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),
)
}
}

View File

@@ -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,
)
}
}
}
}
}

View File

@@ -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)
)
}
}
}

View File

@@ -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()
)

View File

@@ -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

View File

@@ -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

View File

@@ -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