Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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),
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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(
Expand All @@ -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,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
6 changes: 3 additions & 3 deletions composeApp/src/androidMain/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
<string name="home_todays_decks">Today\'s decks</string>
<string name="home_see_all">See all</string>
<string name="home_deck_due_cards">%1$d due · %2$d cards</string>
<string name="home_empty_title">No decks yet</string>
<string name="home_empty_subtitle">Paste a list, import from a file, or start from scratch — your first deck is one tap away.</string>
<string name="home_create_first_deck">Create your first deck</string>
<string name="home_empty_title">Nothing to study yet</string>
<string name="home_empty_subtitle">Create or import a deck to start your first study session. Cards will appear here when they\'re ready for review.</string>
<string name="home_create_first_deck">Create a deck</string>
<string name="home_browse_examples">Browse examples</string>

<!-- Decks library -->
Expand Down
196 changes: 105 additions & 91 deletions design/main/phone-echo.pen
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
]
}
]
}
Expand Down
Loading