From cf3aae360e6199c51755fa0bc2123ccdf35e7e78 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 29 Apr 2025 19:38:35 +0200 Subject: [PATCH] Improve poll voting & media label ui (#5047) - easier to read - easier to see where one text starts and the other one ends especially when multiline - larger clickable areas Before After --- .../tusky/adapter/PollAdapter.kt | 48 ++++-- .../tusky/adapter/StatusBaseViewHolder.java | 17 +- .../tusky/util/StatusViewHelper.kt | 3 +- .../tusky/viewdata/PollViewData.kt | 17 +- .../res/drawable/ic_check_circle_24dp.xml | 10 ++ .../main/res/layout/item_media_preview.xml | 156 ++++++++++++------ app/src/main/res/layout/item_poll.xml | 85 ++++++---- app/src/main/res/layout/item_status.xml | 21 +-- 8 files changed, 237 insertions(+), 120 deletions(-) create mode 100644 app/src/main/res/drawable/ic_check_circle_24dp.xml diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt index aac3c8159..249284bb1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -15,10 +15,12 @@ package com.keylesspalace.tusky.adapter +import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.color.MaterialColors import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ItemPollBinding import com.keylesspalace.tusky.entity.Emoji @@ -91,24 +93,34 @@ class PollAdapter : RecyclerView.Adapter>() { radioButton.isEnabled = enabled checkBox.isEnabled = enabled - when (mode) { - RESULT -> { - val percent = calculatePercent(option.votesCount, votersCount, voteCount) - resultTextView.text = buildDescription(option.title, percent, option.voted, resultTextView.context) - .emojify(emojis, resultTextView, animateEmojis) - - val level = percent * 100 - val optionColor = if (option.voted) { - R.color.colorBackgroundHighlight - } else { - R.color.colorBackgroundAccent - } + if (mode == RESULT) { + val percent = calculatePercent(option.votesCount, votersCount, voteCount) + resultTextView.text = buildDescription(option.title, percent, option.voted, resultTextView.context, resultTextView) + .emojify(emojis, resultTextView, animateEmojis) + + val level = percent * 100 + val optionColor = if (option.voted) { + R.color.colorBackgroundHighlight + } else { + R.color.colorBackgroundAccent + } - resultTextView.background.level = level - resultTextView.background.setTint(resultTextView.context.getColor(optionColor)) - resultTextView.setOnClickListener(resultClickListener) + holder.binding.pollLayout.setBackgroundResource(R.drawable.poll_option_background) + holder.binding.pollLayout.background.level = level + holder.binding.pollLayout.background.setTint(resultTextView.context.getColor(optionColor)) + holder.binding.root.strokeColor = holder.binding.root.context.getColor(optionColor) + resultTextView.setOnClickListener(resultClickListener) + } else { + holder.binding.pollLayout.background = null + + if (option.selected) { + holder.binding.root.setCardBackgroundColor(ColorStateList.valueOf(MaterialColors.getColor(holder.binding.root, com.google.android.material.R.attr.colorSurface))) + holder.binding.root.strokeColor = MaterialColors.getColor(holder.binding.root, com.google.android.material.R.attr.colorSurface) + } else { + holder.binding.root.setCardBackgroundColor(ColorStateList.valueOf(MaterialColors.getColor(holder.binding.root, android.R.attr.colorBackground))) + holder.binding.root.strokeColor = MaterialColors.getColor(holder.binding.root, R.attr.colorBackgroundAccent) } - SINGLE -> { + if (mode == SINGLE) { radioButton.text = option.title.emojify(emojis, radioButton, animateEmojis) radioButton.isChecked = option.selected radioButton.setOnClickListener { @@ -117,12 +129,12 @@ class PollAdapter : RecyclerView.Adapter>() { notifyItemChanged(index) } } - } - MULTIPLE -> { + } else { // mode == MULTIPLE checkBox.text = option.title.emojify(emojis, checkBox, animateEmojis) checkBox.isChecked = option.selected checkBox.setOnCheckedChangeListener { _, isChecked -> pollOptions[holder.bindingAdapterPosition].selected = isChecked + notifyItemChanged(holder.bindingAdapterPosition) } } } 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 b28bcc702..fe5de1467 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -103,6 +103,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { private final TextView sensitiveMediaWarning; private final View sensitiveMediaShow; protected final TextView[] mediaLabels; + protected final MaterialCardView[] mediaLabelContainers; protected final CharSequence[] mediaDescriptions; private final MaterialButton contentWarningButton; private final ImageView avatarInset; @@ -167,6 +168,12 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { itemView.findViewById(R.id.status_media_label_2), itemView.findViewById(R.id.status_media_label_3) }; + mediaLabelContainers = new MaterialCardView[]{ + itemView.findViewById(R.id.status_media_label_container_0), + itemView.findViewById(R.id.status_media_label_container_1), + itemView.findViewById(R.id.status_media_label_container_2), + itemView.findViewById(R.id.status_media_label_container_3) + }; mediaDescriptions = new CharSequence[mediaLabels.length]; contentWarningDescription = itemView.findViewById(R.id.status_content_warning_description); contentWarningButton = itemView.findViewById(R.id.status_content_warning_button); @@ -621,7 +628,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { TextView mediaLabel = mediaLabels[i]; if (i < attachments.size()) { Attachment attachment = attachments.get(i); - mediaLabel.setVisibility(View.VISIBLE); + mediaLabelContainers[i].setVisibility(View.VISIBLE); mediaDescriptions[i] = AttachmentHelper.getFormattedDescription(attachment, context); updateMediaLabel(i, sensitive, showingContent); @@ -631,7 +638,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { setAttachmentClickListener(mediaLabel, listener, i, mediaDescriptions[i], false); } else { - mediaLabel.setVisibility(View.GONE); + mediaLabelContainers[i].setVisibility(View.GONE); } } } @@ -822,8 +829,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { hideSensitiveMediaWarning(); } // Hide the unused label. - for (TextView mediaLabel : mediaLabels) { - mediaLabel.setVisibility(View.GONE); + for (MaterialCardView mediaLabelContainer : mediaLabelContainers) { + mediaLabelContainer.setVisibility(View.GONE); } } else { mediaContainer.setVisibility(View.VISIBLE); @@ -1034,7 +1041,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { for (int i = 0; i < args.length; i++) { if (i < options.size()) { int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotersCount(), poll.getVotesCount()); - args[i] = buildDescription(options.get(i).getTitle(), percent, options.get(i).getVoted(), context); + args[i] = buildDescription(options.get(i).getTitle(), percent, options.get(i).getVoted(), context, null); } else { args[i] = ""; } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index d73d9e87f..fdf848023 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -348,7 +348,8 @@ class StatusViewHelper(private val itemView: View) { options[i].title, percent, options[i].voted, - pollResults[i].context + pollResults[i].context, + pollResults[i] ) pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis) pollResults[i].visibility = View.VISIBLE diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt index 07d95982e..40995804d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt @@ -16,9 +16,16 @@ package com.keylesspalace.tusky.viewdata import android.content.Context +import android.text.Spannable import android.text.SpannableStringBuilder import android.text.Spanned +import android.text.style.DynamicDrawableSpan +import android.text.style.ImageSpan +import android.widget.TextView +import androidx.appcompat.content.res.AppCompatResources import androidx.core.text.parseAsHtml +import androidx.core.text.set +import com.google.android.material.color.MaterialColors import com.keylesspalace.tusky.R import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.PollOption @@ -52,13 +59,21 @@ fun calculatePercent(fraction: Int?, totalVoters: Int?, totalVotes: Int): Int { } } -fun buildDescription(title: String, percent: Int, voted: Boolean, context: Context): Spanned { +fun buildDescription(title: String, percent: Int, voted: Boolean, context: Context, textView: TextView? = null): Spanned { val builder = SpannableStringBuilder( context.getString(R.string.poll_percent_format, percent).parseAsHtml() ) if (voted) { builder.append(" ✓ ") + + if (textView != null) { + val size = (textView.textSize * 1.1).toInt() + val drawable = AppCompatResources.getDrawable(context, R.drawable.ic_check_circle_24dp)!! + drawable.setBounds(0, 0, size, size) + drawable.setTint(MaterialColors.getColor(textView, android.R.attr.textColorPrimary)) + builder.setSpan(ImageSpan(drawable, DynamicDrawableSpan.ALIGN_CENTER), builder.length - 2, builder.length - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } } else { builder.append(" ") } diff --git a/app/src/main/res/drawable/ic_check_circle_24dp.xml b/app/src/main/res/drawable/ic_check_circle_24dp.xml new file mode 100644 index 000000000..728be3ba6 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_circle_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/item_media_preview.xml b/app/src/main/res/layout/item_media_preview.xml index d6dd8181a..f4ef7e91a 100644 --- a/app/src/main/res/layout/item_media_preview.xml +++ b/app/src/main/res/layout/item_media_preview.xml @@ -47,68 +47,120 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintTop_toTopOf="parent"> - + + + + + app:layout_constraintTop_toBottomOf="@id/status_media_label_container_0"> - + + + + + app:layout_constraintTop_toBottomOf="@id/status_media_label_container_1"> - + + + + + app:layout_constraintTop_toBottomOf="@id/status_media_label_container_2"> + + + + diff --git a/app/src/main/res/layout/item_poll.xml b/app/src/main/res/layout/item_poll.xml index fdc0614a7..25504dbe8 100644 --- a/app/src/main/res/layout/item_poll.xml +++ b/app/src/main/res/layout/item_poll.xml @@ -1,43 +1,62 @@ - + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + app:cardCornerRadius="8dp" + app:cardBackgroundColor="?android:attr/colorBackground" + app:strokeColor="?attr/colorBackgroundAccent"> - + android:paddingStart="4dp" + android:paddingTop="4dp" + android:paddingEnd="4dp" + android:paddingBottom="4dp"> - + - + + + - + + diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index 9767905c2..17f123a1d 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -84,8 +84,8 @@ android:ellipsize="end" android:importantForAccessibility="no" android:maxLines="1" - android:paddingEnd="@dimen/status_display_name_padding_end" android:paddingStart="@dimen/status_display_name_padding_end" + android:paddingEnd="@dimen/status_display_name_padding_end" android:textColor="?android:textColorSecondary" android:textSize="?attr/status_text_medium" app:layout_constraintEnd_toStartOf="@id/status_meta_info" @@ -256,37 +256,38 @@