Merge pull request #2040 from vector-im/feature/date_formatting

Feature/date formatting
This commit is contained in:
Benoit Marty 2020-09-09 12:16:18 +02:00 committed by GitHub
commit 94e43475e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 366 additions and 126 deletions

View File

@ -5,7 +5,7 @@ Features ✨:
-
Improvements 🙌:
-
- Handle date formatting properly (show time am/pm if needed, display year when needed)
Bugfix 🐛:
-

View File

@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt
enum class===76
enum class===77
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2020 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 im.vector.app.core.date
import android.text.format.DateFormat
import im.vector.app.core.resources.LocaleProvider
import org.threeten.bp.format.DateTimeFormatter
import javax.inject.Inject
class AbbrevDateFormatterProvider @Inject constructor(private val localeProvider: LocaleProvider) : DateFormatterProvider {
override val dateWithMonthFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMM")
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
}
override val dateWithYearFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "dd.MM.yyyy")
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 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 im.vector.app.core.date
/* This will represent all kind of available date formats for the app.
We will use the date Sep 7 2020 at 9:30am as an example.
The formatting is depending of the current date.
*/
enum class DateFormatKind {
// Will show date relative and time (today or yesterday or Sep 7 or 09/07/2020 at 9:30am)
DEFAULT_DATE_AND_TIME,
// Will show hour or date relative (9:30am or yesterday or Sep 7 or 09/07/2020)
ROOM_LIST,
// Will show full date (Sep 7 2020)
TIMELINE_DAY_DIVIDER,
// Will show full date and time (Mon, Sep 7 2020, 9:30am)
MESSAGE_DETAIL,
// Will only show time (9:30am)
MESSAGE_SIMPLE,
// Will only show time (9:30am)
EDIT_HISTORY_ROW,
// Will only show date relative (today or yesterday or Sep 7 or 09/07/2020)
EDIT_HISTORY_HEADER
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2020 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 im.vector.app.core.date
import org.threeten.bp.format.DateTimeFormatter
interface DateFormatterProvider {
val dateWithMonthFormatter: DateTimeFormatter
val dateWithYearFormatter: DateTimeFormatter
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 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 im.vector.app.core.date
import javax.inject.Inject
class DateFormatterProviders @Inject constructor(private val defaultDateFormatterProvider: DefaultDateFormatterProvider,
private val abbrevDateFormatterProvider: AbbrevDateFormatterProvider) {
fun provide(abbrev: Boolean): DateFormatterProvider {
return if (abbrev) {
abbrevDateFormatterProvider
} else {
defaultDateFormatterProvider
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2020 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 im.vector.app.core.date
import android.content.Context
import android.text.format.DateFormat
import im.vector.app.core.resources.LocaleProvider
import org.threeten.bp.format.DateTimeFormatter
import javax.inject.Inject
class DefaultDateFormatterProvider @Inject constructor(private val context: Context,
private val localeProvider: LocaleProvider)
: DateFormatterProvider {
override val dateWithMonthFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMMMM")
DateTimeFormatter.ofPattern(pattern)
}
override val dateWithYearFormatter: DateTimeFormatter by lazy {
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMM y")
DateTimeFormatter.ofPattern(pattern)
}
}

View File

@ -19,64 +19,147 @@ package im.vector.app.core.date
import android.content.Context
import android.text.format.DateFormat
import android.text.format.DateUtils
import im.vector.app.core.resources.DateProvider
import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.toTimestamp
import org.threeten.bp.LocalDateTime
import org.threeten.bp.Period
import org.threeten.bp.format.DateTimeFormatter
import java.util.Calendar
import java.util.Date
import javax.inject.Inject
/**
* Returns the timestamp for the start of the day of the provided time.
* For example, for the time "Jul 21, 11:11" the start of the day: "Jul 21, 00:00" is returned.
*/
fun startOfDay(time: Long): Long {
val calendar = Calendar.getInstance()
calendar.time = Date(time)
calendar.set(Calendar.HOUR_OF_DAY, 0)
calendar.set(Calendar.MINUTE, 0)
calendar.set(Calendar.SECOND, 0)
calendar.set(Calendar.MILLISECOND, 0)
return calendar.time.time
}
import kotlin.math.absoluteValue
class VectorDateFormatter @Inject constructor(private val context: Context,
private val localeProvider: LocaleProvider) {
private val localeProvider: LocaleProvider,
private val dateFormatterProviders: DateFormatterProviders) {
private val messageHourFormatter by lazy {
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
private val hourFormatter by lazy {
if (DateFormat.is24HourFormat(context)) {
DateTimeFormatter.ofPattern("HH:mm", localeProvider.current())
} else {
DateTimeFormatter.ofPattern("h:mm a", localeProvider.current())
}
}
private val messageDayFormatter by lazy {
DateTimeFormatter.ofPattern(DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE d MMM"))
}
fun formatMessageHour(localDateTime: LocalDateTime): String {
return messageHourFormatter.format(localDateTime)
}
fun formatMessageDay(localDateTime: LocalDateTime): String {
return messageDayFormatter.format(localDateTime)
private val fullDateFormatter by lazy {
val pattern = if (DateFormat.is24HourFormat(context)) {
DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE, d MMM yyyy HH:mm")
} else {
DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE, d MMM yyyy h:mm a")
}
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
}
/**
* Formats a localized relative date time for the last 2 days, e.g, "Today, HH:MM", "Yesterday, HH:MM" or
* "2 days ago, HH:MM".
* For earlier timestamps the absolute date time is returned, e.g. "Month Day, HH:MM".
* This method is used to format some date in the app.
* It will be able to show only time, only date or both with some logic.
* @param ts the timestamp to format or null.
* @param dateFormatKind the kind of format to use
*
* @param time the absolute timestamp [ms] that should be formatted relative to now
* @return the formatted date as string.
*/
fun formatRelativeDateTime(time: Long?): String {
if (time == null) {
fun format(ts: Long?, dateFormatKind: DateFormatKind): String {
if (ts == null) return "-"
val localDateTime = DateProvider.toLocalDateTime(ts)
return when (dateFormatKind) {
DateFormatKind.DEFAULT_DATE_AND_TIME -> formatDateAndTime(ts)
DateFormatKind.ROOM_LIST -> formatTimeOrDate(
date = localDateTime,
showTimeIfSameDay = true,
abbrev = true,
useRelative = true
)
DateFormatKind.TIMELINE_DAY_DIVIDER -> formatTimeOrDate(
date = localDateTime,
alwaysShowYear = true
)
DateFormatKind.MESSAGE_DETAIL -> formatFullDate(localDateTime)
DateFormatKind.MESSAGE_SIMPLE -> formatHour(localDateTime)
DateFormatKind.EDIT_HISTORY_ROW -> formatHour(localDateTime)
DateFormatKind.EDIT_HISTORY_HEADER -> formatTimeOrDate(
date = localDateTime,
abbrev = true,
useRelative = true
)
}
}
private fun formatFullDate(localDateTime: LocalDateTime): String {
return fullDateFormatter.format(localDateTime)
}
private fun formatHour(localDateTime: LocalDateTime): String {
return hourFormatter.format(localDateTime)
}
private fun formatDateWithMonth(localDateTime: LocalDateTime, abbrev: Boolean = false): String {
return dateFormatterProviders.provide(abbrev).dateWithMonthFormatter.format(localDateTime)
}
private fun formatDateWithYear(localDateTime: LocalDateTime, abbrev: Boolean = false): String {
return dateFormatterProviders.provide(abbrev).dateWithYearFormatter.format(localDateTime)
}
/**
* This method will only show time or date following the parameters.
*/
private fun formatTimeOrDate(
date: LocalDateTime?,
showTimeIfSameDay: Boolean = false,
useRelative: Boolean = false,
alwaysShowYear: Boolean = false,
abbrev: Boolean = false
): String {
if (date == null) {
return ""
}
val now = System.currentTimeMillis()
return DateUtils.getRelativeDateTimeString(
context,
time,
val currentDate = DateProvider.currentLocalDateTime()
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
return if (showTimeIfSameDay && isSameDay) {
formatHour(date)
} else {
formatDate(date, currentDate, alwaysShowYear, abbrev, useRelative)
}
}
private fun formatDate(
date: LocalDateTime,
currentDate: LocalDateTime,
alwaysShowYear: Boolean,
abbrev: Boolean,
useRelative: Boolean
): String {
val period = Period.between(date.toLocalDate(), currentDate.toLocalDate())
return if (period.years.absoluteValue >= 1 || alwaysShowYear) {
formatDateWithYear(date, abbrev)
} else if (useRelative && period.days.absoluteValue < 2 && period.months.absoluteValue < 1) {
getRelativeDay(date.toTimestamp())
} else {
formatDateWithMonth(date, abbrev)
}
}
/**
* This method will show date and time with a preposition
*/
private fun formatDateAndTime(ts: Long): String {
val date = DateProvider.toLocalDateTime(ts)
val currentDate = DateProvider.currentLocalDateTime()
// This fake date is created to be able to use getRelativeTimeSpanString so we can get a "at"
// preposition and the right am/pm management.
val fakeDate = LocalDateTime.of(currentDate.toLocalDate(), date.toLocalTime())
val formattedTime = DateUtils.getRelativeTimeSpanString(context, fakeDate.toTimestamp(), true).toString()
val formattedDate = formatDate(date, currentDate, alwaysShowYear = false, abbrev = true, useRelative = true)
return "$formattedDate $formattedTime"
}
/**
* We are using this method for the keywords Today/Yesterday
*/
private fun getRelativeDay(ts: Long): String {
return DateUtils.getRelativeTimeSpanString(
ts,
System.currentTimeMillis(),
DateUtils.DAY_IN_MILLIS,
now - startOfDay(now - 2 * DateUtils.DAY_IN_MILLIS),
DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_TIME
).toString()
DateUtils.FORMAT_SHOW_WEEKDAY).toString()
}
}

View File

@ -23,6 +23,10 @@ import org.threeten.bp.ZoneId
object DateProvider {
private val zoneId = ZoneId.systemDefault()
private val zoneOffset by lazy {
val now = currentLocalDateTime()
zoneId.rules.getOffset(now)
}
fun toLocalDateTime(timestamp: Long?): LocalDateTime {
val instant = Instant.ofEpochMilli(timestamp ?: 0)
@ -33,4 +37,10 @@ object DateProvider {
val instant = Instant.now()
return LocalDateTime.ofInstant(instant, zoneId)
}
fun toTimestamp(localDateTime: LocalDateTime): Long {
return localDateTime.toInstant(zoneOffset).toEpochMilli()
}
}
fun LocalDateTime.toTimestamp(): Long = DateProvider.toTimestamp(this)

View File

@ -21,6 +21,8 @@ package im.vector.app.features.crypto.keysrequest
import android.content.Context
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.features.popup.DefaultVectorAlert
import im.vector.app.features.popup.PopupAlertManager
import org.matrix.android.sdk.api.MatrixCallback
@ -38,10 +40,6 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import timber.log.Timber
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
@ -54,8 +52,11 @@ import javax.inject.Singleton
*/
@Singleton
class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager)
: GossipingRequestListener,
class KeyRequestHandler @Inject constructor(
private val context: Context,
private val popupAlertManager: PopupAlertManager,
private val dateFormatter: VectorDateFormatter
) : GossipingRequestListener,
VerificationService.Listener {
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
@ -156,16 +157,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context, privat
moreInfo.lastSeenIp
}
val lastSeenTime = moreInfo.lastSeenTs?.let { ts ->
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
val date = Date(ts)
val time = dateFormatTime.format(date)
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
dateFormat.format(date) + ", " + time
} ?: "-"
val lastSeenTime = dateFormatter.format(moreInfo.lastSeenTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
val lastSeenInfo = context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
dialogText = if (wasNewDevice) {
context.getString(R.string.you_added_a_new_device_with_info, deviceName, lastSeenInfo)

View File

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.detail.readreceipts
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
@ -36,7 +37,7 @@ class DisplayReadReceiptsController @Inject constructor(private val dateFormatte
override fun buildModels(readReceipts: List<ReadReceiptData>) {
readReceipts.forEach {
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
val timestamp = dateFormatter.format(it.timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
DisplayReadReceiptItem_()
.id(it.userId)
.matrixItem(it.toMatrixItem())

View File

@ -25,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.VisibilityState
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.LoadingItem_
import im.vector.app.core.extensions.localDateTime
@ -53,7 +54,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoCon
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.threeten.bp.LocalDateTime
import javax.inject.Inject
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
@ -333,13 +333,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
) {
requestModelBuild()
}
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date)
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, event.root.originServerTs)
return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem)
}
private fun buildDaySeparatorItem(addDaySeparator: Boolean, date: LocalDateTime): DaySeparatorItem? {
private fun buildDaySeparatorItem(addDaySeparator: Boolean, originServerTs: Long?): DaySeparatorItem? {
return if (addDaySeparator) {
val formattedDay = dateFormatter.formatMessageDay(date)
val formattedDay = dateFormatter.format(originServerTs, DateFormatKind.TIMELINE_DAY_DIVIDER)
DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)
} else {
null

View File

@ -22,9 +22,6 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.app.core.extensions.canReact
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* Quick reactions state
@ -56,11 +53,7 @@ data class MessageActionState(
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)
private val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
fun senderName(): String = informationData.memberName?.toString() ?: ""
fun time(): String? = timelineEvent()?.root?.originServerTs?.let { dateFormat.format(Date(it)) } ?: ""
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
}

View File

@ -20,6 +20,8 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Success
import im.vector.app.EmojiCompatFontProvider
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.bottomsheet.BottomSheetQuickReactionsItem
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
import im.vector.app.core.epoxy.bottomsheet.bottomSheetMessagePreviewItem
@ -40,13 +42,16 @@ import javax.inject.Inject
class MessageActionsEpoxyController @Inject constructor(
private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer,
private val fontProvider: EmojiCompatFontProvider
private val fontProvider: EmojiCompatFontProvider,
private val dateFormatter: VectorDateFormatter
) : TypedEpoxyController<MessageActionState>() {
var listener: MessageActionsEpoxyControllerListener? = null
override fun buildModels(state: MessageActionState) {
// Message preview
val date = state.timelineEvent()?.root?.originServerTs
val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL)
bottomSheetMessagePreviewItem {
id("preview")
avatarRenderer(avatarRenderer)
@ -54,7 +59,7 @@ class MessageActionsEpoxyController @Inject constructor(
movementMethod(createLinkMovementMethod(listener))
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
body(state.messageBody.linkify(listener))
time(state.time())
time(formattedDate)
}
// Send state

View File

@ -17,27 +17,26 @@ package im.vector.app.features.home.room.detail.timeline.edithistory
import android.content.Context
import android.text.Spannable
import android.text.format.DateUtils
import androidx.core.content.ContextCompat
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.extensions.localDateTime
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.genericItem
import im.vector.app.core.ui.list.genericItemHeader
import im.vector.app.core.ui.list.genericLoaderItem
import im.vector.app.features.html.EventHtmlRenderer
import me.gujun.android.span.span
import name.fraser.neil.plaintext.diff_match_patch
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
import org.matrix.android.sdk.internal.session.room.send.TextContent
import me.gujun.android.span.span
import name.fraser.neil.plaintext.diff_match_patch
import java.util.Calendar
/**
@ -82,11 +81,9 @@ class ViewEditHistoryEpoxyController(private val context: Context,
}
if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
// need to display header with day
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
else dateFormatter.formatMessageDay(timelineEvent.localDateTime())
genericItemHeader {
id(evDate.hashCode())
text(dateString)
text(dateFormatter.format(evDate.timeInMillis, DateFormatKind.EDIT_HISTORY_HEADER))
}
}
lastDate = evDate
@ -130,7 +127,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
}
genericItem {
id(timelineEvent.eventId)
title(dateFormatter.formatMessageHour(timelineEvent.localDateTime()))
title(dateFormatter.format(timelineEvent.originServerTs, DateFormatKind.EDIT_HISTORY_ROW))
description(spannedDiff ?: body)
}
}

View File

@ -18,6 +18,7 @@
package im.vector.app.features.home.room.detail.timeline.helper
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.extensions.localDateTime
import im.vector.app.core.resources.ColorProvider
@ -68,7 +69,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|| isNextMessageReceivedMoreThanOneHourAgo
|| isTileTypeMessage(nextEvent)
val time = dateFormatter.formatMessageHour(date)
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
val e2eDecoration = getE2EDecoration(event)
return MessageInformationData(

View File

@ -24,17 +24,18 @@ import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
import io.reactivex.Observable
import io.reactivex.Single
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
import org.matrix.android.sdk.rx.RxRoom
import org.matrix.android.sdk.rx.unwrap
import io.reactivex.Observable
import io.reactivex.Single
data class DisplayReactionsViewState(
val eventId: String,
@ -112,7 +113,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
summary.key,
event.root.senderId ?: "",
event.senderInfo.disambiguatedDisplayName,
dateFormatter.formatRelativeDateTime(event.root.originServerTs)
dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
)
}

View File

@ -18,10 +18,9 @@ package im.vector.app.features.home.room.list
import android.view.View
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.localDateTime
import im.vector.app.core.resources.DateProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
@ -53,8 +52,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
}
private fun createInvitationItem(roomSummary: RoomSummary,
changeMembershipState: ChangeMembershipState,
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
changeMembershipState: ChangeMembershipState,
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
val secondLine = if (roomSummary.isDirect) {
roomSummary.inviterId
} else {
@ -87,15 +86,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
var latestEventTime: CharSequence = ""
val latestEvent = roomSummary.latestPreviewableEvent
if (latestEvent != null) {
val date = latestEvent.root.localDateTime()
val currentDate = DateProvider.currentLocalDateTime()
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect.not())
latestEventTime = if (isSameDay) {
dateFormatter.formatMessageHour(date)
} else {
dateFormatter.formatMessageDay(date)
}
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
}
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
return RoomSummaryItem_()

View File

@ -19,8 +19,8 @@ package im.vector.app.features.media
import android.content.Context
import android.view.View
import androidx.core.view.isVisible
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.extensions.localDateTime
import im.vector.lib.attachmentviewer.AttachmentInfo
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
@ -78,9 +78,7 @@ class DataAttachmentRoomProvider(
val item = attachments[position]
val timeLineEvent = room?.getTimeLineEvent(item.eventId)
if (timeLineEvent != null) {
val dateString = timeLineEvent.root.localDateTime().let {
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
}
val dateString = dateFormatter.format(timeLineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${timeLineEvent.senderInfo.displayName} $dateString")
overlayView?.videoControlsGroup?.isVisible = timeLineEvent.root.isVideoMessage()
} else {

View File

@ -19,8 +19,8 @@ package im.vector.app.features.media
import android.content.Context
import android.view.View
import androidx.core.view.isVisible
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.extensions.localDateTime
import im.vector.lib.attachmentviewer.AttachmentInfo
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.Session
@ -128,9 +128,7 @@ class RoomEventsAttachmentProvider(
override fun overlayViewAtPosition(context: Context, position: Int): View? {
super.overlayViewAtPosition(context, position)
val item = attachments[position]
val dateString = item.root.localDateTime().let {
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
}
val dateString = dateFormatter.format(item.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${item.senderInfo.displayName} $dateString")
overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage()
return overlayView

View File

@ -18,12 +18,13 @@ package im.vector.app.features.roomprofile.uploads.files
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.epoxy.VisibilityState
import org.matrix.android.sdk.api.session.room.uploads.UploadEvent
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.roomprofile.uploads.RoomUploadsViewState
import org.matrix.android.sdk.api.session.room.uploads.UploadEvent
import javax.inject.Inject
class UploadsFileController @Inject constructor(
@ -71,7 +72,7 @@ class UploadsFileController @Inject constructor(
title(uploadEvent.contentWithAttachmentContent.body)
subtitle(stringProvider.getString(R.string.uploads_files_subtitle,
uploadEvent.senderInfo.disambiguatedDisplayName,
dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs)))
dateFormatter.format(uploadEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)))
listener(object : UploadsFileItem.Listener {
override fun onItemClicked() {
listener?.onOpenClicked(uploadEvent)

View File

@ -31,10 +31,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.DimensionConverter
import me.gujun.android.span.span
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* A list item for Device.
@ -45,6 +41,9 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
@EpoxyAttribute
lateinit var deviceInfo: DeviceInfo
@EpoxyAttribute
var lastSeenFormatted: String? = null
@EpoxyAttribute
var currentDevice = false
@ -105,15 +104,7 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
val lastSeenIp = deviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
val lastSeenTime = deviceInfo.lastSeenTs?.let { ts ->
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.ROOT)
val date = Date(ts)
val time = dateFormatTime.format(date)
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
dateFormat.format(date) + ", " + time
} ?: "-"
val lastSeenTime = lastSeenFormatted ?: "-"
holder.deviceLastSeenText.text = holder.root.context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)

View File

@ -21,9 +21,9 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.errorWithRetryItem
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.error.ErrorFormatter
@ -32,11 +32,14 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericItemHeader
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import javax.inject.Inject
class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter,
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider,
private val dateFormatter: VectorDateFormatter,
private val dimensionConverter: DimensionConverter,
private val vectorPreferences: VectorPreferences) : EpoxyController() {
@ -100,6 +103,7 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
deviceInfo(deviceInfo)
currentDevice(true)
e2eCapable(true)
lastSeenFormatted(dateFormatter.format(deviceInfo.lastSeenTs, DateFormatKind.DEFAULT_DATE_AND_TIME))
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
trusted(DeviceTrustLevel(currentSessionCrossTrusted, true))
}

View File

@ -31,11 +31,11 @@ import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
import im.vector.app.R
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DateProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.GenericItem
import im.vector.app.core.ui.list.genericFooterItem
@ -94,8 +94,7 @@ class GossipingEventsEpoxyController @Inject constructor(
)
description(
span {
+vectorDateFormatter.formatMessageDay(DateProvider.toLocalDateTime(event.ageLocalTs))
+" ${vectorDateFormatter.formatMessageHour(DateProvider.toLocalDateTime(event.ageLocalTs))}"
+vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
span("\nfrom: ") {
textStyle = "bold"
}