mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +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 🙌:
|
Improvements 🙌:
|
||||||
-
|
- Handle date formatting properly (show time am/pm if needed, display year when needed)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
-
|
-
|
||||||
|
|
|
@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# 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
|
### 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
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
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.content.Context
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.text.format.DateUtils
|
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.LocaleProvider
|
||||||
|
import im.vector.app.core.resources.toTimestamp
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
|
import org.threeten.bp.Period
|
||||||
import org.threeten.bp.format.DateTimeFormatter
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
|
|
||||||
class VectorDateFormatter @Inject constructor(private val context: Context,
|
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 {
|
private val hourFormatter by lazy {
|
||||||
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
if (DateFormat.is24HourFormat(context)) {
|
||||||
|
DateTimeFormatter.ofPattern("HH:mm", localeProvider.current())
|
||||||
|
} else {
|
||||||
|
DateTimeFormatter.ofPattern("h:mm a", localeProvider.current())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val messageDayFormatter by lazy {
|
private val fullDateFormatter by lazy {
|
||||||
DateTimeFormatter.ofPattern(DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE d MMM"))
|
val pattern = if (DateFormat.is24HourFormat(context)) {
|
||||||
}
|
DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE, d MMM yyyy HH:mm")
|
||||||
|
} else {
|
||||||
fun formatMessageHour(localDateTime: LocalDateTime): String {
|
DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE, d MMM yyyy h:mm a")
|
||||||
return messageHourFormatter.format(localDateTime)
|
}
|
||||||
}
|
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
|
||||||
|
|
||||||
fun formatMessageDay(localDateTime: LocalDateTime): String {
|
|
||||||
return messageDayFormatter.format(localDateTime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a localized relative date time for the last 2 days, e.g, "Today, HH:MM", "Yesterday, HH:MM" or
|
* This method is used to format some date in the app.
|
||||||
* "2 days ago, HH:MM".
|
* It will be able to show only time, only date or both with some logic.
|
||||||
* For earlier timestamps the absolute date time is returned, e.g. "Month Day, HH:MM".
|
* @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 {
|
fun format(ts: Long?, dateFormatKind: DateFormatKind): String {
|
||||||
if (time == null) {
|
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 ""
|
return ""
|
||||||
}
|
}
|
||||||
val now = System.currentTimeMillis()
|
val currentDate = DateProvider.currentLocalDateTime()
|
||||||
return DateUtils.getRelativeDateTimeString(
|
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
||||||
context,
|
return if (showTimeIfSameDay && isSameDay) {
|
||||||
time,
|
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,
|
DateUtils.DAY_IN_MILLIS,
|
||||||
now - startOfDay(now - 2 * DateUtils.DAY_IN_MILLIS),
|
DateUtils.FORMAT_SHOW_WEEKDAY).toString()
|
||||||
DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_TIME
|
|
||||||
).toString()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ import org.threeten.bp.ZoneId
|
||||||
object DateProvider {
|
object DateProvider {
|
||||||
|
|
||||||
private val zoneId = ZoneId.systemDefault()
|
private val zoneId = ZoneId.systemDefault()
|
||||||
|
private val zoneOffset by lazy {
|
||||||
|
val now = currentLocalDateTime()
|
||||||
|
zoneId.rules.getOffset(now)
|
||||||
|
}
|
||||||
|
|
||||||
fun toLocalDateTime(timestamp: Long?): LocalDateTime {
|
fun toLocalDateTime(timestamp: Long?): LocalDateTime {
|
||||||
val instant = Instant.ofEpochMilli(timestamp ?: 0)
|
val instant = Instant.ofEpochMilli(timestamp ?: 0)
|
||||||
|
@ -33,4 +37,10 @@ object DateProvider {
|
||||||
val instant = Instant.now()
|
val instant = Instant.now()
|
||||||
return LocalDateTime.ofInstant(instant, zoneId)
|
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 android.content.Context
|
||||||
import im.vector.app.R
|
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.DefaultVectorAlert
|
||||||
import im.vector.app.features.popup.PopupAlertManager
|
import im.vector.app.features.popup.PopupAlertManager
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
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.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import timber.log.Timber
|
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.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -54,8 +52,11 @@ import javax.inject.Singleton
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager)
|
class KeyRequestHandler @Inject constructor(
|
||||||
: GossipingRequestListener,
|
private val context: Context,
|
||||||
|
private val popupAlertManager: PopupAlertManager,
|
||||||
|
private val dateFormatter: VectorDateFormatter
|
||||||
|
) : GossipingRequestListener,
|
||||||
VerificationService.Listener {
|
VerificationService.Listener {
|
||||||
|
|
||||||
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
||||||
|
@ -156,16 +157,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context, privat
|
||||||
moreInfo.lastSeenIp
|
moreInfo.lastSeenIp
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastSeenTime = moreInfo.lastSeenTs?.let { ts ->
|
val lastSeenTime = dateFormatter.format(moreInfo.lastSeenTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
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 lastSeenInfo = context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
val lastSeenInfo = context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
||||||
dialogText = if (wasNewDevice) {
|
dialogText = if (wasNewDevice) {
|
||||||
context.getString(R.string.you_added_a_new_device_with_info, deviceName, lastSeenInfo)
|
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
|
package im.vector.app.features.home.room.detail.readreceipts
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
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>) {
|
override fun buildModels(readReceipts: List<ReadReceiptData>) {
|
||||||
readReceipts.forEach {
|
readReceipts.forEach {
|
||||||
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
val timestamp = dateFormatter.format(it.timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
DisplayReadReceiptItem_()
|
DisplayReadReceiptItem_()
|
||||||
.id(it.userId)
|
.id(it.userId)
|
||||||
.matrixItem(it.toMatrixItem())
|
.matrixItem(it.toMatrixItem())
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.VisibilityState
|
import com.airbnb.epoxy.VisibilityState
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.epoxy.LoadingItem_
|
import im.vector.app.core.epoxy.LoadingItem_
|
||||||
import im.vector.app.core.extensions.localDateTime
|
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.model.message.MessageVideoContent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.threeten.bp.LocalDateTime
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||||
|
@ -333,13 +333,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
) {
|
) {
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date)
|
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, event.root.originServerTs)
|
||||||
return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem)
|
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) {
|
return if (addDaySeparator) {
|
||||||
val formattedDay = dateFormatter.formatMessageDay(date)
|
val formattedDay = dateFormatter.format(originServerTs, DateFormatKind.TIMELINE_DAY_DIVIDER)
|
||||||
DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)
|
DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
|
@ -22,9 +22,6 @@ import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.app.core.extensions.canReact
|
import im.vector.app.core.extensions.canReact
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
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
|
* Quick reactions state
|
||||||
|
@ -56,11 +53,7 @@ data class MessageActionState(
|
||||||
|
|
||||||
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)
|
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 senderName(): String = informationData.memberName?.toString() ?: ""
|
||||||
|
|
||||||
fun time(): String? = timelineEvent()?.root?.originServerTs?.let { dateFormat.format(Date(it)) } ?: ""
|
|
||||||
|
|
||||||
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
|
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import im.vector.app.EmojiCompatFontProvider
|
import im.vector.app.EmojiCompatFontProvider
|
||||||
import im.vector.app.R
|
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.BottomSheetQuickReactionsItem
|
||||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
|
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
|
||||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetMessagePreviewItem
|
import im.vector.app.core.epoxy.bottomsheet.bottomSheetMessagePreviewItem
|
||||||
|
@ -40,13 +42,16 @@ import javax.inject.Inject
|
||||||
class MessageActionsEpoxyController @Inject constructor(
|
class MessageActionsEpoxyController @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val fontProvider: EmojiCompatFontProvider
|
private val fontProvider: EmojiCompatFontProvider,
|
||||||
|
private val dateFormatter: VectorDateFormatter
|
||||||
) : TypedEpoxyController<MessageActionState>() {
|
) : TypedEpoxyController<MessageActionState>() {
|
||||||
|
|
||||||
var listener: MessageActionsEpoxyControllerListener? = null
|
var listener: MessageActionsEpoxyControllerListener? = null
|
||||||
|
|
||||||
override fun buildModels(state: MessageActionState) {
|
override fun buildModels(state: MessageActionState) {
|
||||||
// Message preview
|
// Message preview
|
||||||
|
val date = state.timelineEvent()?.root?.originServerTs
|
||||||
|
val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL)
|
||||||
bottomSheetMessagePreviewItem {
|
bottomSheetMessagePreviewItem {
|
||||||
id("preview")
|
id("preview")
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
|
@ -54,7 +59,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
||||||
movementMethod(createLinkMovementMethod(listener))
|
movementMethod(createLinkMovementMethod(listener))
|
||||||
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
|
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
|
||||||
body(state.messageBody.linkify(listener))
|
body(state.messageBody.linkify(listener))
|
||||||
time(state.time())
|
time(formattedDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send state
|
// Send state
|
||||||
|
|
|
@ -17,27 +17,26 @@ package im.vector.app.features.home.room.detail.timeline.edithistory
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.format.DateUtils
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Incomplete
|
import com.airbnb.mvrx.Incomplete
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
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.genericFooterItem
|
||||||
import im.vector.app.core.ui.list.genericItem
|
import im.vector.app.core.ui.list.genericItem
|
||||||
import im.vector.app.core.ui.list.genericItemHeader
|
import im.vector.app.core.ui.list.genericItemHeader
|
||||||
import im.vector.app.core.ui.list.genericLoaderItem
|
import im.vector.app.core.ui.list.genericLoaderItem
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
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.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
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.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
|
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
import org.matrix.android.sdk.internal.session.room.send.TextContent
|
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
|
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)) {
|
if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
|
||||||
// need to display header with day
|
// need to display header with day
|
||||||
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
|
|
||||||
else dateFormatter.formatMessageDay(timelineEvent.localDateTime())
|
|
||||||
genericItemHeader {
|
genericItemHeader {
|
||||||
id(evDate.hashCode())
|
id(evDate.hashCode())
|
||||||
text(dateString)
|
text(dateFormatter.format(evDate.timeInMillis, DateFormatKind.EDIT_HISTORY_HEADER))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastDate = evDate
|
lastDate = evDate
|
||||||
|
@ -130,7 +127,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
||||||
}
|
}
|
||||||
genericItem {
|
genericItem {
|
||||||
id(timelineEvent.eventId)
|
id(timelineEvent.eventId)
|
||||||
title(dateFormatter.formatMessageHour(timelineEvent.localDateTime()))
|
title(dateFormatter.format(timelineEvent.originServerTs, DateFormatKind.EDIT_HISTORY_ROW))
|
||||||
description(spannedDiff ?: body)
|
description(spannedDiff ?: body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.helper
|
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.date.VectorDateFormatter
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
@ -68,7 +69,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
||||||
|| isNextMessageReceivedMoreThanOneHourAgo
|
|| isNextMessageReceivedMoreThanOneHourAgo
|
||||||
|| isTileTypeMessage(nextEvent)
|
|| isTileTypeMessage(nextEvent)
|
||||||
|
|
||||||
val time = dateFormatter.formatMessageHour(date)
|
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
|
||||||
val e2eDecoration = getE2EDecoration(event)
|
val e2eDecoration = getE2EDecoration(event)
|
||||||
|
|
||||||
return MessageInformationData(
|
return MessageInformationData(
|
||||||
|
|
|
@ -24,17 +24,18 @@ import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
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.date.VectorDateFormatter
|
||||||
import im.vector.app.core.platform.EmptyAction
|
import im.vector.app.core.platform.EmptyAction
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
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.Session
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
|
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
|
||||||
import org.matrix.android.sdk.rx.RxRoom
|
import org.matrix.android.sdk.rx.RxRoom
|
||||||
import org.matrix.android.sdk.rx.unwrap
|
import org.matrix.android.sdk.rx.unwrap
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.Single
|
|
||||||
|
|
||||||
data class DisplayReactionsViewState(
|
data class DisplayReactionsViewState(
|
||||||
val eventId: String,
|
val eventId: String,
|
||||||
|
@ -112,7 +113,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
||||||
summary.key,
|
summary.key,
|
||||||
event.root.senderId ?: "",
|
event.root.senderId ?: "",
|
||||||
event.senderInfo.disambiguatedDisplayName,
|
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 android.view.View
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
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.resources.StringProvider
|
||||||
import im.vector.app.core.utils.DebouncedClickListener
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
@ -53,8 +52,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||||
changeMembershipState: ChangeMembershipState,
|
changeMembershipState: ChangeMembershipState,
|
||||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||||
val secondLine = if (roomSummary.isDirect) {
|
val secondLine = if (roomSummary.isDirect) {
|
||||||
roomSummary.inviterId
|
roomSummary.inviterId
|
||||||
} else {
|
} else {
|
||||||
|
@ -87,15 +86,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
var latestEventTime: CharSequence = ""
|
var latestEventTime: CharSequence = ""
|
||||||
val latestEvent = roomSummary.latestPreviewableEvent
|
val latestEvent = roomSummary.latestPreviewableEvent
|
||||||
if (latestEvent != null) {
|
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())
|
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect.not())
|
||||||
latestEventTime = if (isSameDay) {
|
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
||||||
dateFormatter.formatMessageHour(date)
|
|
||||||
} else {
|
|
||||||
dateFormatter.formatMessageDay(date)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
||||||
return RoomSummaryItem_()
|
return RoomSummaryItem_()
|
||||||
|
|
|
@ -19,8 +19,8 @@ package im.vector.app.features.media
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.extensions.localDateTime
|
|
||||||
import im.vector.lib.attachmentviewer.AttachmentInfo
|
import im.vector.lib.attachmentviewer.AttachmentInfo
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
|
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
|
||||||
|
@ -78,9 +78,7 @@ class DataAttachmentRoomProvider(
|
||||||
val item = attachments[position]
|
val item = attachments[position]
|
||||||
val timeLineEvent = room?.getTimeLineEvent(item.eventId)
|
val timeLineEvent = room?.getTimeLineEvent(item.eventId)
|
||||||
if (timeLineEvent != null) {
|
if (timeLineEvent != null) {
|
||||||
val dateString = timeLineEvent.root.localDateTime().let {
|
val dateString = dateFormatter.format(timeLineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
|
|
||||||
}
|
|
||||||
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${timeLineEvent.senderInfo.displayName} $dateString")
|
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${timeLineEvent.senderInfo.displayName} $dateString")
|
||||||
overlayView?.videoControlsGroup?.isVisible = timeLineEvent.root.isVideoMessage()
|
overlayView?.videoControlsGroup?.isVisible = timeLineEvent.root.isVideoMessage()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -19,8 +19,8 @@ package im.vector.app.features.media
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.extensions.localDateTime
|
|
||||||
import im.vector.lib.attachmentviewer.AttachmentInfo
|
import im.vector.lib.attachmentviewer.AttachmentInfo
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -128,9 +128,7 @@ class RoomEventsAttachmentProvider(
|
||||||
override fun overlayViewAtPosition(context: Context, position: Int): View? {
|
override fun overlayViewAtPosition(context: Context, position: Int): View? {
|
||||||
super.overlayViewAtPosition(context, position)
|
super.overlayViewAtPosition(context, position)
|
||||||
val item = attachments[position]
|
val item = attachments[position]
|
||||||
val dateString = item.root.localDateTime().let {
|
val dateString = dateFormatter.format(item.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
"${dateFormatter.formatMessageDay(it)} at ${dateFormatter.formatMessageHour(it)} "
|
|
||||||
}
|
|
||||||
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${item.senderInfo.displayName} $dateString")
|
overlayView?.updateWith("${position + 1} of ${attachments.size}", "${item.senderInfo.displayName} $dateString")
|
||||||
overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage()
|
overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage()
|
||||||
return overlayView
|
return overlayView
|
||||||
|
|
|
@ -18,12 +18,13 @@ package im.vector.app.features.roomprofile.uploads.files
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import com.airbnb.epoxy.VisibilityState
|
import com.airbnb.epoxy.VisibilityState
|
||||||
import org.matrix.android.sdk.api.session.room.uploads.UploadEvent
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsViewState
|
import im.vector.app.features.roomprofile.uploads.RoomUploadsViewState
|
||||||
|
import org.matrix.android.sdk.api.session.room.uploads.UploadEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UploadsFileController @Inject constructor(
|
class UploadsFileController @Inject constructor(
|
||||||
|
@ -71,7 +72,7 @@ class UploadsFileController @Inject constructor(
|
||||||
title(uploadEvent.contentWithAttachmentContent.body)
|
title(uploadEvent.contentWithAttachmentContent.body)
|
||||||
subtitle(stringProvider.getString(R.string.uploads_files_subtitle,
|
subtitle(stringProvider.getString(R.string.uploads_files_subtitle,
|
||||||
uploadEvent.senderInfo.disambiguatedDisplayName,
|
uploadEvent.senderInfo.disambiguatedDisplayName,
|
||||||
dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs)))
|
dateFormatter.format(uploadEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)))
|
||||||
listener(object : UploadsFileItem.Listener {
|
listener(object : UploadsFileItem.Listener {
|
||||||
override fun onItemClicked() {
|
override fun onItemClicked() {
|
||||||
listener?.onOpenClicked(uploadEvent)
|
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.resources.ColorProvider
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import me.gujun.android.span.span
|
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.
|
* A list item for Device.
|
||||||
|
@ -45,6 +41,9 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var deviceInfo: DeviceInfo
|
lateinit var deviceInfo: DeviceInfo
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var lastSeenFormatted: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
var currentDevice = false
|
var currentDevice = false
|
||||||
|
|
||||||
|
@ -105,15 +104,7 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
|
||||||
|
|
||||||
val lastSeenIp = deviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
|
val lastSeenIp = deviceInfo.lastSeenIp?.takeIf { ip -> ip.isNotBlank() } ?: "-"
|
||||||
|
|
||||||
val lastSeenTime = deviceInfo.lastSeenTs?.let { ts ->
|
val lastSeenTime = lastSeenFormatted ?: "-"
|
||||||
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
|
|
||||||
} ?: "-"
|
|
||||||
|
|
||||||
holder.deviceLastSeenText.text = holder.root.context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
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.Loading
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.Uninitialized
|
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.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.errorWithRetryItem
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
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.ui.list.genericItemHeader
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter,
|
class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val dimensionConverter: DimensionConverter,
|
private val dimensionConverter: DimensionConverter,
|
||||||
private val vectorPreferences: VectorPreferences) : EpoxyController() {
|
private val vectorPreferences: VectorPreferences) : EpoxyController() {
|
||||||
|
|
||||||
|
@ -100,6 +103,7 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
|
||||||
deviceInfo(deviceInfo)
|
deviceInfo(deviceInfo)
|
||||||
currentDevice(true)
|
currentDevice(true)
|
||||||
e2eCapable(true)
|
e2eCapable(true)
|
||||||
|
lastSeenFormatted(dateFormatter.format(deviceInfo.lastSeenTs, DateFormatKind.DEFAULT_DATE_AND_TIME))
|
||||||
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
|
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
|
||||||
trusted(DeviceTrustLevel(currentSessionCrossTrusted, true))
|
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.RoomKeyShareRequest
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.epoxy.loadingItem
|
import im.vector.app.core.epoxy.loadingItem
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.resources.ColorProvider
|
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.resources.StringProvider
|
||||||
import im.vector.app.core.ui.list.GenericItem
|
import im.vector.app.core.ui.list.GenericItem
|
||||||
import im.vector.app.core.ui.list.genericFooterItem
|
import im.vector.app.core.ui.list.genericFooterItem
|
||||||
|
@ -94,8 +94,7 @@ class GossipingEventsEpoxyController @Inject constructor(
|
||||||
)
|
)
|
||||||
description(
|
description(
|
||||||
span {
|
span {
|
||||||
+vectorDateFormatter.formatMessageDay(DateProvider.toLocalDateTime(event.ageLocalTs))
|
+vectorDateFormatter.format(event.ageLocalTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
+" ${vectorDateFormatter.formatMessageHour(DateProvider.toLocalDateTime(event.ageLocalTs))}"
|
|
||||||
span("\nfrom: ") {
|
span("\nfrom: ") {
|
||||||
textStyle = "bold"
|
textStyle = "bold"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue