Unlimited number of poll options (#1340)

* implement unlimited number of poll options

* fixes

* extract percent calculation into function so it can be used anywhere

* add license header
This commit is contained in:
Konrad Pozniak 2019-06-22 21:55:03 +02:00 committed by GitHub
parent cacac782ca
commit 6a0d7014f5
13 changed files with 325 additions and 689 deletions

View File

@ -0,0 +1,123 @@
/* Copyright 2019 Conny Duck
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.RadioButton
import android.widget.TextView
import androidx.emoji.text.EmojiCompat
import androidx.recyclerview.widget.RecyclerView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.util.CustomEmojiHelper
import com.keylesspalace.tusky.util.HtmlUtils
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.viewdata.PollOptionViewData
import com.keylesspalace.tusky.viewdata.calculatePercent
class PollAdapter: RecyclerView.Adapter<PollViewHolder>() {
private var pollOptions: List<PollOptionViewData> = emptyList()
private var voteCount: Int = 0
private var mode = RESULT
private var emojis: List<Emoji> = emptyList()
fun setup(options: List<PollOptionViewData>, voteCount: Int, emojis: List<Emoji>, mode: Int) {
this.pollOptions = options
this.voteCount = voteCount
this.emojis = emojis
this.mode = mode
notifyDataSetChanged()
}
fun getSelected() : List<Int> {
return pollOptions.filter { it.selected }
.map { pollOptions.indexOf(it) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollViewHolder {
return PollViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_poll, parent, false))
}
override fun getItemCount(): Int {
return pollOptions.size
}
override fun onBindViewHolder(holder: PollViewHolder, position: Int) {
val option = pollOptions[position]
holder.resultTextView.visible(mode == RESULT)
holder.radioButton.visible(mode == SINGLE)
holder.checkBox.visible(mode == MULTIPLE)
when(mode) {
RESULT -> {
val percent = calculatePercent(option.votesCount, voteCount)
val pollOptionText = holder.resultTextView.context.getString(R.string.poll_option_format, percent, option.title)
val emojifiedPollOptionText = CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, holder.resultTextView)
holder.resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText)
val level = percent * 100
holder.resultTextView.background.level = level
}
SINGLE -> {
val emojifiedPollOptionText = CustomEmojiHelper.emojifyString(option.title, emojis, holder.radioButton)
holder.radioButton.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.radioButton.isChecked = option.selected
holder.radioButton.setOnClickListener {
pollOptions.forEachIndexed { index, pollOption ->
pollOption.selected = index == holder.adapterPosition
notifyItemChanged(index)
}
}
}
MULTIPLE -> {
val emojifiedPollOptionText = CustomEmojiHelper.emojifyString(option.title, emojis, holder.checkBox)
holder.checkBox.text = EmojiCompat.get().process(emojifiedPollOptionText)
holder.checkBox.isChecked = option.selected
holder.checkBox.setOnCheckedChangeListener { _, isChecked ->
pollOptions[holder.adapterPosition].selected = isChecked
}
}
}
}
companion object {
const val RESULT = 0
const val SINGLE = 1
const val MULTIPLE = 2
}
}
class PollViewHolder(view: View): RecyclerView.ViewHolder(view) {
val resultTextView: TextView = view.findViewById(R.id.status_poll_option_result)
val radioButton: RadioButton = view.findViewById(R.id.status_poll_radio_button)
val checkBox: CheckBox = view.findViewById(R.id.status_poll_checkbox)
}

View File

@ -7,11 +7,8 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
@ -19,7 +16,8 @@ import android.widget.ToggleButton;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.emoji.text.EmojiCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
@ -28,8 +26,6 @@ import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Attachment.Focus;
import com.keylesspalace.tusky.entity.Attachment.MetaData;
import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Poll;
import com.keylesspalace.tusky.entity.PollOption;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
@ -39,13 +35,14 @@ import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.ThemeUtils;
import com.keylesspalace.tusky.view.MediaPreviewImageView;
import com.keylesspalace.tusky.viewdata.PollOptionViewData;
import com.keylesspalace.tusky.viewdata.PollViewData;
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import com.mikepenz.iconics.utils.Utils;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@ -74,20 +71,19 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private View sensitiveMediaShow;
protected TextView[] mediaLabels;
private ToggleButton contentWarningButton;
protected ImageView avatarInset;
private ImageView avatarInset;
public ImageView avatar;
public TextView timestampInfo;
public TextView content;
public TextView contentWarningDescription;
private TextView[] pollResults;
private RecyclerView pollOptions;
private TextView pollDescription;
private RadioGroup pollRadioGroup;
private RadioButton[] pollRadioOptions;
private CheckBox[] pollCheckboxOptions;
private Button pollButton;
private PollAdapter pollAdapter;
private boolean useAbsoluteTime;
private SimpleDateFormat shortSdf;
private SimpleDateFormat longSdf;
@ -135,31 +131,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
avatarInset = itemView.findViewById(R.id.status_avatar_inset);
pollResults = new TextView[]{
itemView.findViewById(R.id.status_poll_option_result_0),
itemView.findViewById(R.id.status_poll_option_result_1),
itemView.findViewById(R.id.status_poll_option_result_2),
itemView.findViewById(R.id.status_poll_option_result_3)
};
pollOptions = itemView.findViewById(R.id.status_poll_options);
pollDescription = itemView.findViewById(R.id.status_poll_description);
pollRadioGroup = itemView.findViewById(R.id.status_poll_radio_group);
pollRadioOptions = new RadioButton[] {
pollRadioGroup.findViewById(R.id.status_poll_radio_button_0),
pollRadioGroup.findViewById(R.id.status_poll_radio_button_1),
pollRadioGroup.findViewById(R.id.status_poll_radio_button_2),
pollRadioGroup.findViewById(R.id.status_poll_radio_button_3)
};
pollCheckboxOptions = new CheckBox[] {
itemView.findViewById(R.id.status_poll_checkbox_0),
itemView.findViewById(R.id.status_poll_checkbox_1),
itemView.findViewById(R.id.status_poll_checkbox_2),
itemView.findViewById(R.id.status_poll_checkbox_3)
};
pollButton = itemView.findViewById(R.id.status_poll_button);
pollAdapter = new PollAdapter();
pollOptions.setAdapter(pollAdapter);
pollOptions.setLayoutManager(new LinearLayoutManager(pollOptions.getContext()));
((DefaultItemAnimator) pollOptions.getItemAnimator()).setSupportsChangeAnimations(false);
this.useAbsoluteTime = useAbsoluteTime;
shortSdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
longSdf = new SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault());
@ -795,15 +775,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private CharSequence getPollDescription(Context context,
@NonNull StatusViewData.Concrete status) {
Poll poll = status.getPoll();
PollViewData poll = status.getPoll();
if (poll == null) {
return "";
} else {
CharSequence[] args = new CharSequence[5];
List<PollOption> options = poll.getOptions();
List<PollOptionViewData> options = poll.getOptions();
for (int i = 0; i < args.length; i++) {
if (i < options.size()) {
int percent = options.get(i).getPercent(poll.getVotesCount());
int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), poll.getVotesCount());
args[i] = HtmlUtils.fromHtml(context.getString(
R.string.poll_option_format,
percent,
@ -835,19 +815,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
protected void setupPoll(Poll poll, List<Emoji> emojis, StatusActionListener listener) {
protected void setupPoll(PollViewData poll, List<Emoji> emojis, StatusActionListener listener) {
if (poll == null) {
for (TextView pollResult : pollResults) {
pollResult.setVisibility(View.GONE);
}
pollDescription.setVisibility(View.GONE);
pollRadioGroup.setVisibility(View.GONE);
for (CheckBox checkBox : pollCheckboxOptions) {
checkBox.setVisibility(View.GONE);
}
pollOptions.setVisibility(View.GONE);
pollDescription.setVisibility(View.GONE);
pollButton.setVisibility(View.GONE);
} else {
long timestamp = System.currentTimeMillis();
@ -855,25 +831,49 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
Context context = pollDescription.getContext();
pollOptions.setVisibility(View.VISIBLE);
if (expired || poll.getVoted()) {
// no voting possible
setupPollResult(poll, emojis);
pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), emojis, PollAdapter.RESULT);
pollButton.setVisibility(View.GONE);
} else {
// voting possible
setupPollVoting(poll, emojis, listener);
pollAdapter.setup(poll.getOptions(), poll.getVotesCount(), emojis, poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE);
pollButton.setVisibility(View.VISIBLE);
pollButton.setOnClickListener(v -> {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
List<Integer> pollResult = pollAdapter.getSelected();
if(!pollResult.isEmpty()) {
listener.onVoteInPoll(position, pollResult);
}
}
});
}
pollDescription.setVisibility(View.VISIBLE);
pollDescription.setText(getPollInfoText(timestamp, poll, context));
}
}
private CharSequence getPollInfoText(long timestamp, Poll poll, Context context) {
private CharSequence getPollInfoText(long timestamp, PollViewData poll, Context context) {
String votes = numberFormat.format(poll.getVotesCount());
String votesText = context.getResources().getQuantityString(R.plurals.poll_info_votes, poll.getVotesCount(), votes);
CharSequence pollDurationInfo;
if (poll.getExpired()) {
pollDurationInfo = context.getString(R.string.poll_info_closed);
} else if (poll.getExpiresAt() == null) {
return votesText;
} else {
if (useAbsoluteTime) {
pollDurationInfo = context.getString(R.string.poll_info_time_absolute, getAbsoluteTime(poll.getExpiresAt()));
@ -886,129 +886,4 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return pollDescription.getContext().getString(R.string.poll_info_format, votesText, pollDurationInfo);
}
private void setupPollResult(Poll poll, List<Emoji> emojis) {
List<PollOption> options = poll.getOptions();
for(int i = 0; i < Status.MAX_POLL_OPTIONS; i++) {
if(i < options.size()) {
int percent = options.get(i).getPercent(poll.getVotesCount());
String pollOptionText = pollResults[i].getContext().getString(R.string.poll_option_format, percent, options.get(i).getTitle());
pollResults[i].setText(CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, pollResults[i]));
pollResults[i].setVisibility(View.VISIBLE);
int level = percent * 100;
pollResults[i].getBackground().setLevel(level);
} else {
pollResults[i].setVisibility(View.GONE);
}
}
pollRadioGroup.setVisibility(View.GONE);
for(CheckBox checkBox: pollCheckboxOptions) {
checkBox.setVisibility(View.GONE);
}
pollButton.setVisibility(View.GONE);
}
private void setupPollVoting(Poll poll, List<Emoji> emojis, StatusActionListener listener) {
List<PollOption> options = poll.getOptions();
pollButton.setVisibility(View.VISIBLE);
for(TextView pollResult: pollResults) {
pollResult.setVisibility(View.GONE);
}
if(poll.getMultiple()) {
pollRadioGroup.setVisibility(View.GONE);
for(int i = 0; i < Status.MAX_POLL_OPTIONS; i++) {
if(i < options.size()) {
CharSequence emojifiedPollOptionText = CustomEmojiHelper.emojifyString(options.get(i).getTitle(), emojis, pollCheckboxOptions[i]);
emojifiedPollOptionText = EmojiCompat.get().process(emojifiedPollOptionText);
pollCheckboxOptions[i].setText(emojifiedPollOptionText);
pollCheckboxOptions[i].setVisibility(View.VISIBLE);
pollCheckboxOptions[i].setChecked(false);
} else {
pollCheckboxOptions[i].setVisibility(View.GONE);
}
}
pollButton.setOnClickListener(v -> {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
List<Integer> pollResult = new ArrayList<>(options.size());
for (int i = 0; i < options.size(); i++) {
if (pollCheckboxOptions[i].isChecked()) {
pollResult.add(i);
}
}
if (pollResult.size() == 0) {
return;
}
listener.onVoteInPoll(position, pollResult);
}
});
} else {
for(CheckBox pollCheckbox: pollCheckboxOptions) {
pollCheckbox.setVisibility(View.GONE);
}
pollRadioGroup.setVisibility(View.VISIBLE);
pollRadioGroup.clearCheck();
for(int i = 0; i < Status.MAX_POLL_OPTIONS; i++) {
if(i < options.size()) {
CharSequence emojifiedPollOptionText = CustomEmojiHelper.emojifyString(options.get(i).getTitle(), emojis, pollRadioOptions[i]);
emojifiedPollOptionText = EmojiCompat.get().process(emojifiedPollOptionText);
pollRadioOptions[i].setText(emojifiedPollOptionText);
pollRadioOptions[i].setVisibility(View.VISIBLE);
} else {
pollRadioOptions[i].setVisibility(View.GONE);
}
}
pollButton.setOnClickListener(v -> {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
int selectedRadioButtonIndex;
switch (pollRadioGroup.getCheckedRadioButtonId()) {
case R.id.status_poll_radio_button_0:
selectedRadioButtonIndex = 0;
break;
case R.id.status_poll_radio_button_1:
selectedRadioButtonIndex = 1;
break;
case R.id.status_poll_radio_button_2:
selectedRadioButtonIndex = 2;
break;
case R.id.status_poll_radio_button_3:
selectedRadioButtonIndex = 3;
break;
default:
return;
}
listener.onVoteInPoll(position, Collections.singletonList(selectedRadioButtonIndex));
}
});
}
}
}

View File

@ -33,6 +33,7 @@ import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.SmartLengthInputFilter;
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
import java.util.List;
@ -109,7 +110,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
setAvatars(conversation.getAccounts());
setupPoll(status.getPoll(), status.getEmojis(), listener);
setupPoll(PollViewDataKt.toViewData(status.getPoll()), status.getEmojis(), listener);
}

View File

@ -27,6 +27,7 @@ import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.NO_INPUT_FILTER
import com.keylesspalace.tusky.viewdata.toViewData
import kotlinx.android.synthetic.main.item_report_status.view.*
import java.util.*
@ -72,7 +73,7 @@ class StatusViewHolder(itemView: View,
viewState.isMediaShow(status.id, status.sensitive),
mediaViewHeight)
statusViewHelper.setupPollReadonly(status.poll, status.emojis, useAbsoluteTime)
statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, useAbsoluteTime)
setCreatedAt(status.createdAt)
}

View File

@ -2,7 +2,6 @@ package com.keylesspalace.tusky.entity
import com.google.gson.annotations.SerializedName
import java.util.*
import kotlin.math.roundToInt
data class Poll(
val id: String,
@ -31,12 +30,4 @@ data class Poll(
data class PollOption(
val title: String,
@SerializedName("votes_count") val votesCount: Int
) {
fun getPercent(totalVotes: Int): Int {
return if (votesCount == 0) {
0
} else {
(votesCount / totalVotes.toDouble() * 100).roundToInt()
}
}
}
)

View File

@ -27,6 +27,9 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
@ -36,9 +39,6 @@ import androidx.core.app.TaskStackBuilder;
import androidx.core.content.ContextCompat;
import androidx.core.text.BidiFormatter;
import android.text.TextUtils;
import android.util.Log;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.FutureTarget;
@ -55,6 +55,7 @@ import com.keylesspalace.tusky.entity.PollOption;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver;
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver;
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
import org.json.JSONArray;
import org.json.JSONException;
@ -626,7 +627,7 @@ public class NotificationHelper {
builder.append('\n');
Poll poll = notification.getStatus().getPoll();
for(PollOption option: poll.getOptions()) {
int percent = option.getPercent(poll.getVotesCount());
int percent = PollViewDataKt.calculatePercent(option.getVotesCount(), poll.getVotesCount());
CharSequence optionText = HtmlUtils.fromHtml(context.getString(R.string.poll_option_format, percent, option.getTitle()));
builder.append(optionText);
builder.append('\n');

View File

@ -27,12 +27,14 @@ import com.bumptech.glide.Glide
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.view.MediaPreviewImageView
import com.keylesspalace.tusky.viewdata.PollViewData
import com.keylesspalace.tusky.viewdata.calculatePercent
import java.text.NumberFormat
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.min
class StatusViewHelper(private val itemView: View) {
interface MediaPreviewListener {
@ -85,7 +87,7 @@ class StatusViewHelper(private val itemView: View) {
val mediaPreviewUnloadedId = ThemeUtils.getDrawableId(context, R.attr.media_preview_unloaded_drawable, android.R.color.black)
val n = Math.min(attachments.size, Status.MAX_MEDIA_ATTACHMENTS)
val n = min(attachments.size, Status.MAX_MEDIA_ATTACHMENTS)
for (i in 0 until n) {
val previewUrl = attachments[i].previewUrl
@ -229,7 +231,7 @@ class StatusViewHelper(private val itemView: View) {
}
}
fun setupPollReadonly(poll: Poll?, emojis: List<Emoji>, useAbsoluteTime: Boolean) {
fun setupPollReadonly(poll: PollViewData?, emojis: List<Emoji>, useAbsoluteTime: Boolean) {
val pollResults = listOf<TextView>(
itemView.findViewById(R.id.status_poll_option_result_0),
itemView.findViewById(R.id.status_poll_option_result_1),
@ -254,7 +256,7 @@ class StatusViewHelper(private val itemView: View) {
}
}
private fun getPollInfoText(timestamp: Long, poll: Poll, pollDescription: TextView, useAbsoluteTime: Boolean): CharSequence {
private fun getPollInfoText(timestamp: Long, poll: PollViewData, pollDescription: TextView, useAbsoluteTime: Boolean): CharSequence {
val context = pollDescription.context
val votes = NumberFormat.getNumberInstance().format(poll.votesCount.toLong())
val votesText = context.resources.getQuantityString(R.plurals.poll_info_votes, poll.votesCount, votes)
@ -274,12 +276,12 @@ class StatusViewHelper(private val itemView: View) {
}
private fun setupPollResult(poll: Poll, emojis: List<Emoji>, pollResults: List<TextView>) {
private fun setupPollResult(poll: PollViewData, emojis: List<Emoji>, pollResults: List<TextView>) {
val options = poll.options
for (i in 0 until Status.MAX_POLL_OPTIONS) {
if (i < options.size) {
val percent = options[i].getPercent(poll.votesCount)
val percent = calculatePercent(options[i].votesCount, poll.votesCount)
val pollOptionText = pollResults[i].context.getString(R.string.poll_option_format, percent, options[i].title)
pollResults[i].text = CustomEmojiHelper.emojifyText(HtmlUtils.fromHtml(pollOptionText), emojis, pollResults[i])

View File

@ -0,0 +1,66 @@
/* Copyright 2019 Conny Duck
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.viewdata
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.PollOption
import java.util.*
import kotlin.math.roundToInt
data class PollViewData(
val id: String,
val expiresAt: Date?,
val expired: Boolean,
val multiple: Boolean,
val votesCount: Int,
val options: List<PollOptionViewData>,
var voted: Boolean
)
data class PollOptionViewData(
val title: String,
var votesCount: Int,
var selected: Boolean
)
fun calculatePercent(fraction: Int, total: Int): Int {
return if (fraction == 0) {
0
} else {
(fraction / total.toDouble() * 100).roundToInt()
}
}
fun Poll?.toViewData(): PollViewData? {
if (this == null) return null
return PollViewData(
id,
expiresAt,
expired,
multiple,
votesCount,
options.map { it.toViewData() },
voted
)
}
fun PollOption.toViewData(): PollOptionViewData {
return PollOptionViewData(
title,
votesCount,
false
)
}

View File

@ -89,7 +89,7 @@ public abstract class StatusViewData {
private final boolean isCollapsible; /** Whether the status meets the requirement to be collapse */
final boolean isCollapsed; /** Whether the status is shown partially or fully */
@Nullable
private final Poll poll;
private final PollViewData poll;
private final boolean isBot;
public Concrete(String id, Spanned content, boolean reblogged, boolean favourited,
@ -99,7 +99,7 @@ public abstract class StatusViewData {
Date createdAt, int reblogsCount, int favouritesCount, @Nullable String inReplyToId,
@Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled,
Status.Application application, List<Emoji> statusEmojis, List<Emoji> accountEmojis, @Nullable Card card,
boolean isCollapsible, boolean isCollapsed, @Nullable Poll poll, boolean isBot) {
boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot) {
this.id = id;
if (Build.VERSION.SDK_INT == 23) {
@ -273,7 +273,7 @@ public abstract class StatusViewData {
}
@Nullable
public Poll getPoll() {
public PollViewData getPoll() {
return poll;
}
@ -418,7 +418,7 @@ public abstract class StatusViewData {
private Card card;
private boolean isCollapsible; /** Whether the status meets the requirement to be collapsed */
private boolean isCollapsed; /** Whether the status is shown partially or fully */
private Poll poll;
private PollViewData poll;
private boolean isBot;
public Builder() {
@ -617,7 +617,7 @@ public abstract class StatusViewData {
}
public Builder setPoll(Poll poll) {
this.poll = poll;
this.poll = PollViewDataKt.toViewData(poll);
return this;
}

View File

@ -382,171 +382,16 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_0"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/status_poll_options"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:nestedScrollingEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container"
tools:text="40%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_0"
tools:text="10%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_1"
tools:text="20%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_2"
tools:text="30%" />
<RadioGroup
android:id="@+id/status_poll_radio_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_3">
<RadioButton
android:id="@+id/status_poll_radio_button_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 1" />
<RadioButton
android:id="@+id/status_poll_radio_button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 2" />
<RadioButton
android:id="@+id/status_poll_radio_button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 3" />
<RadioButton
android:id="@+id/status_poll_radio_button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 4" />
</RadioGroup>
<CheckBox
android:id="@+id/status_poll_checkbox_0"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_radio_group"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_0"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_1"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_2"
tools:text="Option 1" />
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" />
<!-- using AppCompatButton because we don't want the inflater to turn it into a MaterialButton -->
<androidx.appcompat.widget.AppCompatButton
@ -565,7 +410,7 @@
android:text="@string/poll_vote"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_3" />
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
<TextView
android:id="@+id/status_poll_description"

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
tools:text="40%" />
<RadioButton
android:id="@+id/status_poll_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 1" />
</FrameLayout>

View File

@ -366,171 +366,16 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_0"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/status_poll_options"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:nestedScrollingEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container"
tools:text="40%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_0"
tools:text="10%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_1"
tools:text="20%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_2"
tools:text="30%" />
<RadioGroup
android:id="@+id/status_poll_radio_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_3">
<RadioButton
android:id="@+id/status_poll_radio_button_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 1" />
<RadioButton
android:id="@+id/status_poll_radio_button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 2" />
<RadioButton
android:id="@+id/status_poll_radio_button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 3" />
<RadioButton
android:id="@+id/status_poll_radio_button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 4" />
</RadioGroup>
<CheckBox
android:id="@+id/status_poll_checkbox_0"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_radio_group"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_0"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_1"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_2"
tools:text="Option 1" />
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" />
<!-- using AppCompatButton because we don't want the inflater to turn it into a MaterialButton -->
<androidx.appcompat.widget.AppCompatButton
@ -549,7 +394,7 @@
android:text="@string/poll_vote"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_3" />
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
<TextView
android:id="@+id/status_poll_description"

View File

@ -381,172 +381,16 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_0"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/status_poll_options"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:nestedScrollingEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container"
tools:text="40%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_0"
tools:text="10%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_1"
tools:text="20%" />
<androidx.emoji.widget.EmojiTextView
android:id="@+id/status_poll_option_result_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:background="@drawable/poll_option_background"
android:ellipsize="end"
android:lines="1"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="?attr/status_text_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_2"
tools:text="30%" />
<RadioGroup
android:id="@+id/status_poll_radio_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_option_result_3">
<RadioButton
android:id="@+id/status_poll_radio_button_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 1" />
<RadioButton
android:id="@+id/status_poll_radio_button_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 2" />
<RadioButton
android:id="@+id/status_poll_radio_button_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 3" />
<RadioButton
android:id="@+id/status_poll_radio_button_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="?attr/status_text_medium"
app:buttonTint="?attr/compound_button_color"
tools:text="Option 4" />
</RadioGroup>
<CheckBox
android:id="@+id/status_poll_checkbox_0"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_radio_group"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_0"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_1"
tools:text="Option 1" />
<CheckBox
android:id="@+id/status_poll_checkbox_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
app:buttonTint="?attr/compound_button_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_2"
tools:text="Option 1" />
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" />
<!-- using AppCompatButton because we don't want the inflater to turn it into a MaterialButton -->
<androidx.appcompat.widget.AppCompatButton
@ -565,7 +409,7 @@
android:text="@string/poll_vote"
android:textSize="?attr/status_text_medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_poll_checkbox_3" />
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
<TextView
android:id="@+id/status_poll_description"