diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt new file mode 100644 index 0000000000..02edac1fcc --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToLibkiwixMigratorTest.kt @@ -0,0 +1,155 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.mockk.mockk +import io.objectbox.Box +import io.objectbox.BoxStore +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.jupiter.api.Test +import org.junit.runner.RunWith +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks +import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity +import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.libkiwix.Book + +@RunWith(AndroidJUnit4::class) +class ObjectBoxToLibkiwixMigratorTest { + private var boxStore: BoxStore = mockk() + private var libkiwixBookmarks: LibkiwixBookmarks = mockk(relaxed = true) + private lateinit var objectBoxToLibkiwixMigrator: ObjectBoxToLibkiwixMigrator + + @Test + fun migrateBookmarkTest(): Unit = runBlocking { + val sharedPreferenceUtil: SharedPreferenceUtil = mockk(relaxed = true) + objectBoxToLibkiwixMigrator = ObjectBoxToLibkiwixMigrator() + objectBoxToLibkiwixMigrator.sharedPreferenceUtil = sharedPreferenceUtil + objectBoxToLibkiwixMigrator.boxStore = boxStore + objectBoxToLibkiwixMigrator.libkiwixBookmarks = libkiwixBookmarks + val box = boxStore.boxFor(BookmarkEntity::class.java) + val expectedZimName = "Alpine_Linux" + val expectedZimId = "8812214350305159407L" + val expectedZimFilePath = "data/Android/kiwix/alpine_linux_2022.zim" + val expectedTitle = "Installing" + val expectedBookmarkUrl = "https://alpine_linux/InstallingPage" + val expectedFavicon = "" + val bookmarkEntity = BookmarkEntity( + 0, + expectedZimId, + expectedZimName, + expectedZimFilePath, + expectedBookmarkUrl, + expectedTitle, + expectedFavicon + ) + box.put(bookmarkEntity) + // migrate data into room database + objectBoxToLibkiwixMigrator.migrateBookMarks(box) + // check if data successfully migrated to room + val actual = libkiwixBookmarks.bookmarks().blockingFirst() + assertEquals(actual.size, 1) + assertEquals(actual[0].zimFilePath, expectedZimFilePath) + assertEquals(actual[0].zimId, expectedZimId) + assertEquals(actual[0].favicon, expectedFavicon) + assertEquals(actual[0].title, expectedTitle) + assertEquals(actual[0].url, expectedBookmarkUrl) + + // clear both databases for recent searches to test more edge cases + clearBookmarks(box, libkiwixBookmarks) + // Migrate data from empty ObjectBox database + objectBoxToLibkiwixMigrator.migrateBookMarks(box) + val actualData = libkiwixBookmarks.bookmarks().blockingFirst() + assertTrue(actualData.isEmpty()) + + // Test if data successfully migrated to Room and existing data is preserved + val existingTitle = "Home Page" + val existingBookmarkUrl = "https://alpine_linux/HomePage" + val secondBookmarkEntity = BookmarkEntity( + 0, + expectedZimId, + expectedZimName, + expectedZimFilePath, + existingBookmarkUrl, + existingTitle, + expectedFavicon + ) + val libkiwixBook: Book = mockk(relaxed = true) + libkiwixBookmarks.saveBookmark(LibkiwixBookmarkItem(secondBookmarkEntity, libkiwixBook)) + box.put(bookmarkEntity) + // Migrate data into Room database + objectBoxToLibkiwixMigrator.migrateBookMarks(box) + val actualDataAfterMigration = libkiwixBookmarks.bookmarks().blockingFirst() + assertEquals(2, actual.size) + val existingItem = + actualDataAfterMigration.find { + it.url == existingBookmarkUrl && it.title == existingTitle + } + assertNotNull(existingItem) + val newItem = + actualDataAfterMigration.find { + it.url == expectedBookmarkUrl && it.title == expectedTitle + } + assertNotNull(newItem) + + clearBookmarks(box, libkiwixBookmarks) + + // Test large data migration for recent searches + val numEntities = 10000 + // Insert a large number of recent search entities into ObjectBox + for (i in 1..numEntities) { + val bookMarkUrl = "https://alpine_linux/search_$i" + val title = "title_$i" + val bookmarkEntity1 = BookmarkEntity( + 0, + expectedZimId, + expectedZimName, + expectedZimFilePath, + bookMarkUrl, + title, + expectedFavicon + ) + box.put(bookmarkEntity1) + } + val startTime = System.currentTimeMillis() + // Migrate data into Room database + objectBoxToLibkiwixMigrator.migrateBookMarks(box) + val endTime = System.currentTimeMillis() + val migrationTime = endTime - startTime + // Check if data successfully migrated to Room + val actualDataAfterLargeMigration = + libkiwixBookmarks.bookmarks().blockingFirst() + assertEquals(numEntities, actualDataAfterLargeMigration.size) + // Assert that the migration completes within a reasonable time frame + assertTrue("Migration took too long: $migrationTime ms", migrationTime < 5000) + } + + private fun clearBookmarks(box: Box, libkiwixBookmark: LibkiwixBookmarks) { + // delete bookmarks for testing other edge cases + libkiwixBookmark.deleteBookmarks( + libkiwixBookmark.bookmarks().blockingFirst() as List + ) + box.removeAll() + } +} diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/BookmarksRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/BookmarksRobot.kt index df59cd09a1..19584fd70c 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/BookmarksRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/BookmarksRobot.kt @@ -18,18 +18,29 @@ package org.kiwix.kiwixmobile.page.bookmarks +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers.withText import applyWithViewHierarchyPrinting import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.adevinta.android.barista.interaction.BaristaSleepInteractions import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.Findable.StringId.ContentDesc import org.kiwix.kiwixmobile.Findable.StringId.TextId +import org.kiwix.kiwixmobile.Findable.Text +import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.testutils.TestUtils +import java.util.concurrent.TimeUnit fun bookmarks(func: BookmarksRobot.() -> Unit) = BookmarksRobot().applyWithViewHierarchyPrinting(func) class BookmarksRobot : BaseRobot() { + private var retryCountForBookmarkAddedButton = 5 + fun assertBookMarksDisplayed() { assertDisplayed(R.string.bookmarks_from_current_book) } @@ -41,4 +52,45 @@ class BookmarksRobot : BaseRobot() { fun assertDeleteBookmarksDialogDisplayed() { isVisible(TextId(R.string.delete_bookmarks)) } + + fun clickOnSaveBookmarkImage() { + pauseForBetterTestPerformance() + clickOn(ViewId(R.id.bottom_toolbar_bookmark)) + } + + fun longClickOnSaveBookmarkImage() { + // wait for disappearing the snack-bar after removing the bookmark + BaristaSleepInteractions.sleep(5L, TimeUnit.SECONDS) + longClickOn(ViewId(R.id.bottom_toolbar_bookmark)) + } + + fun clickOnOpenSavedBookmarkButton() { + try { + onView(withText("OPEN")).perform(click()) + } catch (runtimeException: RuntimeException) { + if (retryCountForBookmarkAddedButton > 0) { + retryCountForBookmarkAddedButton-- + clickOnOpenSavedBookmarkButton() + } else { + throw RuntimeException( + "Unable to save the bookmark, original exception is" + + " ${runtimeException.localizedMessage}" + ) + } + } + } + + fun assertBookmarkSaved() { + pauseForBetterTestPerformance() + isVisible(Text("Test Zim")) + } + + fun assertBookmarkRemoved() { + pauseForBetterTestPerformance() + onView(withText("Test Zim")).check(ViewAssertions.doesNotExist()) + } + + private fun pauseForBetterTestPerformance() { + BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS_FOR_SEARCH_TEST.toLong()) + } } diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/LibkiwixBookmarkTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/LibkiwixBookmarkTest.kt new file mode 100644 index 0000000000..27a411b5dd --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/page/bookmarks/LibkiwixBookmarkTest.kt @@ -0,0 +1,108 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.page.bookmarks + +import androidx.core.content.edit +import androidx.core.net.toUri +import androidx.lifecycle.Lifecycle +import androidx.preference.PreferenceManager +import androidx.test.core.app.ActivityScenario +import androidx.test.internal.runner.junit4.statement.UiThreadStatement +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.kiwix.kiwixmobile.BaseActivityTest +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.main.KiwixMainActivity +import org.kiwix.kiwixmobile.nav.destination.library.LocalLibraryFragmentDirections +import org.kiwix.kiwixmobile.search.SearchFragmentTest +import org.kiwix.kiwixmobile.testutils.RetryRule +import org.kiwix.kiwixmobile.testutils.TestUtils +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream + +class LibkiwixBookmarkTest : BaseActivityTest() { + + @Rule + @JvmField + var retryRule = RetryRule() + + private lateinit var kiwixMainActivity: KiwixMainActivity + + @Before + override fun waitForIdle() { + UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply { + if (TestUtils.isSystemUINotRespondingDialogVisible(this)) { + TestUtils.closeSystemDialogs(context) + } + waitForIdle() + } + PreferenceManager.getDefaultSharedPreferences(context).edit { + putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false) + putBoolean(SharedPreferenceUtil.PREF_WIFI_ONLY, false) + putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true) + } + activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply { + moveToState(Lifecycle.State.RESUMED) + } + } + + @Test + fun testBookmarks() { + activityScenario.onActivity { + kiwixMainActivity = it + kiwixMainActivity.navigate(R.id.libraryFragment) + } + val loadFileStream = + SearchFragmentTest::class.java.classLoader.getResourceAsStream("testzim.zim") + val zimFile = File(context.cacheDir, "testzim.zim") + if (zimFile.exists()) zimFile.delete() + zimFile.createNewFile() + loadFileStream.use { inputStream -> + val outputStream: OutputStream = FileOutputStream(zimFile) + outputStream.use { it -> + val buffer = ByteArray(inputStream.available()) + var length: Int + while (inputStream.read(buffer).also { length = it } > 0) { + it.write(buffer, 0, length) + } + } + } + + UiThreadStatement.runOnUiThread { + kiwixMainActivity.navigate( + LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader() + .apply { zimFileUri = zimFile.toUri().toString() } + ) + } + bookmarks { + clickOnSaveBookmarkImage() + clickOnOpenSavedBookmarkButton() + assertBookmarkSaved() + pressBack() + clickOnSaveBookmarkImage() + longClickOnSaveBookmarkImage() + assertBookmarkRemoved() + } + } +} diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index c013798c99..1e17257517 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -86,7 +86,7 @@ object Versions { const val core_ktx: String = "1.3.2" - const val libkiwix: String = "1.0.0" + const val libkiwix: String = "1.0.1" const val material: String = "1.2.1" diff --git a/core/detekt_baseline.xml b/core/detekt_baseline.xml index d3886eedb9..9d11100032 100644 --- a/core/detekt_baseline.xml +++ b/core/detekt_baseline.xml @@ -68,6 +68,7 @@ ReturnCount:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$@SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean TooGenericExceptionCaught:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$exception: Exception TooGenericExceptionCaught:JNIInitialiser.kt$JNIInitialiser$e: Exception + TooGenericExceptionCaught:LibkiwixBookmarks.kt$LibkiwixBookmarks$exception: Exception TooGenericExceptionCaught:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$exception: Exception TooGenericExceptionCaught:ZimFileReader.kt$ZimFileReader$exception: Exception TooGenericExceptionThrown:AdapterDelegateManager.kt$AdapterDelegateManager$throw RuntimeException("No delegate registered for $item") 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 new file mode 100644 index 0000000000..253bd13687 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/LibkiwixBookmarks.kt @@ -0,0 +1,234 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.dao + +import android.os.Build +import android.util.Base64 +import android.util.Log +import io.reactivex.BackpressureStrategy +import io.reactivex.BackpressureStrategy.LATEST +import io.reactivex.Flowable +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.BehaviorSubject +import org.kiwix.kiwixmobile.core.BuildConfig +import org.kiwix.kiwixmobile.core.extensions.isFileExist +import org.kiwix.kiwixmobile.core.page.adapter.Page +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem +import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE +import org.kiwix.kiwixmobile.core.reader.ZimFileReader +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.libkiwix.Book +import org.kiwix.libkiwix.Bookmark +import org.kiwix.libkiwix.Library +import org.kiwix.libkiwix.Manager +import java.io.File +import javax.inject.Inject + +class LibkiwixBookmarks @Inject constructor( + val library: Library, + manager: Manager, + val sharedPreferenceUtil: SharedPreferenceUtil +) : PageDao { + + private val bookmarkListBehaviour: BehaviorSubject>? by lazy { + BehaviorSubject.createDefault(getBookmarksList()) + } + + private val bookmarksFolderPath: String by lazy { + if (Build.DEVICE.contains("generic")) { + // Workaround for emulators: Emulators have limited memory and + // restrictions on creating folders, so we will use the default + // path for saving the bookmark file. + sharedPreferenceUtil.context.filesDir.path + } else { + "${sharedPreferenceUtil.defaultStorage()}/Bookmarks/" + } + } + + private val bookmarkFile: File by lazy { + File("$bookmarksFolderPath/bookmark.xml") + } + + private val libraryFile: File by lazy { + File("$bookmarksFolderPath/library.xml") + } + + init { + // Check if bookmark folder exist if not then create the folder first. + if (!File(bookmarksFolderPath).isFileExist()) File(bookmarksFolderPath).mkdir() + // Check if library file exist if not then create the file to save the library with book information. + if (!libraryFile.isFileExist()) libraryFile.createNewFile() + // set up manager to read the library from this file + manager.readFile(libraryFile.canonicalPath) + // Check if bookmark file exist if not then create the file to save the bookmarks. + if (!bookmarkFile.isFileExist()) bookmarkFile.createNewFile() + // set up manager to read the bookmarks from this file + manager.readBookmarkFile(bookmarkFile.canonicalPath) + } + + fun bookmarks(): Flowable> = + flowableBookmarkList() + .map { it } + + override fun pages(): Flowable> = bookmarks() + + override fun deletePages(pagesToDelete: List) = + deleteBookmarks(pagesToDelete as List) + + fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List { + return zimFileReader?.let { reader -> + getBookmarksList() + .filter { it.zimId == reader.id } + .map(LibkiwixBookmarkItem::bookmarkUrl) + } ?: emptyList() + } + + fun bookmarkUrlsForCurrentBook(zimFileReader: ZimFileReader): Flowable> = + flowableBookmarkList() + .map { bookmarksList -> + bookmarksList.filter { it.zimId == zimFileReader.id } + .map(LibkiwixBookmarkItem::bookmarkUrl) + } + .subscribeOn(Schedulers.io()) + + fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem) { + if (!isBookMarkExist(libkiwixBookmarkItem)) { + addBookToLibraryIfNotExist(libkiwixBookmarkItem.libKiwixBook) + val bookmark = Bookmark().apply { + bookId = libkiwixBookmarkItem.zimId + title = libkiwixBookmarkItem.title + url = libkiwixBookmarkItem.url + bookTitle = libkiwixBookmarkItem.libKiwixBook?.title ?: libkiwixBookmarkItem.zimId + } + library.addBookmark(bookmark).also { + writeBookMarksAndSaveLibraryToFile() + updateFlowableBookmarkList() + } + } + } + + private fun addBookToLibraryIfNotExist(libKiwixBook: Book?) { + libKiwixBook?.let { book -> + if (!library.booksIds.any { it == book.id }) { + library.addBook(libKiwixBook).also { + if (BuildConfig.DEBUG) { + Log.d( + TAG, + "Added Book to Library:\n" + + "ZIM File Path: ${book.path}\n" + + "Book ID: ${book.id}\n" + + "Book Title: ${book.title}" + ) + } + } + } + } + } + + fun deleteBookmarks(bookmarks: List) { + bookmarks.map { deleteBookmark(it.zimId, it.bookmarkUrl) } + } + + fun deleteBookmark(bookId: String, bookmarkUrl: String) { + library.removeBookmark(bookId, bookmarkUrl).also { + writeBookMarksAndSaveLibraryToFile() + updateFlowableBookmarkList() + } + } + + /** + * Asynchronously writes the library and bookmarks data to their respective files in a background thread + * to prevent potential data loss and ensures that the library holds the updated ZIM file paths and favicons. + */ + private fun writeBookMarksAndSaveLibraryToFile() { + // Save the library, which contains ZIM file paths and favicons, to a file. + library.writeToFile(libraryFile.canonicalPath) + + // Save the bookmarks data to a separate file. + library.writeBookmarksToFile(bookmarkFile.canonicalPath) + } + + private fun getBookmarksList(): List { + // Retrieve the list of bookmarks from the library, or return an empty list if it's null. + val bookmarkList = library.getBookmarks(false)?.toList() ?: return emptyList() + + // Create a list to store LibkiwixBookmarkItem objects. + return bookmarkList.mapNotNull { bookmark -> + // Check if the library contains the book associated with the bookmark. + val book = if (library.booksIds.contains(bookmark.bookId)) { + library.getBookById(bookmark.bookId) + } else { + if (BuildConfig.DEBUG) { + Log.d( + TAG, + "Library does not contain the book for this bookmark:\n" + + "Book Title: ${bookmark.bookTitle}\n" + + "Bookmark URL: ${bookmark.url}" + ) + } + null + } + + // Check if the book has an illustration of the specified size and encode it to Base64. + val favicon = book?.getIllustration(ILLUSTRATION_SIZE)?.data?.let { + Base64.encodeToString(it, Base64.DEFAULT) + } + + // Return the LibkiwixBookmarkItem, filtering out null results. + return@mapNotNull LibkiwixBookmarkItem( + bookmark, + favicon, + book?.path + ) + } + } + + private fun isBookMarkExist(libkiwixBookmarkItem: LibkiwixBookmarkItem): Boolean = + getBookmarksList() + .any { + it.url == libkiwixBookmarkItem.bookmarkUrl && + it.zimFilePath == libkiwixBookmarkItem.zimFilePath + } + + 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 fun updateFlowableBookmarkList() { + bookmarkListBehaviour?.onNext(getBookmarksList()) + } + + companion object { + const val TAG = "LibkiwixBookmark" + } +} 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 e9d4b24e83..3ff5bfc576 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 @@ -20,7 +20,7 @@ package org.kiwix.kiwixmobile.core.data import io.reactivex.Completable import io.reactivex.Flowable import io.reactivex.Single -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem @@ -40,12 +40,12 @@ interface DataSource { fun saveHistory(history: HistoryItem): Completable fun deleteHistory(historyList: List): Completable fun clearHistory(): Completable - fun getBookmarks(): Flowable> + fun getBookmarks(): Flowable> fun getCurrentZimBookmarksUrl(): Single> - fun saveBookmark(bookmark: BookmarkItem): Completable - fun deleteBookmarks(bookmarks: List): Completable - fun deleteBookmark(bookmarkUrl: String): Completable? + fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem): Completable + fun deleteBookmarks(bookmarks: List): Completable + fun deleteBookmark(bookId: String, bookmarkUrl: String): Completable? fun booksOnDiskAsListItems(): Flowable> fun saveNote(noteListItem: NoteListItem): 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 2aaf489cb1..179d6ed6ee 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 @@ -23,15 +23,15 @@ import io.reactivex.Flowable import io.reactivex.Scheduler import io.reactivex.Single import org.kiwix.kiwixmobile.core.dao.HistoryDao +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.NewBookDao -import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewNoteDao import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao import org.kiwix.kiwixmobile.core.di.qualifiers.IO import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread import org.kiwix.kiwixmobile.core.extensions.HeaderizableList -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem @@ -53,7 +53,7 @@ class Repository @Inject internal constructor( @param:IO private val io: Scheduler, @param:MainThread private val mainThread: Scheduler, private val bookDao: NewBookDao, - private val bookmarksDao: NewBookmarksDao, + private val libkiwixBookmarks: LibkiwixBookmarks, private val historyDao: HistoryDao, private val notesDao: NewNoteDao, private val languageDao: NewLanguagesDao, @@ -104,23 +104,24 @@ class Repository @Inject internal constructor( recentSearchDao.deleteSearchHistory() } - override fun getBookmarks() = bookmarksDao.bookmarks() as Flowable> + override fun getBookmarks() = + libkiwixBookmarks.bookmarks() as Flowable> override fun getCurrentZimBookmarksUrl() = - Single.just(bookmarksDao.getCurrentZimBookmarksUrl(zimReaderContainer.zimFileReader)) + Single.just(libkiwixBookmarks.getCurrentZimBookmarksUrl(zimReaderContainer.zimFileReader)) .subscribeOn(io) .observeOn(mainThread) - override fun saveBookmark(bookmark: BookmarkItem) = - Completable.fromAction { bookmarksDao.saveBookmark(bookmark) } + override fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem) = + Completable.fromAction { libkiwixBookmarks.saveBookmark(libkiwixBookmarkItem) } .subscribeOn(io) - override fun deleteBookmarks(bookmarks: List) = - Completable.fromAction { bookmarksDao.deleteBookmarks(bookmarks) } + override fun deleteBookmarks(bookmarks: List) = + Completable.fromAction { libkiwixBookmarks.deleteBookmarks(bookmarks) } .subscribeOn(io) - override fun deleteBookmark(bookmarkUrl: String): Completable? = - Completable.fromAction { bookmarksDao.deleteBookmark(bookmarkUrl) } + override fun deleteBookmark(bookId: String, bookmarkUrl: String): Completable? = + Completable.fromAction { libkiwixBookmarks.deleteBookmark(bookId, bookmarkUrl) } .subscribeOn(io) override fun saveNote(noteListItem: NoteListItem): Completable = diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/ObjectBoxToLibkiwixMigrator.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/ObjectBoxToLibkiwixMigrator.kt new file mode 100644 index 0000000000..036891feb8 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/data/remote/ObjectBoxToLibkiwixMigrator.kt @@ -0,0 +1,69 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.data.remote + +import io.objectbox.Box +import io.objectbox.BoxStore +import io.objectbox.kotlin.boxFor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.kiwix.kiwixmobile.core.CoreApp +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks +import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil +import org.kiwix.libkiwix.Book +import org.kiwix.libzim.Archive +import javax.inject.Inject + +class ObjectBoxToLibkiwixMigrator { + @Inject lateinit var boxStore: BoxStore + @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil + @Inject lateinit var libkiwixBookmarks: LibkiwixBookmarks + + fun migrateBookmarksToLibkiwix() { + CoreApp.coreComponent.inject(this) + migrateBookMarks(boxStore.boxFor()) + // TODO we will migrate here for other entities + } + + fun migrateBookMarks(box: Box) { + val bookMarksList = box.all + bookMarksList.forEachIndexed { _, bookmarkEntity -> + CoroutineScope(Dispatchers.IO).launch { + // for saving book to library, otherwise it does not save the favicon and zimFilePath in library. + val libkiwixBook = Book().apply { + update(Archive(bookmarkEntity.zimFilePath)) + } + libkiwixBookmarks.saveBookmark(LibkiwixBookmarkItem(bookmarkEntity, libkiwixBook)) + // TODO should we remove data from objectBox? + // removing the single entity from the object box after migration. + // box.query { + // equal( + // BookmarkEntity_.bookmarkUrl, + // bookmarkEntity.bookmarkUrl, + // QueryBuilder.StringOrder.CASE_INSENSITIVE + // ) + // }.remove() + } + } + sharedPreferenceUtil.putPrefBookMarkMigrated(true) + } +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt index c207a7d8d1..8b550a9caf 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/components/CoreComponent.kt @@ -29,6 +29,7 @@ import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao import org.kiwix.kiwixmobile.core.dao.HistoryDao +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao @@ -37,6 +38,7 @@ import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao import org.kiwix.kiwixmobile.core.data.DataModule import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.remote.KiwixService +import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator import org.kiwix.kiwixmobile.core.di.modules.ApplicationModule import org.kiwix.kiwixmobile.core.di.modules.CoreViewModelModule import org.kiwix.kiwixmobile.core.di.modules.JNIModule @@ -93,6 +95,8 @@ interface CoreComponent { fun newBookmarksDao(): NewBookmarksDao fun connectivityManager(): ConnectivityManager fun wifiManager(): WifiManager + fun objectBoxToLibkiwixMigrator(): ObjectBoxToLibkiwixMigrator + fun libkiwixBookmarks(): LibkiwixBookmarks fun context(): Context fun downloader(): Downloader fun notificationManager(): NotificationManager @@ -104,6 +108,7 @@ interface CoreComponent { fun inject(errorActivity: ErrorActivity) fun inject(searchFragment: SearchFragment) + fun inject(objectBoxToLibkiwixMigrator: ObjectBoxToLibkiwixMigrator) fun inject(settingsFragment: CoreSettingsFragment) fun coreServiceComponent(): CoreServiceComponent.Builder diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt index ca8e3cba7d..af9980272b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/ApplicationModule.kt @@ -30,6 +30,7 @@ import io.reactivex.Scheduler import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import org.kiwix.kiwixmobile.core.NightModeConfig +import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator import org.kiwix.kiwixmobile.core.di.qualifiers.Computation import org.kiwix.kiwixmobile.core.di.qualifiers.IO import org.kiwix.kiwixmobile.core.di.qualifiers.MainThread @@ -66,6 +67,10 @@ class ApplicationModule { @Singleton internal fun provideBookUtils(): BookUtils = BookUtils() + @Provides + @Singleton + fun provideObjectBoxToLibkiwixMigrator() = ObjectBoxToLibkiwixMigrator() + @IO @Provides fun provideIoThread(): Scheduler = Schedulers.io() diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt index ba1001d12f..f4b1a7ebeb 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/di/modules/JNIModule.kt @@ -20,11 +20,31 @@ package org.kiwix.kiwixmobile.core.di.modules import android.content.Context import dagger.Module import dagger.Provides +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.libkiwix.JNIKiwix +import org.kiwix.libkiwix.Library +import org.kiwix.libkiwix.Manager import javax.inject.Singleton @Module class JNIModule { @Provides @Singleton fun providesJNIKiwix(context: Context): JNIKiwix = JNIKiwix(context) + + @Provides + @Singleton + fun provideLibrary(): Library = Library() + + @Provides + @Singleton + fun providesManager(library: Library): Manager = Manager(library) + + @Provides + @Singleton + fun providesLibkiwixBookmarks( + library: Library, + manager: Manager, + sharedPreferenceUtil: SharedPreferenceUtil + ): LibkiwixBookmarks = LibkiwixBookmarks(library, manager, sharedPreferenceUtil) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt index 5d61f77ef1..281ea8343c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreMainActivity.kt @@ -42,6 +42,7 @@ import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions +import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToLibkiwixMigrator import org.kiwix.kiwixmobile.core.di.components.CoreActivityComponent import org.kiwix.kiwixmobile.core.error.ErrorActivity import org.kiwix.kiwixmobile.core.extensions.browserIntent @@ -83,6 +84,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { abstract val topLevelDestinations: Set abstract val navHostContainer: FragmentContainerView abstract val mainActivity: AppCompatActivity + @Inject lateinit var objectBoxToLibkiwixMigrator: ObjectBoxToLibkiwixMigrator override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.KiwixTheme) @@ -101,7 +103,11 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider { exitProcess(KIWIX_INTERNAL_ERROR) } } + setMainActivityToCoreApp() + if (!sharedPreferenceUtil.prefIsBookmarksMigrated) { + objectBoxToLibkiwixMigrator.migrateBookmarksToLibkiwix() + } } @Suppress("DEPRECATION") 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 18e7236be6..62aa80904c 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 @@ -105,8 +105,8 @@ import org.kiwix.kiwixmobile.core.R2 import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.base.BaseFragment import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.NewBookDao -import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao import org.kiwix.kiwixmobile.core.downloader.fetch.DOWNLOAD_NOTIFICATION_TITLE import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.hasNotificationPermission import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission @@ -121,7 +121,7 @@ import org.kiwix.kiwixmobile.core.main.MainMenu.MenuClickListener import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection import org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener import org.kiwix.kiwixmobile.core.navigateToAppSettings -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.history.NavigationHistoryClickListener import org.kiwix.kiwixmobile.core.page.history.NavigationHistoryDialog import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem @@ -154,6 +154,7 @@ import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog import org.kiwix.kiwixmobile.core.utils.files.FileUtils.deleteCachedFiles import org.kiwix.kiwixmobile.core.utils.files.FileUtils.readFile +import org.kiwix.libkiwix.Book import java.io.File import java.io.IOException import java.text.SimpleDateFormat @@ -237,7 +238,7 @@ abstract class CoreReaderFragment : @JvmField @Inject - var newBookmarksDao: NewBookmarksDao? = null + var libkiwixBookmarks: LibkiwixBookmarks? = null @JvmField @Inject @@ -1461,7 +1462,7 @@ abstract class CoreReaderFragment : protected fun setUpBookmarks(zimFileReader: ZimFileReader) { safeDispose() bookmarkingDisposable = Flowable.combineLatest( - newBookmarksDao?.bookmarkUrlsForCurrentBook(zimFileReader), + libkiwixBookmarks?.bookmarkUrlsForCurrentBook(zimFileReader), webUrlsProcessor, List::contains ) @@ -1575,14 +1576,17 @@ abstract class CoreReaderFragment : @OnClick(R2.id.bottom_toolbar_bookmark) fun toggleBookmark() { getCurrentWebView()?.url?.let { articleUrl -> - if (isBookmarked) { - repositoryActions?.deleteBookmark(articleUrl) - snackBarRoot?.snack(R.string.bookmark_removed) - } else { - zimReaderContainer?.zimFileReader?.let { zimFileReader -> + zimReaderContainer?.zimFileReader?.let { zimFileReader -> + val libKiwixBook = Book().apply { + update(zimFileReader.jniKiwixReader) + } + if (isBookmarked) { + repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl) + snackBarRoot?.snack(R.string.bookmark_removed) + } else { getCurrentWebView()?.title?.let { repositoryActions?.saveBookmark( - BookmarkItem(it, articleUrl, zimFileReader) + LibkiwixBookmarkItem(it, articleUrl, zimFileReader, libKiwixBook) ) snackBarRoot?.snack( stringId = R.string.bookmark_added, @@ -1597,9 +1601,9 @@ abstract class CoreReaderFragment : ) ) } - } ?: kotlin.run { - requireActivity().toast(R.string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT) } + } ?: kotlin.run { + requireActivity().toast(R.string.unable_to_add_to_bookmarks, Toast.LENGTH_SHORT) } } } 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 6acee4c889..9490de80a4 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 @@ -21,7 +21,7 @@ import android.util.Log import io.reactivex.disposables.Disposable import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.di.ActivityScope -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk @@ -42,13 +42,13 @@ class MainRepositoryActions @Inject constructor(private val dataSource: DataSour .subscribe({}, { e -> Log.e(TAG, "Unable to save history", e) }) } - fun saveBookmark(bookmark: BookmarkItem) { + fun saveBookmark(bookmark: LibkiwixBookmarkItem) { saveBookmarkDisposable = dataSource.saveBookmark(bookmark) .subscribe({}, { e -> Log.e(TAG, "Unable to save bookmark", e) }) } - fun deleteBookmark(bookmarkUrl: String) { - dataSource.deleteBookmark(bookmarkUrl) + 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") } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/LibkiwixBookmarkItem.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/LibkiwixBookmarkItem.kt new file mode 100644 index 0000000000..e04b042280 --- /dev/null +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/adapter/LibkiwixBookmarkItem.kt @@ -0,0 +1,81 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package org.kiwix.kiwixmobile.core.page.bookmark.adapter + +import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity +import org.kiwix.kiwixmobile.core.page.adapter.Page +import org.kiwix.kiwixmobile.core.reader.ZimFileReader +import org.kiwix.libkiwix.Book +import org.kiwix.libkiwix.Bookmark + +data class LibkiwixBookmarkItem( + val databaseId: Long = 0L, + override val zimId: String, + val zimName: String, + override val zimFilePath: String?, + val bookmarkUrl: String, + override val title: String, + override val favicon: String?, + override var isSelected: Boolean = false, + override val url: String = bookmarkUrl, + override val id: Long = databaseId, + val libKiwixBook: Book?, +) : Page { + constructor( + libkiwixBookmark: Bookmark, + favicon: String?, + zimFilePath: String? + ) : this( + zimId = libkiwixBookmark.bookId, + zimName = libkiwixBookmark.bookTitle, + zimFilePath = zimFilePath, + bookmarkUrl = libkiwixBookmark.url, + title = libkiwixBookmark.title, + favicon = favicon, + libKiwixBook = null + ) + + constructor( + title: String, + articleUrl: String, + zimFileReader: ZimFileReader, + libKiwixBook: Book + ) : this( + zimFilePath = zimFileReader.zimFile?.canonicalPath, + zimId = libKiwixBook.id, + zimName = libKiwixBook.name, + bookmarkUrl = articleUrl, + title = title, + favicon = zimFileReader.favicon, + libKiwixBook = libKiwixBook + ) + + constructor( + bookmarkEntity: BookmarkEntity, + libkiwixBook: Book + ) : this( + zimId = bookmarkEntity.zimId, + zimFilePath = bookmarkEntity.zimFilePath, + zimName = bookmarkEntity.zimName, + bookmarkUrl = bookmarkEntity.bookmarkUrl, + title = bookmarkEntity.bookmarkTitle, + favicon = bookmarkEntity.favicon, + libKiwixBook = libkiwixBook + ) +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkState.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkState.kt index 810e6f9906..aea012598e 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkState.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkState.kt @@ -19,17 +19,17 @@ package org.kiwix.kiwixmobile.core.page.bookmark.viewmodel import org.kiwix.kiwixmobile.core.page.adapter.PageRelated -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.viewmodel.PageState data class BookmarkState( - override val pageItems: List, + override val pageItems: List, override val showAll: Boolean, override val currentZimId: String?, override val searchTerm: String = "" -) : PageState() { +) : PageState() { override val visiblePageItems: List = filteredPageItems - override fun copyWithNewItems(newItems: List): PageState = - copy(pageItems = newItems) + override fun copyWithNewItems(newItems: List): + PageState = copy(pageItems = newItems) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModel.kt index ebdee2849b..a955b7b4cb 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/BookmarkViewModel.kt @@ -18,8 +18,8 @@ package org.kiwix.kiwixmobile.core.page.bookmark.viewmodel -import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.effects.ShowDeleteBookmarksDialog import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.effects.UpdateAllBookmarksPreference import org.kiwix.kiwixmobile.core.page.viewmodel.Action @@ -29,10 +29,14 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import javax.inject.Inject class BookmarkViewModel @Inject constructor( - bookmarksDao: NewBookmarksDao, + libkiwixBookmarks: LibkiwixBookmarks, zimReaderContainer: ZimReaderContainer, sharedPrefs: SharedPreferenceUtil -) : PageViewModel(bookmarksDao, sharedPrefs, zimReaderContainer) { +) : PageViewModel( + libkiwixBookmarks, + sharedPrefs, + zimReaderContainer +) { override fun initialState(): BookmarkState = BookmarkState(emptyList(), sharedPreferenceUtil.showBookmarksAllBooks, zimReaderContainer.id) @@ -47,7 +51,7 @@ class BookmarkViewModel @Inject constructor( state: BookmarkState, action: Action.UpdatePages ): BookmarkState = - state.copy(pageItems = action.pages.filterIsInstance()) + state.copy(pageItems = action.pages.filterIsInstance()) override fun offerUpdateToShowAllToggle( action: Action.UserClickedShowAllToggle, @@ -63,6 +67,9 @@ class BookmarkViewModel @Inject constructor( override fun createDeletePageDialogEffect(state: BookmarkState) = ShowDeleteBookmarksDialog(effects, state, pageDao) - override fun copyWithNewItems(state: BookmarkState, newItems: List): BookmarkState = + override fun copyWithNewItems( + state: BookmarkState, + newItems: List + ): BookmarkState = state.copy(pageItems = newItems) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/effects/ShowDeleteBookmarksDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/effects/ShowDeleteBookmarksDialog.kt index 68591834d4..22ba4ad65c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/effects/ShowDeleteBookmarksDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/bookmark/viewmodel/effects/ShowDeleteBookmarksDialog.kt @@ -23,7 +23,7 @@ import io.reactivex.processors.PublishProcessor import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.dao.PageDao import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.cachedComponent -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.viewmodel.PageState import org.kiwix.kiwixmobile.core.page.viewmodel.effects.DeletePageItems import org.kiwix.kiwixmobile.core.utils.dialog.DialogShower @@ -33,7 +33,7 @@ import javax.inject.Inject data class ShowDeleteBookmarksDialog( private val effects: PublishProcessor>, - private val state: PageState, + private val state: PageState, private val pageDao: PageDao ) : SideEffect { @Inject lateinit var dialogShower: DialogShower diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageState.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageState.kt index 01841b9ca2..05decb8746 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageState.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageState.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.page.viewmodel import org.kiwix.kiwixmobile.core.page.adapter.Page import org.kiwix.kiwixmobile.core.page.adapter.PageRelated +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem abstract class PageState { abstract val pageItems: List @@ -36,7 +37,12 @@ abstract class PageState { fun getItemsAfterToggleSelectionOfItem(page: Page): List { return pageItems.map { - if (it.id == page.id) it.apply { + // check if the current item is `LibkiwixBookmarkItem` because we have not saving + // the bookmarks in database so it does not have any unique value so to get the + // selected items we check for url since url is unique for every bookmark. + val currentItemIdentifier = if (it is LibkiwixBookmarkItem) it.url else it.id + val pageIdentifier = if (it is LibkiwixBookmarkItem) page.url else page.id + if (currentItemIdentifier == pageIdentifier) it.apply { isSelected = !isSelected } else it } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt index 5a85bf35cf..6c95f0b7be 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimFileReader.kt @@ -54,7 +54,7 @@ private const val TAG = "ZimFileReader" class ZimFileReader constructor( val zimFile: File?, val assetFileDescriptor: AssetFileDescriptor? = null, - private val jniKiwixReader: Archive, + val jniKiwixReader: Archive, private val nightModeConfig: NightModeConfig, private val searcher: SuggestionSearcher = SuggestionSearcher(jniKiwixReader) ) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt index d2025385ca..691cd25439 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt @@ -90,6 +90,9 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { val prefDeviceDefaultLanguage: String get() = sharedPreferences.getString(PREF_DEVICE_DEFAULT_LANG, "") ?: "" + val prefIsBookmarksMigrated: Boolean + get() = sharedPreferences.getBoolean(PREF_BOOKMARKS_MIGRATED, false) + val prefStorage: String get() { val storage = sharedPreferences.getString(PREF_STORAGE, null) @@ -108,13 +111,15 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { val storagePosition: Int get() = sharedPreferences.getInt(STORAGE_POSITION, 0) - private fun defaultStorage(): String = + fun defaultStorage(): String = getExternalFilesDirs(context, null)[0]?.path ?: context.filesDir.path // a workaround for emulators fun getPrefStorageTitle(defaultTitle: String): String = sharedPreferences.getString(PREF_STORAGE_TITLE, defaultTitle) ?: defaultTitle + fun putPrefBookMarkMigrated(isMigrated: Boolean) = + sharedPreferences.edit { putBoolean(PREF_BOOKMARKS_MIGRATED, isMigrated) } fun putPrefLanguage(language: String) = sharedPreferences.edit { putString(PREF_LANG, language) } @@ -251,5 +256,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { private const val DEFAULT_ZOOM = 100 const val PREF_MANAGE_EXTERNAL_FILES = "pref_manage_external_files" const val IS_PLAY_STORE_BUILD = "is_play_store_build" + const val PREF_BOOKMARKS_MIGRATED = "pref_bookmarks_migrated" } } diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/page/PageTestHelpers.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/page/PageTestHelpers.kt index 26784f24dc..4c0e3c0875 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/page/PageTestHelpers.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/page/PageTestHelpers.kt @@ -19,7 +19,7 @@ package org.kiwix.kiwixmobile.core.page import org.kiwix.kiwixmobile.core.page.adapter.Page -import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem +import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.BookmarkState import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem import org.kiwix.kiwixmobile.core.page.history.viewmodel.HistoryState @@ -79,21 +79,22 @@ fun bookmark( zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" -): BookmarkItem { - return BookmarkItem( - id, - zimId, - zimName, - zimFilePath, - bookmarkUrl, - bookmarkTitle, - favicon, - isSelected +): LibkiwixBookmarkItem { + return LibkiwixBookmarkItem( + id = id, + zimId = zimId, + zimName = zimName, + zimFilePath = zimFilePath, + bookmarkUrl = bookmarkUrl, + title = bookmarkTitle, + isSelected = isSelected, + favicon = favicon, + libKiwixBook = null ) } fun bookmarkState( - bookmarks: List = emptyList(), + bookmarks: List = emptyList(), showAll: Boolean = true, zimId: String = "id", searchTerm: String = "" 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 3e555c0bff..0c320ddf03 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 @@ -28,7 +28,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao +import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.page.adapter.Page import org.kiwix.kiwixmobile.core.page.bookmark import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.effects.ShowDeleteBookmarksDialog @@ -44,7 +44,7 @@ import org.kiwix.sharedFunctions.setScheduler @ExtendWith(InstantExecutorExtension::class) internal class BookmarkViewModelTest { - private val bookmarksDao: NewBookmarksDao = mockk() + private val libkiwixBookMarks: LibkiwixBookmarks = mockk() private val zimReaderContainer: ZimReaderContainer = mockk() private val sharedPreferenceUtil: SharedPreferenceUtil = mockk() @@ -64,9 +64,9 @@ internal class BookmarkViewModelTest { every { zimReaderContainer.id } returns "id" every { zimReaderContainer.name } returns "zimName" every { sharedPreferenceUtil.showBookmarksAllBooks } returns true - every { bookmarksDao.bookmarks() } returns itemsFromDb.distinctUntilChanged() - every { bookmarksDao.pages() } returns bookmarksDao.bookmarks() - viewModel = BookmarkViewModel(bookmarksDao, zimReaderContainer, sharedPreferenceUtil) + every { libkiwixBookMarks.bookmarks() } returns itemsFromDb.distinctUntilChanged() + every { libkiwixBookMarks.pages() } returns libkiwixBookMarks.bookmarks() + viewModel = BookmarkViewModel(libkiwixBookMarks, zimReaderContainer, sharedPreferenceUtil) } @Test @@ -132,7 +132,7 @@ internal class BookmarkViewModelTest { assertThat( viewModel.createDeletePageDialogEffect(bookmarkState()) ).isEqualTo( - ShowDeleteBookmarksDialog(viewModel.effects, bookmarkState(), bookmarksDao) + ShowDeleteBookmarksDialog(viewModel.effects, bookmarkState(), libkiwixBookMarks) ) }