diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt index a430d1895..fa11b33de 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt @@ -28,6 +28,7 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import io.objectbox.Box import io.objectbox.BoxStore +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before @@ -130,7 +131,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { box = boxStore!!.boxFor(BookmarkEntity::class.java) // clear the data before running the test case - clearBookmarks() + runBlocking { clearBookmarks() } // add a file in fileSystem because we need to actual file path for making object of Archive. val loadFileStream = @@ -162,7 +163,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { objectBoxToLibkiwixMigrator.migrateBookMarks(box) // check if data successfully migrated to room val actualDataAfterMigration = - objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().blockingFirst() + objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() assertEquals(1, actualDataAfterMigration.size) assertEquals(actualDataAfterMigration[0].zimReaderSource?.toDatabase(), expectedZimFilePath) assertEquals(actualDataAfterMigration[0].zimId, expectedZimId) @@ -178,7 +179,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { // Migrate data from empty ObjectBox database objectBoxToLibkiwixMigrator.migrateBookMarks(box) val actualDataAfterMigration = - objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().blockingFirst() + objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() assertTrue(actualDataAfterMigration.isEmpty()) // Clear the bookmarks list from device to not affect the other test cases. clearBookmarks() @@ -212,7 +213,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { // Migrate data into Room database objectBoxToLibkiwixMigrator.migrateBookMarks(box) val actualDataAfterMigration = - objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().blockingFirst() + objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() assertEquals(2, actualDataAfterMigration.size) val existingItem = actualDataAfterMigration.find { @@ -250,7 +251,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { objectBoxToLibkiwixMigrator.migrateBookMarks(box) // Check if data successfully migrated to Room val actualDataAfterMigration = - objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().blockingFirst() + objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() assertEquals(1000, actualDataAfterMigration.size) // Clear the bookmarks list from device to not affect the other test cases. clearBookmarks() @@ -276,7 +277,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { objectBoxToLibkiwixMigrator.migrateBookMarks(box) // check if data successfully migrated to room val actualDataAfterMigration = - objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().blockingFirst() + objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() assertEquals(1, actualDataAfterMigration.size) assertEquals(actualDataAfterMigration[0].zimReaderSource?.toDatabase(), null) assertEquals(actualDataAfterMigration[0].zimId, expectedZimId) @@ -307,7 +308,7 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { objectBoxToLibkiwixMigrator.migrateBookMarks(box) // check if data successfully migrated to room val actualDataAfterMigration = - objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().blockingFirst() + objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks().first() assertEquals(1, actualDataAfterMigration.size) assertEquals(actualDataAfterMigration[0].zimReaderSource?.toDatabase(), null) assertEquals(actualDataAfterMigration[0].zimId, expectedZimId) @@ -317,11 +318,11 @@ class ObjectBoxToLibkiwixMigratorTest : BaseActivityTest() { clearBookmarks() } - private fun clearBookmarks() { + private suspend fun clearBookmarks() { // delete bookmarks for testing other edge cases objectBoxToLibkiwixMigrator.libkiwixBookmarks.deleteBookmarks( objectBoxToLibkiwixMigrator.libkiwixBookmarks.bookmarks() - .blockingFirst() as List + .first() as List ) box.removeAll() if (::zimFile.isInitialized) { diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/ImportBookmarkTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/ImportBookmarkTest.kt index 2656f8aa6..4858a8ce9 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/ImportBookmarkTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/ImportBookmarkTest.kt @@ -31,6 +31,7 @@ import com.google.android.apps.common.testing.accessibility.framework.Accessibil import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck import io.objectbox.BoxStore +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.anyOf @@ -171,13 +172,13 @@ class ImportBookmarkTest : BaseActivityTest() { // test with empty data file var tempBookmarkFile = getTemporaryBookmarkFile(true) importBookmarks(tempBookmarkFile) - var actualDataAfterImporting = libkiwixBookmarks.bookmarks().blockingFirst() + var actualDataAfterImporting = libkiwixBookmarks.bookmarks().first() assertEquals(0, actualDataAfterImporting.size) // import the bookmark tempBookmarkFile = getTemporaryBookmarkFile() importBookmarks(tempBookmarkFile) - actualDataAfterImporting = libkiwixBookmarks.bookmarks().blockingFirst() + actualDataAfterImporting = libkiwixBookmarks.bookmarks().first() assertEquals(3, actualDataAfterImporting.size) assertEquals(actualDataAfterImporting[0].title, "Main Page") assertEquals(actualDataAfterImporting[0].url, "https://kiwix.app/A/Main_Page") @@ -185,7 +186,7 @@ class ImportBookmarkTest : BaseActivityTest() { // import duplicate bookmarks importBookmarks(tempBookmarkFile) - actualDataAfterImporting = libkiwixBookmarks.bookmarks().blockingFirst() + actualDataAfterImporting = libkiwixBookmarks.bookmarks().first() assertEquals(3, actualDataAfterImporting.size) // delete the temp file @@ -200,11 +201,11 @@ class ImportBookmarkTest : BaseActivityTest() { } } - private fun clearBookmarks() { + private suspend fun clearBookmarks() { // delete bookmarks for testing other edge cases libkiwixBookmarks.deleteBookmarks( libkiwixBookmarks.bookmarks() - .blockingFirst() as List + .first() as List ) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/LibkiwixBookmarks.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/LibkiwixBookmarks.kt index 547288a1a..e8ddf3f86 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/LibkiwixBookmarks.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/LibkiwixBookmarks.kt @@ -21,17 +21,18 @@ package org.kiwix.kiwixmobile.core.dao import android.os.Build import android.os.Environment import android.util.Base64 -import io.reactivex.BackpressureStrategy -import io.reactivex.BackpressureStrategy.LATEST import io.reactivex.Flowable -import io.reactivex.schedulers.Schedulers -import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.coroutines.reactive.asPublisher import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.rx3.rxSingle +import kotlinx.coroutines.withContext import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.DarkModeConfig import org.kiwix.kiwixmobile.core.R @@ -71,15 +72,19 @@ class LibkiwixBookmarks @Inject constructor( private var bookmarkList: List = arrayListOf() private var libraryBooksList: List = arrayListOf() - @Suppress("CheckResult", "IgnoredReturnValue") - private val bookmarkListBehaviour: BehaviorSubject>? by lazy { - BehaviorSubject.create>().also { subject -> - rxSingle { getBookmarksList() } - .subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io()) - .subscribe(subject::onNext, subject::onError) + @Suppress("InjectDispatcher", "TooGenericExceptionCaught") + private val bookmarkListFlow: MutableStateFlow> by lazy { + MutableStateFlow>(emptyList()).also { flow -> + CoroutineScope(Dispatchers.IO).launch { + try { + val bookmarks = getBookmarksList() + flow.emit(bookmarks) + } catch (e: Exception) { + e.printStackTrace() + } + } } } - private val bookmarksFolderPath: String by lazy { if (Build.DEVICE.contains("generic")) { // Workaround for emulators: Emulators have limited memory and @@ -112,30 +117,35 @@ class LibkiwixBookmarks @Inject constructor( manager.readBookmarkFile(bookmarkFile.canonicalPath) } - fun bookmarks(): Flowable> = - flowableBookmarkList() + fun bookmarks(): Flow> = + bookmarkListFlow .map { it } - override fun pages(): Flowable> = bookmarks() + // Currently kept in RxJava Flowable because `PageViewModel` still expects RxJava streams. + // This can be refactored to use Kotlin Flow once `PageViewModel` is migrated to coroutines. + override fun pages(): Flowable> = + Flowable.fromPublisher(bookmarks().asPublisher()) override fun deletePages(pagesToDelete: List) = deleteBookmarks(pagesToDelete as List) - suspend fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List { - return zimFileReader?.let { reader -> - getBookmarksList() - .filter { it.zimId == reader.id } - .map(LibkiwixBookmarkItem::bookmarkUrl) - }.orEmpty() - } + @Suppress("InjectDispatcher") + suspend fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List = + withContext(Dispatchers.IO) { + return@withContext zimFileReader?.let { reader -> + getBookmarksList() + .filter { it.zimId == reader.id } + .map(LibkiwixBookmarkItem::bookmarkUrl) + }.orEmpty() + } - fun bookmarkUrlsForCurrentBook(zimId: String): Flowable> = - flowableBookmarkList() + @Suppress("InjectDispatcher") + fun bookmarkUrlsForCurrentBook(zimId: String): Flow> = + bookmarkListFlow .map { bookmarksList -> bookmarksList.filter { it.zimId == zimId } .map(LibkiwixBookmarkItem::bookmarkUrl) - } - .subscribeOn(Schedulers.io()) + }.flowOn(Dispatchers.IO) /** * Saves bookmarks in libkiwix. The use of `shouldWriteBookmarkToFile` is primarily @@ -165,7 +175,7 @@ class LibkiwixBookmarks @Inject constructor( library.addBookmark(bookmark).also { if (shouldWriteBookmarkToFile) { writeBookMarksAndSaveLibraryToFile() - updateFlowableBookmarkList() + updateFlowBookmarkList() } // dispose the bookmark bookmark.dispose() @@ -185,7 +195,7 @@ class LibkiwixBookmarks @Inject constructor( } } addBookToLibraryIfNotExist(book) - updateFlowableBookmarkList() + updateFlowBookmarkList() } catch (ignore: Exception) { Log.e( TAG, @@ -228,7 +238,7 @@ class LibkiwixBookmarks @Inject constructor( .also { CoroutineScope(dispatcher).launch { writeBookMarksAndSaveLibraryToFile() - updateFlowableBookmarkList() + updateFlowBookmarkList() } } } @@ -363,27 +373,8 @@ class LibkiwixBookmarks @Inject constructor( it.zimReaderSource == libkiwixBookmarkItem.zimReaderSource } - private fun flowableBookmarkList( - backpressureStrategy: BackpressureStrategy = LATEST - ): Flowable> { - return Flowable.create({ emitter -> - val disposable = - bookmarkListBehaviour?.subscribe( - { list -> - if (!emitter.isCancelled) { - emitter.onNext(list.toList()) - } - }, - emitter::onError, - emitter::onComplete - ) - - emitter.setDisposable(disposable) - }, backpressureStrategy) - } - - private suspend fun updateFlowableBookmarkList() { - bookmarkListBehaviour?.onNext(getBookmarksList()) + private suspend fun updateFlowBookmarkList() { + bookmarkListFlow.emit(getBookmarksList()) } // Export the `bookmark.xml` file to the `Download/org.kiwix/` directory of internal storage. diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/DataSource.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/DataSource.kt index 31ccc27ae..5791b2890 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/DataSource.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/DataSource.kt @@ -41,11 +41,11 @@ interface DataSource { fun saveHistory(history: HistoryItem): Completable fun deleteHistory(historyList: List): Completable fun clearHistory(): Completable - fun getBookmarks(): Flowable> - fun getCurrentZimBookmarksUrl(): io.reactivex.rxjava3.core.Single> - fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem): io.reactivex.rxjava3.core.Completable - fun deleteBookmarks(bookmarks: List): Completable - fun deleteBookmark(bookId: String, bookmarkUrl: String): Completable? + fun getBookmarks(): Flow> + suspend fun getCurrentZimBookmarksUrl(): List + suspend fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem) + suspend fun deleteBookmarks(bookmarks: List) + suspend fun deleteBookmark(bookId: String, bookmarkUrl: String) fun booksOnDiskAsListItems(): Flowable> fun saveNote(noteListItem: NoteListItem): Completable fun deleteNote(noteTitle: String): Completable diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt index 6be1b5d59..6d26d2604 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/Repository.kt @@ -21,9 +21,7 @@ package org.kiwix.kiwixmobile.core.data import io.reactivex.Completable import io.reactivex.Flowable import io.reactivex.Scheduler -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.rx3.rxCompletable -import kotlinx.coroutines.rx3.rxSingle +import kotlinx.coroutines.flow.Flow import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.NewBookDao @@ -122,24 +120,19 @@ class Repository @Inject internal constructor( }.subscribeOn(ioThread) override fun getBookmarks() = - libkiwixBookmarks.bookmarks() as Flowable> + libkiwixBookmarks.bookmarks() as Flow> - override fun getCurrentZimBookmarksUrl() = - rxSingle { - libkiwixBookmarks.getCurrentZimBookmarksUrl(zimReaderContainer.zimFileReader) - }.subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io()) + override suspend fun getCurrentZimBookmarksUrl() = + libkiwixBookmarks.getCurrentZimBookmarksUrl(zimReaderContainer.zimFileReader) - override fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem) = - rxCompletable { libkiwixBookmarks.saveBookmark(libkiwixBookmarkItem) } - .subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io()) + override suspend fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem) = + libkiwixBookmarks.saveBookmark(libkiwixBookmarkItem) - override fun deleteBookmarks(bookmarks: List) = - Completable.fromAction { libkiwixBookmarks.deleteBookmarks(bookmarks) } - .subscribeOn(ioThread) + override suspend fun deleteBookmarks(bookmarks: List) = + libkiwixBookmarks.deleteBookmarks(bookmarks) - override fun deleteBookmark(bookId: String, bookmarkUrl: String): Completable? = - Completable.fromAction { libkiwixBookmarks.deleteBookmark(bookId, bookmarkUrl) } - .subscribeOn(ioThread) + override suspend fun deleteBookmark(bookId: String, bookmarkUrl: String) = + libkiwixBookmarks.deleteBookmark(bookId, bookmarkUrl) override fun saveNote(noteListItem: NoteListItem): Completable = Completable.fromAction { notesRoomDao.saveNote(noteListItem) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index 0a777dfcf..2b7ebe71c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -94,15 +94,15 @@ import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar -import io.reactivex.Flowable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.processors.BehaviorProcessor import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -202,7 +202,7 @@ abstract class CoreReaderFragment : NavigationHistoryClickListener, ShowDonationDialogCallback { protected val webViewList: MutableList = ArrayList() - private val webUrlsProcessor = BehaviorProcessor.create() + private val webUrlsFlow = MutableStateFlow("") private var fragmentReaderBinding: FragmentReaderBinding? = null var toolbar: Toolbar? = null @@ -333,7 +333,7 @@ abstract class CoreReaderFragment : private var tableDrawerRight: RecyclerView? = null private var tabCallback: ItemTouchHelper.Callback? = null private var donationLayout: FrameLayout? = null - private var bookmarkingDisposable: Disposable? = null + private var bookmarkingJob: Job? = null private var isBookmarked = false private lateinit var serviceConnection: ServiceConnection private var readAloudService: ReadAloudService? = null @@ -1269,7 +1269,7 @@ abstract class CoreReaderFragment : (requireActivity() as? AppCompatActivity)?.setSupportActionBar(null) } repositoryActions?.dispose() - safeDispose() + safelyCancelBookmarkJob() unBindViewsAndBinding() tabCallback = null hideBackToTopTimer?.cancel() @@ -1546,7 +1546,7 @@ abstract class CoreReaderFragment : tabsAdapter?.selected = currentWebViewIndex updateBottomToolbarVisibility() loadPrefs() - updateUrlProcessor() + updateUrlFlow() updateTableOfContents() updateTitle() } @@ -1898,24 +1898,25 @@ abstract class CoreReaderFragment : } protected fun setUpBookmarks(zimFileReader: ZimFileReader) { - safeDispose() - bookmarkingDisposable = Flowable.combineLatest( - libkiwixBookmarks?.bookmarkUrlsForCurrentBook(zimFileReader.id), - webUrlsProcessor, - List::contains - ) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ isBookmarked: Boolean -> - this.isBookmarked = isBookmarked + safelyCancelBookmarkJob() + bookmarkingJob = CoroutineScope(Dispatchers.Main).launch { + combine( + libkiwixBookmarks?.bookmarkUrlsForCurrentBook(zimFileReader.id) ?: emptyFlow(), + webUrlsFlow, + List::contains + ).collect { isBookmarked -> + this@CoreReaderFragment.isBookmarked = isBookmarked bottomToolbarBookmark?.setImageResource( if (isBookmarked) R.drawable.ic_bookmark_24dp else R.drawable.ic_bookmark_border_24dp ) - }, Throwable::printStackTrace) - updateUrlProcessor() + } + } + updateUrlFlow() } - private fun safeDispose() { - bookmarkingDisposable?.dispose() + private fun safelyCancelBookmarkJob() { + bookmarkingJob?.cancel() + bookmarkingJob = null } private fun isNotPreviouslyOpenZim(zimReaderSource: ZimReaderSource?): Boolean = @@ -2464,8 +2465,8 @@ abstract class CoreReaderFragment : protected fun urlIsValid(): Boolean = getCurrentWebView()?.url != null - private fun updateUrlProcessor() { - getCurrentWebView()?.url?.let(webUrlsProcessor::offer) + private fun updateUrlFlow() { + getCurrentWebView()?.url?.let { webUrlsFlow.value = it } } private fun updateNightMode() { @@ -2673,7 +2674,7 @@ abstract class CoreReaderFragment : // If a URL fails to load, update the bookmark toggle. // This fixes the scenario where the previous page is bookmarked and the next // page fails to load, ensuring the bookmark toggle is unset correctly. - updateUrlProcessor() + updateUrlFlow() Log.d(TAG_KIWIX, String.format(getString(R.string.error_article_url_not_found), url)) } } @@ -2681,7 +2682,7 @@ abstract class CoreReaderFragment : @Suppress("MagicNumber") override fun webViewProgressChanged(progress: Int, webView: WebView) { if (isAdded) { - updateUrlProcessor() + updateUrlFlow() showProgressBarWithProgress(progress) if (progress == 100) { hideProgressBar() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt index 67e05743c..0f4104ac4 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/MainRepositoryActions.kt @@ -19,6 +19,8 @@ package org.kiwix.kiwixmobile.core.main import io.reactivex.disposables.Disposable import kotlinx.coroutines.flow.first +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.kiwix.kiwixmobile.core.dao.entities.WebViewHistoryEntity import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.di.ActivityScope @@ -35,7 +37,6 @@ private const val TAG = "MainPresenter" @ActivityScope class MainRepositoryActions @Inject constructor(private val dataSource: DataSource) { private var saveHistoryDisposable: Disposable? = null - private var saveBookmarkDisposable: io.reactivex.rxjava3.disposables.Disposable? = null private var saveNoteDisposable: Disposable? = null private var saveBookDisposable: Disposable? = null private var deleteNoteDisposable: Disposable? = null @@ -48,16 +49,26 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour .subscribe({}, { e -> Log.e(TAG, "Unable to save history", e) }) } - fun saveBookmark(bookmark: LibkiwixBookmarkItem) { - saveBookmarkDisposable = - dataSource.saveBookmark(bookmark) - .subscribe({}, { e -> Log.e(TAG, "Unable to save bookmark", e) }) + @Suppress("InjectDispatcher", "TooGenericExceptionCaught") + suspend fun saveBookmark(bookmark: LibkiwixBookmarkItem) { + withContext(Dispatchers.IO) { + try { + dataSource.saveBookmark(bookmark) + } catch (e: Exception) { + Log.e(TAG, "Unable to save bookmark", e) + } + } } - fun deleteBookmark(bookId: String, bookmarkUrl: String) { - dataSource.deleteBookmark(bookId, bookmarkUrl) - ?.subscribe({}, { e -> Log.e(TAG, "Unable to delete bookmark", e) }) - ?: Log.e(TAG, "Unable to delete bookmark") + @Suppress("InjectDispatcher", "TooGenericExceptionCaught") + suspend fun deleteBookmark(bookId: String, bookmarkUrl: String) { + withContext(Dispatchers.IO) { + try { + dataSource.deleteBookmark(bookId, bookmarkUrl) + } catch (e: Exception) { + Log.e(TAG, "Unable to delete bookmark", e) + } + } } fun saveNote(note: NoteListItem) { @@ -93,7 +104,6 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour fun dispose() { saveHistoryDisposable?.dispose() - saveBookmarkDisposable?.dispose() saveNoteDisposable?.dispose() deleteNoteDisposable?.dispose() saveBookDisposable?.dispose() diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModelTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModelTest.kt index 6a7f1bd0f..978a0b298 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModelTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModelTest.kt @@ -21,11 +21,11 @@ package org.kiwix.kiwixmobile.core.page.bookmark.viewmodel import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk -import io.reactivex.plugins.RxJavaPlugins -import io.reactivex.processors.PublishProcessor -import io.reactivex.schedulers.Schedulers +import io.reactivex.Flowable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.reactive.asPublisher import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach @@ -45,7 +45,6 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderSource import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.dialog.AlertDialogShower import org.kiwix.sharedFunctions.InstantExecutorExtension -import org.kiwix.sharedFunctions.setScheduler import java.util.UUID @ExtendWith(InstantExecutorExtension::class) @@ -58,13 +57,8 @@ internal class BookmarkViewModelTest { private lateinit var viewModel: BookmarkViewModel - private val itemsFromDb: PublishProcessor> = - PublishProcessor.create() - - init { - setScheduler(Schedulers.trampoline()) - RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } - } + private val itemsFromDb: MutableStateFlow> = + MutableStateFlow(emptyList()) @BeforeEach fun init() { @@ -72,8 +66,10 @@ internal class BookmarkViewModelTest { every { zimReaderContainer.id } returns "id" every { zimReaderContainer.name } returns "zimName" every { sharedPreferenceUtil.showBookmarksAllBooks } returns true - every { libkiwixBookMarks.bookmarks() } returns itemsFromDb.distinctUntilChanged() - every { libkiwixBookMarks.pages() } returns libkiwixBookMarks.bookmarks() + every { libkiwixBookMarks.bookmarks() } returns itemsFromDb + every { libkiwixBookMarks.pages() } returns Flowable.fromPublisher( + libkiwixBookMarks.bookmarks().asPublisher() + ) viewModel = BookmarkViewModel(libkiwixBookMarks, zimReaderContainer, sharedPreferenceUtil).apply { alertDialogShower = dialogShower