Notifications
This commit is contained in:
parent
b437f6f3a3
commit
37bef85f6a
|
@ -10,7 +10,7 @@ android {
|
|||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 31
|
||||
versionCode 5
|
||||
versionCode 6
|
||||
versionName "0.1"
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import java.util.HashSet;
|
|||
import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AudioPlayerService extends Service{
|
||||
private static final int NOTIFICATION_SERVICE=1;
|
||||
|
@ -153,10 +154,10 @@ public class AudioPlayerService extends Service{
|
|||
}
|
||||
});
|
||||
|
||||
Drawable d=ImageCache.getInstance(this).getFromTop(new UrlImageLoaderRequest(status.account.avatar));
|
||||
Drawable d=ImageCache.getInstance(this).getFromTop(new UrlImageLoaderRequest(status.account.avatar, V.dp(50), V.dp(50)));
|
||||
if(d instanceof BitmapDrawable){
|
||||
statusAvatar=((BitmapDrawable) d).getBitmap();
|
||||
}else{
|
||||
}else if(d!=null){
|
||||
statusAvatar=Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||
d.draw(new Canvas(statusAvatar));
|
||||
|
|
|
@ -5,12 +5,13 @@ import com.google.gson.reflect.TypeToken;
|
|||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class GetAccountRelationships extends MastodonAPIRequest<List<Relationship>>{
|
||||
public GetAccountRelationships(@NonNull List<String> ids){
|
||||
public GetAccountRelationships(@NonNull Collection<String> ids){
|
||||
super(HttpMethod.GET, "/accounts/relationships", new TypeToken<>(){});
|
||||
for(String id:ids)
|
||||
addQueryParameter("id[]", id);
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class GetNotifications extends MastodonAPIRequest<List<Notification>>{
|
||||
public GetNotifications(String maxID, int limit){
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> excludeTypes){
|
||||
super(HttpMethod.GET, "/notifications", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(excludeTypes!=null){
|
||||
for(Notification.Type nt:excludeTypes){
|
||||
try{
|
||||
addQueryParameter("exclude_types[]", nt.getDeclaringClass().getField(nt.name()).getAnnotation(SerializedName.class).value());
|
||||
}catch(NoSuchFieldException ignore){}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ import android.view.ViewTreeObserver;
|
|||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
|
@ -41,6 +43,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -63,6 +66,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
protected String accountID;
|
||||
protected PhotoViewer currentPhotoViewer;
|
||||
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
|
||||
public BaseStatusListFragment(){
|
||||
super(20);
|
||||
|
@ -255,6 +259,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
}
|
||||
});
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
private Rect tmpRect=new Rect();
|
||||
private Paint paint=new Paint();
|
||||
{
|
||||
paint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
|
||||
|
@ -264,13 +269,18 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
for(int i=0;i<parent.getChildCount()-1;i++){
|
||||
View child=parent.getChildAt(i);
|
||||
View bottomSibling=parent.getChildAt(i+1);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
if(holder instanceof FooterStatusDisplayItem.Holder){
|
||||
float y=child.getY()+child.getHeight()-V.dp(.5f);
|
||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||
if(holder instanceof StatusDisplayItem.Holder && siblingHolder instanceof StatusDisplayItem.Holder
|
||||
&& !((StatusDisplayItem.Holder<?>) holder).getItemID().equals(((StatusDisplayItem.Holder<?>) siblingHolder).getItemID())){
|
||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||
tmpRect.offset(0, Math.round(child.getTranslationY()));
|
||||
float y=tmpRect.bottom-V.dp(.5f);
|
||||
paint.setAlpha(Math.round(255*child.getAlpha()));
|
||||
c.drawLine(child.getX(), y, child.getX()+child.getWidth(), y, paint);
|
||||
c.drawLine(0, y, parent.getWidth(), y, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -316,9 +326,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
}
|
||||
});
|
||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||
private Rect tmpRect=new Rect();
|
||||
@Override
|
||||
public void getSelectorBounds(View view, Rect outRect){
|
||||
outRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
|
||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
String id=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||
|
@ -328,10 +339,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||
if(otherID.equals(id)){
|
||||
outRect.left=Math.min(outRect.left, child.getLeft());
|
||||
outRect.top=Math.min(outRect.top, child.getTop());
|
||||
outRect.right=Math.max(outRect.right, child.getRight());
|
||||
outRect.bottom=Math.max(outRect.bottom, child.getBottom());
|
||||
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||
outRect.top=Math.min(outRect.top, tmpRect.top);
|
||||
outRect.right=Math.max(outRect.right, tmpRect.right);
|
||||
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -509,6 +521,37 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
return accountID;
|
||||
}
|
||||
|
||||
public Relationship getRelationship(String id){
|
||||
return relationships.get(id);
|
||||
}
|
||||
|
||||
public void putRelationship(String id, Relationship rel){
|
||||
relationships.put(id, rel);
|
||||
}
|
||||
|
||||
protected void loadRelationships(Set<String> ids){
|
||||
if(ids.isEmpty())
|
||||
return;
|
||||
// TODO somehow manage these and cancel outstanding requests on refresh
|
||||
new GetAccountRelationships(ids)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> result){
|
||||
for(Relationship r:result)
|
||||
relationships.put(r.id, r);
|
||||
onRelationshipsLoaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
protected void onRelationshipsLoaded(){}
|
||||
|
||||
@Nullable
|
||||
protected <I extends StatusDisplayItem> I findItemOfType(String id, Class<I> type){
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
|
|
|
@ -170,6 +170,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||
lf.loadData();
|
||||
}else if(newFragment instanceof DiscoverFragment){
|
||||
((DiscoverFragment) newFragment).loadData();
|
||||
}else if(newFragment instanceof NotificationsFragment){
|
||||
((NotificationsFragment) newFragment).loadData();
|
||||
// TODO make an interface?
|
||||
}
|
||||
currentTab=tab;
|
||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||
|
|
|
@ -1,25 +1,49 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.parceler.Parcels;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
public class NotificationsFragment extends ToolbarFragment implements ScrollableToTop{
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
private FrameLayout[] tabViews;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
|
||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
||||
|
||||
private String accountID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
|
||||
accountID=getArguments().getString("account");
|
||||
}
|
||||
|
||||
public class NotificationsFragment extends BaseStatusListFragment<Notification>{
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
|
@ -27,80 +51,135 @@ public class NotificationsFragment extends BaseStatusListFragment<Notification>{
|
|||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||
ReblogOrReplyLineStatusDisplayItem titleItem=new ReblogOrReplyLineStatusDisplayItem(n.id, this, switch(n.type){
|
||||
case FOLLOW -> getString(R.string.user_followed_you, n.account.displayName);
|
||||
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request, n.account.displayName);
|
||||
case MENTION -> getString(R.string.user_mentioned_you, n.account.displayName);
|
||||
case REBLOG -> getString(R.string.user_boosted, n.account.displayName);
|
||||
case FAVORITE -> getString(R.string.user_favorited, n.account.displayName);
|
||||
case POLL -> getString(R.string.poll_ended);
|
||||
case STATUS -> getString(R.string.user_posted, n.account.displayName);
|
||||
}, n.account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled);
|
||||
if(n.status!=null){
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts);
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else{
|
||||
return Collections.singletonList(titleItem);
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
LinearLayout view=(LinearLayout) inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
pager=view.findViewById(R.id.pager);
|
||||
|
||||
tabViews=new FrameLayout[2];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.notifications_all;
|
||||
case 1 -> R.id.notifications_mentions;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
||||
tabViews[i]=tabView;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Notification s){
|
||||
if(!knownAccounts.containsKey(s.account.id))
|
||||
knownAccounts.put(s.account.id, s.account);
|
||||
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
||||
knownAccounts.put(s.status.account.id, s.status.account);
|
||||
}
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetNotifications(offset>0 ? getMaxID() : null, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setAdapter(new DiscoverPagerAdapter());
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
public void onPageSelected(int position){
|
||||
if(position==0)
|
||||
return;
|
||||
Fragment _page=getFragmentForPage(position);
|
||||
if(_page instanceof BaseRecyclerFragment){
|
||||
BaseRecyclerFragment page=(BaseRecyclerFragment) _page;
|
||||
if(!page.loaded && !page.isDataLoading())
|
||||
page.loadData();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
Notification n=getNotificationByID(id);
|
||||
if(n.status!=null){
|
||||
Status status=n.status;
|
||||
if(allNotificationsFragment==null){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(status));
|
||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||
args.putBoolean("__is_tab", true);
|
||||
|
||||
allNotificationsFragment=new NotificationsListFragment();
|
||||
allNotificationsFragment.setArguments(args);
|
||||
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("onlyMentions", true);
|
||||
mentionsFragment=new NotificationsListFragment();
|
||||
mentionsFragment.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.notifications_all, allNotificationsFragment)
|
||||
.add(R.id.notifications_mentions, mentionsFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.all_notifications;
|
||||
case 1 -> R.string.mentions;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
}
|
||||
});
|
||||
tabLayoutMediator.attach();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updatePoll(String itemID, Poll poll){
|
||||
Notification notification=getNotificationByID(itemID);
|
||||
if(notification==null || notification.status==null)
|
||||
return;
|
||||
notification.status.poll=poll;
|
||||
super.updatePoll(itemID, poll);
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
updateToolbar();
|
||||
}
|
||||
|
||||
private Notification getNotificationByID(String id){
|
||||
for(Notification n:data){
|
||||
if(n.id.equals(id))
|
||||
return n;
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
updateToolbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
||||
allNotificationsFragment.loadData();
|
||||
}
|
||||
|
||||
private void updateToolbar(){
|
||||
getToolbar().setOutlineProvider(null);
|
||||
}
|
||||
|
||||
private NotificationsListFragment getFragmentForPage(int page){
|
||||
return switch(page){
|
||||
case 0 -> allNotificationsFragment;
|
||||
case 1 -> mentionsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
}
|
||||
|
||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||
private EnumSet<Notification.Type> types;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.notifications);
|
||||
if(getArguments().getBoolean("onlyMentions", false)){
|
||||
types=EnumSet.complementOf(EnumSet.of(Notification.Type.MENTION));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||
String extraText=switch(n.type){
|
||||
case FOLLOW -> getString(R.string.user_followed_you);
|
||||
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
||||
case MENTION, STATUS -> null;
|
||||
case REBLOG -> getString(R.string.user_boosted);
|
||||
case FAVORITE -> getString(R.string.user_favorited);
|
||||
case POLL -> getString(R.string.poll_ended);
|
||||
};
|
||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText) : null;
|
||||
if(n.status!=null){
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null);
|
||||
if(titleItem!=null)
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else{
|
||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account);
|
||||
return Arrays.asList(titleItem, card);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Notification s){
|
||||
if(!knownAccounts.containsKey(s.account.id))
|
||||
knownAccounts.put(s.account.id, s.account);
|
||||
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
||||
knownAccounts.put(s.status.account.id, s.status.account);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetNotifications(offset>0 ? getMaxID() : null, count, types)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
if(refreshing)
|
||||
relationships.clear();
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
Set<String> needRelationships=result.stream()
|
||||
.filter(ntf->ntf.status==null && !relationships.containsKey(ntf.account.id))
|
||||
.map(ntf->ntf.account.id)
|
||||
.collect(Collectors.toSet());
|
||||
loadRelationships(needRelationships);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelationshipsLoaded(){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof AccountCardStatusDisplayItem.Holder)
|
||||
((AccountCardStatusDisplayItem.Holder) holder).rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
// if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
// loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
Notification n=getNotificationByID(id);
|
||||
if(n.status!=null){
|
||||
Status status=n.status;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(status));
|
||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updatePoll(String itemID, Poll poll){
|
||||
Notification notification=getNotificationByID(itemID);
|
||||
if(notification==null || notification.status==null)
|
||||
return;
|
||||
notification.status.poll=poll;
|
||||
super.updatePoll(itemID, poll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private int bgColor=UiUtils.getThemeColor(getActivity(), android.R.attr.colorBackground);
|
||||
private int borderColor=UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted);
|
||||
private RectF rect=new RectF();
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
int pos=0;
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
pos=holder.getAbsoluteAdapterPosition();
|
||||
boolean inset=(holder instanceof StatusDisplayItem.Holder) && ((StatusDisplayItem.Holder<?>) holder).getItem().inset;
|
||||
if(inset){
|
||||
if(rect.isEmpty()){
|
||||
rect.set(child.getX(), i==0 && pos>0 && displayItems.get(pos-1).inset ? V.dp(-10) : child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight());
|
||||
}else{
|
||||
rect.bottom=Math.max(rect.bottom, child.getY()+child.getHeight());
|
||||
rect.right=Math.max(rect.right, child.getX()+child.getHeight());
|
||||
}
|
||||
}else if(!rect.isEmpty()){
|
||||
drawInsetBackground(c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
if(!rect.isEmpty()){
|
||||
if(pos<displayItems.size()-1 && displayItems.get(pos+1).inset){
|
||||
rect.bottom=parent.getHeight()+V.dp(10);
|
||||
}
|
||||
drawInsetBackground(c);
|
||||
rect.setEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawInsetBackground(Canvas c){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(bgColor);
|
||||
rect.inset(V.dp(4), V.dp(4));
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(V.dp(1));
|
||||
paint.setColor(borderColor);
|
||||
rect.inset(paint.getStrokeWidth()/2f, paint.getStrokeWidth()/2f);
|
||||
c.drawRoundRect(rect, V.dp(4), V.dp(4), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder){
|
||||
boolean inset=((StatusDisplayItem.Holder<?>) holder).getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
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 ImageStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
else
|
||||
pad=V.dp(12);
|
||||
outRect.left=outRect.right=pad;
|
||||
if(!topSiblingInset)
|
||||
outRect.top=pad;
|
||||
if(!bottomSiblingInset)
|
||||
outRect.bottom=pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Notification getNotificationByID(String id){
|
||||
for(Notification n:data){
|
||||
if(n.id.equals(id))
|
||||
return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ import me.grishka.appkit.Nav;
|
|||
|
||||
public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts);
|
||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
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, Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,8 +45,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
public final Status status;
|
||||
private boolean hasVisibilityToggle;
|
||||
boolean needBottomPadding;
|
||||
private String extraText;
|
||||
|
||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status){
|
||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText){
|
||||
super(parentID, parentFragment);
|
||||
this.user=user;
|
||||
this.createdAt=createdAt;
|
||||
|
@ -56,15 +57,18 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
this.status=status;
|
||||
HtmlParser.parseCustomEmoji(parsedName, user.emojis);
|
||||
emojiHelper.setText(parsedName);
|
||||
hasVisibilityToggle=status.sensitive || !TextUtils.isEmpty(status.spoilerText);
|
||||
if(!hasVisibilityToggle && !status.mediaAttachments.isEmpty()){
|
||||
for(Attachment att:status.mediaAttachments){
|
||||
if(att.type!=Attachment.Type.AUDIO){
|
||||
hasVisibilityToggle=true;
|
||||
break;
|
||||
if(status!=null){
|
||||
hasVisibilityToggle=status.sensitive || !TextUtils.isEmpty(status.spoilerText);
|
||||
if(!hasVisibilityToggle && !status.mediaAttachments.isEmpty()){
|
||||
for(Attachment att:status.mediaAttachments){
|
||||
if(att.type!=Attachment.Type.AUDIO){
|
||||
hasVisibilityToggle=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.extraText=extraText;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -86,7 +90,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final TextView name, username, timestamp;
|
||||
private final TextView name, username, timestamp, extraText;
|
||||
private final ImageView avatar, more, visibility;
|
||||
|
||||
private static final ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
|
||||
|
@ -104,6 +108,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
visibility=findViewById(R.id.visibility);
|
||||
extraText=findViewById(R.id.extra_text);
|
||||
avatar.setOnClickListener(this::onAvaClick);
|
||||
avatar.setOutlineProvider(roundCornersOutline);
|
||||
avatar.setClipToOutline(true);
|
||||
|
@ -121,6 +126,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
|
||||
}
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
|
||||
if(TextUtils.isEmpty(item.extraText)){
|
||||
extraText.setVisibility(View.GONE);
|
||||
}else{
|
||||
extraText.setVisibility(View.VISIBLE);
|
||||
extraText.setText(item.extraText);
|
||||
}
|
||||
more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -148,11 +160,11 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
}
|
||||
|
||||
private void onMoreClick(View v){
|
||||
Account account=item.status.account;
|
||||
Account account=item.user;
|
||||
PopupMenu popup=new PopupMenu(v.getContext(), v);
|
||||
Menu menu=popup.getMenu();
|
||||
popup.getMenuInflater().inflate(R.menu.post, menu);
|
||||
if(!AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account))
|
||||
if(item.status==null || !AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account))
|
||||
menu.findItem(R.id.delete).setVisible(false);
|
||||
menu.findItem(R.id.mute).setTitle(v.getResources().getString(/*relationship.muting ? R.string.unmute_user :*/ R.string.mute_user, account.displayName));
|
||||
menu.findItem(R.id.block).setTitle(v.getResources().getString(/*relationship.blocking ? R.string.unblock_user :*/ R.string.block_user, account.displayName));
|
||||
|
|
|
@ -29,6 +29,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
|||
public abstract class StatusDisplayItem{
|
||||
public final String parentID;
|
||||
public final BaseStatusListFragment parentFragment;
|
||||
public boolean inset;
|
||||
|
||||
public StatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
this.parentID=parentID;
|
||||
|
@ -58,10 +59,11 @@ 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);
|
||||
};
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts){
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter){
|
||||
String parentID=parentObject.getID();
|
||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||
Status statusForContent=status.getContentStatus();
|
||||
|
@ -72,7 +74,7 @@ public abstract class StatusDisplayItem{
|
|||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled));
|
||||
}
|
||||
HeaderStatusDisplayItem header;
|
||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent));
|
||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
|
||||
if(!TextUtils.isEmpty(statusForContent.content))
|
||||
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
|
||||
else
|
||||
|
@ -102,10 +104,14 @@ public abstract class StatusDisplayItem{
|
|||
if(statusForContent.poll!=null){
|
||||
buildPollItems(parentID, fragment, statusForContent.poll, items);
|
||||
}
|
||||
if(statusForContent.card!=null){
|
||||
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){
|
||||
items.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
|
||||
}
|
||||
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
||||
if(addFooter){
|
||||
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
|
||||
}
|
||||
for(StatusDisplayItem item:items)
|
||||
item.inset=inset;
|
||||
return items;
|
||||
}
|
||||
|
||||
|
@ -128,6 +134,7 @@ public abstract class StatusDisplayItem{
|
|||
POLL_FOOTER,
|
||||
CARD,
|
||||
FOOTER,
|
||||
ACCOUNT_CARD,
|
||||
}
|
||||
|
||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.Clickable{
|
||||
|
|
|
@ -24,10 +24,12 @@ public class HeaderSubtitleLinearLayout extends LinearLayout{
|
|||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
if(getChildCount()>1){
|
||||
if(getLayoutChildCount()>1){
|
||||
int remainingWidth=MeasureSpec.getSize(widthMeasureSpec);
|
||||
for(int i=1;i<getChildCount();i++){
|
||||
View v=getChildAt(i);
|
||||
if(v.getVisibility()==GONE)
|
||||
continue;
|
||||
v.measure(MeasureSpec.getSize(widthMeasureSpec) | MeasureSpec.AT_MOST, heightMeasureSpec);
|
||||
LayoutParams lp=(LayoutParams) v.getLayoutParams();
|
||||
remainingWidth-=v.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
|
||||
|
@ -36,7 +38,21 @@ public class HeaderSubtitleLinearLayout extends LinearLayout{
|
|||
if(first instanceof TextView){
|
||||
((TextView) first).setMaxWidth(remainingWidth);
|
||||
}
|
||||
}else{
|
||||
View first=getChildAt(0);
|
||||
if(first instanceof TextView){
|
||||
((TextView) first).setMaxWidth(Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
private int getLayoutChildCount(){
|
||||
int count=0;
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
if(getChildAt(i).getVisibility()!=GONE)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/item_discover_account"
|
||||
android:id="@+id/card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -37,22 +37,42 @@
|
|||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:id="@+id/name_wrap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/more"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Eugen" />
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Eugen" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/extra_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="boosted your cat picture" />
|
||||
|
||||
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_below="@id/name_wrap"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_toStartOf="@id/visibility"
|
||||
android:layoutDirection="locale"
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicator="@drawable/mtrl_tabs_default_indicator"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="?android:textColorPrimary"
|
||||
app:tabMode="fixed"
|
||||
android:background="@drawable/bg_discover_tabs"/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -11,4 +11,7 @@
|
|||
<item name="discover_hashtags" type="id"/>
|
||||
<item name="discover_news" type="id"/>
|
||||
<item name="discover_users" type="id"/>
|
||||
|
||||
<item name="notifications_all" type="id"/>
|
||||
<item name="notifications_mentions" type="id"/>
|
||||
</resources>
|
|
@ -14,12 +14,10 @@
|
|||
<string name="in_reply_to">In reply to %s</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
|
||||
<string name="user_followed_you">%s followed you</string>
|
||||
<string name="user_sent_follow_request">%s sent you a follow request</string>
|
||||
<string name="user_mentioned_you">%s mentioned you</string>
|
||||
<string name="user_favorited">%s favorited your toot</string>
|
||||
<string name="poll_ended">Poll you voted in has ended</string>
|
||||
<string name="user_posted">%s posted</string>
|
||||
<string name="user_followed_you">followed you</string>
|
||||
<string name="user_sent_follow_request">sent you a follow request</string>
|
||||
<string name="user_favorited">favorited your toot</string>
|
||||
<string name="poll_ended">poll ended</string>
|
||||
|
||||
<string name="time_seconds">%ds</string>
|
||||
<string name="time_minutes">%dm</string>
|
||||
|
@ -137,6 +135,8 @@
|
|||
<string name="hashtags">Hashtags</string>
|
||||
<string name="news">News</string>
|
||||
<string name="for_you">For you</string>
|
||||
<string name="all_notifications">All</string>
|
||||
<string name="mentions">Mentions</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d person is talking</item>
|
||||
<item quantity="other">%d people are talking</item>
|
||||
|
|
Loading…
Reference in New Issue