Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion Examples/Reminders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ comma-separated list of all of its tags. SQLite is an incredibly powerful langua
not embrace abstractions that keep you from querying SQLite directly as SwiftData does.

[reminders-app-store]: https://apps.apple.com/us/app/reminders/id1108187841
[tags-concat]: https://github.com/pointfreeco/sharing-grdb/blob/0391201992241f62e7bd10c8d1ece63b078c16ad/Examples/Reminders/RemindersListDetail.swift#L146-L147
[tags-concat]: https://github.com/pointfreeco/sharing-grdb/blob/0391201992241f62e7bd10c8d1ece63b078c16ad/Examples/Reminders/RemindersDetail.swift#L146-L147
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CasePaths
import SharingGRDB
import SwiftUI

struct RemindersListDetailView: View {
struct RemindersDetailView: View {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this view is generalized to just handle lists of reminders in general I've renamed the file and types.

@FetchAll private var reminderStates: [ReminderState]
@AppStorage private var ordering: Ordering
@AppStorage private var showCompleted: Bool
Expand Down Expand Up @@ -46,6 +46,9 @@ struct RemindersListDetailView: View {
tags: reminderState.tags
)
}
.onMove { indexSet, index in
move(from: indexSet, to: index)
}
}
.onScrollGeometryChange(for: Bool.self) { geometry in
geometry.contentOffset.y + geometry.contentInsets.top > navigationTitleHeight
Expand Down Expand Up @@ -126,13 +129,38 @@ struct RemindersListDetailView: View {
}
}

func move(from source: IndexSet, to destination: Int) {
withErrorReporting {
try database.write { db in
var ids = reminderStates.map(\.reminder.id)
ids.move(fromOffsets: source, toOffset: destination)
try Reminder
.where { $0.id.in(ids) }
.update {
let ids = Array(ids.enumerated())
let (first, rest) = (ids.first!, ids.dropFirst())
$0.position =
rest
.reduce(Case($0.id).when(first.element, then: first.offset)) { cases, id in
cases.when(id.element, then: id.offset)
}
.else($0.position)
}
.execute(db)
}
}
ordering = .manual
}

