From 282a707bf960c1c54168fef150b39dbf4b89814a Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Wed, 17 Dec 2025 13:20:36 -0500 Subject: [PATCH] Fix talkback saying meters instead of minutes. --- .../securesms/calls/log/CallLogAdapter.kt | 12 ++++++++- .../components/ConversationItemFooter.java | 5 ++++ .../app/privacy/PrivacySettingsFragment.kt | 3 +++ .../conversation/ConversationMessage.java | 3 ++- .../conversation/ConversationTitleView.java | 1 + .../colors/ui/ChatColorPreviewView.kt | 2 +- .../conversation/v2/computed/FormattedDate.kt | 3 ++- .../V2ConversationItemTextOnlyViewHolder.kt | 11 +++++--- .../ConversationListItem.java | 16 +++++++++--- .../mediapreview/MediaPreviewV2Fragment.kt | 15 ++++++----- .../BackupsPreferenceFragment.java | 7 +++-- .../RestoreLocalBackupFragment.kt | 4 ++- .../stories/landing/StoriesLandingItem.kt | 4 ++- .../securesms/stories/my/MyStoriesItem.kt | 4 ++- .../viewer/page/StoryViewerPageFragment.kt | 3 ++- .../viewer/reply/group/StoryGroupReplyItem.kt | 4 ++- .../stories/viewer/views/StoryViewItem.kt | 6 +++-- .../securesms/util/BackupUtil.java | 8 +++--- .../thoughtcrime/securesms/util/DateUtils.kt | 26 ++++++++++++++----- app/src/main/res/values/strings.xml | 5 ++++ 20 files changed, 106 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt index 9655d8d644..28b5513221 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogAdapter.kt @@ -346,6 +346,7 @@ class CallLogAdapter( private fun presentCallInfo(call: CallLogRow.Call, date: Long) { val callState = context.getString(getCallStateStringRes(call.record, call.children.size)) + val (dateString, dateContentDescription) = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), date) binding.callInfo.text = context.getString( R.string.CallLogAdapter__s_dot_s, if (call.children.size > 1) { @@ -353,7 +354,16 @@ class CallLogAdapter( } else { callState }, - DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), date) + dateString + ) + binding.callInfo.contentDescription = context.getString( + R.string.CallLogAdapter__s_dot_s, + if (call.children.size > 1) { + context.getString(R.string.CallLogAdapter__d_s, call.children.size, callState) + } else { + callState + }, + dateContentDescription ) binding.callInfo.setRelativeDrawables( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index 991f371de9..ac2e3e8bd5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -306,16 +306,21 @@ public class ConversationItemFooter extends ConstraintLayout { long timestamp = (displayMode == ConversationItemDisplayMode.EditHistory.INSTANCE) ? messageRecord.getDateSent() : messageRecord.getTimestamp(); FormattedDate date = DateUtils.getDatelessRelativeTimeSpanFormattedDate(getContext(), locale, timestamp); String dateLabel = date.getValue(); + String dateLabelContentDesc = date.getContentDescValue(); if (displayMode != ConversationItemDisplayMode.Detailed.INSTANCE && messageRecord.isEditMessage() && messageRecord.isLatestRevision()) { if (date.isNow()) { dateLabel = getContext().getString(R.string.ConversationItem_edited_now_timestamp_footer); + dateLabelContentDesc = dateLabel; } else if (date.isRelative()) { dateLabel = getContext().getString(R.string.ConversationItem_edited_relative_timestamp_footer, date.getValue()); + dateLabelContentDesc = getContext().getString(R.string.ConversationItem_edited_relative_timestamp_footer, date.getContentDescValue()); } else { dateLabel = getContext().getString(R.string.ConversationItem_edited_absolute_timestamp_footer, date.getValue()); + dateLabelContentDesc = dateLabel; } } dateView.setText(dateLabel); + dateView.setContentDescription(dateLabelContentDesc); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt index d45ccde7e3..0b9b5112a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt @@ -168,6 +168,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac customPref( ValueClickPreference( value = DSLSettingsText.from(ExpirationUtil.getExpirationAbbreviatedDisplayValue(requireContext(), state.universalExpireTimer)), + contentDescription = DSLSettingsText.from(ExpirationUtil.getExpirationDisplayValue(requireContext(), state.universalExpireTimer)), clickPreference = ClickPreference( title = DSLSettingsText.from(R.string.PrivacySettingsFragment__default_timer_for_new_changes), summary = DSLSettingsText.from(R.string.PrivacySettingsFragment__set_a_default_disappearing_message_timer_for_all_new_chats_started_by_you), @@ -364,6 +365,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac private class ValueClickPreference( val value: DSLSettingsText, + val contentDescription: DSLSettingsText, val clickPreference: ClickPreference ) : PreferenceModel( title = clickPreference.title, @@ -386,6 +388,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac super.bind(model) clickPreferenceViewHolder.bind(model.clickPreference) valueText.text = model.value.resolve(context) + valueText.contentDescription = model.contentDescription.resolve(context) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java index 0f024be393..a611343fe5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java @@ -165,7 +165,8 @@ public class ConversationMessage { } public static @NonNull FormattedDate getFormattedDate(@NonNull Context context, @NonNull MessageRecord messageRecord) { - return MessageRecordUtil.isScheduled(messageRecord) ? new FormattedDate(false, false, DateUtils.getOnlyTimeString(context, ((MmsMessageRecord) messageRecord).getScheduledDate())) + String time = DateUtils.getOnlyTimeString(context, ((MmsMessageRecord) messageRecord).getScheduledDate()); + return MessageRecordUtil.isScheduled(messageRecord) ? new FormattedDate(false, false, time, time) : DateUtils.getDatelessRelativeTimeSpanFormattedDate(context, Locale.getDefault(), messageRecord.getTimestamp()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationTitleView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationTitleView.java index 8e97a77491..c72ee35ddd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationTitleView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationTitleView.java @@ -95,6 +95,7 @@ public class ConversationTitleView extends ConstraintLayout { isSelf = recipient.isSelf(); expirationBadgeTime.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(getContext(), recipient.getExpiresInSeconds())); + expirationBadgeTime.setContentDescription(ExpirationUtil.getExpirationDisplayValue(getContext(), recipient.getExpiresInSeconds())); expirationBadgeContainer.setVisibility(View.VISIBLE); updateSubtitleVisibility(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/ChatColorPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/ChatColorPreviewView.kt index 8725983c88..defd205726 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/ChatColorPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/colors/ui/ChatColorPreviewView.kt @@ -87,7 +87,7 @@ class ChatColorPreviewView @JvmOverloads constructor( findViewById(R.id.bubble_4_delivery) ) - val now: String = DateUtils.getExtendedRelativeTimeSpanString(context, Locale.getDefault(), System.currentTimeMillis()) + val (now, _) = DateUtils.getExtendedRelativeTimeSpanString(context, Locale.getDefault(), System.currentTimeMillis()) listOf(sent1, sent2, recv1, recv2).forEach { it.time.text = now it.delivery?.setRead() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/computed/FormattedDate.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/computed/FormattedDate.kt index d28dfb185c..10ce8d3eef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/computed/FormattedDate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/computed/FormattedDate.kt @@ -8,5 +8,6 @@ package org.thoughtcrime.securesms.conversation.v2.computed data class FormattedDate( val isRelative: Boolean, val isNow: Boolean, - val value: String + val value: String, + val contentDescValue: String ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt index 95d18bb8cb..69aa348a17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt @@ -709,13 +709,17 @@ open class V2ConversationItemTextOnlyViewHolder>( binding.footerDate.text = conversationMessage.computedProperties.formattedDate.value } else { var dateLabel = conversationMessage.computedProperties.formattedDate.value + var dateLabelContentDesc = conversationMessage.computedProperties.formattedDate.contentDescValue if (conversationContext.displayMode != ConversationItemDisplayMode.Detailed && record is MmsMessageRecord && record.isEditMessage) { - dateLabel = if (conversationMessage.computedProperties.formattedDate.isNow) { - getContext().getString(R.string.ConversationItem_edited_now_timestamp_footer) + if (conversationMessage.computedProperties.formattedDate.isNow) { + dateLabel = getContext().getString(R.string.ConversationItem_edited_now_timestamp_footer) + dateLabelContentDesc = dateLabel } else if (conversationMessage.computedProperties.formattedDate.isRelative) { - getContext().getString(R.string.ConversationItem_edited_relative_timestamp_footer, dateLabel) + dateLabel = getContext().getString(R.string.ConversationItem_edited_relative_timestamp_footer, dateLabel) + dateLabelContentDesc = getContext().getString(R.string.ConversationItem_edited_relative_timestamp_footer, dateLabelContentDesc) } else { getContext().getString(R.string.ConversationItem_edited_absolute_timestamp_footer, dateLabel) + dateLabelContentDesc = dateLabel } binding.footerDate.setOnClickListener { @@ -728,6 +732,7 @@ open class V2ConversationItemTextOnlyViewHolder>( } binding.footerDate.text = dateLabel + binding.footerDate.contentDescription = dateLabelContentDesc } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index b67a168c0a..6e41f1805b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -97,6 +97,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import kotlin.Pair; import static org.thoughtcrime.securesms.database.model.LiveUpdateMessage.recipientToStringAsync; @@ -270,8 +271,9 @@ public final class ConversationListItem extends ConstraintLayout implements Bind : ContextCompat.getColor(getContext(), R.color.signal_text_primary)); updateDateView = () -> { - CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate()); - dateView.setText(date); + Pair date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate()); + dateView.setText(date.getFirst()); + dateView.setContentDescription(date.getSecond()); }; updateDateView.run(); @@ -318,7 +320,11 @@ public final class ConversationListItem extends ConstraintLayout implements Bind fromView.setText(recipient.get(), recipient.get().getDisplayName(getContext()), null, false); setSubjectViewText(SearchUtil.getHighlightedSpan(locale, searchStyleFactory, messageResult.getBodySnippet(), highlightSubstring, SearchUtil.MATCH_ALL)); - updateDateView = () -> dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.getReceivedTimestampMs())); + updateDateView = () -> { + Pair date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.getReceivedTimestampMs()); + dateView.setText(date.getFirst()); + dateView.setContentDescription(date.getSecond()); + }; updateDateView.run(); archivedView.setVisibility(GONE); @@ -353,7 +359,9 @@ public final class ConversationListItem extends ConstraintLayout implements Bind updateDateView = () -> { if (groupWithMembers.getDate() > 0) { - dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, groupWithMembers.getDate())); + Pair date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, groupWithMembers.getDate()); + dateView.setText(date.getFirst()); + dateView.setContentDescription(date.getSecond()); } else { dateView.setText(""); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index ac659f80dc..76683ea6ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -274,8 +274,11 @@ class MediaPreviewV2Fragment : } private fun bindTextViews(currentItem: MediaTable.MediaRecord, showThread: Boolean, messageBodies: Map) { - binding.toolbar.title = getTitleText(currentItem, showThread) - binding.toolbar.subtitle = getSubTitleText(currentItem) + val title = getTitleText(currentItem, showThread) + val (subtitle, subtitleContentDesc) = getSubTitleText(currentItem) + binding.toolbar.title = title + binding.toolbar.subtitle = subtitle + binding.toolbar.contentDescription = "$title $subtitleContentDesc" val messageId: Long? = currentItem.attachment?.mmsId if (messageId != null) { binding.toolbar.setOnClickListener { v -> @@ -469,11 +472,11 @@ class MediaPreviewV2Fragment : } } - private fun getSubTitleText(mediaRecord: MediaTable.MediaRecord): CharSequence { - val text = if (mediaRecord.date > 0) { + private fun getSubTitleText(mediaRecord: MediaTable.MediaRecord): Pair { + val (text, contentDesc) = if (mediaRecord.date > 0) { DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.getDefault(), mediaRecord.date) } else { - getString(R.string.MediaPreviewActivity_draft) + Pair(getString(R.string.MediaPreviewActivity_draft), getString(R.string.MediaPreviewActivity_draft)) } val builder = SpannableStringBuilder(text) @@ -482,7 +485,7 @@ class MediaPreviewV2Fragment : chevron.colorFilter = PorterDuffColorFilter(onSurfaceColor, PorterDuff.Mode.SRC_IN) SpanUtil.appendCenteredImageSpan(builder, chevron, 10, 10) - return builder + return Pair(builder, contentDesc) } private fun anchorMarginsToBottomInsets(viewToAnchor: View) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java index 22c30b4e0f..fd968a2645 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BackupsPreferenceFragment.java @@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.util.BackupUtil; import org.thoughtcrime.securesms.util.JavaTimeExtensionsKt; -import org.thoughtcrime.securesms.util.RemoteConfig; import org.thoughtcrime.securesms.util.StorageUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -50,6 +49,8 @@ import java.time.LocalTime; import java.util.Locale; import java.util.Objects; +import kotlin.Pair; + public class BackupsPreferenceFragment extends Fragment { private static final String TAG = Log.tag(BackupsPreferenceFragment.class); @@ -187,7 +188,9 @@ public class BackupsPreferenceFragment extends Fragment { } private void setBackupSummary() { - summary.setText(getString(R.string.BackupsPreferenceFragment__last_backup, BackupUtil.getLastBackupTime(requireContext(), Locale.getDefault()))); + Pair date = BackupUtil.getLastBackupTime(requireContext(), Locale.getDefault()); + summary.setText(getString(R.string.BackupsPreferenceFragment__last_backup, date.getFirst())); + summary.setContentDescription(getString(R.string.BackupsPreferenceFragment__last_backup, date.getSecond())); } private void setBackupFolderName() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt index d7403e93d1..0c9a10ede0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/restore/restorelocalbackup/RestoreLocalBackupFragment.kt @@ -303,7 +303,9 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc } if (backupTimestamp > 0) { - binding.backupCreatedText.text = getString(R.string.RegistrationActivity_backup_timestamp_s, DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.getDefault(), backupTimestamp)) + val (date, dateContentDesc) = DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.getDefault(), backupTimestamp) + binding.backupCreatedText.text = getString(R.string.RegistrationActivity_backup_timestamp_s, date) + binding.backupCreatedText.contentDescription = getString(R.string.RegistrationActivity_backup_timestamp_s, dateContentDesc) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt index a7ba9eeb96..8dae6a11f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingItem.kt @@ -264,7 +264,9 @@ object StoriesLandingItem { date.text = SpanUtil.color(ContextCompat.getColor(context, R.color.signal_alert_primary), context.getString(message)) } else { errorIndicator.visible = false - date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.data.dateInMilliseconds) + val (dateString, dateContentDescription) = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.data.dateInMilliseconds) + date.text = dateString + date.contentDescription = dateContentDescription } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt index 375bf76408..6d0f42845e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesItem.kt @@ -176,9 +176,11 @@ object MyStoriesItem { viewCount.setText(R.string.StoriesLandingItem__partially_sent) date.setText(R.string.StoriesLandingItem__tap_to_retry) } else { + val (dateString, dateContentDescription) = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.distributionStory.messageRecord.dateSent) errorIndicator.visible = false date.visible = true - date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.distributionStory.messageRecord.dateSent) + date.text = dateString + date.contentDescription = dateContentDescription } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index 2c46e2e688..c021af3b5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -1058,9 +1058,10 @@ class StoryViewerPageFragment : } private fun presentDate(date: TextView, storyPost: StoryPost) { - val formattedDate = DateUtils.getBriefRelativeTimeSpanString(requireContext(), Locale.getDefault(), storyPost.dateInMilliseconds) + val (formattedDate, formattedDateContentDesc) = DateUtils.getBriefRelativeTimeSpanString(requireContext(), Locale.getDefault(), storyPost.dateInMilliseconds) if (date.text != formattedDate) { date.text = formattedDate + date.contentDescription = formattedDateContentDesc } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt index 0246ddd467..26f75dda5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt @@ -182,9 +182,11 @@ object StoryGroupReplyItem { alertView.setNone() itemView.setOnClickListener(null) - val dateText = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.sentAtMillis) + val (dateText, dateContentDesc) = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.sentAtMillis) date.text = dateText + date.contentDescription = dateContentDesc dateBelow.text = dateText + dateBelow.contentDescription = dateContentDesc } itemView.post { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewItem.kt index 2b874b382e..a7a437b06b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/views/StoryViewItem.kt @@ -51,14 +51,16 @@ object StoryViewItem { override fun bind(model: Model) { avatarView.setAvatar(model.storyViewItemData.recipient) nameView.text = model.storyViewItemData.recipient.getDisplayName(context) - viewedAtView.text = formatDate(model.storyViewItemData.timeViewedInMillis) + val (dateString, dateContentDesc) = formatDate(model.storyViewItemData.timeViewedInMillis) + viewedAtView.text = dateString + viewedAtView.contentDescription = dateContentDesc itemView.setOnClickListener { showContextMenu(model) } } - private fun formatDate(dateInMilliseconds: Long): String { + private fun formatDate(dateInMilliseconds: Long): Pair { return DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), dateInMilliseconds) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java index 1d56cea8ef..644a05852b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.java @@ -31,21 +31,23 @@ import java.util.List; import java.util.Locale; import java.util.Objects; +import kotlin.Pair; + public class BackupUtil { private static final String TAG = Log.tag(BackupUtil.class); public static final int PASSPHRASE_LENGTH = 30; - public static @NonNull String getLastBackupTime(@NonNull Context context, @NonNull Locale locale) { + public static @NonNull Pair getLastBackupTime(@NonNull Context context, @NonNull Locale locale) { try { BackupInfo backup = getLatestBackup(); - if (backup == null) return context.getString(R.string.BackupUtil_never); + if (backup == null) return new Pair<>(context.getString(R.string.BackupUtil_never), context.getString(R.string.BackupUtil_never)); else return DateUtils.getExtendedRelativeTimeSpanString(context, locale, backup.getTimestamp()); } catch (NoExternalStorageException e) { Log.w(TAG, e); - return context.getString(R.string.BackupUtil_unknown); + return new Pair<>(context.getString(R.string.BackupUtil_unknown), context.getString(R.string.BackupUtil_unknown)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt index f46b0c5b44..8f040dc2ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DateUtils.kt @@ -23,6 +23,7 @@ import android.text.format.DateFormat import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.conversation.v2.computed.FormattedDate +import org.thoughtcrime.securesms.util.DateUtils.getBriefRelativeTimeSpanString import java.text.DateFormatSymbols import java.text.ParseException import java.text.SimpleDateFormat @@ -59,8 +60,8 @@ object DateUtils : android.text.format.DateUtils() { * A relative timestamp to use in space-constrained areas, like the conversation list. */ @JvmStatic - fun getBriefRelativeTimeSpanString(c: Context, locale: Locale, timestamp: Long): String { - return when { + fun getBriefRelativeTimeSpanString(c: Context, locale: Locale, timestamp: Long): Pair { + val time = when { isNow(timestamp) -> { c.getString(R.string.DateUtils_just_now) } @@ -82,14 +83,15 @@ object DateUtils : android.text.format.DateUtils() { timestamp.toDateString("MMM d, yyyy", locale) } } + return Pair(time, time.toAccessibleFormat(c, timestamp)) } /** * Similar to [getBriefRelativeTimeSpanString], except this will include additional time information in longer formats. */ @JvmStatic - fun getExtendedRelativeTimeSpanString(context: Context, locale: Locale, timestamp: Long): String { - return when { + fun getExtendedRelativeTimeSpanString(context: Context, locale: Locale, timestamp: Long): Pair { + val time = when { isNow(timestamp) -> { context.getString(R.string.DateUtils_just_now) } @@ -117,6 +119,7 @@ object DateUtils : android.text.format.DateUtils() { timestamp.toDateString(format.toString(), locale) } } + return Pair(time, time.toAccessibleFormat(context, timestamp)) } /** @@ -132,14 +135,14 @@ object DateUtils : android.text.format.DateUtils() { fun getDatelessRelativeTimeSpanFormattedDate(context: Context, locale: Locale, timestamp: Long): FormattedDate { return when { isNow(timestamp) -> { - FormattedDate(isRelative = true, isNow = true, value = context.getString(R.string.DateUtils_just_now)) + FormattedDate(isRelative = true, isNow = true, value = context.getString(R.string.DateUtils_just_now), contentDescValue = context.getString(R.string.DateUtils_just_now)) } timestamp.isWithin(1.hours) -> { val minutes = timestamp.convertDeltaTo(DurationUnit.MINUTES) - FormattedDate(isRelative = true, isNow = false, value = context.resources.getString(R.string.DateUtils_minutes_ago, minutes)) + FormattedDate(isRelative = true, isNow = false, value = context.resources.getString(R.string.DateUtils_minutes_ago, minutes), contentDescValue = context.resources.getQuantityString(R.plurals.DateUtils_minutes_ago_content_description, minutes, minutes)) } else -> { - FormattedDate(isRelative = false, isNow = false, value = getOnlyTimeString(context, timestamp)) + FormattedDate(isRelative = false, isNow = false, value = getOnlyTimeString(context, timestamp), contentDescValue = getOnlyTimeString(context, timestamp)) } } } @@ -428,6 +431,15 @@ object DateUtils : android.text.format.DateUtils() { } } + fun String.toAccessibleFormat(context: Context, timestamp: Long): String { + return if (!isNow(timestamp) && timestamp.isWithin(1.hours)) { + val minutes = timestamp.convertDeltaTo(DurationUnit.MINUTES) + context.resources.getQuantityString(R.plurals.DateUtils_minutes_ago_content_description, minutes, minutes) + } else { + this + } + } + private fun SimpleDateFormat.setLowercaseAmPmStrings(locale: Locale): SimpleDateFormat { val symbols = dateFormatSymbolsCache.getOrPut(locale) { DateFormatSymbols(locale).apply { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de88766157..f051dceed9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -970,6 +970,11 @@ Now %dm + + + %1$d minute + %1$d minutes + Today Yesterday