Notifications M3 redesign (+ read marker support)
This commit is contained in:
@@ -1,166 +0,0 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
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.ProgressBarButton;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
||||
private final Account account;
|
||||
public ImageLoaderRequest avaRequest, coverRequest;
|
||||
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
public CharSequence parsedName, parsedBio;
|
||||
|
||||
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account){
|
||||
super(parentID, parentFragment);
|
||||
this.account=account;
|
||||
if(!TextUtils.isEmpty(account.avatar))
|
||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
||||
if(!TextUtils.isEmpty(account.header))
|
||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID());
|
||||
if(account.emojis.isEmpty()){
|
||||
parsedName=account.displayName;
|
||||
}else{
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.ACCOUNT_CARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCount(){
|
||||
return 2+emojiHelper.getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int index){
|
||||
return switch(index){
|
||||
case 0 -> avaRequest;
|
||||
case 1 -> coverRequest;
|
||||
default -> emojiHelper.getImageRequest(index-2);
|
||||
};
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<AccountCardStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final ImageView cover, avatar;
|
||||
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
||||
private final ProgressBarButton actionButton;
|
||||
private final ProgressBar actionProgress;
|
||||
private final View actionWrap;
|
||||
|
||||
private Relationship relationship;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_account_card, parent);
|
||||
|
||||
cover=findViewById(R.id.cover);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
bio=findViewById(R.id.bio);
|
||||
followersCount=findViewById(R.id.followers_count);
|
||||
followersLabel=findViewById(R.id.followers_label);
|
||||
followingCount=findViewById(R.id.following_count);
|
||||
followingLabel=findViewById(R.id.following_label);
|
||||
postsCount=findViewById(R.id.posts_count);
|
||||
postsLabel=findViewById(R.id.posts_label);
|
||||
actionButton=findViewById(R.id.action_btn);
|
||||
actionProgress=findViewById(R.id.action_progress);
|
||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||
|
||||
View card=findViewById(R.id.card);
|
||||
card.setOutlineProvider(OutlineProviders.roundedRect(6));
|
||||
card.setClipToOutline(true);
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setClipToOutline(true);
|
||||
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||
cover.setClipToOutline(true);
|
||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(AccountCardStatusDisplayItem item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText('@'+item.account.acct);
|
||||
bio.setText(item.parsedBio);
|
||||
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
relationship=item.parentFragment.getRelationship(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
}else{
|
||||
actionWrap.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void onActionButtonClick(View v){
|
||||
itemView.setHasTransientState(true);
|
||||
UiUtils.performAccountAction((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||
itemView.setHasTransientState(false);
|
||||
item.parentFragment.putRelationship(item.account.id, rel);
|
||||
rebind();
|
||||
});
|
||||
}
|
||||
|
||||
private void setActionProgressVisible(boolean visible){
|
||||
actionButton.setTextVisible(!visible);
|
||||
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
actionButton.setClickable(!visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else if(index==1){
|
||||
cover.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-2, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
}
|
||||
if(image instanceof Animatable && !((Animatable) image).isRunning())
|
||||
((Animatable) image).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
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.parceler.Parcels;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Notification notification;
|
||||
private ImageLoaderRequest avaRequest;
|
||||
private String accountID;
|
||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
private CharSequence text;
|
||||
|
||||
public NotificationHeaderStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Notification notification, String accountID){
|
||||
super(parentID, parentFragment);
|
||||
this.notification=notification;
|
||||
this.accountID=accountID;
|
||||
|
||||
if(notification.type==Notification.Type.POLL){
|
||||
text=parentFragment.getString(R.string.poll_ended);
|
||||
}else{
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic, V.dp(50), V.dp(50));
|
||||
SpannableStringBuilder parsedName=new SpannableStringBuilder(notification.account.displayName);
|
||||
HtmlParser.parseCustomEmoji(parsedName, notification.account.emojis);
|
||||
emojiHelper.setText(parsedName);
|
||||
|
||||
String[] parts=parentFragment.getString(switch(notification.type){
|
||||
case FOLLOW -> R.string.user_followed_you;
|
||||
case FOLLOW_REQUEST -> R.string.user_sent_follow_request;
|
||||
case REBLOG -> R.string.notification_boosted;
|
||||
case FAVORITE -> R.string.user_favorited;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+notification.type);
|
||||
}).split("%s", 2);
|
||||
SpannableStringBuilder text=new SpannableStringBuilder();
|
||||
if(parts.length>1 && !TextUtils.isEmpty(parts[0]))
|
||||
text.append(parts[0]);
|
||||
text.append(parsedName, new TypefaceSpan("sans-serif-medium"), 0);
|
||||
if(parts.length==1){
|
||||
text.append(' ');
|
||||
text.append(parts[0]);
|
||||
}else if(!TextUtils.isEmpty(parts[1])){
|
||||
text.append(parts[1]);
|
||||
}
|
||||
this.text=text;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.NOTIFICATION_HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCount(){
|
||||
return 1+emojiHelper.getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int index){
|
||||
if(index>0){
|
||||
return emojiHelper.getImageRequest(index-1);
|
||||
}
|
||||
return avaRequest;
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<NotificationHeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final ImageView icon, avatar;
|
||||
private final TextView text;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_notification_header, parent);
|
||||
icon=findViewById(R.id.icon);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
text=findViewById(R.id.text);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
avatar.setClipToOutline(true);
|
||||
avatar.setOnClickListener(this::onAvaClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
text.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
if(index==0)
|
||||
avatar.setImageResource(R.drawable.image_placeholder);
|
||||
else
|
||||
ImageLoaderViewHolder.super.clearImage(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(NotificationHeaderStatusDisplayItem item){
|
||||
text.setText(item.text);
|
||||
avatar.setVisibility(item.notification.type==Notification.Type.POLL ? View.GONE : View.VISIBLE);
|
||||
// TODO use real icons
|
||||
icon.setImageResource(switch(item.notification.type){
|
||||
case FAVORITE -> R.drawable.ic_star_fill1_24px;
|
||||
case REBLOG -> R.drawable.ic_repeat_fill1_24px;
|
||||
case FOLLOW, FOLLOW_REQUEST -> R.drawable.ic_person_add_fill1_24px;
|
||||
case POLL -> R.drawable.ic_insert_chart_fill1_24px;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+item.notification.type);
|
||||
});
|
||||
icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(item.parentFragment.getActivity(), switch(item.notification.type){
|
||||
case FAVORITE -> R.attr.colorFavorite;
|
||||
case REBLOG -> R.attr.colorBoost;
|
||||
case FOLLOW, FOLLOW_REQUEST -> R.attr.colorFollow;
|
||||
case POLL -> R.attr.colorPoll;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+item.notification.type);
|
||||
})));
|
||||
}
|
||||
|
||||
private void onAvaClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.notification.account));
|
||||
Nav.go(item.parentFragment.getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import org.joinmastodon.android.model.Poll;
|
||||
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 java.util.Locale;
|
||||
|
||||
@@ -65,7 +66,7 @@ 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 Drawable progressBg;
|
||||
private final Drawable progressBg, progressBgInset;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_poll_option, parent);
|
||||
@@ -74,6 +75,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
||||
check=findViewById(R.id.checkbox);
|
||||
button=findViewById(R.id.button);
|
||||
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
|
||||
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
|
||||
itemView.setOnClickListener(this::onButtonClick);
|
||||
button.setOutlineProvider(OutlineProviders.roundedRect(20));
|
||||
button.setClipToOutline(true);
|
||||
@@ -85,13 +87,21 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
||||
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
|
||||
itemView.setClickable(!item.showResults);
|
||||
if(item.showResults){
|
||||
progressBg.setLevel(Math.round(10000f*item.votesFraction));
|
||||
button.setBackground(progressBg);
|
||||
Drawable bg=item.inset ? progressBgInset : progressBg;
|
||||
bg.setLevel(Math.round(10000f*item.votesFraction));
|
||||
button.setBackground(bg);
|
||||
itemView.setSelected(item.isMostVoted);
|
||||
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
|
||||
}else{
|
||||
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
|
||||
button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
|
||||
button.setBackgroundResource(item.inset ? R.drawable.bg_poll_option_clickable_inset : R.drawable.bg_poll_option_clickable);
|
||||
}
|
||||
if(item.inset){
|
||||
text.setTextColor(itemView.getContext().getColorStateList(R.color.poll_option_text_inset));
|
||||
percent.setTextColor(itemView.getContext().getColorStateList(R.color.poll_option_text_inset));
|
||||
}else{
|
||||
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Primary));
|
||||
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ public abstract class StatusDisplayItem{
|
||||
public static final int FLAG_NO_FOOTER=1 << 1;
|
||||
public static final int FLAG_CHECKABLE=1 << 2;
|
||||
public static final int FLAG_MEDIA_FORCE_HIDDEN=1 << 3;
|
||||
public static final int FLAG_NO_HEADER=1 << 4;
|
||||
|
||||
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
this.parentID=parentID;
|
||||
@@ -64,7 +65,6 @@ public abstract class StatusDisplayItem{
|
||||
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
||||
case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent);
|
||||
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
|
||||
case ACCOUNT_CARD -> new AccountCardStatusDisplayItem.Holder(activity, parent);
|
||||
case ACCOUNT -> new AccountStatusDisplayItem.Holder(activity, parent);
|
||||
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
|
||||
@@ -72,6 +72,7 @@ public abstract class StatusDisplayItem{
|
||||
case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent);
|
||||
case SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent);
|
||||
case SECTION_HEADER -> new SectionHeaderStatusDisplayItem.Holder(activity, parent);
|
||||
case NOTIFICATION_HEADER -> new NotificationHeaderStatusDisplayItem.Holder(activity, parent);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,17 +89,19 @@ public abstract class StatusDisplayItem{
|
||||
String parentID=parentObject.getID();
|
||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||
Status statusForContent=status.getContentStatus();
|
||||
if(status.reblog!=null){
|
||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_repeat_20px));
|
||||
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
|
||||
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
|
||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_reply_20px));
|
||||
HeaderStatusDisplayItem header=null;
|
||||
if((flags & FLAG_NO_HEADER)==0){
|
||||
if(status.reblog!=null){
|
||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_repeat_20px));
|
||||
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
|
||||
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
|
||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_reply_20px));
|
||||
}
|
||||
if((flags & FLAG_CHECKABLE)!=0)
|
||||
items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
|
||||
else
|
||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
|
||||
}
|
||||
HeaderStatusDisplayItem header;
|
||||
if((flags & FLAG_CHECKABLE)!=0)
|
||||
items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
|
||||
else
|
||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
|
||||
|
||||
ArrayList<StatusDisplayItem> contentItems;
|
||||
if(!TextUtils.isEmpty(statusForContent.spoilerText)){
|
||||
@@ -109,10 +112,13 @@ public abstract class StatusDisplayItem{
|
||||
contentItems=items;
|
||||
}
|
||||
|
||||
if(!TextUtils.isEmpty(statusForContent.content))
|
||||
contentItems.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
|
||||
else
|
||||
if(!TextUtils.isEmpty(statusForContent.content)){
|
||||
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent);
|
||||
text.reduceTopPadding=header==null;
|
||||
contentItems.add(text);
|
||||
}else if(header!=null){
|
||||
header.needBottomPadding=true;
|
||||
}
|
||||
|
||||
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
|
||||
if(!imageAttachments.isEmpty()){
|
||||
@@ -171,7 +177,6 @@ public abstract class StatusDisplayItem{
|
||||
POLL_FOOTER,
|
||||
CARD,
|
||||
FOOTER,
|
||||
ACCOUNT_CARD,
|
||||
ACCOUNT,
|
||||
HASHTAG,
|
||||
GAP,
|
||||
@@ -179,7 +184,8 @@ public abstract class StatusDisplayItem{
|
||||
MEDIA_GRID,
|
||||
SPOILER,
|
||||
SECTION_HEADER,
|
||||
HEADER_CHECKABLE
|
||||
HEADER_CHECKABLE,
|
||||
NOTIFICATION_HEADER
|
||||
}
|
||||
|
||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||
|
||||
@@ -9,16 +9,19 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.MovieDrawable;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
private CharSequence text;
|
||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
public boolean textSelectable;
|
||||
public boolean reduceTopPadding;
|
||||
public final Status status;
|
||||
|
||||
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){
|
||||
@@ -57,6 +60,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
text.setTextIsSelectable(item.textSelectable);
|
||||
text.setInvalidateOnEveryFrame(false);
|
||||
itemView.setClickable(false);
|
||||
text.setPadding(text.getPaddingLeft(), item.reduceTopPadding ? V.dp(8) : V.dp(16), text.getPaddingRight(), text.getPaddingBottom());
|
||||
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,8 +28,8 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
|
||||
public InsetStatusItemDecoration(BaseStatusListFragment<?> listFragment){
|
||||
this.listFragment=listFragment;
|
||||
bgColor=UiUtils.getThemeColor(listFragment.getActivity(), android.R.attr.colorBackground);
|
||||
borderColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorPollVoted);
|
||||
bgColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorM3SurfaceVariant);
|
||||
borderColor=UiUtils.getThemeColor(listFragment.getActivity(), R.attr.colorM3OutlineVariant);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,9 +64,8 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private void drawInsetBackground(RecyclerView list, Canvas c){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(bgColor);
|
||||
rect.left=V.dp(12);
|
||||
rect.right=list.getWidth()-V.dp(12);
|
||||
rect.inset(V.dp(4), V.dp(4));
|
||||
rect.left=V.dp(16);
|
||||
rect.right=list.getWidth()-V.dp(16);
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(V.dp(1));
|
||||
@@ -85,20 +84,15 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
StatusDisplayItem.Type type=sdi.getItem().getType();
|
||||
if(type==StatusDisplayItem.Type.CARD || type==StatusDisplayItem.Type.MEDIA_GRID)
|
||||
outRect.left=outRect.right=V.dp(16);
|
||||
else
|
||||
pad=V.dp(12);
|
||||
boolean insetLeft=true, insetRight=true;
|
||||
if(insetLeft)
|
||||
outRect.left=pad;
|
||||
if(insetRight)
|
||||
outRect.right=pad;
|
||||
if(!topSiblingInset)
|
||||
outRect.top=pad;
|
||||
outRect.left=outRect.right=V.dp(8);
|
||||
if(!bottomSiblingInset)
|
||||
outRect.bottom=pad;
|
||||
outRect.bottom=V.dp(16);
|
||||
if(!topSiblingInset)
|
||||
outRect.top=V.dp(-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
public class CheckIconSelectableTextView extends TextView{
|
||||
|
||||
private boolean currentlySelected;
|
||||
|
||||
public CheckIconSelectableTextView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CheckIconSelectableTextView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CheckIconSelectableTextView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged(){
|
||||
super.drawableStateChanged();
|
||||
if(currentlySelected==isSelected())
|
||||
return;
|
||||
currentlySelected=isSelected();
|
||||
Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()).mutate() : null;
|
||||
if(start!=null)
|
||||
start.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurface));
|
||||
Drawable end=getCompoundDrawablesRelative()[2];
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package org.joinmastodon.android.ui.views;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -11,9 +10,7 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FilterChipView extends Button{
|
||||
|
||||
private boolean currentlySelected;
|
||||
public class FilterChipView extends CheckIconSelectableTextView{
|
||||
|
||||
public FilterChipView(Context context){
|
||||
this(context, null);
|
||||
@@ -35,14 +32,6 @@ public class FilterChipView extends Button{
|
||||
@Override
|
||||
protected void drawableStateChanged(){
|
||||
super.drawableStateChanged();
|
||||
if(currentlySelected==isSelected())
|
||||
return;
|
||||
currentlySelected=isSelected();
|
||||
Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()).mutate() : null;
|
||||
if(start!=null)
|
||||
start.setTint(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurface));
|
||||
Drawable end=getCompoundDrawablesRelative()[2];
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
|
||||
updatePadding();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class NestedRecyclerScrollView extends CustomScrollView{
|
||||
private Supplier<RecyclerView> scrollableChildSupplier;
|
||||
private boolean takePriorityOverChildViews;
|
||||
|
||||
public NestedRecyclerScrollView(Context context){
|
||||
super(context);
|
||||
@@ -25,32 +26,43 @@ public class NestedRecyclerScrollView extends CustomScrollView{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
|
||||
if(target instanceof RecyclerView rv && ((dy < 0 && isScrolledToTop(rv)) || (dy > 0 && !isScrolledToBottom()))){
|
||||
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed){
|
||||
if(takePriorityOverChildViews){
|
||||
if((dy<0 && getScrollY()>0) || (dy>0 && canScrollVertically(1))){
|
||||
scrollBy(0, dy);
|
||||
consumed[1]=dy;
|
||||
return;
|
||||
}
|
||||
}else if((dy<0 && target instanceof RecyclerView rv && isScrolledToTop(rv)) || (dy>0 && !isScrolledToBottom())){
|
||||
scrollBy(0, dy);
|
||||
consumed[1] = dy;
|
||||
consumed[1]=dy;
|
||||
return;
|
||||
}
|
||||
super.onNestedPreScroll(target, dx, dy, consumed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNestedPreFling(View target, float velX, float velY) {
|
||||
if (target instanceof RecyclerView rv && ((velY < 0 && isScrolledToTop(rv)) || (velY > 0 && !isScrolledToBottom()))){
|
||||
public boolean onNestedPreFling(View target, float velX, float velY){
|
||||
if(takePriorityOverChildViews){
|
||||
if((velY<0 && getScrollY()>0) || (velY>0 && canScrollVertically(1))){
|
||||
fling((int)velY);
|
||||
return true;
|
||||
}
|
||||
}else if((velY<0 && target instanceof RecyclerView rv && isScrolledToTop(rv)) || (velY>0 && !isScrolledToBottom())){
|
||||
fling((int) velY);
|
||||
return true;
|
||||
}
|
||||
return super.onNestedPreFling(target, velX, velY);
|
||||
}
|
||||
|
||||
private boolean isScrolledToBottom() {
|
||||
private boolean isScrolledToBottom(){
|
||||
return !canScrollVertically(1);
|
||||
}
|
||||
|
||||
private boolean isScrolledToTop(RecyclerView rv) {
|
||||
final LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager();
|
||||
return lm.findFirstVisibleItemPosition() == 0
|
||||
&& lm.findViewByPosition(0).getTop() == rv.getPaddingTop();
|
||||
private boolean isScrolledToTop(RecyclerView rv){
|
||||
final LinearLayoutManager lm=(LinearLayoutManager) rv.getLayoutManager();
|
||||
return lm.findFirstVisibleItemPosition()==0
|
||||
&& lm.findViewByPosition(0).getTop()==rv.getPaddingTop();
|
||||
}
|
||||
|
||||
public void setScrollableChildSupplier(Supplier<RecyclerView> scrollableChildSupplier){
|
||||
@@ -59,12 +71,20 @@ public class NestedRecyclerScrollView extends CustomScrollView{
|
||||
|
||||
@Override
|
||||
protected boolean onScrollingHitEdge(float velocity){
|
||||
if(velocity>0){
|
||||
if(velocity>0 || takePriorityOverChildViews){
|
||||
RecyclerView view=scrollableChildSupplier.get();
|
||||
if(view!=null){
|
||||
return view.fling(0, (int)velocity);
|
||||
return view.fling(0, (int) velocity);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isTakePriorityOverChildViews(){
|
||||
return takePriorityOverChildViews;
|
||||
}
|
||||
|
||||
public void setTakePriorityOverChildViews(boolean takePriorityOverChildViews){
|
||||
this.takePriorityOverChildViews=takePriorityOverChildViews;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
public class TopBarsScrollAwayLinearLayout extends LinearLayout{
|
||||
public TopBarsScrollAwayLinearLayout(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TopBarsScrollAwayLinearLayout(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TopBarsScrollAwayLinearLayout(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int topBarsHeight=0;
|
||||
for(int i=0;i<getChildCount()-1;i++){
|
||||
topBarsHeight+=getChildAt(i).getMeasuredHeight();
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, (MeasureSpec.getSize(heightMeasureSpec)+topBarsHeight) | MeasureSpec.EXACTLY);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user