1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3126 Date as an Object | Updated header (#1880)

This commit is contained in:
Konstantin Ivanov 2024-12-06 14:59:39 +01:00 committed by GitHub
parent b9a38a4fb2
commit d455a4828c
Signed by: github
GPG key ID: B5690EEEBB952194
20 changed files with 361 additions and 111 deletions

View file

@ -43,6 +43,7 @@ import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsState
@Composable
fun FieldsScreen(
modifier: Modifier,
uiState: UiFieldsState,
onDateEvent: (DateEvent) -> Unit
) {
@ -66,9 +67,9 @@ fun FieldsScreen(
LazyRow(
state = lazyFieldsListState,
modifier = Modifier.fillMaxWidth(),
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 24.dp)
contentPadding = PaddingValues(start = 16.dp, end = 16.dp)
) {
items(
count = items.size,
@ -181,6 +182,7 @@ fun FieldsScreen(
@DefaultPreviews
fun FieldsScreenPreview() {
FieldsScreen(
modifier = Modifier.fillMaxWidth().height(40.dp),
uiState = UiFieldsState(
items = StubHorizontalItems,
selectedRelationKey = RelationKey("1")
@ -193,6 +195,7 @@ fun FieldsScreenPreview() {
@DefaultPreviews
fun LoadingPreview() {
FieldsScreen(
modifier = Modifier.fillMaxWidth().height(40.dp),
uiState = UiFieldsState.LoadingState,
onDateEvent = {}
)

View file

@ -2,11 +2,15 @@ package com.anytypeio.anytype.feature_date.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -18,13 +22,18 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_models.DayOfWeekCustom
import com.anytypeio.anytype.core_models.RelativeDate
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.common.ShimmerEffect
import com.anytypeio.anytype.core_ui.extensions.getLocalizedDayName
import com.anytypeio.anytype.core_ui.extensions.getPrettyName
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.HeadlineTitle
import com.anytypeio.anytype.core_ui.views.Relations2
import com.anytypeio.anytype.feature_date.R
import com.anytypeio.anytype.feature_date.viewmodel.UiHeaderState
import com.anytypeio.anytype.feature_date.ui.models.DateEvent
import com.anytypeio.anytype.feature_date.viewmodel.UiHeaderState
@Composable
fun HeaderScreen(
@ -32,91 +41,155 @@ fun HeaderScreen(
uiState: UiHeaderState,
onDateEvent: (DateEvent) -> Unit
) {
Row(
modifier = modifier
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
RelativeDateAndDayOfWeek(
modifier = Modifier
.height(48.dp)
.width(52.dp)
.rotate(180f)
.noRippleThrottledClickable {
onDateEvent(DateEvent.Header.OnPreviousClick)
},
contentDescription = "Previous day",
painter = painterResource(id = R.drawable.ic_arrow_disclosure_18),
contentScale = ContentScale.None
.wrapContentSize(),
uiState = uiState
)
when (uiState) {
is UiHeaderState.Content -> {
Text(
textAlign = TextAlign.Center,
text = uiState.title,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.align(Alignment.CenterVertically),
style = HeadlineTitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary)
)
}
UiHeaderState.Loading -> {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.align(Alignment.CenterVertically),
contentAlignment = Alignment.Center
) {
ShimmerEffect(
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.height(32.dp)
.width(52.dp)
.rotate(180f)
.noRippleThrottledClickable {
onDateEvent(DateEvent.Header.OnPreviousClick)
},
contentDescription = "Previous day",
painter = painterResource(id = R.drawable.ic_arrow_disclosure_18),
contentScale = ContentScale.None
)
when (uiState) {
is UiHeaderState.Content -> {
Text(
textAlign = TextAlign.Center,
text = uiState.title,
modifier = Modifier
.width(200.dp)
.height(30.dp)
.wrapContentHeight()
.fillMaxWidth()
.weight(1f)
.noRippleThrottledClickable {
onDateEvent(
DateEvent.Header.OnHeaderClick(
timeInMillis = uiState.relativeDate.initialTimeInMillis
)
)
},
style = HeadlineTitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = colorResource(id = R.color.text_primary)
)
}
}
UiHeaderState.Empty -> {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.align(Alignment.CenterVertically),
contentAlignment = Alignment.Center
) {
Spacer(
UiHeaderState.Loading -> {
Box(
modifier = Modifier
.width(200.dp)
.height(30.dp)
)
.fillMaxWidth()
.weight(1f)
.align(Alignment.CenterVertically),
contentAlignment = Alignment.Center
) {
ShimmerEffect(
modifier = Modifier
.width(200.dp)
.height(30.dp)
)
}
}
UiHeaderState.Empty -> {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.align(Alignment.CenterVertically),
contentAlignment = Alignment.Center
) {
Spacer(
modifier = Modifier
.width(200.dp)
.height(30.dp)
)
}
}
}
Image(
modifier = Modifier
.height(32.dp)
.width(52.dp)
.noRippleThrottledClickable {
onDateEvent(DateEvent.Header.OnNextClick)
},
contentDescription = "Next day",
painter = painterResource(id = R.drawable.ic_arrow_disclosure_18),
contentScale = ContentScale.None
)
}
Image(
modifier = Modifier
.height(48.dp)
.width(52.dp)
.noRippleThrottledClickable {
onDateEvent(DateEvent.Header.OnNextClick)
},
contentDescription = "Next day",
painter = painterResource(id = R.drawable.ic_arrow_disclosure_18),
contentScale = ContentScale.None
)
}
}
@Composable
fun RelativeDateAndDayOfWeek(
modifier: Modifier = Modifier,
uiState: UiHeaderState
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
if (uiState is UiHeaderState.Content) {
uiState.relativeDate.takeIf { it is RelativeDate.Today || it is RelativeDate.Tomorrow || it is RelativeDate.Yesterday }
?.let { relativeDate ->
DateWithDot(relativeDate.getPrettyName())
}
val dayOfWeek = getLocalizedDayName(uiState.relativeDate.dayOfWeek)
Text(
text = dayOfWeek,
style = Relations2,
color = colorResource(id = R.color.text_secondary),
modifier = Modifier.wrapContentSize()
)
}
}
}
@Composable
fun DateWithDot(dateText: String) {
Text(
text = dateText,
style = Relations2,
color = colorResource(id = R.color.text_secondary),
modifier = Modifier.wrapContentSize()
)
Image(
painter = painterResource(id = R.drawable.ic_dot_3),
contentDescription = "dot",
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(horizontal = 8.dp)
.wrapContentSize()
)
}
@Composable
@DefaultPreviews
fun DateLayoutHeaderEmptyPreview() {
val state = UiHeaderState.Empty
HeaderScreen(
modifier = Modifier
.fillMaxWidth()
.height(48.dp), uiState = state
.fillMaxWidth(), uiState = state
) {}
}
@ -126,18 +199,22 @@ fun DateLayoutHeaderLoadingPreview() {
val state = UiHeaderState.Loading
HeaderScreen(
Modifier
.fillMaxWidth()
.height(48.dp), state
.fillMaxWidth(), state
) {}
}
@Composable
@DefaultPreviews
fun DateLayoutHeaderPreview() {
val state = UiHeaderState.Content("Tue, 12 Oct")
val state = UiHeaderState.Content(
title = "Tue, 12 Oct",
relativeDate = RelativeDate.Today(
initialTimeInMillis = 1634016000000,
dayOfWeek = DayOfWeekCustom.MONDAY
)
)
HeaderScreen(
Modifier
.fillMaxWidth()
.height(48.dp), state
.fillMaxWidth(), state
) {}
}

View file

@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
@ -83,16 +84,20 @@ fun DateMainScreen(
onDateEvent = onDateEvent
)
Spacer(
modifier = Modifier.height(24.dp)
modifier = Modifier.height(16.dp)
)
HeaderScreen(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
.height(54.dp),
uiState = uiHeaderState,
onDateEvent = onDateEvent
)
Spacer(
modifier = Modifier.height(32.dp)
)
FieldsScreen(
modifier = Modifier.fillMaxWidth().height(40.dp),
uiState = uiFieldsState,
onDateEvent = onDateEvent
)

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.feature_date.ui.models
import com.anytypeio.anytype.core_models.TimeInMillis
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState
import com.anytypeio.anytype.core_models.primitives.TimestampInSeconds
import com.anytypeio.anytype.feature_date.viewmodel.UiFieldsItem
@ -15,6 +16,7 @@ sealed class DateEvent {
sealed class Header : DateEvent() {
data object OnNextClick : Header()
data object OnPreviousClick : Header()
data class OnHeaderClick(val timeInMillis: TimeInMillis): Header()
}
sealed class FieldsSheet : DateEvent() {

View file

@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_models.RelativeDate
import com.anytypeio.anytype.core_models.TimeInMillis
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState
import com.anytypeio.anytype.core_models.primitives.RelationKey
@ -29,7 +30,8 @@ sealed class UiHeaderState {
data object Empty : UiHeaderState()
data object Loading : UiHeaderState()
data class Content(
val title: String
val title: String,
val relativeDate: RelativeDate
) : UiHeaderState()
}

View file

@ -293,7 +293,8 @@ class DateObjectViewModel(
.collect { id ->
val params = GetObject.Params(
target = id,
space = vmParams.spaceId
space = vmParams.spaceId,
saveAsLastOpened = true
)
Timber.d("Start GetObject with params: $params")
getObject.async(params).fold(
@ -312,7 +313,10 @@ class DateObjectViewModel(
timestampInSeconds = TimestampInSeconds(timestampInSeconds)
)
uiHeaderState.value = UiHeaderState.Content(
title = formattedDate
title = formattedDate,
relativeDate = dateProvider.calculateRelativeDates(
dateInSeconds = timestampInSeconds
)
)
}
},
@ -668,21 +672,7 @@ class DateObjectViewModel(
private fun onTopToolbarEvent(event: DateEvent.TopToolbar) {
when (event) {
is DateEvent.TopToolbar.OnCalendarClick -> {
val timestampInSeconds = event.timestampInSeconds
val timeInMillis = dateProvider.adjustToStartOfDayInUserTimeZone(
timestamp = timestampInSeconds.time
)
val isValid = dateProvider.isTimestampWithinYearRange(
timeStampInMillis = timeInMillis,
yearRange = DATE_PICKER_YEAR_RANGE
)
if (isValid) {
uiCalendarState.value = UiCalendarState.Calendar(
timeInMillis = timeInMillis
)
} else {
showDateOutOfRangeError()
}
proceedWithShowCalendar(timestampInSeconds = event.timestampInSeconds)
}
is DateEvent.TopToolbar.OnSyncStatusClick -> {
@ -694,10 +684,33 @@ class DateObjectViewModel(
}
}
private fun proceedWithShowCalendar(timestampInSeconds: TimestampInSeconds) {
val timeInMillis = dateProvider.adjustToStartOfDayInUserTimeZone(
timestamp = timestampInSeconds.time
)
val isValid = dateProvider.isTimestampWithinYearRange(
timeStampInMillis = timeInMillis,
yearRange = DATE_PICKER_YEAR_RANGE
)
if (isValid) {
uiCalendarState.value = UiCalendarState.Calendar(
timeInMillis = timeInMillis
)
} else {
showDateOutOfRangeError()
}
}
private fun onHeaderEvent(event: DateEvent.Header) {
when (event) {
DateEvent.Header.OnNextClick -> proceedWithReopeningDate(offset = SECONDS_IN_DAY)
DateEvent.Header.OnPreviousClick -> proceedWithReopeningDate(offset = -SECONDS_IN_DAY)
is DateEvent.Header.OnHeaderClick -> {
val timestampInSeconds = TimestampInSeconds(
time = event.timeInMillis / 1000
)
proceedWithShowCalendar(timestampInSeconds)
}
}
}