Use DaySeperatorFormatter
This commit is contained in:
parent
085dbec3f7
commit
2bbdecb6f0
7 changed files with 219 additions and 87 deletions
|
|
@ -39,6 +39,7 @@ dependencies {
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.textcomposer)
|
implementation(projects.libraries.textcomposer)
|
||||||
|
implementation(projects.libraries.dateformatter)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.datetime)
|
implementation(libs.datetime)
|
||||||
implementation(libs.accompanist.flowlayout)
|
implementation(libs.accompanist.flowlayout)
|
||||||
|
|
|
||||||
|
|
@ -18,30 +18,16 @@ package io.element.android.features.messages.timeline.factories.virtual
|
||||||
|
|
||||||
import io.element.android.features.messages.timeline.model.virtual.TimelineItemDaySeparatorModel
|
import io.element.android.features.messages.timeline.model.virtual.TimelineItemDaySeparatorModel
|
||||||
import io.element.android.features.messages.timeline.model.virtual.TimelineItemVirtualModel
|
import io.element.android.features.messages.timeline.model.virtual.TimelineItemVirtualModel
|
||||||
import kotlinx.datetime.Instant
|
import io.element.android.libraries.dateformatter.DaySeparatorFormatter
|
||||||
import kotlinx.datetime.TimeZone
|
|
||||||
import kotlinx.datetime.toJavaLocalDateTime
|
|
||||||
import kotlinx.datetime.toLocalDateTime
|
|
||||||
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
|
import org.matrix.rustcomponents.sdk.VirtualTimelineItem
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineItemDaySeparatorFactory @Inject constructor() {
|
class TimelineItemDaySeparatorFactory @Inject constructor(private val daySeparatorFormatter: DaySeparatorFormatter) {
|
||||||
|
|
||||||
//TODO use proper formatter
|
|
||||||
private val locale: Locale = Locale.getDefault()
|
|
||||||
|
|
||||||
private val dateWithYearFormatter: DateTimeFormatter by lazy {
|
|
||||||
val pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "dd.MM.yyyy")
|
|
||||||
DateTimeFormatter.ofPattern(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel {
|
fun create(virtualItem: VirtualTimelineItem.DayDivider): TimelineItemVirtualModel {
|
||||||
val tsInstant = Instant.fromEpochMilliseconds(virtualItem.ts.toLong())
|
val formattedDate = daySeparatorFormatter.format(virtualItem.ts.toLong())
|
||||||
val tsDateTime = tsInstant.toLocalDateTime(TimeZone.currentSystemDefault())
|
|
||||||
return TimelineItemDaySeparatorModel(
|
return TimelineItemDaySeparatorModel(
|
||||||
formattedDate = dateWithYearFormatter.format(tsDateTime.toJavaLocalDateTime())
|
formattedDate = formattedDate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter
|
||||||
|
|
||||||
|
interface DaySeparatorFormatter {
|
||||||
|
fun format(timestamp: Long): String
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toInstant
|
||||||
|
import kotlinx.datetime.toJavaLocalDate
|
||||||
|
import kotlinx.datetime.toJavaLocalDateTime
|
||||||
|
import java.time.Period
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
class DateFormatters @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val locale: Locale,
|
||||||
|
private val clock: Clock,
|
||||||
|
private val timeZone: TimeZone,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val hourFormatter by lazy {
|
||||||
|
if (DateFormat.is24HourFormat(context)) {
|
||||||
|
DateTimeFormatter.ofPattern("HH:mm", locale)
|
||||||
|
} else {
|
||||||
|
DateTimeFormatter.ofPattern("h:mm a", locale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fullDateFormatter by lazy {
|
||||||
|
val pattern = if (DateFormat.is24HourFormat(context)) {
|
||||||
|
DateFormat.getBestDateTimePattern(locale, "EEE, d MMM yyyy HH:mm")
|
||||||
|
} else {
|
||||||
|
DateFormat.getBestDateTimePattern(locale, "EEE, d MMM yyyy h:mm a")
|
||||||
|
}
|
||||||
|
DateTimeFormatter.ofPattern(pattern, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = DateFormat.getBestDateTimePattern(locale, "d MMM")
|
||||||
|
DateTimeFormatter.ofPattern(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateWithYearFormatter: DateTimeFormatter by lazy {
|
||||||
|
val pattern = DateFormat.getBestDateTimePattern(locale, "d MMM y")
|
||||||
|
DateTimeFormatter.ofPattern(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatFullDate(localDateTime: LocalDateTime): String {
|
||||||
|
return fullDateFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatHour(localDateTime: LocalDateTime): String {
|
||||||
|
return hourFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatDateWithMonth(localDateTime: LocalDateTime): String {
|
||||||
|
return dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatDateWithYear(localDateTime: LocalDateTime): String {
|
||||||
|
return dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun formatDate(
|
||||||
|
dateToFormat: LocalDateTime,
|
||||||
|
currentDate: LocalDateTime,
|
||||||
|
useRelative: Boolean
|
||||||
|
): String {
|
||||||
|
val period = Period.between(dateToFormat.date.toJavaLocalDate(), currentDate.date.toJavaLocalDate())
|
||||||
|
return if (period.years.absoluteValue >= 1) {
|
||||||
|
formatDateWithYear(dateToFormat)
|
||||||
|
} else if (useRelative && period.days.absoluteValue < 2 && period.months.absoluteValue < 1) {
|
||||||
|
getRelativeDay(dateToFormat.toInstant(timeZone).toEpochMilliseconds())
|
||||||
|
} else {
|
||||||
|
formatDateWithMonth(dateToFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRelativeDay(ts: Long): String {
|
||||||
|
return DateUtils.getRelativeTimeSpanString(
|
||||||
|
ts,
|
||||||
|
clock.now().toEpochMilliseconds(),
|
||||||
|
DateUtils.DAY_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_SHOW_WEEKDAY
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.dateformatter.DaySeparatorFormatter
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultDaySeparatorFormatter @Inject constructor(
|
||||||
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
|
private val dateFormatters: DateFormatters,
|
||||||
|
) : DaySeparatorFormatter {
|
||||||
|
|
||||||
|
override fun format(timestamp: Long): String {
|
||||||
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
|
return dateFormatters.formatDateWithYear(dateToFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,91 +16,33 @@
|
||||||
|
|
||||||
package io.element.android.libraries.dateformatter.impl
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
import android.text.format.DateFormat
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.dateformatter.LastMessageFormatter
|
import io.element.android.libraries.dateformatter.LastMessageFormatter
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import kotlinx.datetime.LocalDateTime
|
|
||||||
import kotlinx.datetime.TimeZone
|
|
||||||
import kotlinx.datetime.toInstant
|
|
||||||
import kotlinx.datetime.toJavaLocalDate
|
|
||||||
import kotlinx.datetime.toJavaLocalDateTime
|
|
||||||
import kotlinx.datetime.toLocalDateTime
|
|
||||||
import java.time.Period
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultLastMessageFormatter @Inject constructor(
|
class DefaultLastMessageFormatter @Inject constructor(
|
||||||
private val clock: Clock,
|
private val localDateTimeProvider: LocalDateTimeProvider,
|
||||||
private val locale: Locale,
|
private val dateFormatters: DateFormatters,
|
||||||
private val timezone: TimeZone,
|
|
||||||
) : LastMessageFormatter {
|
) : LastMessageFormatter {
|
||||||
private val onlyTimeFormatter: DateTimeFormatter by lazy {
|
|
||||||
val pattern = DateFormat.getBestDateTimePattern(locale, "HH:mm") ?: "HH:mm"
|
|
||||||
DateTimeFormatter.ofPattern(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
|
||||||
val pattern = DateFormat.getBestDateTimePattern(locale, "d MMM") ?: "d MMM"
|
|
||||||
DateTimeFormatter.ofPattern(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val dateWithYearFormatter: DateTimeFormatter by lazy {
|
|
||||||
val pattern = DateFormat.getBestDateTimePattern(locale, "dd.MM.yyyy") ?: "dd.MM.yyyy"
|
|
||||||
DateTimeFormatter.ofPattern(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun format(timestamp: Long?): String {
|
override fun format(timestamp: Long?): String {
|
||||||
if (timestamp == null) return ""
|
if (timestamp == null) return ""
|
||||||
val now: Instant = clock.now()
|
val currentDate = localDateTimeProvider.providesNow()
|
||||||
val tsInstant = Instant.fromEpochMilliseconds(timestamp)
|
val dateToFormat = localDateTimeProvider.providesFromTimestamp(timestamp)
|
||||||
val nowDateTime = now.toLocalDateTime(timezone)
|
val isSameDay = currentDate.date == dateToFormat.date
|
||||||
val tsDateTime = tsInstant.toLocalDateTime(timezone)
|
|
||||||
val isSameDay = nowDateTime.date == tsDateTime.date
|
|
||||||
return when {
|
return when {
|
||||||
isSameDay -> {
|
isSameDay -> {
|
||||||
onlyTimeFormatter.format(tsDateTime.toJavaLocalDateTime())
|
dateFormatters.formatHour(dateToFormat)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
formatDate(tsDateTime, nowDateTime)
|
dateFormatters.formatDate(
|
||||||
|
dateToFormat = dateToFormat,
|
||||||
|
currentDate = currentDate,
|
||||||
|
useRelative = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatDate(
|
|
||||||
date: LocalDateTime,
|
|
||||||
currentDate: LocalDateTime,
|
|
||||||
): String {
|
|
||||||
val period = Period.between(date.date.toJavaLocalDate(), currentDate.date.toJavaLocalDate())
|
|
||||||
return if (period.years.absoluteValue >= 1) {
|
|
||||||
formatDateWithYear(date)
|
|
||||||
} else if (period.days.absoluteValue < 2 && period.months.absoluteValue < 1) {
|
|
||||||
getRelativeDay(date.toInstant(timezone).toEpochMilliseconds())
|
|
||||||
} else {
|
|
||||||
formatDateWithMonth(date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatDateWithMonth(localDateTime: LocalDateTime): String {
|
|
||||||
return dateWithMonthFormatter.format(localDateTime.toJavaLocalDateTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun formatDateWithYear(localDateTime: LocalDateTime): String {
|
|
||||||
return dateWithYearFormatter.format(localDateTime.toJavaLocalDateTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRelativeDay(ts: Long): String {
|
|
||||||
return DateUtils.getRelativeTimeSpanString(
|
|
||||||
ts,
|
|
||||||
clock.now().toEpochMilliseconds(),
|
|
||||||
DateUtils.DAY_IN_MILLIS,
|
|
||||||
DateUtils.FORMAT_SHOW_WEEKDAY
|
|
||||||
)?.toString() ?: ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl
|
||||||
|
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class LocalDateTimeProvider @Inject constructor(
|
||||||
|
private val clock: Clock,
|
||||||
|
private val timezone: TimeZone,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun providesNow(): LocalDateTime {
|
||||||
|
val now: Instant = clock.now()
|
||||||
|
return now.toLocalDateTime(timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun providesFromTimestamp(timestamp: Long): LocalDateTime {
|
||||||
|
val tsInstant = Instant.fromEpochMilliseconds(timestamp)
|
||||||
|
return tsInstant.toLocalDateTime(timezone)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue