mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Merge pull request #2040 from vector-im/feature/date_formatting
Feature/date formatting
This commit is contained in:
commit
94e43475e2
24 changed files with 366 additions and 126 deletions
|
@ -5,7 +5,7 @@ Features ✨:
|
|||
-
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
- Handle date formatting properly (show time am/pm if needed, display year when needed)
|
||||
|
||||
Bugfix 🐛:
|
||||
-
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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_()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue