app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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:
Mapp/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationNetworkScreen.kt | 14+++++++-------
Mapp/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt | 30++++++++++++++++--------------
Mapp/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationTodayScreen.kt | 6+++---
Mapp/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/MainPanelTemplates.kt | 26+++++++++++++++++++++++---
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)