From d5b12531a62255a49ff281ba32fa9a12680168da Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Thu, 20 Feb 2025 19:51:48 -0800 Subject: [PATCH 1/2] Implement 'top tags' feature. --- Examples/Reminders/ReminderForm.swift | 41 +--------- Examples/Reminders/Schema.swift | 5 ++ Examples/Reminders/TagsForm.swift | 111 ++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 40 deletions(-) create mode 100644 Examples/Reminders/TagsForm.swift diff --git a/Examples/Reminders/ReminderForm.swift b/Examples/Reminders/ReminderForm.swift index 6808bd8..9507a8b 100644 --- a/Examples/Reminders/ReminderForm.swift +++ b/Examples/Reminders/ReminderForm.swift @@ -57,7 +57,7 @@ struct ReminderFormView: View { } .popover(isPresented: $isPresentingTagsPopover) { NavigationStack { - TagsPopover(selectedTags: $selectedTags) + TagsView(selectedTags: $selectedTags) } } @@ -185,45 +185,6 @@ extension Optional { } } -struct TagsPopover: View { - @SharedReader(.fetchAll(sql: #"SELECT * FROM "tags" ORDER BY "name" ASC"#)) - var availableTags: [Tag] - - @Binding var selectedTags: [Tag] - - @Environment(\.dismiss) var dismiss - - var body: some View { - List { - let selectedTagIDs = Set(selectedTags.map(\.id)) - ForEach(availableTags, id: \.id) { tag in - let tagIsSelected = selectedTagIDs.contains(tag.id) - Button { - if tagIsSelected { - selectedTags.removeAll(where: { $0.id == tag.id }) - } else { - selectedTags.append(tag) - } - } label: { - HStack { - if tagIsSelected { - Image.init(systemName: "checkmark") - } - Text(tag.name) - } - } - .tint(tagIsSelected ? .blue : .black) - } - } - .toolbar { - ToolbarItem { - Button("Done") { dismiss() } - } - } - .navigationTitle(Text("Tags")) - } -} - #Preview { let (remindersList, reminder) = try! prepareDependencies { $0.defaultDatabase = try Reminders.appDatabase(inMemory: true) diff --git a/Examples/Reminders/Schema.swift b/Examples/Reminders/Schema.swift index b2ff595..7c0416d 100644 --- a/Examples/Reminders/Schema.swift +++ b/Examples/Reminders/Schema.swift @@ -215,10 +215,15 @@ func appDatabase(inMemory: Bool = false) throws -> any DatabaseWriter { _ = try Tag(name: "kids").inserted(self) _ = try Tag(name: "someday").inserted(self) _ = try Tag(name: "optional").inserted(self) + _ = try Tag(name: "social").inserted(self) + _ = try Tag(name: "night").inserted(self) + _ = try Tag(name: "adulting").inserted(self) _ = try ReminderTag(reminderID: 1, tagID: 3).inserted(self) _ = try ReminderTag(reminderID: 1, tagID: 4).inserted(self) + _ = try ReminderTag(reminderID: 1, tagID: 7).inserted(self) _ = try ReminderTag(reminderID: 2, tagID: 3).inserted(self) _ = try ReminderTag(reminderID: 2, tagID: 4).inserted(self) + _ = try ReminderTag(reminderID: 3, tagID: 7).inserted(self) _ = try ReminderTag(reminderID: 4, tagID: 1).inserted(self) _ = try ReminderTag(reminderID: 4, tagID: 2).inserted(self) } diff --git a/Examples/Reminders/TagsForm.swift b/Examples/Reminders/TagsForm.swift new file mode 100644 index 0000000..bb869ee --- /dev/null +++ b/Examples/Reminders/TagsForm.swift @@ -0,0 +1,111 @@ +import SharingGRDB +import SwiftUI + +struct TagsView: View { + @SharedReader(.fetchAll(sql: #"SELECT * FROM "tags" ORDER BY "name" ASC"#)) + var availableTags: [Tag] + @SharedReader(.fetch(Tags())) var tags = Tags.Value() + @Binding var selectedTags: [Tag] + + @Environment(\.dismiss) var dismiss + + var body: some View { + Form { + let selectedTagIDs = Set(selectedTags.map(\.id)) + if !tags.top.isEmpty { + Section { + ForEach(tags.top, id: \.id) { tag in + TagView( + isSelected: selectedTagIDs.contains(tag.id), + selectedTags: $selectedTags, + tag: tag + ) + } + } header: { + Text("Top tags") + } + } + if !tags.rest.isEmpty { + Section { + ForEach(tags.rest, id: \.id) { tag in + TagView( + isSelected: selectedTagIDs.contains(tag.id), + selectedTags: $selectedTags, + tag: tag + ) + } + } + } + } + .toolbar { + ToolbarItem { + Button("Done") { dismiss() } + } + } + .navigationTitle(Text("Tags")) + } + + struct Tags: FetchKeyRequest { + func fetch(_ db: Database) throws -> Value { + let top = try Tag.fetchAll( + db, + sql: """ + SELECT "tags".*, count("reminders"."id") + FROM "tags" + LEFT JOIN "remindersTags" ON "tags"."id" = "remindersTags"."tagID" + LEFT JOIN "reminders" ON "remindersTags"."reminderID" = "reminders"."id" + GROUP BY "tags"."id" + HAVING count("reminders"."id") > 0 + ORDER BY count("reminders"."id") DESC, "name" + LIMIT 3 + """) + let rest = try Tag.fetchAll( + db, + SQLRequest(literal: """ + SELECT "tags".* + FROM "tags" + WHERE "id" NOT IN \(top.compactMap(\.id)) + ORDER BY "name" + """) + ) + return Value(rest: rest, top: top) + } + struct Value { + var rest: [Tag] = [] + var top: [Tag] = [] + } + } +} + +private struct TagView: View { + let isSelected: Bool + @Binding var selectedTags: [Tag] + let tag: Tag + + var body: some View { + Button { + if isSelected { + selectedTags.removeAll(where: { $0.id == tag.id }) + } else { + selectedTags.append(tag) + } + } label: { + HStack { + if isSelected { + Image.init(systemName: "checkmark") + } + Text(tag.name) + } + } + .tint(isSelected ? .blue : .black) + } +} + +#Preview { + @Previewable @State var tags: [Tag] = [] + let _ = prepareDependencies { + $0.defaultDatabase = try! Reminders.appDatabase(inMemory: true) + } + + TagsView(selectedTags: $tags) +} From 1de293ba0e448dbfad158b3f643033a04420366a Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 21 Feb 2025 09:40:51 -0800 Subject: [PATCH 2/2] clean up --- Examples/Reminders/TagsForm.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Examples/Reminders/TagsForm.swift b/Examples/Reminders/TagsForm.swift index bb869ee..a387a2d 100644 --- a/Examples/Reminders/TagsForm.swift +++ b/Examples/Reminders/TagsForm.swift @@ -2,8 +2,6 @@ import SharingGRDB import SwiftUI struct TagsView: View { - @SharedReader(.fetchAll(sql: #"SELECT * FROM "tags" ORDER BY "name" ASC"#)) - var availableTags: [Tag] @SharedReader(.fetch(Tags())) var tags = Tags.Value() @Binding var selectedTags: [Tag] @@ -52,8 +50,10 @@ struct TagsView: View { sql: """ SELECT "tags".*, count("reminders"."id") FROM "tags" - LEFT JOIN "remindersTags" ON "tags"."id" = "remindersTags"."tagID" - LEFT JOIN "reminders" ON "remindersTags"."reminderID" = "reminders"."id" + LEFT JOIN "remindersTags" + ON "tags"."id" = "remindersTags"."tagID" + LEFT JOIN "reminders" + ON "remindersTags"."reminderID" = "reminders"."id" GROUP BY "tags"."id" HAVING count("reminders"."id") > 0 ORDER BY count("reminders"."id") DESC, "name"