Merge pull request #3724 from kiwix/Fix#822

Added the 'Export bookmarks' feature.
This commit is contained in:
Kelson 2024-06-07 06:40:40 +02:00 committed by GitHub
commit 1b255d56dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 542 additions and 10 deletions

View File

@ -0,0 +1,189 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.page.bookmarks
import androidx.core.content.edit
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import io.objectbox.BoxStore
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import org.kiwix.kiwixmobile.BaseActivityTest
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager
import java.io.File
class ImportBookmarkTest : BaseActivityTest() {
@Rule
@JvmField
var retryRule = RetryRule()
private val boxStore: BoxStore? = DatabaseModule.boxStore
private val library = Library()
private val manager = Manager(library)
private val newBookDao = NewBookDao(boxStore!!.boxFor(BookOnDiskEntity::class.java))
lateinit var libkiwixBookmarks: LibkiwixBookmarks
private val bookmarkXmlData = """
<bookmarks>
<bookmark>
<book>
<id>1f88ab6f-c265-b3ff-8f49-b7f442950380</id>
<title>Alpine Linux Wiki</title>
<name>alpinelinux_en_all</name>
<flavour>maxi</flavour>
<language>eng</language>
<date>2023-01-18</date>
</book>
<title>Main Page</title>
<url>https://kiwix.app/A/Main_Page</url>
</bookmark>
<bookmark>
<book>
<id>1f88ab6f-c265-b3ff-8f49-b7f442950380</id>
<title>Alpine Linux Wiki</title>
<name>alpinelinux_en_all</name>
<flavour>maxi</flavour>
<language>eng</language>
<date>2023-01-18</date>
</book>
<title>Installation</title>
<url>https://kiwix.app/A/Installation</url>
</bookmark>
<bookmark>
<book>
<id>04bf4329-9bfb-3681-03e2-cfae7b047f24</id>
<title>Ray Charles</title>
<name>wikipedia_en_ray_charles</name>
<flavour>maxi</flavour>
<language>eng</language>
<date>2024-03-17</date>
</book>
<title>Wikipedia</title>
<url>https://kiwix.app/A/index</url>
</bookmark>
</bookmarks>
""".trimIndent()
@Before
override fun waitForIdle() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
if (TestUtils.isSystemUINotRespondingDialogVisible(this)) {
TestUtils.closeSystemDialogs(context, this)
}
waitForIdle()
}
PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false)
putBoolean(SharedPreferenceUtil.PREF_WIFI_ONLY, false)
putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true)
putBoolean(SharedPreferenceUtil.PREF_PLAY_STORE_RESTRICTION, false)
putString(SharedPreferenceUtil.PREF_LANG, "en")
}
activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply {
moveToState(Lifecycle.State.RESUMED)
onActivity {
LanguageUtils.handleLocaleChange(
it,
"en",
SharedPreferenceUtil(context)
)
}
}
libkiwixBookmarks =
LibkiwixBookmarks(library, manager, SharedPreferenceUtil(context), newBookDao)
}
init {
AccessibilityChecks.enable().setRunChecksFromRootView(true)
}
@Test
fun importBookmark() = runBlocking {
// clear the bookmarks to perform tes case properly.
clearBookmarks()
// test with empty data file
var tempBookmarkFile = getTemporaryBookmarkFile(true)
importBookmarks(tempBookmarkFile)
var actualDataAfterImporting = libkiwixBookmarks.bookmarks().blockingFirst()
assertEquals(0, actualDataAfterImporting.size)
// import the bookmark
tempBookmarkFile = getTemporaryBookmarkFile()
importBookmarks(tempBookmarkFile)
actualDataAfterImporting = libkiwixBookmarks.bookmarks().blockingFirst()
assertEquals(3, actualDataAfterImporting.size)
assertEquals(actualDataAfterImporting[0].title, "Main Page")
assertEquals(actualDataAfterImporting[0].url, "https://kiwix.app/A/Main_Page")
assertEquals(actualDataAfterImporting[0].zimId, "1f88ab6f-c265-b3ff-8f49-b7f442950380")
// import duplicate bookmarks
importBookmarks(tempBookmarkFile)
actualDataAfterImporting = libkiwixBookmarks.bookmarks().blockingFirst()
assertEquals(3, actualDataAfterImporting.size)
// delete the temp file
if (tempBookmarkFile.exists()) tempBookmarkFile.delete()
}
private fun importBookmarks(tempBookmarkFile: File) {
activityScenario.onActivity {
runBlocking {
libkiwixBookmarks.importBookmarks(tempBookmarkFile)
}
}
}
private fun clearBookmarks() {
// delete bookmarks for testing other edge cases
libkiwixBookmarks.deleteBookmarks(
libkiwixBookmarks.bookmarks()
.blockingFirst() as List<LibkiwixBookmarkItem>
)
}
private fun getTemporaryBookmarkFile(isWithEmptyData: Boolean = false): File =
File(context.externalCacheDir, "bookmark.xml").apply {
createNewFile()
if (exists()) delete()
if (!isWithEmptyData) {
// Write the XML data to the temp file
writeText(bookmarkXmlData)
}
}
}

