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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.quran.shared.persistence.util.PlatformDateTime
data class AyahBookmark(
val sura: Int,
val ayah: Int,
val localId: String,
val lastUpdated: PlatformDateTime,
val localId: String
val addedDate: PlatformDateTime = lastUpdated
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ internal fun DatabaseBookmark.toAyahBookmark(): AyahBookmark {
return AyahBookmark(
sura = requireNotNull(sura).toInt(),
ayah = requireNotNull(ayah).toInt(),
localId = local_id.toString(),
lastUpdated = Instant.fromEpochMilliseconds(modified_at).toPlatform(),
localId = local_id.toString()
addedDate = Instant.fromEpochMilliseconds(created_at).toPlatform()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,39 +37,39 @@ interface BookmarksRepository {
/**
* Add a saved ayah bookmark and add it to the requested memberships.
*
* Null or empty memberships normalize to the virtual default collection. A non-empty list is
* Empty memberships normalize to the virtual default collection. A non-empty list is
* additive: requested memberships are added, while existing custom memberships not present in
* the list are left unchanged. [com.quran.shared.persistence.model.DEFAULT_COLLECTION_ID]
* represents default membership.
*/
@NativeCoroutines
suspend fun addBookmark(sura: Int, ayah: Int, collectionLocalIds: List<String>?): AyahBookmark
suspend fun addBookmark(sura: Int, ayah: Int, collectionLocalIds: List<String>): AyahBookmark

@NativeCoroutines
suspend fun addBookmark(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): AyahBookmark

/**
* Replaces the saved collection memberships for an existing ayah bookmark.
*
* Null or empty memberships normalize to the virtual default collection. Use [deleteBookmark]
* Empty memberships normalize to the virtual default collection. Use [deleteBookmark]
* when a saved bookmark should be removed from every collection.
*
* @return `true` when memberships changed, or `false` when the bookmark is missing, deleted,
* or already has exactly the requested memberships.
*/
@NativeCoroutines
suspend fun replaceBookmarkCollections(localId: String, collectionLocalIds: List<String>?): Boolean
suspend fun replaceBookmarkCollections(localId: String, collectionLocalIds: List<String>): Boolean

/**
* Replaces the saved collection memberships for an existing ayah bookmark with an explicit
* mutation timestamp.
*
* Null or empty memberships normalize to the virtual default collection. Use [deleteBookmark]
* Empty memberships normalize to the virtual default collection. Use [deleteBookmark]
* when a saved bookmark should be removed from every collection.
*
* @return `true` when memberships changed, or `false` when the bookmark is missing, deleted,
Expand All @@ -78,33 +78,33 @@ interface BookmarksRepository {
@NativeCoroutines
suspend fun replaceBookmarkCollections(
localId: String,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): Boolean

/**
* Creates an ayah bookmark if needed, then replaces its saved collection memberships exactly.
*
* Null or empty memberships normalize to the virtual default collection.
* Empty memberships normalize to the virtual default collection.
*/
@NativeCoroutines
suspend fun replaceAyahBookmarkCollections(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?
collectionLocalIds: List<String>
): BookmarkCollectionsReplacementResult

/**
* Creates an ayah bookmark if needed, then replaces its saved collection memberships exactly
* with an explicit mutation timestamp.
*
* Null or empty memberships normalize to the virtual default collection.
* Empty memberships normalize to the virtual default collection.
*/
@NativeCoroutines
suspend fun replaceAyahBookmarkCollections(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): BookmarkCollectionsReplacementResult

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,22 @@ class BookmarksRepositoryImpl(
}

override suspend fun addBookmark(sura: Int, ayah: Int): AyahBookmark {
return addBookmark(sura = sura, ayah = ayah, collectionLocalIds = null)
return addBookmark(sura = sura, ayah = ayah, collectionLocalIds = emptyList())
}

override suspend fun addBookmark(sura: Int, ayah: Int, timestamp: PlatformDateTime): AyahBookmark {
return addBookmark(
sura = sura,
ayah = ayah,
collectionLocalIds = null,
collectionLocalIds = emptyList(),
timestamp = timestamp
)
}

override suspend fun addBookmark(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?
collectionLocalIds: List<String>
): AyahBookmark {
return addBookmarkWithTimestampMillis(
sura = sura,
Expand All @@ -91,7 +91,7 @@ class BookmarksRepositoryImpl(
override suspend fun addBookmark(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): AyahBookmark {
return addBookmarkWithTimestampMillis(
Expand All @@ -105,7 +105,7 @@ class BookmarksRepositoryImpl(
private suspend fun addBookmarkWithTimestampMillis(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestampMillis: Long?
): AyahBookmark {
logger.i { "Adding ayah bookmark for $sura:$ayah" }
Expand Down Expand Up @@ -163,7 +163,7 @@ class BookmarksRepositoryImpl(

override suspend fun replaceBookmarkCollections(
localId: String,
collectionLocalIds: List<String>?
collectionLocalIds: List<String>
): Boolean {
return replaceBookmarkCollectionsWithTimestampMillis(
localId = localId,
Expand All @@ -174,7 +174,7 @@ class BookmarksRepositoryImpl(

override suspend fun replaceBookmarkCollections(
localId: String,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): Boolean {
return replaceBookmarkCollectionsWithTimestampMillis(
Expand All @@ -186,7 +186,7 @@ class BookmarksRepositoryImpl(

private suspend fun replaceBookmarkCollectionsWithTimestampMillis(
localId: String,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestampMillis: Long?
): Boolean {
logger.i { "Replacing ayah bookmark collection memberships localId=$localId" }
Expand All @@ -210,7 +210,7 @@ class BookmarksRepositoryImpl(
override suspend fun replaceAyahBookmarkCollections(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?
collectionLocalIds: List<String>
): BookmarkCollectionsReplacementResult {
return replaceAyahBookmarkCollectionsWithTimestampMillis(
sura = sura,
Expand All @@ -223,7 +223,7 @@ class BookmarksRepositoryImpl(
override suspend fun replaceAyahBookmarkCollections(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): BookmarkCollectionsReplacementResult {
return replaceAyahBookmarkCollectionsWithTimestampMillis(
Expand All @@ -237,7 +237,7 @@ class BookmarksRepositoryImpl(
private suspend fun replaceAyahBookmarkCollectionsWithTimestampMillis(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestampMillis: Long?
): BookmarkCollectionsReplacementResult {
logger.i { "Replacing ayah bookmark collection memberships for $sura:$ayah" }
Expand Down Expand Up @@ -326,7 +326,7 @@ class BookmarksRepositoryImpl(

private fun replaceBookmarkCollectionsInTransaction(
bookmark: DatabaseBookmark,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestampMillis: Long?
): Boolean {
require(bookmark.bookmark_type == "AYAH") {
Expand Down Expand Up @@ -809,12 +809,11 @@ class BookmarksRepositoryImpl(
}
}

private fun normalizeCollectionIds(collectionLocalIds: List<String>?): List<String> {
private fun normalizeCollectionIds(collectionLocalIds: List<String>): List<String> {
val nonBlankIds = collectionLocalIds
?.map { it.trim() }
?.filter { it.isNotEmpty() }
?.distinct()
.orEmpty()
.map { it.trim() }
.filter { it.isNotEmpty() }
.distinct()
return nonBlankIds.ifEmpty { listOf(DEFAULT_COLLECTION_ID) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,17 @@ class BookmarkSyncArchitectureTest {
assertTrue(defaults.all { it.collectionLocalId == DEFAULT_COLLECTION_ID })
}

@Test
fun `saved bookmark exposes added date separately from last updated`() = runTest {
bookmarksRepository.addBookmark(2, 20, listOf(DEFAULT_COLLECTION_ID), at(100))
bookmarksRepository.addBookmark(2, 20, listOf(DEFAULT_COLLECTION_ID), at(250))

val bookmark = bookmarksRepository.getAllBookmarks().single()

assertEquals(100L, bookmark.addedDate.fromPlatform().toEpochMilliseconds())
assertEquals(250L, bookmark.lastUpdated.fromPlatform().toEpochMilliseconds())
}

@Test
fun `addBookmark supports default and custom membership together`() = runTest {
val collectionId = createCollection("Both", "remote-both")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import com.quran.shared.auth.repository.RemoteLogoutFailure
import com.quran.shared.auth.repository.RemoteLogoutOperation
import com.quran.shared.auth.service.AuthService
import com.quran.shared.di.AppScope
import com.quran.shared.persistence.input.PersistenceImportData
import com.quran.shared.persistence.input.PersistenceImportResult
import com.quran.shared.persistence.model.AyahBookmark
import com.quran.shared.persistence.model.AyahReadingBookmark
import com.quran.shared.persistence.model.Collection
Expand All @@ -18,18 +20,15 @@ import com.quran.shared.persistence.model.Note
import com.quran.shared.persistence.model.PageReadingBookmark
import com.quran.shared.persistence.model.ReadingBookmark
import com.quran.shared.persistence.model.ReadingSession
import com.quran.shared.persistence.input.PersistenceImportData
import com.quran.shared.persistence.input.PersistenceImportResult
import com.quran.shared.persistence.util.PlatformDateTime
import com.quran.shared.persistence.util.toPlatform
import com.quran.shared.persistence.repository.bookmark.repository.BookmarksRepository
import com.quran.shared.persistence.repository.PersistenceResetRepository
import com.quran.shared.persistence.repository.bookmark.repository.BookmarksRepository
import com.quran.shared.persistence.repository.collection.repository.CollectionsRepository
import com.quran.shared.persistence.repository.collectionbookmark.repository.CollectionBookmarksRepository
import com.quran.shared.persistence.repository.importdata.PersistenceImportRepository
import com.quran.shared.persistence.repository.note.repository.NotesRepository
import com.quran.shared.persistence.repository.readingbookmark.repository.ReadingBookmarksRepository
import com.quran.shared.persistence.repository.readingsession.repository.ReadingSessionsRepository
import com.quran.shared.persistence.util.PlatformDateTime
import com.quran.shared.persistence.util.toPlatform
import com.quran.shared.syncengine.AuthenticationDataFetcher
import com.quran.shared.syncengine.LocalModificationDateFetcher
import com.quran.shared.syncengine.SyncLifecycleGate
Expand All @@ -39,10 +38,8 @@ import com.rickclephas.kmp.nativecoroutines.NativeCoroutines
import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import kotlin.native.HiddenFromObjC
import kotlin.time.Instant
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
Expand All @@ -53,6 +50,8 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlin.native.HiddenFromObjC
import kotlin.time.Instant

/**
* Creates the scheduler-backed synchronization client used by [QuranDataService].
Expand Down Expand Up @@ -399,7 +398,7 @@ class QuranDataService internal constructor(
}

@NativeCoroutines
suspend fun addBookmark(sura: Int, ayah: Int, collectionLocalIds: List<String>?): AyahBookmark {
suspend fun addBookmark(sura: Int, ayah: Int, collectionLocalIds: List<String>): AyahBookmark {
return mutatingCall("Failed to add ayah bookmark with collection memberships") {
bookmarksRepository.addBookmark(sura, ayah, collectionLocalIds)
}
Expand All @@ -409,7 +408,7 @@ class QuranDataService internal constructor(
suspend fun addBookmark(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): AyahBookmark {
return mutatingCall("Failed to add ayah bookmark with collection memberships") {
Expand All @@ -418,7 +417,7 @@ class QuranDataService internal constructor(
}

@NativeCoroutines
suspend fun replaceBookmarkCollections(localId: String, collectionLocalIds: List<String>?): Boolean {
suspend fun replaceBookmarkCollections(localId: String, collectionLocalIds: List<String>): Boolean {
return mutatingCall("Failed to replace bookmark collection memberships", triggerAfter = false) {
val changed = bookmarksRepository.replaceBookmarkCollections(localId, collectionLocalIds)
if (changed) {
Expand All @@ -435,7 +434,7 @@ class QuranDataService internal constructor(
@NativeCoroutines
suspend fun replaceBookmarkCollections(
localId: String,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): Boolean {
return mutatingCall("Failed to replace bookmark collection memberships", triggerAfter = false) {
Expand All @@ -451,7 +450,7 @@ class QuranDataService internal constructor(
suspend fun replaceAyahBookmarkCollections(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?
collectionLocalIds: List<String>
): AyahBookmark {
return mutatingCall("Failed to replace ayah bookmark collection memberships", triggerAfter = false) {
val result = bookmarksRepository.replaceAyahBookmarkCollections(sura, ayah, collectionLocalIds)
Expand All @@ -470,7 +469,7 @@ class QuranDataService internal constructor(
suspend fun replaceAyahBookmarkCollections(
sura: Int,
ayah: Int,
collectionLocalIds: List<String>?,
collectionLocalIds: List<String>,
timestamp: PlatformDateTime
): AyahBookmark {
return mutatingCall("Failed to replace ayah bookmark collection memberships", triggerAfter = false) {
Expand Down
Loading
Loading