mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-17 11:25:34 -04:00
#2154 more merging of viewmodels bookmark and history
This commit is contained in:
parent
8679e390f2
commit
37418efb53
@ -1,11 +1,12 @@
|
|||||||
package org.kiwix.kiwixmobile.core.page.bookmark
|
package org.kiwix.kiwixmobile.core.page.bookmark
|
||||||
|
|
||||||
import org.kiwix.kiwixmobile.core.R
|
import org.kiwix.kiwixmobile.core.R
|
||||||
|
import org.kiwix.kiwixmobile.core.di.components.CoreComponent
|
||||||
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
|
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
|
||||||
import org.kiwix.kiwixmobile.core.page.PageActivity
|
import org.kiwix.kiwixmobile.core.page.PageActivity
|
||||||
import org.kiwix.kiwixmobile.core.page.adapter.PageAdapter
|
import org.kiwix.kiwixmobile.core.page.adapter.PageAdapter
|
||||||
import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.BookmarkViewModel
|
|
||||||
import org.kiwix.kiwixmobile.core.page.adapter.PageDelegate.PageItemDelegate
|
import org.kiwix.kiwixmobile.core.page.adapter.PageDelegate.PageItemDelegate
|
||||||
|
import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.BookmarkViewModel
|
||||||
|
|
||||||
class BookmarksActivity : PageActivity() {
|
class BookmarksActivity : PageActivity() {
|
||||||
override val pageViewModel by lazy { viewModel<BookmarkViewModel>(viewModelFactory) }
|
override val pageViewModel by lazy { viewModel<BookmarkViewModel>(viewModelFactory) }
|
||||||
@ -14,6 +15,10 @@ class BookmarksActivity : PageActivity() {
|
|||||||
PageAdapter(PageItemDelegate(this))
|
PageAdapter(PageItemDelegate(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun injection(coreComponent: CoreComponent) {
|
||||||
|
activityComponent.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
override val title: String by lazy { getString(R.string.bookmarks) }
|
override val title: String by lazy { getString(R.string.bookmarks) }
|
||||||
override val noItemsString: String by lazy { getString(R.string.no_bookmarks) }
|
override val noItemsString: String by lazy { getString(R.string.no_bookmarks) }
|
||||||
override val switchString: String by lazy { getString(R.string.bookmarks_from_current_book) }
|
override val switchString: String by lazy { getString(R.string.bookmarks_from_current_book) }
|
||||||
|
@ -31,13 +31,13 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class BookmarkViewModel @Inject constructor(
|
class BookmarkViewModel @Inject constructor(
|
||||||
override val pageDao: NewBookmarksDao,
|
bookmarksDao: NewBookmarksDao,
|
||||||
override val zimReaderContainer: ZimReaderContainer,
|
override val zimReaderContainer: ZimReaderContainer,
|
||||||
override val sharedPreferenceUtil: SharedPreferenceUtil
|
override val sharedPreferenceUtil: SharedPreferenceUtil
|
||||||
) : PageViewModel<BookmarkState>() {
|
) : PageViewModel<BookmarkState>(bookmarksDao) {
|
||||||
|
|
||||||
override fun initialState(): BookmarkState =
|
override fun initialState(): BookmarkState =
|
||||||
BookmarkState(emptyList(), sharedPreferenceUtil.showBookmarksAllBooks, zimReaderContainer.id)
|
BookmarkState(emptyList(), true, null)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
compositeDisposable.addAll(
|
compositeDisposable.addAll(
|
||||||
@ -61,11 +61,9 @@ class BookmarkViewModel @Inject constructor(
|
|||||||
return (state as BookmarkState).copy(showAll = action.isChecked)
|
return (state as BookmarkState).copy(showAll = action.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun offerShowDeleteDialog(state: PageState): PageState {
|
|
||||||
effects.offer(ShowDeleteBookmarksDialog(effects, state as BookmarkState, pageDao))
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deselectAllPages(state: PageState): PageState =
|
override fun deselectAllPages(state: PageState): PageState =
|
||||||
(state as BookmarkState).copy(pageItems = state.pageItems.map { it.copy(isSelected = false) })
|
(state as BookmarkState).copy(pageItems = state.pageItems.map { it.copy(isSelected = false) })
|
||||||
|
|
||||||
|
override fun createDeletePageDialogEffect(state: PageState) =
|
||||||
|
ShowDeleteBookmarksDialog(effects, state as BookmarkState, pageDao)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ package org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.effects
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import org.kiwix.kiwixmobile.core.base.SideEffect
|
import org.kiwix.kiwixmobile.core.base.SideEffect
|
||||||
import org.kiwix.kiwixmobile.core.dao.NewBookmarksDao
|
import org.kiwix.kiwixmobile.core.dao.PageDao
|
||||||
import org.kiwix.kiwixmobile.core.page.bookmark.BookmarksActivity
|
import org.kiwix.kiwixmobile.core.page.bookmark.BookmarksActivity
|
||||||
import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.BookmarkState
|
import org.kiwix.kiwixmobile.core.page.bookmark.viewmodel.BookmarkState
|
||||||
import org.kiwix.kiwixmobile.core.page.viewmodel.effects.DeletePageItems
|
import org.kiwix.kiwixmobile.core.page.viewmodel.effects.DeletePageItems
|
||||||
@ -33,7 +33,7 @@ import javax.inject.Inject
|
|||||||
data class ShowDeleteBookmarksDialog(
|
data class ShowDeleteBookmarksDialog(
|
||||||
private val effects: PublishProcessor<SideEffect<*>>,
|
private val effects: PublishProcessor<SideEffect<*>>,
|
||||||
private val state: BookmarkState,
|
private val state: BookmarkState,
|
||||||
private val bookmarksDao: NewBookmarksDao
|
private val bookmarksDao: PageDao
|
||||||
) : SideEffect<Unit> {
|
) : SideEffect<Unit> {
|
||||||
@Inject lateinit var dialogShower: DialogShower
|
@Inject lateinit var dialogShower: DialogShower
|
||||||
override fun invokeWith(activity: AppCompatActivity) {
|
override fun invokeWith(activity: AppCompatActivity) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.kiwix.kiwixmobile.core.page.history
|
package org.kiwix.kiwixmobile.core.page.history
|
||||||
|
|
||||||
import org.kiwix.kiwixmobile.core.R
|
import org.kiwix.kiwixmobile.core.R
|
||||||
|
import org.kiwix.kiwixmobile.core.di.components.CoreComponent
|
||||||
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
|
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
|
||||||
import org.kiwix.kiwixmobile.core.page.PageActivity
|
import org.kiwix.kiwixmobile.core.page.PageActivity
|
||||||
import org.kiwix.kiwixmobile.core.page.adapter.PageAdapter
|
import org.kiwix.kiwixmobile.core.page.adapter.PageAdapter
|
||||||
@ -17,6 +18,10 @@ class HistoryActivity : PageActivity() {
|
|||||||
PageAdapter(PageItemDelegate(this), HistoryDateDelegate())
|
PageAdapter(PageItemDelegate(this), HistoryDateDelegate())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun injection(coreComponent: CoreComponent) {
|
||||||
|
activityComponent.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
override val noItemsString: String by lazy { getString(R.string.no_history) }
|
override val noItemsString: String by lazy { getString(R.string.no_history) }
|
||||||
override val switchString: String by lazy { getString(R.string.history_from_current_book) }
|
override val switchString: String by lazy { getString(R.string.history_from_current_book) }
|
||||||
override val title: String by lazy { getString(R.string.history) }
|
override val title: String by lazy { getString(R.string.history) }
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package org.kiwix.kiwixmobile.core.page.history.viewmodel
|
package org.kiwix.kiwixmobile.core.page.history.viewmodel
|
||||||
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
import org.kiwix.kiwixmobile.core.dao.HistoryDao
|
import org.kiwix.kiwixmobile.core.dao.HistoryDao
|
||||||
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
|
import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.HistoryItem
|
||||||
import org.kiwix.kiwixmobile.core.page.history.viewmodel.effects.ShowDeleteHistoryDialog
|
import org.kiwix.kiwixmobile.core.page.history.viewmodel.effects.ShowDeleteHistoryDialog
|
||||||
@ -31,21 +30,13 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class HistoryViewModel @Inject constructor(
|
class HistoryViewModel @Inject constructor(
|
||||||
override val pageDao: HistoryDao,
|
|
||||||
override val zimReaderContainer: ZimReaderContainer,
|
override val zimReaderContainer: ZimReaderContainer,
|
||||||
override val sharedPreferenceUtil: SharedPreferenceUtil
|
override val sharedPreferenceUtil: SharedPreferenceUtil,
|
||||||
) : PageViewModel<HistoryState>() {
|
historyDao: HistoryDao
|
||||||
|
) : PageViewModel<HistoryState>(historyDao) {
|
||||||
|
|
||||||
override fun initialState(): HistoryState =
|
override fun initialState(): HistoryState =
|
||||||
HistoryState(emptyList(), sharedPreferenceUtil.showHistoryAllBooks, zimReaderContainer.id)
|
HistoryState(emptyList(), true, null)
|
||||||
|
|
||||||
init {
|
|
||||||
compositeDisposable.addAll(
|
|
||||||
viewStateReducer(),
|
|
||||||
pageDao.pages().subscribeOn(Schedulers.io())
|
|
||||||
.subscribe({ actions.offer(Action.UpdatePages(it)) }, Throwable::printStackTrace)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updatePagesBasedOnFilter(state: PageState, action: Action.Filter): PageState =
|
override fun updatePagesBasedOnFilter(state: PageState, action: Action.Filter): PageState =
|
||||||
(state as HistoryState).copy(searchTerm = action.searchTerm)
|
(state as HistoryState).copy(searchTerm = action.searchTerm)
|
||||||
@ -61,10 +52,8 @@ class HistoryViewModel @Inject constructor(
|
|||||||
return (state as HistoryState).copy(showAll = action.isChecked)
|
return (state as HistoryState).copy(showAll = action.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun offerShowDeleteDialog(state: PageState): PageState {
|
override fun createDeletePageDialogEffect(state: PageState) =
|
||||||
effects.offer(ShowDeleteHistoryDialog(effects, state as HistoryState, pageDao))
|
ShowDeleteHistoryDialog(effects, state as HistoryState, pageDao)
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deselectAllPages(state: PageState): PageState =
|
override fun deselectAllPages(state: PageState): PageState =
|
||||||
(state as HistoryState).copy(pageItems = state.pageItems.map { it.copy(isSelected = false) })
|
(state as HistoryState).copy(pageItems = state.pageItems.map { it.copy(isSelected = false) })
|
||||||
|
@ -21,7 +21,7 @@ package org.kiwix.kiwixmobile.core.page.history.viewmodel.effects
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import org.kiwix.kiwixmobile.core.base.SideEffect
|
import org.kiwix.kiwixmobile.core.base.SideEffect
|
||||||
import org.kiwix.kiwixmobile.core.dao.HistoryDao
|
import org.kiwix.kiwixmobile.core.dao.PageDao
|
||||||
import org.kiwix.kiwixmobile.core.page.history.HistoryActivity
|
import org.kiwix.kiwixmobile.core.page.history.HistoryActivity
|
||||||
import org.kiwix.kiwixmobile.core.page.history.viewmodel.HistoryState
|
import org.kiwix.kiwixmobile.core.page.history.viewmodel.HistoryState
|
||||||
import org.kiwix.kiwixmobile.core.page.viewmodel.effects.DeletePageItems
|
import org.kiwix.kiwixmobile.core.page.viewmodel.effects.DeletePageItems
|
||||||
@ -33,7 +33,7 @@ import javax.inject.Inject
|
|||||||
data class ShowDeleteHistoryDialog(
|
data class ShowDeleteHistoryDialog(
|
||||||
private val effects: PublishProcessor<SideEffect<*>>,
|
private val effects: PublishProcessor<SideEffect<*>>,
|
||||||
private val state: HistoryState,
|
private val state: HistoryState,
|
||||||
private val historyDao: HistoryDao
|
private val historyDao: PageDao
|
||||||
) : SideEffect<Unit> {
|
) : SideEffect<Unit> {
|
||||||
@Inject lateinit var dialogShower: DialogShower
|
@Inject lateinit var dialogShower: DialogShower
|
||||||
override fun invokeWith(activity: AppCompatActivity) {
|
override fun invokeWith(activity: AppCompatActivity) {
|
||||||
|
@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
import org.kiwix.kiwixmobile.core.base.SideEffect
|
import org.kiwix.kiwixmobile.core.base.SideEffect
|
||||||
import org.kiwix.kiwixmobile.core.dao.PageDao
|
import org.kiwix.kiwixmobile.core.dao.PageDao
|
||||||
import org.kiwix.kiwixmobile.core.page.viewmodel.Action.Exit
|
import org.kiwix.kiwixmobile.core.page.viewmodel.Action.Exit
|
||||||
@ -39,17 +40,24 @@ import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
|||||||
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
|
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||||
|
|
||||||
abstract class PageViewModel<out T : PageState> : ViewModel() {
|
abstract class PageViewModel<out T : PageState>(val pageDao: PageDao) : ViewModel() {
|
||||||
abstract val zimReaderContainer: ZimReaderContainer
|
abstract val zimReaderContainer: ZimReaderContainer
|
||||||
abstract val sharedPreferenceUtil: SharedPreferenceUtil
|
abstract val sharedPreferenceUtil: SharedPreferenceUtil
|
||||||
abstract val pageDao: PageDao
|
|
||||||
val state = MutableLiveData<PageState>().apply {
|
val state = MutableLiveData<PageState>().apply {
|
||||||
value = initialState()
|
value = initialState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val compositeDisposable = CompositeDisposable()
|
||||||
val effects = PublishProcessor.create<SideEffect<*>>()
|
val effects = PublishProcessor.create<SideEffect<*>>()
|
||||||
val actions = PublishProcessor.create<Action>()
|
val actions = PublishProcessor.create<Action>()
|
||||||
val compositeDisposable = CompositeDisposable()
|
|
||||||
|
init {
|
||||||
|
compositeDisposable.addAll(
|
||||||
|
viewStateReducer(),
|
||||||
|
pageDao.pages().subscribeOn(Schedulers.io())
|
||||||
|
.subscribe({ actions.offer(Action.UpdatePages(it)) }, Throwable::printStackTrace)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun viewStateReducer(): Disposable =
|
fun viewStateReducer(): Disposable =
|
||||||
actions.map { reduce(it, state.value!!) }
|
actions.map { reduce(it, state.value!!) }
|
||||||
@ -77,6 +85,11 @@ abstract class PageViewModel<out T : PageState> : ViewModel() {
|
|||||||
state: PageState
|
state: PageState
|
||||||
): PageState
|
): PageState
|
||||||
|
|
||||||
|
private fun offerShowDeleteDialog(state: PageState): PageState {
|
||||||
|
effects.offer(createDeletePageDialogEffect(state))
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleItemLongClick(state: PageState, action: OnItemLongClick): PageState =
|
private fun handleItemLongClick(state: PageState, action: OnItemLongClick): PageState =
|
||||||
state.toggleSelectionOfItem(action.page)
|
state.toggleSelectionOfItem(action.page)
|
||||||
|
|
||||||
@ -88,8 +101,6 @@ abstract class PageViewModel<out T : PageState> : ViewModel() {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun offerShowDeleteDialog(state: PageState): PageState
|
|
||||||
|
|
||||||
abstract fun deselectAllPages(state: PageState): PageState
|
abstract fun deselectAllPages(state: PageState): PageState
|
||||||
|
|
||||||
private fun finishActivity(state: PageState): PageState {
|
private fun finishActivity(state: PageState): PageState {
|
||||||
@ -101,4 +112,6 @@ abstract class PageViewModel<out T : PageState> : ViewModel() {
|
|||||||
compositeDisposable.clear()
|
compositeDisposable.clear()
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract fun createDeletePageDialogEffect(state: PageState): SideEffect<*>
|
||||||
}
|
}
|
||||||
|
@ -84,14 +84,26 @@ internal class HistoryViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
internal fun `UserClickedDeleteButton offers ShowDeleteHistoryDialog`() {
|
internal fun `UserClickedDeleteButton offers ShowDeleteHistoryDialog`() {
|
||||||
viewModel.effects.test().also { viewModel.actions.offer(UserClickedDeleteButton) }
|
viewModel.effects.test().also { viewModel.actions.offer(UserClickedDeleteButton) }
|
||||||
.assertValue(ShowDeleteHistoryDialog(viewModel.effects, historyState(), historyDao))
|
.assertValue(
|
||||||
|
ShowDeleteHistoryDialog(
|
||||||
|
viewModel.effects,
|
||||||
|
historyState(),
|
||||||
|
historyDao
|
||||||
|
)
|
||||||
|
)
|
||||||
viewModel.state.test().assertValue(historyState())
|
viewModel.state.test().assertValue(historyState())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
internal fun `UserClickedDeleteSelectedHistoryItems offers ShowDeleteHistoryDialog`() {
|
internal fun `UserClickedDeleteSelectedHistoryItems offers ShowDeleteHistoryDialog`() {
|
||||||
viewModel.effects.test().also { viewModel.actions.offer(UserClickedDeleteSelectedPages) }
|
viewModel.effects.test().also { viewModel.actions.offer(UserClickedDeleteSelectedPages) }
|
||||||
.assertValue(ShowDeleteHistoryDialog(viewModel.effects, historyState(), historyDao))
|
.assertValue(
|
||||||
|
ShowDeleteHistoryDialog(
|
||||||
|
viewModel.effects,
|
||||||
|
historyState(),
|
||||||
|
historyDao
|
||||||
|
)
|
||||||
|
)
|
||||||
viewModel.state.test().assertValue(historyState())
|
viewModel.state.test().assertValue(historyState())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,12 @@ internal class ShowDeleteHistoryDialogTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `invoke with shows dialog that offers ConfirmDelete action`() {
|
fun `invoke with shows dialog that offers ConfirmDelete action`() {
|
||||||
val showDeleteHistoryDialog = ShowDeleteHistoryDialog(effects, historyState(), historyDao)
|
val showDeleteHistoryDialog =
|
||||||
|
ShowDeleteHistoryDialog(
|
||||||
|
effects,
|
||||||
|
historyState(),
|
||||||
|
historyDao
|
||||||
|
)
|
||||||
mockkActivityInjection(showDeleteHistoryDialog)
|
mockkActivityInjection(showDeleteHistoryDialog)
|
||||||
val lambdaSlot = slot<() -> Unit>()
|
val lambdaSlot = slot<() -> Unit>()
|
||||||
showDeleteHistoryDialog.invokeWith(activity)
|
showDeleteHistoryDialog.invokeWith(activity)
|
||||||
@ -35,7 +40,8 @@ internal class ShowDeleteHistoryDialogTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `invoke with selected item shows dialog with delete selected items title`() {
|
fun `invoke with selected item shows dialog with delete selected items title`() {
|
||||||
val showDeleteHistoryDialog = ShowDeleteHistoryDialog(
|
val showDeleteHistoryDialog =
|
||||||
|
ShowDeleteHistoryDialog(
|
||||||
effects,
|
effects,
|
||||||
historyState(listOf(historyItem(isSelected = true))),
|
historyState(listOf(historyItem(isSelected = true))),
|
||||||
historyDao
|
historyDao
|
||||||
@ -47,7 +53,12 @@ internal class ShowDeleteHistoryDialogTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `invoke with no selected items shows dialog with delete all items title`() {
|
fun `invoke with no selected items shows dialog with delete all items title`() {
|
||||||
val showDeleteHistoryDialog = ShowDeleteHistoryDialog(effects, historyState(), historyDao)
|
val showDeleteHistoryDialog =
|
||||||
|
ShowDeleteHistoryDialog(
|
||||||
|
effects,
|
||||||
|
historyState(),
|
||||||
|
historyDao
|
||||||
|
)
|
||||||
mockkActivityInjection(showDeleteHistoryDialog)
|
mockkActivityInjection(showDeleteHistoryDialog)
|
||||||
showDeleteHistoryDialog.invokeWith(activity)
|
showDeleteHistoryDialog.invokeWith(activity)
|
||||||
verify { dialogShower.show(DeleteAllHistory, any()) }
|
verify { dialogShower.show(DeleteAllHistory, any()) }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user