feat: Show announcement dates (#35) (#151)

Display the time that an announcement was posted, as well as the
most recent update to the announcement (if there is one). Time display
honours the user's "use absolute time" preference.

Fixes #35
This commit is contained in:
sanao 2023-10-17 03:04:30 +09:00 committed by GitHub
parent db2bd3199e
commit 3cfb7a0d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 3 deletions

View File

@ -27,9 +27,12 @@ import app.pachli.R
import app.pachli.databinding.ItemAnnouncementBinding import app.pachli.databinding.ItemAnnouncementBinding
import app.pachli.entity.Announcement import app.pachli.entity.Announcement
import app.pachli.interfaces.LinkListener import app.pachli.interfaces.LinkListener
import app.pachli.util.AbsoluteTimeFormatter
import app.pachli.util.BindingHolder import app.pachli.util.BindingHolder
import app.pachli.util.EmojiSpan import app.pachli.util.EmojiSpan
import app.pachli.util.emojify import app.pachli.util.emojify
import app.pachli.util.equalByMinute
import app.pachli.util.getRelativeTimeSpanString
import app.pachli.util.parseAsMastodonHtml import app.pachli.util.parseAsMastodonHtml
import app.pachli.util.setClickableText import app.pachli.util.setClickableText
import app.pachli.util.visible import app.pachli.util.visible
@ -48,7 +51,9 @@ class AnnouncementAdapter(
private val listener: AnnouncementActionListener, private val listener: AnnouncementActionListener,
private val wellbeingEnabled: Boolean = false, private val wellbeingEnabled: Boolean = false,
private val animateEmojis: Boolean = false, private val animateEmojis: Boolean = false,
private val useAbsoluteTime: Boolean = false,
) : RecyclerView.Adapter<BindingHolder<ItemAnnouncementBinding>>() { ) : RecyclerView.Adapter<BindingHolder<ItemAnnouncementBinding>>() {
private val absoluteTimeFormatter = AbsoluteTimeFormatter()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAnnouncementBinding> { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemAnnouncementBinding> {
val binding = ItemAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding = ItemAnnouncementBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -57,6 +62,29 @@ class AnnouncementAdapter(
override fun onBindViewHolder(holder: BindingHolder<ItemAnnouncementBinding>, position: Int) { override fun onBindViewHolder(holder: BindingHolder<ItemAnnouncementBinding>, position: Int) {
val item = items[position] val item = items[position]
val now = System.currentTimeMillis()
val publishTimeToDisplay = if (useAbsoluteTime) {
absoluteTimeFormatter.format(item.publishedAt, shortFormat = item.allDay)
} else {
getRelativeTimeSpanString(holder.binding.root.context, item.publishedAt.time, now)
}
val updatedAtText = if (item.updatedAt.equalByMinute(item.publishedAt)) {
// they're the same, don't show the "updated" indicator
""
} else {
// they're a minute or more apart, show the "updated" indicator
val formattedUpdatedAt = if (useAbsoluteTime) {
absoluteTimeFormatter.format(item.updatedAt, item.allDay)
} else {
getRelativeTimeSpanString(holder.binding.root.context, item.updatedAt.time, now)
}
holder.binding.root.context.getString(R.string.announcement_date_updated, formattedUpdatedAt)
}
val announcementDate = holder.binding.root.context.getString(R.string.announcement_date, publishTimeToDisplay, updatedAtText)
holder.binding.announcementDate.text = announcementDate
val text = holder.binding.text val text = holder.binding.text
val chips = holder.binding.chipGroup val chips = holder.binding.chipGroup
@ -88,7 +116,8 @@ class AnnouncementAdapter(
) )
.apply { .apply {
if (reaction.url == null) { if (reaction.url == null) {
this.text = "${reaction.name} ${reaction.count}" val reactionNameAndCountText = holder.binding.root.context.getString(R.string.reaction_name_and_count, reaction.name, reaction.count)
this.text = reactionNameAndCountText
} else { } else {
// we set the EmojiSpan on a space, because otherwise the Chip won't have the right size // we set the EmojiSpan on a space, because otherwise the Chip won't have the right size
// https://github.com/tuskyapp/Tusky/issues/2308 // https://github.com/tuskyapp/Tusky/issues/2308

View File

@ -98,8 +98,9 @@ class AnnouncementsActivity :
val wellbeingEnabled = sharedPreferencesRepository.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false) val wellbeingEnabled = sharedPreferencesRepository.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false)
val animateEmojis = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) val animateEmojis = sharedPreferencesRepository.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
val useAbsoluteTime = sharedPreferencesRepository.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false)
adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled, animateEmojis) adapter = AnnouncementAdapter(emptyList(), this, wellbeingEnabled, animateEmojis, useAbsoluteTime)
binding.announcementsList.adapter = adapter binding.announcementsList.adapter = adapter

View File

@ -0,0 +1,18 @@
package app.pachli.util
import java.util.Date
/**
* Compares this [Date] with [other], and returns true if they are equal to within the same
* minute.
*
* "Same minute" means "they are within the same clock minute", not "they are within 60 seconds
* of each other".
*/
fun Date.equalByMinute(other: Date): Boolean {
return this.minutes == other.minutes &&
this.hours == other.hours &&
this.date == other.date &&
this.month == other.month &&
this.year == other.year
}

View File

@ -6,6 +6,7 @@
<TextView <TextView
android:id="@+id/text" android:id="@+id/text"
android:textIsSelectable="true"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.1" android:lineSpacingMultiplier="1.1"
@ -22,7 +23,8 @@
android:padding="8dp" android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text"> app:layout_constraintTop_toBottomOf="@id/text"
app:layout_constraintBottom_toTopOf="@id/announcementDate">
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/addReactionChip" android:id="@+id/addReactionChip"
@ -39,4 +41,16 @@
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
<TextView
android:id="@+id/announcementDate"
android:textIsSelectable="true"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:paddingBottom="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/chipGroup"
/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -745,6 +745,8 @@
<string name="pachli_compose_post_quicksetting_label">Compose Post</string> <string name="pachli_compose_post_quicksetting_label">Compose Post</string>
<string name="account_date_joined">Joined %1$s</string> <string name="account_date_joined">Joined %1$s</string>
<string name="announcement_date_updated">(Updated: %1$s)</string>
<string name="announcement_date">%1$s %2$s</string>
<string name="saving_draft">Saving draft…</string> <string name="saving_draft">Saving draft…</string>
@ -824,4 +826,6 @@
<string name="dialog_delete_filter_text">Delete filter \'%1$s\'?"</string> <string name="dialog_delete_filter_text">Delete filter \'%1$s\'?"</string>
<string name="dialog_delete_filter_positive_action">Delete</string> <string name="dialog_delete_filter_positive_action">Delete</string>
<string name="dialog_save_profile_changes_message">Do you want to save your profile changes?</string> <string name="dialog_save_profile_changes_message">Do you want to save your profile changes?</string>
<string name="reaction_name_and_count">%1$s %2$d</string>
</resources> </resources>