mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 13:57:10 +09:00
DROID-3126 Date as an Object | Updated header (#1880)
This commit is contained in:
parent
b9a38a4fb2
commit
d455a4828c
20 changed files with 361 additions and 111 deletions
|
@ -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 = {}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
) {}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue