1
0
Fork 0
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:
Konstantin Ivanov 2024-12-17 11:36:44 +01:00 committed by Evgenii Kozlov
parent 7cea5bd9a8
commit 832627e6b0
2 changed files with 356 additions and 27 deletions

View file

@ -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)

View file

@ -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,