From 49b2f4db6e244def4e54678a84e79802b7d81494 Mon Sep 17 00:00:00 2001 From: rzen Date: Sat, 8 Aug 2026 12:42:44 -0400 Subject: [PATCH] =?UTF-8?q?The=20sort=20control=20leaves=20the=20list=20?= =?UTF-8?q?=E2=80=94=20three=20pickers=20settle=20into=20the=20bottom=20ba?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same segments, same persistence; the picker now rides each screen's toolbar as a bottom-bar item shown only when there are rows to reorder, instead of masquerading as the first row of the list it sorts. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY --- KanbanMobile/CHANGELOG.md | 2 +- KanbanMobile/Screens/BoardScreen.swift | 30 ++++++++++++++++-------- KanbanMobile/Screens/BoardsTabView.swift | 28 +++++++++++++++------- KanbanMobile/Screens/LaneScreen.swift | 30 ++++++++++++++++-------- 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/KanbanMobile/CHANGELOG.md b/KanbanMobile/CHANGELOG.md index ae06350..007f521 100644 --- a/KanbanMobile/CHANGELOG.md +++ b/KanbanMobile/CHANGELOG.md @@ -4,7 +4,7 @@ Sort lanes and cards by your own arrangement, name, or most recent change. Boards, lanes, and cards now show their icon and color in the lists. -Sort your board list by name or by most recently changed with the new control above the list. +Sort your board list by name or by most recently changed with the sort control in the toolbar. Version 1.0: Lanework comes to iPhone — browse your boards from iCloud Drive, move cards between lanes, and edit them on the go. diff --git a/KanbanMobile/Screens/BoardScreen.swift b/KanbanMobile/Screens/BoardScreen.swift index 4c48fb0..f43debe 100644 --- a/KanbanMobile/Screens/BoardScreen.swift +++ b/KanbanMobile/Screens/BoardScreen.swift @@ -5,7 +5,7 @@ import SwiftUI /// Holds only `boardRoot`, never a `BoardSummary`/`BoardModel` value: the session and its /// snapshot are pulled from the environment's index store fresh on every body evaluation, so a /// write from anywhere in the stack (a lane created, a card moved) reaches this screen the moment -/// the session's reload lands. A segmented control above the lane rows lets the list be sorted by +/// the session's reload lands. A segmented control in the toolbar lets the list be sorted by /// name or by most recent change, on top of the board's own manual arrangement, remembered across /// launches. struct BoardScreen: View { @@ -39,6 +39,9 @@ struct BoardScreen: View { } } } + .toolbar { + sortToolbarItem + } .task { session.open() } .onDisappear { // Stops the materializing/downloading retry timer. In practice a no-op here: a @@ -73,6 +76,22 @@ struct BoardScreen: View { return title } + /// Shown only once there is something to sort — an empty or not-yet-loaded lane list has no + /// rows for the segments to reorder. + @ToolbarContentBuilder + private var sortToolbarItem: some ToolbarContent { + if case .ready = session.phase, let snapshot = session.snapshot, !snapshot.lanes.isEmpty { + ToolbarItem(placement: .bottomBar) { + Picker("Sort", selection: sortOrder) { + Text("Manual").tag(ItemSortOrder.manual) + Text("Name").tag(ItemSortOrder.name) + Text("Recent").tag(ItemSortOrder.recent) + } + .pickerStyle(.segmented) + } + } + } + @ViewBuilder private var content: some View { switch session.phase { @@ -123,15 +142,6 @@ struct BoardScreen: View { description: Text("Add a lane to start organizing cards.") ) } else { - Picker("Sort", selection: sortOrder) { - Text("Manual").tag(ItemSortOrder.manual) - Text("Name").tag(ItemSortOrder.name) - Text("Recent").tag(ItemSortOrder.recent) - } - .pickerStyle(.segmented) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - // The trusted order is `.manual`'s base — `BoardModel.lanes` is already in display // order (the loader's `Ranks.sortedForDisplay`) — and `.name`/`.recent` layer a // display-only re-sort on top, never touching that base or what's on disk. diff --git a/KanbanMobile/Screens/BoardsTabView.swift b/KanbanMobile/Screens/BoardsTabView.swift index 5b0f54d..f81f30b 100644 --- a/KanbanMobile/Screens/BoardsTabView.swift +++ b/KanbanMobile/Screens/BoardsTabView.swift @@ -6,7 +6,7 @@ import SwiftUI /// Renders both of `BoardIndexStore.Phase`'s live states and, above the list, the iCloud notice — /// which is a row, not a wall (softened 2026-08-08). A phone with no account still has a device home /// and therefore still has boards, so the missing half of the app is reported next to the half that -/// works rather than in place of it. A segmented control above the rows lets the list be sorted by +/// works rather than in place of it. A segmented control in the toolbar lets the list be sorted by /// name or by most recent change, remembered across launches. struct BoardsTabView: View { @Environment(BoardIndexStore.self) private var index @@ -32,6 +32,9 @@ struct BoardsTabView: View { } } } + .toolbar { + sortToolbarItem + } .navigationDestination(for: BoardRoute.self) { route in switch route { case let .lanes(boardRoot): @@ -73,6 +76,21 @@ struct BoardsTabView: View { sortOrder.wrappedValue.sorted(index.boards) } + /// Shown only once there is something to sort — an empty or not-yet-loaded list has no rows + /// for the segments to reorder. + @ToolbarContentBuilder + private var sortToolbarItem: some ToolbarContent { + if case .ready = index.phase, !index.boards.isEmpty { + ToolbarItem(placement: .bottomBar) { + Picker("Sort", selection: sortOrder) { + Text("Name").tag(BoardSortOrder.name) + Text("Recent").tag(BoardSortOrder.recent) + } + .pickerStyle(.segmented) + } + } + } + @ViewBuilder private var content: some View { switch index.phase { @@ -93,14 +111,6 @@ struct BoardsTabView: View { ) } } else { - Picker("Sort", selection: sortOrder) { - Text("Name").tag(BoardSortOrder.name) - Text("Recent").tag(BoardSortOrder.recent) - } - .pickerStyle(.segmented) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - ForEach(sortedBoards) { board in NavigationLink(value: BoardRoute.lanes(boardRoot: board.rootURL)) { BoardSummaryRow(board: board) diff --git a/KanbanMobile/Screens/LaneScreen.swift b/KanbanMobile/Screens/LaneScreen.swift index 4c9a82c..dd60dfa 100644 --- a/KanbanMobile/Screens/LaneScreen.swift +++ b/KanbanMobile/Screens/LaneScreen.swift @@ -6,7 +6,7 @@ import SwiftUI /// `session.snapshot` on every body evaluation, so a write this screen makes — or one relayed /// from elsewhere through the metadata query — reaches the list the moment the session's reload /// lands, and a lane deleted on another device is noticed rather than shown stale. A segmented -/// control above the card rows lets the list be sorted by name or by most recent change, on top +/// control in the toolbar lets the list be sorted by name or by most recent change, on top /// of the lane's own manual arrangement, remembered across launches. struct LaneScreen: View { let boardRoot: URL @@ -47,6 +47,9 @@ struct LaneScreen: View { } } } + .toolbar { + sortToolbarItem + } .task { session.open() } .titlePromptAlert("New Card", isPresented: $isPresentingNewCard, placeholder: "Card Title") { title in createCard(titled: title.isEmpty ? nil : title) @@ -88,6 +91,22 @@ struct LaneScreen: View { return title } + /// Shown only once there is something to sort — an empty or not-yet-loaded card list has no + /// rows for the segments to reorder. + @ToolbarContentBuilder + private var sortToolbarItem: some ToolbarContent { + if let lane, !lane.cards.isEmpty { + ToolbarItem(placement: .bottomBar) { + Picker("Sort", selection: sortOrder) { + Text("Manual").tag(ItemSortOrder.manual) + Text("Name").tag(ItemSortOrder.name) + Text("Recent").tag(ItemSortOrder.recent) + } + .pickerStyle(.segmented) + } + } + } + @ViewBuilder private var content: some View { if let lane { @@ -106,15 +125,6 @@ struct LaneScreen: View { description: Text("Add a card to this lane.") ) } else { - Picker("Sort", selection: sortOrder) { - Text("Manual").tag(ItemSortOrder.manual) - Text("Name").tag(ItemSortOrder.name) - Text("Recent").tag(ItemSortOrder.recent) - } - .pickerStyle(.segmented) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - // The trusted order is `.manual`'s base — `Lane.cards` is already in display // order, exactly as the lane list trusts `BoardModel.lanes` — and `.name`/ // `.recent` layer a display-only re-sort on top, never touching that base or