From 76c6ec5510d1721831ce852a8da3f5ac195ed020 Mon Sep 17 00:00:00 2001 From: Christophe Beyls Date: Fri, 3 May 2024 13:21:02 +0200 Subject: [PATCH] Show tooltips instead of Toasts when long-pressing attachment images (#4382) - Use `TooltipCompat.setTooltipText()` instead of setting an `OnLongClickListener` showing a Toast, to show the attachment description. This method will display native tooltips on API 26+, and set an `OnLongClickListener` on older versions to display a special Toast anchored to the view. In both cases this provides a better user experience. - Simplify `Attachment.getFormattedDescription()` by using Kotlin's `Duration`. Since it's an inline class, no extra memory will be allocated on the heap. Also, ensure that the calculation of minutes and hours use the rounded number of seconds instead of the non-rounded one. --- .../tusky/adapter/StatusBaseViewHolder.java | 18 +++++-------- .../account/media/AccountMediaGridAdapter.kt | 8 ++---- .../tusky/util/AttachmentHelper.kt | 26 +++++++++---------- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 03267c49b..0b64acebc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -17,12 +17,12 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.PopupMenu; +import androidx.appcompat.widget.TooltipCompat; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; import androidx.core.text.HtmlCompat; @@ -74,7 +74,6 @@ import java.text.NumberFormat; import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Locale; import at.connyduck.sparkbutton.SparkButton; import at.connyduck.sparkbutton.helpers.Utils; @@ -541,7 +540,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { imageView.setForeground(null); } - setAttachmentClickListener(imageView, listener, i, attachment, true); + final CharSequence formattedDescription = AttachmentHelper.getFormattedDescription(attachment, imageView.getContext()); + setAttachmentClickListener(imageView, listener, i, formattedDescription, true); if (sensitive) { sensitiveMediaWarning.setText(R.string.post_sensitive_media_title); @@ -613,15 +613,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { int drawableId = getLabelIcon(attachments.get(0).getType()); mediaLabel.setCompoundDrawablesWithIntrinsicBounds(drawableId, 0, 0, 0); - setAttachmentClickListener(mediaLabel, listener, i, attachment, false); + setAttachmentClickListener(mediaLabel, listener, i, mediaDescriptions[i], false); } else { mediaLabel.setVisibility(View.GONE); } } } - private void setAttachmentClickListener(View view, @NonNull StatusActionListener listener, - int index, Attachment attachment, boolean animateTransition) { + private void setAttachmentClickListener(@NonNull View view, @NonNull StatusActionListener listener, + int index, CharSequence description, boolean animateTransition) { view.setOnClickListener(v -> { int position = getBindingAdapterPosition(); if (position != RecyclerView.NO_POSITION) { @@ -632,11 +632,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { } } }); - view.setOnLongClickListener(v -> { - CharSequence description = AttachmentHelper.getFormattedDescription(attachment, view.getContext()); - Toast.makeText(view.getContext(), description, Toast.LENGTH_LONG).show(); - return true; - }); + TooltipCompat.setTooltipText(view, description); } protected void hideSensitiveMediaWarning() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt index c392927d3..72358c7c2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/account/media/AccountMediaGridAdapter.kt @@ -5,8 +5,8 @@ import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.TooltipCompat import androidx.core.view.setPadding import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil @@ -141,11 +141,7 @@ class AccountMediaGridAdapter( onAttachmentClickListener(item, imageView) } - holder.binding.root.setOnLongClickListener { view -> - val description = item.attachment.getFormattedDescription(view.context) - Toast.makeText(view.context, description, Toast.LENGTH_LONG).show() - true - } + TooltipCompat.setTooltipText(holder.binding.root, imageView.contentDescription) } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/AttachmentHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/AttachmentHelper.kt index f49b901a0..287e3e774 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/AttachmentHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/AttachmentHelper.kt @@ -6,24 +6,22 @@ import android.content.Context import com.keylesspalace.tusky.R import com.keylesspalace.tusky.entity.Attachment import kotlin.math.roundToInt +import kotlin.time.Duration.Companion.seconds fun Attachment.getFormattedDescription(context: Context): CharSequence { - var duration = "" - if (meta?.duration != null && meta.duration > 0) { - duration = formatDuration(meta.duration.toDouble()) + " " - } - return if (description.isNullOrEmpty()) { - duration + context.getString(R.string.description_post_media_no_description_placeholder) + val durationInSeconds = meta?.duration ?: 0f + val duration = if (durationInSeconds > 0f) { + durationInSeconds.roundToInt().seconds.toComponents { hours, minutes, seconds, _ -> + "%d:%02d:%02d ".format(hours, minutes, seconds) + } } else { - duration + description + "" + } + return duration + if (description.isNullOrEmpty()) { + context.getString(R.string.description_post_media_no_description_placeholder) + } else { + description } -} - -private fun formatDuration(durationInSeconds: Double): String { - val seconds = durationInSeconds.roundToInt() % 60 - val minutes = durationInSeconds.toInt() % 3600 / 60 - val hours = durationInSeconds.toInt() / 3600 - return "%d:%02d:%02d".format(hours, minutes, seconds) } fun List.aspectRatios(): List {