diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt index 7574ffb04..0156bc3bc 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt @@ -57,6 +57,7 @@ import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.Re import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestOpen import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RestartActionMode import org.kiwix.kiwixmobile.zim_manager.fileselect_view.FileSelectListState import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.None @@ -93,6 +94,7 @@ class ZimManageViewModel @Inject constructor( object RequestDeleteMultiSelection : FileSelectActions() object RequestShareMultiSelection : FileSelectActions() object MultiModeFinished : FileSelectActions() + object RestartActionMode : FileSelectActions() } val sideEffects = PublishProcessor.create>() @@ -150,6 +152,7 @@ class ZimManageViewModel @Inject constructor( RequestShareMultiSelection -> ShareFiles(selectionsFromState()) MultiModeFinished -> noSideEffectAndClearSelectionState() is RequestSelect -> noSideEffectSelectBook(it.bookOnDisk) + RestartActionMode -> StartMultiSelection(fileSelectActions) } ) }, Throwable::printStackTrace) @@ -165,7 +168,7 @@ class ZimManageViewModel @Inject constructor( ) ) } - return StartMultiSelection(bookOnDisk, fileSelectActions) + return StartMultiSelection(fileSelectActions) } private fun selectBook( @@ -181,10 +184,7 @@ class ZimManageViewModel @Inject constructor( private fun noSideEffectSelectBook(bookOnDisk: BookOnDisk): SideEffect { fileSelectListStates.value?.let { fileSelectListStates.postValue( - it.copy(bookOnDiskListItems = it.bookOnDiskListItems.map { listItem -> - if (listItem.id == bookOnDisk.id) listItem.apply { isSelected = !isSelected } - else listItem - }) + it.copy(bookOnDiskListItems = selectBook(it, bookOnDisk)) ) } return None diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt index 6c9b02872..25190eecb 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt @@ -23,11 +23,11 @@ import android.content.pm.PackageManager import android.os.Build.VERSION import android.os.Build.VERSION_CODES import android.os.Bundle -import android.view.ActionMode import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ActionMode import androidx.core.content.ContextCompat import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -56,6 +56,8 @@ import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.Re import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect import javax.inject.Inject +private const val WAS_IN_ACTION_MODE = "WAS_IN_ACTION_MODE" + class ZimFileSelectFragment : BaseFragment() { @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil @@ -108,6 +110,9 @@ class ZimFileSelectFragment : BaseFragment() { zimManageViewModel.deviceListIsRefreshing.observe(this, Observer { zim_swiperefresh.isRefreshing = it!! }) + if (savedInstanceState != null && savedInstanceState.getBoolean(WAS_IN_ACTION_MODE)) { + zimManageViewModel.fileSelectActions.offer(FileSelectActions.RestartActionMode) + } } private fun sideEffects() = zimManageViewModel.sideEffects.subscribe( @@ -132,6 +137,11 @@ class ZimFileSelectFragment : BaseFragment() { checkPermissions() } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean(WAS_IN_ACTION_MODE, actionMode != null) + } + override fun onDestroy() { super.onDestroy() disposable.clear() diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt index 60ab7e741..190372a54 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt @@ -31,7 +31,7 @@ import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDis import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity import javax.inject.Inject -class DeleteFiles(private val booksOnDiskListItem: List) : +data class DeleteFiles(private val booksOnDiskListItem: List) : SideEffect { @Inject lateinit var dialogShower: DialogShower diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt index dcb7a1e54..350cbb51a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt @@ -26,7 +26,7 @@ import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.main.KiwixMainActivity -class OpenFile(private val bookOnDisk: BookOnDisk) : +data class OpenFile(private val bookOnDisk: BookOnDisk) : SideEffect { override fun invokeWith(activity: AppCompatActivity) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt index be9f8ba56..f815b3ef3 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt @@ -27,7 +27,7 @@ import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk -class ShareFiles(private val selectedBooks: List) : +data class ShareFiles(private val selectedBooks: List) : SideEffect { override fun invokeWith(activity: AppCompatActivity) { val selectedFileShareIntent = Intent() diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt index efa311d1b..1caece9d3 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt @@ -24,21 +24,20 @@ import io.reactivex.processors.PublishProcessor import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.startActionMode -import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.MultiModeFinished import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection data class StartMultiSelection( - val bookOnDisk: BooksOnDiskListItem.BookOnDisk, val fileSelectActions: PublishProcessor ) : SideEffect { - override fun invokeWith(activity: AppCompatActivity) = + override fun invokeWith(activity: AppCompatActivity): ActionMode? = activity.startActionMode( R.menu.menu_zim_files_contextual, mapOf( R.id.zim_file_delete_item to { fileSelectActions.offer(RequestDeleteMultiSelection) }, R.id.zim_file_share_item to { fileSelectActions.offer(RequestShareMultiSelection) } ) - ) { fileSelectActions.offer(FileSelectActions.MultiModeFinished) } + ) { fileSelectActions.offer(MultiModeFinished) } } diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt index 2451d83a8..ec7eb8ef2 100644 --- a/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt +++ b/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt @@ -44,6 +44,8 @@ import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.core.utils.BookUtils import org.kiwix.kiwixmobile.core.zim_manager.Language +import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode.MULTI +import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode.NORMAL import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState @@ -51,7 +53,19 @@ import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4G import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.MultiModeFinished +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestMultiSelection +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestOpen +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RestartActionMode import org.kiwix.kiwixmobile.zim_manager.fileselect_view.FileSelectListState +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.None +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.OpenFile +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.ShareFiles +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.StartMultiSelection import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem import org.kiwix.sharedFunctions.InstantExecutorExtension import org.kiwix.sharedFunctions.book @@ -406,4 +420,101 @@ class ZimManageViewModelTest { ) ) } + + @Nested + inner class SideEffects { + @Test + fun `RequestOpen offers OpenFile`() { + val bookOnDisk = bookOnDisk() + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(RequestOpen(bookOnDisk)) } + .assertValues(OpenFile(bookOnDisk)) + } + + @Test + fun `RequestMultiSelection offers StartMultiSelection and selects a book`() { + val bookToSelect = bookOnDisk(databaseId = 0L) + val unSelectedBook = bookOnDisk(databaseId = 1L) + viewModel.fileSelectListStates.value = FileSelectListState( + listOf( + bookToSelect, + unSelectedBook + ), + NORMAL + ) + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(RequestMultiSelection(bookToSelect)) } + .assertValues(StartMultiSelection(viewModel.fileSelectActions)) + viewModel.fileSelectListStates.test() + .assertValue( + FileSelectListState( + listOf(bookToSelect.apply { isSelected = !isSelected }, unSelectedBook), + MULTI + ) + ) + } + + @Test + fun `RequestDeleteMultiSelection offers DeleteFiles with selected books`() { + val selectedBook = bookOnDisk().apply { isSelected = true } + viewModel.fileSelectListStates.value = + FileSelectListState(listOf(selectedBook, bookOnDisk()), NORMAL) + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(RequestDeleteMultiSelection) } + .assertValues(DeleteFiles(listOf(selectedBook))) + } + + @Test + fun `RequestShareMultiSelection offers ShareFiles with selected books`() { + val selectedBook = bookOnDisk().apply { isSelected = true } + viewModel.fileSelectListStates.value = + FileSelectListState(listOf(selectedBook, bookOnDisk()), NORMAL) + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(RequestShareMultiSelection) } + .assertValues(ShareFiles(listOf(selectedBook))) + } + + @Test + fun `MultiModeFinished offers None`() { + val selectedBook = bookOnDisk().apply { isSelected = true } + viewModel.fileSelectListStates.value = + FileSelectListState(listOf(selectedBook, bookOnDisk()), NORMAL) + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(MultiModeFinished) } + .assertValues(None) + viewModel.fileSelectListStates.test().assertValue( + FileSelectListState( + listOf( + selectedBook.apply { isSelected = false }, + bookOnDisk() + ) + ) + ) + } + + @Test + fun `RequestSelect offers None and inverts selection`() { + val selectedBook = bookOnDisk(0L).apply { isSelected = true } + viewModel.fileSelectListStates.value = + FileSelectListState(listOf(selectedBook, bookOnDisk(1L)), NORMAL) + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(RequestSelect(selectedBook)) } + .assertValues(None) + viewModel.fileSelectListStates.test().assertValue( + FileSelectListState( + listOf( + selectedBook.apply { isSelected = false }, + bookOnDisk(1L) + ) + ) + ) + } + + @Test + fun `RestartActionMode offers StartMultiSelection`() { + viewModel.sideEffects.test() + .also { viewModel.fileSelectActions.offer(RestartActionMode) } + .assertValues(StartMultiSelection(viewModel.fileSelectActions)) + } + } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt index ea5faba81..bd85fc5cd 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt @@ -38,28 +38,19 @@ object ActivityExtensions { onDestroyAction: () -> Unit ): ActionMode? { return startSupportActionMode(object : ActionMode.Callback { - override fun onActionItemClicked( - mode: ActionMode, - item: MenuItem - ) = idsToClickActions[item.itemId]?.let { - it() - mode.finish() - true - } ?: false + override fun onActionItemClicked(mode: ActionMode, item: MenuItem) = + idsToClickActions[item.itemId]?.let { + it() + mode.finish() + true + } ?: false - override fun onCreateActionMode( - mode: ActionMode, - menu: Menu? - ): Boolean { - mode.menuInflater - .inflate(menuId, menu) + override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { + mode.menuInflater.inflate(menuId, menu) return true } - override fun onPrepareActionMode( - mode: ActionMode?, - menu: Menu? - ) = false + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false override fun onDestroyActionMode(mode: ActionMode?) { onDestroyAction()