mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-12 00:13:38 +00:00
Show date break header by hour instead of by day
Also ditch relative timestamps in favor of absolute ones
This commit is contained in:
parent
7aefa288d9
commit
183f013c31
@ -189,7 +189,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
CharSequence relativeTimeSpan;
|
||||
|
||||
if (mediaItem.date > 0) {
|
||||
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||
relativeTimeSpan = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||
} else {
|
||||
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
|
||||
}
|
||||
|
@ -1141,7 +1141,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
for (message in sortedMessages) {
|
||||
val body = MentionUtilities.highlightMentions(message.body, message.threadId, this)
|
||||
if (TextUtils.isEmpty(body)) { continue }
|
||||
val formattedTimestamp = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
||||
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
||||
builder.append("$formattedTimestamp: $body").append('\n')
|
||||
}
|
||||
if (builder.isNotEmpty() && builder[builder.length - 1] == '\n') {
|
||||
|
@ -110,11 +110,11 @@ class VisibleMessageView : LinearLayout {
|
||||
senderNameTextView.visibility = View.GONE
|
||||
}
|
||||
// Date break
|
||||
val showDateBreak = (previous == null || !DateUtils.isSameDay(message.timestamp, previous.timestamp))
|
||||
val showDateBreak = (previous == null || !DateUtils.isSameHour(message.timestamp, previous.timestamp))
|
||||
dateBreakTextView.isVisible = showDateBreak
|
||||
dateBreakTextView.text = if (showDateBreak) DateUtils.getRelativeDate(context, Locale.getDefault(), message.timestamp) else ""
|
||||
dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else ""
|
||||
// Timestamp
|
||||
messageTimestampTextView.text = DateUtils.getExtendedRelativeTimeSpanString(context, Locale.getDefault(), message.timestamp)
|
||||
messageTimestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp)
|
||||
// Margins
|
||||
val startPadding: Int
|
||||
if (isGroupThread) {
|
||||
@ -177,10 +177,10 @@ class VisibleMessageView : LinearLayout {
|
||||
|
||||
private fun isEndOfMessageCluster(current: MessageRecord, next: MessageRecord?, isGroupThread: Boolean): Boolean {
|
||||
return if (isGroupThread) {
|
||||
next == null || next.isUpdate || !DateUtils.isSameDay(current.timestamp, next.timestamp)
|
||||
next == null || next.isUpdate || !DateUtils.isSameHour(current.timestamp, next.timestamp)
|
||||
|| current.recipient.address != next.recipient.address
|
||||
} else {
|
||||
next == null || next.isUpdate || !DateUtils.isSameDay(current.timestamp, next.timestamp)
|
||||
next == null || next.isUpdate || !DateUtils.isSameHour(current.timestamp, next.timestamp)
|
||||
|| current.isOutgoing != next.isOutgoing
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class ConversationView : LinearLayout {
|
||||
profilePictureView.update(thread.recipient, thread.threadId)
|
||||
val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString()
|
||||
conversationViewDisplayNameTextView.text = senderDisplayName
|
||||
timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE
|
||||
val rawSnippet = thread.getDisplayBody(context)
|
||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
||||
|
@ -118,7 +118,7 @@ object BackupUtil {
|
||||
if (timestamp == null) {
|
||||
return context.getString(R.string.BackupUtil_never)
|
||||
}
|
||||
return DateUtils.getExtendedRelativeTimeSpanString(context, locale, timestamp.time)
|
||||
return DateUtils.getDisplayFormattedTimeSpanString(context, locale, timestamp.time)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
@ -28,6 +28,7 @@ import org.session.libsignal.utilities.Log;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -40,8 +41,9 @@ import network.loki.messenger.R;
|
||||
public class DateUtils extends android.text.format.DateUtils {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DateUtils.class.getSimpleName();
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
|
||||
private static final String TAG = DateUtils.class.getSimpleName();
|
||||
private static final SimpleDateFormat DAY_PRECISION_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
|
||||
private static final SimpleDateFormat HOUR_PRECISION_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHH");
|
||||
|
||||
private static boolean isWithin(final long millis, final long span, final TimeUnit unit) {
|
||||
return System.currentTimeMillis() - millis <= unit.toMillis(span);
|
||||
@ -60,6 +62,91 @@ public class DateUtils extends android.text.format.DateUtils {
|
||||
return new SimpleDateFormat(localizedPattern, locale).format(new Date(time));
|
||||
}
|
||||
|
||||
public static String getHourFormat(Context c) {
|
||||
return (DateFormat.is24HourFormat(c)) ? "HH:mm" : "hh:mm a";
|
||||
}
|
||||
|
||||
public static String getDisplayFormattedTimeSpanString(final Context c, final Locale locale, final long timestamp) {
|
||||
if (isWithin(timestamp, 1, TimeUnit.MINUTES)) {
|
||||
return c.getString(R.string.DateUtils_just_now);
|
||||
} else if (isToday(timestamp)) {
|
||||
return getFormattedDateTime(timestamp, getHourFormat(c), locale);
|
||||
} else if (isWithin(timestamp, 6, TimeUnit.DAYS)) {
|
||||
return getFormattedDateTime(timestamp, "EEE " + getHourFormat(c), locale);
|
||||
} else if (isWithin(timestamp, 365, TimeUnit.DAYS)) {
|
||||
return getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c), locale);
|
||||
} else {
|
||||
return getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c) + ", yyyy", locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getDetailedDateFormatter(Context context, Locale locale) {
|
||||
String dateFormatPattern;
|
||||
|
||||
if (DateFormat.is24HourFormat(context)) {
|
||||
dateFormatPattern = getLocalizedPattern("MMM d, yyyy HH:mm:ss zzz", locale);
|
||||
} else {
|
||||
dateFormatPattern = getLocalizedPattern("MMM d, yyyy hh:mm:ss a zzz", locale);
|
||||
}
|
||||
|
||||
return new SimpleDateFormat(dateFormatPattern, locale);
|
||||
}
|
||||
|
||||
public static String getRelativeDate(@NonNull Context context,
|
||||
@NonNull Locale locale,
|
||||
long timestamp)
|
||||
{
|
||||
if (isToday(timestamp)) {
|
||||
return context.getString(R.string.DateUtils_today);
|
||||
} else if (isYesterday(timestamp)) {
|
||||
return context.getString(R.string.DateUtils_yesterday);
|
||||
} else {
|
||||
return getFormattedDateTime(timestamp, "EEE, MMM d, yyyy", locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSameDay(long t1, long t2) {
|
||||
return DAY_PRECISION_DATE_FORMAT.format(new Date(t1)).equals(DAY_PRECISION_DATE_FORMAT.format(new Date(t2)));
|
||||
}
|
||||
|
||||
public static boolean isSameHour(long t1, long t2) {
|
||||
return HOUR_PRECISION_DATE_FORMAT.format(new Date(t1)).equals(HOUR_PRECISION_DATE_FORMAT.format(new Date(t2)));
|
||||
}
|
||||
|
||||
private static String getLocalizedPattern(String template, Locale locale) {
|
||||
return DateFormat.getBestDateTimePattern(locale, template);
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. 2020-09-04T19:17:51Z
|
||||
* https://www.iso.org/iso-8601-date-and-time-format.html
|
||||
*
|
||||
* Note: SDK_INT == 0 check needed to pass unit tests due to JVM date parser differences.
|
||||
*
|
||||
* @return The timestamp if able to be parsed, otherwise -1.
|
||||
*/
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
public static long parseIso8601(@Nullable String date) {
|
||||
SimpleDateFormat format;
|
||||
if (Build.VERSION.SDK_INT == 0 || Build.VERSION.SDK_INT >= 24) {
|
||||
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault());
|
||||
} else {
|
||||
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
|
||||
}
|
||||
|
||||
if (date.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return format.parse(date).getTime();
|
||||
} catch (ParseException e) {
|
||||
Log.w(TAG, "Failed to parse date.", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// region Deprecated
|
||||
public static String getBriefRelativeTimeSpanString(final Context c, final Locale locale, final long timestamp) {
|
||||
if (isWithin(timestamp, 1, TimeUnit.MINUTES)) {
|
||||
return c.getString(R.string.DateUtils_just_now);
|
||||
@ -96,86 +183,5 @@ public class DateUtils extends android.text.format.DateUtils {
|
||||
return getFormattedDateTime(timestamp, format.toString(), locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDayPrecisionTimeSpanString(Context context, Locale locale, long timestamp) {
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
|
||||
|
||||
if (simpleDateFormat.format(System.currentTimeMillis()).equals(simpleDateFormat.format(timestamp))) {
|
||||
return context.getString(R.string.DeviceListItem_today);
|
||||
} else {
|
||||
String format;
|
||||
|
||||
if (isWithin(timestamp, 6, TimeUnit.DAYS)) format = "EEE ";
|
||||
else if (isWithin(timestamp, 365, TimeUnit.DAYS)) format = "MMM d";
|
||||
else format = "MMM d, yyy";
|
||||
|
||||
return getFormattedDateTime(timestamp, format, locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getDetailedDateFormatter(Context context, Locale locale) {
|
||||
String dateFormatPattern;
|
||||
|
||||
if (DateFormat.is24HourFormat(context)) {
|
||||
dateFormatPattern = getLocalizedPattern("MMM d, yyyy HH:mm:ss zzz", locale);
|
||||
} else {
|
||||
dateFormatPattern = getLocalizedPattern("MMM d, yyyy hh:mm:ss a zzz", locale);
|
||||
}
|
||||
|
||||
return new SimpleDateFormat(dateFormatPattern, locale);
|
||||
}
|
||||
|
||||
public static String getRelativeDate(@NonNull Context context,
|
||||
@NonNull Locale locale,
|
||||
long timestamp)
|
||||
{
|
||||
if (isToday(timestamp)) {
|
||||
return context.getString(R.string.DateUtils_today);
|
||||
} else if (isYesterday(timestamp)) {
|
||||
return context.getString(R.string.DateUtils_yesterday);
|
||||
} else {
|
||||
return getFormattedDateTime(timestamp, "EEE, MMM d, yyyy", locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSameDay(long t1, long t2) {
|
||||
return DATE_FORMAT.format(new Date(t1)).equals(DATE_FORMAT.format(new Date(t2)));
|
||||
}
|
||||
|
||||
public static boolean isSameExtendedRelativeTimestamp(@NonNull Context context, @NonNull Locale locale, long t1, long t2) {
|
||||
return getExtendedRelativeTimeSpanString(context, locale, t1).equals(getExtendedRelativeTimeSpanString(context, locale, t2));
|
||||
}
|
||||
|
||||
private static String getLocalizedPattern(String template, Locale locale) {
|
||||
return DateFormat.getBestDateTimePattern(locale, template);
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. 2020-09-04T19:17:51Z
|
||||
* https://www.iso.org/iso-8601-date-and-time-format.html
|
||||
*
|
||||
* Note: SDK_INT == 0 check needed to pass unit tests due to JVM date parser differences.
|
||||
*
|
||||
* @return The timestamp if able to be parsed, otherwise -1.
|
||||
*/
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
public static long parseIso8601(@Nullable String date) {
|
||||
SimpleDateFormat format;
|
||||
if (Build.VERSION.SDK_INT == 0 || Build.VERSION.SDK_INT >= 24) {
|
||||
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.getDefault());
|
||||
} else {
|
||||
format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault());
|
||||
}
|
||||
|
||||
if (date.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return format.parse(date).getTime();
|
||||
} catch (ParseException e) {
|
||||
Log.w(TAG, "Failed to parse date.", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user