diff --git a/libraries/dateformatter/impl/build.gradle.kts b/libraries/dateformatter/impl/build.gradle.kts index e814a1e2b8..2fb4f8461f 100644 --- a/libraries/dateformatter/impl/build.gradle.kts +++ b/libraries/dateformatter/impl/build.gradle.kts @@ -8,7 +8,7 @@ import extension.setupAnvil */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } setupAnvil() @@ -25,6 +25,7 @@ android { dependencies { implementation(libs.dagger) implementation(projects.libraries.core) + implementation(projects.libraries.designsystem) implementation(projects.libraries.di) implementation(projects.libraries.uiStrings) implementation(projects.services.toolbox.api) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt index e2637b5613..a041952fc3 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt @@ -26,12 +26,13 @@ class DateFormatters @Inject constructor( localeChangeObserver: LocaleChangeObserver, private val clock: Clock, private val timeZoneProvider: TimezoneProvider, + locale: Locale, ) : LocaleChangeListener { init { localeChangeObserver.addListener(this) } - private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(Locale.getDefault()) + private var dateTimeFormatters: DateTimeFormatters = DateTimeFormatters(locale) override fun onLocaleChange() { Timber.w("Locale changed, updating formatters") diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt new file mode 100644 index 0000000000..5b9f732ceb --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt @@ -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", + ), +) diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt new file mode 100644 index 0000000000..36d7acabfc --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt @@ -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 { + override val values: Sequence + get() = DateFormatterMode.entries.asSequence() +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt new file mode 100644 index 0000000000..d12f7b0724 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt @@ -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, + ) + } + } + } + } +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt new file mode 100644 index 0000000000..cf9787e9d3 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt @@ -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, + ), + ) +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt new file mode 100644 index 0000000000..3486d169a2 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt @@ -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 +} diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt new file mode 100644 index 0000000000..6498b30d88 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt @@ -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) + } +} diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt index 98b20c81b6..dd1572fde6 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.dateformatter.impl import io.element.android.tests.testutils.InstrumentationStringProvider import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone +import java.util.Locale /** * Create DefaultDateFormatter and set current time to the provided date. @@ -21,6 +22,7 @@ fun createFormatter(currentDate: String): DefaultDateFormatter { localeChangeObserver = {}, clock = clock, timeZoneProvider = { TimeZone.UTC }, + locale = Locale.getDefault(), ) val stringProvider = InstrumentationStringProvider() val dateFormatterDay = DefaultDateFormatterDay(