feat: new onboarding screens #4

Merged
reya merged 4 commits from feat/onboarding into master 2026-05-26 02:04:08 +00:00
13 changed files with 316 additions and 168 deletions

View File

@@ -21,6 +21,7 @@ kotlin {
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.core.splashscreen)
implementation("io.coil-kt.coil3:coil-compose:3.4.0") implementation("io.coil-kt.coil3:coil-compose:3.4.0")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0") implementation("io.coil-kt.coil3:coil-network-okhttp:3.4.0")
implementation("su.reya:nostr-sdk-kmp:0.2.3") implementation("su.reya:nostr-sdk-kmp:0.2.3")

View File

@@ -20,7 +20,8 @@
android:theme="@android:style/Theme.Material.Light.NoActionBar"> android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"> android:exported="true"
android:theme="@style/Theme.App.Starting">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@@ -1,18 +1,3 @@
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="750dp" android:width="750dp"
android:height="750dp" android:height="750dp"

View File

@@ -6,10 +6,13 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge() enableEdgeToEdge()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val intent = Intent(this, NostrForegroundService::class.java) val intent = Intent(this, NostrForegroundService::class.java)

View File

@@ -7,11 +7,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@@ -40,7 +43,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -58,6 +63,7 @@ import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen import su.reya.coop.Screen
import su.reya.coop.shared.Avatar import su.reya.coop.shared.Avatar
import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.short import su.reya.coop.short
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -69,6 +75,7 @@ fun ImportScreen(
) { ) {
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val navController = LocalNavController.current val navController = LocalNavController.current
val focusManager = LocalFocusManager.current
val viewModel = LocalNostrViewModel.current val viewModel = LocalNostrViewModel.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -145,40 +152,44 @@ fun ImportScreen(
}, },
content = { innerPadding -> content = { innerPadding ->
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(top = innerPadding.calculateTopPadding())
.imePadding(),
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f)
.fillMaxWidth() .fillMaxWidth()
.padding(top = innerPadding.calculateTopPadding()), .weight(1f),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(120.dp) .size(120.dp)
.clip(MaterialShapes.Pentagon.toShape()), .clip(MaterialShapes.Cookie9Sided.toShape()),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Avatar( Avatar(
picture = picture, picture = picture,
description = "Profile picture", description = "Profile picture",
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
shape = MaterialShapes.Pentagon.toShape(), shape = MaterialShapes.Cookie9Sided.toShape(),
) )
} }
Spacer(modifier = Modifier.size(8.dp)) Spacer(modifier = Modifier.size(8.dp))
Text( Text(
text = displayName, text = displayName,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLargeEmphasized, style = MaterialTheme.typography.titleLargeEmphasized.copy(
fontFamily = getExpressiveFontFamily()
),
) )
} }
Surface( Surface(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth()
.fillMaxWidth(), .weight(1f, fill = false),
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
) { ) {
@@ -186,44 +197,57 @@ fun ImportScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(24.dp) .padding(24.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( Column(
text = "Enter your Secret Key or Bunker URI:", modifier = Modifier
style = MaterialTheme.typography.titleMediumEmphasized.copy( .weight(1f)
fontWeight = FontWeight.SemiBold, .verticalScroll(rememberScrollState()),
), verticalArrangement = Arrangement.spacedBy(16.dp)
) ) {
BasicTextField( Text(
value = secret, text = "Enter your Secret Key or Bunker URI:",
onValueChange = { secret = it }, style = MaterialTheme.typography.titleMediumEmphasized.copy(
modifier = Modifier.fillMaxWidth(), fontWeight = FontWeight.SemiBold,
maxLines = 4, ),
visualTransformation = PasswordVisualTransformation('*'), )
textStyle = MaterialTheme.typography.bodyMediumEmphasized.copy( BasicTextField(
color = MaterialTheme.colorScheme.primaryFixed, value = secret,
fontWeight = FontWeight.SemiBold, onValueChange = { secret = it },
), modifier = Modifier.fillMaxWidth(),
cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary), maxLines = 4,
decorationBox = { innerTextField -> keyboardOptions = KeyboardOptions(
Box(contentAlignment = Alignment.CenterStart) { imeAction = ImeAction.Done,
if (secret.isEmpty()) { ),
Text( keyboardActions = KeyboardActions(
"bunker://", onDone = {
style = MaterialTheme.typography.bodyMediumEmphasized.copy( focusManager.clearFocus()
fontWeight = FontWeight.SemiBold, }
), ),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy( visualTransformation = PasswordVisualTransformation('*'),
alpha = 0.5f textStyle = MaterialTheme.typography.bodyMediumEmphasized.copy(
) color = MaterialTheme.colorScheme.primaryFixed,
) fontWeight = FontWeight.SemiBold,
),
cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary),
decorationBox = { innerTextField ->
Box(contentAlignment = Alignment.CenterStart) {
if (secret.isEmpty()) {
Text(
"bunker://",
style = MaterialTheme.typography.bodyMediumEmphasized.copy(
fontWeight = FontWeight.SemiBold,
),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
alpha = 0.5f
)
)
}
innerTextField()
} }
innerTextField()
} }
} )
) }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.size(16.dp))
Button( Button(
onClick = { onClick = {
if (pubkey == null) { if (pubkey == null) {

View File

@@ -11,11 +11,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@@ -42,7 +45,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.Res
@@ -59,6 +64,8 @@ fun NewIdentityScreen(
onSave: (name: String, bio: String?, picture: Uri?) -> Unit onSave: (name: String, bio: String?, picture: Uri?) -> Unit
) { ) {
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val focusManager = LocalFocusManager.current
var name by remember { mutableStateOf("") } var name by remember { mutableStateOf("") }
var bio by remember { mutableStateOf("") } var bio by remember { mutableStateOf("") }
var picture by remember { mutableStateOf<Uri?>(null) } var picture by remember { mutableStateOf<Uri?>(null) }
@@ -95,15 +102,16 @@ fun NewIdentityScreen(
}, },
content = { innerPadding -> content = { innerPadding ->
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(top = innerPadding.calculateTopPadding())
.imePadding(),
) { ) {
Column( Box(
modifier = Modifier modifier = Modifier
.weight(1f)
.fillMaxWidth() .fillMaxWidth()
.padding(top = innerPadding.calculateTopPadding()), .weight(1f),
verticalArrangement = Arrangement.Center, contentAlignment = Alignment.Center
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@@ -139,8 +147,8 @@ fun NewIdentityScreen(
} }
Surface( Surface(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth()
.fillMaxWidth(), .weight(1f, fill = true),
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
) { ) {
@@ -148,77 +156,98 @@ fun NewIdentityScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(24.dp) .padding(24.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text( Column(
text = "What others should call you?", modifier = Modifier
style = MaterialTheme.typography.titleLargeEmphasized.copy( .weight(1f)
fontWeight = FontWeight.SemiBold, .verticalScroll(rememberScrollState()),
), verticalArrangement = Arrangement.spacedBy(16.dp)
) ) {
BasicTextField( Text(
value = name, text = "What others should call you?",
onValueChange = { name = it }, style = MaterialTheme.typography.titleLargeEmphasized.copy(
modifier = Modifier.fillMaxWidth(), fontWeight = FontWeight.SemiBold,
maxLines = 1, ),
textStyle = MaterialTheme.typography.headlineLargeEmphasized.copy( )
color = MaterialTheme.colorScheme.primaryFixed, BasicTextField(
fontWeight = FontWeight.SemiBold, value = name,
), onValueChange = { name = it },
cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary), modifier = Modifier.fillMaxWidth(),
decorationBox = { innerTextField -> singleLine = true,
Box(contentAlignment = Alignment.CenterStart) { keyboardOptions = KeyboardOptions(
if (name.isEmpty()) { imeAction = ImeAction.Done,
Text( ),
"Alice", keyboardActions = KeyboardActions(
style = MaterialTheme.typography.headlineLargeEmphasized.copy( onDone = {
fontWeight = FontWeight.SemiBold, focusManager.clearFocus()
),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
alpha = 0.5f
)
)
} }
innerTextField() ),
} textStyle = MaterialTheme.typography.headlineLargeEmphasized.copy(
} color = MaterialTheme.colorScheme.primaryFixed,
) fontWeight = FontWeight.SemiBold,
Spacer(modifier = Modifier.size(8.dp)) ),
Text( cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary),
text = "Your bio (optional)", decorationBox = { innerTextField ->
style = MaterialTheme.typography.titleLargeEmphasized.copy( Box(contentAlignment = Alignment.CenterStart) {
fontWeight = FontWeight.SemiBold, if (name.isEmpty()) {
), Text(
) "Alice",
BasicTextField( style = MaterialTheme.typography.headlineLargeEmphasized.copy(
value = bio, fontWeight = FontWeight.SemiBold,
onValueChange = { bio = it }, ),
modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
maxLines = 3, alpha = 0.5f
textStyle = MaterialTheme.typography.bodyLarge.copy( )
color = MaterialTheme.colorScheme.primaryFixed,
fontWeight = FontWeight.SemiBold,
),
cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary),
decorationBox = { innerTextField ->
Box(contentAlignment = Alignment.CenterStart) {
if (bio.isEmpty()) {
Text(
"I love cat",
style = MaterialTheme.typography.headlineLargeEmphasized.copy(
fontWeight = FontWeight.SemiBold,
),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
alpha = 0.5f
) )
) }
innerTextField()
} }
innerTextField()
} }
} )
) Spacer(modifier = Modifier.size(8.dp))
Spacer(modifier = Modifier.weight(1f)) Text(
text = "Your bio (optional)",
style = MaterialTheme.typography.titleLargeEmphasized.copy(
fontWeight = FontWeight.SemiBold,
),
)
BasicTextField(
value = bio,
onValueChange = { bio = it },
modifier = Modifier.fillMaxWidth(),
maxLines = 3,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(
onDone = {
focusManager.clearFocus()
}
),
textStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.primaryFixed,
fontWeight = FontWeight.SemiBold,
),
cursorBrush = SolidColor(MaterialTheme.colorScheme.secondary),
decorationBox = { innerTextField ->
Box(contentAlignment = Alignment.CenterStart) {
if (bio.isEmpty()) {
Text(
"I love cat",
style = MaterialTheme.typography.headlineLargeEmphasized.copy(
fontWeight = FontWeight.SemiBold,
),
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
alpha = 0.5f
)
)
}
innerTextField()
}
}
)
}
Spacer(modifier = Modifier.size(16.dp))
Button( Button(
onClick = { onClick = {
onSave(name, bio, picture) onSave(name, bio, picture)

View File

@@ -9,16 +9,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
@@ -26,27 +27,65 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.buildAnnotatedString
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.dp
import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.coop import coop.composeapp.generated.resources.coop
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import su.reya.coop.LocalSnackbarHostState import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.shared.getExpressiveFontFamily
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun OnboardingScreen(onOpenImport: () -> Unit, onOpenNew: () -> Unit) { fun OnboardingScreen(onOpenImport: () -> Unit, onOpenNew: () -> Unit) {
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val logoPainter = painterResource(Res.drawable.coop) val logoPainter = painterResource(Res.drawable.coop)
val expressiveFont = getExpressiveFontFamily()
val annotatedText = buildAnnotatedString {
append("By using Coop, you agree to accept\nour ")
// Push "Terms of Use" link
pushLink(
LinkAnnotation.Url(
url = "https://coop.free/terms",
styles = TextLinkStyles(
style = SpanStyle(
color = MaterialTheme.colorScheme.onSecondaryContainer,
fontWeight = FontWeight.SemiBold,
)
)
)
)
append("Terms of Use")
pop()
append(" and ")
// Push "Privacy Policy" link
pushLink(
LinkAnnotation.Url(
url = "https://coop.free/privacy",
styles = TextLinkStyles(
style = SpanStyle(
color = MaterialTheme.colorScheme.onSecondaryContainer,
fontWeight = FontWeight.SemiBold,
)
)
)
)
append("Privacy Policy")
pop()
append(".")
}
Scaffold( Scaffold(
containerColor = MaterialTheme.colorScheme.surfaceContainer, containerColor = MaterialTheme.colorScheme.secondaryContainer,
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
content = { innerPadding -> content = { innerPadding ->
Box( Box(modifier = Modifier.fillMaxSize()) {
modifier = Modifier
.fillMaxSize()
.padding(bottom = innerPadding.calculateBottomPadding())
) {
LogoRepeatingBackground( LogoRepeatingBackground(
painter = logoPainter, painter = logoPainter,
logosPerRow = 6, logosPerRow = 6,
@@ -54,55 +93,71 @@ fun OnboardingScreen(onOpenImport: () -> Unit, onOpenNew: () -> Unit) {
horizontalOffset = 0.5f horizontalOffset = 0.5f
) )
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(bottom = innerPadding.calculateBottomPadding() + 16.dp),
) { ) {
Box( Spacer(modifier = Modifier.weight(2f))
Surface(
modifier = Modifier modifier = Modifier
.weight(2f)
.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
// TODO: Add headline
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = innerPadding.calculateBottomPadding()), .padding(24.dp),
contentAlignment = Alignment.BottomEnd, shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.onSurface,
shadowElevation = 4.dp,
) { ) {
Column( Column(
modifier = Modifier.padding(horizontal = innerPadding.calculateBottomPadding()), modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
) { ) {
Text(
text = "Get Started",
style = MaterialTheme.typography.headlineSmallEmphasized.copy(
fontFamily = expressiveFont,
),
fontWeight = FontWeight.SemiBold,
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "Coop is a secure and easy to use messaging app. All your communications are encrypted and private by default.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.size(24.dp))
Button( Button(
onClick = onOpenNew, onClick = onOpenNew,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.size(ButtonDefaults.LargeContainerHeight), .size(ButtonDefaults.MediumContainerHeight),
) { ) {
Text( Text(
text = "Start messaging", text = "Start Messaging",
style = MaterialTheme.typography.titleLargeEmphasized, style = MaterialTheme.typography.titleMediumEmphasized,
) )
} }
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(8.dp))
FilledTonalButton( OutlinedButton(
onClick = onOpenImport, onClick = onOpenImport,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(ButtonDefaults.LargeContainerHeight), .height(ButtonDefaults.MediumContainerHeight),
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer
),
) { ) {
Text( Text(
text = "Import identity", text = "Add an Existing Identity",
style = MaterialTheme.typography.titleLargeEmphasized, style = MaterialTheme.typography.titleMedium,
) )
} }
} }
} }
Text(
text = annotatedText,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSecondaryContainer,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
} }
} }
} }
@@ -116,7 +171,7 @@ fun LogoRepeatingBackground(
rotationDegrees: Float = 0f, rotationDegrees: Float = 0f,
horizontalOffset: Float = 0.5f horizontalOffset: Float = 0.5f
) { ) {
val tintColor = MaterialTheme.colorScheme.primary val tintColor = MaterialTheme.colorScheme.onSecondaryContainer
Canvas(modifier = Modifier.fillMaxSize()) { Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width val canvasWidth = size.width

View File

@@ -0,0 +1,13 @@
package su.reya.coop.shared
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import coop.composeapp.generated.resources.PaytoneOne_Regular
import coop.composeapp.generated.resources.Res
import org.jetbrains.compose.resources.Font
@Composable
fun getExpressiveFontFamily() = FontFamily(
Font(Res.font.PaytoneOne_Regular, FontWeight.Normal)
)

View File

@@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="120"
android:viewportHeight="120">
<group
android:translateX="20"
android:translateY="20">
<path
android:pathData="M37.54,74C52.75,74 65.08,61.581 65.08,46.262C65.08,44.87 63.959,43.74 62.576,43.74H12.504C11.12,43.74 10,44.87 10,46.262C10,61.581 22.33,74 37.54,74Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<path
android:pathData="M49.707,14.321L49.493,14.263L49.279,14.207L49.062,14.153L48.843,14.099L48.627,14.049L48.411,14L48.194,13.952L47.976,13.907L47.748,13.861L47.518,13.817L47.287,13.775L47.055,13.735L46.94,13.715L46.828,13.697L46.604,13.662L46.381,13.629L46.157,13.598L45.927,13.568L45.696,13.54L45.467,13.514L45.236,13.49L45.005,13.467L44.774,13.447L44.543,13.428L44.31,13.412L44.08,13.398L43.851,13.385L43.623,13.375L43.392,13.366L43.159,13.359L42.926,13.354L42.694,13.351L42.46,13.35H42.184L41.903,13.356L41.623,13.363L41.343,13.373L41.063,13.385L40.784,13.401L40.504,13.419L40.224,13.44L39.932,13.466L39.64,13.494L39.357,13.524L39.074,13.558L38.781,13.596L38.489,13.637L38.209,13.679L37.929,13.724L37.649,13.772L37.369,13.823L37.103,13.875L36.836,13.929L36.57,13.986L36.418,14.02L36.306,14.045L36.026,14.111L35.746,14.18L35.466,14.252L35.187,14.328L34.917,14.403L34.648,14.482L34.512,14.523L34.376,14.564L34.109,14.649L33.847,14.734L33.587,14.821C33.587,14.821 32.45,13.249 32.536,12.136C32.644,10.731 33.588,9.592 34.837,9.163C34.764,8.837 34.687,8.51 34.713,8.16C34.859,6.274 36.501,4.864 38.377,5.01C39.485,5.096 40.376,5.734 40.932,6.605C41.616,5.83 42.598,5.339 43.705,5.425C45.449,5.561 46.733,7.01 46.794,8.726C47.033,8.691 47.264,8.617 47.516,8.637C49.394,8.783 50.796,10.429 50.65,12.315C50.58,13.232 49.707,14.321 49.707,14.321Z"
android:fillColor="#000000"/>
<path
android:pathData="M14.92,41.088C14.92,25.769 27.25,13.35 42.46,13.35C57.669,13.35 70,25.769 70,41.088C70,42.481 68.879,43.61 67.496,43.61H17.424C16.04,43.61 14.92,42.481 14.92,41.088ZM54.627,26.453C54.476,26.411 54.325,26.389 54.172,26.389C54.018,26.389 53.867,26.411 53.717,26.453C53.566,26.496 53.42,26.561 53.279,26.645C53.138,26.73 53.003,26.833 52.876,26.956C52.748,27.079 52.63,27.218 52.522,27.375C52.413,27.531 52.316,27.701 52.232,27.885C52.147,28.068 52.075,28.262 52.016,28.467C51.957,28.671 51.913,28.882 51.883,29.098C51.854,29.315 51.839,29.533 51.839,29.755C51.839,29.976 51.854,30.195 51.883,30.411C51.913,30.628 51.957,30.838 52.016,31.043C52.075,31.247 52.147,31.441 52.232,31.625C52.316,31.808 52.413,31.979 52.522,32.135C52.63,32.291 52.748,32.43 52.876,32.553C53.003,32.676 53.138,32.78 53.279,32.864C53.42,32.949 53.566,33.013 53.717,33.056C53.867,33.1 54.018,33.121 54.172,33.121C54.325,33.121 54.476,33.1 54.627,33.056C54.777,33.013 54.923,32.949 55.064,32.864C55.206,32.78 55.341,32.676 55.468,32.553C55.595,32.43 55.714,32.291 55.822,32.135C55.93,31.979 56.027,31.808 56.112,31.625C56.197,31.441 56.268,31.247 56.327,31.043C56.386,30.838 56.431,30.628 56.46,30.411C56.49,30.195 56.505,29.976 56.505,29.755C56.505,29.533 56.49,29.315 56.46,29.098C56.431,28.882 56.386,28.671 56.327,28.467C56.268,28.262 56.197,28.068 56.112,27.885C56.027,27.701 55.93,27.531 55.822,27.375C55.714,27.218 55.595,27.079 55.468,26.956C55.341,26.833 55.206,26.73 55.064,26.645C54.923,26.561 54.777,26.496 54.627,26.453Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="secondaryContainer">#F8FF37</color>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/secondaryContainer</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/coop</item>
<item name="postSplashScreenTheme">@android:style/Theme.Material.Light.NoActionBar</item>
</style>
</resources>

View File

@@ -10,6 +10,7 @@ androidx-espresso = "3.7.0"
androidx-lifecycle = "2.10.0" androidx-lifecycle = "2.10.0"
androidx-navigation = "2.9.8" androidx-navigation = "2.9.8"
androidx-testExt = "1.3.0" androidx-testExt = "1.3.0"
androidx-splashscreen = "1.2.0"
composeMultiplatform = "1.11.0" composeMultiplatform = "1.11.0"
datastorePreferences = "1.2.1" datastorePreferences = "1.2.1"
junit = "4.13.2" junit = "4.13.2"
@@ -28,6 +29,7 @@ androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx
androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" } androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splashscreen" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }