Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Examples/Reminders/ReminderForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ struct ReminderFormView: View {
}
}
Picker(selection: $reminder.priority) {
Text("None").tag(Priority?.none)
Text("None").tag(Reminder.Priority?.none)
Divider()
Text("High").tag(Priority.high)
Text("Medium").tag(Priority.medium)
Text("Low").tag(Priority.low)
Text("High").tag(Reminder.Priority.high)
Text("Medium").tag(Reminder.Priority.medium)
Text("Low").tag(Reminder.Priority.low)
} label: {
HStack {
Image(systemName: "exclamationmark.circle.fill")
Expand Down
37 changes: 7 additions & 30 deletions Examples/Reminders/ReminderRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ struct ReminderRow: View {
let title: String?

@State var editReminder: Reminder.Draft?
@State var isCompleted: Bool

@Dependency(\.defaultDatabase) private var database

Expand All @@ -34,14 +33,13 @@ struct ReminderRow: View {
self.showCompleted = showCompleted
self.tags = tags
self.title = title
self.isCompleted = reminder.isCompleted
}

var body: some View {
HStack {
HStack(alignment: .firstTextBaseline) {
Button(action: completeButtonTapped) {
Image(systemName: isCompleted ? "circle.inset.filled" : "circle")
Image(systemName: reminder.isCompleted ? "circle.inset.filled" : "circle")
.foregroundStyle(.gray)
.font(.title2)
.padding([.trailing], 5)
Expand All @@ -59,7 +57,7 @@ struct ReminderRow: View {
}
}
Spacer()
if !isCompleted {
if !reminder.isCompleted {
HStack {
if reminder.isFlagged {
Image(systemName: "flag.fill")
Expand Down Expand Up @@ -104,36 +102,15 @@ struct ReminderRow: View {
.navigationTitle("Details")
}
}
.task(id: isCompleted) {
guard !showCompleted else { return }
guard
isCompleted,
isCompleted != reminder.isCompleted
else { return }
do {
try await Task.sleep(for: .seconds(2))
toggleCompletion()
} catch {}
}
}

private func completeButtonTapped() {
if showCompleted {
toggleCompletion()
} else {
isCompleted.toggle()
}
}

private func toggleCompletion() {
withErrorReporting {
try database.write { db in
isCompleted =
try Reminder
try Reminder
.find(reminder.id)
.update { $0.isCompleted.toggle() }
.returning(\.isCompleted)
.fetchOne(db) ?? isCompleted
.update { $0.toggleStatus() }
.execute(db)
}
}
}
Expand Down Expand Up @@ -161,10 +138,10 @@ struct ReminderRow: View {
HStack(alignment: .firstTextBaseline) {
if let priority = reminder.priority {
Text(String(repeating: "!", count: priority.rawValue))
.foregroundStyle(isCompleted ? .gray : remindersList.color)
.foregroundStyle(reminder.isCompleted ? .gray : remindersList.color)
}
highlight(title ?? reminder.title)
.foregroundStyle(isCompleted ? .gray : .primary)
.foregroundStyle(reminder.isCompleted ? .gray : .primary)
}
.font(.title3)
}
Expand Down
10 changes: 8 additions & 2 deletions Examples/Reminders/RemindersDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,16 @@ class RemindersDetailModel: HashableObject {
Reminder
.where {
if !showCompleted {
!$0.isCompleted
$0.status.neq(Reminder.Status.completed)
}
}
.order {
if showCompleted {
$0.isCompleted
} else {
$0.status.eq(Reminder.Status.completed)
}
}
.order(by: \.isCompleted)
.order {
switch ordering {
case .dueDate: $0.dueDate.asc(nulls: .last)
Expand Down
75 changes: 62 additions & 13 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import IssueReporting
import OSLog
import SQLiteData
import SwiftUI
import Synchronization

@Table
struct RemindersList: Hashable, Identifiable {
Expand Down Expand Up @@ -31,13 +32,33 @@ struct RemindersListAsset: Hashable, Identifiable {
struct Reminder: Hashable, Identifiable {
let id: UUID
var dueDate: Date?
var isCompleted = false
var isFlagged = false
var notes = ""
var position = 0
var priority: Priority?
var remindersListID: RemindersList.ID
var status: Status = .incomplete
var title = ""
var isCompleted: Bool {
status != .incomplete
}
enum Priority: Int, QueryBindable {
case low = 1
case medium
case high
}
enum Status: Int, QueryBindable {
case completed = 1
case completing = 2
case incomplete = 0
}
}
extension Updates<Reminder> {
mutating func toggleStatus() {
self.status = Case(self.status)
.when(Reminder.Status.incomplete, then: Reminder.Status.completing)
.else(Reminder.Status.incomplete)
}
}

extension Reminder.Draft: Identifiable {}
Expand All @@ -49,12 +70,6 @@ struct Tag: Hashable, Identifiable {
var id: String { title }
}

enum Priority: Int, QueryBindable {
case low = 1
case medium
case high
}

extension Reminder {
static let incomplete = Self.where { !$0.isCompleted }
static let withTags = group(by: \.id)
Expand All @@ -63,6 +78,9 @@ extension Reminder {
}

extension Reminder.TableColumns {
var isCompleted: some QueryExpression<Bool> {
status.neq(Reminder.Status.incomplete)
}
var isPastDue: some QueryExpression<Bool> {
@Dependency(\.date.now) var now
return !isCompleted && #sql("coalesce(date(\(dueDate)) < date(\(now)), 0)")
Expand Down Expand Up @@ -117,6 +135,7 @@ func appDatabase() throws -> any DatabaseWriter {
configuration.foreignKeysEnabled = true
configuration.prepareDatabase { db in
try db.attachMetadatabase()
db.add(function: $handleReminderStatusUpdate)
#if DEBUG
db.trace(options: .profile) {
if context == .live {
Expand Down Expand Up @@ -166,12 +185,12 @@ func appDatabase() throws -> any DatabaseWriter {
CREATE TABLE "reminders" (
"id" TEXT PRIMARY KEY NOT NULL ON CONFLICT REPLACE DEFAULT (uuid()),
"dueDate" TEXT,
"isCompleted" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0,
"isFlagged" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0,
"notes" TEXT NOT NULL ON CONFLICT REPLACE DEFAULT '',
"position" INTEGER NOT NULL ON CONFLICT REPLACE DEFAULT 0,
"priority" INTEGER,
"remindersListID" TEXT NOT NULL REFERENCES "remindersLists"("id") ON DELETE CASCADE,
"status" INTEGER NOT NULL DEFAULT 0,
"title" TEXT NOT NULL ON CONFLICT REPLACE DEFAULT ''
) STRICT
"""
Expand Down Expand Up @@ -313,6 +332,17 @@ func appDatabase() throws -> any DatabaseWriter {
)
.execute(db)

try Reminder.createTemporaryTrigger(
after: .update {
$0.status
} forEachRow: { _, _ in
Values($handleReminderStatusUpdate())
} when: { _, new in
new.status.eq(Reminder.Status.completing)
}
)
.execute(db)

if context != .live {
try db.seedSampleData()
}
Expand All @@ -321,6 +351,25 @@ func appDatabase() throws -> any DatabaseWriter {
return database
}

let reminderStatusMutex = Mutex<Task<Void, any Error>?>(nil)
@DatabaseFunction
func handleReminderStatusUpdate() {
reminderStatusMutex.withLock {
$0?.cancel()
$0 = Task {
@Dependency(\.defaultDatabase) var database
@Dependency(\.continuousClock) var clock
try await clock.sleep(for: .seconds(5))
try await database.write { db in
try Reminder
.where { $0.status.eq(Reminder.Status.completing) }
.update { $0.status = .completed }
.execute(db)
}
}
}
}

private let logger = Logger(subsystem: "Reminders", category: "Database")

#if DEBUG
Expand Down Expand Up @@ -370,8 +419,8 @@ private let logger = Logger(subsystem: "Reminders", category: "Database")
Reminder(
id: reminderIDs[3],
dueDate: now.addingTimeInterval(-60 * 60 * 24 * 190),
isCompleted: true,
remindersListID: remindersListIDs[0],
status: .completed,
title: "Take a walk"
)
Reminder(
Expand All @@ -391,17 +440,17 @@ private let logger = Logger(subsystem: "Reminders", category: "Database")
Reminder(
id: reminderIDs[6],
dueDate: now.addingTimeInterval(-60 * 60 * 24 * 2),
isCompleted: true,
priority: .low,
remindersListID: remindersListIDs[1],
status: .completed,
title: "Get laundry"
)
Reminder(
id: reminderIDs[7],
dueDate: now.addingTimeInterval(60 * 60 * 24 * 4),
isCompleted: false,
priority: .high,
remindersListID: remindersListIDs[1],
status: .incomplete,
title: "Take out trash"
)
Reminder(
Expand All @@ -418,16 +467,16 @@ private let logger = Logger(subsystem: "Reminders", category: "Database")
Reminder(
id: reminderIDs[9],
dueDate: now.addingTimeInterval(-60 * 60 * 24 * 2),
isCompleted: true,
priority: .medium,
remindersListID: remindersListIDs[2],
status: .completed,
title: "Send weekly emails"
)
Reminder(
id: reminderIDs[10],
dueDate: now.addingTimeInterval(60 * 60 * 24 * 2),
isCompleted: false,
remindersListID: remindersListIDs[2],
status: .incomplete,
title: "Prepare for WWDC"
)
let tagIDs = ["car", "kids", "someday", "optional", "social", "night", "adulting"]
Expand Down
8 changes: 4 additions & 4 deletions Examples/RemindersTests/RemindersDetailsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ extension BaseTestSuite {
reminder: Reminder(
id: UUID(00000000-0000-0000-0000-000000000004),
dueDate: Date(2009-02-11T23:31:30.000Z),
isCompleted: false,
isFlagged: true,
notes: "",
position: 2,
priority: nil,
remindersListID: UUID(00000000-0000-0000-0000-000000000000),
status: .incomplete,
title: "Haircut"
),
remindersList: RemindersList(
Expand All @@ -44,12 +44,12 @@ extension BaseTestSuite {
reminder: Reminder(
id: UUID(00000000-0000-0000-0000-000000000005),
dueDate: Date(2009-02-13T23:31:30.000Z),
isCompleted: false,
isFlagged: false,
notes: "Ask about diet",
position: 3,
priority: .high,
remindersListID: UUID(00000000-0000-0000-0000-000000000000),
status: .incomplete,
title: "Doctor appointment"
),
remindersList: RemindersList(
Expand All @@ -66,12 +66,12 @@ extension BaseTestSuite {
reminder: Reminder(
id: UUID(00000000-0000-0000-0000-000000000007),
dueDate: Date(2009-02-13T23:31:30.000Z),
isCompleted: false,
isFlagged: false,
notes: "",
position: 5,
priority: nil,
remindersListID: UUID(00000000-0000-0000-0000-000000000000),
status: .incomplete,
title: "Buy concert tickets"
),
remindersList: RemindersList(
Expand All @@ -88,7 +88,6 @@ extension BaseTestSuite {
reminder: Reminder(
id: UUID(00000000-0000-0000-0000-000000000003),
dueDate: nil,
isCompleted: false,
isFlagged: false,
notes: """
Milk
Expand All @@ -100,6 +99,7 @@ extension BaseTestSuite {
position: 1,
priority: nil,
remindersListID: UUID(00000000-0000-0000-0000-000000000000),
status: .incomplete,
title: "Groceries"
),
remindersList: RemindersList(
Expand Down
Loading