mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-2793 Date as an Object | Tech | Refactoring main state change (#1933)
This commit is contained in:
parent
7cea5bd9a8
commit
832627e6b0
2 changed files with 356 additions and 27 deletions
|
@ -65,6 +65,7 @@ import kotlinx.coroutines.flow.emitAll
|
|||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
|
@ -150,7 +151,8 @@ class DateObjectViewModel(
|
|||
uiObjectsListState.value = UiObjectsListState.LoadingState
|
||||
proceedWithObservingRelationListWithValue()
|
||||
proceedWithObservingPermissions()
|
||||
proceedWithObservingDateId()
|
||||
observeDateIdForObject()
|
||||
observeDateIdForRelations()
|
||||
proceedWithObservingSyncStatus()
|
||||
setupSearchStateFlow()
|
||||
_dateId.value = vmParams.objectId
|
||||
|
@ -223,6 +225,9 @@ class DateObjectViewModel(
|
|||
uiFieldsState.value = UiFieldsState.Empty
|
||||
uiObjectsListState.value = UiObjectsListState.Empty
|
||||
uiFieldsSheetState.value = UiFieldsSheetState.Hidden
|
||||
_dateObjectFieldIds.value = emptyList()
|
||||
_dateId.value = null
|
||||
_dateTimestamp.value = null
|
||||
_activeField.value = null
|
||||
_dateId.value = dateObjectId
|
||||
}
|
||||
|
@ -286,27 +291,35 @@ class DateObjectViewModel(
|
|||
viewModelScope.launch {
|
||||
combine(
|
||||
_dateObjectFieldIds,
|
||||
storeOfRelations.observe().filter { it.isNotEmpty() }
|
||||
) { relationIds, _ ->
|
||||
relationIds
|
||||
}.collect { relationIds ->
|
||||
storeOfRelations.observe()
|
||||
) { relationIds, store ->
|
||||
relationIds to store
|
||||
}.collect { (relationIds, store) ->
|
||||
Timber.d("RelationListWithValue: $relationIds")
|
||||
initFieldsState(
|
||||
items = relationIds.toUiFieldsItem(storeOfRelations = storeOfRelations)
|
||||
)
|
||||
if (store.isEmpty()) {
|
||||
handleEmptyFieldsState()
|
||||
} else {
|
||||
initFieldsState(
|
||||
items = relationIds.toUiFieldsItem(storeOfRelations = storeOfRelations)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithObservingDateId() {
|
||||
private fun observeDateIdForObject() {
|
||||
viewModelScope.launch {
|
||||
_dateId
|
||||
.filterNotNull()
|
||||
.collect { id ->
|
||||
Timber.d("Getting date object with id: $id")
|
||||
proceedWithGettingDateObject(id)
|
||||
proceedWithGettingDateObjectRelationList(id)
|
||||
}
|
||||
_dateId.filterNotNull().collect { id ->
|
||||
proceedWithGettingDateObject(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeDateIdForRelations() {
|
||||
viewModelScope.launch {
|
||||
_dateId.filterNotNull().collect { id ->
|
||||
proceedWithGettingDateObjectRelationList(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,6 +330,7 @@ class DateObjectViewModel(
|
|||
)
|
||||
getObjectRelationListById.async(params).fold(
|
||||
onSuccess = { result ->
|
||||
Timber.d("RelationListWithValue success: $result")
|
||||
_dateObjectFieldIds.value = result
|
||||
},
|
||||
onFailure = { e ->
|
||||
|
@ -382,21 +396,35 @@ class DateObjectViewModel(
|
|||
private fun setupUiStateFlow() {
|
||||
viewModelScope.launch {
|
||||
combine(
|
||||
_dateId.filterNotNull(),
|
||||
_dateTimestamp.filterNotNull(),
|
||||
_activeField.filterNotNull(),
|
||||
_dateId,
|
||||
_dateTimestamp,
|
||||
_activeField,
|
||||
restartSubscription
|
||||
) { dateId, timestamp, activeField, _ ->
|
||||
createSearchParams(
|
||||
dateId = dateId,
|
||||
timestamp = timestamp,
|
||||
space = vmParams.spaceId,
|
||||
itemsLimit = _itemsLimit,
|
||||
field = activeField
|
||||
)
|
||||
Timber.d("setupUiStateFlow, Combine: dateId: $dateId, timestamp: $timestamp, activeField: $activeField")
|
||||
|
||||
// If any of these are null, we return null to indicate we should skip loading data.
|
||||
if (dateId == null || timestamp == null || activeField == null) {
|
||||
null
|
||||
} else {
|
||||
createSearchParams(
|
||||
dateId = dateId,
|
||||
timestamp = timestamp,
|
||||
space = vmParams.spaceId,
|
||||
itemsLimit = _itemsLimit,
|
||||
field = activeField
|
||||
)
|
||||
}
|
||||
}
|
||||
.flatMapLatest { searchParams ->
|
||||
loadData(searchParams)
|
||||
if (searchParams == null) {
|
||||
Timber.d("Search params are null, skipping loadData")
|
||||
// If searchParams is null, we skip loadData and emit an empty list.
|
||||
flowOf(emptyList())
|
||||
} else {
|
||||
Timber.d("Search params are not null, loading data")
|
||||
loadData(searchParams)
|
||||
}
|
||||
}
|
||||
.catch {
|
||||
errorState.value = UiErrorState.Show(
|
||||
|
@ -670,6 +698,7 @@ class DateObjectViewModel(
|
|||
}
|
||||
|
||||
fun onDateEvent(event: DateEvent) {
|
||||
Timber.d("onDateEvent: $event")
|
||||
when (event) {
|
||||
is DateEvent.Calendar -> onCalendarEvent(event)
|
||||
is DateEvent.TopToolbar -> onTopToolbarEvent(event)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.anytypeio.anytype.feature_date
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.*
|
||||
import com.anytypeio.anytype.core_models.multiplayer.SpaceMemberPermissions
|
||||
|
@ -25,8 +26,11 @@ import com.anytypeio.anytype.feature_date.viewmodel.ActiveField
|
|||
import com.anytypeio.anytype.feature_date.viewmodel.DateObjectViewModel
|
||||
import com.anytypeio.anytype.feature_date.viewmodel.DateObjectViewModel.Companion.DEFAULT_SEARCH_LIMIT
|
||||
import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams
|
||||
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem
|
||||
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsState
|
||||
import com.anytypeio.anytype.feature_date.viewmodel.createSearchParams
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
@ -622,6 +626,302 @@ class DateObjectViewModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should restart subscription with updated params after next day clicked`() =
|
||||
runTest {
|
||||
|
||||
// Arrange
|
||||
val firstDayTimestamp = 101.0
|
||||
val firstDayObjectId = "firstDayId-${RandomString.make()}"
|
||||
val stubFirstDayObjectView = StubObjectView(
|
||||
root = firstDayObjectId,
|
||||
details = mapOf(
|
||||
firstDayObjectId to mapOf(
|
||||
Relations.TIMESTAMP to firstDayTimestamp
|
||||
)
|
||||
)
|
||||
)
|
||||
val firstDayRelation = RelationKey("firstDayRelation-${RandomString.make()}")
|
||||
val firstDayRelationsListWithValues = listOf(
|
||||
RelationListWithValueItem(
|
||||
key = firstDayRelation,
|
||||
counter = 5L
|
||||
)
|
||||
)
|
||||
|
||||
val nextDayTimestamp = 102.0
|
||||
val nextDayObjectId = "nextDayId-${RandomString.make()}"
|
||||
val stubNextDayObjectView = StubObjectView(
|
||||
root = nextDayObjectId,
|
||||
details = mapOf(
|
||||
nextDayObjectId to mapOf(
|
||||
Relations.TIMESTAMP to nextDayTimestamp
|
||||
)
|
||||
)
|
||||
)
|
||||
val nextDayRelation = RelationKey("nextDayRelation-${RandomString.make()}")
|
||||
val nextDayRelationsListWithValues = listOf(
|
||||
RelationListWithValueItem(
|
||||
key = nextDayRelation,
|
||||
counter = 5L
|
||||
)
|
||||
)
|
||||
val relationObjects = listOf(
|
||||
StubRelationObject(
|
||||
key = firstDayRelation.key,
|
||||
name = "First day date relation",
|
||||
format = RelationFormat.DATE
|
||||
),
|
||||
StubRelationObject(
|
||||
key = nextDayRelation.key,
|
||||
name = "Next day date relation",
|
||||
format = RelationFormat.DATE
|
||||
)
|
||||
)
|
||||
storeOfRelations.merge(relationObjects)
|
||||
|
||||
mockGetObjectSuccess(firstDayObjectId, stubFirstDayObjectView)
|
||||
mockRelationListWithValueSuccess(firstDayObjectId, firstDayRelationsListWithValues)
|
||||
|
||||
whenever(dateProvider.formatTimestampToDateAndTime(firstDayTimestamp.toLong() * 1000))
|
||||
.thenReturn("01-01-2024" to "12:00")
|
||||
whenever(dateProvider.calculateRelativeDates(firstDayTimestamp.toLong()))
|
||||
.thenReturn(
|
||||
RelativeDate.Other(
|
||||
initialTimeInMillis = firstDayTimestamp.toLong() * 1000,
|
||||
dayOfWeek = DayOfWeekCustom.MONDAY,
|
||||
formattedDate = "01-01-2024",
|
||||
formattedTime = "12:00"
|
||||
)
|
||||
)
|
||||
|
||||
val vm = getViewModel(objectId = firstDayObjectId, spaceId = spaceId)
|
||||
|
||||
val subscribeParams = vm.createSearchParams(
|
||||
dateId = firstDayObjectId,
|
||||
timestamp = firstDayTimestamp.toLong(),
|
||||
space = spaceId,
|
||||
itemsLimit = DEFAULT_SEARCH_LIMIT,
|
||||
field = ActiveField(
|
||||
key = firstDayRelation,
|
||||
format = RelationFormat.DATE
|
||||
)
|
||||
)
|
||||
|
||||
whenever(storelessSubscriptionContainer.subscribe(subscribeParams))
|
||||
.thenReturn(emptyFlow<List<ObjectWrapper.Basic>>())
|
||||
|
||||
vm.onStart()
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
verifyBlocking(storelessSubscriptionContainer, times(1)) {
|
||||
subscribe(subscribeParams)
|
||||
}
|
||||
|
||||
// Arrange, next day clicked, imitate the next day has no relations
|
||||
whenever(dateProvider.getTimestampForTomorrowAtStartOfDay())
|
||||
.thenReturn(nextDayTimestamp.toLong())
|
||||
val params = GetDateObjectByTimestamp.Params(
|
||||
timestampInSeconds = nextDayTimestamp.toLong(),
|
||||
space = spaceId
|
||||
)
|
||||
whenever(getDateObjectByTimestamp.async(params)).thenReturn(
|
||||
Resultat.success(
|
||||
mapOf(
|
||||
Relations.ID to nextDayObjectId
|
||||
)
|
||||
)
|
||||
)
|
||||
mockGetObjectSuccess(nextDayObjectId, stubNextDayObjectView)
|
||||
mockRelationListWithValueSuccess(nextDayObjectId, nextDayRelationsListWithValues)
|
||||
val subscribeParamsNextDay = vm.createSearchParams(
|
||||
dateId = nextDayObjectId,
|
||||
timestamp = nextDayTimestamp.toLong(),
|
||||
space = spaceId,
|
||||
itemsLimit = DEFAULT_SEARCH_LIMIT,
|
||||
field = ActiveField(
|
||||
key = nextDayRelation,
|
||||
format = RelationFormat.DATE
|
||||
)
|
||||
)
|
||||
whenever(dateProvider.formatTimestampToDateAndTime(nextDayTimestamp.toLong() * 1000))
|
||||
.thenReturn("02-01-2024" to "12:00")
|
||||
whenever(dateProvider.calculateRelativeDates(nextDayTimestamp.toLong()))
|
||||
.thenReturn(
|
||||
RelativeDate.Other(
|
||||
initialTimeInMillis = nextDayTimestamp.toLong() * 1000,
|
||||
dayOfWeek = DayOfWeekCustom.THURSDAY,
|
||||
formattedDate = "02-01-2024",
|
||||
formattedTime = "12:00"
|
||||
)
|
||||
)
|
||||
|
||||
// Act, next day clicked
|
||||
vm.onDateEvent(DateEvent.Calendar.OnTomorrowClick)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert, new subscription started
|
||||
vm.uiFieldsState.test{
|
||||
val state = expectMostRecentItem()
|
||||
assertEquals(
|
||||
listOf<UiFieldsItem>(
|
||||
UiFieldsItem.Settings(),
|
||||
UiFieldsItem.Item.Default(
|
||||
key = nextDayRelation,
|
||||
title = "Next day date relation",
|
||||
relationFormat = RelationFormat.DATE,
|
||||
id = nextDayRelation.key
|
||||
)
|
||||
),
|
||||
state.items
|
||||
)
|
||||
}
|
||||
verifyBlocking(storelessSubscriptionContainer, times(1)) {
|
||||
subscribe(subscribeParamsNextDay)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not restart subscription with updated params when new date has no active fields`() =
|
||||
runTest {
|
||||
|
||||
// Arrange
|
||||
val firstDayTimestamp = 101.0
|
||||
val firstDayObjectId = "firstDayId-${RandomString.make()}"
|
||||
val stubFirstDayObjectView = StubObjectView(
|
||||
root = firstDayObjectId,
|
||||
details = mapOf(
|
||||
firstDayObjectId to mapOf(
|
||||
Relations.TIMESTAMP to firstDayTimestamp
|
||||
)
|
||||
)
|
||||
)
|
||||
val firstDayRelation = RelationKey("firstDayRelation-${RandomString.make()}")
|
||||
val firstDayRelationsListWithValues = listOf(
|
||||
RelationListWithValueItem(
|
||||
key = firstDayRelation,
|
||||
counter = 5L
|
||||
)
|
||||
)
|
||||
|
||||
val nextDayTimestamp = 102.0
|
||||
val nextDayObjectId = "nextDayId-${RandomString.make()}"
|
||||
val stubNextDayObjectView = StubObjectView(
|
||||
root = nextDayObjectId,
|
||||
details = mapOf(
|
||||
nextDayObjectId to mapOf(
|
||||
Relations.TIMESTAMP to nextDayTimestamp
|
||||
)
|
||||
)
|
||||
)
|
||||
val nextDayRelation = RelationKey("nextDayRelation-${RandomString.make()}")
|
||||
val nextDayRelationsListWithValues = listOf(
|
||||
RelationListWithValueItem(
|
||||
key = nextDayRelation,
|
||||
counter = 5L
|
||||
)
|
||||
)
|
||||
val relationObjects = listOf(
|
||||
StubRelationObject(
|
||||
key = firstDayRelation.key,
|
||||
name = "First day date relation",
|
||||
format = RelationFormat.DATE
|
||||
),
|
||||
StubRelationObject(
|
||||
key = nextDayRelation.key,
|
||||
name = "Next day date relation",
|
||||
format = RelationFormat.DATE,
|
||||
isHidden = true
|
||||
)
|
||||
)
|
||||
storeOfRelations.merge(relationObjects)
|
||||
|
||||
mockGetObjectSuccess(firstDayObjectId, stubFirstDayObjectView)
|
||||
mockRelationListWithValueSuccess(firstDayObjectId, firstDayRelationsListWithValues)
|
||||
|
||||
whenever(dateProvider.formatTimestampToDateAndTime(firstDayTimestamp.toLong() * 1000))
|
||||
.thenReturn("01-01-2024" to "12:00")
|
||||
whenever(dateProvider.calculateRelativeDates(firstDayTimestamp.toLong()))
|
||||
.thenReturn(
|
||||
RelativeDate.Other(
|
||||
initialTimeInMillis = firstDayTimestamp.toLong() * 1000,
|
||||
dayOfWeek = DayOfWeekCustom.MONDAY,
|
||||
formattedDate = "01-01-2024",
|
||||
formattedTime = "12:00"
|
||||
)
|
||||
)
|
||||
|
||||
val vm = getViewModel(objectId = firstDayObjectId, spaceId = spaceId)
|
||||
|
||||
val subscribeParams = vm.createSearchParams(
|
||||
dateId = firstDayObjectId,
|
||||
timestamp = firstDayTimestamp.toLong(),
|
||||
space = spaceId,
|
||||
itemsLimit = DEFAULT_SEARCH_LIMIT,
|
||||
field = ActiveField(
|
||||
key = firstDayRelation,
|
||||
format = RelationFormat.DATE
|
||||
)
|
||||
)
|
||||
|
||||
whenever(storelessSubscriptionContainer.subscribe(subscribeParams))
|
||||
.thenReturn(emptyFlow<List<ObjectWrapper.Basic>>())
|
||||
|
||||
vm.onStart()
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
verifyBlocking(storelessSubscriptionContainer, times(1)) {
|
||||
subscribe(subscribeParams)
|
||||
}
|
||||
|
||||
// Arrange, next day clicked, imitate the next day has no relations
|
||||
whenever(dateProvider.getTimestampForTomorrowAtStartOfDay())
|
||||
.thenReturn(nextDayTimestamp.toLong())
|
||||
val params = GetDateObjectByTimestamp.Params(
|
||||
timestampInSeconds = nextDayTimestamp.toLong(),
|
||||
space = spaceId
|
||||
)
|
||||
whenever(getDateObjectByTimestamp.async(params)).thenReturn(
|
||||
Resultat.success(
|
||||
mapOf(
|
||||
Relations.ID to nextDayObjectId
|
||||
)
|
||||
)
|
||||
)
|
||||
mockGetObjectSuccess(nextDayObjectId, stubNextDayObjectView)
|
||||
mockRelationListWithValueSuccess(nextDayObjectId, nextDayRelationsListWithValues)
|
||||
val subscribeParamsNextDay = vm.createSearchParams(
|
||||
dateId = nextDayObjectId,
|
||||
timestamp = nextDayTimestamp.toLong(),
|
||||
space = spaceId,
|
||||
itemsLimit = DEFAULT_SEARCH_LIMIT,
|
||||
field = ActiveField(
|
||||
key = nextDayRelation,
|
||||
format = RelationFormat.DATE
|
||||
)
|
||||
)
|
||||
whenever(dateProvider.formatTimestampToDateAndTime(nextDayTimestamp.toLong() * 1000))
|
||||
.thenReturn("02-01-2024" to "12:00")
|
||||
whenever(dateProvider.calculateRelativeDates(nextDayTimestamp.toLong()))
|
||||
.thenReturn(
|
||||
RelativeDate.Other(
|
||||
initialTimeInMillis = nextDayTimestamp.toLong() * 1000,
|
||||
dayOfWeek = DayOfWeekCustom.THURSDAY,
|
||||
formattedDate = "02-01-2024",
|
||||
formattedTime = "12:00"
|
||||
)
|
||||
)
|
||||
|
||||
// Act, next day clicked
|
||||
vm.onDateEvent(DateEvent.Calendar.OnTomorrowClick)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Assert, new subscription not started
|
||||
verifyNoMoreInteractions(storelessSubscriptionContainer)
|
||||
}
|
||||
|
||||
private fun getViewModel(objectId: Id, spaceId: SpaceId): DateObjectViewModel {
|
||||
val vmParams = DateObjectVmParams(
|
||||
objectId = objectId,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue