Skip to content

Commit 2bdef94

Browse files
Improve demo
1 parent d405ddd commit 2bdef94

File tree

4 files changed

+71
-52
lines changed

4 files changed

+71
-52
lines changed

Demo/PowerSyncExample.xcodeproj/xcshareddata/xcschemes/PowerSyncExample.xcscheme

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
ignoresPersistentStateOnLaunch = "NO"
4040
debugDocumentVersioning = "YES"
4141
debugServiceExtension = "internal"
42+
enableGPUValidationMode = "1"
4243
allowLocationSimulation = "YES">
4344
<BuildableProductRunnable
4445
runnableDebuggingMode = "0">

Demo/PowerSyncExample/Components/TodoListRow.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import SwiftUI
22

33
struct TodoListRow: View {
44
let todo: Todo
5+
let isCameraAvailable: Bool
56
let completeTapped: () -> Void
67
let deletePhotoTapped: () -> Void
78
let capturePhotoTapped: () -> Void
@@ -13,19 +14,20 @@ struct TodoListRow: View {
1314
HStack {
1415
Text(todo.description)
1516
Group {
16-
if todo.photoUri == nil {
17-
// Nothing to display when photoURI is nil
18-
EmptyView()
19-
} else if let image = image {
17+
if let image = image {
2018
Image(uiImage: image)
2119
.resizable()
2220
.scaledToFit()
21+
2322
} else if todo.photoUri != nil {
24-
// Only show loading indicator if we have a URL string
23+
// Show progress while loading the image
2524
ProgressView()
2625
.onAppear {
2726
loadImage()
2827
}
28+
} else if todo.photoId != nil {
29+
// Show progres, wait for a URI to be present
30+
ProgressView()
2931
} else {
3032
EmptyView()
3133
}
@@ -34,12 +36,14 @@ struct TodoListRow: View {
3436
VStack {
3537
if todo.photoId == nil {
3638
HStack {
37-
Button {
38-
capturePhotoTapped()
39-
} label: {
40-
Image(systemName: "camera.fill")
39+
if isCameraAvailable {
40+
Button {
41+
capturePhotoTapped()
42+
} label: {
43+
Image(systemName: "camera.fill")
44+
}
45+
.buttonStyle(.plain)
4146
}
42-
.buttonStyle(.plain)
4347
Button {
4448
selectPhotoTapped()
4549
} label: {
@@ -62,6 +66,11 @@ struct TodoListRow: View {
6266
Image(systemName: todo.isComplete ? "checkmark.circle.fill" : "circle")
6367
}
6468
.buttonStyle(.plain)
69+
}.onChange(of: todo.photoId) { _, newPhotoId in
70+
if newPhotoId == nil {
71+
// Clear the image when photoId becomes nil
72+
image = nil
73+
}
6574
}
6675
}
6776
}
@@ -93,6 +102,7 @@ struct TodoListRow: View {
93102
completedBy: nil,
94103

95104
),
105+
isCameraAvailable: true,
96106
completeTapped: {},
97107
deletePhotoTapped: {},
98108
capturePhotoTapped: {}

Demo/PowerSyncExample/Components/TodoListView.swift

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AVFoundation
12
import IdentifiedCollections
23
import SwiftUI
34
import SwiftUINavigation
@@ -15,6 +16,7 @@ struct TodoListView: View {
1516
@State private var onMediaSelect: ((_: Data) async throws -> Void)?
1617
@State private var pickMediaType: UIImagePickerController.SourceType = .camera
1718
@State private var showMediaPicker = false
19+
@State private var isCameraAvailable: Bool = false
1820

1921
var body: some View {
2022
List {
@@ -33,6 +35,7 @@ struct TodoListView: View {
3335
ForEach(todos) { todo in
3436
TodoListRow(
3537
todo: todo,
38+
isCameraAvailable: isCameraAvailable,
3639
completeTapped: {
3740
Task {
3841
await toggleCompletion(of: todo)
@@ -59,13 +62,12 @@ struct TodoListView: View {
5962
registerMediaCallback(todo: todo)
6063
pickMediaType = .camera
6164
showMediaPicker = true
62-
},
63-
selectPhotoTapped: {
64-
registerMediaCallback(todo: todo)
65-
pickMediaType = .photoLibrary
66-
showMediaPicker = true
6765
}
68-
)
66+
) {
67+
registerMediaCallback(todo: todo)
68+
pickMediaType = .photoLibrary
69+
showMediaPicker = true
70+
}
6971
}
7072
.onDelete { indexSet in
7173
Task {
@@ -109,6 +111,9 @@ struct TodoListView: View {
109111
}
110112
}
111113
}
114+
.onAppear {
115+
checkCameraAvailability()
116+
}
112117
.task {
113118
await system.watchTodos(listId) { tds in
114119
withAnimation {
@@ -138,7 +143,7 @@ struct TodoListView: View {
138143
self.error = error
139144
}
140145
}
141-
146+
142147
/// Registers a callback which saves a photo for the specified Todo item if media is sucessfully loaded.
143148
func registerMediaCallback(todo: Todo) {
144149
// Register a callback for successful image capture
@@ -164,6 +169,12 @@ struct TodoListView: View {
164169
}
165170
}
166171
}
172+
173+
private func checkCameraAvailability() {
174+
// https://developer.apple.com/forums/thread/748448
175+
// On MacOS MetalAPI validation needs to be disabled
176+
isCameraAvailable = UIImagePickerController.isSourceTypeAvailable(.camera)
177+
}
167178
}
168179

169180
#Preview {
@@ -177,7 +188,7 @@ struct TodoListView: View {
177188
struct CameraView: UIViewControllerRepresentable {
178189
@Binding var onMediaSelect: ((_: Data) async throws -> Void)?
179190
@Binding var mediaType: UIImagePickerController.SourceType
180-
191+
181192
@Environment(\.presentationMode) var presentationMode
182193

183194
func makeUIViewController(context: Context) -> UIImagePickerController {
@@ -214,6 +225,7 @@ struct CameraView: UIViewControllerRepresentable {
214225
}
215226
}
216227
}
228+
parent.onMediaSelect = nil
217229
}
218230
}
219231

@@ -222,6 +234,7 @@ struct CameraView: UIViewControllerRepresentable {
222234

223235
func imagePickerControllerDidCancel(_: UIImagePickerController) {
224236
parent.presentationMode.wrappedValue.dismiss()
237+
parent.onMediaSelect = nil
225238
}
226239
}
227240
}

Tests/PowerSyncTests/AttachmentTests.swift

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,25 @@ final class AttachmentTests: XCTestCase {
4444
* Download a file from remote storage
4545
*/
4646
func downloadFile(attachment: Attachment) async throws -> Data {
47-
return Data([1,2,3])
47+
return Data([1, 2, 3])
4848
}
4949

5050
/**
5151
* Delete a file from remote storage
5252
*/
5353
func deleteFile(attachment: Attachment) async throws {}
54-
5554
}
5655

5756
return MockRemoteStorage()
5857
}(),
5958
attachmentsDirectory: NSTemporaryDirectory(),
60-
watchedAttachments: try database.watch(options: WatchOptions(
59+
watchAttachments: { try self.database.watch(options: WatchOptions(
6160
sql: "SELECT photo_id FROM users WHERE photo_id IS NOT NULL",
62-
mapper: { cursor in WatchedAttachmentItem(
63-
id: try cursor.getString(name: "photo_id"),
61+
mapper: { cursor in try WatchedAttachmentItem(
62+
id: cursor.getString(name: "photo_id"),
6463
fileExtension: "jpg"
65-
)}
66-
))
64+
) }
65+
)) }
6766
)
6867

6968
try await queue.startSync()
@@ -79,14 +78,14 @@ final class AttachmentTests: XCTestCase {
7978
let attachmentsWatch = try database.watch(
8079
options: WatchOptions(
8180
sql: "SELECT * FROM attachments",
82-
mapper: {cursor in try Attachment.fromCursor(cursor)}
81+
mapper: { cursor in try Attachment.fromCursor(cursor) }
8382
)).makeAsyncIterator()
8483

85-
let attachmentRecord = try await waitForMatch(
84+
let attachmentRecord = try await waitForMatch(
8685
iterator: attachmentsWatch,
87-
where: {results in results.first?.state == AttachmentState.synced},
86+
where: { results in results.first?.state == AttachmentState.synced },
8887
timeout: 5
89-
).first
88+
).first
9089

9190
// The file should exist
9291
let localData = try await queue.localStorage.readFile(filePath: attachmentRecord!.localUri!)
@@ -97,71 +96,68 @@ final class AttachmentTests: XCTestCase {
9796
}
9897

9998
func testAttachmentUpload() async throws {
100-
10199
class MockRemoteStorage: RemoteStorageAdapter {
102100
public var uploadCalled = false
103101

104102
func uploadFile(
105103
fileData: Data,
106104
attachment: Attachment
107105
) async throws {
108-
self.uploadCalled = true
106+
uploadCalled = true
109107
}
110108

111109
/**
112110
* Download a file from remote storage
113111
*/
114112
func downloadFile(attachment: Attachment) async throws -> Data {
115-
return Data([1,2,3])
113+
return Data([1, 2, 3])
116114
}
117115

118116
/**
119117
* Delete a file from remote storage
120118
*/
121119
func deleteFile(attachment: Attachment) async throws {}
122-
123120
}
124121

125-
126-
127122
let mockedRemote = MockRemoteStorage()
128123

129124
let queue = AttachmentQueue(
130125
db: database,
131126
remoteStorage: mockedRemote,
132127
attachmentsDirectory: NSTemporaryDirectory(),
133-
watchedAttachments: try database.watch(options: WatchOptions(
128+
watchAttachments: { try self.database.watch(options: WatchOptions(
134129
sql: "SELECT photo_id FROM users WHERE photo_id IS NOT NULL",
135-
mapper: { cursor in WatchedAttachmentItem(
136-
id: try cursor.getString(name: "photo_id"),
130+
mapper: { cursor in try WatchedAttachmentItem(
131+
id: cursor.getString(name: "photo_id"),
137132
fileExtension: "jpg"
138-
)}
139-
))
133+
) }
134+
)) }
140135
)
141136

142137
try await queue.startSync()
143138

144139
let attachmentsWatch = try database.watch(
145140
options: WatchOptions(
146141
sql: "SELECT * FROM attachments",
147-
mapper: {cursor in try Attachment.fromCursor(cursor)}
142+
mapper: { cursor in try Attachment.fromCursor(cursor) }
148143
)).makeAsyncIterator()
149144

150145
_ = try await queue.saveFile(
151-
data: Data([3,4,5]),
146+
data: Data([3, 4, 5]),
152147
mediaType: "image/jpg",
153-
fileExtension: "jpg") {tx, attachment in
154-
_ = try tx.execute(
155-
sql: "INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), 'john', '[email protected]', ?)",
156-
parameters: [attachment.id]
157-
)
158-
}
148+
fileExtension: "jpg"
149+
) { tx, attachment in
150+
_ = try tx.execute(
151+
sql: "INSERT INTO users (id, name, email, photo_id) VALUES (uuid(), 'john', '[email protected]', ?)",
152+
parameters: [attachment.id]
153+
)
154+
}
159155

160-
_ = try await waitForMatch(
156+
_ = try await waitForMatch(
161157
iterator: attachmentsWatch,
162-
where: {results in results.first?.state == AttachmentState.synced},
158+
where: { results in results.first?.state == AttachmentState.synced },
163159
timeout: 5
164-
).first
160+
).first
165161

166162
// Upload should have been called
167163
XCTAssertTrue(mockedRemote.uploadCalled)
@@ -171,7 +167,6 @@ final class AttachmentTests: XCTestCase {
171167
}
172168
}
173169

174-
175170
enum WaitForMatchError: Error {
176171
case timeout
177172
}

0 commit comments

Comments
 (0)