package com.quran.labs.androidquran.model.bookmark; import android.support.v4.util.Pair; import com.quran.labs.androidquran.dao.Bookmark; import com.quran.labs.androidquran.dao.BookmarkData; import com.quran.labs.androidquran.dao.Tag; import com.quran.labs.androidquran.database.BookmarksDBAdapter; import com.quran.labs.androidquran.ui.helpers.QuranRow; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import io.reactivex.Completable; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.Subject; @Singleton public class BookmarkModel { private final RecentPageModel recentPageModel; private final BookmarksDBAdapter bookmarksDBAdapter; private final Subject<Tag> tagPublishSubject; private final Subject<Boolean> bookmarksPublishSubject; @Inject public BookmarkModel(BookmarksDBAdapter bookmarksAdapter, RecentPageModel recentPageModel) { this.recentPageModel = recentPageModel; this.bookmarksDBAdapter = bookmarksAdapter; tagPublishSubject = PublishSubject.<Tag>create().toSerialized(); bookmarksPublishSubject = PublishSubject.<Boolean>create().toSerialized(); } public Observable<Tag> tagsObservable() { return tagPublishSubject.hide(); } public Observable<Boolean> recentPagesUpdatedObservable() { return recentPageModel.getRecentPagesUpdatedObservable(); } public Observable<Boolean> bookmarksObservable() { return bookmarksPublishSubject.hide(); } public Single<BookmarkData> getBookmarkDataObservable(final int sortOrder) { return Single.zip(getTagsObservable(), getBookmarksObservable(sortOrder), recentPageModel.getRecentPagesObservable(), BookmarkData::new) .subscribeOn(Schedulers.io()); } public Completable removeItemsObservable( final List<QuranRow> itemsToRemoveRef) { return Completable.fromCallable(() -> { List<Long> tagsToDelete = new ArrayList<>(); List<Long> bookmarksToDelete = new ArrayList<>(); List<Pair<Long, Long>> untag = new ArrayList<>(); for (int i = 0, size = itemsToRemoveRef.size(); i < size; i++) { QuranRow row = itemsToRemoveRef.get(i); if (row.isBookmarkHeader() && row.tagId > 0) { tagsToDelete.add(row.tagId); } else if (row.isBookmark() && row.bookmarkId > 0) { if (row.tagId > 0) { untag.add(new Pair<>(row.bookmarkId, row.tagId)); } else { bookmarksToDelete.add(row.bookmarkId); } } } bookmarksDBAdapter.bulkDelete(tagsToDelete, bookmarksToDelete, untag); return null; }).subscribeOn(Schedulers.io()); } public Observable<Long> addTagObservable(final String title) { return Observable.fromCallable(() -> { Long result = bookmarksDBAdapter.addTag(title); tagPublishSubject.onNext(new Tag(result, title)); return result; }).subscribeOn(Schedulers.io()); } public Completable updateTag(final Tag tag) { return Completable.fromCallable(() -> { boolean result = bookmarksDBAdapter.updateTag(tag.id, tag.name); if (result) { tagPublishSubject.onNext(tag); } return null; }).subscribeOn(Schedulers.io()); } public Observable<Boolean> updateBookmarkTags(final long[] bookmarkIds, final Set<Long> tagIds, final boolean deleteNonTagged) { return Observable.fromCallable(() -> { boolean result = bookmarksDBAdapter.tagBookmarks(bookmarkIds, tagIds, deleteNonTagged); if (result) { bookmarksPublishSubject.onNext(true); } return result; }).subscribeOn(Schedulers.io()); } public Observable<Long> safeAddBookmark(final Integer sura, final Integer ayah, final int page) { return Observable.fromCallable(() -> { long result = bookmarksDBAdapter.addBookmarkIfNotExists(sura, ayah, page); bookmarksPublishSubject.onNext(true); return result; }).subscribeOn(Schedulers.io()); } public Single<List<Tag>> getTagsObservable() { return Single.fromCallable(bookmarksDBAdapter::getTags).subscribeOn(Schedulers.io()); } private Single<List<Bookmark>> getBookmarksObservable(final int sortOrder) { return Single.fromCallable(() -> bookmarksDBAdapter.getBookmarks(sortOrder)); } public Maybe<List<Long>> getBookmarkTagIds(Single<Long> bookmarkIdSingle) { return bookmarkIdSingle.filter(bookmarkId -> bookmarkId > 0) .map(bookmarksDBAdapter::getBookmarkTagIds) .subscribeOn(Schedulers.io()); } public Single<Long> getBookmarkId(final Integer sura, final Integer ayah, final int page) { return Single.fromCallable(() -> bookmarksDBAdapter.getBookmarkId(sura, ayah, page)) .subscribeOn(Schedulers.io()); } public Observable<List<Bookmark>> getBookmarkedAyahsOnPageObservable(Integer... pages) { return Observable.fromArray(pages) .map(bookmarksDBAdapter::getBookmarkedAyahsOnPage) .filter(bookmarks -> !bookmarks.isEmpty()) .subscribeOn(Schedulers.io()); } public Observable<Pair<Integer, Boolean>> getIsBookmarkedObservable(Integer... pages) { return Observable.fromArray(pages) .map(page -> new Pair<>(page, bookmarksDBAdapter.getBookmarkId(null, null, page) > 0)) .subscribeOn(Schedulers.io()); } public Single<Boolean> getIsBookmarkedObservable( final Integer sura, final Integer ayah, final int page) { return getBookmarkId(sura, ayah, page) .map(bookmarkId -> bookmarkId > 0) .subscribeOn(Schedulers.io()); } public Single<Boolean> toggleBookmarkObservable( final Integer sura, final Integer ayah, final int page) { return getBookmarkId(sura, ayah, page) .map(bookmarkId -> { boolean result; if (bookmarkId > 0) { bookmarksDBAdapter.removeBookmark(bookmarkId); result = false; } else { bookmarksDBAdapter.addBookmark(sura, ayah, page); result = true; } bookmarksPublishSubject.onNext(true); return result; }).subscribeOn(Schedulers.io()); } public Observable<Boolean> importBookmarksObservable(final BookmarkData data) { return Observable.fromCallable(() -> { boolean result = bookmarksDBAdapter.importBookmarks(data); if (result) { bookmarksPublishSubject.onNext(true); } return result; }).subscribeOn(Schedulers.io()).cache(); } }