Skip to content
Open
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
1 change: 1 addition & 0 deletions Core/Localization/Resources/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,4 @@
"ayah-bookmark.remove-reading-bookmark" = "إزالة علامة القراءة";
"ayah-bookmark.collection-picker.no-data.title" = "لا توجد مجموعات بعد";
"ayah-bookmark.collection-picker.no-data.text" = "أضف مجموعة لحفظ هذه الآية.";
"bookmarks.collections.favourites" = "المفضلة";
1 change: 1 addition & 0 deletions Core/Localization/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,4 @@
"ayah-bookmark.remove-reading-bookmark" = "Remove Reading Bookmark";
"ayah-bookmark.collection-picker.no-data.title" = "No collections yet";
"ayah-bookmark.collection-picker.no-data.text" = "Add a collection to save this verse.";
"bookmarks.collections.favourites" = "Favourites";
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import AnnotationsService
import Foundation
import Localization
import QuranAnnotations
import QuranKit
import VLogging
Expand Down Expand Up @@ -75,9 +76,10 @@
}

func start() async {
async let collections: () = loadCollections()
async let readingBookmark: () = loadReadingBookmark()
_ = await [collections, readingBookmark]
async let collections: Void = loadCollections()
async let bookmarks: Void = loadBookmarks()
async let readingBookmark: Void = loadReadingBookmark()
_ = await [collections, bookmarks, readingBookmark]
}

