Files
lanework/KanbanMobile/UI/ItemSortOrder.swift
T
rzen e8791b239d The phone's rows learn their faces and two more orders — icons everywhere, lanes and cards sortable
Boards, lanes, and cards all show their frontmatter icon leading the
row, tinted through the palette with a secondary fallback; the board
scanner now reads icon and iconColor from the same one-file parse as
the title. Lane and card lists gain the segmented Manual/Name/Recent
control — display-only sorting layered over the rank order, never
rewriting what's on disk.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-08-08 12:34:10 -04:00

57 lines
2.3 KiB
Swift

import Foundation
/// How the lane and card lists order their rows. Raw values are persisted (`AppStorage`), so
/// they are API. `.manual` is the board's own arrangement — the rank order the loader already
/// delivered — and sorting here is display-only: it never rewrites ranks on disk.
enum ItemSortOrder: String, CaseIterable, Sendable {
case manual
case name
case recent
}
extension ItemSortOrder {
/// Orders `items` for display without touching the manual arrangement they arrived in.
///
/// **`.manual` is a no-op** — `items` is already in the loader's rank order, so this returns it
/// unchanged.
///
/// **`.name` sorts by `title`**, case/diacritic-insensitive (`localizedStandardCompare`). Callers
/// pass the row's own display title — the "Untitled Lane"/"Untitled Card" fallback already
/// applied — so there is no separate nil case to sort here.
///
/// **`.recent` sorts by `modified` descending, with `nil` last.** Both sorts tie-break the same
/// way: Swift's `sort` is not a stable sort, so ties — equal titles, equal or absent dates — keep
/// the incoming order via each item's original offset, exactly as `BoardSortOrder.sorted` does.
nonisolated func sorted<Item>(
_ items: [Item],
title: (Item) -> String,
modified: (Item) -> Date?
) -> [Item] {
switch self {
case .manual:
return items
case .name:
return items.enumerated().sorted { lhs, rhs in
let comparison = title(lhs.element).localizedStandardCompare(title(rhs.element))
if comparison != .orderedSame { return comparison == .orderedAscending }
return lhs.offset < rhs.offset
}.map(\.element)
case .recent:
return items.enumerated().sorted { lhs, rhs in
switch (modified(lhs.element), modified(rhs.element)) {
case let (l?, r?) where l != r:
return l > r
case (nil, .some):
return false
case (.some, nil):
return true
default:
return lhs.offset < rhs.offset
}
}.map(\.element)
}
}
}