Skip to content

Commit

Permalink
Merge pull request #1834 from quran/migrate_bookmarks_on_page_type_ch…
Browse files Browse the repository at this point in the history
…ange
  • Loading branch information
ahmedre authored Jan 8, 2022
2 parents 2607847 + d9c971c commit 8d34473
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ class BookmarksDBAdapter @Inject constructor(bookmarksDatabase: BookmarksDatabas
.executeAsList()
}

fun replaceRecentPages(pages: List<RecentPage>) {
lastPageQueries.transaction {
pages.forEach { addRecentPage(it.page) }
}
}

fun replaceRecentRangeWithPage(deleteRangeStart: Int, deleteRangeEnd: Int, page: Int) {
val maxPages = Constants.MAX_RECENT_PAGES.toLong()
lastPageQueries.replaceRangeWithPage(deleteRangeStart, deleteRangeEnd, page, maxPages)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.quran.labs.androidquran.database

import com.quran.data.dao.BookmarksDao
import com.quran.data.model.bookmark.Bookmark
import com.quran.data.model.bookmark.RecentPage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
Expand All @@ -21,4 +22,16 @@ class BookmarksDaoImpl @Inject constructor(
bookmarksDBAdapter.updateBookmarks(bookmarks)
}
}

override suspend fun recentPages(): List<RecentPage> {
return withContext(Dispatchers.IO) {
bookmarksDBAdapter.getRecentPages()
}
}

