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