From e51af2b90f9bbe8f493f049a81e84753cdbc9cda Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 15 Jul 2026 17:39:27 +0000 Subject: [PATCH 1/7] Added TaskItem composable with tests. Co-authored-by: MessiasLima --- .../composeResources/values/strings.xml | 4 + .../core/ui/component/task/TaskItem.kt | 138 ++++++++++++++++++ .../core/ui/component/task/TaskItemTest.kt | 102 +++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt create mode 100644 foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt diff --git a/foliary/src/commonMain/composeResources/values/strings.xml b/foliary/src/commonMain/composeResources/values/strings.xml index 73cb403..d76edee 100644 --- a/foliary/src/commonMain/composeResources/values/strings.xml +++ b/foliary/src/commonMain/composeResources/values/strings.xml @@ -46,6 +46,10 @@ Foliary + + Overdue + + Open Foliary Quit diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt new file mode 100644 index 0000000..8942753 --- /dev/null +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt @@ -0,0 +1,138 @@ +package dev.appoutlet.foliary.core.ui.component.task + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import dev.appoutlet.foliary.core.ui.component.card.FoliaryCard +import dev.appoutlet.foliary.core.ui.theme.FoliaryTheme +import foliary.foliary.generated.resources.Res +import foliary.foliary.generated.resources.task_item_overdue +import org.jetbrains.compose.resources.stringResource +import androidx.compose.ui.tooling.preview.Preview + +@Composable +fun TaskItem( + title: String, + modifier: Modifier = Modifier, + description: String? = null, + isCompleted: Boolean = false, + isOverdue: Boolean = false, +) { + FoliaryCard(modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Checkbox( + checked = isCompleted, + onCheckedChange = null, + modifier = Modifier.testTag("TaskItem:Checkbox"), + ) + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = title, + modifier = Modifier.testTag("TaskItem:Title"), + style = MaterialTheme.typography.titleMedium, + color = if (isCompleted) { + MaterialTheme.colorScheme.onSurface.copy(alpha = CompletedAlpha) + } else { + MaterialTheme.colorScheme.onSurface + }, + textDecoration = if (isCompleted) TextDecoration.LineThrough else null, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + description?.let { + Text( + text = it, + modifier = Modifier.testTag("TaskItem:Description"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = MaxDescriptionLines, + overflow = TextOverflow.Ellipsis, + ) + } + + if (isOverdue && !isCompleted) { + OverduePill() + } + } + } + } +} + +@Composable +private fun OverduePill() { + Surface( + modifier = Modifier + .padding(top = 4.dp) + .testTag("TaskItem:OverduePill"), + shape = MaterialTheme.shapes.small, + color = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + ) { + Text( + text = stringResource(Res.string.task_item_overdue), + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), + style = MaterialTheme.typography.labelSmall, + ) + } +} + +private const val CompletedAlpha = 0.38f +private const val MaxDescriptionLines = 3 + +@Preview +@Composable +private fun TaskItemPreview() { + FoliaryTheme { + TaskItem( + title = "Write API Documentation", + description = "Draft the OpenAPI spec for the new endpoints", + ) + } +} + +@Preview +@Composable +private fun TaskItemOverduePreview() { + FoliaryTheme { + TaskItem( + title = "Pay electricity bill", + isOverdue = true, + ) + } +} + +@Preview +@Composable +private fun TaskItemCompletedPreview() { + FoliaryTheme { + TaskItem( + title = "Inbox Zero", + description = "Process all unread emails and archive newsletters", + isCompleted = true, + ) + } +} diff --git a/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt b/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt new file mode 100644 index 0000000..cff6f8c --- /dev/null +++ b/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt @@ -0,0 +1,102 @@ +package dev.appoutlet.foliary.core.ui.component.task + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.runComposeUiTest +import foliary.foliary.generated.resources.Res +import foliary.foliary.generated.resources.task_item_overdue +import org.jetbrains.compose.resources.getString +import kotlin.test.Test + +@OptIn(ExperimentalTestApi::class) +class TaskItemTest { + + @Test + fun `should render title and description`() = runComposeUiTest { + val title = "Task title" + val description = "Task description" + + setContent { + TaskItem( + title = title, + description = description, + ) + } + + onNodeWithTag("TaskItem:Title").assertIsDisplayed().assertTextEquals(title) + onNodeWithTag("TaskItem:Description").assertIsDisplayed().assertTextEquals(description) + } + + @Test + fun `should not show description when null`() = runComposeUiTest { + setContent { + TaskItem(title = "Task title") + } + + onNodeWithTag("TaskItem:Description").assertDoesNotExist() + } + + @Test + fun `should show unchecked checkbox by default`() = runComposeUiTest { + setContent { + TaskItem(title = "Task title") + } + + onNodeWithTag("TaskItem:Checkbox").assertIsDisplayed() + } + + @Test + fun `should show overdue pill when isOverdue is true and not completed`() = runComposeUiTest { + setContent { + TaskItem( + title = "Task title", + isOverdue = true, + isCompleted = false, + ) + } + + onNodeWithTag("TaskItem:OverduePill").assertIsDisplayed() + onNodeWithText(getString(Res.string.task_item_overdue)).assertIsDisplayed() + } + + @Test + fun `should not show overdue pill when not overdue`() = runComposeUiTest { + setContent { + TaskItem( + title = "Task title", + isOverdue = false, + ) + } + + onNodeWithTag("TaskItem:OverduePill").assertDoesNotExist() + } + + @Test + fun `should not show overdue pill when completed`() = runComposeUiTest { + setContent { + TaskItem( + title = "Task title", + isOverdue = true, + isCompleted = true, + ) + } + + onNodeWithTag("TaskItem:OverduePill").assertDoesNotExist() + } + + @Test + fun `should show checked checkbox and distinct title when completed`() = runComposeUiTest { + setContent { + TaskItem( + title = "Task title", + isCompleted = true, + ) + } + + onNodeWithTag("TaskItem:Checkbox").assertIsDisplayed() + onNodeWithTag("TaskItem:Title").assertIsDisplayed() + } +} From 9ddd322992dbffb55102831746cf1d356f8dd191 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 20 Jul 2026 17:38:36 +0100 Subject: [PATCH 2/7] Foliary chekcbox --- .../ui/component/checkbox/FoliaryCheckbox.kt | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt new file mode 100644 index 0000000..abdf948 --- /dev/null +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt @@ -0,0 +1,82 @@ +package dev.appoutlet.foliary.core.ui.component.checkbox + +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import com.composables.icons.lucide.Check +import com.composables.icons.lucide.Lucide + +@Composable +fun FoliaryCheckbox( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + val containerColor by animateColorAsState( + targetValue = if (checked) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.background + }, + label = "FoliaryCheckboxContainerColor", + ) + + val borderColor by animateColorAsState( + targetValue = if (checked) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.outline + }, + label = "FoliaryCheckboxBorderColor", + ) + + Box( + modifier = modifier + .size(24.dp) + .clip(CircleShape) + .toggleable( + value = checked, + enabled = enabled && onCheckedChange != null, + role = Role.Checkbox, + onValueChange = { onCheckedChange?.invoke(it) }, + ) + .background(containerColor) + .border(BorderStroke(2.dp, borderColor), CircleShape), + contentAlignment = Alignment.Center, + ) { + if (checked) { + Checkmark(color = MaterialTheme.colorScheme.onPrimary) + } + } +} + +@Composable +private fun Checkmark(color: Color) { + Icon( + modifier = Modifier.size(14.dp), + imageVector = Lucide.Check, + contentDescription = null, + tint = color + ) +} From f6488926963e3108d24f2a88feb2208b15310385 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 20 Jul 2026 17:43:03 +0100 Subject: [PATCH 3/7] adjust card shadow --- .../appoutlet/foliary/core/ui/component/card/FoliaryCard.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt index d1a9d39..41f165c 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt @@ -23,9 +23,9 @@ fun FoliaryCard( shape = MaterialTheme.shapes.extraLarge, shadow = Shadow( radius = 60.dp, - spread = 0.dp, + spread = 10.dp, offset = DpOffset(0.dp, 0.dp), - color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.06f), + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f), ) ), shape = MaterialTheme.shapes.extraLarge, From 653b2c720a79baa43981d481b80493698cd09abe Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 20 Jul 2026 18:18:10 +0100 Subject: [PATCH 4/7] task item adjustments --- .../core/ui/component/card/FoliaryCard.kt | 22 ++- .../core/ui/component/task/TaskItem.kt | 125 +++++++++++------- .../appoutlet/foliary/core/ui/theme/Color.kt | 2 +- 3 files changed, 92 insertions(+), 57 deletions(-) diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt index 41f165c..d8ff852 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt @@ -3,11 +3,13 @@ package dev.appoutlet.foliary.core.ui.component.card import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.ColumnScope import androidx.compose.material3.Card +import androidx.compose.material3.CardColors import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.shadow.Shadow import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp @@ -15,6 +17,7 @@ import androidx.compose.ui.unit.dp @Composable fun FoliaryCard( modifier: Modifier = Modifier, + colors: CardColors = FoliaryCardDefaults.colors(), content: @Composable ColumnScope.() -> Unit ) { Card( @@ -27,14 +30,23 @@ fun FoliaryCard( offset = DpOffset(0.dp, 0.dp), color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f), ) - ), - shape = MaterialTheme.shapes.extraLarge, - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface, - contentColor = MaterialTheme.colorScheme.onSurface ), + shape = MaterialTheme.shapes.extraLarge, + colors = colors, border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline), elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), content = content ) } + +object FoliaryCardDefaults { + + @Composable + fun colors( + containerColor: Color = MaterialTheme.colorScheme.surface, + contentColor: Color = MaterialTheme.colorScheme.onSurface, + ): CardColors = CardDefaults.cardColors( + containerColor = containerColor, + contentColor = contentColor, + ) +} diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt index 8942753..a206469 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt @@ -1,27 +1,36 @@ package dev.appoutlet.foliary.core.ui.component.task +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Checkbox +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text 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.platform.testTag import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import com.composables.icons.lucide.Info +import com.composables.icons.lucide.Lucide +import com.composables.icons.lucide.Play import dev.appoutlet.foliary.core.ui.component.card.FoliaryCard -import dev.appoutlet.foliary.core.ui.theme.FoliaryTheme +import dev.appoutlet.foliary.core.ui.component.card.FoliaryCardDefaults +import dev.appoutlet.foliary.core.ui.component.checkbox.FoliaryCheckbox import foliary.foliary.generated.resources.Res import foliary.foliary.generated.resources.task_item_overdue import org.jetbrains.compose.resources.stringResource -import androidx.compose.ui.tooling.preview.Preview @Composable fun TaskItem( @@ -29,9 +38,30 @@ fun TaskItem( modifier: Modifier = Modifier, description: String? = null, isCompleted: Boolean = false, + onCompletedChange: (Boolean) -> Unit = { }, + onStartClick: () -> Unit = {}, isOverdue: Boolean = false, ) { - FoliaryCard(modifier = modifier.fillMaxWidth()) { + val titleColor = animateColorAsState( + targetValue = if (isCompleted) { + MaterialTheme.colorScheme.onSurface.copy(alpha = CompletedAlpha) + } else { + MaterialTheme.colorScheme.primary + }, + ) + + val cardContainerColor = animateColorAsState( + targetValue = if (isCompleted) { + MaterialTheme.colorScheme.surfaceDim + } else { + MaterialTheme.colorScheme.surface + }, + ) + + FoliaryCard( + modifier = modifier.fillMaxWidth(), + colors = FoliaryCardDefaults.colors(containerColor = cardContainerColor.value), + ) { Row( modifier = Modifier .fillMaxWidth() @@ -39,9 +69,9 @@ fun TaskItem( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - Checkbox( + FoliaryCheckbox( checked = isCompleted, - onCheckedChange = null, + onCheckedChange = onCompletedChange, modifier = Modifier.testTag("TaskItem:Checkbox"), ) @@ -53,13 +83,9 @@ fun TaskItem( text = title, modifier = Modifier.testTag("TaskItem:Title"), style = MaterialTheme.typography.titleMedium, - color = if (isCompleted) { - MaterialTheme.colorScheme.onSurface.copy(alpha = CompletedAlpha) - } else { - MaterialTheme.colorScheme.onSurface - }, + color = titleColor.value, textDecoration = if (isCompleted) TextDecoration.LineThrough else null, - maxLines = 1, + maxLines = MaxTitleLines, overflow = TextOverflow.Ellipsis, ) @@ -68,71 +94,68 @@ fun TaskItem( text = it, modifier = Modifier.testTag("TaskItem:Description"), style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = MaterialTheme.colorScheme.onBackground, maxLines = MaxDescriptionLines, overflow = TextOverflow.Ellipsis, ) } - if (isOverdue && !isCompleted) { - OverduePill() + Row { + if (isOverdue && !isCompleted) { + OverduePill() + } } } + + StartButton(onClick = onStartClick) } } } @Composable private fun OverduePill() { - Surface( + Row( modifier = Modifier .padding(top = 4.dp) + .clip(CircleShape) + .background(color = MaterialTheme.colorScheme.surfaceDim, shape = CircleShape) + .padding(horizontal = 8.dp, vertical = 4.dp) .testTag("TaskItem:OverduePill"), - shape = MaterialTheme.shapes.small, - color = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) ) { + Icon( + modifier = Modifier.size(12.dp), + imageVector = Lucide.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.error + ) + Text( text = stringResource(Res.string.task_item_overdue), - modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), + modifier = Modifier, style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.error ) } } -private const val CompletedAlpha = 0.38f -private const val MaxDescriptionLines = 3 - -@Preview @Composable -private fun TaskItemPreview() { - FoliaryTheme { - TaskItem( - title = "Write API Documentation", - description = "Draft the OpenAPI spec for the new endpoints", +private fun StartButton(onClick: () -> Unit) { + IconButton( + onClick = onClick, + colors = IconButtonDefaults.iconButtonColors( + containerColor = MaterialTheme.colorScheme.secondary ) - } -} - -@Preview -@Composable -private fun TaskItemOverduePreview() { - FoliaryTheme { - TaskItem( - title = "Pay electricity bill", - isOverdue = true, + ) { + Icon( + imageVector = Lucide.Play, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary ) } } -@Preview -@Composable -private fun TaskItemCompletedPreview() { - FoliaryTheme { - TaskItem( - title = "Inbox Zero", - description = "Process all unread emails and archive newsletters", - isCompleted = true, - ) - } -} +private const val CompletedAlpha = 0.38f +private const val MaxDescriptionLines = 3 +private const val MaxTitleLines = 2 diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/theme/Color.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/theme/Color.kt index 8310bb7..7c0e19b 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/theme/Color.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/theme/Color.kt @@ -29,7 +29,7 @@ val scrimLight = Color(0xFF000000) val inverseSurfaceLight = Color(0xFF313030) val inverseOnSurfaceLight = Color(0xFFF4F0EF) val inversePrimaryLight = Color(0xFF9ED1BD) -val surfaceDimLight = Color(0xFFDDD9D8) +val surfaceDimLight = Color(0xFFF4F3F0) val surfaceBrightLight = Color(0xFFFCF8F7) val surfaceContainerLowestLight = Color(0xFFFFFFFF) val surfaceContainerLowLight = Color(0xFFF7F3F2) From 22e61378e913c266a4607b5c7d48e57b68836b8f Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 20 Jul 2026 18:25:21 +0100 Subject: [PATCH 5/7] fix bottom bar padding --- .../foliary/core/ui/component/card/FoliaryCard.kt | 2 +- .../foliary/core/ui/component/checkbox/FoliaryCheckbox.kt | 5 ----- .../dev/appoutlet/foliary/feature/main/MainScreen.kt | 8 +++----- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt index d8ff852..da0f6ff 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/card/FoliaryCard.kt @@ -30,7 +30,7 @@ fun FoliaryCard( offset = DpOffset(0.dp, 0.dp), color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f), ) - ), + ), shape = MaterialTheme.shapes.extraLarge, colors = colors, border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline), diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt index abdf948..9758d17 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt @@ -2,7 +2,6 @@ package dev.appoutlet.foliary.core.ui.component.checkbox import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -17,10 +16,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.StrokeJoin -import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import com.composables.icons.lucide.Check diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/main/MainScreen.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/main/MainScreen.kt index dd9184f..ceea3a1 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/main/MainScreen.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/feature/main/MainScreen.kt @@ -46,18 +46,16 @@ fun MainScreen() { adaptiveInfo = currentWindowAdaptiveInfoV2(), ) - val itemTopPadding = remember(layoutType) { + val (windowDecorationPadding, itemTopPadding) = remember(layoutType) { when (layoutType) { NavigationSuiteType.NavigationRail, NavigationSuiteType.WideNavigationRailExpanded, - NavigationSuiteType.WideNavigationRailCollapsed -> 16.dp + NavigationSuiteType.WideNavigationRailCollapsed -> getWindowDecorationPadding() to 16.dp - else -> 0.dp + else -> 0.dp to 0.dp } } - val windowDecorationPadding = remember { getWindowDecorationPadding() } - NavigationSuiteScaffold( modifier = Modifier .fillMaxSize() From 30cf1a73ddc258d8e196e8ae96055fa32c2f8d69 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 20 Jul 2026 18:33:56 +0100 Subject: [PATCH 6/7] update tests --- .../core/ui/component/task/TaskItem.kt | 92 +++++++++++-------- .../core/ui/component/task/TaskItemTest.kt | 37 +++++++- 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt index a206469..67ad370 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItem.kt @@ -42,14 +42,6 @@ fun TaskItem( onStartClick: () -> Unit = {}, isOverdue: Boolean = false, ) { - val titleColor = animateColorAsState( - targetValue = if (isCompleted) { - MaterialTheme.colorScheme.onSurface.copy(alpha = CompletedAlpha) - } else { - MaterialTheme.colorScheme.primary - }, - ) - val cardContainerColor = animateColorAsState( targetValue = if (isCompleted) { MaterialTheme.colorScheme.surfaceDim @@ -75,39 +67,64 @@ fun TaskItem( modifier = Modifier.testTag("TaskItem:Checkbox"), ) - Column( + TaskItemContent( + title = title, + description = description, + isCompleted = isCompleted, + isOverdue = isOverdue, modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - Text( - text = title, - modifier = Modifier.testTag("TaskItem:Title"), - style = MaterialTheme.typography.titleMedium, - color = titleColor.value, - textDecoration = if (isCompleted) TextDecoration.LineThrough else null, - maxLines = MaxTitleLines, - overflow = TextOverflow.Ellipsis, - ) + ) - description?.let { - Text( - text = it, - modifier = Modifier.testTag("TaskItem:Description"), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onBackground, - maxLines = MaxDescriptionLines, - overflow = TextOverflow.Ellipsis, - ) - } + StartButton(onClick = onStartClick) + } + } +} - Row { - if (isOverdue && !isCompleted) { - OverduePill() - } - } - } +@Composable +private fun TaskItemContent( + title: String, + description: String?, + isCompleted: Boolean, + isOverdue: Boolean, + modifier: Modifier = Modifier, +) { + val titleColor = animateColorAsState( + targetValue = if (isCompleted) { + MaterialTheme.colorScheme.onSurface.copy(alpha = CompletedAlpha) + } else { + MaterialTheme.colorScheme.primary + }, + ) - StartButton(onClick = onStartClick) + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = title, + modifier = Modifier.testTag("TaskItem:Title"), + style = MaterialTheme.typography.titleMedium, + color = titleColor.value, + textDecoration = if (isCompleted) TextDecoration.LineThrough else null, + maxLines = MaxTitleLines, + overflow = TextOverflow.Ellipsis, + ) + + description?.let { + Text( + text = it, + modifier = Modifier.testTag("TaskItem:Description"), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground, + maxLines = MaxDescriptionLines, + overflow = TextOverflow.Ellipsis, + ) + } + + Row { + if (isOverdue && !isCompleted) { + OverduePill() + } } } } @@ -144,6 +161,7 @@ private fun OverduePill() { private fun StartButton(onClick: () -> Unit) { IconButton( onClick = onClick, + modifier = Modifier.testTag("TaskItem:StartButton"), colors = IconButtonDefaults.iconButtonColors( containerColor = MaterialTheme.colorScheme.secondary ) diff --git a/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt b/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt index cff6f8c..c665ff9 100644 --- a/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt +++ b/foliary/src/commonTest/kotlin/dev/appoutlet/foliary/core/ui/component/task/TaskItemTest.kt @@ -1,13 +1,18 @@ package dev.appoutlet.foliary.core.ui.component.task +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsOff +import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.compose.ui.test.runComposeUiTest import foliary.foliary.generated.resources.Res import foliary.foliary.generated.resources.task_item_overdue +import io.kotest.matchers.shouldBe import org.jetbrains.compose.resources.getString import kotlin.test.Test @@ -88,15 +93,39 @@ class TaskItemTest { } @Test - fun `should show checked checkbox and distinct title when completed`() = runComposeUiTest { + fun `should toggle completion state when checkbox is clicked`() = runComposeUiTest { + val completed = mutableStateOf(false) + setContent { TaskItem( title = "Task title", - isCompleted = true, + isCompleted = completed.value, + onCompletedChange = { completed.value = it }, ) } - onNodeWithTag("TaskItem:Checkbox").assertIsDisplayed() - onNodeWithTag("TaskItem:Title").assertIsDisplayed() + onNodeWithTag("TaskItem:Checkbox").assertIsOff() + + onNodeWithTag("TaskItem:Checkbox").performClick() + onNodeWithTag("TaskItem:Checkbox").assertIsOn() + + onNodeWithTag("TaskItem:Checkbox").performClick() + onNodeWithTag("TaskItem:Checkbox").assertIsOff() + } + + @Test + fun `should call onStartClick when start button is clicked`() = runComposeUiTest { + var started = false + + setContent { + TaskItem( + title = "Task title", + onStartClick = { started = true }, + ) + } + + onNodeWithTag("TaskItem:StartButton").performClick() + + started shouldBe true } } From afb0670bbaedf9b1724297c20e996e5dc41e2856 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 20 Jul 2026 18:37:09 +0100 Subject: [PATCH 7/7] animate check button --- .../foliary/core/ui/component/checkbox/FoliaryCheckbox.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt index 9758d17..8e863df 100644 --- a/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt +++ b/foliary/src/commonMain/kotlin/dev/appoutlet/foliary/core/ui/component/checkbox/FoliaryCheckbox.kt @@ -1,6 +1,9 @@ package dev.appoutlet.foliary.core.ui.component.checkbox +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -60,7 +63,7 @@ fun FoliaryCheckbox( .border(BorderStroke(2.dp, borderColor), CircleShape), contentAlignment = Alignment.Center, ) { - if (checked) { + AnimatedVisibility(visible = checked, enter = fadeIn(), exit = fadeOut()) { Checkmark(color = MaterialTheme.colorScheme.onPrimary) } }