override suspend fun replaceRecentPages(pages: List<RecentPage>) {
withContext(Dispatchers.IO) {
bookmarksDBAdapter.replaceRecentPages(pages)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ public Observable<Boolean> bookmarksObservable() {
return bookmarksPublishSubject.hide();
}

public void notifyBookmarksUpdated() {
bookmarksPublishSubject.onNext(true);
}

public void notifyRecentPagesUpdated() {
recentPageModel.notifyRecentPagesUpdated();
}

public Single<BookmarkData> getBookmarkDataObservable(final int sortOrder) {
return Single.zip(getTagsObservable(),
getBookmarksObservable(sortOrder),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import javax.inject.Inject;
import javax.inject.Singleton;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
Expand All @@ -25,6 +25,7 @@ public class RecentPageModel {

private final BookmarksDBAdapter bookmarksDBAdapter;
private final Subject<Integer> lastPageSubject;
private final Subject<Boolean> refreshRecentPagePublishSubject;

private DisposableSingleObserver<List<RecentPage>> initialDataSubscription;
private final Observable<Boolean> recentPagesUpdatedObservable;
Expand All @@ -35,18 +36,25 @@ public RecentPageModel(BookmarksDBAdapter adapter) {
this.bookmarksDBAdapter = adapter;
this.lastPageSubject = BehaviorSubject.create();
this.recentWriterSubject = PublishSubject.create();

recentPagesUpdatedObservable = this.recentWriterSubject.hide()
.observeOn(Schedulers.io())
.map(update -> {
if (update.deleteRangeStart != null) {
bookmarksDBAdapter.replaceRecentRangeWithPage(
update.deleteRangeStart, update.deleteRangeEnd, update.page);
} else {
bookmarksDBAdapter.addRecentPage(update.page);
}
return true;
}).share();
this.refreshRecentPagePublishSubject = PublishSubject.<Boolean>create().toSerialized();

Observable<Boolean> recentWritesObservable =
this.recentWriterSubject.hide()
.observeOn(Schedulers.io())
.map(update -> {
if (update.deleteRangeStart != null) {
bookmarksDBAdapter.replaceRecentRangeWithPage(
update.deleteRangeStart, update.deleteRangeEnd, update.page);
} else {
bookmarksDBAdapter.addRecentPage(update.page);
}
return true;
});

recentPagesUpdatedObservable = Observable.merge(
recentWritesObservable, refreshRecentPagePublishSubject.hide()
)
.share();

// there needs to always be one subscriber in order for us to properly be able
// to write updates to the database (even when no one else is subscribed).
Expand All @@ -70,6 +78,10 @@ public void onError(Throwable e) {
});
}

public void notifyRecentPagesUpdated() {
refreshRecentPagePublishSubject.onNext(true);
}

@UiThread
public void updateLatestPage(int page) {
if (initialDataSubscription != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import com.quran.labs.androidquran.QuranDataActivity
import com.quran.labs.androidquran.R
import com.quran.labs.androidquran.ui.helpers.QuranDisplayHelper
import com.quran.labs.androidquran.util.QuranSettings
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import javax.inject.Inject

class PageSelectActivity : AppCompatActivity() {
Expand All @@ -19,6 +22,9 @@ class PageSelectActivity : AppCompatActivity() {
private lateinit var adapter : PageSelectAdapter
private lateinit var viewPager: ViewPager

private val scope = MainScope()
private var isProcessing = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(application as QuranApplication).applicationComponent.inject(this)
Expand Down Expand Up @@ -55,6 +61,7 @@ class PageSelectActivity : AppCompatActivity() {

override fun onDestroy() {
adapter.cleanUp()
scope.cancel()
super.onDestroy()
}

Expand All @@ -65,13 +72,26 @@ class PageSelectActivity : AppCompatActivity() {
private fun onPageTypeSelected(type: String) {
val pageType = quranSettings.pageType
if (pageType != type) {
quranSettings.removeDidDownloadPages()
quranSettings.pageType = type
val intent = Intent(this, QuranDataActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
isProcessing = true
scope.launch {
// migrate the bookmarks
presenter.migrateBookmarksData(pageType, type)

// we need to re-check the pages now
quranSettings.removeDidDownloadPages()
// and we can set up our new page type
quranSettings.pageType = type

// go back to Quran Data Activity
val intent = Intent(this@PageSelectActivity, QuranDataActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
finish()
}
startActivity(intent)
} else {
finish()
}
finish()
isProcessing = false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.quran.labs.androidquran.pageselect

import com.quran.data.core.QuranInfo
import com.quran.data.dao.BookmarksDao
import com.quran.data.source.PageProvider
import com.quran.labs.androidquran.database.BookmarksDBAdapter
import com.quran.labs.androidquran.model.bookmark.BookmarkModel
import com.quran.labs.androidquran.presenter.Presenter
import com.quran.labs.androidquran.util.ImageUtil
import com.quran.labs.androidquran.util.QuranFileUtils
Expand All @@ -9,19 +13,27 @@ import dagger.Reusable
import io.reactivex.rxjava3.core.Scheduler
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
import javax.inject.Inject

@Reusable
class PageSelectPresenter @Inject
constructor(private val imageUtil: ImageUtil,
private val quranFileUtils: QuranFileUtils,
private val mainThreadScheduler: Scheduler,
private val urlUtil: UrlUtil,
private val pageTypes:
Map<@JvmSuppressWildcards String, @JvmSuppressWildcards PageProvider>) :
Presenter<PageSelectActivity> {
constructor(
private val imageUtil: ImageUtil,
private val quranFileUtils: QuranFileUtils,
private val mainThreadScheduler: Scheduler,
private val urlUtil: UrlUtil,
private val bookmarksDao: BookmarksDao,
// unfortunately needed for now due to the old Rx code
// not knowing about changes from BookmarksDao, etc.
private val bookmarkModel: BookmarkModel,
private val pageTypes:
Map<@JvmSuppressWildcards String, @JvmSuppressWildcards PageProvider>
) :
Presenter<PageSelectActivity> {
private val baseUrl = "https://android.quran.com/data/pagetypes/snips"
private val compositeDisposable = CompositeDisposable()
private val downloadingSet = mutableSetOf<String>()
Expand All @@ -41,31 +53,92 @@ class PageSelectPresenter @Inject
val previewImage = File(outputPath, "${it.key}.png")
val downloadedImage = if (previewImage.exists()) {
previewImage
} else if (!downloadingSet.contains(it.key)){
} else if (!downloadingSet.contains(it.key)) {
downloadingSet.add(it.key)
val url = "$baseUrl/${it.key}.png"
compositeDisposable.add(
imageUtil.downloadImage(url, previewImage)
.onErrorResumeWith(
imageUtil.downloadImage(urlUtil.fallbackUrl(url), previewImage))
.subscribeOn(Schedulers.io())
.observeOn(mainThreadScheduler)
.subscribe({ generateData() }, { e -> Timber.e(e) })
imageUtil.downloadImage(url, previewImage)
.onErrorResumeWith(
imageUtil.downloadImage(urlUtil.fallbackUrl(url), previewImage)
)
.subscribeOn(Schedulers.io())
.observeOn(mainThreadScheduler)
.subscribe({ generateData() }, { e -> Timber.e(e) })
)
null
} else {
// already downloading
null
}
PageTypeItem(it.key,
downloadedImage,
provider.getPreviewTitle(),
provider.getPreviewDescription())
PageTypeItem(
it.key,
downloadedImage,
provider.getPreviewTitle(),
provider.getPreviewDescription()
)
}
currentView?.onUpdatedData(data)
}
}

/**
* Migrate bookmark and recent page data between two page types
* Consider a page set like madani (604 pages) versus one like Shemerly (521 pages).
* When switching between them, bookmarks need to be mapped so that the same bookmark
* retains its meaning.
*
* Note that this does not support non-Hafs qira'at yet, where the ayah numbers may
* have changed due to kufi versus madani counting.
*/
suspend fun migrateBookmarksData(sourcePageType: String, destinationPageType: String) {
val source = pageTypes[sourcePageType]?.getDataSource()
val destination = pageTypes[destinationPageType]?.getDataSource()
if (source != null && destination != null && source.getNumberOfPages() != destination.getNumberOfPages()) {
val sourcePageSuraStart = source.getSuraForPageArray()
val sourcePageAyahStart = source.getAyahForPageArray()
val destinationQuranInfo = QuranInfo(destination)

val suraAyahFromPage = { page: Int ->
sourcePageSuraStart[page - 1] to sourcePageAyahStart[page - 1]
}

// update the bookmarks
val updatedBookmarks = bookmarksDao.bookmarks()
.map {
val page = it.page
val (pageSura, pageAyah) = suraAyahFromPage(page)
val sura = it.sura ?: pageSura
val ayah = it.ayah ?: pageAyah

val mappedPage = destinationQuranInfo.getPageFromSuraAyah(sura, ayah)

// we only copy the page because sura and ayah are the same.
it.copy(page = mappedPage)
}

if (updatedBookmarks.isNotEmpty()) {
bookmarksDao.replaceBookmarks(updatedBookmarks)
bookmarkModel.notifyBookmarksUpdated()
}

// and update the recents
val updatedRecentPages = bookmarksDao.recentPages()
.sortedBy { it.timestamp }
.map {
val page = it.page
val (pageSura, pageAyah) = suraAyahFromPage(page)

val mappedPage = destinationQuranInfo.getPageFromSuraAyah(pageSura, pageAyah)
it.copy(page = mappedPage)
}

if (updatedRecentPages.isNotEmpty()) {
bookmarksDao.replaceRecentPages(updatedRecentPages)
bookmarkModel.notifyRecentPagesUpdated()
}
}
}

override fun bind(what: PageSelectActivity) {
currentView = what
generateData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

import android.annotation.SuppressLint;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.android.material.snackbar.Snackbar;

import com.google.android.material.snackbar.Snackbar;
import com.quran.data.core.QuranInfo;
import com.quran.data.model.bookmark.Bookmark;
import com.quran.data.model.bookmark.BookmarkData;
import com.quran.data.model.bookmark.RecentPage;
import com.quran.data.model.bookmark.Tag;
import com.quran.labs.androidquran.dao.bookmark.BookmarkResult;
import com.quran.labs.androidquran.data.Constants;
import com.quran.labs.androidquran.model.bookmark.BookmarkModel;
import com.quran.labs.androidquran.dao.bookmark.BookmarkResult;
import com.quran.labs.androidquran.model.translation.ArabicDatabaseUtils;
import com.quran.labs.androidquran.presenter.Presenter;
import com.quran.labs.androidquran.ui.fragment.BookmarksFragment;
Expand All @@ -31,16 +32,14 @@
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Singleton;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
import io.reactivex.rxjava3.schedulers.Schedulers;
import timber.log.Timber;

@Singleton
public class BookmarkPresenter implements Presenter<BookmarksFragment> {
@Snackbar.Duration public static final int DELAY_DELETION_DURATION_IN_MS = 4 * 1000; // 4 seconds
private static final long BOOKMARKS_WITHOUT_TAGS_ID = -1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package com.quran.labs.androidquran.ui.helpers;

import android.content.Context;

import androidx.core.content.ContextCompat;

import com.quran.data.core.QuranInfo;
import com.quran.labs.androidquran.R;
import com.quran.data.model.bookmark.Bookmark;
import com.quran.data.model.bookmark.Tag;
import com.quran.labs.androidquran.R;
import com.quran.labs.androidquran.data.QuranDisplayData;

import javax.inject.Inject;

import dagger.Reusable;

@Reusable
public class QuranRowFactory {
private final QuranInfo quranInfo;
private final QuranDisplayData quranDisplayData;
Expand Down
Loading

0 comments on commit 8d34473

Please sign in to comment.