Highlight your own votes when displaying poll results (#2242)

* Highlight your own votes when displaying poll results

* Unbreak tests

* Add a checkmark to the description of self-voted options
This commit is contained in:
Levi Bard 2021-09-17 22:12:17 +02:00 committed by GitHub
parent 45598cf047
commit d07c1b098e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 47 additions and 15 deletions

View file

@ -18,8 +18,10 @@ package com.keylesspalace.tusky.adapter
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ItemPollBinding import com.keylesspalace.tusky.databinding.ItemPollBinding
import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.util.BindingHolder import com.keylesspalace.tusky.util.BindingHolder
@ -85,13 +87,19 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
when (mode) { when (mode) {
RESULT -> { RESULT -> {
val percent = calculatePercent(option.votesCount, votersCount, voteCount) val percent = calculatePercent(option.votesCount, votersCount, voteCount)
val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context) val emojifiedPollOptionText = buildDescription(option.title, percent, option.voted, resultTextView.context)
.emojify(emojis, resultTextView, animateEmojis) .emojify(emojis, resultTextView, animateEmojis)
resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100 val level = percent * 100
val optionColor = if (option.voted) {
R.color.colorBackgroundHighlight
} else {
R.color.colorBackgroundAccent
}
resultTextView.background.level = level resultTextView.background.level = level
resultTextView.background.setTint(ContextCompat.getColor(resultTextView.context, optionColor))
resultTextView.setOnClickListener(resultClickListener) resultTextView.setOnClickListener(resultClickListener)
} }
SINGLE -> { SINGLE -> {

View file

@ -889,7 +889,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if (i < options.size()) { if (i < options.size()) {
int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotersCount(), poll.getVotesCount()); int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotersCount(), poll.getVotesCount());
args[i] = buildDescription(options.get(i).getTitle(), percent, context); args[i] = buildDescription(options.get(i).getTitle(), percent, options.get(i).getVoted(), context);
} else { } else {
args[i] = ""; args[i] = "";
} }

View file

@ -659,9 +659,12 @@ public class NotificationHelper {
StringBuilder builder = new StringBuilder(notification.getStatus().getContent()); StringBuilder builder = new StringBuilder(notification.getStatus().getContent());
builder.append('\n'); builder.append('\n');
Poll poll = notification.getStatus().getPoll(); Poll poll = notification.getStatus().getPoll();
for(PollOption option: poll.getOptions()) { List<PollOption> options = poll.getOptions();
for(int i = 0; i < options.size(); ++i) {
PollOption option = options.get(i);
builder.append(buildDescription(option.getTitle(), builder.append(buildDescription(option.getTitle(),
PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotersCount(), poll.getVotesCount()), PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotersCount(), poll.getVotesCount()),
poll.getOwnVotes() != null && poll.getOwnVotes().contains(i),
context)); context));
builder.append('\n'); builder.append('\n');
} }

View file

@ -11,7 +11,8 @@ data class Poll(
@SerializedName("votes_count") val votesCount: Int, @SerializedName("votes_count") val votesCount: Int,
@SerializedName("voters_count") val votersCount: Int?, // nullable for compatibility with Pleroma @SerializedName("voters_count") val votersCount: Int?, // nullable for compatibility with Pleroma
val options: List<PollOption>, val options: List<PollOption>,
val voted: Boolean val voted: Boolean,
@SerializedName("own_votes") val ownVotes: List<Int>?
) { ) {
fun votedCopy(choices: List<Int>): Poll { fun votedCopy(choices: List<Int>): Poll {

View file

@ -23,6 +23,7 @@ import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
@ -310,13 +311,19 @@ class StatusViewHelper(private val itemView: View) {
if (i < options.size) { if (i < options.size) {
val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount) val percent = calculatePercent(options[i].votesCount, poll.votersCount, poll.votesCount)
val pollOptionText = buildDescription(options[i].title, percent, pollResults[i].context) val pollOptionText = buildDescription(options[i].title, percent, options[i].voted, pollResults[i].context)
pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis) pollResults[i].text = pollOptionText.emojify(emojis, pollResults[i], animateEmojis)
pollResults[i].visibility = View.VISIBLE pollResults[i].visibility = View.VISIBLE
val level = percent * 100 val level = percent * 100
val optionColor = if (options[i].voted) {
R.color.colorBackgroundHighlight
} else {
R.color.colorBackgroundAccent
}
pollResults[i].background.level = level pollResults[i].background.level = level
pollResults[i].background.setTint(ContextCompat.getColor(pollResults[i].context, optionColor))
} else { } else {
pollResults[i].visibility = View.GONE pollResults[i].visibility = View.GONE
} }

View file

@ -39,7 +39,8 @@ data class PollViewData(
data class PollOptionViewData( data class PollOptionViewData(
val title: String, val title: String,
var votesCount: Int, var votesCount: Int,
var selected: Boolean var selected: Boolean,
var voted: Boolean
) )
fun calculatePercent(fraction: Int, totalVoters: Int?, totalVotes: Int): Int { fun calculatePercent(fraction: Int, totalVoters: Int?, totalVotes: Int): Int {
@ -51,10 +52,14 @@ fun calculatePercent(fraction: Int, totalVoters: Int?, totalVotes: Int): Int {
} }
} }
fun buildDescription(title: String, percent: Int, context: Context): Spanned { fun buildDescription(title: String, percent: Int, voted: Boolean, context: Context): Spanned {
return SpannableStringBuilder(context.getString(R.string.poll_percent_format, percent).parseAsHtml()) val builder = SpannableStringBuilder(context.getString(R.string.poll_percent_format, percent).parseAsHtml())
.append(" ") if (voted) {
.append(title) builder.append("")
} else {
builder.append(" ")
}
return builder.append(title)
} }
fun Poll?.toViewData(): PollViewData? { fun Poll?.toViewData(): PollViewData? {
@ -66,15 +71,16 @@ fun Poll?.toViewData(): PollViewData? {
multiple = multiple, multiple = multiple,
votesCount = votesCount, votesCount = votesCount,
votersCount = votersCount, votersCount = votersCount,
options = options.map { it.toViewData() }, options = options.mapIndexed { index, option -> option.toViewData(ownVotes?.contains(index) == true) },
voted = voted voted = voted,
) )
} }
fun PollOption.toViewData(): PollOptionViewData { fun PollOption.toViewData(voted: Boolean): PollOptionViewData {
return PollOptionViewData( return PollOptionViewData(
title = title, title = title,
votesCount = votesCount, votesCount = votesCount,
selected = false selected = false,
voted = voted
) )
} }

View file

@ -15,6 +15,7 @@
<color name="iconColor">@color/tusky_grey_70</color> <color name="iconColor">@color/tusky_grey_70</color>
<color name="colorBackgroundAccent">@color/tusky_grey_30</color> <color name="colorBackgroundAccent">@color/tusky_grey_30</color>
<color name="colorBackgroundHighlight">@color/tusky_grey_50</color>
<color name="dividerColor">@color/tusky_grey_25</color> <color name="dividerColor">@color/tusky_grey_25</color>
<color name="favoriteButtonActiveColor">@color/tusky_orange</color> <color name="favoriteButtonActiveColor">@color/tusky_orange</color>

View file

@ -10,6 +10,7 @@
<!--Themed Attributes--> <!--Themed Attributes-->
<attr name="colorBackgroundAccent" format="reference|color" /> <attr name="colorBackgroundAccent" format="reference|color" />
<attr name="colorBackgroundHighlight" format="reference|color" />
<attr name="textColorDisabled" format="reference|color" /> <attr name="textColorDisabled" format="reference|color" />
<attr name="iconColor" format="reference|color" /> <attr name="iconColor" format="reference|color" />
<attr name="windowBackgroundColor" format="reference|color" /> <attr name="windowBackgroundColor" format="reference|color" />

View file

@ -54,6 +54,7 @@
<item name="android:colorBackground">@color/colorBackground</item> <item name="android:colorBackground">@color/colorBackground</item>
<item name="colorBackgroundAccent">@color/colorBackgroundAccent</item> <item name="colorBackgroundAccent">@color/colorBackgroundAccent</item>
<item name="colorBackgroundHighlight">@color/colorBackgroundHighlight</item>
<item name="windowBackgroundColor">@color/windowBackground</item> <item name="windowBackgroundColor">@color/windowBackground</item>
<item name="android:textColorPrimary">@color/textColorPrimary</item> <item name="android:textColorPrimary">@color/textColorPrimary</item>
@ -142,6 +143,7 @@
<item name="colorSurface">@color/tusky_grey_10</item> <item name="colorSurface">@color/tusky_grey_10</item>
<item name="iconColor">@color/tusky_grey_40</item> <item name="iconColor">@color/tusky_grey_40</item>
<item name="colorBackgroundHighlight">@color/tusky_grey_40</item>
<item name="colorBackgroundAccent">@color/tusky_grey_20</item> <item name="colorBackgroundAccent">@color/tusky_grey_20</item>
<item name="dividerColor">@color/tusky_grey_10</item> <item name="dividerColor">@color/tusky_grey_10</item>

View file

@ -15,6 +15,7 @@
<color name="iconColor">@color/tusky_grey_50</color> <color name="iconColor">@color/tusky_grey_50</color>
<color name="colorBackgroundAccent">@color/tusky_grey_70</color> <color name="colorBackgroundAccent">@color/tusky_grey_70</color>
<color name="colorBackgroundHighlight">@color/tusky_grey_50</color>
<color name="dividerColor">@color/tusky_grey_80</color> <color name="dividerColor">@color/tusky_grey_80</color>
<color name="favoriteButtonActiveColor">@color/tusky_orange_light</color> <color name="favoriteButtonActiveColor">@color/tusky_orange_light</color>

View file

@ -173,7 +173,8 @@ class FilterTest {
options = pollOptions.map { options = pollOptions.map {
PollOption(it, 0) PollOption(it, 0)
}, },
voted = false voted = false,
ownVotes = null
) )
} else null, } else null,
card = null card = null

View file

@ -697,6 +697,7 @@ class TimelineViewModelTest {
votesCount = 1, votesCount = 1,
voted = false, voted = false,
options = listOf(PollOption("1", 1), PollOption("2", 2)), options = listOf(PollOption("1", 1), PollOption("2", 2)),
ownVotes = null
) )
val status4 = makeStatus("4").copy(poll = poll) val status4 = makeStatus("4").copy(poll = poll)
val status3 = makeStatus("3") val status3 = makeStatus("3")