Skip to content

Commit

Permalink
Implement 'top tags' feature. (#20)
Browse files Browse the repository at this point in the history
* Implement 'top tags' feature.

* clean up
  • Loading branch information
mbrandonw authored Feb 21, 2025
1 parent 1093770 commit d4e2002
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 40 deletions.
41 changes: 1 addition & 40 deletions Examples/Reminders/ReminderForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct ReminderFormView: View {
}
.popover(isPresented: $isPresentingTagsPopover) {
NavigationStack {
TagsPopover(selectedTags: $selectedTags)
TagsView(selectedTags: $selectedTags)
}
}

Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,15 @@ func appDatabase() 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)
}
Expand Down
111 changes: 111 additions & 0 deletions Examples/Reminders/TagsForm.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import SharingGRDB
import SwiftUI

struct TagsView: View {
@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)
}

0 comments on commit d4e2002

Please sign in to comment.