mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-09 07:16:04 -04:00
Merge pull request #3724 from kiwix/Fix#822
Added the 'Export bookmarks' feature.
This commit is contained in:
commit
1b255d56dd
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -107,6 +107,12 @@ class KiwixSettingsFragmentTest {
|
||||
clickClearHistoryPreference()
|
||||
assertHistoryDialogDisplayed()
|
||||
dismissDialog()
|
||||
clickExportBookmarkPreference()
|
||||
assertExportBookmarkDialogDisplayed()
|
||||
dismissDialog()
|
||||
clickOnImportBookmarkPreference()
|
||||
assertImportBookmarkDialogDisplayed()
|
||||
dismissDialog()
|
||||
clickNightModePreference()
|
||||
assertNightModeDialogDisplayed()
|
||||
dismissDialog()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 can’t 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>
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user