Browse Source

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
<img
src="https://github.com/user-attachments/assets/131e30df-2880-4c71-9895-017d28a05853"
width="300"/> <img
src="https://github.com/user-attachments/assets/796ab3ab-7f2e-4c5e-8afb-6daaf6938406"
width="300"/>


After
<img
src="https://github.com/user-attachments/assets/dff1577b-7931-4478-be0d-9541b148b4be"
width="300"/> <img
src="https://github.com/user-attachments/assets/09f6f513-3d94-41eb-af97-342cf42d7466"
width="300"/>
pull/5074/head^2
Konrad Pozniak 11 months ago committed by GitHub
parent
commit
cf3aae360e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 48
      app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt
  2. 17
      app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
  3. 3
      app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt
  4. 17
      app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt
  5. 10
      app/src/main/res/drawable/ic_check_circle_24dp.xml
  6. 156
      app/src/main/res/layout/item_media_preview.xml
  7. 85
      app/src/main/res/layout/item_poll.xml
  8. 21
      app/src/main/res/layout/item_status.xml

48
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<BindingHolder<ItemPollBinding>>() {
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<BindingHolder<ItemPollBinding>>() {
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)
}
}
}

17
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] = "";
}

3
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

17
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(" ")
}

10
app/src/main/res/drawable/ic_check_circle_24dp.xml

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M424,664L706,382L650,326L424,552L310,438L254,494L424,664ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

156
app/src/main/res/layout/item_media_preview.xml

@ -47,68 +47,120 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/status_media_label_0"
android:layout_width="wrap_content"
<com.google.android.material.card.MaterialCardView
android:id="@+id/status_media_label_container_0"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:maxLines="10"
android:textSize="?attr/status_text_medium"
android:visibility="gone"
app:drawableTint="?android:attr/textColorTertiary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/status_media_label_1"
android:layout_width="wrap_content"
<TextView
android:id="@+id/status_media_label_0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1"
android:maxLines="10"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:drawableTint="?android:attr/textColorPrimary" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/status_media_label_container_1"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:maxLines="10"
android:textSize="?attr/status_text_medium"
android:visibility="gone"
app:drawableTint="?android:attr/textColorTertiary"
android:layout_marginTop="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_media_label_0" />
app:layout_constraintTop_toBottomOf="@id/status_media_label_container_0">
<TextView
android:id="@+id/status_media_label_2"
android:layout_width="wrap_content"
<TextView
android:id="@+id/status_media_label_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1"
android:maxLines="10"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:drawableTint="?android:attr/textColorPrimary" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/status_media_label_container_2"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:maxLines="10"
android:textSize="?attr/status_text_medium"
android:visibility="gone"
app:drawableTint="?android:attr/textColorTertiary"
android:layout_marginTop="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_media_label_1" />
app:layout_constraintTop_toBottomOf="@id/status_media_label_container_1">
<TextView
android:id="@+id/status_media_label_3"
android:layout_width="wrap_content"
<TextView
android:id="@+id/status_media_label_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1"
android:maxLines="10"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:drawableTint="?android:attr/textColorPrimary" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/status_media_label_container_3"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:maxLines="10"
android:textSize="?attr/status_text_medium"
android:visibility="gone"
app:drawableTint="?android:attr/textColorTertiary"
android:layout_marginTop="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_media_label_2" />
app:layout_constraintTop_toBottomOf="@id/status_media_label_container_2">
<TextView
android:id="@+id/status_media_label_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawablePadding="4dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:importantForAccessibility="no"
android:lineSpacingMultiplier="1.1"
android:maxLines="10"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:drawableTint="?android:attr/textColorPrimary" />
</com.google.android.material.card.MaterialCardView>
</merge>

85
app/src/main/res/layout/item_poll.xml

@ -1,43 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:cardCornerRadius="8dp"
app:cardBackgroundColor="?android:attr/colorBackground"
app:strokeColor="?attr/colorBackgroundAccent">
<TextView
android:id="@+id/status_poll_option_result"
<FrameLayout
android:id="@+id/pollLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:maxLines="3"
android:ellipsize="end"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
tools:text="40%" />
android:paddingStart="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingBottom="4dp">
<RadioButton
android:id="@+id/status_poll_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:buttonTint="@color/compound_button_color"
tools:text="Option 1" />
<TextView
android:id="@+id/status_poll_option_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lineSpacingMultiplier="1.1"
android:maxLines="3"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
tools:text="40%" />
<CheckBox
android:id="@+id/status_poll_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:buttonTint="@color/compound_button_color"
tools:text="Option 1" />
<RadioButton
android:id="@+id/status_poll_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.1"
android:paddingStart="4dp"
android:paddingEnd="0dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:buttonTint="@color/compound_button_color"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.1"
android:paddingStart="4dp"
android:paddingTop="2dp"
android:paddingEnd="0dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:buttonTint="@color/compound_button_color"
tools:text="Option 1" />
</FrameLayout>
</FrameLayout>
</com.google.android.material.card.MaterialCardView>

21
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 @@
<Button
android:id="@+id/status_poll_button"
style="@style/TuskyButton.Outlined"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginTop="2dp"
android:gravity="center"
android:minWidth="150dp"
android:minHeight="0dp"
android:paddingLeft="16dp"
android:paddingTop="4dp"
android:minHeight="0dp"
android:paddingRight="16dp"
android:paddingBottom="4dp"
android:text="@string/poll_vote"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toStartOf="@id/status_poll_results_button"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
<Button
android:id="@+id/status_poll_results_button"
style="@style/TuskyButton.Outlined"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginStart="6dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="14dp"
android:gravity="center"
android:minWidth="150dp"
android:minHeight="0dp"
android:paddingLeft="16dp"
android:paddingTop="4dp"
android:minHeight="0dp"
android:paddingRight="16dp"
android:paddingBottom="4dp"
android:layout_marginStart="4dp"
android:text="@string/poll_show_results"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/status_poll_button"
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />

Loading…
Cancel
Save