Improve time format of posts when using absolute time (#2413)
* Improve time format of posts when using absolute time * fix AbsoluteTimeFormatter, add tests * fix tests Co-authored-by: Conny Duck <k.pozniak@gmx.at>
This commit is contained in:
parent
216f094e98
commit
e0abcbfada
6 changed files with 127 additions and 61 deletions
|
@ -47,6 +47,7 @@ import com.keylesspalace.tusky.entity.TimelineAccount;
|
||||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||||
|
import com.keylesspalace.tusky.util.AbsoluteTimeFormatter;
|
||||||
import com.keylesspalace.tusky.util.CardViewMode;
|
import com.keylesspalace.tusky.util.CardViewMode;
|
||||||
import com.keylesspalace.tusky.util.CustomEmojiHelper;
|
import com.keylesspalace.tusky.util.CustomEmojiHelper;
|
||||||
import com.keylesspalace.tusky.util.ImageLoadingHelper;
|
import com.keylesspalace.tusky.util.ImageLoadingHelper;
|
||||||
|
@ -58,10 +59,8 @@ import com.keylesspalace.tusky.util.TimestampUtils;
|
||||||
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import at.connyduck.sparkbutton.helpers.Utils;
|
import at.connyduck.sparkbutton.helpers.Utils;
|
||||||
|
|
||||||
|
@ -90,6 +89,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
private NotificationActionListener notificationActionListener;
|
private NotificationActionListener notificationActionListener;
|
||||||
private AccountActionListener accountActionListener;
|
private AccountActionListener accountActionListener;
|
||||||
private AdapterDataSource<NotificationViewData> dataSource;
|
private AdapterDataSource<NotificationViewData> dataSource;
|
||||||
|
private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter();
|
||||||
|
|
||||||
public NotificationsAdapter(String accountId,
|
public NotificationsAdapter(String accountId,
|
||||||
AdapterDataSource<NotificationViewData> dataSource,
|
AdapterDataSource<NotificationViewData> dataSource,
|
||||||
|
@ -119,7 +119,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
case VIEW_TYPE_STATUS_NOTIFICATION: {
|
case VIEW_TYPE_STATUS_NOTIFICATION: {
|
||||||
View view = inflater
|
View view = inflater
|
||||||
.inflate(R.layout.item_status_notification, parent, false);
|
.inflate(R.layout.item_status_notification, parent, false);
|
||||||
return new StatusNotificationViewHolder(view, statusDisplayOptions);
|
return new StatusNotificationViewHolder(view, statusDisplayOptions, absoluteTimeFormatter);
|
||||||
}
|
}
|
||||||
case VIEW_TYPE_FOLLOW: {
|
case VIEW_TYPE_FOLLOW: {
|
||||||
View view = inflater
|
View view = inflater
|
||||||
|
@ -383,19 +383,22 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
private final Button contentWarningButton;
|
private final Button contentWarningButton;
|
||||||
private final Button contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
|
private final Button contentCollapseButton; // TODO: This code SHOULD be based on StatusBaseViewHolder
|
||||||
private StatusDisplayOptions statusDisplayOptions;
|
private StatusDisplayOptions statusDisplayOptions;
|
||||||
|
private final AbsoluteTimeFormatter absoluteTimeFormatter;
|
||||||
|
|
||||||
private String accountId;
|
private String accountId;
|
||||||
private String notificationId;
|
private String notificationId;
|
||||||
private NotificationActionListener notificationActionListener;
|
private NotificationActionListener notificationActionListener;
|
||||||
private StatusViewData.Concrete statusViewData;
|
private StatusViewData.Concrete statusViewData;
|
||||||
private SimpleDateFormat shortSdf;
|
|
||||||
private SimpleDateFormat longSdf;
|
|
||||||
|
|
||||||
private int avatarRadius48dp;
|
private int avatarRadius48dp;
|
||||||
private int avatarRadius36dp;
|
private int avatarRadius36dp;
|
||||||
private int avatarRadius24dp;
|
private int avatarRadius24dp;
|
||||||
|
|
||||||
StatusNotificationViewHolder(View itemView, StatusDisplayOptions statusDisplayOptions) {
|
StatusNotificationViewHolder(
|
||||||
|
View itemView,
|
||||||
|
StatusDisplayOptions statusDisplayOptions,
|
||||||
|
AbsoluteTimeFormatter absoluteTimeFormatter
|
||||||
|
) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
message = itemView.findViewById(R.id.notification_top_text);
|
message = itemView.findViewById(R.id.notification_top_text);
|
||||||
statusNameBar = itemView.findViewById(R.id.status_name_bar);
|
statusNameBar = itemView.findViewById(R.id.status_name_bar);
|
||||||
|
@ -409,6 +412,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
|
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
|
||||||
contentCollapseButton = itemView.findViewById(R.id.button_toggle_notification_content);
|
contentCollapseButton = itemView.findViewById(R.id.button_toggle_notification_content);
|
||||||
this.statusDisplayOptions = statusDisplayOptions;
|
this.statusDisplayOptions = statusDisplayOptions;
|
||||||
|
this.absoluteTimeFormatter = absoluteTimeFormatter;
|
||||||
|
|
||||||
int darkerFilter = Color.rgb(123, 123, 123);
|
int darkerFilter = Color.rgb(123, 123, 123);
|
||||||
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
|
||||||
|
@ -417,8 +421,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
itemView.setOnClickListener(this);
|
itemView.setOnClickListener(this);
|
||||||
message.setOnClickListener(this);
|
message.setOnClickListener(this);
|
||||||
statusContent.setOnClickListener(this);
|
statusContent.setOnClickListener(this);
|
||||||
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
|
|
||||||
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
|
|
||||||
|
|
||||||
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
|
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
|
||||||
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
|
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
|
||||||
|
@ -448,17 +450,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
|
|
||||||
protected void setCreatedAt(@Nullable Date createdAt) {
|
protected void setCreatedAt(@Nullable Date createdAt) {
|
||||||
if (statusDisplayOptions.useAbsoluteTime()) {
|
if (statusDisplayOptions.useAbsoluteTime()) {
|
||||||
String time;
|
timestampInfo.setText(absoluteTimeFormatter.format(createdAt, true));
|
||||||
if (createdAt != null) {
|
|
||||||
if (System.currentTimeMillis() - createdAt.getTime() > 86400000L) {
|
|
||||||
time = longSdf.format(createdAt);
|
|
||||||
} else {
|
|
||||||
time = shortSdf.format(createdAt);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
time = "??:??:??";
|
|
||||||
}
|
|
||||||
timestampInfo.setText(time);
|
|
||||||
} else {
|
} else {
|
||||||
// This is the visible timestampInfo.
|
// This is the visible timestampInfo.
|
||||||
String readout;
|
String readout;
|
||||||
|
|
|
@ -40,6 +40,7 @@ import com.keylesspalace.tusky.entity.Emoji;
|
||||||
import com.keylesspalace.tusky.entity.HashTag;
|
import com.keylesspalace.tusky.entity.HashTag;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||||
|
import com.keylesspalace.tusky.util.AbsoluteTimeFormatter;
|
||||||
import com.keylesspalace.tusky.util.CardViewMode;
|
import com.keylesspalace.tusky.util.CardViewMode;
|
||||||
import com.keylesspalace.tusky.util.CustomEmojiHelper;
|
import com.keylesspalace.tusky.util.CustomEmojiHelper;
|
||||||
import com.keylesspalace.tusky.util.ImageLoadingHelper;
|
import com.keylesspalace.tusky.util.ImageLoadingHelper;
|
||||||
|
@ -54,10 +55,8 @@ import com.keylesspalace.tusky.viewdata.PollViewDataKt;
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import at.connyduck.sparkbutton.SparkButton;
|
import at.connyduck.sparkbutton.SparkButton;
|
||||||
import at.connyduck.sparkbutton.helpers.Utils;
|
import at.connyduck.sparkbutton.helpers.Utils;
|
||||||
|
@ -103,10 +102,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
private TextView cardUrl;
|
private TextView cardUrl;
|
||||||
private PollAdapter pollAdapter;
|
private PollAdapter pollAdapter;
|
||||||
|
|
||||||
private SimpleDateFormat shortSdf;
|
|
||||||
private SimpleDateFormat longSdf;
|
|
||||||
|
|
||||||
private final NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
private final NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
||||||
|
private final AbsoluteTimeFormatter absoluteTimeFormatter = new AbsoluteTimeFormatter();
|
||||||
|
|
||||||
protected int avatarRadius48dp;
|
protected int avatarRadius48dp;
|
||||||
private int avatarRadius36dp;
|
private int avatarRadius36dp;
|
||||||
|
@ -170,9 +167,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
pollOptions.setLayoutManager(new LinearLayoutManager(pollOptions.getContext()));
|
pollOptions.setLayoutManager(new LinearLayoutManager(pollOptions.getContext()));
|
||||||
((DefaultItemAnimator) pollOptions.getItemAnimator()).setSupportsChangeAnimations(false);
|
((DefaultItemAnimator) pollOptions.getItemAnimator()).setSupportsChangeAnimations(false);
|
||||||
|
|
||||||
this.shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
|
|
||||||
this.longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
|
|
||||||
|
|
||||||
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
|
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
|
||||||
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
|
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
|
||||||
this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
|
this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
|
||||||
|
@ -320,7 +314,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
protected void setCreatedAt(Date createdAt, StatusDisplayOptions statusDisplayOptions) {
|
protected void setCreatedAt(Date createdAt, StatusDisplayOptions statusDisplayOptions) {
|
||||||
if (statusDisplayOptions.useAbsoluteTime()) {
|
if (statusDisplayOptions.useAbsoluteTime()) {
|
||||||
timestampInfo.setText(getAbsoluteTime(createdAt));
|
timestampInfo.setText(absoluteTimeFormatter.format(createdAt, true));
|
||||||
} else {
|
} else {
|
||||||
if (createdAt == null) {
|
if (createdAt == null) {
|
||||||
timestampInfo.setText("?m");
|
timestampInfo.setText("?m");
|
||||||
|
@ -333,21 +327,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAbsoluteTime(Date createdAt) {
|
|
||||||
if (createdAt == null) {
|
|
||||||
return "??:??:??";
|
|
||||||
}
|
|
||||||
if (DateUtils.isToday(createdAt.getTime())) {
|
|
||||||
return shortSdf.format(createdAt);
|
|
||||||
} else {
|
|
||||||
return longSdf.format(createdAt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CharSequence getCreatedAtDescription(Date createdAt,
|
private CharSequence getCreatedAtDescription(Date createdAt,
|
||||||
StatusDisplayOptions statusDisplayOptions) {
|
StatusDisplayOptions statusDisplayOptions) {
|
||||||
if (statusDisplayOptions.useAbsoluteTime()) {
|
if (statusDisplayOptions.useAbsoluteTime()) {
|
||||||
return getAbsoluteTime(createdAt);
|
return absoluteTimeFormatter.format(createdAt, true);
|
||||||
} else {
|
} else {
|
||||||
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
|
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
|
||||||
* as 17 meters instead of minutes. */
|
* as 17 meters instead of minutes. */
|
||||||
|
@ -1028,7 +1011,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
return votesText;
|
return votesText;
|
||||||
} else {
|
} else {
|
||||||
if (statusDisplayOptions.useAbsoluteTime()) {
|
if (statusDisplayOptions.useAbsoluteTime()) {
|
||||||
pollDurationInfo = context.getString(R.string.poll_info_time_absolute, getAbsoluteTime(poll.getExpiresAt()));
|
pollDurationInfo = context.getString(R.string.poll_info_time_absolute, absoluteTimeFormatter.format(poll.getExpiresAt(), false));
|
||||||
} else {
|
} else {
|
||||||
pollDurationInfo = TimestampUtils.formatPollDuration(pollDescription.getContext(), poll.getExpiresAt().getTime(), timestamp);
|
pollDurationInfo = TimestampUtils.formatPollDuration(pollDescription.getContext(), poll.getExpiresAt().getTime(), timestamp);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.keylesspalace.tusky.entity.Emoji
|
||||||
import com.keylesspalace.tusky.entity.HashTag
|
import com.keylesspalace.tusky.entity.HashTag
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||||
|
import com.keylesspalace.tusky.util.AbsoluteTimeFormatter
|
||||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||||
import com.keylesspalace.tusky.util.StatusViewHelper
|
import com.keylesspalace.tusky.util.StatusViewHelper
|
||||||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
|
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
|
||||||
|
@ -51,6 +52,7 @@ class StatusViewHolder(
|
||||||
|
|
||||||
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
|
private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height)
|
||||||
private val statusViewHelper = StatusViewHelper(itemView)
|
private val statusViewHelper = StatusViewHelper(itemView)
|
||||||
|
private val absoluteTimeFormatter = AbsoluteTimeFormatter()
|
||||||
|
|
||||||
private val previewListener = object : StatusViewHelper.MediaPreviewListener {
|
private val previewListener = object : StatusViewHelper.MediaPreviewListener {
|
||||||
override fun onViewMedia(v: View?, idx: Int) {
|
override fun onViewMedia(v: View?, idx: Int) {
|
||||||
|
@ -154,7 +156,7 @@ class StatusViewHolder(
|
||||||
|
|
||||||
private fun setCreatedAt(createdAt: Date?) {
|
private fun setCreatedAt(createdAt: Date?) {
|
||||||
if (statusDisplayOptions.useAbsoluteTime) {
|
if (statusDisplayOptions.useAbsoluteTime) {
|
||||||
binding.timestampInfo.text = statusViewHelper.getAbsoluteTime(createdAt)
|
binding.timestampInfo.text = absoluteTimeFormatter.format(createdAt)
|
||||||
} else {
|
} else {
|
||||||
binding.timestampInfo.text = if (createdAt != null) {
|
binding.timestampInfo.text = if (createdAt != null) {
|
||||||
val then = createdAt.time
|
val then = createdAt.time
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* Copyright 2022 Tusky Contributors
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky.util
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class AbsoluteTimeFormatter @JvmOverloads constructor(private val tz: TimeZone = TimeZone.getDefault()) {
|
||||||
|
private val sameDaySdf = SimpleDateFormat("HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
|
||||||
|
private val sameYearSdf = SimpleDateFormat("MM-dd HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
|
||||||
|
private val otherYearSdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).apply { this.timeZone = tz }
|
||||||
|
private val otherYearCompleteSdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).apply { this.timeZone = tz }
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun format(time: Date?, shortFormat: Boolean = true, now: Date = Date()): String {
|
||||||
|
return when {
|
||||||
|
time == null -> "??"
|
||||||
|
isSameDate(time, now, tz) -> sameDaySdf.format(time)
|
||||||
|
isSameYear(time, now, tz) -> sameYearSdf.format(time)
|
||||||
|
shortFormat -> otherYearSdf.format(time)
|
||||||
|
else -> otherYearCompleteSdf.format(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun isSameDate(dateOne: Date, dateTwo: Date, tz: TimeZone): Boolean {
|
||||||
|
val calendarOne = Calendar.getInstance(tz).apply { time = dateOne }
|
||||||
|
val calendarTwo = Calendar.getInstance(tz).apply { time = dateTwo }
|
||||||
|
|
||||||
|
return calendarOne.get(Calendar.YEAR) == calendarTwo.get(Calendar.YEAR) &&
|
||||||
|
calendarOne.get(Calendar.MONTH) == calendarTwo.get(Calendar.MONTH) &&
|
||||||
|
calendarOne.get(Calendar.DAY_OF_MONTH) == calendarTwo.get(Calendar.DAY_OF_MONTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSameYear(dateOne: Date, dateTwo: Date, timeZone1: TimeZone): Boolean {
|
||||||
|
val calendarOne = Calendar.getInstance(timeZone1).apply { time = dateOne }
|
||||||
|
val calendarTwo = Calendar.getInstance(timeZone1).apply { time = dateTwo }
|
||||||
|
|
||||||
|
return calendarOne.get(Calendar.YEAR) == calendarTwo.get(Calendar.YEAR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,20 +34,16 @@ import com.keylesspalace.tusky.viewdata.PollViewData
|
||||||
import com.keylesspalace.tusky.viewdata.buildDescription
|
import com.keylesspalace.tusky.viewdata.buildDescription
|
||||||
import com.keylesspalace.tusky.viewdata.calculatePercent
|
import com.keylesspalace.tusky.viewdata.calculatePercent
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class StatusViewHelper(private val itemView: View) {
|
class StatusViewHelper(private val itemView: View) {
|
||||||
|
private val absoluteTimeFormatter = AbsoluteTimeFormatter()
|
||||||
|
|
||||||
interface MediaPreviewListener {
|
interface MediaPreviewListener {
|
||||||
fun onViewMedia(v: View?, idx: Int)
|
fun onViewMedia(v: View?, idx: Int)
|
||||||
fun onContentHiddenChange(isShowing: Boolean)
|
fun onContentHiddenChange(isShowing: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val shortSdf = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
|
||||||
private val longSdf = SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault())
|
|
||||||
|
|
||||||
fun setMediasPreview(
|
fun setMediasPreview(
|
||||||
statusDisplayOptions: StatusDisplayOptions,
|
statusDisplayOptions: StatusDisplayOptions,
|
||||||
attachments: List<Attachment>,
|
attachments: List<Attachment>,
|
||||||
|
@ -295,7 +291,7 @@ class StatusViewHelper(private val itemView: View) {
|
||||||
context.getString(R.string.poll_info_closed)
|
context.getString(R.string.poll_info_closed)
|
||||||
} else {
|
} else {
|
||||||
if (useAbsoluteTime) {
|
if (useAbsoluteTime) {
|
||||||
context.getString(R.string.poll_info_time_absolute, getAbsoluteTime(poll.expiresAt))
|
context.getString(R.string.poll_info_time_absolute, absoluteTimeFormatter.format(poll.expiresAt, false))
|
||||||
} else {
|
} else {
|
||||||
TimestampUtils.formatPollDuration(context, poll.expiresAt!!.time, timestamp)
|
TimestampUtils.formatPollDuration(context, poll.expiresAt!!.time, timestamp)
|
||||||
}
|
}
|
||||||
|
@ -330,18 +326,6 @@ class StatusViewHelper(private val itemView: View) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAbsoluteTime(time: Date?): String {
|
|
||||||
return if (time != null) {
|
|
||||||
if (android.text.format.DateUtils.isToday(time.time)) {
|
|
||||||
shortSdf.format(time)
|
|
||||||
} else {
|
|
||||||
longSdf.format(time)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"??:??:??"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val COLLAPSE_INPUT_FILTER = arrayOf<InputFilter>(SmartLengthInputFilter)
|
val COLLAPSE_INPUT_FILTER = arrayOf<InputFilter>(SmartLengthInputFilter)
|
||||||
val NO_INPUT_FILTER = arrayOfNulls<InputFilter>(0)
|
val NO_INPUT_FILTER = arrayOfNulls<InputFilter>(0)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.keylesspalace.tusky.util
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class AbsoluteTimeFormatterTest {
|
||||||
|
|
||||||
|
private val formatter = AbsoluteTimeFormatter(TimeZone.getTimeZone("UTC"))
|
||||||
|
private val now = Date.from(Instant.parse("2022-04-11T00:00:00.00Z"))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `null handling`() {
|
||||||
|
assertEquals("??", formatter.format(null, true, now))
|
||||||
|
assertEquals("??", formatter.format(null, false, now))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `same day formatting`() {
|
||||||
|
val tenTen = Date.from(Instant.parse("2022-04-11T10:10:00.00Z"))
|
||||||
|
assertEquals("10:10", formatter.format(tenTen, true, now))
|
||||||
|
assertEquals("10:10", formatter.format(tenTen, false, now))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `same year formatting`() {
|
||||||
|
val nextDay = Date.from(Instant.parse("2022-04-12T00:10:00.00Z"))
|
||||||
|
assertEquals("04-12 00:10", formatter.format(nextDay, true, now))
|
||||||
|
assertEquals("04-12 00:10", formatter.format(nextDay, false, now))
|
||||||
|
val endOfYear = Date.from(Instant.parse("2022-12-31T23:59:00.00Z"))
|
||||||
|
assertEquals("12-31 23:59", formatter.format(endOfYear, true, now))
|
||||||
|
assertEquals("12-31 23:59", formatter.format(endOfYear, false, now))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `other year formatting`() {
|
||||||
|
val firstDayNextYear = Date.from(Instant.parse("2023-01-01T00:00:00.00Z"))
|
||||||
|
assertEquals("2023-01-01", formatter.format(firstDayNextYear, true, now))
|
||||||
|
assertEquals("2023-01-01 00:00", formatter.format(firstDayNextYear, false, now))
|
||||||
|
val inTenYears = Date.from(Instant.parse("2032-04-11T10:10:00.00Z"))
|
||||||
|
assertEquals("2032-04-11", formatter.format(inTenYears, true, now))
|
||||||
|
assertEquals("2032-04-11 10:10", formatter.format(inTenYears, false, now))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue