Skip to content

Commit 54b76d9

Browse files
committed
Add bookmark control to comments toolbar
1 parent 0268080 commit 54b76d9

File tree

4 files changed

+90
-0
lines changed

4 files changed

+90
-0
lines changed

Features/Comments/Sources/Comments/CommentsComponents.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,32 @@ struct ToolbarTitle: View {
484484
}
485485
}
486486

487+
struct BookmarkToolbarButton: View {
488+
let isBookmarked: Bool
489+
let toggleBookmark: @Sendable () async -> Bool
490+
@State private var isSubmitting = false
491+
492+
var body: some View {
493+
Button {
494+
guard !isSubmitting else { return }
495+
isSubmitting = true
496+
Task { @MainActor in
497+
_ = await toggleBookmark()
498+
isSubmitting = false
499+
}
500+
} label: {
501+
Image(systemName: isBookmarked ? "bookmark.fill" : "bookmark")
502+
}
503+
.accessibilityLabel(isBookmarked ? "Remove Bookmark" : "Save Bookmark")
504+
.accessibilityHint(
505+
isBookmarked
506+
? "Double-tap to remove from bookmarks"
507+
: "Double-tap to add to bookmarks"
508+
)
509+
.disabled(isSubmitting)
510+
}
511+
}
512+
487513
struct ShareMenu: View {
488514
let post: Post
489515

Features/Comments/Sources/Comments/CommentsView.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ public struct CommentsView<NavigationStore: NavigationStoreProtocol>: View {
9898
)
9999
}
100100
}
101+
ToolbarItem(placement: .navigationBarTrailing) {
102+
if let post = viewModel.post {
103+
BookmarkToolbarButton(
104+
isBookmarked: post.isBookmarked,
105+
toggleBookmark: { await viewModel.toggleBookmark() }
106+
)
107+
}
108+
}
101109
ToolbarItem(placement: .navigationBarTrailing) {
102110
if let post = viewModel.post {
103111
ShareMenu(post: post)

Features/Comments/Sources/Comments/CommentsViewModel.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public final class CommentsViewModel: @unchecked Sendable {
2828
private let settingsUseCase: any SettingsUseCase
2929
private let bookmarksController: BookmarksController
3030
private var settingsCancellable: AnyCancellable?
31+
private var bookmarksObservation: AnyCancellable?
3132

3233
public var comments: [Comment] { commentsLoader.data }
3334
public var isLoading: Bool { commentsLoader.isLoading }
@@ -77,6 +78,20 @@ public final class CommentsViewModel: @unchecked Sendable {
7778
self.showThumbnails = currentValue
7879
}
7980
}
81+
82+
bookmarksObservation = NotificationCenter.default.publisher(for: .bookmarksDidChange)
83+
.receive(on: DispatchQueue.main)
84+
.sink { [weak self] notification in
85+
guard let self else { return }
86+
guard let postId = notification.userInfo?["postId"] as? Int,
87+
postId == self.postID,
88+
let isBookmarked = notification.userInfo?["isBookmarked"] as? Bool
89+
else { return }
90+
if var currentPost = self.post {
91+
currentPost.isBookmarked = isBookmarked
92+
self.post = currentPost
93+
}
94+
}
8095
}
8196

8297
@MainActor

Features/Comments/Tests/CommentsTests/CommentsViewModelTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,47 @@ struct CommentsViewModelTests {
209209
#expect(viewModel.showThumbnails == true)
210210
}
211211

212+
@Test("Bookmark notifications update local post state")
213+
@MainActor
214+
func bookmarkNotificationsUpdatePostState() async {
215+
var unbookmarkedPost = testPost
216+
unbookmarkedPost.isBookmarked = false
217+
let viewModel = CommentsViewModel(
218+
post: unbookmarkedPost,
219+
postUseCase: mockPostUseCase,
220+
commentUseCase: mockCommentUseCase,
221+
voteUseCase: mockVoteUseCase,
222+
settingsUseCase: StubSettingsUseCase(showThumbnails: true),
223+
bookmarksController: bookmarksController
224+
)
225+
226+
#expect(viewModel.post?.isBookmarked == false)
227+
228+
NotificationCenter.default.post(
229+
name: .bookmarksDidChange,
230+
object: nil,
231+
userInfo: ["postId": unbookmarkedPost.id, "isBookmarked": true]
232+
)
233+
await Task.yield()
234+
#expect(viewModel.post?.isBookmarked == true)
235+
236+
NotificationCenter.default.post(
237+
name: .bookmarksDidChange,
238+
object: nil,
239+
userInfo: ["postId": unbookmarkedPost.id + 1, "isBookmarked": false]
240+
)
241+
await Task.yield()
242+
#expect(viewModel.post?.isBookmarked == true)
243+
244+
NotificationCenter.default.post(
245+
name: .bookmarksDidChange,
246+
object: nil,
247+
userInfo: ["postId": unbookmarkedPost.id, "isBookmarked": false]
248+
)
249+
await Task.yield()
250+
#expect(viewModel.post?.isBookmarked == false)
251+
}
252+
212253
// MARK: - Voting Tests
213254

214255
@Test("Upvoting post updates state correctly", arguments: [

0 commit comments

Comments
 (0)