From 61a3d2001f6021313e6f942ae286739acabcf696 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Sat, 20 Jun 2026 12:08:34 -0300 Subject: [PATCH 1/3] design: improve empty state design --- design/main/phone-echo.pen | 196 ++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 91 deletions(-) diff --git a/design/main/phone-echo.pen b/design/main/phone-echo.pen index bb1dd7f..2049af9 100644 --- a/design/main/phone-echo.pen +++ b/design/main/phone-echo.pen @@ -9059,113 +9059,127 @@ }, { "type": "frame", - "id": "6wNJm", - "name": "emptyCard", + "id": "IB66Z", + "name": "centerWrap", "width": "fill_container", - "fill": "$surface-card", - "cornerRadius": "$radius-xl", - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#1A132614", - "offset": { - "x": 0, - "y": 10 - }, - "blur": 24 - }, + "height": "fill_container", + "fill": "#00000000", "layout": "vertical", - "gap": 20, - "padding": [ - 36, - 28 - ], + "gap": 24, + "justifyContent": "center", "alignItems": "center", "children": [ { "type": "frame", - "id": "7maWS", - "name": "iconWrap", - "width": 140, - "height": 140, - "fill": "$accent-primary-soft", + "id": "6wNJm", + "name": "emptyCard", + "width": "fill_container", + "fill": "$surface-card", "cornerRadius": "$radius-xl", - "justifyContent": "center", + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#1A132614", + "offset": { + "x": 0, + "y": 10 + }, + "blur": 24 + }, + "layout": "vertical", + "gap": 16, + "padding": [ + 32, + 24 + ], "alignItems": "center", "children": [ { - "type": "icon", - "id": "pqHop", - "width": 72, - "height": 72, - "icon": "layers", - "library": "lucide", - "fill": "$accent-primary" + "type": "frame", + "id": "7maWS", + "name": "iconWrap", + "width": 100, + "height": 100, + "fill": "$accent-primary-soft", + "cornerRadius": 50, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "pqHop", + "width": 48, + "height": 48, + "icon": "book-open", + "library": "lucide", + "fill": "$accent-primary" + } + ] + }, + { + "type": "text", + "id": "dGTtr", + "name": "title", + "fill": "$foreground-primary", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Nothing to study yet", + "textAlign": "center", + "fontFamily": "Funnel Sans", + "fontSize": 24, + "fontWeight": "800" + }, + { + "type": "text", + "id": "PIB3B", + "name": "sub", + "fill": "$foreground-muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Create or import a deck to start your first study session. Cards will appear here when they're ready for review.", + "lineHeight": 1.5, + "textAlign": "center", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" } ] }, { - "type": "text", - "id": "dGTtr", - "name": "title", - "fill": "$foreground-primary", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "No decks yet", - "textAlign": "center", - "fontFamily": "Funnel Sans", - "fontSize": 24, - "fontWeight": "800" - }, - { - "type": "text", - "id": "PIB3B", - "name": "sub", - "fill": "$foreground-muted", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Paste a list, import from a file, or start from scratch — your first deck is one tap away.", - "lineHeight": 1.4, - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "500" - } - ] - }, - { - "type": "frame", - "id": "5loUQ", - "name": "btnGroup", - "width": "fill_container", - "fill": "#00000000", - "layout": "vertical", - "gap": 12, - "alignItems": "center", - "children": [ - { - "id": "bmQqo", - "type": "ref", - "ref": "1sozq", - "width": "fill_container", - "name": "primary", - "descendants": { - "h1MHC": { - "content": "Create your first deck" - } - } - }, - { - "id": "2aEsV", - "type": "ref", - "ref": "7KZM9", + "type": "frame", + "id": "5loUQ", + "name": "btnGroup", "width": "fill_container", - "name": "secondary", - "descendants": { - "YjRk8": { - "content": "Browse examples" + "fill": "#00000000", + "layout": "vertical", + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "bmQqo", + "type": "ref", + "ref": "1sozq", + "width": "fill_container", + "name": "primary", + "descendants": { + "h1MHC": { + "content": "Create a deck" + } + } + }, + { + "id": "2aEsV", + "type": "ref", + "ref": "7KZM9", + "width": "fill_container", + "name": "secondary", + "descendants": { + "YjRk8": { + "content": "Browse examples" + } + } } - } + ] } ] } From 312f94e8e5074645a854566e9e18521896d99c7d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Sat, 20 Jun 2026 12:18:36 -0300 Subject: [PATCH 2/3] design: match study empty state to design nwHYV Align the Study tab empty state with Pencil node nwHYV: circular book-open badge (Material MenuBook) in place of the rounded-square emoji, design padding/gap/line-height, updated title/subtitle/primary-button copy, and vertical centering of the card + actions in the space below the greeting. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../jvsena42/echo/ui/home/HomeEmptyContent.kt | 28 ++++++----- .../jvsena42/echo/ui/home/HomeScreen.kt | 48 +++++++++++++++---- .../src/androidMain/res/values/strings.xml | 6 +-- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeEmptyContent.kt b/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeEmptyContent.kt index bdc7856..be766df 100644 --- a/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeEmptyContent.kt +++ b/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeEmptyContent.kt @@ -4,12 +4,14 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.MenuBook +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -29,7 +31,8 @@ import com.github.jvsena42.echo.ui.components.EchoSecondaryButton import com.github.jvsena42.echo.ui.theme.EchoTheme /** - * Mirrors Pencil node `nwHYV` — "No decks yet" empty state. + * Mirrors Pencil node `nwHYV` — "Nothing to study yet" empty state: a white card with a circular + * book-open badge, title + subtitle, followed by the "Create a deck" / "Browse examples" actions. */ @Composable fun HomeEmptyContent( @@ -48,18 +51,23 @@ fun HomeEmptyContent( ) .clip(RoundedCornerShape(28.dp)) .background(colors.surfaceCard) - .padding(horizontal = 28.dp, vertical = 36.dp), + .padding(horizontal = 24.dp, vertical = 32.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), ) { Box( modifier = Modifier - .size(140.dp) - .clip(RoundedCornerShape(28.dp)) + .size(100.dp) + .clip(CircleShape) .background(colors.accentPrimarySoft), contentAlignment = Alignment.Center, ) { - Text(text = "\uD83D\uDCDA", fontSize = 64.sp) + Icon( + imageVector = Icons.AutoMirrored.Filled.MenuBook, + contentDescription = null, + tint = colors.accentPrimary, + modifier = Modifier.size(48.dp), + ) } Text( text = stringResource(R.string.home_empty_title), @@ -74,12 +82,10 @@ fun HomeEmptyContent( fontSize = 14.sp, fontWeight = FontWeight.Medium, textAlign = TextAlign.Center, - lineHeight = 20.sp, + lineHeight = 21.sp, ) } - Spacer(Modifier.height(4.dp)) - Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp), diff --git a/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeScreen.kt b/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeScreen.kt index f14e149..f2c067b 100644 --- a/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeScreen.kt +++ b/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/home/HomeScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding @@ -123,6 +124,17 @@ private fun HomeScreenContent( onDeckClick: (String) -> Unit, onRetry: () -> Unit, ) { + // The empty state centers its card + actions in the space below the greeting (design `nwHYV`), + // so it gets a non-scrolling full-height layout instead of the shared scroll column. + if (state is HomeUiState.Empty) { + HomeEmptyScreen( + greetingName = state.greetingName, + onCreateDeckClick = onCreateDeckClick, + onBrowseExamplesClick = onBrowseExamplesClick, + ) + return + } + Column( modifier = Modifier .fillMaxSize() @@ -131,14 +143,6 @@ private fun HomeScreenContent( verticalArrangement = Arrangement.spacedBy(24.dp), ) { when (state) { - HomeUiState.Loading -> Unit - is HomeUiState.Empty -> { - GreetingHeader(name = state.greetingName) - HomeEmptyContent( - onCreateDeckClick = onCreateDeckClick, - onBrowseExamplesClick = onBrowseExamplesClick, - ) - } is HomeUiState.Content -> { GreetingHeader(name = state.greetingName) HomeContent( @@ -151,6 +155,34 @@ private fun HomeScreenContent( GreetingHeader(name = state.greetingName) ErrorBlock(message = state.message, onRetry = onRetry) } + HomeUiState.Loading, is HomeUiState.Empty -> Unit + } + } +} + +@Composable +private fun HomeEmptyScreen( + greetingName: String, + onCreateDeckClick: () -> Unit, + onBrowseExamplesClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(PaddingValues(start = 20.dp, end = 20.dp, top = 8.dp, bottom = 24.dp)), + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + GreetingHeader(name = greetingName) + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterVertically), + ) { + HomeEmptyContent( + onCreateDeckClick = onCreateDeckClick, + onBrowseExamplesClick = onBrowseExamplesClick, + ) } } } diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml index d20ae90..c9a07a7 100644 --- a/composeApp/src/androidMain/res/values/strings.xml +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -15,9 +15,9 @@ Today\'s decks See all %1$d due · %2$d cards - No decks yet - Paste a list, import from a file, or start from scratch — your first deck is one tap away. - Create your first deck + Nothing to study yet + Create or import a deck to start your first study session. Cards will appear here when they\'re ready for review. + Create a deck Browse examples From 12dce0b6f2313788fbe4d7213ecf4259174ea83a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Sat, 20 Jun 2026 12:18:36 -0300 Subject: [PATCH 3/3] feat: navigate Browse examples to Discover tab Wire the home empty-state 'Browse examples' action to switch the pager to the Discover tab. The effect was already plumbed through HomeRoute/HomeViewModel; only the destination lambda in MainScreen was missing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../kotlin/com/github/jvsena42/echo/ui/nav/MainScreen.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/nav/MainScreen.kt b/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/nav/MainScreen.kt index f6f434e..ffadd17 100644 --- a/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/nav/MainScreen.kt +++ b/composeApp/src/androidMain/kotlin/com/github/jvsena42/echo/ui/nav/MainScreen.kt @@ -53,6 +53,9 @@ fun MainScreen( EchoTab.STUDY -> HomeRoute( onOpenDeck = { deckId -> onNavigateDeckDetail(deckId, null) }, onCreateDeck = onNavigateCreateDeck, + onBrowseExamples = { + scope.launch { pagerState.animateScrollToPage(EchoTab.DISCOVER.ordinal) } + }, onStartStudy = { onNavigateStudy(null) }, onSignedOut = onSignOut, )