commit 9dd4f257d8231790ca6230cb3d190c14dbcc96d6
parent e10269970580dfa8ec6b6ac2c44bbba84bc2063e
Author: triesap <tyson@radroots.org>
Date: Thu, 13 Aug 2026 16:01:25 +0000
ui: migrate HarvestCircle product content panes
- render Today, Network, and Settings with owned pane spacing
- replace isolated legacy tabs with one accessible segmented control
- preserve truthful deferred-action and local-runtime product copy
- verify product screens, scrolling, settings, and full capsule checks
Diffstat:
4 files changed, 49 insertions(+), 27 deletions(-)
diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationNetworkScreen.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationNetworkScreen.kt
@@ -2,7 +2,6 @@ package org.harvestcircle.ui.shell
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -10,12 +9,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.dp
import org.harvestcircle.application.ApplicationLifecycle
import org.harvestcircle.application.HarvestCircleShellState
import org.harvestcircle.application.RelayConnectionState
import org.harvestcircle.application.RelayDestination
import org.harvestcircle.application.SignerAvailability
+import org.harvestcircle.designsystem.component.navigation.HarvestCircleTab
+import org.harvestcircle.designsystem.component.navigation.HarvestCircleTabRow
+import org.harvestcircle.designsystem.theme.HarvestCircleTheme
enum class NetworkIdentityState { ReadOnly, Active, CredentialUnavailable, Available, SignedOut }
@@ -92,13 +93,12 @@ fun FoundationNetworkScreen(
tabs = tabs,
selected = selected,
tabRail = { available, current ->
- Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ HarvestCircleTabRow {
available.forEach { tab ->
- ShellTab(
+ HarvestCircleTab(
label = tab.label,
- description = "Show ${tab.label}",
selected = tab.key == current,
- onClick = { selected = tab.key },
+ onClick = { if (tab.key != current) selected = tab.key },
modifier = Modifier.testTag("network-tab-${tab.key.value}"),
)
}
@@ -118,7 +118,7 @@ private fun NetworkDetail(
) {
Column(
Modifier.testTag("network-${selection.value}"),
- verticalArrangement = Arrangement.spacedBy(12.dp),
+ verticalArrangement = Arrangement.spacedBy(HarvestCircleTheme.shell.layout.contentGap),
) {
when (selection.value) {
"overview" -> {
diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt
@@ -6,13 +6,15 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.dp
import org.harvestcircle.application.BuildDirtyState
import org.harvestcircle.application.BuildInfo
import org.harvestcircle.design.AppearanceState
import org.harvestcircle.design.MotionPreference
import org.harvestcircle.design.TextSizePreference
import org.harvestcircle.design.ThemePreference
+import org.harvestcircle.designsystem.component.navigation.HarvestCircleTab
+import org.harvestcircle.designsystem.component.navigation.HarvestCircleTabRow
+import org.harvestcircle.designsystem.theme.HarvestCircleTheme
import org.harvestcircle.identities.ui.HarvestCirclePlatformActions
import org.harvestcircle.navigation.SettingsSection
@@ -46,16 +48,17 @@ fun FoundationSettingsScreen(
tabs = tabs,
selected = selected,
tabRail = { available, current ->
- Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ HarvestCircleTabRow {
available.forEach { tab ->
- ShellTab(
+ HarvestCircleTab(
label = tab.label,
- description = "Show ${tab.label} settings",
selected = tab.key == current,
onClick = {
- actions.selectSection(
- if (tab.key.value == "appearance") SettingsSection.Appearance else SettingsSection.Project,
- )
+ if (tab.key != current) {
+ actions.selectSection(
+ if (tab.key.value == "appearance") SettingsSection.Appearance else SettingsSection.Project,
+ )
+ }
},
modifier = Modifier.testTag("settings-${tab.key.value}"),
)
@@ -79,7 +82,7 @@ private fun AppearanceSettings(
) {
Column(
Modifier.testTag("settings-appearance-panel"),
- verticalArrangement = Arrangement.spacedBy(16.dp),
+ verticalArrangement = Arrangement.spacedBy(HarvestCircleTheme.shell.layout.sectionGap),
) {
ShellText("Theme", textRole = ShellTextRole.SectionTitle)
OptionRow(
@@ -112,14 +115,13 @@ private fun <T : Enum<T>> OptionRow(
tagPrefix: String,
select: (T) -> Unit,
) {
- Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ HarvestCircleTabRow {
values.forEach { value ->
val label = value.label()
- ShellTab(
+ HarvestCircleTab(
label = label,
- description = "Select $label",
selected = value == selected,
- onClick = { select(value) },
+ onClick = { if (value != selected) select(value) },
modifier = Modifier.testTag("$tagPrefix-${value.name.lowercase()}"),
)
}
@@ -133,7 +135,7 @@ private fun ProjectSettings(
) {
Column(
Modifier.testTag("settings-project-panel"),
- verticalArrangement = Arrangement.spacedBy(8.dp),
+ verticalArrangement = Arrangement.spacedBy(HarvestCircleTheme.shell.layout.inlineGap),
) {
ProjectFact("HarvestCircle version", build.productVersion)
ProjectFact("Source commit", build.sourceCommit)
@@ -150,7 +152,7 @@ private fun ProjectSettings(
"${build.minimumStorageSchemaVersion}..${build.currentStorageSchemaVersion}",
)
ProjectFact("Licence", "GPL-3.0-only")
- Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ Row(horizontalArrangement = Arrangement.spacedBy(HarvestCircleTheme.shell.layout.inlineGap)) {
ShellAction("Source", "Open HarvestCircle source", "project-open-source", onClick = platformActions.openSource)
ShellAction(
"Licence",
diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt
@@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.dp
import org.harvestcircle.application.ShellFocusTarget
+import org.harvestcircle.designsystem.theme.HarvestCircleTheme
data class FoundationTodayModel(
val context: String,
@@ -21,12 +21,12 @@ fun FoundationTodayScreen(
SingleFocusTemplate {
Column(
Modifier.testTag("foundation-today"),
- verticalArrangement = Arrangement.spacedBy(16.dp),
+ verticalArrangement = Arrangement.spacedBy(HarvestCircleTheme.shell.layout.contentGap),
) {
ShellBadge(model.context, Modifier.testTag("today-context"))
ShellText("No active commitments", textRole = ShellTextRole.SectionTitle)
ShellText("Explore nearby buying circles or open a shared Nostr reference.")
- Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ Row(horizontalArrangement = Arrangement.spacedBy(HarvestCircleTheme.shell.layout.inlineGap)) {
ShellAction(
"Explore circles",
"Explore circles",
diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/MainPanelTemplates.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/MainPanelTemplates.kt
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -12,6 +13,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
+import org.harvestcircle.designsystem.layout.HarvestCirclePane
+import org.harvestcircle.designsystem.primitive.HarvestCircleSurfaceRole
+import org.harvestcircle.designsystem.theme.HarvestCircleTheme
@JvmInline
value class TemplateSelectionKey(
@@ -35,7 +39,12 @@ enum class DetailPaneKind { Network, Settings }
@Composable
fun SingleFocusTemplate(content: @Composable () -> Unit) {
- Box(Modifier.fillMaxSize().testTag("template-single-focus")) { content() }
+ HarvestCirclePane(
+ modifier = Modifier.fillMaxSize().testTag("template-single-focus"),
+ role = HarvestCircleSurfaceRole.Canvas,
+ ) {
+ content()
+ }
}
@Composable
@@ -68,8 +77,19 @@ fun TabbedDetailTemplate(
) {
require(tabs.map(TemplateTab::key).distinct().size == tabs.size)
require(tabs.any { it.key == selected })
- Column(Modifier.fillMaxSize().testTag("template-tabbed-detail")) {
- Box(Modifier.testTag("template-tabs")) { tabRail(tabs, selected) }
+ Column(
+ Modifier
+ .fillMaxSize()
+ .padding(HarvestCircleTheme.shell.layout.paneInset)
+ .testTag("template-tabbed-detail"),
+ ) {
+ Box(
+ Modifier
+ .padding(bottom = HarvestCircleTheme.shell.layout.contentGap)
+ .testTag("template-tabs"),
+ ) {
+ tabRail(tabs, selected)
+ }
val detailModifier =
Modifier
.weight(1f)