From 6a0d7014f50dfc31b7cf9a363b9a7ccb16bc0f15 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sat, 22 Jun 2019 21:55:03 +0200 Subject: [PATCH] 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 --- .../tusky/adapter/PollAdapter.kt | 123 ++++++++++ .../tusky/adapter/StatusBaseViewHolder.java | 225 ++++-------------- .../conversation/ConversationViewHolder.java | 3 +- .../report/adapter/StatusViewHolder.kt | 3 +- .../com/keylesspalace/tusky/entity/Poll.kt | 11 +- .../tusky/util/NotificationHelper.java | 9 +- .../tusky/util/StatusViewHelper.kt | 14 +- .../tusky/viewdata/PollViewData.kt | 66 +++++ .../tusky/viewdata/StatusViewData.java | 10 +- app/src/main/res/layout/item_conversation.xml | 169 +------------ app/src/main/res/layout/item_poll.xml | 42 ++++ app/src/main/res/layout/item_status.xml | 169 +------------ .../main/res/layout/item_status_detailed.xml | 170 +------------ 13 files changed, 325 insertions(+), 689 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt create mode 100644 app/src/main/res/layout/item_poll.xml diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt new file mode 100644 index 00000000..adfc681f --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -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 . */ + +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() { + + private var pollOptions: List = emptyList() + private var voteCount: Int = 0 + private var mode = RESULT + private var emojis: List = emptyList() + + fun setup(options: List, voteCount: Int, emojis: List, mode: Int) { + this.pollOptions = options + this.voteCount = voteCount + this.emojis = emojis + this.mode = mode + notifyDataSetChanged() + } + + fun getSelected() : List { + 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) + +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index 0c9d1566..76a28fcb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -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 options = poll.getOptions(); + List 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 emojis, StatusActionListener listener) { + protected void setupPoll(PollViewData poll, List 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 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 emojis) { - List 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 emojis, StatusActionListener listener) { - List 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 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)); - } - }); - - } - } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java index 1b8adba1..9fed2203 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationViewHolder.java @@ -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); } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt index a3d91950..d8c86d8f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt @@ -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) } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt index d1ae0c1c..b5a1fa76 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt @@ -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() - } - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java index 1de3b912..0f08f9b3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationHelper.java @@ -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'); diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index 931f1a4b..148461e8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -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, useAbsoluteTime: Boolean) { + fun setupPollReadonly(poll: PollViewData?, emojis: List, useAbsoluteTime: Boolean) { val pollResults = listOf( 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, pollResults: List) { + private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List) { 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]) diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt new file mode 100644 index 00000000..3b893f8b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt @@ -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 . */ + +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, + 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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java index f4183a9d..365e5c8a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java @@ -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 statusEmojis, List 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; } diff --git a/app/src/main/res/layout/item_conversation.xml b/app/src/main/res/layout/item_conversation.xml index 899b5b04..ce6673f4 100644 --- a/app/src/main/res/layout/item_conversation.xml +++ b/app/src/main/res/layout/item_conversation.xml @@ -382,171 +382,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" /> + app:layout_constraintTop_toBottomOf="@id/status_poll_options" /> + + + + + + + + + diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index 54796a70..af868bac 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -366,171 +366,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" /> + app:layout_constraintTop_toBottomOf="@id/status_poll_options" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" /> + app:layout_constraintTop_toBottomOf="@id/status_poll_options" />