View File

@ -107,6 +107,12 @@ class KiwixSettingsFragmentTest {
clickClearHistoryPreference()
assertHistoryDialogDisplayed()
dismissDialog()
clickExportBookmarkPreference()
assertExportBookmarkDialogDisplayed()
dismissDialog()
clickOnImportBookmarkPreference()
assertImportBookmarkDialogDisplayed()
dismissDialog()
clickNightModePreference()
assertNightModeDialogDisplayed()
dismissDialog()

View File

@ -116,6 +116,22 @@ class SettingsRobot : BaseRobot() {
isVisible(TextId(R.string.clear_all_history_dialog_title))
}
fun clickExportBookmarkPreference() {
clickRecyclerViewItems(R.string.pref_export_bookmark_title)
}
fun assertExportBookmarkDialogDisplayed() {
isVisible(TextId(R.string.export_all_bookmarks_dialog_title))
}
fun clickOnImportBookmarkPreference() {
clickRecyclerViewItems(R.string.pref_import_bookmark_title)
}
fun assertImportBookmarkDialogDisplayed() {
isVisible(TextId(R.string.import_bookmarks_dialog_title))
}
fun clickNightModePreference() {
clickRecyclerViewItems(R.string.pref_night_mode)
}

View File

@ -23,6 +23,7 @@ import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.runBlocking
import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.utils.files.FileSearch
@ -34,7 +35,8 @@ import javax.inject.Inject
class StorageObserver @Inject constructor(
private val downloadDao: FetchDownloadDao,
private val fileSearch: FileSearch,
private val zimReaderFactory: ZimFileReader.Factory
private val zimReaderFactory: ZimFileReader.Factory,
private val libkiwixBookmarks: LibkiwixBookmarks
) {
fun getBooksOnFileSystem(
@ -56,6 +58,12 @@ class StorageObserver @Inject constructor(
private fun convertToBookOnDisk(file: File) = runBlocking {
zimReaderFactory.create(file)
?.let { zimFileReader -> BookOnDisk(file, zimFileReader).also { zimFileReader.dispose() } }
?.let { zimFileReader ->
BookOnDisk(file, zimFileReader).also {
// add the book to libkiwix library to validate the imported bookmarks
libkiwixBookmarks.addBookToLibrary(archive = zimFileReader.jniKiwixReader)
zimFileReader.dispose()
}
}
}
}

View File

@ -19,8 +19,8 @@
package org.kiwix.kiwixmobile.core.dao
import android.os.Build
import android.os.Environment
import android.util.Base64
import org.kiwix.kiwixmobile.core.utils.files.Log
import io.reactivex.BackpressureStrategy
import io.reactivex.BackpressureStrategy.LATEST
import io.reactivex.Flowable
@ -29,23 +29,28 @@ import io.reactivex.subjects.BehaviorSubject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.toast
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.kiwixmobile.core.utils.files.Log
import org.kiwix.libkiwix.Book
import org.kiwix.libkiwix.Bookmark
import org.kiwix.libkiwix.Library
import org.kiwix.libkiwix.Manager
import org.kiwix.libzim.Archive
import java.io.File
import javax.inject.Inject
class LibkiwixBookmarks @Inject constructor(
val library: Library,
manager: Manager,
val sharedPreferenceUtil: SharedPreferenceUtil
val sharedPreferenceUtil: SharedPreferenceUtil,
private val bookDao: NewBookDao
) : PageDao {
/**
@ -132,7 +137,13 @@ class LibkiwixBookmarks @Inject constructor(
bookId = libkiwixBookmarkItem.zimId
title = libkiwixBookmarkItem.title
url = libkiwixBookmarkItem.url
bookTitle = libkiwixBookmarkItem.libKiwixBook?.title ?: libkiwixBookmarkItem.zimId
bookTitle = when {
libkiwixBookmarkItem.libKiwixBook?.title != null ->
libkiwixBookmarkItem.libKiwixBook.title
libkiwixBookmarkItem.zimName.isNotBlank() -> libkiwixBookmarkItem.zimName
else -> libkiwixBookmarkItem.zimId
}
}
library.addBookmark(bookmark).also {
if (shouldWriteBookmarkToFile) {
@ -145,6 +156,26 @@ class LibkiwixBookmarks @Inject constructor(
}
}
fun addBookToLibrary(file: File? = null, archive: Archive? = null) {
try {
bookmarksChanged = true
val book = Book().apply {
archive?.let {
update(archive)
} ?: run {
update(Archive(file?.canonicalPath))
}
}
addBookToLibraryIfNotExist(book)
updateFlowableBookmarkList()
} catch (ignore: Exception) {
Log.e(
TAG,
"Error: Couldn't add the book to library.\nOriginal exception = $ignore"
)
}
}
private fun addBookToLibraryIfNotExist(libKiwixBook: Book?) {
libKiwixBook?.let { book ->
if (!isBookAlreadyExistInLibrary(book.id)) {
@ -209,7 +240,9 @@ class LibkiwixBookmarks @Inject constructor(
return bookmarkList
}
// Retrieve the list of bookmarks from the library, or return an empty list if it's null.
val bookmarkArray = library.getBookmarks(false)?.toList() ?: return bookmarkList
val bookmarkArray =
library.getBookmarks(false)?.toList()
?: return bookmarkList.distinctBy(LibkiwixBookmarkItem::bookmarkUrl)
// Create a list to store LibkiwixBookmarkItem objects.
bookmarkList = bookmarkArray.mapNotNull { bookmark ->
@ -241,7 +274,21 @@ class LibkiwixBookmarks @Inject constructor(
bookmarksChanged = false
}
}
return bookmarkList
// Delete duplicates bookmarks if any exist
deleteDuplicateBookmarks()
return bookmarkList.distinctBy(LibkiwixBookmarkItem::bookmarkUrl)
}
private fun deleteDuplicateBookmarks() {
bookmarkList.groupBy { it.bookmarkUrl to it.zimFilePath }
.filter { it.value.size > 1 }
.forEach { (_, value) ->
value.drop(1).forEach { bookmarkItem ->
deleteBookmark(bookmarkItem.zimId, bookmarkItem.bookmarkUrl)
}
}
}
private fun isBookMarkExist(libkiwixBookmarkItem: LibkiwixBookmarkItem): Boolean =
@ -273,6 +320,65 @@ class LibkiwixBookmarks @Inject constructor(
bookmarkListBehaviour?.onNext(getBookmarksList())
}
// Export the `bookmark.xml` file to the `Download/org.kiwix/` directory of internal storage.
fun exportBookmark() {
try {
val bookmarkDestinationFile = exportedFile("bookmark.xml")
bookmarkFile.inputStream().use { inputStream ->
bookmarkDestinationFile.outputStream().use(inputStream::copyTo)
}
sharedPreferenceUtil.context.toast(
sharedPreferenceUtil.context.getString(
R.string.export_bookmark_saved,
bookmarkDestinationFile.name
)
)
} catch (ignore: Exception) {
Log.e(TAG, "Error: bookmark couldn't export.\n Original exception = $ignore")
sharedPreferenceUtil.context.toast(R.string.export_bookmark_error)
}
}
private fun exportedFile(fileName: String): File {
val rootFolder = File(
"${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}" +
"/org.kiwix"
)
if (!rootFolder.isFileExist()) rootFolder.mkdir()
return sequence {
yield(File(rootFolder, fileName))
yieldAll(
generateSequence(1) { it + 1 }.map {
File(
rootFolder, fileName.replace(".", "_$it.")
)
}
)
}.first { !it.isFileExist() }
}
fun importBookmarks(bookmarkFile: File) {
// Create a temporary library manager to import the bookmarks.
val tempLibrary = Library()
Manager(tempLibrary).apply {
// Read the bookmark file.
readBookmarkFile(bookmarkFile.canonicalPath)
}
// Add the ZIM files to the library for validating the bookmarks.
bookDao.getBooks().forEach {
addBookToLibrary(file = it.file)
}
// Save the imported bookmarks to the current library.
tempLibrary.getBookmarks(false)?.toList()?.forEach {
saveBookmark(LibkiwixBookmarkItem(it, null, null))
}
sharedPreferenceUtil.context.toast(R.string.bookmark_imported_message)
if (bookmarkFile.exists()) {
bookmarkFile.delete()
}
}
companion object {
const val TAG = "LibkiwixBookmark"
}

View File

@ -21,6 +21,7 @@ import android.content.Context
import dagger.Module
import dagger.Provides
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.libkiwix.JNIKiwix
import org.kiwix.libkiwix.Library
@ -45,6 +46,7 @@ class JNIModule {
fun providesLibkiwixBookmarks(
library: Library,
manager: Manager,
sharedPreferenceUtil: SharedPreferenceUtil
): LibkiwixBookmarks = LibkiwixBookmarks(library, manager, sharedPreferenceUtil)
sharedPreferenceUtil: SharedPreferenceUtil,
bookDao: NewBookDao
): LibkiwixBookmarks = LibkiwixBookmarks(library, manager, sharedPreferenceUtil, bookDao)
}

View File

@ -20,14 +20,18 @@ package org.kiwix.kiwixmobile.core.settings
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.webkit.WebView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.preference.EditTextPreference
@ -43,8 +47,11 @@ import org.kiwix.kiwixmobile.core.NightModeConfig
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getVersionCode
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.main.AddNoteDialog
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.navigateToAppSettings
import org.kiwix.kiwixmobile.core.utils.EXTERNAL_SELECT_POSITION
import org.kiwix.kiwixmobile.core.utils.INTERNAL_SELECT_POSITION
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
@ -56,8 +63,10 @@ import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.OpenCredits
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.SelectFolder
import org.kiwix.kiwixmobile.core.utils.files.FileUtils.getPathFromUri
import java.io.File
import java.io.InputStream
import java.util.Locale
import javax.inject.Inject
import javax.xml.parsers.DocumentBuilderFactory
abstract class CorePrefsFragment :
PreferenceFragmentCompat(),
@ -83,6 +92,10 @@ abstract class CorePrefsFragment :
@JvmField
@Inject
protected var alertDialogShower: DialogShower? = null
@JvmField
@Inject
internal var libkiwixBookmarks: LibkiwixBookmarks? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
coreComponent
.activityComponentBuilder()
@ -131,6 +144,8 @@ abstract class CorePrefsFragment :
override fun onDestroyView() {
presenter?.dispose()
storagePermissionForNotesLauncher?.unregister()
storagePermissionForNotesLauncher = null
super.onDestroyView()
}
@ -289,9 +304,149 @@ abstract class CorePrefsFragment :
if (preference.key.equals(SharedPreferenceUtil.PREF_STORAGE, ignoreCase = true)) {
openFolderSelect()
}
if (preference.key.equals(PREF_EXPORT_BOOKMARK, ignoreCase = true) &&
requestExternalStorageWritePermissionForExportBookmark()
) {
showExportBookmarkDialog()
}
if (preference.key.equals(PREF_IMPORT_BOOKMARK, ignoreCase = true)) {
showImportBookmarkDialog()
}
return true
}
@Suppress("NestedBlockDepth")
private fun requestExternalStorageWritePermissionForExportBookmark(): Boolean {
var isPermissionGranted = false
if (sharedPreferenceUtil?.isPlayStoreBuildWithAndroid11OrAbove() == false &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
) {
if (requireActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED
) {
isPermissionGranted = true
} else {
storagePermissionForNotesLauncher?.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
} else {
isPermissionGranted = true
}
return isPermissionGranted
}
private var storagePermissionForNotesLauncher: ActivityResultLauncher<String>? =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// Successfully granted permission, so opening the export bookmark Dialog
showExportBookmarkDialog()
} else {
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
/* shouldShowRequestPermissionRationale() returns false when:
* 1) User has previously checked on "Don't ask me again", and/or
* 2) Permission has been disabled on device
*/
requireActivity().toast(
R.string.ext_storage_permission_rationale_export_bookmark,
Toast.LENGTH_LONG
)
} else {
requireActivity().toast(
R.string.ext_storage_write_permission_denied_export_bookmark,
Toast.LENGTH_LONG
)
alertDialogShower?.show(
KiwixDialog.ReadPermissionRequired,
requireActivity()::navigateToAppSettings
)
}
}
}
private fun showExportBookmarkDialog() {
alertDialogShower?.show(
KiwixDialog.YesNoDialog.ExportBookmarks,
{ libkiwixBookmarks?.exportBookmark() }
)
}
private fun showImportBookmarkDialog() {
alertDialogShower?.show(
KiwixDialog.ImportBookmarks,
::showFileChooser
)
}
private fun showFileChooser() {
val intent = Intent().apply {
action = Intent.ACTION_GET_CONTENT
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
}
try {
fileSelectLauncher.launch(Intent.createChooser(intent, "Select a bookmark file"))
} catch (ex: ActivityNotFoundException) {
activity.toast(
resources.getString(R.string.no_app_found_to_select_bookmark_file),
Toast.LENGTH_SHORT
)
}
}
private val fileSelectLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.data?.let { uri ->
val contentResolver = requireActivity().contentResolver
if (!isValidBookmarkFile(contentResolver.getType(uri))) {
activity.toast(
resources.getString(R.string.error_invalid_bookmark_file),
Toast.LENGTH_SHORT
)
return@registerForActivityResult
}
createTempFile(contentResolver.openInputStream(uri)).apply {
if (isValidXmlFile(this)) {
libkiwixBookmarks?.importBookmarks(this)
} else {
activity.toast(
resources.getString(R.string.error_invalid_bookmark_file),
Toast.LENGTH_SHORT
)
}
}
}
}
}
private fun isValidXmlFile(file: File): Boolean {
return try {
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file)
true
} catch (ignore: Exception) {
Log.e("IMPORT_BOOKMARKS", "Invalid XML file", ignore)
false
}
}
private fun createTempFile(inputStream: InputStream?): File {
// create a temp file for importing the saved bookmarks
val tempFile = File(requireActivity().externalCacheDir, "bookmark.xml")
if (tempFile.exists()) {
tempFile.delete()
}
tempFile.createNewFile()
inputStream?.let {
tempFile.outputStream().use(inputStream::copyTo)
}
return tempFile
}
private fun isValidBookmarkFile(mimeType: String?) =
mimeType == "application/xml" || mimeType == "text/xml"
private fun openFolderSelect() {
val dialogFragment = StorageSelectDialog()
dialogFragment.onSelectAction =
@ -383,5 +538,7 @@ abstract class CorePrefsFragment :
private const val ZOOM_OFFSET = 2
private const val ZOOM_SCALE = 25
private const val INTERNAL_TEXT_ZOOM = "text_zoom"
private const val PREF_EXPORT_BOOKMARK = "pref_export_bookmark"
private const val PREF_IMPORT_BOOKMARK = "pref_import_bookmark"
}
}

