#2119 re-re-write of History activity

This commit is contained in:
Frans-Lukas Lövenvald 2020-06-08 15:34:38 +02:00
parent 935536b027
commit 0b699fd89d
14 changed files with 270 additions and 238 deletions

View File

@ -67,10 +67,9 @@ class Repository @Inject internal constructor(
override fun booksOnDiskAsListItems(): Flowable<List<BooksOnDiskListItem>> = bookDao.books()
.map { it.sortedBy { bookOnDisk -> bookOnDisk.book.language + bookOnDisk.book.title } }
.map {
HeaderizableList(it as List<BooksOnDiskListItem>).foldOverAddingHeaders(
{ bookOnDisk -> LanguageItem((bookOnDisk as BookOnDisk).locale) },
{ current, next ->
(current as BookOnDisk).locale.displayName != (next as BookOnDisk).locale.displayName })
HeaderizableList<BooksOnDiskListItem, BookOnDisk, LanguageItem>(it).foldOverAddingHeaders(
{ bookOnDisk -> LanguageItem(bookOnDisk.locale) },
{ current, next -> current.locale.displayName != next.locale.displayName })
}
.map { it.toList() }
@ -93,11 +92,12 @@ class Repository @Inject internal constructor(
zimReaderContainer.zimCanonicalPath
)
).map {
HeaderizableList(it as List<HistoryListItem>).foldOverAddingHeaders(
{ historyItem -> DateItem((historyItem as HistoryItem).dateString) },
{ current, next ->
(current as HistoryItem).dateString != (next as HistoryItem).dateString })
}
HeaderizableList<HistoryListItem, HistoryItem, DateItem>(it).foldOverAddingHeaders(
{ historyItem -> DateItem(historyItem.dateString) },
{ current, next ->
current.dateString != next.dateString
})
}
.subscribeOn(io)
.observeOn(mainThread)

View File