func toggleSelection(for collection: AyahBookmarkCollection) {
Expand Down Expand Up @@ -147,20 +149,48 @@
private let didFinish: () -> Void
private var didEnsureHighlightCollections = false
private var didUpdateSelection = false
private var syncedCollections: [AyahBookmarkCollection] = []
private var directBookmarks: [AyahCollectionBookmark] = []

private func loadCollections() async {
do {
let sequence = ayahBookmarkCollectionService.collectionsSequence()
for try await collections in sequence {
self.collections = Self.sorted(collections)
selectExistingBookmarksIfNeeded(in: self.collections)
syncedCollections = collections
refreshCollections()
try await ensureHighlightCollections(collections)
}
} catch {
self.error = error
}
}

private func loadBookmarks() async {
do {
let sequence = ayahBookmarkCollectionService.bookmarksSequence()
for try await bookmarks in sequence {
directBookmarks = bookmarks
refreshCollections()
}
} catch {
self.error = error
}
}

private func refreshCollections() {
let collections = syncedCollections + [favouritesCollection()]
self.collections = Self.sorted(collections)
selectExistingBookmarksIfNeeded(in: self.collections)
}

private func favouritesCollection() -> AyahBookmarkCollection {
FavouritesBookmarkCollection.make(
name: l("bookmarks.collections.favourites"),
bookmarks: directBookmarks,
collections: syncedCollections
)
}

private func ensureHighlightCollections(_ collections: [AyahBookmarkCollection]) async throws {
guard !didEnsureHighlightCollections else {
return
Expand Down Expand Up @@ -225,10 +255,14 @@
for collection in bookmarkCollections {
if selectedCollectionIDs.contains(collection.collection.localId) {
for verse in Self.bookmarksToAdd(to: collection, verses: verses) {
try await ayahBookmarkCollectionService.addAyahBookmarkToCollection(
collectionLocalId: collection.collection.localId,
ayah: verse
)
if collection.isLocalOnly {
try await ayahBookmarkCollectionService.addAyahBookmark(verse)
} else {
try await ayahBookmarkCollectionService.addAyahBookmarkToCollection(
collectionLocalId: collection.collection.localId,
ayah: verse
)
}
}
} else {
for bookmark in Self.bookmarksToRemove(from: collection, verses: verses) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,33 @@
public struct AyahBookmarkCollection {
public let collection: Collection_
public let bookmarks: [AyahCollectionBookmark]
public let isLocalOnly: Bool

public init(collection: Collection_, bookmarks: [AyahCollectionBookmark], isLocalOnly: Bool = false) {
self.collection = collection
self.bookmarks = bookmarks
self.isLocalOnly = isLocalOnly
}
}

public struct AyahCollectionBookmark {
public let bookmark: CollectionAyahBookmark
public enum Bookmark {
case collection(CollectionAyahBookmark)
case ayah(AyahBookmark)
}

public let bookmark: Bookmark
public let ayah: AyahNumber

public init(bookmark: CollectionAyahBookmark, ayah: AyahNumber) {
self.bookmark = .collection(bookmark)
self.ayah = ayah
}

public init(bookmark: AyahBookmark, ayah: AyahNumber) {
self.bookmark = .ayah(bookmark)
self.ayah = ayah
}
}

public struct AyahBookmarkCollectionsSequence: AsyncSequence {
Expand Down Expand Up @@ -50,6 +72,37 @@
private let makeIterator: () -> AsyncIterator
}

public struct AyahBookmarksSequence: AsyncSequence {
public typealias Element = [AyahCollectionBookmark]

public struct AsyncIterator: AsyncIteratorProtocol {
init<S: AsyncSequence>(_ sequence: S) where S.Element == Element {
var iterator = sequence.makeAsyncIterator()
nextValue = {
try await iterator.next()
}
}

public mutating func next() async throws -> Element? {
try await nextValue()
}

private let nextValue: () async throws -> Element?
}

init<S: AsyncSequence>(_ sequence: S) where S.Element == Element {
makeIterator = {
AsyncIterator(sequence)
}
}

public func makeAsyncIterator() -> AsyncIterator {
makeIterator()
}

private let makeIterator: () -> AsyncIterator
}

public struct AyahBookmarkCollectionService {
// MARK: Lifecycle

Expand All @@ -67,6 +120,13 @@
try await syncService.createCollection(named: name)
}

public func addAyahBookmark(_ ayah: AyahNumber) async throws {
_ = try await syncService.addAyahBookmark(
sura: Int32(ayah.sura.suraNumber),
ayah: Int32(ayah.ayah)
)
}

public func addAyahBookmarkToCollection(collectionLocalId: String, ayah: AyahNumber) async throws {
_ = try await syncService.addAyahBookmarkToCollection(
collectionLocalId: collectionLocalId,
Expand All @@ -80,7 +140,12 @@
}

public func removeBookmarkFromCollection(_ bookmark: AyahCollectionBookmark) async throws {
try await syncService.removeAyahBookmarkFromCollection(bookmark.bookmark)
switch bookmark.bookmark {
case .collection(let bookmark):
try await syncService.removeAyahBookmarkFromCollection(bookmark)
case .ayah(let bookmark):
try await syncService.removeBookmark(bookmark)
}
}

public func collectionsSequence() -> AyahBookmarkCollectionsSequence {
Expand All @@ -95,6 +160,15 @@
return AyahBookmarkCollectionsSequence(sequence)
}

public func bookmarksSequence() -> AyahBookmarksSequence {
let readingPreferences = readingPreferences
let sequence = syncService.bookmarksSequence()
.map { bookmarks in
Self.bookmarks(from: bookmarks, quran: readingPreferences.reading.quran)
}
return AyahBookmarksSequence(sequence)
}

// MARK: Internal

static func collections(from collections: [CollectionWithAyahBookmarks], quran: Quran) -> [AyahBookmarkCollection] {
Expand All @@ -106,6 +180,10 @@
}
}

static func bookmarks(from bookmarks: [AyahBookmark], quran: Quran) -> [AyahCollectionBookmark] {
bookmarks.compactMap { bookmark(for: $0, quran: quran) }
}

// MARK: Private

private let syncService: SyncService
Expand All @@ -122,6 +200,18 @@

return AyahCollectionBookmark(bookmark: bookmark, ayah: ayah)
}

private static func bookmark(for bookmark: AyahBookmark, quran: Quran) -> AyahCollectionBookmark? {
guard let ayah = AyahNumber(
quran: quran,
sura: Int(bookmark.sura),
ayah: Int(bookmark.ayah)
) else {
return nil
}

return AyahCollectionBookmark(bookmark: bookmark, ayah: ayah)
}
}

#endif
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
#if QURAN_SYNC
import MobileSync

extension AyahBookmarkCollection: Identifiable {
public var id: String { collection.localId }
}

extension AyahCollectionBookmark: Identifiable {
public var id: String { bookmark.localId }
public var id: String {
switch bookmark {
case .collection(let bookmark):
return bookmark.localId
case .ayah(let bookmark):
return bookmark.localId
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
)

let highlightColor = HighlightColor(collectionName: collection.collection.name)
let allowsCollectionDeletion = allowsCollectionManagement && highlightColor == nil
let allowsCollectionDeletion = allowsCollectionManagement && highlightColor == nil && !collection.isLocalOnly

NoorEditableCollapsibleSection(
title: highlightColor?.localizedName ?? collection.collection.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import Localization
import QuranAnnotations
import QuranKit
import SwiftUI
Expand Down Expand Up @@ -52,15 +53,9 @@
}

func start() async {
do {
let sequence = ayahBookmarkCollectionService.collectionsSequence()
for try await collections in sequence {
self.collections = Self.sorted(filtered(collections))
try await prepareCollectionsIfNeeded(collections)
}
} catch {
self.error = error
}
async let collections: Void = observeCollections()
async let bookmarks: Void = observeBookmarks()
_ = await [collections, bookmarks]
}

func isCollectionExpanded(_ collection: AyahBookmarkCollection) -> Bool {
Expand Down Expand Up @@ -90,6 +85,10 @@
}

func deleteCollection(_ collection: AyahBookmarkCollection) async {
guard !collection.isLocalOnly else {
return
}

do {
try await ayahBookmarkCollectionService.removeCollection(localId: collection.collection.localId)
} catch {
Expand All @@ -113,6 +112,8 @@
private let prepareCollections: ([AyahBookmarkCollection]) async throws -> Void
private let navigateToPage: (Page) -> Void
private var didPrepareCollections = false
private var syncedCollections: [AyahBookmarkCollection] = []
private var directBookmarks: [AyahCollectionBookmark] = []

private nonisolated static func highlightSortIndex(_ collection: AyahBookmarkCollection) -> Int? {
guard let color = HighlightColor(collectionName: collection.collection.name) else {
Expand All @@ -121,6 +122,43 @@
return HighlightColor.allCases.firstIndex(of: color)
}

private func observeCollections() async {
do {
let sequence = ayahBookmarkCollectionService.collectionsSequence()
for try await collections in sequence {
syncedCollections = collections
refreshCollections()
try await prepareCollectionsIfNeeded(collections)
}
} catch {
self.error = error
}
}

private func observeBookmarks() async {
do {
let sequence = ayahBookmarkCollectionService.bookmarksSequence()
for try await bookmarks in sequence {
directBookmarks = bookmarks
refreshCollections()
}
} catch {
self.error = error
}
}

private func refreshCollections() {
collections = Self.sorted(filtered(syncedCollections + [favouritesCollection()]))
}

private func favouritesCollection() -> AyahBookmarkCollection {
FavouritesBookmarkCollection.make(
name: l("bookmarks.collections.favourites"),
bookmarks: directBookmarks,
collections: syncedCollections
)
}

private func filtered(_ collections: [AyahBookmarkCollection]) -> [AyahBookmarkCollection] {
collections.filter { collection in
let name = collection.collection.name
Expand Down
Loading
Loading