Add preview for date rendering
This commit is contained in:
@@ -8,7 +8,7 @@ import extension.setupAnvil
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAnvil()
|
setupAnvil()
|
||||||
@@ -25,6 +25,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.dagger)
|
implementation(libs.dagger)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.di)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.services.toolbox.api)
|
implementation(projects.services.toolbox.api)
|
||||||
|
|||||||
@@ -26,12 +26,13 @@ class DateFormatters @Inject constructor(
|
|||||||
localeChangeObserver: LocaleChangeObserver,
|
localeChangeObserver: LocaleChangeObserver,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
private val timeZoneProvider: TimezoneProvider,
|
private val timeZoneProvider: TimezoneProvider,
|
||||||
|
locale: Locale,
|
||||||
) : LocaleChangeListener {
|
) : LocaleChangeListener {
|
||||||
init {
|
init {
|
||||||
localeChangeObserver.addListener(this)
|
localeChangeObserver.addListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(Locale.getDefault())
|
private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(locale)
|
||||||
|
|
||||||
override fun onLocaleChange() {
|
override fun onLocaleChange() {
|
||||||
Timber.w("Locale changed, updating formatters")
|
Timber.w("Locale changed, updating formatters")
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
data class DateForPreview(
|
||||||
|
val semantic: String,
|
||||||
|
val date: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
val dateForPreviewToday = DateForPreview(
|
||||||
|
semantic = "Today",
|
||||||
|
date = "1980-04-06T18:35:24.00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
val dateForPreviews = listOf(
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "Now",
|
||||||
|
date = dateForPreviewToday.date,
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One second ago",
|
||||||
|
date = "1980-04-06T18:35:23.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One minute ago",
|
||||||
|
date = "1980-04-06T18:34:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One hour ago",
|
||||||
|
date = "1980-04-06T17:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One day ago",
|
||||||
|
date = "1980-04-05T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "Two days ago",
|
||||||
|
date = "1980-04-04T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One month ago",
|
||||||
|
date = "1980-03-06T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
DateForPreview(
|
||||||
|
semantic = "One year ago",
|
||||||
|
date = "1979-04-06T18:35:24.00Z",
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
|
||||||
|
class DateFormatterModeProvider : PreviewParameterProvider<DateFormatterMode> {
|
||||||
|
override val values: Sequence<DateFormatterMode>
|
||||||
|
get() = DateFormatterMode.entries.asSequence()
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
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.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.intl.Locale
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.utils.allBooleans
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun DateFormatterModeViewPreview(
|
||||||
|
@PreviewParameter(DateFormatterModeProvider::class) dateFormatterMode: DateFormatterMode,
|
||||||
|
) = ElementPreview {
|
||||||
|
DateFormatterModeView(dateFormatterMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DateFormatterModeView(
|
||||||
|
mode: DateFormatterMode,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val composeLocale = Locale.current
|
||||||
|
val dateFormatter = remember {
|
||||||
|
createFormatter(
|
||||||
|
context = context,
|
||||||
|
currentDate = dateForPreviewToday.date,
|
||||||
|
locale = java.util.Locale.Builder()
|
||||||
|
.setLanguageTag(composeLocale.toLanguageTag())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(4.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Mode $mode / $composeLocale",
|
||||||
|
style = ElementTheme.typography.fontHeadingSmMedium
|
||||||
|
)
|
||||||
|
val today = Instant.parse(dateForPreviewToday.date).toEpochMilliseconds()
|
||||||
|
Text(
|
||||||
|
text = "Today is: ${dateFormatter.format(today, DateFormatterMode.Full, useRelative = false)}",
|
||||||
|
style = ElementTheme.typography.fontHeadingSmMedium,
|
||||||
|
)
|
||||||
|
dateForPreviews.forEach { dateForPreview ->
|
||||||
|
DateForPreviewItem(
|
||||||
|
dateForPreview = dateForPreview,
|
||||||
|
dateFormatter = dateFormatter,
|
||||||
|
mode = mode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DateForPreviewItem(
|
||||||
|
dateForPreview: DateForPreview,
|
||||||
|
dateFormatter: DefaultDateFormatter,
|
||||||
|
mode: DateFormatterMode,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 8.dp),
|
||||||
|
text = dateForPreview.semantic,
|
||||||
|
style = ElementTheme.typography.fontBodyMdMedium,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
)
|
||||||
|
val ts = Instant.parse(dateForPreview.date).toEpochMilliseconds()
|
||||||
|
Row {
|
||||||
|
Column {
|
||||||
|
listOf("Absolute:", "Relative:").forEach { label ->
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Column {
|
||||||
|
allBooleans.forEach { useRelative ->
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
text = dateFormatter.format(ts, mode, useRelative),
|
||||||
|
style = ElementTheme.typography.fontBodyMdRegular,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterFull
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterMonth
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterTime
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatterTimeOnly
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DateFormatters
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.impl.DefaultDateFormatterDay
|
||||||
|
import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create DefaultDateFormatter and set current time to the provided date.
|
||||||
|
*/
|
||||||
|
fun createFormatter(
|
||||||
|
context: Context,
|
||||||
|
currentDate: String,
|
||||||
|
locale: Locale,
|
||||||
|
): DefaultDateFormatter {
|
||||||
|
val clock = PreviewClock().apply { givenInstant(Instant.parse(currentDate)) }
|
||||||
|
val localDateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.UTC }
|
||||||
|
val dateFormatters = DateFormatters(
|
||||||
|
localeChangeObserver = {},
|
||||||
|
clock = clock,
|
||||||
|
timeZoneProvider = { TimeZone.UTC },
|
||||||
|
locale = locale,
|
||||||
|
)
|
||||||
|
val stringProvider = PreviewStringProvider(context.resources)
|
||||||
|
val dateFormatterDay = DefaultDateFormatterDay(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
)
|
||||||
|
return DefaultDateFormatter(
|
||||||
|
dateFormatterFull = DateFormatterFull(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
dateFormatterDay = dateFormatterDay,
|
||||||
|
),
|
||||||
|
dateFormatterMonth = DateFormatterMonth(
|
||||||
|
stringProvider = stringProvider,
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
dateFormatterDay = dateFormatterDay,
|
||||||
|
dateFormatterTime = DateFormatterTime(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
dateFormatterTimeOnly = DateFormatterTimeOnly(
|
||||||
|
localDateTimeProvider = localDateTimeProvider,
|
||||||
|
dateFormatters = dateFormatters,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023, 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
class PreviewClock : Clock {
|
||||||
|
private var instant: Instant = Instant.fromEpochMilliseconds(0)
|
||||||
|
|
||||||
|
fun givenInstant(instant: Instant) {
|
||||||
|
this.instant = instant
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun now(): Instant = instant
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl.previews
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.PluralsRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
|
||||||
|
class PreviewStringProvider(
|
||||||
|
private val resources: Resources
|
||||||
|
) : StringProvider {
|
||||||
|
override fun getString(@StringRes resId: Int): String {
|
||||||
|
return resources.getString(resId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resources.getString(resId, *formatArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
|
||||||
|
return resources.getQuantityString(resId, quantity, *formatArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ package io.element.android.libraries.dateformatter.impl
|
|||||||
import io.element.android.tests.testutils.InstrumentationStringProvider
|
import io.element.android.tests.testutils.InstrumentationStringProvider
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create DefaultDateFormatter and set current time to the provided date.
|
* Create DefaultDateFormatter and set current time to the provided date.
|
||||||
@@ -21,6 +22,7 @@ fun createFormatter(currentDate: String): DefaultDateFormatter {
|
|||||||
localeChangeObserver = {},
|
localeChangeObserver = {},
|
||||||
clock = clock,
|
clock = clock,
|
||||||
timeZoneProvider = { TimeZone.UTC },
|
timeZoneProvider = { TimeZone.UTC },
|
||||||
|
locale = Locale.getDefault(),
|
||||||
)
|
)
|
||||||
val stringProvider = InstrumentationStringProvider()
|
val stringProvider = InstrumentationStringProvider()
|
||||||
val dateFormatterDay = DefaultDateFormatterDay(
|
val dateFormatterDay = DefaultDateFormatterDay(
|
||||||
|
|||||||
Reference in New Issue
Block a user