@ -1,6 +1,6 @@
package org.kiwix.kiwixmobile.core.extensions
inline class HeaderizableList<SUPERTYPE, ITEM : SUPERTYPE, HEADER : SUPERTYPE>(
inline class HeaderizableList<SUPERTYPE, out ITEM : SUPERTYPE, in HEADER : SUPERTYPE>(
val list: List<ITEM>
) {
fun foldOverAddingHeaders(
@ -13,7 +13,7 @@ inline class HeaderizableList<SUPERTYPE, ITEM : SUPERTYPE, HEADER : SUPERTYPE>(
}
acc.add(currentItem)
if (index < list.size - 1) {
val nextItem = list.get(index + 1)
val nextItem = list[index + 1]
if (criteriaToAddHeader.invoke(currentItem, nextItem)) {
acc.add(headerConstructor.invoke(nextItem))
}

View File

@ -18,7 +18,6 @@ import kotlinx.android.synthetic.main.activity_history.no_history
import kotlinx.android.synthetic.main.activity_history.recycler_view
import kotlinx.android.synthetic.main.layout_toolbar.toolbar
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.R.id
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.di.components.CoreComponent
@ -33,12 +32,11 @@ import org.kiwix.kiwixmobile.core.history.viewmodel.Action
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ExitHistory
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.RequestDeleteAllHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.RequestDeleteSelectedHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ToggleShowHistoryFromAllBooks
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDeleteButton
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDeleteSelectedHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.HistoryViewModel
import org.kiwix.kiwixmobile.core.history.viewmodel.State
import org.kiwix.kiwixmobile.core.history.viewmodel.State.NoResults
import org.kiwix.kiwixmobile.core.history.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.history.viewmodel.State.SelectionResults
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
@ -60,13 +58,11 @@ class HistoryActivity : OnItemClickListener, BaseActivity() {
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return false
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
if (item.itemId == R.id.menu_context_delete) {
historyViewModel.actions.offer(RequestDeleteSelectedHistoryItems)
historyViewModel.actions.offer(UserClickedDeleteSelectedHistoryItems)
return true
}
historyViewModel.actions.offer(Action.ExitActionModeMenu)
@ -101,7 +97,7 @@ class HistoryActivity : OnItemClickListener, BaseActivity() {
history_switch.setOnCheckedChangeListener { _, isChecked ->
historyViewModel.actions.offer(ToggleShowHistoryFromAllBooks(isChecked))
}
history_switch.isChecked = !sharedPreferenceUtil.showHistoryCurrentBook
history_switch.isChecked = !sharedPreferenceUtil.showHistoryAllBooks
compositeDisposable.add(historyViewModel.effects.subscribe { it.invokeWith(this) })
}
@ -127,7 +123,7 @@ class HistoryActivity : OnItemClickListener, BaseActivity() {
historyViewModel.actions.offer(ExitHistory)
}
if (item.itemId == R.id.menu_history_clear) {
historyViewModel.actions.offer(RequestDeleteAllHistoryItems)
historyViewModel.actions.offer(UserClickedDeleteButton)
}
return super.onOptionsItemSelected(item)
}
@ -136,23 +132,18 @@ class HistoryActivity : OnItemClickListener, BaseActivity() {
when (state) {
is Results -> {
actionMode?.finish()
state.historyItems?.let { historyAdapter.items = it }
state.historyListItems.let { historyAdapter.items = it }
history_switch.isEnabled = true
no_history.visibility = View.GONE
}
is SelectionResults -> {
if (state.historyItems?.filterIsInstance<HistoryItem>()?.any { it.isSelected } == true &&
actionMode == null) {
if (state.historyItems.any(HistoryItem::isSelected) && actionMode == null) {
actionMode = startSupportActionMode(actionModeCallback)
}
state.historyItems?.let { historyAdapter.items = it }
state.historyListItems.let { historyAdapter.items = it }
history_switch.isEnabled = false
no_history.visibility = View.GONE
}
is NoResults -> {
state.historyItems?.let { historyAdapter.items = it }
no_history.visibility = View.VISIBLE
}
}
override fun onItemClick(favicon: ImageView, history: HistoryItem) {

View File

@ -18,18 +18,19 @@
package org.kiwix.kiwixmobile.core.history.viewmodel
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.HistoryItem
sealed class Action {
object ExitHistory : Action()
object ExitActionModeMenu : Action()
object DeleteHistoryItems : Action()
object RequestDeleteAllHistoryItems : Action()
object RequestDeleteSelectedHistoryItems : Action()
object UserClickedDelete : Action()
object UserClickedDeleteButton : Action()
object UserClickedDeleteSelectedHistoryItems : Action()
data class OnItemClick(val historyListItem: HistoryListItem) : Action()
data class OnItemClick(val historyItem: HistoryItem) : Action()
data class OnItemLongClick(val historyItem: HistoryItem) : Action()
data class ToggleShowHistoryFromAllBooks(val isChecked: Boolean) : Action()
data class AllHistoryPreferenceChanged(val showAll: Boolean) : Action()
data class Filter(val searchTerm: String) : Action()
data class UpdateHistory(val history: List<HistoryItem>) : Action()
}

View File

@ -1,41 +1,31 @@
package org.kiwix.kiwixmobile.core.history.viewmodel
import DeleteSelectedOrAllHistoryItems
import OpenHistoryItem
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.Flowable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.Function3
import io.reactivex.processors.BehaviorProcessor
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.HistoryDao
import org.kiwix.kiwixmobile.core.extensions.HeaderizableList
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.DateItem
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.DeleteHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.AllHistoryPreferenceChanged
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ExitActionModeMenu
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ExitHistory
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.RequestDeleteAllHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.RequestDeleteSelectedHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ToggleShowHistoryFromAllBooks
import org.kiwix.kiwixmobile.core.history.viewmodel.State.NoResults
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UpdateHistory
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDelete
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDeleteButton
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDeleteSelectedHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.history.viewmodel.State.SelectionResults
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.DeleteHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.ShowDeleteHistoryDialog
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.ToggleShowAllHistorySwitchAndSaveItsStateToPrefs
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.UpdateAllHistoryPreference
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteAllHistory
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteSelectedHistory
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import java.text.SimpleDateFormat
import java.util.Locale
import javax.inject.Inject
class HistoryViewModel @Inject constructor(
@ -43,133 +33,142 @@ class HistoryViewModel @Inject constructor(
private val zimReaderContainer: ZimReaderContainer,
private val sharedPreferenceUtil: SharedPreferenceUtil
) : ViewModel() {
val state = MutableLiveData<State>().apply { value = NoResults(emptyList()) }
val state = MutableLiveData<State>().apply {
value = Results(emptyList(), sharedPreferenceUtil.showHistoryAllBooks)
}
val effects = PublishProcessor.create<SideEffect<*>>()
val actions = PublishProcessor.create<Action>()
private val filter = BehaviorProcessor.createDefault("")
private val compositeDisposable = CompositeDisposable()
val showAllSwitchToggle =
BehaviorProcessor.createDefault(!sharedPreferenceUtil.showHistoryCurrentBook)
private val dateFormatter = SimpleDateFormat("d MMM yyyy", Locale.getDefault())
private val deselectAllItems = BehaviorProcessor.createDefault(false)
init {
compositeDisposable.addAll(
viewStateReducer(),
actionMapper(),
deselectAllItems()
historyDao.history().subscribe { actions.offer(UpdateHistory(it)) },
sharedPreferenceUtil.showAllHistoryToggleSwitch.subscribe {
actions.offer(AllHistoryPreferenceChanged(it))
},
viewStateReducer()
)
}
private fun deselectAllItems() = deselectAllItems.subscribe {
state.value?.historyItems?.filterIsInstance<HistoryItem>()?.forEach { it.isSelected = false }
}
private fun viewStateReducer() =
Flowable.combineLatest(
filter,
showAllSwitchToggle,
historyDao.history(),
Function3(::searchResults)
).subscribe { state.postValue(updateResultsState(it)) }
actions.map { reduce(it, state.value!!) }.subscribe(state::postValue)
private fun updateResultsState(
historyListWithDateItems: List<HistoryListItem>
private fun reduce(action: Action, state: State): State =
when (action) {
ExitHistory -> finishHistoryActivity(state)
ExitActionModeMenu -> deselectAllHistoryItems(state)
UserClickedDelete -> offerDeletionOfItems(state)
UserClickedDeleteButton -> offerShowDeleteDialog(state)
is OnItemClick -> handleItemClick(state, action)
is OnItemLongClick -> handleItemLongClick(state, action)
is ToggleShowHistoryFromAllBooks -> offerUpdateToShowAllToggle(action, state)
is Filter -> updateHistoryItemsBasedOnFilter(state, action)
is UpdateHistory -> updateHistoryList(state, action)
UserClickedDeleteSelectedHistoryItems -> offerShowDeleteDialog(state)
is AllHistoryPreferenceChanged -> changeShowHistoryToggle(state, action)
}
private fun updateHistoryItemsBasedOnFilter(state: State, action: Filter) =
when (state) {
is Results -> state.copy(searchTerm = action.searchTerm)
is SelectionResults -> state.copy(searchTerm = action.searchTerm)
}
private fun changeShowHistoryToggle(
state: State,
action: AllHistoryPreferenceChanged
): State {
return when {
historyListWithDateItems.isEmpty() -> NoResults(historyListWithDateItems)
historyListWithDateItems.filterIsInstance<HistoryItem>()
.any { it.isSelected } -> SelectionResults(
historyListWithDateItems
)
else -> Results(historyListWithDateItems)
return when (state) {
is SelectionResults -> state
is Results -> state.copy(showAll = action.showAll)
}
}
private fun searchResults(
searchString: String,
showAllToggle: Boolean,
historyList: List<HistoryListItem>
): List<HistoryListItem> = HeaderizableList(historyList
.filterIsInstance<HistoryItem>()
.filter { h ->
h.historyTitle.contains(searchString, true) &&
(h.zimName == zimReaderContainer.name || showAllToggle)
private fun updateHistoryList(
state: State,
action: UpdateHistory
): State {
return when (state) {
is Results -> state.copy(historyItems = action.history)
is SelectionResults -> {
val selectedItems = state.selectedItems
state.copy(historyItems = action.history.map {
if (selectedItems.contains(it)) it.copy(isSelected = true) else it
})
}
.sortedByDescending { dateFormatter.parse(it.dateString) } as List<HistoryListItem>)
.foldOverAddingHeaders(
{ historyItem -> DateItem((historyItem as HistoryItem).dateString) },
{ current, next -> (current as HistoryItem).dateString != (next as HistoryItem).dateString }
}
}
private fun offerUpdateToShowAllToggle(
action: ToggleShowHistoryFromAllBooks,
state: State
): State {
effects.offer(
UpdateAllHistoryPreference(
sharedPreferenceUtil,
action.isChecked
)
)
return state
}
private fun handleItemLongClick(
state: State,
action: OnItemLongClick
): State {
return when (state) {
is Results -> state.toggleSelectionOfItem(action.historyItem)
else -> state
}
}
private fun handleItemClick(
state: State,
action: OnItemClick
): State {
return when (state) {
is Results -> {
effects.offer(OpenHistoryItem(action.historyItem, zimReaderContainer))
state
}
is SelectionResults -> state.toggleSelectionOfItem(action.historyItem)
}
}
private fun offerShowDeleteDialog(state: State): State {
effects.offer(ShowDeleteHistoryDialog(actions))
return state
}
private fun offerDeletionOfItems(state: State): State {
return when (state) {
is Results -> {
effects.offer(DeleteHistoryItems(state.historyItems, historyDao))
state
}
is SelectionResults -> {
effects.offer(DeleteHistoryItems(state.selectedItems, historyDao))
state
}
}
}
private fun deselectAllHistoryItems(state: State): State {
return when (state) {
is SelectionResults -> {
state.copy(historyItems = state.historyItems.map { it.copy(isSelected = false) })
}
else -> state
}
}
private fun finishHistoryActivity(state: State): State {
effects.offer(Finish)
return state
}
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
private fun toggleSelectionOfItem(historyItem: HistoryItem): State =
when (state.value) {
is Results -> SelectionResults(toggleGivenItemAndReturnListOfResultingItems(historyItem))
is SelectionResults -> {
if (toggleGivenItemAndReturnListOfResultingItems(historyItem)
?.filterIsInstance<HistoryItem>()
?.any { it.isSelected } == true) {
SelectionResults(toggleGivenItemAndReturnListOfResultingItems(historyItem))
} else {
Results(toggleGivenItemAndReturnListOfResultingItems(historyItem))
}
}
is NoResults -> NoResults(emptyList())
null -> NoResults(emptyList())
}
private fun toggleGivenItemAndReturnListOfResultingItems(historyItem: HistoryItem):
List<HistoryListItem>? {
return state.value
?.historyItems
?.map {
if (it.id == historyItem.id && it is HistoryItem)
it.copy(isSelected = !it.isSelected) else it
}
}
private fun actionMapper() = actions.map {
when (it) {
ExitHistory -> effects.offer(Finish)
is Filter -> filter.offer(it.searchTerm)
is ToggleShowHistoryFromAllBooks ->
toggleShowAllHistorySwitchAndSaveItsStateToPrefs(it.isChecked)
is OnItemLongClick -> state.postValue(toggleSelectionOfItem(it.historyItem))
is OnItemClick -> appendItemToSelectionOrOpenIt(it)
is RequestDeleteAllHistoryItems ->
effects.offer(ShowDeleteHistoryDialog(actions, DeleteAllHistory))
is RequestDeleteSelectedHistoryItems ->
effects.offer(ShowDeleteHistoryDialog(actions, DeleteSelectedHistory))
ExitActionModeMenu -> state.postValue(Results(
state.value
?.historyItems
?.map { item -> if (item is HistoryItem) item.copy(isSelected = false) else item })
)
DeleteHistoryItems -> effects.offer(DeleteSelectedOrAllHistoryItems(state, historyDao))
}
}.subscribe({}, Throwable::printStackTrace)
private fun toggleShowAllHistorySwitchAndSaveItsStateToPrefs(isChecked: Boolean) {
effects.offer(
ToggleShowAllHistorySwitchAndSaveItsStateToPrefs(
showAllSwitchToggle,
sharedPreferenceUtil,
isChecked
)
)
}
private fun appendItemToSelectionOrOpenIt(onItemClick: OnItemClick) {
val historyItem = onItemClick.historyListItem as HistoryItem
if (state.value?.containsSelectedItems() == true) {
state.postValue(toggleSelectionOfItem(historyItem))
} else {
effects.offer(OpenHistoryItem(historyItem, zimReaderContainer))
}
}
}

View File

@ -18,26 +18,64 @@
package org.kiwix.kiwixmobile.core.history.viewmodel
import org.kiwix.kiwixmobile.core.extensions.HeaderizableList
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.DateItem
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.HistoryItem
import java.text.SimpleDateFormat
import java.util.Locale
sealed class State(
open val historyItems: List<HistoryListItem>?
open val historyItems: List<HistoryItem>,
open val showAll: Boolean,
open val searchTerm: String = ""
) {
fun containsSelectedItems(): Boolean {
return historyItems?.filterIsInstance<HistoryItem>()?.any { it.isSelected } == true
private val dateFormatter = SimpleDateFormat("d MMM yyyy", Locale.getDefault())
val historyListItems: List<HistoryListItem> by lazy {
HeaderizableList<HistoryListItem, HistoryItem, DateItem>(historyItems
.filter { it.historyTitle.contains(searchTerm, true) }
.sortedByDescending { dateFormatter.parse(it.dateString) })
.foldOverAddingHeaders(
{ historyItem -> DateItem(historyItem.dateString) },
{ current, next -> current.dateString != next.dateString }
)
}
data class Results(
override val historyItems: List<HistoryListItem>?
) : State(historyItems)
fun toggleSelectionOfItem(historyListItem: HistoryItem): State {
val newList = historyItems.map {
if (it.id == historyListItem.id) it.apply {
isSelected = !isSelected
} else it
}
if (newList.isEmpty()) {
return Results(newList, showAll)
}
return SelectionResults(newList, showAll, searchTerm)
}
data class NoResults(
override val historyItems: List<HistoryListItem>?
) : State(historyItems)
// fun filterBasedOnSearchTerm(searchTerm: String): State =
// copy(historyListItems = historyItems.filter {
// it.historyTitle.contains(
// searchTerm,
// true
// )
// })
data class Results(
override val historyItems: List<HistoryItem>,
override val showAll: Boolean,
override val searchTerm: String = ""
) : State(historyItems, showAll, searchTerm)
data class SelectionResults(
override val historyItems: List<HistoryListItem>?
) : State(historyItems)
override val historyItems: List<HistoryItem>,
override val showAll: Boolean,
override val searchTerm: String
) : State(historyItems, showAll, searchTerm) {
val selectedItems: List<HistoryItem> =
historyListItems.filterIsInstance<HistoryItem>().filter(HistoryItem::isSelected)
}
}

View File

@ -1,3 +1,5 @@
package org.kiwix.kiwixmobile.core.history.viewmodel.effects
/*
* Kiwix Android
* Copyright (c) 2020 Kiwix <android.kiwix.org>
@ -17,22 +19,15 @@
*/
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.HistoryDao
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.history.viewmodel.State
data class DeleteSelectedOrAllHistoryItems(
private val state: MutableLiveData<State>,
data class DeleteHistoryItems(
private val itemsToDelete: List<HistoryItem>,
private val historyDao: HistoryDao
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
val historyItems = state.value?.historyItems?.filterIsInstance<HistoryItem>()
if (historyItems?.any { it.isSelected } == true) {
historyDao.deleteHistory(historyItems.filter { it.isSelected })
} else if (historyItems != null) {
historyDao.deleteHistory(historyItems)
}
historyDao.deleteHistory(itemsToDelete)
}
}

View File

@ -23,20 +23,19 @@ import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.history.HistoryActivity
import org.kiwix.kiwixmobile.core.history.viewmodel.Action
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.DeleteHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDelete
import org.kiwix.kiwixmobile.core.utils.DialogShower
import org.kiwix.kiwixmobile.core.utils.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteAllHistory
import javax.inject.Inject
data class ShowDeleteHistoryDialog(
private val actions: PublishProcessor<Action>,
private val dialogType: KiwixDialog
private val actions: PublishProcessor<Action>
) : SideEffect<Unit> {
@Inject lateinit var dialogShower: DialogShower
override fun invokeWith(activity: AppCompatActivity) {
(activity as HistoryActivity).activityComponent.inject(this)
dialogShower.show(dialogType, {
actions.offer(DeleteHistoryItems)
dialogShower.show(DeleteAllHistory, {
actions.offer(UserClickedDelete)
})
}
}

View File

@ -1,17 +1,14 @@
package org.kiwix.kiwixmobile.core.history.viewmodel.effects
import androidx.appcompat.app.AppCompatActivity
import io.reactivex.processors.BehaviorProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
data class ToggleShowAllHistorySwitchAndSaveItsStateToPrefs(
private val showAllSwitchToggle: BehaviorProcessor<Boolean>,
data class UpdateAllHistoryPreference(
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val isChecked: Boolean
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
showAllSwitchToggle.offer(isChecked)
sharedPreferenceUtil.showHistoryCurrentBook = !isChecked
sharedPreferenceUtil.showHistoryAllBooks = !isChecked
}
}

View File

@ -55,11 +55,12 @@ public class SharedPreferenceUtil {
private static final String PREF_EXTERNAL_LINK_POPUP = "pref_external_link_popup";
private static final String PREF_IS_FIRST_RUN = "isFirstRun";
private static final String PREF_SHOW_BOOKMARKS_CURRENT_BOOK = "show_bookmarks_current_book";
private static final String PREF_SHOW_HISTORY_CURRENT_BOOK = "show_history_current_book";
private static final String PREF_SHOW_HISTORY_ALL_BOOKS = "show_history_current_book";
private static final String PREF_HOSTED_BOOKS = "hosted_books";
public static final String PREF_NIGHT_MODE = "pref_night_mode";
private SharedPreferences sharedPreferences;
private final PublishProcessor<String> prefStorages = PublishProcessor.create();
public final PublishProcessor<Boolean> showAllHistoryToggleSwitch = PublishProcessor.create();
private final PublishProcessor<NightModeConfig.Mode> nightModes = PublishProcessor.create();
@Inject
@ -152,6 +153,10 @@ public class SharedPreferenceUtil {
return prefStorages.startWith(getPrefStorage());
}
public Flowable<Boolean> getShowAllHistoryToggleSwitches() {
return showAllHistoryToggleSwitch.startWith(getShowHistoryAllBooks());
}
public void putPrefFullScreen(boolean fullScreen) {
sharedPreferences.edit().putBoolean(PREF_FULLSCREEN, fullScreen).apply();
}
@ -168,14 +173,15 @@ public class SharedPreferenceUtil {
sharedPreferences.edit().putBoolean(PREF_SHOW_INTRO, false).apply();
}
public boolean getShowHistoryCurrentBook() {
return sharedPreferences.getBoolean(PREF_SHOW_HISTORY_CURRENT_BOOK, true);
public boolean getShowHistoryAllBooks() {
return sharedPreferences.getBoolean(PREF_SHOW_HISTORY_ALL_BOOKS, true);
}
public void setShowHistoryCurrentBook(boolean prefShowHistoryCurrentBook) {
public void setShowHistoryAllBooks(boolean prefShowHistoryAllBooks) {
sharedPreferences.edit()
.putBoolean(PREF_SHOW_HISTORY_CURRENT_BOOK, prefShowHistoryCurrentBook)
.putBoolean(PREF_SHOW_HISTORY_ALL_BOOKS, prefShowHistoryAllBooks)
.apply();
showAllHistoryToggleSwitch.offer(prefShowHistoryAllBooks);
}
public boolean getShowBookmarksCurrentBook() {

View File

@ -1,6 +1,5 @@
package org.kiwix.kiwixmobile.core.history.viewmodel
import DeleteSelectedOrAllHistoryItems
import OpenHistoryItem
import com.jraska.livedata.test
import io.mockk.clearAllMocks
@ -18,24 +17,22 @@ import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.HistoryDao
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.DateItem
import org.kiwix.kiwixmobile.core.history.adapter.HistoryListItem.HistoryItem
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.DeleteHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ExitActionModeMenu
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ExitHistory
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.history.viewmodel.State.NoResults
import org.kiwix.kiwixmobile.core.history.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.RequestDeleteAllHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.RequestDeleteSelectedHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.ToggleShowHistoryFromAllBooks
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDelete
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDeleteButton
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDeleteSelectedHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.history.viewmodel.State.SelectionResults
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.DeleteHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.ShowDeleteHistoryDialog
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.ToggleShowAllHistorySwitchAndSaveItsStateToPrefs
import org.kiwix.kiwixmobile.core.history.viewmodel.effects.UpdateAllHistoryPreference
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteAllHistory
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteSelectedHistory
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.sharedFunctions.InstantExecutorExtension
import org.kiwix.sharedFunctions.setScheduler
@ -62,7 +59,7 @@ internal class HistoryViewModelTest {
clearAllMocks()
every { zimReaderContainer.id } returns "id"
every { zimReaderContainer.name } returns "zimName"
every { sharedPreferenceUtil.showHistoryCurrentBook } returns true
every { sharedPreferenceUtil.showHistoryAllBooks } returns true
every { historyDao.history() } returns itemsFromDb.distinctUntilChanged()
viewModel = HistoryViewModel(historyDao, zimReaderContainer, sharedPreferenceUtil)
}
@ -86,7 +83,7 @@ internal class HistoryViewModelTest {
@Test
fun `initial state is Initialising`() {
viewModel.state.test().assertValue(NoResults(listOf()))
viewModel.state.test().assertValue(Results(listOf(), true))
}
@Test
@ -101,7 +98,7 @@ internal class HistoryViewModelTest {
searchTerm = searchTerm,
databaseResults = listOf(item)
)
resultsIn(Results((listOf(date, item))))
resultsIn(Results(listOf(item), true))
}
@Test
@ -114,7 +111,7 @@ internal class HistoryViewModelTest {
)
)
)
resultsIn(NoResults(emptyList()))
resultsIn(Results(emptyList(), true))
}
@Test
@ -126,7 +123,7 @@ internal class HistoryViewModelTest {
searchTerm = "",
databaseResults = listOf(item)
)
resultsIn(Results(listOf(date, item)))
resultsIn(Results(listOf(item), true))
}
@Test
@ -135,7 +132,7 @@ internal class HistoryViewModelTest {
searchTerm = "",
databaseResults = emptyList()
)
resultsIn(NoResults(emptyList()))
resultsIn(Results(emptyList(), true))
}
@Test
@ -150,7 +147,7 @@ internal class HistoryViewModelTest {
databaseResults = listOf(item)
)
viewModel.actions.offer(Filter(searchString2))
resultsIn(Results(listOf(date, item)))
resultsIn(Results(listOf(item), true))
}
@Test
@ -166,7 +163,7 @@ internal class HistoryViewModelTest {
searchTerm = "b",
databaseResults = listOf(item)
)
resultsIn(Results(listOf(date, item)))
resultsIn(Results(listOf(item), true))
}
@Test
@ -184,7 +181,7 @@ internal class HistoryViewModelTest {
searchTerm = "b",
databaseResults = listOf(item)
)
resultsIn(SelectionResults(listOf(date, item)))
resultsIn(SelectionResults(listOf(item), true))
}
@Test
@ -208,7 +205,7 @@ internal class HistoryViewModelTest {
searchTerm = "",
databaseResults = listOf(item2, item3, item1)
)
resultsIn(Results(listOf(date3, item3, date1, item1, date2, item2)))
resultsIn(Results(listOf(item3, item1, item2), true))
}
@Test
@ -231,7 +228,7 @@ internal class HistoryViewModelTest {
searchTerm = "",
databaseResults = listOf(item2, item3, item1)
)
resultsIn(Results(listOf(date1, item2, item1, date3, item3)))
resultsIn(Results(listOf(item2, item1, item3), true))
}
@Test
@ -247,7 +244,7 @@ internal class HistoryViewModelTest {
)
viewModel.actions.offer(OnItemLongClick(item1))
item1.isSelected = true
resultsIn(SelectionResults(listOf(date, item1)))
resultsIn(SelectionResults(listOf(item1), true))
}
@Test
@ -267,8 +264,9 @@ internal class HistoryViewModelTest {
)
viewModel.actions.offer(OnItemLongClick(item1))
viewModel.actions.offer(OnItemClick(item1))
resultsIn(Results(listOf(date, item1, item2)))
resultsIn(Results(listOf(item1, item2), true))
}
@Test
fun `Deselection via OnItemLongClick exits selection state if last item is deselected`() {
val item1 =
@ -286,7 +284,7 @@ internal class HistoryViewModelTest {
)
viewModel.actions.offer(OnItemLongClick(item1))
viewModel.actions.offer(OnItemLongClick(item1))
resultsIn(Results(listOf(date, item1, item2)))
resultsIn(Results(listOf(item1, item2), true))
}
@Test
@ -307,7 +305,7 @@ internal class HistoryViewModelTest {
viewModel.actions.offer(ExitActionModeMenu)
item1.isSelected = false
item2.isSelected = false
resultsIn(Results(listOf(date, item1, item2)))
resultsIn(Results(listOf(item1, item2), true))
}
}
@ -368,12 +366,17 @@ internal class HistoryViewModelTest {
private fun assertItemIsSelected(item: HistoryItem) {
assertTrue(
(viewModel.state.value?.historyItems?.find { it.id == item.id } as HistoryItem).isSelected
(viewModel.state.value?.historyListItems?.find {
it.id == item.id
} as HistoryItem).isSelected
)
}
private fun assertItemIsDeselected(item: HistoryItem) {
assertFalse(
(viewModel.state.value?.historyItems?.find { it.id == item.id } as HistoryItem).isSelected
(viewModel.state.value?.historyListItems?.find {
it.id == item.id
} as HistoryItem).isSelected
)
}
@ -429,31 +432,35 @@ internal class HistoryViewModelTest {
fun `ToggleShowHistoryFromAllBooks switches show all books toggle`() {
actionResultsInEffects(
ToggleShowHistoryFromAllBooks(true),
ToggleShowAllHistorySwitchAndSaveItsStateToPrefs(
viewModel.showAllSwitchToggle,
UpdateAllHistoryPreference(
sharedPreferenceUtil,
true))
true
)
)
}
@Test
fun `RequestDeleteAllHistoryItems opens dialog to request deletion`() {
actionResultsInEffects(
RequestDeleteAllHistoryItems,
ShowDeleteHistoryDialog(viewModel.actions, DeleteAllHistory))
UserClickedDeleteButton,
ShowDeleteHistoryDialog(viewModel.actions)
)
}
@Test
fun `RequestDeleteSelectedHistoryItems opens dialog to request deletion`() {
actionResultsInEffects(
RequestDeleteSelectedHistoryItems,
ShowDeleteHistoryDialog(viewModel.actions, DeleteSelectedHistory))
UserClickedDeleteSelectedHistoryItems,
ShowDeleteHistoryDialog(viewModel.actions)
)
}
@Test
fun `DeleteHistoryItems calls DeleteSelectedOrAllHistoryItems side effect`() {
actionResultsInEffects(
DeleteHistoryItems,
DeleteSelectedOrAllHistoryItems(viewModel.state, historyDao))
UserClickedDelete,
DeleteHistoryItems(viewModel.state.value!!.historyItems, historyDao)
)
}
private fun actionResultsInEffects(

View File

@ -1,6 +1,5 @@
package org.kiwix.kiwixmobile.core.history.viewmodel.effects
import DeleteSelectedOrAllHistoryItems
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData
import io.mockk.every
@ -22,7 +21,7 @@ internal class DeleteSelectedOrAllHistoryItemsTest {
every { state.value } returns SelectionResults(listOf(item1, item2))
val historyDao: HistoryDao = mockk()
val activity: AppCompatActivity = mockk()
DeleteSelectedOrAllHistoryItems(state, historyDao).invokeWith(activity)
DeleteHistoryItems(state, historyDao).invokeWith(activity)
verify { historyDao.deleteHistory(listOf(item1)) }
}
@ -34,7 +33,7 @@ internal class DeleteSelectedOrAllHistoryItemsTest {
every { state.value } returns SelectionResults(listOf(item1, item2))
val historyDao: HistoryDao = mockk()
val activity: AppCompatActivity = mockk()
DeleteSelectedOrAllHistoryItems(state, historyDao).invokeWith(activity)
DeleteHistoryItems(state, historyDao).invokeWith(activity)
verify { historyDao.deleteHistory(listOf(item1, item2)) }
}
}

View File

@ -8,7 +8,7 @@ import io.reactivex.processors.PublishProcessor
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.history.HistoryActivity
import org.kiwix.kiwixmobile.core.history.viewmodel.Action
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.DeleteHistoryItems
import org.kiwix.kiwixmobile.core.history.viewmodel.Action.UserClickedDelete
import org.kiwix.kiwixmobile.core.utils.DialogShower
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteSelectedHistory
@ -30,6 +30,6 @@ internal class ShowDeleteHistoryDialogTest {
dialogShower.show(DeleteSelectedHistory, capture(lambdaSlot))
}
lambdaSlot.captured.invoke()
verify { actions.offer(DeleteHistoryItems) }
verify { actions.offer(UserClickedDelete) }
}
}

View File

@ -15,14 +15,14 @@ internal class ToggleShowAllHistorySwitchAndSaveItsStateToPrefsTest {
every { toggleSwitch.offer(true) } returns true
val sharedPreferenceUtil: SharedPreferenceUtil = mockk()
val activity: AppCompatActivity = mockk()
ToggleShowAllHistorySwitchAndSaveItsStateToPrefs(
UpdateAllHistoryPreference(
toggleSwitch,
sharedPreferenceUtil,
true
).invokeWith(activity)
verify {
toggleSwitch.offer(true)
sharedPreferenceUtil.showHistoryCurrentBook = false
sharedPreferenceUtil.showHistoryAllBooks = false
}
}
}