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:
parent
b9a38a4fb2
commit
d455a4828c
20 changed files with 361 additions and 111 deletions
|
@ -63,10 +63,12 @@ object DateObjectModule {
|
|||
@PerScreen
|
||||
fun getObject(
|
||||
repo: BlockRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
settings: UserSettingsRepository,
|
||||
): GetObject = GetObject(
|
||||
repo = repo,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
settings = settings
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -145,10 +145,12 @@ object HomeScreenModule {
|
|||
@PerScreen
|
||||
fun getObject(
|
||||
repo: BlockRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
settings: UserSettingsRepository,
|
||||
): GetObject = GetObject(
|
||||
repo = repo,
|
||||
dispatchers = dispatchers
|
||||
dispatchers = dispatchers,
|
||||
settings = settings
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.anytypeio.anytype.di.common.componentManager
|
|||
import com.anytypeio.anytype.other.DefaultDeepLinkResolver
|
||||
import com.anytypeio.anytype.presentation.splash.SplashViewModel
|
||||
import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory
|
||||
import com.anytypeio.anytype.ui.date.DateObjectFragment
|
||||
import com.anytypeio.anytype.ui.editor.EditorFragment
|
||||
import com.anytypeio.anytype.ui.home.HomeScreenFragment
|
||||
import com.anytypeio.anytype.ui.onboarding.OnboardingFragment
|
||||
|
@ -171,6 +172,27 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>(R.layout.fragment_spl
|
|||
Timber.e(it, "Error while navigating to set-or-collection from splash")
|
||||
}
|
||||
}
|
||||
is SplashViewModel.Command.NavigateToDateObject -> {
|
||||
runCatching {
|
||||
findNavController().navigate(R.id.actionOpenVaultFromSplash)
|
||||
findNavController().navigate(
|
||||
R.id.actionOpenSpaceFromVault,
|
||||
args = HomeScreenFragment.args(
|
||||
space = command.space,
|
||||
deeplink = null
|
||||
)
|
||||
)
|
||||
findNavController().navigate(
|
||||
resId = R.id.dateObjectScreen,
|
||||
args = DateObjectFragment.args(
|
||||
objectId = command.id,
|
||||
space = command.space
|
||||
)
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.e(it, "Error while navigating to date object from splash")
|
||||
}
|
||||
}
|
||||
is SplashViewModel.Command.NavigateToAuthStart -> {
|
||||
val intent = activity?.intent
|
||||
val deepLink: String?
|
||||
|
|
|
@ -2,13 +2,43 @@ package com.anytypeio.anytype.core_models
|
|||
|
||||
sealed class RelativeDate {
|
||||
abstract val initialTimeInMillis: TimeInMillis
|
||||
abstract val dayOfWeek: DayOfWeekCustom
|
||||
|
||||
/** Represents an unset date (timestamp = 0.0) */
|
||||
data object Empty : RelativeDate() {
|
||||
override val initialTimeInMillis: TimeInMillis = 0
|
||||
override val dayOfWeek: DayOfWeekCustom = DayOfWeekCustom.MONDAY // Default value
|
||||
}
|
||||
|
||||
data class Today(
|
||||
override val initialTimeInMillis: TimeInMillis,
|
||||
override val dayOfWeek: DayOfWeekCustom
|
||||
) : RelativeDate()
|
||||
|
||||
data class Tomorrow(
|
||||
override val initialTimeInMillis: TimeInMillis,
|
||||
override val dayOfWeek: DayOfWeekCustom
|
||||
) : RelativeDate()
|
||||
|
||||
data class Yesterday(
|
||||
override val initialTimeInMillis: TimeInMillis,
|
||||
override val dayOfWeek: DayOfWeekCustom
|
||||
) : RelativeDate()
|
||||
|
||||
data class Today(override val initialTimeInMillis: TimeInMillis) : RelativeDate()
|
||||
data class Tomorrow(override val initialTimeInMillis: TimeInMillis) : RelativeDate()
|
||||
data class Yesterday(override val initialTimeInMillis: TimeInMillis) : RelativeDate()
|
||||
data class Other(
|
||||
override val initialTimeInMillis: TimeInMillis,
|
||||
override val dayOfWeek: DayOfWeekCustom,
|
||||
val formattedDate: String,
|
||||
val formattedTime: String
|
||||
) : RelativeDate()
|
||||
}
|
||||
|
||||
enum class DayOfWeekCustom {
|
||||
MONDAY,
|
||||
TUESDAY,
|
||||
WEDNESDAY,
|
||||
THURSDAY,
|
||||
FRIDAY,
|
||||
SATURDAY,
|
||||
SUNDAY
|
||||
}
|
|
@ -66,6 +66,8 @@ object SupportedLayouts {
|
|||
|
||||
val widgetsLayouts = layouts + dateLayouts
|
||||
|
||||
val lastOpenObjectLayouts = layouts + dateLayouts
|
||||
|
||||
fun isSupported(layout: ObjectType.Layout?) : Boolean {
|
||||
return layouts.contains(layout)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ import androidx.compose.ui.composed
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.anytypeio.anytype.core_models.DayOfWeekCustom
|
||||
import com.anytypeio.anytype.core_models.RelativeDate
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
|
||||
|
@ -96,3 +99,27 @@ fun <T> SnapshotStateList<T>.swapList(newList: List<T>){
|
|||
clear()
|
||||
addAll(newList)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getLocalizedDayName(dayOfWeek: DayOfWeekCustom): String {
|
||||
return when (dayOfWeek) {
|
||||
DayOfWeekCustom.MONDAY -> stringResource(id = R.string.day_of_week_monday)
|
||||
DayOfWeekCustom.TUESDAY -> stringResource(id = R.string.day_of_week_tuesday)
|
||||
DayOfWeekCustom.WEDNESDAY -> stringResource(id = R.string.day_of_week_wednesday)
|
||||
DayOfWeekCustom.THURSDAY -> stringResource(id = R.string.day_of_week_thursday)
|
||||
DayOfWeekCustom.FRIDAY -> stringResource(id = R.string.day_of_week_friday)
|
||||
DayOfWeekCustom.SATURDAY -> stringResource(id = R.string.day_of_week_saturday)
|
||||
DayOfWeekCustom.SUNDAY -> stringResource(id = R.string.day_of_week_sunday)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RelativeDate.getPrettyName(): String {
|
||||
return when (this) {
|
||||
is RelativeDate.Today -> stringResource(id = R.string.today)
|
||||
is RelativeDate.Tomorrow -> stringResource(id = R.string.tomorrow)
|
||||
is RelativeDate.Yesterday -> stringResource(id = R.string.yesterday)
|
||||
is RelativeDate.Other -> this.formattedDate
|
||||
RelativeDate.Empty -> ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -309,4 +309,5 @@ fun RelativeDate.getPrettyName(
|
|||
is RelativeDate.Today -> resources.getString(R.string.today)
|
||||
is RelativeDate.Tomorrow -> resources.getString(R.string.tomorrow)
|
||||
is RelativeDate.Yesterday -> resources.getString(R.string.yesterday)
|
||||
RelativeDate.Empty -> ""
|
||||
}
|
9
core-ui/src/main/res/drawable/ic_dot_3.xml
Normal file
9
core-ui/src/main/res/drawable/ic_dot_3.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="3dp"
|
||||
android:height="4dp"
|
||||
android:viewportWidth="3"
|
||||
android:viewportHeight="4">
|
||||
<path
|
||||
android:pathData="M1.5,2m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
|
||||
android:fillColor="@color/text_secondary"/>
|
||||
</vector>
|
|
@ -1,6 +1,7 @@
|
|||
package com.anytypeio.anytype.device.providers
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import com.anytypeio.anytype.core_models.DayOfWeekCustom
|
||||
import com.anytypeio.anytype.core_models.RelativeDate
|
||||
import com.anytypeio.anytype.core_models.TimeInMillis
|
||||
import com.anytypeio.anytype.core_models.TimeInSeconds
|
||||
|
@ -185,9 +186,9 @@ class DateProviderImpl @Inject constructor(
|
|||
return truncatedDateTime1 == truncatedDateTime2
|
||||
}
|
||||
|
||||
override fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate? {
|
||||
override fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate {
|
||||
|
||||
if (dateInSeconds == null || dateInSeconds == 0L) return null
|
||||
if (dateInSeconds == null || dateInSeconds == 0L) return RelativeDate.Empty
|
||||
val initialTimeInMillis = dateInSeconds * 1000
|
||||
val zoneId = defaultZoneId
|
||||
val dateInstant = Instant.ofEpochSecond(dateInSeconds)
|
||||
|
@ -196,10 +197,33 @@ class DateProviderImpl @Inject constructor(
|
|||
|
||||
val daysDifference = ChronoUnit.DAYS.between(today, givenDate)
|
||||
|
||||
// Convert java.time.DayOfWeek to DayOfWeekCustom
|
||||
val dayOfWeekCustom = when (givenDate.dayOfWeek) {
|
||||
java.time.DayOfWeek.MONDAY -> DayOfWeekCustom.MONDAY
|
||||
java.time.DayOfWeek.TUESDAY -> DayOfWeekCustom.TUESDAY
|
||||
java.time.DayOfWeek.WEDNESDAY -> DayOfWeekCustom.WEDNESDAY
|
||||
java.time.DayOfWeek.THURSDAY -> DayOfWeekCustom.THURSDAY
|
||||
java.time.DayOfWeek.FRIDAY -> DayOfWeekCustom.FRIDAY
|
||||
java.time.DayOfWeek.SATURDAY -> DayOfWeekCustom.SATURDAY
|
||||
java.time.DayOfWeek.SUNDAY -> DayOfWeekCustom.SUNDAY
|
||||
}
|
||||
|
||||
return when (daysDifference) {
|
||||
0L -> RelativeDate.Today(initialTimeInMillis = initialTimeInMillis)
|
||||
1L -> RelativeDate.Tomorrow(initialTimeInMillis = initialTimeInMillis)
|
||||
-1L -> RelativeDate.Yesterday(initialTimeInMillis = initialTimeInMillis)
|
||||
0L -> RelativeDate.Today(
|
||||
initialTimeInMillis = initialTimeInMillis,
|
||||
dayOfWeek = dayOfWeekCustom
|
||||
)
|
||||
|
||||
1L -> RelativeDate.Tomorrow(
|
||||
initialTimeInMillis = initialTimeInMillis,
|
||||
dayOfWeek = dayOfWeekCustom
|
||||
)
|
||||
|
||||
-1L -> RelativeDate.Yesterday(
|
||||
initialTimeInMillis = initialTimeInMillis,
|
||||
dayOfWeek = dayOfWeekCustom
|
||||
)
|
||||
|
||||
else -> {
|
||||
val timestampMillis = TimeUnit.SECONDS.toMillis(dateInSeconds)
|
||||
|
||||
|
@ -207,7 +231,8 @@ class DateProviderImpl @Inject constructor(
|
|||
RelativeDate.Other(
|
||||
initialTimeInMillis = initialTimeInMillis,
|
||||
formattedDate = dateString,
|
||||
formattedTime = timeString
|
||||
formattedTime = timeString,
|
||||
dayOfWeek = dayOfWeekCustom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ interface DateProvider {
|
|||
timestamp: TimeInMillis,
|
||||
timeStyle: Int = DEFAULT_DATE_FORMAT_STYLE
|
||||
): Pair<String, String>
|
||||
fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate?
|
||||
fun calculateRelativeDates(dateInSeconds: TimeInSeconds?): RelativeDate
|
||||
fun isSameMinute(timestamp1: Long, timestamp2: Long): Boolean
|
||||
fun getLocalDateOfTime(epochMilli: Long): LocalDate
|
||||
fun isTimestampWithinYearRange(timeStampInMillis: Long, yearRange: IntRange): Boolean
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId
|
|||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
|
||||
/**
|
||||
* Use-case for opening an object as preview — without subscribing to its subsequent changes.
|
||||
|
@ -13,15 +14,24 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
|||
*/
|
||||
class GetObject(
|
||||
private val repo: BlockRepository,
|
||||
private val settings: UserSettingsRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<GetObject.Params, ObjectView>(dispatchers.io) {
|
||||
override suspend fun doWork(params: Params): ObjectView = repo.getObject(
|
||||
id = params.target,
|
||||
space = params.space
|
||||
)
|
||||
).also {
|
||||
if (params.saveAsLastOpened) {
|
||||
settings.setLastOpenedObject(
|
||||
id = params.target,
|
||||
space = params.space
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val target: Id,
|
||||
val space: SpaceId
|
||||
val space: SpaceId,
|
||||
val saveAsLastOpened: Boolean = false
|
||||
)
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.anytypeio.anytype.core_models.MAX_SNIPPET_SIZE
|
|||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.RelativeDate
|
||||
import com.anytypeio.anytype.core_models.SupportedLayouts
|
||||
import com.anytypeio.anytype.core_models.TimeInSeconds
|
||||
import com.anytypeio.anytype.core_models.primitives.Field
|
||||
|
@ -91,7 +92,7 @@ class FieldParserImpl @Inject constructor(
|
|||
private fun calculateFieldDate(value: Value.Single<Long>?): Field.Date? {
|
||||
val dateInSeconds = value?.single ?: return null
|
||||
val relativeDate = dateProvider.calculateRelativeDates(dateInSeconds)
|
||||
if (relativeDate == null) {
|
||||
if (relativeDate is RelativeDate.Empty) {
|
||||
return null
|
||||
}
|
||||
return Field.Date(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1838,4 +1838,12 @@ Please provide specific details of your needs here.</string>
|
|||
<string name="chat_attachment_upload">Upload</string>
|
||||
<string name="chats_reply">Reply</string>
|
||||
|
||||
<string name="day_of_week_monday">Monday</string>
|
||||
<string name="day_of_week_tuesday">Tuesday</string>
|
||||
<string name="day_of_week_wednesday">Wednesday</string>
|
||||
<string name="day_of_week_thursday">Thursday</string>
|
||||
<string name="day_of_week_friday">Friday</string>
|
||||
<string name="day_of_week_saturday">Saturday</string>
|
||||
<string name="day_of_week_sunday">Sunday</string>
|
||||
|
||||
</resources>
|
|
@ -241,7 +241,7 @@ class SplashViewModel(
|
|||
success = { response ->
|
||||
when (response) {
|
||||
is GetLastOpenedObject.Response.Success -> {
|
||||
if (SupportedLayouts.layouts.contains(response.obj.layout)) {
|
||||
if (SupportedLayouts.lastOpenObjectLayouts.contains(response.obj.layout)) {
|
||||
val id = response.obj.id
|
||||
val space = requireNotNull(response.obj.spaceId)
|
||||
when (response.obj.layout) {
|
||||
|
@ -252,6 +252,14 @@ class SplashViewModel(
|
|||
space = space
|
||||
)
|
||||
)
|
||||
ObjectType.Layout.DATE -> {
|
||||
commands.emit(
|
||||
Command.NavigateToDateObject(
|
||||
id = id,
|
||||
space = space
|
||||
)
|
||||
)
|
||||
}
|
||||
else ->
|
||||
commands.emit(
|
||||
Command.NavigateToObject(
|
||||
|
@ -319,6 +327,7 @@ class SplashViewModel(
|
|||
data object CheckAppStartIntent : Command()
|
||||
data class NavigateToObject(val id: Id, val space: Id) : Command()
|
||||
data class NavigateToObjectSet(val id: Id, val space: Id) : Command()
|
||||
data class NavigateToDateObject(val id: Id, val space: Id) : Command()
|
||||
data class Toast(val message: String) : Command()
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue