11package com.quran.labs.androidquran.presenter.bookmark
22
3- import android.annotation.SuppressLint
43import androidx.annotation.VisibleForTesting
54import com.google.android.material.snackbar.BaseTransientBottomBar
65import com.quran.data.dao.BookmarksDao
6+ import com.quran.data.dao.ReadingBookmarksDao
77import com.quran.data.dao.RecentPagesDao
88import com.quran.data.model.bookmark.Bookmark
99import com.quran.data.model.bookmark.BookmarkData
10+ import com.quran.data.model.bookmark.ReadingBookmark
1011import com.quran.data.model.bookmark.RecentPage
1112import com.quran.data.model.bookmark.Tag
1213import com.quran.labs.androidquran.dao.bookmark.BookmarkRawResult
1314import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData
1415import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData.BookmarkItem
1516import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData.NotTaggedHeader
17+ import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData.ReadingBookmarkHeader
18+ import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData.ReadingBookmarkItem
1619import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData.RecentPageHeader
1720import com.quran.labs.androidquran.dao.bookmark.BookmarkRowData.TagHeader
1821import com.quran.labs.androidquran.model.translation.ArabicDatabaseUtils
@@ -27,16 +30,21 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
2730import io.reactivex.rxjava3.core.Single
2831import io.reactivex.rxjava3.observers.DisposableSingleObserver
2932import io.reactivex.rxjava3.schedulers.Schedulers
33+ import kotlinx.coroutines.Dispatchers
3034import kotlinx.coroutines.MainScope
35+ import kotlinx.coroutines.async
36+ import kotlinx.coroutines.coroutineScope
3137import kotlinx.coroutines.flow.drop
3238import kotlinx.coroutines.launch
3339import kotlinx.coroutines.runBlocking
40+ import kotlinx.coroutines.withContext
3441import timber.log.Timber
3542import java.util.concurrent.TimeUnit
3643
3744open class BookmarkPresenter @Inject internal constructor(
3845 private val bookmarksDao : BookmarksDao ,
3946 private val recentPagesDao : RecentPagesDao ,
47+ private val readingBookmarksDao : ReadingBookmarksDao ,
4048 private val quranSettings : QuranSettings ,
4149 private val arabicDatabaseUtils : Provider <ArabicDatabaseUtils >,
4250) : Presenter<BookmarksFragment> {
@@ -81,6 +89,18 @@ open class BookmarkPresenter @Inject internal constructor(
8189 Timber .e(throwable, " Error observing recent page changes" )
8290 }
8391 }
92+
93+ presenterScope.launch {
94+ try {
95+ readingBookmarksDao.readingBookmarkFlow()
96+ .drop(1 )
97+ .collect {
98+ onObservedDataChanged()
99+ }
100+ } catch (throwable: Throwable ) {
101+ Timber .e(throwable, " Error observing reading bookmark changes" )
102+ }
103+ }
84104 }
85105
86106 private fun onObservedDataChanged () {
@@ -294,36 +314,11 @@ open class BookmarkPresenter @Inject internal constructor(
294314
295315 return Single .fromCallable {
296316 runBlocking {
297- val tagsToDelete = mutableListOf<Tag >()
298- val bookmarksToDelete = mutableListOf<Bookmark >()
299- val bookmarksToUntag = mutableListOf<Pair <Bookmark , String >>()
300-
301- items.forEach { row ->
302- val tagId = row.userTagId()
303- when {
304- row.isBookmarkHeader && tagId != null -> {
305- tagsToDelete + = Tag (tagId, row.text)
306- }
307-
308- row.isBookmark && row.bookmark != null -> {
309- if (tagId != null ) {
310- bookmarksToUntag + = row.bookmark to tagId
311- } else {
312- bookmarksToDelete + = row.bookmark
313- }
314- }
315- }
316- }
317-
318- bookmarksDao.removeTags(tagsToDelete)
319- bookmarksToUntag.forEach { (bookmark, tagId) ->
320- bookmarksDao.removeBookmarkFromTag(bookmark, tagId)
321- }
322- bookmarksDao.removeBookmarks(bookmarksToDelete)
317+ removeItems(items)
318+ getBookmarksList(sortOrder, isGroupedByTags)
323319 }
324- }.flatMap {
325- getBookmarksListObservable(sortOrder, isGroupedByTags)
326320 }
321+ .subscribeOn(Schedulers .io())
327322 }
328323
329324 fun cancelDeletion () {
@@ -332,57 +327,89 @@ open class BookmarkPresenter @Inject internal constructor(
332327 itemsToRemove = null
333328 }
334329
335- private fun getBookmarkDataObservable (sortOrder : Int ): Single <BookmarkData > {
336- return Single .fromCallable {
337- runBlocking {
338- BookmarkData (
339- tags = bookmarksDao.tags(),
340- bookmarks = bookmarksDao.bookmarks(sortOrder)
341- )
330+ private suspend fun removeItems (items : List <QuranRow >) {
331+ withContext(Dispatchers .IO ) {
332+ val tagsToDelete = mutableListOf<Tag >()
333+ val bookmarksToDelete = mutableListOf<Bookmark >()
334+ val bookmarksToUntag = mutableListOf<Pair <Bookmark , String >>()
335+
336+ items.forEach { row ->
337+ val tagId = row.userTagId()
338+ when {
339+ row.isBookmarkHeader && tagId != null -> {
340+ tagsToDelete + = Tag (tagId, row.text)
341+ }
342+
343+ row.isBookmark && row.bookmark != null -> {
344+ if (tagId != null ) {
345+ bookmarksToUntag + = row.bookmark to tagId
346+ } else {
347+ bookmarksToDelete + = row.bookmark
348+ }
349+ }
350+ }
342351 }
343- }.subscribeOn(Schedulers .io())
352+
353+ bookmarksDao.removeTags(tagsToDelete)
354+ bookmarksToUntag.forEach { (bookmark, tagId) ->
355+ bookmarksDao.removeBookmarkFromTag(bookmark, tagId)
356+ }
357+ bookmarksDao.removeBookmarks(bookmarksToDelete)
358+ }
344359 }
345360
346- private fun getBookmarksWithAyatObservable (sortOrder : Int ): Single < BookmarkData > {
347- return Single .zip(
348- getBookmarkDataObservable(sortOrder),
349- getRecentPagesObservable()
350- ) { bookmarkData : BookmarkData , recentPages : List < RecentPage > ->
351- bookmarkData.copy(recentPages = recentPages )
361+ private suspend fun getBookmarkData (sortOrder : Int ): BookmarkData {
362+ return withContext( Dispatchers . IO ) {
363+ BookmarkData (
364+ tags = bookmarksDao.tags(),
365+ bookmarks = bookmarksDao.bookmarks(sortOrder)
366+ )
352367 }
353- .map { bookmarkData ->
354- try {
355- bookmarkData.copy(
356- bookmarks = arabicDatabaseUtils().hydrateAyahText(bookmarkData.bookmarks.toMutableList())
357- )
358- } catch (exception: Exception ) {
359- bookmarkData
360- }
368+ }
369+
370+ private suspend fun getBookmarksWithAyat (sortOrder : Int ): BookmarkData {
371+ return coroutineScope {
372+ val bookmarkData = async { getBookmarkData(sortOrder) }
373+ val recentPages = async { getRecentPages() }
374+
375+ hydrateAyahText(
376+ bookmarkData.await().copy(recentPages = recentPages.await())
377+ )
378+ }
379+ }
380+
381+ private suspend fun hydrateAyahText (bookmarkData : BookmarkData ): BookmarkData {
382+ return withContext(Dispatchers .IO ) {
383+ try {
384+ bookmarkData.copy(
385+ bookmarks = arabicDatabaseUtils().hydrateAyahText(bookmarkData.bookmarks.toMutableList())
386+ )
387+ } catch (exception: Exception ) {
388+ bookmarkData
361389 }
390+ }
362391 }
363392
364- private fun getRecentPagesObservable (): Single <List <RecentPage >> {
365- return Single .fromCallable {
366- runBlocking { recentPagesDao.recentPages() }
367- }.subscribeOn(Schedulers .io())
393+ private suspend fun getRecentPages (): List <RecentPage > {
394+ return withContext(Dispatchers .IO ) { recentPagesDao.recentPages() }
368395 }
369396
370397 @VisibleForTesting
371- fun getBookmarksListObservable (sortOrder : Int , groupByTags : Boolean ): Single <BookmarkRawResult > {
372- return getBookmarksWithAyatObservable(sortOrder)
373- .map { bookmarkData ->
374- val rows = getBookmarkRowData(bookmarkData, groupByTags)
375- val tagMap = generateTagMap(bookmarkData.tags)
376- BookmarkRawResult (rows, tagMap)
377- }
378- .subscribeOn(Schedulers .io())
398+ suspend fun getBookmarksList (sortOrder : Int , groupByTags : Boolean ): BookmarkRawResult {
399+ return coroutineScope {
400+ val bookmarkData = async { getBookmarksWithAyat(sortOrder) }
401+ val readingBookmark = async { readingBookmarksDao.readingBookmark() }
402+ val data = bookmarkData.await()
403+ val rows = getBookmarkRowData(data, groupByTags, readingBookmark.await())
404+ val tagMap = generateTagMap(data.tags)
405+ BookmarkRawResult (rows, tagMap)
406+ }
379407 }
380408
381- @SuppressLint(" CheckResult" )
382409 private fun getBookmarks (sortOrder : Int , groupByTags : Boolean ) {
383- getBookmarksListObservable(sortOrder, groupByTags)
384- .observeOn( AndroidSchedulers .mainThread())
385- .subscribe({ result ->
410+ presenterScope.launch {
411+ try {
412+ val result = getBookmarksList(sortOrder, groupByTags)
386413 cachedData = result
387414 val fragment = fragment
388415 if (fragment != null ) {
@@ -394,14 +421,16 @@ open class BookmarkPresenter @Inject internal constructor(
394421 fragment.onNewRawData(result)
395422 }
396423 }
397- }, { throwable ->
424+ } catch ( throwable: Throwable ) {
398425 Timber .e(throwable, " Unable to load bookmarks" )
399- })
426+ }
427+ }
400428 }
401429
402430 private fun getBookmarkRowData (
403431 data : BookmarkData ,
404- groupByTags : Boolean
432+ groupByTags : Boolean ,
433+ readingBookmark : ReadingBookmark ?
405434 ): MutableList <BookmarkRowData > {
406435 val rows = if (groupByTags) {
407436 getRowDataSortedByTags(data.tags, data.bookmarks)
@@ -420,6 +449,10 @@ open class BookmarkPresenter @Inject internal constructor(
420449 rows.add(i + 1 , BookmarkRowData .RecentPage (recentPages[i]))
421450 }
422451 }
452+ if (readingBookmark != null ) {
453+ rows.add(0 , ReadingBookmarkHeader )
454+ rows.add(1 , ReadingBookmarkItem (readingBookmark))
455+ }
423456 return rows
424457 }
425458
0 commit comments