Post redesign part 2 (AND-18)

This commit is contained in:
Grishka 2024-09-18 03:57:11 +03:00
parent 3820eee174
commit ace0072118
13 changed files with 222 additions and 109 deletions

View File

@ -30,8 +30,6 @@ import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.sheets.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.sheets.OldPostPreReplySheet;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
@ -43,6 +41,8 @@ import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.sheets.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.sheets.OldPostPreReplySheet;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.MediaGridLayout;
@ -51,7 +51,6 @@ import org.joinmastodon.android.utils.TypedObjectPool;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -362,7 +361,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public abstract void onItemClick(String id);
protected void updatePoll(String itemID, Status status, Poll poll){
status.poll=poll;
if(status.poll!=poll)
status.poll=poll;
int firstOptionIndex=-1, footerIndex=-1;
int i=0;
for(StatusDisplayItem item:displayItems){
@ -393,28 +393,68 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onPollOptionClick(PollOptionStatusDisplayItem.Holder holder){
Poll poll=holder.getItem().poll;
Poll.Option option=holder.getItem().option;
if(poll.selectedOptions==null)
poll.selectedOptions=new ArrayList<>();
if(poll.multiple){
if(poll.selectedOptions==null)
poll.selectedOptions=new ArrayList<>();
if(poll.selectedOptions.contains(option)){
poll.selectedOptions.remove(option);
holder.itemView.setSelected(false);
}else{
poll.selectedOptions.add(option);
holder.itemView.setSelected(true);
}
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
if(vh instanceof PollFooterStatusDisplayItem.Holder footer){
if(footer.getItemID().equals(holder.getItemID())){
footer.rebind();
break;
}else{
if(poll.selectedOptions.contains(option))
return;
if(!poll.selectedOptions.isEmpty()){
Poll.Option previouslySelected=poll.selectedOptions.get(0);
poll.selectedOptions.clear();
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
if(vh instanceof PollOptionStatusDisplayItem.Holder otherOption){
if(otherOption.getItemID().equals(holder.getItemID()) && otherOption.getItem().option==previouslySelected){
otherOption.updateCheckedState();
break;
}
}
}
}
}else{
submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
poll.selectedOptions.add(option);
}
holder.updateCheckedState();
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
if(vh instanceof PollFooterStatusDisplayItem.Holder footer){
if(footer.getItemID().equals(holder.getItemID())){
footer.rebind();
break;
}
}
}
}
public void onPollToggleResultsClick(PollFooterStatusDisplayItem.Holder holder){
Status status=holder.getItem().status.getContentStatus();
status.poll.showResults=!status.poll.showResults;
String itemID=holder.getItemID();
if(status.poll.selectedOptions!=null)
status.poll.selectedOptions.clear();
int firstOptionIndex=-1, footerIndex=-1;
int i=0;
for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(itemID)){
if(item instanceof PollOptionStatusDisplayItem optItem){
if(firstOptionIndex==-1)
firstOptionIndex=i;
optItem.showResults=status.poll.showResults;
}else if(item instanceof PollFooterStatusDisplayItem){
footerIndex=i;
break;
}
}
i++;
}
if(firstOptionIndex==-1 || footerIndex==-1)
throw new IllegalStateException("Can't find all poll items in displayItems");
adapter.notifyItemRangeChanged(firstOptionIndex, status.poll.options.size());
}
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){

View File

@ -26,12 +26,14 @@ public class Poll extends BaseModel{
public List<Emoji> emojis;
public transient ArrayList<Option> selectedOptions;
public transient boolean showResults;
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
for(Emoji e:emojis)
e.postprocess();
showResults=voted || isExpired();
}
@Override

View File

@ -9,14 +9,19 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.utils.V;
public class PollFooterStatusDisplayItem extends StatusDisplayItem{
public final Poll poll;
public final Status status;
public PollFooterStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Poll poll){
public PollFooterStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Poll poll, Status status){
super(parentID, parentFragment);
this.poll=poll;
this.status=status;
}
@Override
@ -25,18 +30,24 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<PollFooterStatusDisplayItem>{
private TextView text;
private Button button;
private final TextView text;
private final Button voteButton, toggleResultsButton;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_footer, parent);
text=findViewById(R.id.text);
button=findViewById(R.id.vote_btn);
button.setOnClickListener(v->item.parentFragment.onPollVoteButtonClick(this));
voteButton=findViewById(R.id.vote_btn);
toggleResultsButton=findViewById(R.id.show_results_btn);
voteButton.setOnClickListener(v->item.parentFragment.onPollVoteButtonClick(this));
toggleResultsButton.setOnClickListener(v->{
item.parentFragment.onPollToggleResultsClick(this);
rebind();
});
}
@Override
public void onBind(PollFooterStatusDisplayItem item){
itemView.setPaddingRelative(V.dp(item.fullWidth ? 16 : 64), itemView.getPaddingTop(), itemView.getPaddingEnd(), itemView.getPaddingBottom());
String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_votes, item.poll.votesCount, item.poll.votesCount);
if(item.poll.expiresAt!=null && !item.poll.isExpired()){
text+=" · "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt);
@ -46,8 +57,9 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
text+=" · "+item.parentFragment.getString(R.string.poll_closed);
}
this.text.setText(text);
button.setVisibility(item.poll.isExpired() || item.poll.voted || !item.poll.multiple ? View.GONE : View.VISIBLE);
button.setEnabled(item.poll.selectedOptions!=null && !item.poll.selectedOptions.isEmpty());
voteButton.setEnabled(item.poll.selectedOptions!=null && !item.poll.selectedOptions.isEmpty() && !item.poll.isExpired() && !item.poll.voted);
toggleResultsButton.setVisibility(item.poll.isExpired() || item.poll.voted ? View.GONE : View.VISIBLE);
toggleResultsButton.setText(item.poll.showResults ? R.string.poll_hide_results : R.string.poll_see_results);
}
}
}

