Add category and recipe sorting with multiple modes and order inversion
Categories on the main page can be sorted by Recently Used, Alphabetical, or Manual (drag-to-reorder). The sort menu appears inline next to the Categories header. All Recipes is included in the sort order and manual reorder sheet. Recipes within category and all-recipes lists can be sorted by Recently Added or Alphabetical, with the sort button in the toolbar. All non-manual sort modes support order inversion via a Reverse/Default Order toggle. Date parsing handles both formatted strings and Unix timestamps, with recipe_id as fallback when dates are unavailable. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// CategoryReorderSheet.swift
|
||||
// Nextcloud Cookbook iOS Client
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CategoryReorderSheet: View {
|
||||
@EnvironmentObject var appState: AppState
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
private static let allRecipesSentinel = "__ALL_RECIPES__"
|
||||
|
||||
@State private var orderedNames: [String] = []
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(orderedNames, id: \.self) { name in
|
||||
HStack {
|
||||
if name == Self.allRecipesSentinel {
|
||||
Text("All Recipes")
|
||||
.bold()
|
||||
} else {
|
||||
Text(name)
|
||||
}
|
||||
Spacer()
|
||||
if name == Self.allRecipesSentinel {
|
||||
let total = appState.categories.reduce(0) { $0 + $1.recipe_count }
|
||||
Text("\(total)")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.subheadline)
|
||||
} else if let count = appState.categories.first(where: { $0.name == name })?.recipe_count {
|
||||
Text("\(count)")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onMove { from, to in
|
||||
orderedNames.move(fromOffsets: from, toOffset: to)
|
||||
}
|
||||
}
|
||||
.environment(\.editMode, .constant(.active))
|
||||
.navigationTitle(String(localized: "Reorder Categories"))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(String(localized: "Cancel")) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(String(localized: "Done")) {
|
||||
appState.updateManualCategoryOrder(orderedNames)
|
||||
dismiss()
|
||||
}
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let currentCategoryNames = appState.categories
|
||||
.filter { $0.recipe_count > 0 }
|
||||
.map { $0.name }
|
||||
|
||||
let totalCount = appState.categories.reduce(0) { $0 + $1.recipe_count }
|
||||
|
||||
let existing = appState.manualCategoryOrder
|
||||
|
||||
// Keep only names that still exist on the server (or are the sentinel)
|
||||
var reconciled = existing.filter {
|
||||
$0 == Self.allRecipesSentinel || currentCategoryNames.contains($0)
|
||||
}
|
||||
|
||||
// Ensure the All Recipes sentinel is present
|
||||
if totalCount > 0 && !reconciled.contains(Self.allRecipesSentinel) {
|
||||
reconciled.insert(Self.allRecipesSentinel, at: 0)
|
||||
}
|
||||
|
||||
// Append any new categories not yet in the manual order
|
||||
for name in currentCategoryNames where !reconciled.contains(name) {
|
||||
reconciled.append(name)
|
||||
}
|
||||
|
||||
orderedNames = reconciled
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user