Fixed: Move canReadFile() method to IO thread.

* Moved the file readability check to the IO thread to prevent ANR.
* Refactored the code to accommodate this change.
This commit is contained in:
MohitMaliFtechiz 2024-10-16 16:30:05 +05:30 committed by MohitMaliFtechiz
parent 5ceb3ceccb
commit 33bd3397e3
18 changed files with 170 additions and 100 deletions

View File

@ -31,7 +31,11 @@ import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.cachedComponent
import org.kiwix.kiwixmobile.core.R.anim
@ -219,23 +223,27 @@ class KiwixReaderFragment : CoreReaderFragment() {
) {
val settings = requireActivity().getSharedPreferences(SharedPreferenceUtil.PREF_KIWIX_MOBILE, 0)
val zimReaderSource = fromDatabaseValue(settings.getString(TAG_CURRENT_FILE, null))
if (zimReaderSource != null && zimReaderSource.canOpenInLibkiwix()) {
if (zimReaderContainer?.zimReaderSource == null) {
openZimFile(zimReaderSource)
Log.d(
TAG_KIWIX,
"Kiwix normal start, Opened last used zimFile: -> ${zimReaderSource.toDatabase()}"
)
} else {
zimReaderContainer?.zimFileReader?.let(::setUpBookmarks)
lifecycleScope.launch {
val canOpenInLibkiwix = withContext(Dispatchers.IO) {
zimReaderSource?.canOpenInLibkiwix()
}
} else {
getCurrentWebView()?.snack(string.zim_not_opened)
exitBook() // hide the options for zim file to avoid unexpected UI behavior
return // book not found so don't need to restore the tabs for this file
if (zimReaderSource != null && canOpenInLibkiwix == true) {
if (zimReaderContainer?.zimReaderSource == null) {
openZimFile(zimReaderSource)
Log.d(
TAG_KIWIX,
"Kiwix normal start, Opened last used zimFile: -> ${zimReaderSource.toDatabase()}"
)
} else {
zimReaderContainer?.zimFileReader?.let(::setUpBookmarks)
}
} else {
getCurrentWebView()?.snack(string.zim_not_opened)
exitBook() // hide the options for zim file to avoid unexpected UI behavior
return@launch // book not found so don't need to restore the tabs for this file
}
restoreTabs(zimArticles, zimPositions, currentTab)
}
restoreTabs(zimArticles, zimPositions, currentTab)
}
@Throws(IllegalArgumentException::class)

View File

@ -19,11 +19,16 @@
package org.kiwix.kiwixmobile.zimManager.fileselectView.effects
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.navigate
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.nav.destination.library.LocalLibraryFragmentDirections.actionNavigationLibraryToNavigationReader
data class OpenFileWithNavigation(private val bookOnDisk: BooksOnDiskListItem.BookOnDisk) :
@ -31,14 +36,19 @@ data class OpenFileWithNavigation(private val bookOnDisk: BooksOnDiskListItem.Bo
override fun invokeWith(activity: AppCompatActivity) {
val zimReaderSource = bookOnDisk.zimReaderSource
if (!zimReaderSource.canOpenInLibkiwix()) {
activity.toast(R.string.error_file_not_found)
} else {
activity.navigate(
actionNavigationLibraryToNavigationReader().apply {
zimFileUri = zimReaderSource.toDatabase()
}
)
(activity as KiwixMainActivity).lifecycleScope.launch {
val canOpenInLibkiwix = withContext(Dispatchers.IO) {
zimReaderSource.canOpenInLibkiwix()
}
if (!canOpenInLibkiwix) {
activity.toast(R.string.error_file_not_found)
} else {
activity.navigate(
actionNavigationLibraryToNavigationReader().apply {
zimFileUri = zimReaderSource.toDatabase()
}
)
}
}
}
}

View File

@ -22,6 +22,9 @@ object Libs {
"org.jetbrains.kotlinx:kotlinx-coroutines-android:" +
Versions.org_jetbrains_kotlinx_kotlinx_coroutines
const val kotlinx_coroutines_rx3: String =
"org.jetbrains.kotlinx:kotlinx-coroutines-rx3:" + Versions.kotlinx_coroutines_rx3
/**
* https://github.com/Kotlin/kotlinx.coroutines
*/

View File

@ -16,6 +16,8 @@ object Versions {
const val org_jetbrains_kotlinx_kotlinx_coroutines: String = "1.8.1"
const val kotlinx_coroutines_rx3: String = "1.3.9"
const val androidx_test_espresso: String = "3.5.1"
const val tracing: String = "1.1.0"

View File

@ -61,5 +61,6 @@ dependencies {
implementation(Libs.webkit)
testImplementation(Libs.kotlinx_coroutines_test)
implementation(Libs.kotlinx_coroutines_android)
implementation(Libs.kotlinx_coroutines_rx3)
implementation(Libs.zxing)
}

View File

@ -12,7 +12,7 @@
<ID>LongParameterList:MainMenu.kt$MainMenu$( private val activity: Activity, zimFileReader: ZimFileReader?, menu: Menu, webViews: MutableList&lt;KiwixWebView>, urlIsValid: Boolean, disableReadAloud: Boolean = false, disableTabs: Boolean = false, private val menuClickListener: MenuClickListener )</ID>
<ID>LongParameterList:MainMenu.kt$MainMenu.Factory$( menu: Menu, webViews: MutableList&lt;KiwixWebView>, urlIsValid: Boolean, menuClickListener: MenuClickListener, disableReadAloud: Boolean, disableTabs: Boolean )</ID>
<ID>LongParameterList:PageTestHelpers.kt$( bookmarkTitle: String = "bookmarkTitle", isSelected: Boolean = false, id: Long = 2, zimId: String = "zimId", zimName: String = "zimName", zimFilePath: String = "zimFilePath", bookmarkUrl: String = "bookmarkUrl", favicon: String = "favicon" )</ID>
<ID>LongParameterList:Repository.kt$Repository$( @param:IO private val io: Scheduler, @param:MainThread private val mainThread: Scheduler, private val bookDao: NewBookDao, private val bookmarksDao: NewBookmarksDao, private val historyDao: HistoryDao, private val languageDao: NewLanguagesDao, private val recentSearchDao: NewRecentSearchDao, private val zimReaderContainer: ZimReaderContainer )</ID>
<ID>LongParameterList:Repository.kt$Repository$( @param:IO private val ioThread: Scheduler, @param:MainThread private val mainThread: Scheduler, private val bookDao: NewBookDao, private val libkiwixBookmarks: LibkiwixBookmarks, private val historyRoomDao: HistoryRoomDao, private val notesRoomDao: NotesRoomDao, private val languageDao: NewLanguagesDao, private val recentSearchRoomDao: RecentSearchRoomDao, private val zimReaderContainer: ZimReaderContainer )</ID>
<ID>LongParameterList:ToolbarScrollingKiwixWebView.kt$ToolbarScrollingKiwixWebView$( context: Context, callback: WebViewCallback, attrs: AttributeSet, nonVideoView: ViewGroup, videoView: ViewGroup, webViewClient: CoreWebViewClient, private val toolbarView: View, private val bottomBarView: View, sharedPreferenceUtil: SharedPreferenceUtil, private val parentNavigationBar: View? = null )</ID>
<ID>MagicNumber:ArticleCount.kt$ArticleCount$3</ID>
<ID>MagicNumber:CompatFindActionModeCallback.kt$CompatFindActionModeCallback$100</ID>

View File

@ -29,10 +29,12 @@ import io.reactivex.subjects.BehaviorSubject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.rx3.rxSingle
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
import org.kiwix.kiwixmobile.core.extensions.deleteFile
import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.page.adapter.Page
@ -68,8 +70,13 @@ class LibkiwixBookmarks @Inject constructor(
private var bookmarkList: List<LibkiwixBookmarkItem> = arrayListOf()
private var libraryBooksList: List<String> = arrayListOf()
@Suppress("CheckResult")
private val bookmarkListBehaviour: BehaviorSubject<List<LibkiwixBookmarkItem>>? by lazy {
BehaviorSubject.createDefault(getBookmarksList())
BehaviorSubject.create<List<LibkiwixBookmarkItem>>().also { subject ->
rxSingle { getBookmarksList() }
.subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io())
.subscribe(subject::onNext, subject::onError)
}
}
private val bookmarksFolderPath: String by lazy {
@ -113,7 +120,7 @@ class LibkiwixBookmarks @Inject constructor(
override fun deletePages(pagesToDelete: List<Page>) =
deleteBookmarks(pagesToDelete as List<LibkiwixBookmarkItem>)
fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List<String> {
suspend fun getCurrentZimBookmarksUrl(zimFileReader: ZimFileReader?): List<String> {
return zimFileReader?.let { reader ->
getBookmarksList()
.filter { it.zimId == reader.id }
@ -134,7 +141,7 @@ class LibkiwixBookmarks @Inject constructor(
* during data migration, where data is written to the file only once after all bookmarks
* have been added to libkiwix to optimize the process.
*/
fun saveBookmark(
suspend fun saveBookmark(
libkiwixBookmarkItem: LibkiwixBookmarkItem,
shouldWriteBookmarkToFile: Boolean = true
) {
@ -241,7 +248,7 @@ class LibkiwixBookmarks @Inject constructor(
}
@Suppress("ReturnCount")
private fun getBookmarksList(): List<LibkiwixBookmarkItem> {
private suspend fun getBookmarksList(): List<LibkiwixBookmarkItem> {
if (!bookmarksChanged && bookmarkList.isNotEmpty()) {
// No changes, return the cached data
return bookmarkList.distinctBy(LibkiwixBookmarkItem::bookmarkUrl)
@ -290,7 +297,7 @@ class LibkiwixBookmarks @Inject constructor(
}
@Suppress("NestedBlockDepth")
private fun deleteDuplicateBookmarks() {
private suspend fun deleteDuplicateBookmarks() {
bookmarkList.groupBy { it.bookmarkUrl to it.zimReaderSource }
.filter { it.value.size > 1 }
.forEach { (_, value) ->
@ -319,7 +326,7 @@ class LibkiwixBookmarks @Inject constructor(
}
}
private fun getZimFileReaderFromBookmark(
private suspend fun getZimFileReaderFromBookmark(
bookmarkItem: LibkiwixBookmarkItem,
coreApp: CoreApp
): ZimFileReader? {
@ -342,7 +349,7 @@ class LibkiwixBookmarks @Inject constructor(
}
}
private fun isBookMarkExist(libkiwixBookmarkItem: LibkiwixBookmarkItem): Boolean =
private suspend fun isBookMarkExist(libkiwixBookmarkItem: LibkiwixBookmarkItem): Boolean =
getBookmarksList()
.any {
it.url == libkiwixBookmarkItem.bookmarkUrl &&
@ -368,7 +375,11 @@ class LibkiwixBookmarks @Inject constructor(
}
private fun updateFlowableBookmarkList() {
bookmarkListBehaviour?.onNext(getBookmarksList())
bookmarkListBehaviour?.let { subject ->
rxSingle { getBookmarksList() }
.subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io())
.subscribe(subject::onNext, subject::onError)
}
}
// Export the `bookmark.xml` file to the `Download/org.kiwix/` directory of internal storage.
@ -408,7 +419,7 @@ class LibkiwixBookmarks @Inject constructor(
}.first { !it.isFileExist() }
}
fun importBookmarks(bookmarkFile: File) {
suspend fun importBookmarks(bookmarkFile: File) {
// Create a temporary library manager to import the bookmarks.
val tempLibrary = Library()
Manager(tempLibrary).apply {
@ -426,7 +437,7 @@ class LibkiwixBookmarks @Inject constructor(
sharedPreferenceUtil.context.toast(R.string.bookmark_imported_message)
if (bookmarkFile.exists()) {
bookmarkFile.delete()
bookmarkFile.deleteFile()
}
}

View File

@ -21,6 +21,7 @@ import io.objectbox.Box
import io.objectbox.kotlin.inValues
import io.objectbox.kotlin.query
import io.objectbox.query.QueryBuilder
import kotlinx.coroutines.rx3.rxSingle
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity_
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
@ -31,23 +32,29 @@ import javax.inject.Inject
class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
fun books() = box.asFlowable()
.map { books ->
books.map { bookOnDiskEntity ->
bookOnDiskEntity.file.let { file ->
// set zimReaderSource for previously saved books
.flatMap { books ->
io.reactivex.rxjava3.core.Flowable.fromIterable(books)
.flatMapSingle { bookOnDiskEntity ->
val file = bookOnDiskEntity.file
val zimReaderSource = ZimReaderSource(file)
if (zimReaderSource.canOpenInLibkiwix()) {
bookOnDiskEntity.zimReaderSource = zimReaderSource
}
rxSingle { zimReaderSource.canOpenInLibkiwix() }
.map { canOpen ->
if (canOpen) {
bookOnDiskEntity.zimReaderSource = zimReaderSource
}
bookOnDiskEntity
}
.onErrorReturn { bookOnDiskEntity }
}
bookOnDiskEntity
}
.toList()
.toFlowable()
}
.doOnNext { removeBooksThatDoNotExist(it.toMutableList()) }
.map { books -> books.filter { it.zimReaderSource.exists() } }
.map { it.map(::BookOnDisk) }
fun getBooks() = box.all.map { bookOnDiskEntity ->
suspend fun getBooks() = box.all.map { bookOnDiskEntity ->
bookOnDiskEntity.file.let { file ->
// set zimReaderSource for previously saved books
val zimReaderSource = ZimReaderSource(file)

View File

@ -41,9 +41,11 @@ interface DataSource {
fun deleteHistory(historyList: List<HistoryListItem>): Completable
fun clearHistory(): Completable
fun getBookmarks(): Flowable<List<LibkiwixBookmarkItem>>
fun getCurrentZimBookmarksUrl(): Single<List<String>>
fun getCurrentZimBookmarksUrl(): io.reactivex.rxjava3.core.Single<List<String>>
fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem):
io.reactivex.rxjava3.core.Completable
fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem): Completable
fun deleteBookmarks(bookmarks: List<LibkiwixBookmarkItem>): Completable
fun deleteBookmark(bookId: String, bookmarkUrl: String): Completable?
fun booksOnDiskAsListItems(): Flowable<List<BooksOnDiskListItem>>

View File

@ -21,7 +21,8 @@ package org.kiwix.kiwixmobile.core.data
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.Scheduler
import io.reactivex.Single
import kotlinx.coroutines.rx3.rxCompletable
import kotlinx.coroutines.rx3.rxSingle
import org.kiwix.kiwixmobile.core.dao.HistoryRoomDao
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
import org.kiwix.kiwixmobile.core.dao.NewBookDao
@ -47,10 +48,9 @@ import javax.inject.Singleton
* A central repository of data which should provide the presenters with the required data.
*/
@Suppress("LongParameterList")
@Singleton
class Repository @Inject internal constructor(
@param:IO private val io: Scheduler,
@param:IO private val ioThread: Scheduler,
@param:MainThread private val mainThread: Scheduler,
private val bookDao: NewBookDao,
private val libkiwixBookmarks: LibkiwixBookmarks,
@ -64,7 +64,7 @@ class Repository @Inject internal constructor(
override fun getLanguageCategorizedBooks() =
booksOnDiskAsListItems()
.first(emptyList())
.subscribeOn(io)
.subscribeOn(ioThread)
.observeOn(mainThread)
override fun booksOnDiskAsListItems(): Flowable<List<BooksOnDiskListItem>> = bookDao.books()
@ -91,60 +91,60 @@ class Repository @Inject internal constructor(
override fun saveBooks(books: List<BookOnDisk>) =
Completable.fromAction { bookDao.insert(books) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun saveBook(book: BookOnDisk) =
Completable.fromAction { bookDao.insert(listOf(book)) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun saveLanguages(languages: List<Language>) =
Completable.fromAction { languageDao.insert(languages) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun saveHistory(history: HistoryItem) =
Completable.fromAction { historyRoomDao.saveHistory(history) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun deleteHistory(historyList: List<HistoryListItem>) =
Completable.fromAction {
historyRoomDao.deleteHistory(historyList.filterIsInstance(HistoryItem::class.java))
}
.subscribeOn(io)
.subscribeOn(ioThread)
override fun clearHistory() = Completable.fromAction {
historyRoomDao.deleteAllHistory()
recentSearchRoomDao.deleteSearchHistory()
}.subscribeOn(io)
}.subscribeOn(ioThread)
override fun getBookmarks() =
libkiwixBookmarks.bookmarks() as Flowable<List<LibkiwixBookmarkItem>>
override fun getCurrentZimBookmarksUrl() =
Single.just(libkiwixBookmarks.getCurrentZimBookmarksUrl(zimReaderContainer.zimFileReader))
.subscribeOn(io)
.observeOn(mainThread)
rxSingle {
libkiwixBookmarks.getCurrentZimBookmarksUrl(zimReaderContainer.zimFileReader)
}.subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io())
override fun saveBookmark(libkiwixBookmarkItem: LibkiwixBookmarkItem) =
Completable.fromAction { libkiwixBookmarks.saveBookmark(libkiwixBookmarkItem) }
.subscribeOn(io)
rxCompletable { libkiwixBookmarks.saveBookmark(libkiwixBookmarkItem) }
.subscribeOn(io.reactivex.rxjava3.schedulers.Schedulers.io())
override fun deleteBookmarks(bookmarks: List<LibkiwixBookmarkItem>) =
Completable.fromAction { libkiwixBookmarks.deleteBookmarks(bookmarks) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun deleteBookmark(bookId: String, bookmarkUrl: String): Completable? =
Completable.fromAction { libkiwixBookmarks.deleteBookmark(bookId, bookmarkUrl) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun saveNote(noteListItem: NoteListItem): Completable =
Completable.fromAction { notesRoomDao.saveNote(noteListItem) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun deleteNotes(noteList: List<NoteListItem>) =
Completable.fromAction { notesRoomDao.deleteNotes(noteList) }
.subscribeOn(io)
.subscribeOn(ioThread)
override fun deleteNote(noteTitle: String): Completable =
Completable.fromAction { notesRoomDao.deleteNote(noteTitle) }
.subscribeOn(io)
.subscribeOn(ioThread)
}

View File

@ -25,6 +25,8 @@ import android.os.Process
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation
@ -87,7 +89,9 @@ open class ErrorActivity : BaseActivity() {
private fun setupReportButton() {
activityKiwixErrorBinding?.reportButton?.setOnClickListener {
sendEmailLauncher.launch(Intent.createChooser(emailIntent(), "Send email..."))
lifecycleScope.launch {
sendEmailLauncher.launch(Intent.createChooser(emailIntent(), "Send email..."))
}
}
}
@ -96,7 +100,7 @@ open class ErrorActivity : BaseActivity() {
restartApp()
}
private fun emailIntent(): Intent {
private suspend fun emailIntent(): Intent {
val emailBody = buildBody()
return Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
@ -122,7 +126,7 @@ open class ErrorActivity : BaseActivity() {
}
}
private fun buildBody(): String = """
private suspend fun buildBody(): String = """
$initialBody
${if (activityKiwixErrorBinding?.allowCrash?.isChecked == true && exception != null) exceptionDetails() else ""}
@ -139,7 +143,7 @@ open class ErrorActivity : BaseActivity() {
${exception?.let(::toStackTraceString)}
""".trimIndent()
private fun zimFiles(): String {
private suspend fun zimFiles(): String {
val allZimFiles = bookDao.getBooks().joinToString {
"""
${it.book.title}:

View File

@ -41,10 +41,6 @@ fun File.totalSpace(): Long = runBlocking {
}
}
fun File.canReadFile(): Boolean = runBlocking {
withContext(Dispatchers.IO) {
canRead()
}
}
suspend fun File.canReadFile(): Boolean = withContext(Dispatchers.IO) { canRead() }
suspend fun File.deleteFile(): Boolean = withContext(Dispatchers.IO) { delete() }

View File

@ -80,6 +80,7 @@ import androidx.core.widget.ContentLoadingProgressBar
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -93,6 +94,9 @@ import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.processors.BehaviorProcessor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONException
import org.kiwix.kiwixmobile.core.BuildConfig
@ -1641,16 +1645,21 @@ abstract class CoreReaderFragment :
fun openZimFile(zimReaderSource: ZimReaderSource, isCustomApp: Boolean = false) {
if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) || isCustomApp) {
if (zimReaderSource.canOpenInLibkiwix()) {
// Show content if there is `Open Library` button showing
// and we are opening the ZIM file
reopenBook()
openAndSetInContainer(zimReaderSource)
updateTitle()
} else {
exitBook()
Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + zimReaderSource.toDatabase())
requireActivity().toast(R.string.error_file_not_found, Toast.LENGTH_LONG)
lifecycleScope.launch {
val canOpenInLibkiwix = withContext(Dispatchers.IO) {
zimReaderSource.canOpenInLibkiwix()
}
if (canOpenInLibkiwix) {
// Show content if there is `Open Library` button showing
// and we are opening the ZIM file
reopenBook()
openAndSetInContainer(zimReaderSource)
updateTitle()
} else {
exitBook()
Log.w(TAG_KIWIX, "ZIM file doesn't exist at " + zimReaderSource.toDatabase())
requireActivity().toast(R.string.error_file_not_found, Toast.LENGTH_LONG)
}
}
} else {
this.zimReaderSource = zimReaderSource

View File

@ -17,13 +17,13 @@
*/
package org.kiwix.kiwixmobile.core.main
import org.kiwix.kiwixmobile.core.utils.files.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.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.utils.files.Log
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import javax.inject.Inject
@ -32,7 +32,7 @@ private const val TAG = "MainPresenter"
@ActivityScope
class MainRepositoryActions @Inject constructor(private val dataSource: DataSource) {
private var saveHistoryDisposable: Disposable? = null
private var saveBookmarkDisposable: Disposable? = null
private var saveBookmarkDisposable: io.reactivex.rxjava3.disposables.Disposable? = null
private var saveNoteDisposable: Disposable? = null
private var saveBookDisposable: Disposable? = null
private var deleteNoteDisposable: Disposable? = null

View File

@ -62,7 +62,7 @@ class ZimReaderSource(
}
}
fun canOpenInLibkiwix(): Boolean {
suspend fun canOpenInLibkiwix(): Boolean {
return when {
file?.canReadFile() == true -> true
assetFileDescriptorList?.get(0)?.parcelFileDescriptor?.fd
@ -72,7 +72,7 @@ class ZimReaderSource(
}
}
fun createArchive(): Archive? {
suspend fun createArchive(): Archive? {
if (canOpenInLibkiwix()) {
return when {
file != null -> Archive(file.canonicalPath)

View File

@ -40,6 +40,9 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.snackbar.Snackbar
import eu.mhutti1.utils.storage.StorageDevice
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent
import org.kiwix.kiwixmobile.core.CoreApp.Companion.instance
import org.kiwix.kiwixmobile.core.DarkModeConfig
@ -403,7 +406,9 @@ abstract class CorePrefsFragment :
createTempFile(contentResolver.openInputStream(uri)).apply {
if (isValidXmlFile(this)) {
libkiwixBookmarks?.importBookmarks(this)
CoroutineScope(Dispatchers.IO).launch {
libkiwixBookmarks?.importBookmarks(this@apply)
}
} else {
activity.toast(
resources.getString(R.string.error_invalid_bookmark_file),

View File

@ -19,8 +19,11 @@
package org.kiwix.kiwixmobile.core.utils
import android.app.Activity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import javax.inject.Inject
const val THREE_DAYS_IN_MILLISECONDS = 3 * 24 * 60 * 60 * 1000L
@ -42,15 +45,19 @@ class DonationDialogHandler @Inject constructor(
val currentMilliSeconds = System.currentTimeMillis()
val lastPopupMillis = sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds
val timeDifference = currentMilliSeconds - lastPopupMillis
if (shouldShowInitialPopup(lastPopupMillis) || timeDifference >= THREE_MONTHS_IN_MILLISECONDS) {
if (isZimFilesAvailableInLibrary() && isTimeToShowDonation(currentMilliSeconds)) {
showDonationDialogCallback?.showDonationDialog()
resetDonateLater()
(activity as CoreMainActivity).lifecycleScope.launch {
if (shouldShowInitialPopup(lastPopupMillis) ||
timeDifference >= THREE_MONTHS_IN_MILLISECONDS
) {
if (isZimFilesAvailableInLibrary() && isTimeToShowDonation(currentMilliSeconds)) {
showDonationDialogCallback?.showDonationDialog()
resetDonateLater()
}
}
}
}
private fun shouldShowInitialPopup(lastPopupMillis: Long): Boolean =
private suspend fun shouldShowInitialPopup(lastPopupMillis: Long): Boolean =
lastPopupMillis == 0L && isZimFilesAvailableInLibrary()
private fun isTimeToShowDonation(currentMillis: Long): Boolean =
@ -63,7 +70,7 @@ class DonationDialogHandler @Inject constructor(
return timeDifference >= THREE_DAYS_IN_MILLISECONDS
}
fun isZimFilesAvailableInLibrary(): Boolean =
suspend fun isZimFilesAvailableInLibrary(): Boolean =
if (activity.isCustomApp()) true else newBookDao.getBooks().isNotEmpty()
fun updateLastDonationPopupShownTime() {

View File

@ -22,11 +22,14 @@ import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import androidx.annotation.IdRes
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.di.ActivityScope
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.isCustomApp
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.utils.NetworkUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
@ -67,18 +70,20 @@ class RateDialogHandler @Inject constructor(
tempVisitCount = visitCounterPref?.count ?: 0
++tempVisitCount
visitCounterPref?.count = tempVisitCount
if (shouldShowRateDialog() && NetworkUtils.isNetworkAvailable(activity)) {
showRateDialog(iconResId)
(activity as CoreMainActivity).lifecycleScope.launch {
if (shouldShowRateDialog() && NetworkUtils.isNetworkAvailable(activity)) {
showRateDialog(iconResId)
}
}
}
private fun shouldShowRateDialog(): Boolean {
private suspend fun shouldShowRateDialog(): Boolean {
return tempVisitCount >= VISITS_REQUIRED_TO_SHOW_RATE_DIALOG &&
visitCounterPref?.noThanksState == false && isTwoWeekPassed() &&
isZimFilesAvailableInLibrary() && !BuildConfig.DEBUG
}
private fun isZimFilesAvailableInLibrary(): Boolean {
private suspend fun isZimFilesAvailableInLibrary(): Boolean {
// If it is a custom app, return true since custom apps always have the ZIM file.
if (activity.isCustomApp()) return true
// For Kiwix app, check if there are ZIM files available in the library.