private enum Ordering: String, CaseIterable {
case dueDate = "Due Date"
case manual = "Manual"
case priority = "Priority"
case title = "Title"
var icon: Image {
switch self {
case .dueDate: Image(systemName: "calendar")
case .manual: Image(systemName: "hand.draw")
case .priority: Image(systemName: "chart.bar.fill")
case .title: Image(systemName: "textformat.characters")
}
Expand All @@ -157,7 +185,7 @@ struct RemindersListDetailView: View {

fileprivate var remindersQuery: some StructuredQueriesCore.Statement<ReminderState> {
let query =
Reminder
Reminder
.where {
if !showCompleted {
!$0.isCompleted
Expand All @@ -167,6 +195,7 @@ struct RemindersListDetailView: View {
.order {
switch ordering {
case .dueDate: $0.dueDate
case .manual: $0.position
case .priority: ($0.priority.desc(), $0.isFlagged.desc())
case .title: $0.title
}
Expand Down Expand Up @@ -208,7 +237,7 @@ struct RemindersListDetailView: View {
}
}

extension RemindersListDetailView.DetailType {
extension RemindersDetailView.DetailType {
fileprivate var id: String {
switch self {
case .all: "all"
Expand Down Expand Up @@ -249,7 +278,7 @@ extension RemindersListDetailView.DetailType {
}
}

struct RemindersListDetailPreview: PreviewProvider {
struct RemindersDetailPreview: PreviewProvider {
static var previews: some View {
let (remindersList, tag) = try! prepareDependencies {
$0.defaultDatabase = try Reminders.appDatabase()
Expand All @@ -260,14 +289,14 @@ struct RemindersListDetailPreview: PreviewProvider {
)
}
}
let detailTypes: [RemindersListDetailView.DetailType] = [
let detailTypes: [RemindersDetailView.DetailType] = [
.all,
.list(remindersList),
.tags([tag]),
]
ForEach(detailTypes, id: \.self) { detailType in
NavigationStack {
RemindersListDetailView(detailType: detailType)
RemindersDetailView(detailType: detailType)
}
.previewDisplayName(detailType.navigationTitle)
}
Expand Down
13 changes: 7 additions & 6 deletions Examples/Reminders/RemindersLists.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct RemindersListsView: View {
private var stats = Stats()

@State private var destination: Destination?
@State private var remindersDetailType: RemindersListDetailView.DetailType?
@State private var remindersDetailType: RemindersDetailView.DetailType?
@State private var searchText = ""

@Dependency(\.defaultDatabase) private var database
Expand Down Expand Up @@ -124,13 +124,13 @@ struct RemindersListsView: View {
Section {
ForEach(remindersLists) { state in
NavigationLink {
RemindersListDetailView(detailType: .list(state.remindersList))
RemindersDetailView(detailType: .list(state.remindersList))
} label: {
RemindersListRow(
remindersCount: state.remindersCount,
remindersList: state.remindersList
)
}
}
}
.onMove { indexSet, index in
move(from: indexSet, to: index)
Expand All @@ -148,7 +148,7 @@ struct RemindersListsView: View {
Section {
ForEach(tags) { tag in
NavigationLink {
RemindersListDetailView(detailType: .tags([tag]))
RemindersDetailView(detailType: .tags([tag]))
} label: {
TagRow(tag: tag)
}
Expand Down Expand Up @@ -211,7 +211,7 @@ struct RemindersListsView: View {
}
.searchable(text: $searchText)
.navigationDestination(item: $remindersDetailType) { detailType in
RemindersListDetailView(detailType: detailType)
RemindersDetailView(detailType: detailType)
}
}

Expand All @@ -225,7 +225,8 @@ struct RemindersListsView: View {
.update {
let ids = Array(ids.enumerated())
let (first, rest) = (ids.first!, ids.dropFirst())
$0.position = rest
$0.position =
rest
.reduce(Case($0.id).when(first.element, then: first.offset)) { cases, id in
cases.when(id.element, then: id.offset)
}
Expand Down
47 changes: 45 additions & 2 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct Reminder: Equatable, Identifiable {
var notes = ""
var priority: Priority?
var remindersListID: Int
var position = 0
var title = ""
}

Expand Down Expand Up @@ -86,17 +87,21 @@ struct ReminderTag: Hashable, Identifiable {
}

func appDatabase() throws -> any DatabaseWriter {
@Dependency(\.context) var context
let database: any DatabaseWriter
var configuration = Configuration()
configuration.foreignKeysEnabled = true
configuration.prepareDatabase { db in
#if DEBUG
db.trace(options: .profile) {
logger.debug("\($0.expandedDescription)")
if context == .live {
logger.debug("\($0.expandedDescription)")
} else {
print("\($0.expandedDescription)")
}
}
#endif
}
@Dependency(\.context) var context
if context == .live {
let path = URL.documentsDirectory.appending(component: "db.sqlite").path()
logger.info("open \(path)")
Expand Down Expand Up @@ -179,6 +184,44 @@ func appDatabase() throws -> any DatabaseWriter {
)
.execute(db)
}
migrator.registerMigration("Add 'position' column to 'reminders'") { db in
try #sql(
"""
ALTER TABLE "reminders"
ADD COLUMN "position" INTEGER NOT NULL DEFAULT 0
"""
)
.execute(db)
// Backfill position of reminders based on their completion status and due date.
try #sql(
"""
WITH "reminderPositions" AS (
SELECT
id,
ROW_NUMBER() OVER (PARTITION BY "remindersListID" ORDER BY id) - 1 AS "position"
FROM "reminders"
ORDER BY NOT "isCompleted", "dueDate" DESC
)
UPDATE "reminders"
SET "position" = "reminderPositions"."position"
FROM "reminderPositions"
WHERE "reminders"."id" = "reminderPositions"."id"
"""
)
.execute(db)
try #sql(
"""
CREATE TRIGGER "default_position_reminders"
AFTER INSERT ON "reminders"
FOR EACH ROW BEGIN
UPDATE "reminders"
SET "position" = (SELECT max("position") + 1 FROM "reminders")
WHERE "id" = NEW."id";
END
"""
)
.execute(db)
}
#if DEBUG && targetEnvironment(simulator)
if context != .test {
migrator.registerMigration("Seed sample data") { db in
Expand Down
12 changes: 3 additions & 9 deletions Sources/StructuredQueriesGRDBCore/DefaultDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ extension DependencyValues {
switch context {
case .live:
return """
A blank, in-memory database is being used. To set the database that is used by \
'SharingGRDB', use the 'prepareDependencies' tool as early as possible in the lifetime \
of your app, such as in your app or scene delegate in UIKit, or the app entry point in \
SwiftUI:
A blank, in-memory database is being used. To set the database that is used by 'SharingGRDB', use the 'prepareDependencies' tool as early as possible in the lifetime of your app, such as in your app or scene delegate in UIKit, or the app entry point in SwiftUI:

@main
struct MyApp: App {
Expand All @@ -70,8 +67,7 @@ extension DependencyValues {

case .preview:
return """
A blank, in-memory database is being used. To set the database that is used by \
'SharingGRDB' in a preview, use a tool like the 'dependency' trait:
A blank, in-memory database is being used. To set the database that is used by 'SharingGRDB' in a preview, use a tool like the 'dependency' trait:

#Preview(
traits: .dependency(\\.defaultDatabase, try DatabaseQueue(/* ... */))
Expand All @@ -82,9 +78,7 @@ extension DependencyValues {

case .test:
return """
A blank, in-memory database is being used. To set the database that is used by \
'SharingGRDB' in a test, use a tool like the 'dependency' trait from \
'DependenciesTestSupport':
A blank, in-memory database is being used. To set the database that is used by 'SharingGRDB' in a test, use a tool like the 'dependency' trait from 'DependenciesTestSupport':

import DependenciesTestSupport

Expand Down
Loading