refactor: Extract Poll display code to `PollView` (#177)
This commit is contained in:
parent
24e195f5b9
commit
c50f10a989
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<issues format="6" by="lint 8.1.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.1)" variant="all" version="8.1.1">
|
||||
<issues format="6" by="lint 8.1.2" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.2)" variant="all" version="8.1.2">
|
||||
|
||||
<issue
|
||||
id="InvalidPackage"
|
||||
|
@ -3787,7 +3787,7 @@
|
|||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
|
||||
line="236"
|
||||
line="232"
|
||||
column="25"/>
|
||||
</issue>
|
||||
|
||||
|
@ -3798,7 +3798,7 @@
|
|||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
|
||||
line="324"
|
||||
line="320"
|
||||
column="34"/>
|
||||
</issue>
|
||||
|
||||
|
@ -3809,7 +3809,7 @@
|
|||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
|
||||
line="337"
|
||||
line="333"
|
||||
column="13"/>
|
||||
</issue>
|
||||
|
||||
|
@ -3820,7 +3820,7 @@
|
|||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
|
||||
line="344"
|
||||
line="340"
|
||||
column="17"/>
|
||||
</issue>
|
||||
|
||||
|
@ -3886,7 +3886,7 @@
|
|||
errorLine2=" ~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/fragment/ViewVideoFragment.kt"
|
||||
line="143"
|
||||
line="145"
|
||||
column="32"/>
|
||||
</issue>
|
||||
|
||||
|
@ -3897,7 +3897,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/app/pachli/fragment/ViewVideoFragment.kt"
|
||||
line="224"
|
||||
line="226"
|
||||
column="21"/>
|
||||
</issue>
|
||||
|
||||
|
@ -4381,17 +4381,6 @@
|
|||
column="6"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SelectableText"
|
||||
message="Consider making the text value selectable by specifying `android:textIsSelectable="true"`"
|
||||
errorLine1=" <TextView"
|
||||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_conversation.xml"
|
||||
line="234"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SelectableText"
|
||||
message="Consider making the text value selectable by specifying `android:textIsSelectable="true"`"
|
||||
|
@ -4817,7 +4806,7 @@
|
|||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status.xml"
|
||||
line="292"
|
||||
line="287"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
|
@ -4828,7 +4817,7 @@
|
|||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status.xml"
|
||||
line="319"
|
||||
line="315"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
|
@ -4839,18 +4828,7 @@
|
|||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status.xml"
|
||||
line="347"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SelectableText"
|
||||
message="Consider making the text value selectable by specifying `android:textIsSelectable="true"`"
|
||||
errorLine1=" <TextView"
|
||||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status.xml"
|
||||
line="375"
|
||||
line="343"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
|
@ -4916,7 +4894,7 @@
|
|||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status_detailed.xml"
|
||||
line="255"
|
||||
line="234"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
|
@ -4927,7 +4905,7 @@
|
|||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status_detailed.xml"
|
||||
line="268"
|
||||
line="263"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
|
@ -4938,18 +4916,7 @@
|
|||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status_detailed.xml"
|
||||
line="297"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="SelectableText"
|
||||
message="Consider making the text value selectable by specifying `android:textIsSelectable="true"`"
|
||||
errorLine1=" <TextView"
|
||||
errorLine2=" ~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status_detailed.xml"
|
||||
line="311"
|
||||
line="277"
|
||||
column="6"/>
|
||||
</issue>
|
||||
|
||||
|
@ -5235,7 +5202,7 @@
|
|||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/res/layout/item_status.xml"
|
||||
line="309"
|
||||
line="277"
|
||||
column="9"/>
|
||||
</issue>
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package app.pachli.adapter;
|
||||
|
||||
import static app.pachli.viewdata.PollViewDataKt.buildDescription;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
|
@ -26,8 +24,6 @@ import androidx.appcompat.widget.PopupMenu;
|
|||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
@ -69,9 +65,8 @@ import app.pachli.util.TimestampUtils;
|
|||
import app.pachli.util.TouchDelegateHelper;
|
||||
import app.pachli.view.MediaPreviewImageView;
|
||||
import app.pachli.view.MediaPreviewLayout;
|
||||
import app.pachli.viewdata.PollOptionViewData;
|
||||
import app.pachli.view.PollView;
|
||||
import app.pachli.viewdata.PollViewData;
|
||||
import app.pachli.viewdata.PollViewDataKt;
|
||||
import app.pachli.viewdata.StatusViewData;
|
||||
import at.connyduck.sparkbutton.SparkButton;
|
||||
import at.connyduck.sparkbutton.helpers.Utils;
|
||||
|
@ -108,18 +103,14 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
public final TextView content;
|
||||
public final TextView contentWarningDescription;
|
||||
|
||||
private final RecyclerView pollOptions;
|
||||
private final TextView pollDescription;
|
||||
private final Button pollButton;
|
||||
|
||||
@NonNull
|
||||
private final PollView pollView;
|
||||
private final LinearLayout cardView;
|
||||
private final LinearLayout cardInfo;
|
||||
private final ShapeableImageView cardImage;
|
||||
private final TextView cardTitle;
|
||||
private final TextView cardDescription;
|
||||
private final TextView cardUrl;
|
||||
@NonNull
|
||||
private final PollAdapter pollAdapter;
|
||||
protected final LinearLayout filteredPlaceholder;
|
||||
protected final TextView filteredPlaceholderLabel;
|
||||
protected final Button filteredPlaceholderShowButton;
|
||||
|
@ -166,9 +157,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
contentWarningButton = itemView.findViewById(R.id.status_content_warning_button);
|
||||
avatarInset = itemView.findViewById(R.id.status_avatar_inset);
|
||||
|
||||
pollOptions = itemView.findViewById(R.id.status_poll_options);
|
||||
pollDescription = itemView.findViewById(R.id.status_poll_description);
|
||||
pollButton = itemView.findViewById(R.id.status_poll_button);
|
||||
pollView = itemView.findViewById(R.id.status_poll);
|
||||
|
||||
cardView = itemView.findViewById(R.id.status_card_view);
|
||||
cardInfo = itemView.findViewById(R.id.card_info);
|
||||
|
@ -182,13 +171,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
filteredPlaceholderShowButton = itemView.findViewById(R.id.status_filter_show_anyway);
|
||||
statusContainer = itemView.findViewById(R.id.status_container);
|
||||
|
||||
pollAdapter = new PollAdapter();
|
||||
pollOptions.setAdapter(pollAdapter);
|
||||
pollOptions.setLayoutManager(new LinearLayoutManager(pollOptions.getContext()));
|
||||
|
||||
DefaultItemAnimator itemAnimator = (DefaultItemAnimator) pollOptions.getItemAnimator();
|
||||
if (itemAnimator != null) itemAnimator.setSupportsChangeAnimations(false);
|
||||
|
||||
this.avatarRadius48dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_48dp);
|
||||
this.avatarRadius36dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_36dp);
|
||||
this.avatarRadius24dp = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.avatar_radius_24dp);
|
||||
|
@ -289,12 +271,29 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
updateMediaLabel(i, sensitive, true);
|
||||
}
|
||||
if (poll != null) {
|
||||
setupPoll(PollViewData.Companion.from(poll), emojis, statusDisplayOptions, listener);
|
||||
PollView.OnClickListener pollListener = (List<Integer> choices) -> {
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
if (choices == null) {
|
||||
listener.onViewThread(position);
|
||||
} else {
|
||||
listener.onVoteInPoll(position, choices);
|
||||
}
|
||||
}
|
||||
};
|
||||
pollView.bind(
|
||||
PollViewData.Companion.from(poll),
|
||||
emojis,
|
||||
statusDisplayOptions,
|
||||
numberFormat,
|
||||
absoluteTimeFormatter,
|
||||
pollListener
|
||||
);
|
||||
} else {
|
||||
hidePoll();
|
||||
pollView.hide();
|
||||
}
|
||||
} else {
|
||||
hidePoll();
|
||||
pollView.hide();
|
||||
LinkHelper.setClickableMentions(this.content, mentions, listener);
|
||||
}
|
||||
if (TextUtils.isEmpty(this.content.getText())) {
|
||||
|
@ -304,12 +303,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
|
||||
private void hidePoll() {
|
||||
pollButton.setVisibility(View.GONE);
|
||||
pollDescription.setVisibility(View.GONE);
|
||||
pollOptions.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setAvatar(String url,
|
||||
@Nullable String rebloggedUrl,
|
||||
boolean isBot,
|
||||
|
@ -875,6 +868,17 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
Context context = itemView.getContext();
|
||||
Status actionable = status.getActionable();
|
||||
|
||||
Poll poll = actionable.getPoll();
|
||||
CharSequence pollDescription = "";
|
||||
if (poll != null) {
|
||||
pollDescription = pollView.getPollDescription(
|
||||
PollViewData.Companion.from(poll),
|
||||
statusDisplayOptions,
|
||||
numberFormat,
|
||||
absoluteTimeFormatter
|
||||
);
|
||||
}
|
||||
|
||||
String description = context.getString(R.string.description_status,
|
||||
actionable.getAccount().getDisplayName(),
|
||||
getContentWarningDescription(context, status),
|
||||
|
@ -890,7 +894,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
getVisibilityDescription(context, actionable.getVisibility()),
|
||||
getFavsText(context, actionable.getFavouritesCount()),
|
||||
getReblogsText(context, actionable.getReblogsCount()),
|
||||
getPollDescription(status, context, statusDisplayOptions)
|
||||
pollDescription
|
||||
);
|
||||
itemView.setContentDescription(description);
|
||||
}
|
||||
|
@ -959,34 +963,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
return context.getString(resource);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private CharSequence getPollDescription(@NonNull StatusViewData status,
|
||||
@NonNull Context context,
|
||||
@NonNull StatusDisplayOptions statusDisplayOptions) {
|
||||
Poll poll = status.getActionable().getPoll();
|
||||
if (poll == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
PollViewData pollViewData = PollViewData.Companion.from(poll);
|
||||
Object[] args = new CharSequence[5];
|
||||
List<PollOptionViewData> options = pollViewData.getOptions();
|
||||
int totalVotes = pollViewData.getVotesCount();
|
||||
Integer totalVoters = pollViewData.getVotersCount();
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (i < options.size()) {
|
||||
int percent = PollViewDataKt.calculatePercent(options.get(i).getVotesCount(), totalVoters, totalVotes);
|
||||
args[i] = buildDescription(options.get(i).getTitle(), percent, options.get(i).getVoted(), context);
|
||||
} else {
|
||||
args[i] = "";
|
||||
}
|
||||
}
|
||||
args[4] = getPollInfoText(System.currentTimeMillis(), pollViewData, statusDisplayOptions,
|
||||
context);
|
||||
return context.getString(R.string.description_poll, args);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected CharSequence getFavsText(@NonNull Context context, int count) {
|
||||
if (count > 0) {
|
||||
|
@ -1007,100 +983,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
|
||||
private void setupPoll(@NonNull PollViewData poll, @NonNull List<Emoji> emojis,
|
||||
@NonNull StatusDisplayOptions statusDisplayOptions,
|
||||
@NonNull StatusActionListener listener) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
boolean expired = poll.getExpired() || (poll.getExpiresAt() != null && timestamp > poll.getExpiresAt().getTime());
|
||||
|
||||
Context context = pollDescription.getContext();
|
||||
|
||||
pollOptions.setVisibility(View.VISIBLE);
|
||||
|
||||
if (expired || poll.getVoted()) {
|
||||
// no voting possible
|
||||
View.OnClickListener viewThreadListener = v -> {
|
||||
int position = getBindingAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onViewThread(position);
|
||||
}
|
||||
};
|
||||
pollAdapter.setup(
|
||||
poll.getOptions(),
|
||||
poll.getVotesCount(),
|
||||
poll.getVotersCount(),
|
||||
emojis,
|
||||
PollAdapter.RESULT,
|
||||
viewThreadListener,
|
||||
statusDisplayOptions.animateEmojis()
|
||||
);
|
||||
|
||||
pollButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
// voting possible
|
||||
View.OnClickListener optionClickListener = v -> {
|
||||
pollButton.setEnabled(!pollAdapter.getSelected().isEmpty());
|
||||
};
|
||||
|
||||
pollAdapter.setup(
|
||||
poll.getOptions(),
|
||||
poll.getVotesCount(),
|
||||
poll.getVotersCount(),
|
||||
emojis,
|
||||
poll.getMultiple() ? PollAdapter.MULTIPLE : PollAdapter.SINGLE,
|
||||
null,
|
||||
statusDisplayOptions.animateEmojis(),
|
||||
true,
|
||||
optionClickListener
|
||||
);
|
||||
|
||||
pollButton.setVisibility(View.VISIBLE);
|
||||
pollButton.setEnabled(false);
|
||||
|
||||
pollButton.setOnClickListener(v -> {
|
||||
int position = getBindingAdapterPosition();
|
||||
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, statusDisplayOptions, context));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private CharSequence getPollInfoText(long timestamp, @NonNull PollViewData poll,
|
||||
@NonNull StatusDisplayOptions statusDisplayOptions,
|
||||
@NonNull Context context) {
|
||||
String votesText;
|
||||
if (poll.getVotersCount() == null) {
|
||||
String voters = numberFormat.format(poll.getVotesCount());
|
||||
votesText = context.getResources().getQuantityString(R.plurals.poll_info_votes, poll.getVotesCount(), voters);
|
||||
} else {
|
||||
String voters = numberFormat.format(poll.getVotersCount());
|
||||
votesText = context.getResources().getQuantityString(R.plurals.poll_info_people, poll.getVotersCount(), voters);
|
||||
}
|
||||
CharSequence pollDurationInfo;
|
||||
if (poll.getExpired()) {
|
||||
pollDurationInfo = context.getString(R.string.poll_info_closed);
|
||||
} else if (poll.getExpiresAt() == null) {
|
||||
return votesText;
|
||||
} else {
|
||||
if (statusDisplayOptions.useAbsoluteTime()) {
|
||||
pollDurationInfo = context.getString(R.string.poll_info_time_absolute, absoluteTimeFormatter.format(poll.getExpiresAt(), false));
|
||||
} else {
|
||||
pollDurationInfo = TimestampUtils.formatPollDuration(pollDescription.getContext(), poll.getExpiresAt().getTime(), timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
return pollDescription.getContext().getString(R.string.poll_info_format, votesText, pollDurationInfo);
|
||||
}
|
||||
|
||||
protected void setupCard(
|
||||
@NonNull final StatusViewData status,
|
||||
boolean expanded,
|
||||
|
@ -1245,9 +1127,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
content.setVisibility(visibility);
|
||||
cardView.setVisibility(visibility);
|
||||
mediaContainer.setVisibility(visibility);
|
||||
pollOptions.setVisibility(visibility);
|
||||
pollButton.setVisibility(visibility);
|
||||
pollDescription.setVisibility(visibility);
|
||||
pollView.setVisibility(visibility);
|
||||
replyButton.setVisibility(visibility);
|
||||
reblogButton.setVisibility(visibility);
|
||||
favouriteButton.setVisibility(visibility);
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright 2023 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>.
|
||||
*/
|
||||
|
||||
package app.pachli.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import app.pachli.R
|
||||
import app.pachli.adapter.PollAdapter
|
||||
import app.pachli.databinding.StatusPollBinding
|
||||
import app.pachli.entity.Emoji
|
||||
import app.pachli.util.AbsoluteTimeFormatter
|
||||
import app.pachli.util.StatusDisplayOptions
|
||||
import app.pachli.util.formatPollDuration
|
||||
import app.pachli.util.hide
|
||||
import app.pachli.util.show
|
||||
import app.pachli.viewdata.PollViewData
|
||||
import app.pachli.viewdata.buildDescription
|
||||
import app.pachli.viewdata.calculatePercent
|
||||
import java.text.NumberFormat
|
||||
|
||||
/**
|
||||
* Compound view that displays [PollViewData].
|
||||
*
|
||||
* If the poll is still active / the user hasn't voted then poll options are shown as radio
|
||||
* buttons / checkboxes (depending on whether or not it is multiple choice) and the user can
|
||||
* vote.
|
||||
*
|
||||
* Otherwise the results of the poll are shown.
|
||||
*
|
||||
* Classes hosting this should provide a [PollView.OnClickListener] to be notified when the
|
||||
* user clicks on the poll (either to vote, or to navigate).
|
||||
*/
|
||||
class PollView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
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>?): Unit
|
||||
}
|
||||
|
||||
val binding: StatusPollBinding
|
||||
|
||||
init {
|
||||
val inflater = context.getSystemService(LayoutInflater::class.java)
|
||||
binding = StatusPollBinding.inflate(inflater, this)
|
||||
orientation = VERTICAL
|
||||
}
|
||||
|
||||
fun bind(
|
||||
pollViewData: PollViewData,
|
||||
emojis: List<Emoji>,
|
||||
statusDisplayOptions: StatusDisplayOptions,
|
||||
numberFormat: NumberFormat,
|
||||
absoluteTimeFormatter: AbsoluteTimeFormatter,
|
||||
listener: OnClickListener,
|
||||
) {
|
||||
val adapter = PollAdapter()
|
||||
binding.statusPollOptions.adapter = adapter
|
||||
binding.statusPollOptions.layoutManager = LinearLayoutManager(context)
|
||||
(binding.statusPollOptions.itemAnimator as? DefaultItemAnimator)?.supportsChangeAnimations = false
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
binding.statusPollOptions.show()
|
||||
|
||||
binding.statusPollDescription.text = getPollInfoText(
|
||||
pollViewData,
|
||||
now,
|
||||
statusDisplayOptions,
|
||||
numberFormat,
|
||||
absoluteTimeFormatter,
|
||||
)
|
||||
binding.statusPollDescription.show()
|
||||
|
||||
val expired = pollViewData.expired || ((pollViewData.expiresAt != null) && (now > pollViewData.expiresAt.time))
|
||||
|
||||
// Poll expired or already voted, can't vote now
|
||||
if (expired || pollViewData.voted) {
|
||||
adapter.setup(
|
||||
pollViewData.options,
|
||||
pollViewData.votesCount,
|
||||
pollViewData.votersCount,
|
||||
emojis,
|
||||
PollAdapter.RESULT,
|
||||
{ listener.onClick(null) },
|
||||
statusDisplayOptions.animateEmojis,
|
||||
)
|
||||
binding.statusPollButton.hide()
|
||||
return
|
||||
}
|
||||
|
||||
// Active poll, can vote
|
||||
adapter.setup(
|
||||
pollViewData.options,
|
||||
pollViewData.votesCount,
|
||||
pollViewData.votersCount,
|
||||
emojis,
|
||||
if (pollViewData.multiple) PollAdapter.MULTIPLE else PollAdapter.SINGLE,
|
||||
null,
|
||||
statusDisplayOptions.animateEmojis,
|
||||
true,
|
||||
) {
|
||||
binding.statusPollButton.isEnabled = adapter.getSelected().isNotEmpty()
|
||||
}
|
||||
|
||||
binding.statusPollButton.show()
|
||||
binding.statusPollButton.isEnabled = false
|
||||
binding.statusPollButton.setOnClickListener {
|
||||
val selected = adapter.getSelected()
|
||||
if (selected.isNotEmpty()) listener.onClick(selected)
|
||||
}
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
binding.statusPollOptions.hide()
|
||||
binding.statusPollButton.hide()
|
||||
binding.statusPollDescription.hide()
|
||||
}
|
||||
|
||||
private fun getPollInfoText(
|
||||
pollViewData: PollViewData,
|
||||
timestamp: Long,
|
||||
statusDisplayOptions: StatusDisplayOptions,
|
||||
numberFormat: NumberFormat,
|
||||
absoluteTimeFormatter: AbsoluteTimeFormatter,
|
||||
): CharSequence {
|
||||
val votesText: String = if (pollViewData.votersCount == null) {
|
||||
val voters = numberFormat.format(pollViewData.votesCount.toLong())
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.poll_info_votes,
|
||||
pollViewData.votesCount,
|
||||
voters,
|
||||
)
|
||||
} else {
|
||||
val voters = numberFormat.format(pollViewData.votersCount)
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.poll_info_people,
|
||||
pollViewData.votersCount,
|
||||
voters,
|
||||
)
|
||||
}
|
||||
val pollDurationInfo: CharSequence = if (pollViewData.expired) {
|
||||
context.getString(R.string.poll_info_closed)
|
||||
} else if (pollViewData.expiresAt == null) {
|
||||
return votesText
|
||||
} else {
|
||||
if (statusDisplayOptions.useAbsoluteTime) {
|
||||
context.getString(
|
||||
R.string.poll_info_time_absolute,
|
||||
absoluteTimeFormatter.format(pollViewData.expiresAt, false),
|
||||
)
|
||||
} else {
|
||||
formatPollDuration(context, pollViewData.expiresAt.time, timestamp)
|
||||
}
|
||||
}
|
||||
return context.getString(
|
||||
R.string.poll_info_format,
|
||||
votesText,
|
||||
pollDurationInfo,
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatMatches") // Lint doesn't understand the spread (*) operator
|
||||
fun getPollDescription(
|
||||
pollViewData: PollViewData,
|
||||
statusDisplayOptions: StatusDisplayOptions,
|
||||
numberFormat: NumberFormat,
|
||||
absoluteTimeFormatter: AbsoluteTimeFormatter,
|
||||
): CharSequence {
|
||||
val args: Array<CharSequence?> = arrayOfNulls(5)
|
||||
val options = pollViewData.options
|
||||
val totalVotes = pollViewData.votesCount
|
||||
val totalVoters = pollViewData.votersCount
|
||||
for (i in args.indices) {
|
||||
if (i < options.size) {
|
||||
val percent = calculatePercent(options[i].votesCount, totalVoters, totalVotes)
|
||||
args[i] = buildDescription(options[i].title, percent, options[i].voted, context)
|
||||
} else {
|
||||
args[i] = ""
|
||||
}
|
||||
}
|
||||
args[4] = getPollInfoText(
|
||||
pollViewData,
|
||||
System.currentTimeMillis(),
|
||||
statusDisplayOptions,
|
||||
numberFormat,
|
||||
absoluteTimeFormatter,
|
||||
)
|
||||
return context.getString(R.string.description_poll, *args)
|
||||
}
|
||||
}
|
|
@ -201,46 +201,15 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/status_poll_options"
|
||||
<app.pachli.view.PollView
|
||||
android:id="@+id/status_poll"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/status_poll_button"
|
||||
style="@style/AppButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
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"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_poll_description"
|
||||
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_button"
|
||||
tools:text="7 votes • 7 hours remaining" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/status_reply"
|
||||
style="@style/AppImageButton"
|
||||
|
@ -254,7 +223,7 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/status_favourite"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll"
|
||||
app:srcCompat="@drawable/ic_reply_24dp" />
|
||||
|
||||
<at.connyduck.sparkbutton.SparkButton
|
||||
|
|
|
@ -259,48 +259,16 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/status_poll_options"
|
||||
<app.pachli.view.PollView
|
||||
android:id="@+id/status_poll"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
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" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/status_poll_button"
|
||||
style="@style/AppButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
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"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_poll_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
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_button"
|
||||
tools:text="7 votes • 7 hours remaining" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/status_reply"
|
||||
style="@style/AppImageButton"
|
||||
|
@ -313,7 +281,7 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/status_inset"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="@id/status_display_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll"
|
||||
app:srcCompat="@drawable/ic_reply_24dp" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -220,51 +220,17 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/status_poll_options"
|
||||
<app.pachli.view.PollView
|
||||
android:id="@+id/status_poll"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
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" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/status_poll_button"
|
||||
style="@style/AppButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
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"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_options" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_poll_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_button"
|
||||
tools:text="7 votes • 7 hours remaining" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_meta_info"
|
||||
android:layout_width="0dp"
|
||||
|
@ -279,7 +245,7 @@
|
|||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_poll"
|
||||
tools:text="21 Dec 2018 18:45" />
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2023 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>.
|
||||
-->
|
||||
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/status_poll_options"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/status_poll_button"
|
||||
style="@style/AppButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
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" />
|
||||
|
||||
<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:textIsSelectable="true"
|
||||
tools:text="7 votes • 7 hours remaining" />
|
||||
</merge>
|
Loading…
Reference in New Issue