Merge branch 'master' into donations
This commit is contained in:
@@ -13,7 +13,7 @@ android {
|
|||||||
applicationId "org.joinmastodon.android"
|
applicationId "org.joinmastodon.android"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 100
|
versionCode 101
|
||||||
versionName "2.5.0"
|
versionName "2.5.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,8 +287,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
outRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
outRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
||||||
}
|
}
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder<?> sih){
|
||||||
if(((StatusDisplayItem.Holder<?>) holder).getItem().getType()==StatusDisplayItem.Type.GAP){
|
if(sih.getItem() instanceof StatusDisplayItem sdi && sdi.getType()==StatusDisplayItem.Type.GAP){
|
||||||
outRect.setEmpty();
|
outRect.setEmpty();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -296,8 +296,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
View child=list.getChildAt(i);
|
View child=list.getChildAt(i);
|
||||||
holder=list.getChildViewHolder(child);
|
holder=list.getChildViewHolder(child);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder<?> sih2){
|
||||||
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
String otherID=sih2.getItemID();
|
||||||
if(otherID.equals(id)){
|
if(otherID.equals(id)){
|
||||||
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||||
@@ -760,7 +760,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
// Do not draw dividers between hashtag and/or account rows
|
// Do not draw dividers between hashtag and/or account rows
|
||||||
if((ih instanceof HashtagStatusDisplayItem.Holder || ih instanceof AccountStatusDisplayItem.Holder) && (sh instanceof HashtagStatusDisplayItem.Holder || sh instanceof AccountStatusDisplayItem.Holder))
|
if((ih instanceof HashtagStatusDisplayItem.Holder || ih instanceof AccountStatusDisplayItem.Holder) && (sh instanceof HashtagStatusDisplayItem.Holder || sh instanceof AccountStatusDisplayItem.Holder))
|
||||||
return false;
|
return false;
|
||||||
return !ih.getItemID().equals(sh.getItemID()) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP;
|
return !ih.getItemID().equals(sh.getItemID()) && ih.getItem() instanceof StatusDisplayItem sdi && sdi.getType()!=StatusDisplayItem.Type.GAP;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,56 +2,35 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
|
||||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.HorizontalScrollingTouchListener;
|
import org.joinmastodon.android.ui.viewholders.LinkCardHolder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderAdapter;
|
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> implements ScrollableToTop{
|
public class DiscoverNewsFragment extends BaseRecyclerFragment<DiscoverNewsFragment.CardItem> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private DiscoverInfoBannerHelper bannerHelper;
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
private MergeRecyclerAdapter mergeAdapter;
|
private MergeRecyclerAdapter mergeAdapter;
|
||||||
private UsableRecyclerView cardsList;
|
|
||||||
private ArrayList<CardViewModel> top3=new ArrayList<>();
|
|
||||||
private CardLinksAdapter cardsAdapter;
|
|
||||||
|
|
||||||
public DiscoverNewsFragment(){
|
public DiscoverNewsFragment(){
|
||||||
super(10);
|
super(10);
|
||||||
@@ -70,12 +49,14 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Card> result){
|
public void onSuccess(List<Card> result){
|
||||||
top3.clear();
|
int[] index={0};
|
||||||
top3.addAll(result.subList(0, Math.min(3, result.size())).stream().map(card->new CardViewModel(card, 280, 140)).collect(Collectors.toList()));
|
onDataLoaded(result.stream()
|
||||||
cardsAdapter.notifyDataSetChanged();
|
.map(card->{
|
||||||
|
int actualIndex=index[0]+(refreshing ? 0 : (data.size()+preloadedData.size()));
|
||||||
onDataLoaded(result.subList(top3.size(), result.size()).stream()
|
index[0]++;
|
||||||
.map(card->new CardViewModel(card, 56, 56))
|
int size=actualIndex==0 ? 1000 : 192;
|
||||||
|
return new CardItem(new CardViewModel(card, size, size, card, accountID));
|
||||||
|
})
|
||||||
.collect(Collectors.toList()), false);
|
.collect(Collectors.toList()), false);
|
||||||
bannerHelper.onBannerBecameVisible();
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
@@ -86,27 +67,9 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
|||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
cardsList=new UsableRecyclerView(getActivity());
|
|
||||||
cardsList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
|
|
||||||
ListImageLoaderWrapper cardsImageLoader=new ListImageLoaderWrapper(getActivity(), cardsList, new RecyclerViewDelegate(cardsList), this);
|
|
||||||
cardsList.setAdapter(cardsAdapter=new CardLinksAdapter(cardsImageLoader, top3));
|
|
||||||
cardsList.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(256)));
|
|
||||||
cardsList.setPadding(V.dp(16), V.dp(8), 0, 0);
|
|
||||||
cardsList.setClipToPadding(false);
|
|
||||||
cardsList.addItemDecoration(new RecyclerView.ItemDecoration(){
|
|
||||||
@Override
|
|
||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
outRect.right=V.dp(16);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cardsList.setSelector(R.drawable.bg_rect_12dp_ripple);
|
|
||||||
cardsList.setDrawSelectorOnTop(true);
|
|
||||||
cardsList.setOnTouchListener(new HorizontalScrollingTouchListener(getActivity()));
|
|
||||||
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
mergeAdapter=new MergeRecyclerAdapter();
|
||||||
bannerHelper.maybeAddBanner(list, mergeAdapter);
|
bannerHelper.maybeAddBanner(list, mergeAdapter);
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(cardsList));
|
mergeAdapter.addAdapter(new LinksAdapter(imgLoader));
|
||||||
mergeAdapter.addAdapter(new LinksAdapter(imgLoader, data));
|
|
||||||
return mergeAdapter;
|
return mergeAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,18 +78,46 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LinksAdapter extends UsableRecyclerView.Adapter<BaseLinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
@Override
|
||||||
private final List<CardViewModel> data;
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
|
if(parent.getChildAdapterPosition(view)==0 && !bannerHelper.isBannerShown()){
|
||||||
|
outRect.top=V.dp(16);
|
||||||
|
}
|
||||||
|
if(parent.getChildViewHolder(view) instanceof LinkCardHolder<?>){
|
||||||
|
outRect.bottom=V.dp(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public LinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
public static class CardItem implements LinkCardHolder.LinkCardProvider{
|
||||||
|
public final CardViewModel card;
|
||||||
|
|
||||||
|
private CardItem(CardViewModel card){
|
||||||
|
this.card=card;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CardViewModel getCard(){
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkCardHolder<CardItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
public LinksAdapter(ListImageLoaderWrapper imgLoader){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
this.data=data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public LinkCardHolder<CardItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
return new LinkViewHolder();
|
LinkCardHolder<CardItem> vh=new LinkCardHolder<>(getActivity(), list, viewType==1, accountID);
|
||||||
|
vh.setTryResolving(false);
|
||||||
|
return vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,91 +126,24 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(BaseLinkViewHolder holder, int position){
|
public void onBindViewHolder(LinkCardHolder<CardItem> holder, int position){
|
||||||
holder.bind(data.get(position).card);
|
holder.bind(data.get(position));
|
||||||
super.onBindViewHolder(holder, position);
|
super.onBindViewHolder(holder, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageCountForItem(int position){
|
public int getImageCountForItem(int position){
|
||||||
return data.get(position).imageRequest==null ? 0 : 1;
|
return data.get(position).card.getImageCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
return data.get(position).imageRequest;
|
return data.get(position).card.getImageRequest(image);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CardLinksAdapter extends LinksAdapter{
|
|
||||||
public CardLinksAdapter(ListImageLoaderWrapper imgLoader, List<CardViewModel> data){
|
|
||||||
super(imgLoader, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public BaseLinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new LinkCardViewHolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BaseLinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
|
||||||
protected final TextView name, title;
|
|
||||||
protected final ImageView photo;
|
|
||||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
|
||||||
private boolean didClear;
|
|
||||||
|
|
||||||
public BaseLinkViewHolder(int layout){
|
|
||||||
super(getActivity(), layout, list);
|
|
||||||
name=findViewById(R.id.name);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
photo=findViewById(R.id.photo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Card item){
|
public int getItemViewType(int position){
|
||||||
name.setText(item.providerName);
|
return position==0 ? 1 : 2;
|
||||||
title.setText(item.title);
|
|
||||||
crossfadeDrawable.setSize(item.width, item.height);
|
|
||||||
crossfadeDrawable.setBlurhashDrawable(item.blurhashPlaceholder);
|
|
||||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
|
||||||
didClear=false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setImage(int index, Drawable drawable){
|
|
||||||
crossfadeDrawable.setImageDrawable(drawable);
|
|
||||||
if(didClear)
|
|
||||||
crossfadeDrawable.animateAlpha(0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearImage(int index){
|
|
||||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
|
||||||
didClear=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
UiUtils.launchWebBrowser(getActivity(), item.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LinkViewHolder extends BaseLinkViewHolder{
|
|
||||||
public LinkViewHolder(){
|
|
||||||
super(R.layout.item_trending_link);
|
|
||||||
photo.setOutlineProvider(OutlineProviders.roundedRect(12));
|
|
||||||
photo.setClipToOutline(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LinkCardViewHolder extends BaseLinkViewHolder{
|
|
||||||
public LinkCardViewHolder(){
|
|
||||||
super(R.layout.item_trending_link_card);
|
|
||||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
|
||||||
itemView.setClipToOutline(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,52 @@
|
|||||||
package org.joinmastodon.android.model.viewmodel;
|
package org.joinmastodon.android.model.viewmodel;
|
||||||
|
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class CardViewModel{
|
public class CardViewModel{
|
||||||
|
public final Object parentObject;
|
||||||
public final Card card;
|
public final Card card;
|
||||||
public final ImageLoaderRequest imageRequest;
|
public final ImageLoaderRequest imageRequest;
|
||||||
|
public final UrlImageLoaderRequest authorAvaRequest;
|
||||||
|
public final SpannableStringBuilder parsedAuthorName;
|
||||||
|
public final CustomEmojiHelper authorNameEmojiHelper=new CustomEmojiHelper();
|
||||||
|
|
||||||
public CardViewModel(Card card, int width, int height){
|
public CardViewModel(Card card, int width, int height, Object parentObject, String accountID){
|
||||||
this.card=card;
|
this.card=card;
|
||||||
|
this.parentObject=parentObject;
|
||||||
this.imageRequest=TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(width), V.dp(height));
|
this.imageRequest=TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(width), V.dp(height));
|
||||||
|
|
||||||
|
if(card.authorAccount!=null){
|
||||||
|
parsedAuthorName=new SpannableStringBuilder(card.authorAccount.displayName);
|
||||||
|
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||||
|
HtmlParser.parseCustomEmoji(parsedAuthorName, card.authorAccount.emojis);
|
||||||
|
authorNameEmojiHelper.setText(parsedAuthorName);
|
||||||
|
authorAvaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? card.authorAccount.avatar : card.authorAccount.avatarStatic, V.dp(50), V.dp(50));
|
||||||
|
}else{
|
||||||
|
parsedAuthorName=null;
|
||||||
|
authorAvaRequest=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getImageCount(){
|
||||||
|
return 1+(card.authorAccount!=null ? (1+authorNameEmojiHelper.getImageCount()) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageLoaderRequest getImageRequest(int index){
|
||||||
|
return switch(index){
|
||||||
|
case 0 -> imageRequest;
|
||||||
|
case 1 -> authorAvaRequest;
|
||||||
|
default -> authorNameEmojiHelper.getImageRequest(index-2);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,230 +1,55 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
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.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
import org.joinmastodon.android.ui.viewholders.LinkCardHolder;
|
||||||
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 java.util.Objects;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
public class LinkCardStatusDisplayItem extends StatusDisplayItem implements LinkCardHolder.LinkCardProvider{
|
||||||
private final Status status;
|
private final Status status;
|
||||||
private final UrlImageLoaderRequest imgRequest, authorAvaRequest;
|
private final CardViewModel cardViewModel;
|
||||||
private final SpannableStringBuilder parsedAuthorName;
|
|
||||||
private final CustomEmojiHelper authorNameEmojiHelper=new CustomEmojiHelper();
|
|
||||||
|
|
||||||
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
|
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.status=status;
|
this.status=status;
|
||||||
if(status.card.image!=null)
|
int size=shouldUseLargeCard() ? 1000 : 192;
|
||||||
imgRequest=new UrlImageLoaderRequest(status.card.image, 1000, 1000);
|
cardViewModel=new CardViewModel(status.card, size, size, status, parentFragment.getAccountID());
|
||||||
else
|
|
||||||
imgRequest=null;
|
|
||||||
|
|
||||||
if(status.card.authorAccount!=null){
|
|
||||||
parsedAuthorName=new SpannableStringBuilder(status.card.authorAccount.displayName);
|
|
||||||
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
|
||||||
HtmlParser.parseCustomEmoji(parsedAuthorName, status.card.authorAccount.emojis);
|
|
||||||
authorNameEmojiHelper.setText(parsedAuthorName);
|
|
||||||
authorAvaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? status.card.authorAccount.avatar : status.card.authorAccount.avatarStatic, V.dp(50), V.dp(50));
|
|
||||||
}else{
|
|
||||||
parsedAuthorName=null;
|
|
||||||
authorAvaRequest=null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldUseLargeCard(){
|
||||||
|
return status.card.type==Card.Type.VIDEO || (status.card.image!=null && status.card.width>status.card.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type getType(){
|
public Type getType(){
|
||||||
return status.card.type==Card.Type.VIDEO || (status.card.image!=null && status.card.width>status.card.height) ? Type.CARD_LARGE : Type.CARD_COMPACT;
|
return shouldUseLargeCard() ? Type.CARD_LARGE : Type.CARD_COMPACT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getImageCount(){
|
public int getImageCount(){
|
||||||
return 1+(status.card.authorAccount!=null ? (1+authorNameEmojiHelper.getImageCount()) : 0);
|
return cardViewModel.getImageCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageLoaderRequest getImageRequest(int index){
|
public ImageLoaderRequest getImageRequest(int index){
|
||||||
return switch(index){
|
return cardViewModel.getImageRequest(index);
|
||||||
case 0 -> imgRequest;
|
|
||||||
case 1 -> authorAvaRequest;
|
|
||||||
default -> authorNameEmojiHelper.getImageRequest(index-2);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Holder extends StatusDisplayItem.Holder<LinkCardStatusDisplayItem> implements ImageLoaderViewHolder{
|
|
||||||
private final TextView title, description, domain, timestamp, authorBefore, authorAfter, authorName;
|
|
||||||
private final ImageView photo, authorAva;
|
|
||||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
|
||||||
private boolean didClear;
|
|
||||||
private final View inner, authorFooter, authorChip;
|
|
||||||
private final boolean isLarge;
|
|
||||||
private final Drawable logoIcon;
|
|
||||||
|
|
||||||
public Holder(Context context, ViewGroup parent, boolean isLarge){
|
|
||||||
super(context, isLarge ? R.layout.display_item_link_card : R.layout.display_item_link_card_compact, parent);
|
|
||||||
this.isLarge=isLarge;
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
description=findViewById(R.id.description);
|
|
||||||
domain=findViewById(R.id.domain);
|
|
||||||
timestamp=findViewById(R.id.timestamp);
|
|
||||||
photo=findViewById(R.id.photo);
|
|
||||||
inner=findViewById(R.id.inner);
|
|
||||||
authorBefore=findViewById(R.id.author_before);
|
|
||||||
authorAfter=findViewById(R.id.author_after);
|
|
||||||
authorName=findViewById(R.id.author_name);
|
|
||||||
authorAva=findViewById(R.id.author_ava);
|
|
||||||
authorChip=findViewById(R.id.author_chip);
|
|
||||||
authorFooter=findViewById(R.id.author_footer);
|
|
||||||
|
|
||||||
inner.setOnClickListener(this::onClick);
|
|
||||||
inner.setOutlineProvider(OutlineProviders.roundedRect(8));
|
|
||||||
inner.setClipToOutline(true);
|
|
||||||
if(!isLarge){
|
|
||||||
photo.setOutlineProvider(OutlineProviders.roundedRect(4));
|
|
||||||
photo.setClipToOutline(true);
|
|
||||||
}
|
|
||||||
authorAva.setOutlineProvider(OutlineProviders.roundedRect(3));
|
|
||||||
authorAva.setClipToOutline(true);
|
|
||||||
authorChip.setOnClickListener(this::onAuthorChipClick);
|
|
||||||
|
|
||||||
logoIcon=context.getResources().getDrawable(R.drawable.ic_ntf_logo, context.getTheme()).mutate();
|
|
||||||
logoIcon.setBounds(0, 0, V.dp(17), V.dp(17));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
@Override
|
|
||||||
public void onBind(LinkCardStatusDisplayItem item){
|
|
||||||
Card card=item.status.card;
|
|
||||||
title.setText(card.title);
|
|
||||||
if(description!=null){
|
|
||||||
description.setText(card.description);
|
|
||||||
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
|
||||||
}
|
|
||||||
String cardDomain=HtmlParser.normalizeDomain(Objects.requireNonNull(Uri.parse(card.url).getHost()));
|
|
||||||
domain.setText(TextUtils.isEmpty(card.providerName) ? cardDomain : card.providerName);
|
|
||||||
if(card.authorAccount!=null){
|
|
||||||
authorFooter.setVisibility(View.VISIBLE);
|
|
||||||
authorChip.setVisibility(View.VISIBLE);
|
|
||||||
authorBefore.setVisibility(View.VISIBLE);
|
|
||||||
String[] authorParts=itemView.getContext().getString(R.string.article_by_author, "{author}").split("\\{author\\}");
|
|
||||||
String before=authorParts[0].trim();
|
|
||||||
String after=authorParts.length>1 ? authorParts[1].trim() : "";
|
|
||||||
if(!TextUtils.isEmpty(before)){
|
|
||||||
authorBefore.setText(before);
|
|
||||||
}
|
|
||||||
if(TextUtils.isEmpty(after)){
|
|
||||||
authorAfter.setVisibility(View.GONE);
|
|
||||||
}else{
|
|
||||||
authorAfter.setVisibility(View.VISIBLE);
|
|
||||||
authorAfter.setText(after);
|
|
||||||
}
|
|
||||||
authorName.setText(item.parsedAuthorName);
|
|
||||||
authorBefore.setCompoundDrawablesRelative(logoIcon, null, null, null);
|
|
||||||
}else if(!TextUtils.isEmpty(card.authorName)){
|
|
||||||
authorFooter.setVisibility(View.VISIBLE);
|
|
||||||
authorBefore.setVisibility(View.VISIBLE);
|
|
||||||
authorBefore.setCompoundDrawables(null, null, null, null);
|
|
||||||
authorChip.setVisibility(View.GONE);
|
|
||||||
authorAfter.setVisibility(View.GONE);
|
|
||||||
authorBefore.setText(itemView.getContext().getString(R.string.article_by_author, card.authorName));
|
|
||||||
}else{
|
|
||||||
authorFooter.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(card.publishedAt!=null){
|
|
||||||
timestamp.setVisibility(View.VISIBLE);
|
|
||||||
timestamp.setText(" · "+UiUtils.formatRelativeTimestamp(itemView.getContext(), card.publishedAt));
|
|
||||||
}else{
|
|
||||||
timestamp.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
if(item.imgRequest!=null){
|
|
||||||
photo.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
|
||||||
photo.setBackground(null);
|
|
||||||
photo.setImageTintList(null);
|
|
||||||
crossfadeDrawable.setSize(card.width, card.height);
|
|
||||||
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
|
|
||||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
|
||||||
didClear=false;
|
|
||||||
}else{
|
|
||||||
photo.setBackgroundColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3SurfaceVariant));
|
|
||||||
photo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline)));
|
|
||||||
photo.setScaleType(ImageView.ScaleType.CENTER);
|
|
||||||
photo.setImageResource(R.drawable.ic_feed_48px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setImage(int index, Drawable drawable){
|
public CardViewModel getCard(){
|
||||||
if(index==0){
|
return cardViewModel;
|
||||||
crossfadeDrawable.setImageDrawable(drawable);
|
|
||||||
if(didClear)
|
|
||||||
crossfadeDrawable.animateAlpha(0f);
|
|
||||||
Card card=item.status.card;
|
|
||||||
// Make sure the image is not stretched if the server returned wrong dimensions
|
|
||||||
if(drawable!=null && (drawable.getIntrinsicWidth()!=card.width || drawable.getIntrinsicHeight()!=card.height)){
|
|
||||||
photo.setImageDrawable(null);
|
|
||||||
photo.setImageDrawable(crossfadeDrawable);
|
|
||||||
}
|
|
||||||
}else if(index==1){
|
|
||||||
authorAva.setImageDrawable(drawable);
|
|
||||||
}else{
|
|
||||||
item.authorNameEmojiHelper.setImageDrawable(index-2, drawable);
|
|
||||||
authorName.invalidate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class Holder extends LinkCardHolder<LinkCardStatusDisplayItem>{
|
||||||
public void clearImage(int index){
|
|
||||||
if(index==0){
|
|
||||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
|
||||||
didClear=true;
|
|
||||||
}else{
|
|
||||||
setImage(index, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onClick(View v){
|
public Holder(Activity context, ViewGroup parent, boolean isLarge, String accountID){
|
||||||
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), item.status.card.url, item.status);
|
super(context, parent, isLarge, accountID);
|
||||||
}
|
|
||||||
|
|
||||||
private void onAuthorChipClick(View v){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", item.parentFragment.getAccountID());
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(item.status.card.authorAccount));
|
|
||||||
Nav.go(item.parentFragment.getActivity(), ProfileFragment.class, args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
@@ -68,8 +69,8 @@ public abstract class StatusDisplayItem{
|
|||||||
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
|
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
|
||||||
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
|
||||||
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
|
||||||
case CARD_LARGE -> new LinkCardStatusDisplayItem.Holder(activity, parent, true);
|
case CARD_LARGE -> new LinkCardStatusDisplayItem.Holder(activity, parent, true, ((StatusListFragment)parentFragment).getAccountID());
|
||||||
case CARD_COMPACT -> new LinkCardStatusDisplayItem.Holder(activity, parent, false);
|
case CARD_COMPACT -> new LinkCardStatusDisplayItem.Holder(activity, parent, false, ((StatusListFragment)parentFragment).getAccountID());
|
||||||
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
|
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
|
||||||
case ACCOUNT -> new AccountStatusDisplayItem.Holder(new AccountViewHolder(parentFragment, parent, null));
|
case ACCOUNT -> new AccountStatusDisplayItem.Holder(new AccountViewHolder(parentFragment, parent, null));
|
||||||
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
|
||||||
@@ -226,7 +227,7 @@ public abstract class StatusDisplayItem{
|
|||||||
FILTER_SPOILER
|
FILTER_SPOILER
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
public static abstract class Holder<T> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||||
public Holder(View itemView){
|
public Holder(View itemView){
|
||||||
super(itemView);
|
super(itemView);
|
||||||
}
|
}
|
||||||
@@ -236,17 +237,18 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getItemID(){
|
public String getItemID(){
|
||||||
return item.parentID;
|
return item instanceof StatusDisplayItem sdi ? sdi.parentID : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
item.parentFragment.onItemClick(item.parentID);
|
if(item instanceof StatusDisplayItem sdi)
|
||||||
|
sdi.parentFragment.onItemClick(sdi.parentID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled(){
|
public boolean isEnabled(){
|
||||||
return item.parentFragment.isItemEnabled(item.parentID);
|
return item instanceof StatusDisplayItem sdi && sdi.parentFragment.isItemEnabled(sdi.parentID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ public class DiscoverInfoBannerHelper{
|
|||||||
bannerTypesToShow=EnumSet.allOf(BannerType.class);
|
bannerTypesToShow=EnumSet.allOf(BannerType.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBannerShown(){
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
public enum BannerType{
|
public enum BannerType{
|
||||||
TRENDING_POSTS,
|
TRENDING_POSTS,
|
||||||
TRENDING_LINKS,
|
TRENDING_LINKS,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
|||||||
View child=parent.getChildAt(i);
|
View child=parent.getChildAt(i);
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||||
pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
||||||
boolean inset=(holder instanceof StatusDisplayItem.Holder<?> sdi) && sdi.getItem().inset;
|
boolean inset=holder instanceof StatusDisplayItem.Holder<?> sdi && sdi.getItem() instanceof StatusDisplayItem item && item.inset;
|
||||||
if(inset){
|
if(inset){
|
||||||
if(rect.isEmpty()){
|
if(rect.isEmpty()){
|
||||||
float childY=child.getY();
|
float childY=child.getY();
|
||||||
@@ -80,13 +80,13 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
|||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
if(holder instanceof StatusDisplayItem.Holder<?> sdi && sdi.getItem() instanceof StatusDisplayItem item){
|
||||||
boolean inset=sdi.getItem().inset;
|
boolean inset=item.inset;
|
||||||
int pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
int pos=holder.getAbsoluteAdapterPosition()-listFragment.getMainAdapterOffset();
|
||||||
if(inset){
|
if(inset){
|
||||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||||
StatusDisplayItem.Type type=sdi.getItem().getType();
|
StatusDisplayItem.Type type=item.getType();
|
||||||
if(type==StatusDisplayItem.Type.CARD_LARGE || type==StatusDisplayItem.Type.MEDIA_GRID)
|
if(type==StatusDisplayItem.Type.CARD_LARGE || type==StatusDisplayItem.Type.MEDIA_GRID)
|
||||||
outRect.left=outRect.right=V.dp(16);
|
outRect.left=outRect.right=V.dp(16);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package org.joinmastodon.android.ui.viewholders;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.model.Card;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class LinkCardHolder<T extends LinkCardHolder.LinkCardProvider> extends StatusDisplayItem.Holder<T> implements ImageLoaderViewHolder{
|
||||||
|
private final TextView title, description, domain, timestamp, authorBefore, authorAfter, authorName;
|
||||||
|
private final ImageView photo, authorAva;
|
||||||
|
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||||
|
private boolean didClear;
|
||||||
|
private final View inner, authorFooter, authorChip;
|
||||||
|
private final boolean isLarge;
|
||||||
|
private final Drawable logoIcon;
|
||||||
|
private final String accountID;
|
||||||
|
private Activity activity;
|
||||||
|
private boolean tryResolving=true;
|
||||||
|
|
||||||
|
public LinkCardHolder(Activity context, ViewGroup parent, boolean isLarge, String accountID){
|
||||||
|
super(context, isLarge ? R.layout.display_item_link_card : R.layout.display_item_link_card_compact, parent);
|
||||||
|
this.isLarge=isLarge;
|
||||||
|
this.accountID=accountID;
|
||||||
|
activity=context;
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
description=findViewById(R.id.description);
|
||||||
|
domain=findViewById(R.id.domain);
|
||||||
|
timestamp=findViewById(R.id.timestamp);
|
||||||
|
photo=findViewById(R.id.photo);
|
||||||
|
inner=findViewById(R.id.inner);
|
||||||
|
authorBefore=findViewById(R.id.author_before);
|
||||||
|
authorAfter=findViewById(R.id.author_after);
|
||||||
|
authorName=findViewById(R.id.author_name);
|
||||||
|
authorAva=findViewById(R.id.author_ava);
|
||||||
|
authorChip=findViewById(R.id.author_chip);
|
||||||
|
authorFooter=findViewById(R.id.author_footer);
|
||||||
|
|
||||||
|
inner.setOnClickListener(this::onClick);
|
||||||
|
inner.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||||
|
inner.setClipToOutline(true);
|
||||||
|
if(!isLarge){
|
||||||
|
photo.setOutlineProvider(OutlineProviders.roundedRect(4));
|
||||||
|
photo.setClipToOutline(true);
|
||||||
|
}
|
||||||
|
authorAva.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||||
|
authorAva.setClipToOutline(true);
|
||||||
|
authorChip.setOnClickListener(this::onAuthorChipClick);
|
||||||
|
|
||||||
|
logoIcon=context.getResources().getDrawable(R.drawable.ic_ntf_logo, context.getTheme()).mutate();
|
||||||
|
logoIcon.setBounds(0, 0, V.dp(17), V.dp(17));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
@Override
|
||||||
|
public void onBind(T item){
|
||||||
|
CardViewModel cardVM=item.getCard();
|
||||||
|
Card card=cardVM.card;
|
||||||
|
title.setText(card.title);
|
||||||
|
if(description!=null){
|
||||||
|
description.setText(card.description);
|
||||||
|
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
String cardDomain=HtmlParser.normalizeDomain(Objects.requireNonNull(Uri.parse(card.url).getHost()));
|
||||||
|
domain.setText(TextUtils.isEmpty(card.providerName) ? cardDomain : card.providerName);
|
||||||
|
if(card.authorAccount!=null){
|
||||||
|
authorFooter.setVisibility(View.VISIBLE);
|
||||||
|
authorChip.setVisibility(View.VISIBLE);
|
||||||
|
authorBefore.setVisibility(View.VISIBLE);
|
||||||
|
String[] authorParts=itemView.getContext().getString(R.string.article_by_author, "{author}").split("\\{author\\}");
|
||||||
|
String before=authorParts[0].trim();
|
||||||
|
String after=authorParts.length>1 ? authorParts[1].trim() : "";
|
||||||
|
if(!TextUtils.isEmpty(before)){
|
||||||
|
authorBefore.setText(before);
|
||||||
|
}
|
||||||
|
if(TextUtils.isEmpty(after)){
|
||||||
|
authorAfter.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
authorAfter.setVisibility(View.VISIBLE);
|
||||||
|
authorAfter.setText(after);
|
||||||
|
}
|
||||||
|
authorName.setText(cardVM.parsedAuthorName);
|
||||||
|
authorBefore.setCompoundDrawablesRelative(logoIcon, null, null, null);
|
||||||
|
}else if(!TextUtils.isEmpty(card.authorName)){
|
||||||
|
authorFooter.setVisibility(View.VISIBLE);
|
||||||
|
authorBefore.setVisibility(View.VISIBLE);
|
||||||
|
authorBefore.setCompoundDrawables(null, null, null, null);
|
||||||
|
authorChip.setVisibility(View.GONE);
|
||||||
|
authorAfter.setVisibility(View.GONE);
|
||||||
|
authorBefore.setText(itemView.getContext().getString(R.string.article_by_author, card.authorName));
|
||||||
|
}else{
|
||||||
|
authorFooter.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(card.publishedAt!=null){
|
||||||
|
timestamp.setVisibility(View.VISIBLE);
|
||||||
|
timestamp.setText(" · "+UiUtils.formatRelativeTimestamp(itemView.getContext(), card.publishedAt));
|
||||||
|
}else{
|
||||||
|
timestamp.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
photo.setImageDrawable(null);
|
||||||
|
if(cardVM.imageRequest!=null){
|
||||||
|
photo.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||||
|
photo.setBackground(null);
|
||||||
|
photo.setImageTintList(null);
|
||||||
|
crossfadeDrawable.setSize(card.width, card.height);
|
||||||
|
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
|
||||||
|
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||||
|
photo.setImageDrawable(null);
|
||||||
|
photo.setImageDrawable(crossfadeDrawable);
|
||||||
|
didClear=false;
|
||||||
|
}else{
|
||||||
|
photo.setBackgroundColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3SurfaceVariant));
|
||||||
|
photo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline)));
|
||||||
|
photo.setScaleType(ImageView.ScaleType.CENTER);
|
||||||
|
photo.setImageResource(R.drawable.ic_feed_48px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImage(int index, Drawable drawable){
|
||||||
|
if(index==0){
|
||||||
|
crossfadeDrawable.setImageDrawable(drawable);
|
||||||
|
if(didClear)
|
||||||
|
crossfadeDrawable.animateAlpha(0f);
|
||||||
|
CardViewModel card=item.getCard();
|
||||||
|
// Make sure the image is not stretched if the server returned wrong dimensions
|
||||||
|
if(drawable!=null && (drawable.getIntrinsicWidth()!=card.card.width || drawable.getIntrinsicHeight()!=card.card.height)){
|
||||||
|
photo.setImageDrawable(null);
|
||||||
|
photo.setImageDrawable(crossfadeDrawable);
|
||||||
|
}
|
||||||
|
}else if(index==1){
|
||||||
|
authorAva.setImageDrawable(drawable);
|
||||||
|
}else{
|
||||||
|
item.getCard().authorNameEmojiHelper.setImageDrawable(index-2, drawable);
|
||||||
|
authorName.invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearImage(int index){
|
||||||
|
if(index==0){
|
||||||
|
crossfadeDrawable.setCrossfadeAlpha(1f);
|
||||||
|
didClear=true;
|
||||||
|
}else{
|
||||||
|
setImage(index, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTryResolving(boolean tryResolving){
|
||||||
|
this.tryResolving=tryResolving;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClick(View v){
|
||||||
|
CardViewModel card=item.getCard();
|
||||||
|
if(tryResolving)
|
||||||
|
UiUtils.openURL(activity, accountID, card.card.url, card.parentObject);
|
||||||
|
else
|
||||||
|
UiUtils.launchWebBrowser(activity, card.card.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAuthorChipClick(View v){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(item.getCard().card.authorAccount));
|
||||||
|
Nav.go(activity, ProfileFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LinkCardProvider{
|
||||||
|
CardViewModel getCard();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
<solid android:color="#000"/>
|
<solid android:color="#000"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item android:left="-1dp" android:top="-1dp" android:right="-1dp" android:bottom="-1dp">
|
||||||
<shape>
|
<shape>
|
||||||
<corners android:radius="7dp"/>
|
<corners android:radius="8dp"/>
|
||||||
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
|
<stroke android:color="?colorM3OutlineVariant" android:width="2dp"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</ripple>
|
</ripple>
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingVertical="12dp"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="24dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/photo"
|
|
||||||
android:layout_width="56dp"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
tools:src="#0f0"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:layout_toEndOf="@id/photo"
|
|
||||||
android:textAppearance="@style/m3_label_medium"
|
|
||||||
android:textColor="?colorM3OnSurfaceVariant"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
tools:text="Site Name"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/name"
|
|
||||||
android:layout_toEndOf="@id/photo"
|
|
||||||
android:textAppearance="@style/m3_body_large"
|
|
||||||
android:textColor="?colorM3OnSurface"
|
|
||||||
android:paddingVertical="2.5dp"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:ellipsize="end"
|
|
||||||
tools:text="Title title title"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="280dp"
|
|
||||||
android:layout_height="240dp"
|
|
||||||
android:foreground="@drawable/bg_settings_banner">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/photo"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="140dp"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
tools:src="#0f0"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/photo"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:textAppearance="@style/m3_body_large"
|
|
||||||
android:paddingVertical="2.5dp"
|
|
||||||
android:textColor="?colorM3OnSurface"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:ellipsize="end"
|
|
||||||
tools:text="Title title title"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:layout_below="@id/title"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:textAppearance="@style/m3_body_medium"
|
|
||||||
android:textColor="?colorM3OnSurfaceVariant"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
tools:text="Site Name"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
Reference in New Issue
Block a user