View File

@ -272,6 +272,11 @@ sealed class KiwixDialog(
object OpenInNewTab : YesNoDialog(
null, R.string.open_in_new_tab
)
object ExportBookmarks : YesNoDialog(
R.string.export_all_bookmarks_dialog_title,
message = R.string.export_all_bookmarks_dialog_message,
)
}
object StorageConfigure : KiwixDialog(
@ -282,6 +287,13 @@ sealed class KiwixDialog(
icon = R.drawable.ic_baseline_storage_24
)
object ImportBookmarks : KiwixDialog(
R.string.import_bookmarks_dialog_title,
message = null,
positiveMessage = R.string.yes,
negativeMessage = R.string.no
)
object DeleteSelectedHistory : KiwixDialog(
R.string.delete_selected_history,
null,

View File

@ -68,6 +68,7 @@
<string name="tts_lang_not_supported">The language of this page is not supported. The article may not be properly read.</string>
<string name="no_reader_application_installed">Could not find an installed application for this type of file</string>
<string name="no_email_application_installed">Please install an email service provider or email us at %1s</string>
<string name="no_app_found_to_select_bookmark_file">No app found to select a bookmark file</string>
<string name="no_section_info">No Content Headers Found</string>
<string name="request_storage">To access offline content we need access to your storage</string>
<string name="request_write_storage">To download zim files we need write access to your storage</string>
@ -77,6 +78,11 @@
<string name="pref_clear_all_history_summary">Clear recent searches and tabs history</string>
<string name="pref_notes">Notes</string>
<string name="pref_permission">Permissions</string>
<string name="pref_import_bookmark_title">Import Bookmarks</string>
<string name="pref_import_bookmark_summary">Import the saved bookmarks</string>
<string name="pref_export_bookmark_title">Export Bookmarks</string>
<string name="pref_export_bookmark_summary">Export all saved bookmarks</string>
<string name="export_bookmark_saved">Bookmarks are exported in %s file</string>
<string name="all_history_cleared">All History Cleared</string>
<string name="pref_clear_all_bookmarks_title">Clear bookmarks</string>
<string name="clear_all_history_dialog_title">Clear All History?</string>
@ -226,6 +232,9 @@
<string name="save">Save</string>
<string name="note">Note</string>
<string name="wiki_article_title">Wiki Article Title</string>
<string name="ext_storage_permission_rationale_export_bookmark">Storage access is required for exporting Bookmarks</string>
<string name="ext_storage_write_permission_denied_export_bookmark">Bookmarks cant export without access of storage</string>
<string name="export_bookmark_error">An error occurred when trying to export bookmark!</string>
<string name="ext_storage_permission_rationale_add_note">Storage access is required for Notes</string>
<string name="ext_storage_write_permission_denied_add_note">Notes can\t be used without access to storage</string>
<string name="note_save_unsuccessful">Note save unsuccessful</string>
@ -288,6 +297,11 @@
<string name="delete_selected_history">Delete Selected History?</string>
<string name="delete_bookmarks">Delete All Bookmarks?</string>
<string name="delete_selected_bookmarks">Delete Selected Bookmarks?</string>
<string name="import_bookmarks_dialog_title">Import Bookmarks?</string>
<string name="export_all_bookmarks_dialog_title">Export All Bookmarks?</string>
<string name="export_all_bookmarks_dialog_message">Exporting will generate bookmark.xml file in the Download/org.kiwix…/ folder.</string>
<string name="bookmark_imported_message">All the bookmarks has been imported.</string>
<string name="error_invalid_bookmark_file">Error: The selected file is not a valid bookmark file.</string>
<string name="on">On</string>
<string name="off">Off</string>
<string name="auto">Auto</string>

View File

@ -95,6 +95,25 @@
android:title="@string/pref_clear_all_notes_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_notes"
android:title="@string/bookmarks"
app:iconSpaceReserved="false">
<Preference
android:key="pref_import_bookmark"
android:summary="@string/pref_import_bookmark_summary"
android:title="@string/pref_import_bookmark_title"
app:iconSpaceReserved="false" />
<Preference
android:key="pref_export_bookmark"
android:summary="@string/pref_export_bookmark_summary"
android:title="@string/pref_export_bookmark_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_permissions"

View File

@ -29,6 +29,7 @@ import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory
@ -50,6 +51,7 @@ class StorageObserverTest {
private val file: File = mockk()
private val readerFactory: Factory = mockk()
private val zimFileReader: ZimFileReader = mockk()
private val libkiwixBookmarks: LibkiwixBookmarks = mockk()
private val scanningProgressListener: ScanningProgressListener = mockk()
private val files: PublishProcessor<List<File>> = PublishProcessor.create()
@ -71,8 +73,9 @@ class StorageObserverTest {
every { sharedPreferenceUtil.prefStorage } returns "a"
every { fileSearch.scan(scanningProgressListener) } returns files
every { downloadDao.downloads() } returns downloads
every { zimFileReader.jniKiwixReader } returns mockk()
every { runBlocking { readerFactory.create(file) } } returns zimFileReader
storageObserver = StorageObserver(downloadDao, fileSearch, readerFactory)
storageObserver = StorageObserver(downloadDao, fileSearch, readerFactory, libkiwixBookmarks)
}
@Test