From 718c51904b118cb14775da0c6986ddb9e459cf4e Mon Sep 17 00:00:00 2001 From: CalebK Date: Mon, 15 Jul 2024 17:55:11 +0300 Subject: [PATCH 1/9] Suspended setZimFile and setZimFileDescriptor and switched dispatchers to IO when creating zimFileReader --- .../kiwixmobile/core/reader/ZimReaderContainer.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt index 514e5c3460..6864cc1763 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt @@ -19,7 +19,8 @@ package org.kiwix.kiwixmobile.core.reader import android.content.res.AssetFileDescriptor import android.webkit.WebResourceResponse -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory import java.io.File @@ -35,21 +36,21 @@ class ZimReaderContainer @Inject constructor(private val zimFileReaderFactory: F field = value } - fun setZimFile(file: File?) { + suspend fun setZimFile(file: File?) { if (file?.canonicalPath == zimFileReader?.zimFile?.canonicalPath) { return } - zimFileReader = runBlocking { + zimFileReader = withContext(Dispatchers.IO) { if (file?.isFileExist() == true) zimFileReaderFactory.create(file) else null } } - fun setZimFileDescriptor( + suspend fun setZimFileDescriptor( assetFileDescriptorList: List, filePath: String? = null ) { - zimFileReader = runBlocking { + zimFileReader = withContext(Dispatchers.IO) { if (assetFileDescriptorList.isNotEmpty() && assetFileDescriptorList[0].parcelFileDescriptor.fileDescriptor.valid() ) From 74ef71e1ed97ae3c1ebc8c48aa956cacf1fa3517 Mon Sep 17 00:00:00 2001 From: CalebK Date: Mon, 15 Jul 2024 17:59:11 +0300 Subject: [PATCH 2/9] launched coroutines on lifecycleScope --- .../destination/reader/KiwixReaderFragment.kt | 107 ++++++++++-------- .../core/main/CoreReaderFragment.kt | 62 ++++++---- .../custom/main/CustomReaderFragment.kt | 87 +++++++------- 3 files changed, 147 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt index 9d64a98e3d..9b789887b8 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt @@ -33,7 +33,11 @@ import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toFile import androidx.drawerlayout.widget.DrawerLayout +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.bottomnavigation.BottomNavigationView +import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.cachedComponent import org.kiwix.kiwixmobile.core.R.anim @@ -70,20 +74,23 @@ class KiwixReaderFragment : CoreReaderFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - val activity = activity as CoreMainActivity - noOpenBookButton?.setOnClickListener { - activity.navigate( - KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary() - ) + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + val activity = activity as CoreMainActivity + noOpenBookButton?.setOnClickListener { + activity.navigate( + KiwixReaderFragmentDirections.actionNavigationReaderToNavigationLibrary() + ) + } + activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) + toolbar?.let(activity::setupDrawerToggle) + setFragmentContainerBottomMarginToSizeOfNavBar() + openPageInBookFromNavigationArguments() + } } - activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) - toolbar?.let(activity::setupDrawerToggle) - setFragmentContainerBottomMarginToSizeOfNavBar() - openPageInBookFromNavigationArguments() } - private fun openPageInBookFromNavigationArguments() { + private suspend fun openPageInBookFromNavigationArguments() { val args = KiwixReaderFragmentArgs.fromBundle(requireArguments()) if (args.pageUrl.isNotEmpty()) { @@ -107,7 +114,7 @@ class KiwixReaderFragment : CoreReaderFragment() { requireArguments().clear() } - private fun tryOpeningZimFile(zimFileUri: String) { + private suspend fun tryOpeningZimFile(zimFileUri: String) { val filePath = FileUtils.getLocalFilePathByUri( requireActivity().applicationContext, Uri.parse(zimFileUri) ) @@ -154,14 +161,16 @@ class KiwixReaderFragment : CoreReaderFragment() { contentFrame?.visibility = View.VISIBLE } mainMenu?.showWebViewOptions(true) - if (webViewList.isEmpty()) { - exitBook() - } else { - // Reset the top margin of web views to 0 to remove any previously set margin - // This ensures that the web views are displayed without any additional - // top margin for kiwix main app. - setTopMarginToWebViews(0) - selectTab(currentWebViewIndex) + lifecycleScope.launch { + if (webViewList.isEmpty()) { + exitBook() + } else { + // Reset the top margin of web views to 0 to remove any previously set margin + // This ensures that the web views are displayed without any additional + // top margin for kiwix main app. + setTopMarginToWebViews(0) + selectTab(currentWebViewIndex) + } } } } @@ -198,17 +207,19 @@ class KiwixReaderFragment : CoreReaderFragment() { override fun onResume() { super.onResume() - if (zimReaderContainer?.zimFile == null && - zimReaderContainer?.zimFileReader?.assetFileDescriptorList?.isEmpty() == true - ) { - exitBook() - } - if (isFullScreenVideo || isInFullScreenMode()) { - hideNavBar() + lifecycleScope.launch { + if (zimReaderContainer?.zimFile == null && + zimReaderContainer?.zimFileReader?.assetFileDescriptorList?.isEmpty() == true + ) { + exitBook() + } + if (isFullScreenVideo || isInFullScreenMode()) { + hideNavBar() + } } } - override fun restoreViewStateOnInvalidJSON() { + override suspend fun restoreViewStateOnInvalidJSON() { Log.d(TAG_KIWIX, "Kiwix normal start, no zimFile loaded last time -> display home page") exitBook() } @@ -221,20 +232,22 @@ class KiwixReaderFragment : CoreReaderFragment() { val settings = requireActivity().getSharedPreferences(SharedPreferenceUtil.PREF_KIWIX_MOBILE, 0) val zimFile = settings.getString(TAG_CURRENT_FILE, null) - if (zimFile != null && File(zimFile).isFileExist()) { - if (zimReaderContainer?.zimFile == null) { - openZimFile(File(zimFile)) - Log.d( - TAG_KIWIX, - "Kiwix normal start, Opened last used zimFile: -> $zimFile" - ) + lifecycleScope.launch { + if (zimFile != null && File(zimFile).isFileExist()) { + if (zimReaderContainer?.zimFile == null) { + openZimFile(File(zimFile)) + Log.d( + TAG_KIWIX, + "Kiwix normal start, Opened last used zimFile: -> $zimFile" + ) + } else { + zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) + } } else { - zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) + getCurrentWebView()?.snack(R.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 } - } else { - getCurrentWebView()?.snack(R.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 } restoreTabs(zimArticles, zimPositions, currentTab) } @@ -296,10 +309,12 @@ class KiwixReaderFragment : CoreReaderFragment() { when (it.scheme) { "file" -> { Handler(Looper.getMainLooper()).postDelayed({ - openZimFile(it.toFile()).also { - // if used once then clear it to avoid affecting any other functionality - // of the application. - requireActivity().intent.action = null + lifecycleScope.launch { + openZimFile(it.toFile()).also { + // if used once then clear it to avoid affecting any other functionality + // of the application. + requireActivity().intent.action = null + } } }, 300) } @@ -307,7 +322,9 @@ class KiwixReaderFragment : CoreReaderFragment() { "content" -> { Handler(Looper.getMainLooper()).postDelayed({ getZimFileFromUri(it)?.let { zimFile -> - openZimFile(zimFile) + lifecycleScope.launch { + openZimFile(zimFile) + } }.also { requireActivity().intent.action = null } 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 0beb08a13d..da61ebec2e 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 @@ -77,6 +77,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 @@ -95,6 +96,7 @@ import io.reactivex.Flowable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.processors.BehaviorProcessor +import kotlinx.coroutines.launch import org.json.JSONArray import org.json.JSONException import org.kiwix.kiwixmobile.core.BuildConfig @@ -1269,8 +1271,10 @@ abstract class CoreReaderFragment : it.bringToFront() Snackbar.make(it, R.string.tab_closed, Snackbar.LENGTH_LONG) .setAction(R.string.undo) { undoButton -> - undoButton.isEnabled = false - restoreDeletedTab(index) + lifecycleScope.launch { + undoButton.isEnabled = false + restoreDeletedTab(index) + } }.show() } openHomeScreen() @@ -1282,7 +1286,7 @@ abstract class CoreReaderFragment : mainMenu?.showBookSpecificMenuItems() } - protected fun exitBook() { + protected suspend fun exitBook() { showNoBookOpenViews() bottomToolbar?.visibility = View.GONE actionBar?.title = getString(R.string.reader) @@ -1291,11 +1295,11 @@ abstract class CoreReaderFragment : closeZimBook() } - private fun closeZimBook() { + private suspend fun closeZimBook() { zimReaderContainer?.setZimFile(null) } - private fun restoreDeletedTab(index: Int) { + private suspend fun restoreDeletedTab(index: Int) { if (webViewList.isEmpty()) { reopenBook() } @@ -1551,7 +1555,7 @@ abstract class CoreReaderFragment : unsupportedMimeTypeHandler?.showSaveOrOpenUnsupportedFilesDialog(url, documentType) } - fun openZimFile( + suspend fun openZimFile( file: File?, isCustomApp: Boolean = false, assetFileDescriptorList: List = emptyList(), @@ -1600,11 +1604,12 @@ abstract class CoreReaderFragment : ) } - private fun openAndSetInContainer( + private suspend fun openAndSetInContainer( file: File? = null, assetFileDescriptorList: List = emptyList(), filePath: String? = null ) { + progressBar?.visibility = View.VISIBLE try { if (isNotPreviouslyOpenZim(file?.canonicalPath)) { webViewList.clear() @@ -1631,6 +1636,7 @@ abstract class CoreReaderFragment : } ?: kotlin.run { requireActivity().toast(R.string.error_file_invalid, Toast.LENGTH_LONG) } + progressBar?.visibility = View.GONE } } @@ -1665,18 +1671,22 @@ abstract class CoreReaderFragment : ) { when (requestCode) { REQUEST_STORAGE_PERMISSION -> { - if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { - file?.let(::openZimFile) - } else { - snackBarRoot?.let { snackBarRoot -> - Snackbar.make(snackBarRoot, R.string.request_storage, Snackbar.LENGTH_LONG) - .setAction(R.string.menu_settings) { - val intent = Intent() - intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - val uri = Uri.fromParts("package", requireActivity().packageName, null) - intent.data = uri - startActivity(intent) - }.show() + lifecycleScope.launch { + if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) { + file?.let { file -> + openZimFile(file) + } + } else { + snackBarRoot?.let { snackBarRoot -> + Snackbar.make(snackBarRoot, R.string.request_storage, Snackbar.LENGTH_LONG) + .setAction(R.string.menu_settings) { + val intent = Intent() + intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + val uri = Uri.fromParts("package", requireActivity().packageName, null) + intent.data = uri + startActivity(intent) + }.show() + } } } } @@ -1708,9 +1718,11 @@ abstract class CoreReaderFragment : root.bringToFront() Snackbar.make(root, R.string.tabs_closed, Snackbar.LENGTH_LONG).apply { setAction(R.string.undo) { - it.isEnabled = false // to prevent multiple clicks on this button - setIsCloseAllTabButtonClickable(true) - restoreDeletedTabs() + lifecycleScope.launch { + it.isEnabled = false // to prevent multiple clicks on this button + setIsCloseAllTabButtonClickable(true) + restoreDeletedTabs() + } } show() } @@ -1721,7 +1733,7 @@ abstract class CoreReaderFragment : closeAllTabsButton?.isClickable = isClickable } - private fun restoreDeletedTabs() { + private suspend fun restoreDeletedTabs() { if (tempWebViewListForUndo.isNotEmpty()) { zimReaderContainer?.setZimFile(tempZimFileForUndo) webViewList.addAll(tempWebViewListForUndo) @@ -2271,7 +2283,7 @@ abstract class CoreReaderFragment : private fun isInvalidJson(jsonString: String?): Boolean = jsonString == null || jsonString == "[]" - protected fun manageExternalLaunchAndRestoringViewState() { + protected suspend fun manageExternalLaunchAndRestoringViewState() { val settings = requireActivity().getSharedPreferences( SharedPreferenceUtil.PREF_KIWIX_MOBILE, 0 @@ -2405,5 +2417,5 @@ abstract class CoreReaderFragment : * KiwixReaderFragment.restoreViewStateOnInvalidJSON) to ensure consistent behavior * when handling invalid JSON scenarios. */ - abstract fun restoreViewStateOnInvalidJSON() + abstract suspend fun restoreViewStateOnInvalidJSON() } diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt index 9321cd0858..fd3f6239bd 100644 --- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt +++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomReaderFragment.kt @@ -29,7 +29,11 @@ import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.drawerlayout.widget.DrawerLayout +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.fragment.findNavController +import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.R.dimen import org.kiwix.kiwixmobile.core.base.BaseActivity import org.kiwix.kiwixmobile.core.extensions.getResizedDrawable @@ -68,23 +72,26 @@ class CustomReaderFragment : CoreReaderFragment() { if (enforcedLanguage()) { return } - - if (isAdded) { - setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) - if (BuildConfig.DISABLE_SIDEBAR) { - val toolbarToc = activity?.findViewById(R.id.bottom_toolbar_toc) - toolbarToc?.isEnabled = false - } - with(activity as AppCompatActivity) { - supportActionBar?.setDisplayHomeAsUpEnabled(true) - toolbar?.let(::setUpDrawerToggle) - } - loadPageFromNavigationArguments() - if (BuildConfig.DISABLE_EXTERNAL_LINK) { - // If "external links" are disabled in a custom app, - // this sets the shared preference to not show the external link popup - // when opening external links. - sharedPreferenceUtil?.putPrefExternalLinkPopup(false) + lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + if (isAdded) { + setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) + if (BuildConfig.DISABLE_SIDEBAR) { + val toolbarToc = activity?.findViewById(R.id.bottom_toolbar_toc) + toolbarToc?.isEnabled = false + } + with(activity as AppCompatActivity) { + supportActionBar?.setDisplayHomeAsUpEnabled(true) + toolbar?.let(::setUpDrawerToggle) + } + loadPageFromNavigationArguments() + if (BuildConfig.DISABLE_EXTERNAL_LINK) { + // If "external links" are disabled in a custom app, + // this sets the shared preference to not show the external link popup + // when opening external links. + sharedPreferenceUtil?.putPrefExternalLinkPopup(false) + } + } } } } @@ -129,7 +136,7 @@ class CustomReaderFragment : CoreReaderFragment() { super.setTabSwitcherVisibility(visibility) } - private fun loadPageFromNavigationArguments() { + private suspend fun loadPageFromNavigationArguments() { val args = CustomReaderFragmentArgs.fromBundle(requireArguments()) if (args.pageUrl.isNotEmpty()) { loadUrlWithCurrentWebview(args.pageUrl) @@ -148,7 +155,7 @@ class CustomReaderFragment : CoreReaderFragment() { * due to invalid or corrupted data. In this case, it opens the homepage of the zim file, * as custom apps always have the zim file available. */ - override fun restoreViewStateOnInvalidJSON() { + override suspend fun restoreViewStateOnInvalidJSON() { openHomeScreen() } @@ -179,30 +186,32 @@ class CustomReaderFragment : CoreReaderFragment() { private fun openObbOrZim() { customFileValidator.validate( onFilesFound = { - when (it) { - is ValidationState.HasFile -> { - if (it.assetFileDescriptorList.isNotEmpty()) { - openZimFile(null, true, it.assetFileDescriptorList) - } else { - openZimFile(it.file, true) + lifecycleScope.launch { + when (it) { + is ValidationState.HasFile -> { + if (it.assetFileDescriptorList.isNotEmpty()) { + openZimFile(null, true, it.assetFileDescriptorList) + } else { + openZimFile(it.file, true) + } + // Save book in the database to display it in `ZimHostFragment`. + zimReaderContainer?.zimFileReader?.let { zimFileReader -> + // Check if the file is not null. If the file is null, + // it means we have created zimFileReader with a fileDescriptor, + // so we create a demo file to save it in the database for display on the `ZimHostFragment`. + val file = it.file ?: createDemoFile() + val bookOnDisk = BookOnDisk(file, zimFileReader) + repositoryActions?.saveBook(bookOnDisk) + } } - // Save book in the database to display it in `ZimHostFragment`. - zimReaderContainer?.zimFileReader?.let { zimFileReader -> - // Check if the file is not null. If the file is null, - // it means we have created zimFileReader with a fileDescriptor, - // so we create a demo file to save it in the database for display on the `ZimHostFragment`. - val file = it.file ?: createDemoFile() - val bookOnDisk = BookOnDisk(file, zimFileReader) - repositoryActions?.saveBook(bookOnDisk) + + is ValidationState.HasBothFiles -> { + it.zimFile.delete() + openZimFile(it.obbFile, true) } - } - is ValidationState.HasBothFiles -> { - it.zimFile.delete() - openZimFile(it.obbFile, true) + else -> {} } - - else -> {} } }, onNoFilesFound = { From 1ac177931b90464271df5cd43426d7f0066c7854 Mon Sep 17 00:00:00 2001 From: CalebK Date: Mon, 15 Jul 2024 18:03:39 +0300 Subject: [PATCH 3/9] Passed scope to the constructors and tied the coroutineScope to viewmodelScope --- .../zimManager/ZimManageViewModel.kt | 3 +- .../fileselectView/effects/DeleteFiles.kt | 26 +++++--- .../page/notes/viewmodel/NotesViewModel.kt | 3 +- .../viewmodel/effects/ShowOpenNoteDialog.kt | 61 ++++++++++--------- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt index 275a2311a2..a3e9c08137 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModel.kt @@ -23,6 +23,7 @@ import android.net.ConnectivityManager import androidx.annotation.VisibleForTesting import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import io.reactivex.Flowable import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable @@ -164,7 +165,7 @@ class ZimManageViewModel @Inject constructor( when (it) { is RequestNavigateTo -> OpenFileWithNavigation(it.bookOnDisk) is RequestMultiSelection -> startMultiSelectionAndSelectBook(it.bookOnDisk) - RequestDeleteMultiSelection -> DeleteFiles(selectionsFromState()) + RequestDeleteMultiSelection -> DeleteFiles(selectionsFromState(), viewModelScope) RequestShareMultiSelection -> ShareFiles(selectionsFromState()) MultiModeFinished -> noSideEffectAndClearSelectionState() is RequestSelect -> noSideEffectSelectBook(it.bookOnDisk) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/DeleteFiles.kt b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/DeleteFiles.kt index 4c75a9c8e3..faf456612a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/DeleteFiles.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zimManager/fileselectView/effects/DeleteFiles.kt @@ -19,6 +19,8 @@ package org.kiwix.kiwixmobile.zimManager.fileselectView.effects import androidx.appcompat.app.AppCompatActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.cachedComponent import org.kiwix.kiwixmobile.core.base.BaseActivity @@ -33,7 +35,10 @@ import org.kiwix.kiwixmobile.core.utils.files.FileUtils import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import javax.inject.Inject -data class DeleteFiles(private val booksOnDiskListItems: List) : +data class DeleteFiles( + private val booksOnDiskListItems: List, + private val coroutineScope: CoroutineScope +) : SideEffect { @Inject lateinit var dialogShower: DialogShower @@ -42,21 +47,22 @@ data class DeleteFiles(private val booksOnDiskListItems: List) : override fun invokeWith(activity: AppCompatActivity) { (activity as BaseActivity).cachedComponent.inject(this) - val name = booksOnDiskListItems.joinToString(separator = "\n") { it.book.title } dialogShower.show(DeleteZims(name), { - activity.toast( - if (booksOnDiskListItems.deleteAll()) { - R.string.delete_zims_toast - } else { - R.string.delete_zim_failed - } - ) + coroutineScope.launch { + activity.toast( + if (booksOnDiskListItems.deleteAll()) { + R.string.delete_zims_toast + } else { + R.string.delete_zim_failed + } + ) + } }) } - private fun List.deleteAll(): Boolean { + private suspend fun List.deleteAll(): Boolean { return fold(true) { acc, book -> acc && deleteSpecificZimFile(book).also { if (it && book.file.canonicalPath == zimReaderContainer.zimCanonicalPath) { diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt index 11c47ecbfc..f407d018a2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt @@ -18,6 +18,7 @@ package org.kiwix.kiwixmobile.core.page.notes.viewmodel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineScope import org.kiwix.kiwixmobile.core.dao.NotesRoomDao import org.kiwix.kiwixmobile.core.page.adapter.Page @@ -70,5 +71,5 @@ class NotesViewModel @Inject constructor( ShowDeleteNotesDialog(effects, state, pageDao, viewModelScope) override fun onItemClick(page: Page) = - ShowOpenNoteDialog(effects, page, zimReaderContainer) + ShowOpenNoteDialog(effects, page, zimReaderContainer, viewModelScope) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt index d1ce75018d..e1b533d538 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt @@ -20,6 +20,8 @@ package org.kiwix.kiwixmobile.core.page.notes.viewmodel.effects import androidx.appcompat.app.AppCompatActivity import io.reactivex.processors.PublishProcessor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.json.JSONArray import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.cachedComponent @@ -42,7 +44,8 @@ import javax.inject.Inject data class ShowOpenNoteDialog( private val effects: PublishProcessor>, private val page: Page, - private val zimReaderContainer: ZimReaderContainer + private val zimReaderContainer: ZimReaderContainer, + private val dialogScope: CoroutineScope ) : SideEffect { @Inject lateinit var dialogShower: DialogShower override fun invokeWith(activity: AppCompatActivity) { @@ -51,35 +54,37 @@ data class ShowOpenNoteDialog( ShowNoteDialog, { effects.offer(OpenPage(page, zimReaderContainer)) }, { - val item = page as NoteListItem - // Check if zimFilePath is not null, and then set it in zimReaderContainer. - // For custom apps, we are currently using fileDescriptor, and they only have a single file in them, - // which is already set in zimReaderContainer, so there's no need to set it again. - item.zimFilePath?.let { - val currentZimFilePath = zimReaderContainer.zimCanonicalPath - val file = File(it) - zimReaderContainer.setZimFile(file) - if (zimReaderContainer.zimCanonicalPath != currentZimFilePath) { - // if current zim file is not the same set the main page of that zim file - // so that when we go back it properly loads the article, and do nothing if the - // zim file is same because there might be multiple tabs opened. - val settings = activity.getSharedPreferences( - SharedPreferenceUtil.PREF_KIWIX_MOBILE, - 0 - ) - val editor = settings.edit() - val urls = JSONArray() - val positions = JSONArray() - urls.put(CONTENT_PREFIX + zimReaderContainer.mainPage) - positions.put(0) - editor.putString(TAG_CURRENT_FILE, zimReaderContainer.zimCanonicalPath) - editor.putString(TAG_CURRENT_ARTICLES, "$urls") - editor.putString(TAG_CURRENT_POSITIONS, "$positions") - editor.putInt(TAG_CURRENT_TAB, 0) - editor.apply() + dialogScope.launch { + val item = page as NoteListItem + // Check if zimFilePath is not null, and then set it in zimReaderContainer. + // For custom apps, we are currently using fileDescriptor, and they only have a single file in them, + // which is already set in zimReaderContainer, so there's no need to set it again. + item.zimFilePath?.let { + val currentZimFilePath = zimReaderContainer.zimCanonicalPath + val file = File(it) + zimReaderContainer.setZimFile(file) + if (zimReaderContainer.zimCanonicalPath != currentZimFilePath) { + // if current zim file is not the same set the main page of that zim file + // so that when we go back it properly loads the article, and do nothing if the + // zim file is same because there might be multiple tabs opened. + val settings = activity.getSharedPreferences( + SharedPreferenceUtil.PREF_KIWIX_MOBILE, + 0 + ) + val editor = settings.edit() + val urls = JSONArray() + val positions = JSONArray() + urls.put(CONTENT_PREFIX + zimReaderContainer.mainPage) + positions.put(0) + editor.putString(TAG_CURRENT_FILE, zimReaderContainer.zimCanonicalPath) + editor.putString(TAG_CURRENT_ARTICLES, "$urls") + editor.putString(TAG_CURRENT_POSITIONS, "$positions") + editor.putInt(TAG_CURRENT_TAB, 0) + editor.apply() + } } + effects.offer(OpenNote(item.noteFilePath, item.zimUrl, item.title)) } - effects.offer(OpenNote(item.noteFilePath, item.zimUrl, item.title)) } ) } From 20d7e4ca333b9dea6a8c8c7990ec7da0b411c8a4 Mon Sep 17 00:00:00 2001 From: CalebK Date: Mon, 15 Jul 2024 18:06:00 +0300 Subject: [PATCH 4/9] updated errors on the test classes with runblocking --- .../search/SearchFragmentTestForCustomApp.kt | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/custom/src/androidTest/java/org/kiwix/kiwixmobile/custom/search/SearchFragmentTestForCustomApp.kt b/custom/src/androidTest/java/org/kiwix/kiwixmobile/custom/search/SearchFragmentTestForCustomApp.kt index 9afff2746a..0274dc44d1 100644 --- a/custom/src/androidTest/java/org/kiwix/kiwixmobile/custom/search/SearchFragmentTestForCustomApp.kt +++ b/custom/src/androidTest/java/org/kiwix/kiwixmobile/custom/search/SearchFragmentTestForCustomApp.kt @@ -33,8 +33,10 @@ import androidx.test.internal.runner.junit4.statement.UiThreadStatement import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.ResponseBody @@ -111,7 +113,7 @@ class SearchFragmentTestForCustomApp { } @Test - fun searchFragment() { + fun searchFragment() = runBlocking { activityScenario.onActivity { customMainActivity = it } @@ -251,14 +253,20 @@ class SearchFragmentTestForCustomApp { } } - private fun openZimFileInReaderWithAssetFileDescriptor(downloadingZimFile: File) { - getAssetFileDescriptorFromFile(downloadingZimFile)?.let(::openZimFileInReader) ?: run { - throw RuntimeException("Unable to get fileDescriptor from file. Original exception") - } + private suspend fun openZimFileInReaderWithAssetFileDescriptor(downloadingZimFile: File) { + getAssetFileDescriptorFromFile(downloadingZimFile)?.let { assetFileDescriptor -> + withContext(Dispatchers.IO) { + try { + openZimFileInReader(assetFileDescriptor) + } catch (e: Exception) { + throw RuntimeException("Unable to get fileDescriptor from file. Original exception", e) + } + } + } ?: throw RuntimeException("Unable to get fileDescriptor from file.") } - private fun openZimFileInReader(assetFileDescriptor: AssetFileDescriptor) { - UiThreadStatement.runOnUiThread { + private suspend fun openZimFileInReader(assetFileDescriptor: AssetFileDescriptor) { + withContext(Dispatchers.Main) { val navHostFragment: NavHostFragment = customMainActivity.supportFragmentManager .findFragmentById( @@ -266,7 +274,9 @@ class SearchFragmentTestForCustomApp { ) as NavHostFragment val customReaderFragment = navHostFragment.childFragmentManager.fragments[0] as CustomReaderFragment - customReaderFragment.openZimFile(null, true, listOf(assetFileDescriptor)) + withContext(Dispatchers.IO) { + customReaderFragment.openZimFile(null, true, listOf(assetFileDescriptor)) + } } } From bd518f002189cbe77f9fc7048964db0195d47777 Mon Sep 17 00:00:00 2001 From: CalebK Date: Mon, 15 Jul 2024 18:06:35 +0300 Subject: [PATCH 5/9] Passes scope to the test classes to fix errors --- .../org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt index 4810efdc5f..596914b914 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt @@ -22,6 +22,7 @@ import android.app.Application import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.net.NetworkCapabilities.TRANSPORT_WIFI +import androidx.lifecycle.viewModelScope import com.jraska.livedata.test import io.mockk.clearAllMocks import io.mockk.every @@ -470,7 +471,7 @@ class ZimManageViewModelTest { FileSelectListState(listOf(selectedBook, bookOnDisk()), NORMAL) viewModel.sideEffects.test() .also { viewModel.fileSelectActions.offer(RequestDeleteMultiSelection) } - .assertValues(DeleteFiles(listOf(selectedBook))) + .assertValues(DeleteFiles(listOf(selectedBook), viewModel.viewModelScope)) } @Test From b3e4ef226a7549a48a82221b6fefa16d431313ed Mon Sep 17 00:00:00 2001 From: CalebK Date: Wed, 17 Jul 2024 08:29:01 +0300 Subject: [PATCH 6/9] fixed compile time errors --- .../java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt index 774f609551..a6e95f46d2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/reader/ZimReaderContainer.kt @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.reader import android.content.res.AssetFileDescriptor import android.webkit.WebResourceResponse import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.kiwix.kiwixmobile.core.extensions.isFileExist import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Factory From 10a813e3fca098d8f7ec03e060a1da8eb26db695 Mon Sep 17 00:00:00 2001 From: CalebK Date: Wed, 17 Jul 2024 08:30:26 +0300 Subject: [PATCH 7/9] set setZimFile to null when restoring the tabs --- .../core/main/CoreReaderFragment.kt | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) 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 da61ebec2e..f7a9bcf86a 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 @@ -1271,10 +1271,8 @@ abstract class CoreReaderFragment : it.bringToFront() Snackbar.make(it, R.string.tab_closed, Snackbar.LENGTH_LONG) .setAction(R.string.undo) { undoButton -> - lifecycleScope.launch { - undoButton.isEnabled = false - restoreDeletedTab(index) - } + undoButton.isEnabled = false + restoreDeletedTab(index) }.show() } openHomeScreen() @@ -1299,7 +1297,7 @@ abstract class CoreReaderFragment : zimReaderContainer?.setZimFile(null) } - private suspend fun restoreDeletedTab(index: Int) { + private fun restoreDeletedTab(index: Int) { if (webViewList.isEmpty()) { reopenBook() } @@ -1314,7 +1312,6 @@ abstract class CoreReaderFragment : LinearLayout.LayoutParams.MATCH_PARENT ) } - zimReaderContainer?.setZimFile(tempZimFileForUndo) webViewList.add(index, it) tabsAdapter?.notifyDataSetChanged() snackBarRoot?.let { root -> @@ -1718,11 +1715,9 @@ abstract class CoreReaderFragment : root.bringToFront() Snackbar.make(root, R.string.tabs_closed, Snackbar.LENGTH_LONG).apply { setAction(R.string.undo) { - lifecycleScope.launch { - it.isEnabled = false // to prevent multiple clicks on this button - setIsCloseAllTabButtonClickable(true) - restoreDeletedTabs() - } + it.isEnabled = false // to prevent multiple clicks on this button + setIsCloseAllTabButtonClickable(true) + restoreDeletedTabs() } show() } @@ -1733,9 +1728,8 @@ abstract class CoreReaderFragment : closeAllTabsButton?.isClickable = isClickable } - private suspend fun restoreDeletedTabs() { + private fun restoreDeletedTabs() { if (tempWebViewListForUndo.isNotEmpty()) { - zimReaderContainer?.setZimFile(tempZimFileForUndo) webViewList.addAll(tempWebViewListForUndo) tabsAdapter?.notifyDataSetChanged() snackBarRoot?.let { root -> From bc67ec9cf8118fac1110e004b6a420e058704190 Mon Sep 17 00:00:00 2001 From: CalebK Date: Thu, 18 Jul 2024 13:18:01 +0300 Subject: [PATCH 8/9] wrapped openPageInBookFromNavigationArguments and loadContent into try and catch blocks to monitor the errors --- .../destination/reader/KiwixReaderFragment.kt | 39 +++++++++++-------- .../kiwixmobile/core/reader/ZimFileReader.kt | 33 +++++++++------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt index 9b789887b8..00d35ce99f 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/nav/destination/reader/KiwixReaderFragment.kt @@ -90,28 +90,33 @@ class KiwixReaderFragment : CoreReaderFragment() { } } + @Suppress("TooGenericExceptionCaught") private suspend fun openPageInBookFromNavigationArguments() { val args = KiwixReaderFragmentArgs.fromBundle(requireArguments()) - - if (args.pageUrl.isNotEmpty()) { - if (args.zimFileUri.isNotEmpty()) { - tryOpeningZimFile(args.zimFileUri) - } else { - // Set up bookmarks for the current book when opening bookmarks from the Bookmark screen. - // This is necessary because we are not opening the ZIM file again; the bookmark is - // inside the currently opened book. Bookmarks are set up when opening the ZIM file. - // See https://github.com/kiwix/kiwix-android/issues/3541 - zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) - } - loadUrlWithCurrentWebview(args.pageUrl) - } else { - if (args.zimFileUri.isNotEmpty()) { - tryOpeningZimFile(args.zimFileUri) + try { + if (args.pageUrl.isNotEmpty()) { + if (args.zimFileUri.isNotEmpty()) { + tryOpeningZimFile(args.zimFileUri) + } else { + // Set up bookmarks for the current book when opening bookmarks from the Bookmark screen. + // This is necessary because we are not opening the ZIM file again; the bookmark is + // inside the currently opened book. Bookmarks are set up when opening the ZIM file. + // See https://github.com/kiwix/kiwix-android/issues/3541 + zimReaderContainer?.zimFileReader?.let(::setUpBookmarks) + } + loadUrlWithCurrentWebview(args.pageUrl) } else { - manageExternalLaunchAndRestoringViewState() + if (args.zimFileUri.isNotEmpty()) { + tryOpeningZimFile(args.zimFileUri) + } else { + manageExternalLaunchAndRestoringViewState() + } } + } catch (error: Exception) { + Log.e("KiwixReader", "Error opening ZIM file", error) + } finally { + requireArguments().clear() } - requireArguments().clear() } private suspend fun tryOpeningZimFile(zimFileUri: String) { 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 43eb2b8024..ba2e6eff43 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 @@ -234,24 +234,29 @@ class ZimFileReader constructor( @Suppress("UnreachableCode", "NestedBlockDepth", "ReturnCount") private fun loadContent(uri: String, extension: String): InputStream? { - val item = getItem(uri) - if (compressedExtensions.any { it != extension }) { - item?.size?.let { - // Check if the item size exceeds 1 MB - if (it / Kb > 1024) { - // Retrieve direct access information for the item - val infoPair = getDirectAccessInfoOfItem(item, uri) - val file = infoPair?.filename?.let(::File) - // If no file found or file does not exist, return input stream from item data - if (infoPair == null || file == null || !file.exists()) { - return@loadContent ByteArrayInputStream(item.data?.data) + try { + val item = getItem(uri) + if (compressedExtensions.any { it != extension }) { + item?.size?.let { + // Check if the item size exceeds 1 MB + if (it / Kb > 1024) { + // Retrieve direct access information for the item + val infoPair = getDirectAccessInfoOfItem(item, uri) + val file = infoPair?.filename?.let(::File) + // If no file found or file does not exist, return input stream from item data + if (infoPair == null || file == null || !file.exists()) { + return@loadContent ByteArrayInputStream(item.data?.data) + } + // Return the input stream from the direct access information + return@loadContent getInputStreamFromDirectAccessInfo(item, file, infoPair) } - // Return the input stream from the direct access information - return@loadContent getInputStreamFromDirectAccessInfo(item, file, infoPair) } } + return loadContent(item, uri) + } catch (exception: Exception) { + Log.e("ZimFileReader", "exception caught while loading content$exception") + return null } - return loadContent(item, uri) } fun getMimeTypeFromUrl(uri: String): String? = getItem(uri)?.mimetype From eeb184ee5fedd23493301f2f5f5cb5fca10ce484 Mon Sep 17 00:00:00 2001 From: CalebK Date: Thu, 1 Aug 2024 13:37:50 +0300 Subject: [PATCH 9/9] Added loading functionality to the notes dialog --- .../kiwixmobile/core/page/PageFragment.kt | 22 +++++++++++++------ .../page/bookmark/viewmodel/BookmarkState.kt | 3 ++- .../bookmark/viewmodel/BookmarkViewModel.kt | 10 ++++++++- .../page/history/viewmodel/HistoryState.kt | 3 ++- .../history/viewmodel/HistoryViewModel.kt | 10 ++++++++- .../core/page/notes/viewmodel/NotesState.kt | 3 ++- .../page/notes/viewmodel/NotesViewModel.kt | 14 +++++++++--- .../viewmodel/effects/ShowOpenNoteDialog.kt | 4 ++++ .../kiwixmobile/core/page/viewmodel/Action.kt | 2 +- .../core/page/viewmodel/PageState.kt | 1 + .../core/page/viewmodel/PageViewModel.kt | 3 +++ core/src/main/res/layout/fragment_page.xml | 11 ++++++++++ 12 files changed, 70 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt index 825f9f56f4..5a9fb51b99 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/PageFragment.kt @@ -195,14 +195,22 @@ abstract class PageFragment : OnItemClickListener, BaseFragment(), FragmentActiv pageAdapter.items = state.visiblePageItems fragmentPageBinding?.pageSwitch?.isEnabled = !state.isInSelectionState fragmentPageBinding?.noPage?.visibility = if (state.pageItems.isEmpty()) VISIBLE else GONE - if (state.isInSelectionState) { - if (actionMode == null) { - actionMode = - (requireActivity() as AppCompatActivity).startSupportActionMode(actionModeCallback) + when { + state.isInSelectionState -> { + if (actionMode == null) { + actionMode = + (requireActivity() as AppCompatActivity).startSupportActionMode(actionModeCallback) + } + actionMode?.title = getString(R.string.selected_items, state.numberOfSelectedItems()) + } + + state.isLoading -> { + fragmentPageBinding?.loadingZimfileContent?.visibility = View.VISIBLE + } + + else -> { + actionMode?.finish() } - actionMode?.title = getString(R.string.selected_items, state.numberOfSelectedItems()) - } else { - actionMode?.finish() } } 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 aea012598e..09ed196162 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 @@ -26,7 +26,8 @@ data class BookmarkState( override val pageItems: List, override val showAll: Boolean, override val currentZimId: String?, - override val searchTerm: String = "" + override val searchTerm: String = "", + override val isLoading: Boolean ) : PageState() { override val visiblePageItems: List = filteredPageItems 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 f491ddc782..cee6592faa 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 @@ -40,7 +40,15 @@ class BookmarkViewModel @Inject constructor( ) { override fun initialState(): BookmarkState = - BookmarkState(emptyList(), sharedPreferenceUtil.showBookmarksAllBooks, zimReaderContainer.id) + BookmarkState( + emptyList(), + sharedPreferenceUtil.showBookmarksAllBooks, + zimReaderContainer.id, + isLoading = false + ) + + override fun loadData(state: BookmarkState, action: Action.LoadingData): BookmarkState = + state.copy(isLoading = action.isLoading) override fun updatePagesBasedOnFilter( state: BookmarkState, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryState.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryState.kt index 10ea0b37a8..fa925723b2 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryState.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryState.kt @@ -28,7 +28,8 @@ data class HistoryState( override val pageItems: List, override val showAll: Boolean, override val currentZimId: String?, - override val searchTerm: String = "" + override val searchTerm: String = "", + override val isLoading: Boolean ) : PageState() { override val visiblePageItems: List = HeaderizableList(filteredPageItems) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryViewModel.kt index 5a9493cdd4..04892bb173 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/history/viewmodel/HistoryViewModel.kt @@ -36,7 +36,15 @@ class HistoryViewModel @Inject constructor( ) : PageViewModel(historyRoomDao, sharedPrefs, zimReaderContainer) { override fun initialState(): HistoryState = - HistoryState(emptyList(), sharedPreferenceUtil.showHistoryAllBooks, zimReaderContainer.id) + HistoryState( + emptyList(), + sharedPreferenceUtil.showHistoryAllBooks, + zimReaderContainer.id, + isLoading = false + ) + + override fun loadData(state: HistoryState, action: Action.LoadingData): HistoryState = + state.copy(isLoading = action.isLoading) override fun updatePagesBasedOnFilter( state: HistoryState, diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesState.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesState.kt index 948926b77e..286e35ca40 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesState.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesState.kt @@ -26,7 +26,8 @@ data class NotesState( override val pageItems: List, override val showAll: Boolean, override val currentZimId: String?, - override val searchTerm: String = "" + override val searchTerm: String = "", + override val isLoading: Boolean, ) : PageState() { override val visiblePageItems: List = filteredPageItems diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt index f407d018a2..d4915e548c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/NotesViewModel.kt @@ -1,6 +1,6 @@ /* * Kiwix Android - * Copyright (c) 2020 Kiwix + * Copyright (c) 2024 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 @@ -45,7 +45,15 @@ class NotesViewModel @Inject constructor( } override fun initialState(): NotesState = - NotesState(emptyList(), sharedPreferenceUtil.showNotesAllBooks, zimReaderContainer.id) + NotesState( + emptyList(), + sharedPreferenceUtil.showNotesAllBooks, + zimReaderContainer.id, + isLoading = false + ) + + override fun loadData(state: NotesState, action: Action.LoadingData): NotesState = + state.copy(isLoading = action.isLoading) override fun updatePagesBasedOnFilter(state: NotesState, action: Action.Filter): NotesState = state.copy(searchTerm = action.searchTerm) @@ -71,5 +79,5 @@ class NotesViewModel @Inject constructor( ShowDeleteNotesDialog(effects, state, pageDao, viewModelScope) override fun onItemClick(page: Page) = - ShowOpenNoteDialog(effects, page, zimReaderContainer, viewModelScope) + ShowOpenNoteDialog(effects, actions, page, zimReaderContainer, viewModelScope) } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt index e1b533d538..637912bfbe 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/notes/viewmodel/effects/ShowOpenNoteDialog.kt @@ -27,6 +27,7 @@ import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.cachedComponent import org.kiwix.kiwixmobile.core.page.adapter.Page import org.kiwix.kiwixmobile.core.page.notes.adapter.NoteListItem +import org.kiwix.kiwixmobile.core.page.viewmodel.Action import org.kiwix.kiwixmobile.core.page.viewmodel.effects.OpenNote import org.kiwix.kiwixmobile.core.page.viewmodel.effects.OpenPage import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Companion.CONTENT_PREFIX @@ -43,6 +44,7 @@ import javax.inject.Inject data class ShowOpenNoteDialog( private val effects: PublishProcessor>, + private val actions: PublishProcessor, private val page: Page, private val zimReaderContainer: ZimReaderContainer, private val dialogScope: CoroutineScope @@ -54,6 +56,7 @@ data class ShowOpenNoteDialog( ShowNoteDialog, { effects.offer(OpenPage(page, zimReaderContainer)) }, { + actions.offer(Action.LoadingData(true)) dialogScope.launch { val item = page as NoteListItem // Check if zimFilePath is not null, and then set it in zimReaderContainer. @@ -84,6 +87,7 @@ data class ShowOpenNoteDialog( } } effects.offer(OpenNote(item.noteFilePath, item.zimUrl, item.title)) + actions.offer(Action.LoadingData(false)) } } ) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/Action.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/Action.kt index 428d02275b..33efc5598b 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/Action.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/Action.kt @@ -7,7 +7,7 @@ sealed class Action { object ExitActionModeMenu : Action() object UserClickedDeleteButton : Action() object UserClickedDeleteSelectedPages : Action() - + data class LoadingData(val isLoading: Boolean) : Action() data class OnItemClick(val page: Page) : Action() data class OnItemLongClick(val page: Page) : Action() data class UserClickedShowAllToggle(val isChecked: Boolean) : Action() 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 05decb8746..45ab22c72e 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 @@ -24,6 +24,7 @@ import org.kiwix.kiwixmobile.core.page.bookmark.adapter.LibkiwixBookmarkItem abstract class PageState { abstract val pageItems: List + abstract val isLoading: Boolean val isInSelectionState: Boolean by lazy { pageItems.any(Page::isSelected) } protected val filteredPageItems: List by lazy { pageItems.filter { showAll || it.zimId == currentZimId } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModel.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModel.kt index 95379f6c3e..ff9403d978 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModel.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModel.kt @@ -88,8 +88,11 @@ abstract class PageViewModel>( is OnItemLongClick -> handleItemLongClick(state, action) is Filter -> updatePagesBasedOnFilter(state, action) is UpdatePages -> updatePages(state, action) + is Action.LoadingData -> loadData(state, action) } + abstract fun loadData(state: S, action: Action.LoadingData): S + abstract fun updatePagesBasedOnFilter(state: S, action: Filter): S abstract fun updatePages(state: S, action: UpdatePages): S diff --git a/core/src/main/res/layout/fragment_page.xml b/core/src/main/res/layout/fragment_page.xml index bdb365b429..6a14b36c8b 100644 --- a/core/src/main/res/layout/fragment_page.xml +++ b/core/src/main/res/layout/fragment_page.xml @@ -38,6 +38,17 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/app_bar" /> +