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 headermain
parent
cacac782ca
commit
6a0d7014f5
@ -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) |
||||||
|
|
||||||
|
} |
@ -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 |
||||||
|
) |
||||||
|
} |
@ -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> |
Loading…
Reference in new issue