Skip to content

Commit 7110f00

Browse files
authored
Manual sorting of reminders (#45)
* Manually order reminders. * wip * wip; * wip * wip * wip
1 parent 0294b78 commit 7110f00

3 files changed

Lines changed: 87 additions & 14 deletions

File tree

Examples/Reminders/RemindersListDetail.swift renamed to Examples/Reminders/RemindersDetail.swift

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import CasePaths
22
import SharingGRDB
33
import SwiftUI
44

5-
struct RemindersListDetailView: View {
5+
struct RemindersDetailView: View {
66
@FetchAll private var reminderStates: [ReminderState]
77
@AppStorage private var ordering: Ordering
88
@AppStorage private var showCompleted: Bool
@@ -46,6 +46,9 @@ struct RemindersListDetailView: View {
4646
tags: reminderState.tags
4747
)
4848
}
49+
.onMove { indexSet, index in
50+
move(from: indexSet, to: index)
51+
}
4952
}
5053
.onScrollGeometryChange(for: Bool.self) { geometry in
5154
geometry.contentOffset.y + geometry.contentInsets.top > navigationTitleHeight
@@ -126,13 +129,38 @@ struct RemindersListDetailView: View {
126129
}
127130
}
128131

132+
func move(from source: IndexSet, to destination: Int) {
133+
withErrorReporting {
134+
try database.write { db in
135+
var ids = reminderStates.map(\.reminder.id)
136+
ids.move(fromOffsets: source, toOffset: destination)
137+
try Reminder
138+
.where { $0.id.in(ids) }
139+
.update {
140+
let ids = Array(ids.enumerated())
141+
let (first, rest) = (ids.first!, ids.dropFirst())
142+
$0.position =
143+
rest
144+
.reduce(Case($0.id).when(first.element, then: first.offset)) { cases, id in
145+
cases.when(id.element, then: id.offset)
146+
}
147+
.else($0.position)
148+
}
149+
.execute(db)
150+
}
151+
}
152+
ordering = .manual
153+
}
154+
129155
private enum Ordering: String, CaseIterable {
130156
case dueDate = "Due Date"
157+
case manual = "Manual"
131158
case priority = "Priority"
132159
case title = "Title"
133160
var icon: Image {
134161
switch self {
135162
case .dueDate: Image(systemName: "calendar")
163+
case .manual: Image(systemName: "hand.draw")
136164
case .priority: Image(systemName: "chart.bar.fill")
137165
case .title: Image(systemName: "textformat.characters")
138166
}
@@ -157,7 +185,7 @@ struct RemindersListDetailView: View {
157185

158186
fileprivate var remindersQuery: some StructuredQueriesCore.Statement<ReminderState> {
159187
let query =
160-
Reminder
188+
Reminder
161189
.where {
162190
if !showCompleted {
163191
!$0.isCompleted
@@ -167,6 +195,7 @@ struct RemindersListDetailView: View {
167195
.order {
168196
switch ordering {
169197
case .dueDate: $0.dueDate
198+
case .manual: $0.position
170199
case .priority: ($0.priority.desc(), $0.isFlagged.desc())
171200
case .title: $0.title
172201
}
@@ -208,7 +237,7 @@ struct RemindersListDetailView: View {
208237
}
209238
}
210239

211-
extension RemindersListDetailView.DetailType {
240+
extension RemindersDetailView.DetailType {
212241
fileprivate var id: String {
213242
switch self {
214243
case .all: "all"
@@ -249,7 +278,7 @@ extension RemindersListDetailView.DetailType {
249278
}
250279
}
251280

252-
struct RemindersListDetailPreview: PreviewProvider {
281+
struct RemindersDetailPreview: PreviewProvider {
253282
static var previews: some View {
254283
let (remindersList, tag) = try! prepareDependencies {
255284
$0.defaultDatabase = try Reminders.appDatabase()
@@ -260,14 +289,14 @@ struct RemindersListDetailPreview: PreviewProvider {
260289
)
261290
}
262291
}
263-
let detailTypes: [RemindersListDetailView.DetailType] = [
292+
let detailTypes: [RemindersDetailView.DetailType] = [
264293
.all,
265294
.list(remindersList),
266295
.tags([tag]),
267296
]
268297
ForEach(detailTypes, id: \.self) { detailType in
269298
NavigationStack {
270-
RemindersListDetailView(detailType: detailType)
299+
RemindersDetailView(detailType: detailType)
271300
}
272301
.previewDisplayName(detailType.navigationTitle)
273302
}

Examples/Reminders/RemindersLists.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct RemindersListsView: View {
3737
private var stats = Stats()
3838

3939
@State private var destination: Destination?
40-
@State private var remindersDetailType: RemindersListDetailView.DetailType?
40+
@State private var remindersDetailType: RemindersDetailView.DetailType?
4141
@State private var searchText = ""
4242

4343
@Dependency(\.defaultDatabase) private var database
@@ -124,13 +124,13 @@ struct RemindersListsView: View {
124124
Section {
125125
ForEach(remindersLists) { state in
126126
NavigationLink {
127-
RemindersListDetailView(detailType: .list(state.remindersList))
127+
RemindersDetailView(detailType: .list(state.remindersList))
128128
} label: {
129129
RemindersListRow(
130130
remindersCount: state.remindersCount,
131131
remindersList: state.remindersList
132132
)
133-
}
133+
}
134134
}
135135
.onMove { indexSet, index in
136136
move(from: indexSet, to: index)
@@ -148,7 +148,7 @@ struct RemindersListsView: View {
148148
Section {
149149
ForEach(tags) { tag in
150150
NavigationLink {
151-
RemindersListDetailView(detailType: .tags([tag]))
151+
RemindersDetailView(detailType: .tags([tag]))
152152
} label: {
153153
TagRow(tag: tag)
154154
}
@@ -211,7 +211,7 @@ struct RemindersListsView: View {
211211
}
212212
.searchable(text: $searchText)
213213
.navigationDestination(item: $remindersDetailType) { detailType in
214-
RemindersListDetailView(detailType: detailType)
214+
RemindersDetailView(detailType: detailType)
215215
}
216216
}
217217

@@ -225,7 +225,8 @@ struct RemindersListsView: View {
225225
.update {
226226
let ids = Array(ids.enumerated())
227227
let (first, rest) = (ids.first!, ids.dropFirst())
228-
$0.position = rest
228+
$0.position =
229+
rest
229230
.reduce(Case($0.id).when(first.element, then: first.offset)) { cases, id in
230231
cases.when(id.element, then: id.offset)
231232
}

Examples/Reminders/Schema.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct Reminder: Equatable, Identifiable {
2323
var notes = ""
2424
var priority: Priority?
2525
var remindersListID: Int
26+
var position = 0
2627
var title = ""
2728
}
2829

@@ -86,17 +87,21 @@ struct ReminderTag: Hashable, Identifiable {
8687
}
8788

8889
func appDatabase() throws -> any DatabaseWriter {
90+
@Dependency(\.context) var context
8991
let database: any DatabaseWriter
9092
var configuration = Configuration()
9193
configuration.foreignKeysEnabled = true
9294
configuration.prepareDatabase { db in
9395
#if DEBUG
9496
db.trace(options: .profile) {
95-
logger.debug("\($0.expandedDescription)")
97+
if context == .live {
98+
logger.debug("\($0.expandedDescription)")
99+
} else {
100+
print("\($0.expandedDescription)")
101+
}
96102
}
97103
#endif
98104
}
99-
@Dependency(\.context) var context
100105
if context == .live {
101106
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
102107
logger.info("open \(path)")
@@ -179,6 +184,44 @@ func appDatabase() throws -> any DatabaseWriter {
179184
)
180185
.execute(db)
181186
}
187+
migrator.registerMigration("Add 'position' column to 'reminders'") { db in
188+
try #sql(
189+
"""
190+
ALTER TABLE "reminders"
191+
ADD COLUMN "position" INTEGER NOT NULL DEFAULT 0
192+
"""
193+
)
194+
.execute(db)
195+
// Backfill position of reminders based on their completion status and due date.
196+
try #sql(
197+
"""
198+
WITH "reminderPositions" AS (
199+
SELECT
200+
"reminders"."id",
201+
ROW_NUMBER() OVER (PARTITION BY "remindersListID" ORDER BY id) - 1 AS "position"
202+
FROM "reminders"
203+
ORDER BY NOT "isCompleted", "dueDate" DESC
204+
)
205+
UPDATE "reminders"
206+
SET "position" = "reminderPositions"."position"
207+
FROM "reminderPositions"
208+
WHERE "reminders"."id" = "reminderPositions"."id"
209+
"""
210+
)
211+
.execute(db)
212+
try #sql(
213+
"""
214+
CREATE TRIGGER "default_position_reminders"
215+
AFTER INSERT ON "reminders"
216+
FOR EACH ROW BEGIN
217+
UPDATE "reminders"
218+
SET "position" = (SELECT max("position") + 1 FROM "reminders")
219+
WHERE "id" = NEW."id";
220+
END
221+
"""
222+
)
223+
.execute(db)
224+
}
182225
#if DEBUG && targetEnvironment(simulator)
183226
if context != .test {
184227
migrator.registerMigration("Seed sample data") { db in

0 commit comments

Comments
 (0)