View File

@ -1,32 +1,38 @@
package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Shader;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.CheckableLinearLayout;
import java.util.Locale;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
private CharSequence translatedText;
public final Poll.Option option;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private boolean showResults;
public boolean showResults;
private float votesFraction; // 0..1
private boolean isMostVoted;
private final int optionIndex;
@ -42,9 +48,9 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
this.status=status;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text);
showResults=poll.isExpired() || poll.voted;
showResults=poll.showResults;
int total=poll.votersCount>0 ? poll.votersCount : poll.votesCount;
if(showResults && option.votesCount!=null && total>0){
if(option.votesCount!=null && total>0){
votesFraction=(float)option.votesCount/(float)total;
int mostVotedCount=0;
for(Poll.Option opt:poll.options)
@ -70,8 +76,11 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<PollOptionStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, percent;
private final View check, button;
private final View check;
private final Drawable progressBg;
private final Drawable checkboxIcon, radioIcon;
private final CheckableLinearLayout button;
private LinearGradient textShader, percentShader;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_option, parent);
@ -81,12 +90,26 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
button=findViewById(R.id.button);
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
itemView.setOnClickListener(this::onButtonClick);
button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true);
checkboxIcon=new CheckBox(activity).getButtonDrawable();
radioIcon=new RadioButton(activity).getButtonDrawable();
Matrix matrix=new Matrix();
button.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)->{
if(textShader!=null){
int width=right-left;
matrix.setScale(width, 1f, 0f, 0f);
matrix.postTranslate(-text.getLeft(), 0);
textShader.setLocalMatrix(matrix);
matrix.setScale(width, 1f, 0f, 0f);
matrix.postTranslate(-percent.getLeft(), 0);
percentShader.setLocalMatrix(matrix);
}
});
}
@Override
public void onBind(PollOptionStatusDisplayItem item){
itemView.setPaddingRelative(V.dp(item.fullWidth ? 16 : 64), itemView.getPaddingTop(), itemView.getPaddingEnd(), itemView.getPaddingBottom());
if (item.status.translation != null && item.status.translationState == Status.TranslationState.SHOWN) {
if(item.translatedText==null){
item.translatedText=item.status.translation.poll.options[item.optionIndex].title;
@ -96,20 +119,39 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text);
}
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
check.setVisibility(item.showResults ? View.GONE : View.VISIBLE);
itemView.setClickable(!item.showResults);
if(item.showResults){
Drawable bg=progressBg;
bg.setLevel(Math.round(10000f*item.votesFraction));
int drawableLevel=Math.round(10000f*item.votesFraction);
bg.setLevel(drawableLevel);
button.setBackground(bg);
itemView.setSelected(item.isMostVoted);
check.setSelected(item.poll.ownVotes!=null && item.poll.ownVotes.contains(item.optionIndex));
button.setChecked(item.isMostVoted);
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline));
if(item.isMostVoted){
int leftColor=UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSurfaceInverse);
int rightColor=UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline);
text.getPaint().setShader(textShader=new LinearGradient(0, 0, 1, 0, new int[]{leftColor, rightColor}, new float[]{item.votesFraction, item.votesFraction+Float.MIN_VALUE}, Shader.TileMode.CLAMP));
percent.getPaint().setShader(percentShader=new LinearGradient(0, 0, 1, 0, new int[]{leftColor, rightColor}, new float[]{item.votesFraction, item.votesFraction+Float.MIN_VALUE}, Shader.TileMode.CLAMP));
}else{
textShader=percentShader=null;
text.getPaint().setShader(null);
percent.getPaint().setShader(null);
}
}else{
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
textShader=percentShader=null;
text.getPaint().setShader(null);
percent.getPaint().setShader(null);
button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
check.setForeground(item.poll.multiple ? checkboxIcon : radioIcon);
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Primary));
updateCheckedState();
}
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Primary));
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
}
public void updateCheckedState(){
button.setChecked(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
}
@Override

View File

@ -202,7 +202,7 @@ public abstract class StatusDisplayItem{
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));
i++;
}
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll));
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll, status));
}
public enum Type{

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3Primary" android:state_checked="true"/>
<item android:color="?colorM3Outline"/>
</selector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3TertiaryContainer" android:state_selected="true"/>
<item android:color="?colorM3SurfaceVariant"/>
</selector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorM3OnTertiaryContainer" android:state_selected="true"/>
<item android:color="?colorM3OnSurface"/>
</selector>

View File

@ -1,15 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<selector android:enterFadeDuration="150" android:exitFadeDuration="150">
<item android:state_checked="true">
<shape android:tint="?colorM3Primary">
<solid android:color="#29000000"/>
<corners android:radius="9dp"/>
</shape>
</item>
</selector>
</item>
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>
<stroke android:width="0.5dp" android:color="?colorM3OutlineVariant"/>
<corners android:radius="8dp"/>
</shape>
</item>
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>
<corners android:radius="8dp"/>
</shape>
</item>
</ripple>

View File

@ -1,17 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
<shape>
<solid android:color="?colorM3SecondaryContainer"/>
<corners android:radius="20dp"/>
</shape>
</scale>
<clip>
<selector>
<item android:state_checked="true">
<shape>
<corners android:radius="9dp"/>
<solid android:color="?colorM3SurfaceInverse"/>
</shape>
</item>
<item>
<shape android:tint="?colorM3SurfaceInverse">
<corners android:radius="9dp"/>
<solid android:color="#29000000"/>
</shape>
</item>
</selector>
</clip>
</item>
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>
<stroke android:width="0.5dp" android:color="?colorM3OutlineVariant"/>
<corners android:radius="8dp"/>
</shape>
</item>
</layer-list>

View File

@ -4,29 +4,38 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="20dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/m3_body_medium"
android:layout_height="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="@style/m3_body_small"
android:gravity="center_vertical"
android:textColor="?colorM3OnSurfaceVariant"
android:textColor="?colorM3Outline"
tools:text="fdsafdsafsdafds"/>
<Button
android:id="@+id/vote_btn"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="8dp"
android:enabled="false"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:text="@string/action_vote"/>
android:orientation="horizontal">
<Button
android:id="@+id/vote_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:enabled="false"
android:layout_marginEnd="8dp"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:text="@string/action_vote"/>
<Button
android:id="@+id/show_results_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Mastodon.M3.Button.Tonal"
android:text="@string/poll_see_results"/>
</LinearLayout>
</LinearLayout>

View File

@ -3,62 +3,53 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:clipToPadding="false">
<LinearLayout
<org.joinmastodon.android.ui.views.CheckableLinearLayout
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_height="44dp"
android:paddingHorizontal="12dp"
android:background="@drawable/bg_poll_option_clickable"
android:duplicateParentState="true"
android:layoutDirection="locale">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3Primary"
android:singleLine="true"
android:ellipsize="end"
android:paddingEnd="26dp"
tools:text="scream into void jsfdklfjdalskfjdsalkfjdsalkfjdsalkfdjsalkfdsajlk"/>
<View
android:id="@+id/checkbox"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="-8dp"
android:layout_gravity="center_vertical"
android:duplicateParentState="true"
android:foregroundGravity="center"
android:foregroundTint="@color/poll_option_checkbox"/>
<ImageView
android:id="@+id/checkbox"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="-26dp"
android:tint="?colorM3OnSecondaryContainer"
android:scaleType="center"
android:src="@drawable/ic_poll_check" />
</LinearLayout>
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3Primary"
android:singleLine="true"
android:ellipsize="end"
tools:text="scream into void jsfdklfjdalskfjdsalkfjdsalkfjdsalkfdjsalkfdsajlk"/>
<TextView
android:id="@+id/percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginStart="8dp"
android:layout_gravity="center_vertical"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSecondaryContainer"
android:textAppearance="@style/m3_label_small"
android:textColor="?colorM3Outline"
android:visibility="gone"
android:gravity="end"
tools:visibility="visible"
tools:text="00.0%"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.CheckableLinearLayout>
</FrameLayout>

View File

@ -778,4 +778,6 @@
<string name="settings_donate">Donate to Mastodon</string>
<string name="settings_manage_donations">Manage donations</string>
<string name="cant_load_image">Couldnt load image</string>
<string name="poll_see_results">See results</string>
<string name="poll_hide_results">Hide results</string>
</resources>