feat: Allow user to see poll results before voting (#543)
Show a labelled checkbox to the bottom-right of polls that the user has not voted in and that have votes. If checked the current vote tally (as percentages) will be shown, along with a bar showing the relative value of each option.
This commit is contained in:
parent
b478f38e19
commit
c2fc3d1f08
|
@ -751,7 +751,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="624"
|
||||
line="625"
|
||||
column="5"/>
|
||||
</issue>
|
||||
|
||||
|
@ -1543,7 +1543,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="540"
|
||||
line="541"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
|
@ -1554,7 +1554,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="581"
|
||||
line="582"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
|
@ -1565,7 +1565,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="587"
|
||||
line="588"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
|
@ -1576,7 +1576,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="616"
|
||||
line="617"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
|
@ -1587,7 +1587,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/values/strings.xml"
|
||||
line="643"
|
||||
line="644"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
|
@ -2720,6 +2720,17 @@
|
|||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlSymmetry"
|
||||
message="When you define `paddingEnd` you should probably also define `paddingStart` for right-to-left symmetry"
|
||||
errorLine1=" android:paddingEnd="6dp""
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_poll.xml"
|
||||
line="36"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="RtlSymmetry"
|
||||
message="When you define `paddingStart` you should probably also define `paddingEnd` for right-to-left symmetry"
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package app.pachli.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import app.pachli.core.activity.emojify
|
||||
|
@ -28,7 +27,15 @@ import app.pachli.databinding.ItemPollBinding
|
|||
import app.pachli.viewdata.PollOptionViewData
|
||||
import app.pachli.viewdata.buildDescription
|
||||
import app.pachli.viewdata.calculatePercent
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/** Listener for user clicks on poll items */
|
||||
typealias PollOptionClickListener = (List<PollOptionViewData>) -> Unit
|
||||
|
||||
/** Listener for user clicks on results */
|
||||
typealias ResultClickListener = () -> Unit
|
||||
|
||||
// This can't take [app.pachli.viewdata.PollViewData] as a parameter as it also needs to show
|
||||
// data from polls that have been edited, and the "shape" of that data is quite different (no
|
||||
|
@ -43,9 +50,9 @@ class PollAdapter(
|
|||
/** True if the user can vote in this poll, false otherwise (e.g., it's from an edit) */
|
||||
val enabled: Boolean = true,
|
||||
/** Listener to call when the user clicks on the poll results */
|
||||
private val resultClickListener: View.OnClickListener? = null,
|
||||
private val resultClickListener: ResultClickListener? = null,
|
||||
/** Listener to call when the user clicks on a poll option */
|
||||
private val pollOptionClickListener: View.OnClickListener? = null,
|
||||
private val pollOptionClickListener: PollOptionClickListener? = null,
|
||||
) : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||
|
||||
/** How to display a poll */
|
||||
|
@ -60,6 +67,14 @@ class PollAdapter(
|
|||
MULTIPLE_CHOICE,
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the poll's current vote details should be shown with the controls to
|
||||
* vote, false otherwise. Ignored if the display maode is [DisplayMode.RESULT]
|
||||
*/
|
||||
var showVotes: Boolean by Delegates.observable(false) { _, _, _ ->
|
||||
notifyItemRangeChanged(0, itemCount)
|
||||
}
|
||||
|
||||
/** @return the indices of the selected options */
|
||||
fun getSelected() = options.withIndex().filter { it.value.selected }.map { it.index }
|
||||
|
||||
|
@ -92,47 +107,66 @@ class PollAdapter(
|
|||
checkBox.setTextColor(defaultTextColor)
|
||||
}
|
||||
|
||||
when (displayMode) {
|
||||
DisplayMode.RESULT -> {
|
||||
val percent = calculatePercent(option.votesCount, votersCount, votesCount)
|
||||
resultTextView.text = buildDescription(option.title, percent, option.voted, resultTextView.context)
|
||||
val percent = calculatePercent(option.votesCount, votersCount, votesCount)
|
||||
val level: Int
|
||||
val tintColor: Int
|
||||
val textColor: Int
|
||||
val itemText: CharSequence
|
||||
|
||||
when {
|
||||
displayMode == DisplayMode.RESULT && option.voted -> {
|
||||
level = percent * 100
|
||||
tintColor = MaterialColors.getColor(resultTextView, R.attr.colorPrimaryContainer)
|
||||
textColor = MaterialColors.getColor(resultTextView, R.attr.colorOnPrimaryContainer)
|
||||
itemText = buildDescription(option.title, percent, option.voted, resultTextView.context)
|
||||
.emojify(emojis, resultTextView, animateEmojis)
|
||||
|
||||
val level = percent * 100
|
||||
val optionColor: Int
|
||||
val textColor: Int
|
||||
// Use the "container" colours to ensure the text is visible on the container
|
||||
// and on the background, per https://github.com/pachli/pachli-android/issues/85
|
||||
if (option.voted) {
|
||||
optionColor = MaterialColors.getColor(resultTextView, com.google.android.material.R.attr.colorPrimaryContainer)
|
||||
textColor = MaterialColors.getColor(resultTextView, com.google.android.material.R.attr.colorOnPrimaryContainer)
|
||||
} else {
|
||||
optionColor = MaterialColors.getColor(resultTextView, com.google.android.material.R.attr.colorSecondaryContainer)
|
||||
textColor = MaterialColors.getColor(resultTextView, com.google.android.material.R.attr.colorOnSecondaryContainer)
|
||||
}
|
||||
|
||||
resultTextView.background.level = level
|
||||
resultTextView.background.setTint(optionColor)
|
||||
resultTextView.setTextColor(textColor)
|
||||
resultTextView.setOnClickListener(resultClickListener)
|
||||
}
|
||||
DisplayMode.SINGLE_CHOICE -> {
|
||||
radioButton.text = option.title.emojify(emojis, radioButton, animateEmojis)
|
||||
radioButton.isChecked = option.selected
|
||||
radioButton.setOnClickListener {
|
||||
displayMode == DisplayMode.RESULT || showVotes -> {
|
||||
level = percent * 100
|
||||
tintColor = MaterialColors.getColor(resultTextView, R.attr.colorSecondaryContainer)
|
||||
textColor = MaterialColors.getColor(resultTextView, R.attr.colorOnSecondaryContainer)
|
||||
itemText = buildDescription(option.title, percent, option.voted, resultTextView.context)
|
||||
.emojify(emojis, resultTextView, animateEmojis)
|
||||
}
|
||||
else -> {
|
||||
level = 0
|
||||
tintColor = MaterialColors.getColor(resultTextView, R.attr.colorSecondaryContainer)
|
||||
textColor = MaterialColors.getColor(resultTextView, android.R.attr.textColorPrimary)
|
||||
itemText = option.title.emojify(emojis, radioButton, animateEmojis)
|
||||
}
|
||||
}
|
||||
|
||||
when (displayMode) {
|
||||
DisplayMode.RESULT -> with(resultTextView) {
|
||||
text = itemText
|
||||
background.level = level
|
||||
background.setTint(tintColor)
|
||||
setTextColor(textColor)
|
||||
setOnClickListener { resultClickListener?.invoke() }
|
||||
}
|
||||
DisplayMode.SINGLE_CHOICE -> with(radioButton) {
|
||||
isChecked = option.selected
|
||||
text = itemText
|
||||
background.level = level
|
||||
background.setTint(tintColor)
|
||||
setTextColor(textColor)
|
||||
setOnClickListener {
|
||||
options.forEachIndexed { index, pollOption ->
|
||||
pollOption.selected = index == holder.bindingAdapterPosition
|
||||
notifyItemChanged(index)
|
||||
}
|
||||
pollOptionClickListener?.onClick(radioButton)
|
||||
pollOptionClickListener?.invoke(options)
|
||||
}
|
||||
}
|
||||
DisplayMode.MULTIPLE_CHOICE -> {
|
||||
checkBox.text = option.title.emojify(emojis, checkBox, animateEmojis)
|
||||
checkBox.isChecked = option.selected
|
||||
DisplayMode.MULTIPLE_CHOICE -> with(checkBox) {
|
||||
isChecked = option.selected
|
||||
text = itemText
|
||||
background.level = level
|
||||
background.setTint(tintColor)
|
||||
setTextColor(textColor)
|
||||
checkBox.setOnCheckedChangeListener { _, isChecked ->
|
||||
options[holder.bindingAdapterPosition].selected = isChecked
|
||||
pollOptionClickListener?.onClick(checkBox)
|
||||
pollOptionClickListener?.invoke(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,14 +19,20 @@ package app.pachli.view
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.text.style.ReplacementSpan
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import app.pachli.R
|
||||
import app.pachli.adapter.PollAdapter
|
||||
import app.pachli.adapter.PollOptionClickListener
|
||||
import app.pachli.adapter.ResultClickListener
|
||||
import app.pachli.core.common.extensions.hide
|
||||
import app.pachli.core.common.extensions.show
|
||||
import app.pachli.core.common.util.AbsoluteTimeFormatter
|
||||
|
@ -39,6 +45,13 @@ import app.pachli.viewdata.buildDescription
|
|||
import app.pachli.viewdata.calculatePercent
|
||||
import java.text.NumberFormat
|
||||
|
||||
/**
|
||||
* @param choices If null the user has clicked on the poll without voting and this
|
||||
* should be treated as a navigation click. If non-null the user has voted,
|
||||
* and [choices] contains the option(s) they voted for.
|
||||
*/
|
||||
typealias PollClickListener = (choices: List<Int>?) -> Unit
|
||||
|
||||
/**
|
||||
* Compound view that displays [PollViewData].
|
||||
*
|
||||
|
@ -56,22 +69,12 @@ class PollView @JvmOverloads constructor(
|
|||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0,
|
||||
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
|
||||
fun interface OnClickListener {
|
||||
/**
|
||||
* @param choices If null the user has clicked on the poll without voting and this
|
||||
* should be treated as a navigation click. If non-null the user has voted,
|
||||
* and [choices] contains the option(s) they voted for.
|
||||
*/
|
||||
fun onClick(choices: List<Int>?)
|
||||
}
|
||||
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
|
||||
val binding: StatusPollBinding
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(LayoutInflater::class.java)
|
||||
binding = StatusPollBinding.inflate(inflater, this)
|
||||
orientation = VERTICAL
|
||||
}
|
||||
|
||||
fun bind(
|
||||
|
@ -80,12 +83,12 @@ class PollView @JvmOverloads constructor(
|
|||
statusDisplayOptions: StatusDisplayOptions,
|
||||
numberFormat: NumberFormat,
|
||||
absoluteTimeFormatter: AbsoluteTimeFormatter,
|
||||
listener: OnClickListener,
|
||||
listener: PollClickListener,
|
||||
) {
|
||||
val now = System.currentTimeMillis()
|
||||
var displayMode: PollAdapter.DisplayMode = PollAdapter.DisplayMode.RESULT
|
||||
var resultClickListener: View.OnClickListener? = null
|
||||
var pollOptionClickListener: View.OnClickListener? = null
|
||||
var resultClickListener: ResultClickListener? = null
|
||||
var pollOptionClickListener: PollOptionClickListener? = null
|
||||
|
||||
// Translated? Create new options from old, using the translated title
|
||||
val options = pollViewData.translatedPoll?.let {
|
||||
|
@ -96,13 +99,14 @@ class PollView @JvmOverloads constructor(
|
|||
|
||||
val canVote = !(pollViewData.expired(now) || pollViewData.voted)
|
||||
if (canVote) {
|
||||
pollOptionClickListener = View.OnClickListener {
|
||||
binding.statusPollButton.isEnabled = options.firstOrNull { it.selected } != null
|
||||
pollOptionClickListener = {
|
||||
binding.statusPollVoteButton.isEnabled = it.any { it.selected }
|
||||
}
|
||||
displayMode = if (pollViewData.multiple) PollAdapter.DisplayMode.MULTIPLE_CHOICE else PollAdapter.DisplayMode.SINGLE_CHOICE
|
||||
} else {
|
||||
resultClickListener = View.OnClickListener { listener.onClick(null) }
|
||||
binding.statusPollButton.hide()
|
||||
resultClickListener = { listener(null) }
|
||||
binding.statusPollVoteButton.hide()
|
||||
binding.statusPollShowResults.hide()
|
||||
}
|
||||
|
||||
val adapter = PollAdapter(
|
||||
|
@ -136,17 +140,31 @@ class PollView @JvmOverloads constructor(
|
|||
if (!canVote) return
|
||||
|
||||
// Set up voting
|
||||
binding.statusPollButton.show()
|
||||
binding.statusPollButton.isEnabled = false
|
||||
binding.statusPollButton.setOnClickListener {
|
||||
val selected = adapter.getSelected()
|
||||
if (selected.isNotEmpty()) listener.onClick(selected)
|
||||
with(binding.statusPollVoteButton) {
|
||||
show()
|
||||
isEnabled = false
|
||||
setOnClickListener {
|
||||
val selected = adapter.getSelected()
|
||||
if (selected.isNotEmpty()) listener(selected)
|
||||
}
|
||||
}
|
||||
|
||||
// Set up showing/hiding votes
|
||||
if (pollViewData.votesCount > 0) {
|
||||
with(binding.statusPollShowResults) {
|
||||
show()
|
||||
isChecked = adapter.showVotes
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
adapter.showVotes = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
binding.statusPollOptions.hide()
|
||||
binding.statusPollButton.hide()
|
||||
binding.statusPollVoteButton.hide()
|
||||
binding.statusPollShowResults.hide()
|
||||
binding.statusPollDescription.hide()
|
||||
}
|
||||
|
||||
|
@ -222,3 +240,42 @@ class PollView @JvmOverloads constructor(
|
|||
return context.getString(R.string.description_poll, *args)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Span to show vote percentages inline in a poll.
|
||||
*
|
||||
* Shows the text at 80% of normal size and bold. Text is right-justified in a space guaranteed
|
||||
* to be large enough to accomodate "100%".
|
||||
*/
|
||||
class VotePercentSpan : ReplacementSpan() {
|
||||
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
|
||||
paint.textSize *= 0.8f
|
||||
return paint.measureText(TEMPLATE).toInt()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
|
||||
text ?: return
|
||||
val actualText = text.subSequence(start, end).toString()
|
||||
|
||||
paint.textSize *= 0.8f
|
||||
paint.typeface = Typeface.create(paint.typeface, Typeface.BOLD)
|
||||
|
||||
// Compute an x-offset for the text so it will be right aligned
|
||||
val actualTextWidth = paint.measureText(actualText)
|
||||
val spanWidth = paint.measureText(TEMPLATE)
|
||||
val xOffset = spanWidth - actualTextWidth
|
||||
|
||||
// Compute a new y value so the text will be centre-aligned within the span
|
||||
val textBounds = Rect()
|
||||
paint.getTextBounds(actualText, 0, actualText.length, textBounds)
|
||||
val spanHeight = (bottom - top)
|
||||
val newY = (spanHeight / 2) + (textBounds.height() / 2)
|
||||
|
||||
canvas.drawText(actualText, start, end, x + xOffset, newY.toFloat(), paint)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Span will be sized to be large enough for this text */
|
||||
private const val TEMPLATE = "100%"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,12 @@ package app.pachli.viewdata
|
|||
import android.content.Context
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import androidx.core.text.parseAsHtml
|
||||
import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
import app.pachli.R
|
||||
import app.pachli.core.network.model.Poll
|
||||
import app.pachli.core.network.model.PollOption
|
||||
import app.pachli.core.network.model.TranslatedPoll
|
||||
import app.pachli.view.VotePercentSpan
|
||||
import java.util.Date
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
@ -86,11 +87,11 @@ fun calculatePercent(fraction: Int, totalVoters: Int?, totalVotes: Int): Int {
|
|||
}
|
||||
|
||||
fun buildDescription(title: String, percent: Int, voted: Boolean, context: Context): Spanned {
|
||||
val builder = SpannableStringBuilder(context.getString(R.string.poll_percent_format, percent).parseAsHtml())
|
||||
val percentStr = context.getString(R.string.poll_percent_format, percent)
|
||||
val builder = SpannableStringBuilder(percentStr)
|
||||
builder.setSpan(VotePercentSpan(), 0, percentStr.length, SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
if (voted) {
|
||||
builder.append(" ✓ ")
|
||||
} else {
|
||||
builder.append(" ")
|
||||
}
|
||||
return builder.append(title)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2024 Pachli Association
|
||||
~
|
||||
~ This file is a part of Pachli.
|
||||
~
|
||||
~ 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.
|
||||
~
|
||||
~ Pachli 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 Pachli; if not,
|
||||
~ see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
|
||||
<inset
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/poll_option_background"
|
||||
android:insetTop="3dp"
|
||||
android:insetBottom="3dp" />
|
|
@ -9,7 +9,8 @@
|
|||
android:id="@+id/status_poll_option_result"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@drawable/poll_option_background"
|
||||
android:maxLines="3"
|
||||
android:ellipsize="end"
|
||||
|
@ -20,24 +21,30 @@
|
|||
android:textAlignment="viewStart"
|
||||
android:textColor="?colorOnSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
tools:visibility="gone"
|
||||
tools:text="40%" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/status_poll_radio_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:buttonTint="@color/compound_button_color"
|
||||
android:background="@drawable/poll_option_background_inset"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingEnd="6dp"
|
||||
tools:text="Option 1" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/status_poll_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:buttonTint="@color/compound_button_color"
|
||||
android:background="@drawable/poll_option_background_inset"
|
||||
tools:visibility="gone"
|
||||
tools:text="Option 1" />
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -213,7 +213,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container"
|
||||
tools:visibility="gone" />
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/translationProvider"
|
||||
|
|
|
@ -15,38 +15,55 @@
|
|||
~ see <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<merge 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"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/status_poll_options"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
android:nestedScrollingEnabled="false"
|
||||
tools:listitem="@layout/item_poll"
|
||||
tools:itemCount="4" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/status_poll_button"
|
||||
android:id="@+id/status_poll_vote_button"
|
||||
style="@style/AppButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="150dp"
|
||||
android:minHeight="0dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="@string/poll_vote"
|
||||
android:textSize="?attr/status_text_medium" />
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/status_poll_show_results"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/status_poll_options"
|
||||
app:layout_constraintWidth_max="150dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/status_poll_show_results"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:text="@string/poll_show_votes"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/status_poll_vote_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/status_poll_vote_button"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_poll_vote_button"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_poll_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_vote_button"
|
||||
tools:text="7 votes • 7 hours remaining" />
|
||||
</merge>
|
||||
|
|
|
@ -229,7 +229,7 @@
|
|||
<item>31536000</item>
|
||||
</integer-array>
|
||||
|
||||
<string name="poll_percent_format"><!-- 15% --> <b>%1$d%%</b></string>
|
||||
<string name="poll_percent_format"><!-- 15% -->%1$d%%</string>
|
||||
|
||||
<string-array name="mute_duration_names">
|
||||
<item>@string/duration_indefinite</item>
|
||||
|
|
|
@ -513,6 +513,7 @@
|
|||
<string name="poll_info_time_absolute">ends at %s</string>
|
||||
<string name="poll_info_closed">closed</string>
|
||||
<string name="poll_vote">Vote</string>
|
||||
<string name="poll_show_votes">Show votes</string>
|
||||
<string name="poll_ended_voted">A poll you have voted in has ended</string>
|
||||
<string name="poll_ended_created">A poll you created has ended</string>
|
||||
<!--These are for timestamps on polls -->
|
||||
|
|
Loading…
Reference in New Issue