Notifications

This commit is contained in:
Grishka
2022-01-24 02:56:57 +03:00
parent c3b7fb7002
commit a4a514d37a
17 changed files with 448 additions and 271 deletions

View File

@@ -0,0 +1,251 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.PhotoStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost{
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
protected DisplayItemsAdapter adapter;
protected String accountID;
protected PhotoViewer currentPhotoViewer;
public BaseStatusListFragment(){
super(20);
}
@Override
protected RecyclerView.Adapter getAdapter(){
return adapter=new DisplayItemsAdapter();
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
accountID=getArguments().getString("account");
}
@Override
public void onAppendItems(List<T> items){
super.onAppendItems(items);
for(T s:items){
displayItems.addAll(buildDisplayItems(s));
}
}
@Override
public void onClearItems(){
super.onClearItems();
displayItems.clear();
}
protected void prependItems(List<T> items){
data.addAll(0, items);
int offset=0;
for(T s:items){
List<StatusDisplayItem> toAdd=buildDisplayItems(s);
displayItems.addAll(offset, toAdd);
offset+=toAdd.size();
}
adapter.notifyItemRangeInserted(0, offset);
}
protected String getMaxID(){
if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty())
return data.get(data.size()-1).getID();
else
return null;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
@Override
protected void onHidden(){
super.onHidden();
// Clear any loaded images from the list to make it possible for the GC to deallocate them.
// The delay avoids blank image views showing up in the app switcher.
content.postDelayed(()->{
if(!isHidden())
return;
imgLoader.deactivate();
UsableRecyclerView list=(UsableRecyclerView) this.list;
for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof ImageLoaderViewHolder){
for(int j=0; j<list.getImageCountForItem(holder.getAbsoluteAdapterPosition()); j++){
((ImageLoaderViewHolder) holder).clearImage(j);
}
}
}
}, 100);
}
@Override
protected void onShown(){
super.onShown();
imgLoader.activate();
}
@Override
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){
final Status status=_status.reblog!=null ? _status.reblog : _status;
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
private PhotoStatusDisplayItem.Holder transitioningHolder;
@Override
public void setPhotoViewVisibility(int index, boolean visible){
PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index);
if(holder!=null)
holder.photo.setAlpha(visible ? 1f : 0f);
}
@Override
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index);
if(holder!=null){
transitioningHolder=holder;
View view=transitioningHolder.photo;
int[] pos={0, 0};
view.getLocationOnScreen(pos);
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
list.setClipChildren(false);
transitioningHolder.itemView.setElevation(1f);
return true;
}
return false;
}
@Override
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
View view=transitioningHolder.photo;
view.setTranslationX(translateX);
view.setTranslationY(translateY);
view.setScaleX(scale);
view.setScaleY(scale);
}
@Override
public void endPhotoViewTransition(){
View view=transitioningHolder.photo;
view.setTranslationX(0f);
view.setTranslationY(0f);
view.setScaleX(1f);
view.setScaleY(1f);
transitioningHolder.itemView.setElevation(0f);
list.setClipChildren(true);
transitioningHolder=null;
}
@Override
public Drawable getPhotoViewCurrentDrawable(int index){
PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index);
if(holder!=null)
return holder.photo.getDrawable();
return null;
}
@Override
public void photoViewerDismissed(){
currentPhotoViewer=null;
}
private PhotoStatusDisplayItem.Holder findPhotoViewHolder(int index){
int offset=0;
for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(parentID)){
if(item instanceof PhotoStatusDisplayItem){
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index);
if(holder instanceof PhotoStatusDisplayItem.Holder){
return (PhotoStatusDisplayItem.Holder) holder;
}
return null;
}
}
offset++;
}
return null;
}
});
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
if(currentPhotoViewer!=null)
currentPhotoViewer.offsetView(-dx, -dy);
}
});
}
protected int getMainAdapterOffset(){
return 0;
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
public DisplayItemsAdapter(){
super(imgLoader);
}
@NonNull
@Override
public BindableViewHolder<StatusDisplayItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return (BindableViewHolder<StatusDisplayItem>) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType], getActivity(), parent);
}
@Override
public void onBindViewHolder(BindableViewHolder<StatusDisplayItem> holder, int position){
holder.bind(displayItems.get(position));
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return displayItems.size();
}
@Override
public int getItemViewType(int position){
return displayItems.get(position).getType().ordinal();
}
@Override
public int getImageCountForItem(int position){
return displayItems.get(position).getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
return displayItems.get(position).getImageRequest(image);
}
}
}

View File

@@ -11,8 +11,10 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.Status;
import org.parceler.Parcels;
import java.util.Collections;
import java.util.List;
@@ -32,14 +34,7 @@ public class HomeTimelineFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
String maxID;
if(offset>0 && !preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).id;
else if(offset>0 && !data.isEmpty())
maxID=data.get(data.size()-1).id;
else
maxID=null;
new GetHomeTimeline(maxID, null, count)
new GetHomeTimeline(offset>0 ? getMaxID() : null, null, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
@@ -51,14 +46,22 @@ public class HomeTimelineFragment extends StatusListFragment{
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
menu.add("New toot");
inflater.inflate(R.menu.home, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), CreateTootFragment.class, args);
int id=item.getItemId();
if(id==R.id.new_toot){
Nav.go(getActivity(), CreateTootFragment.class, args);
}else if(id==R.id.notifications){
Nav.go(getActivity(), NotificationsFragment.class, args);
}else if(id==R.id.my_profile){
args.putParcelable("profileAccount", Parcels.wrap(AccountSessionManager.getInstance().getAccount(accountID).self));
Nav.go(getActivity(), ProfileFragment.class, args);
}
return true;
}

View File

@@ -0,0 +1,56 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.api.SimpleCallback;
public class NotificationsFragment extends BaseStatusListFragment<Notification>{
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setTitle(R.string.notifications);
loadData();
}
@Override
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
ReblogOrReplyLineStatusDisplayItem titleItem=new ReblogOrReplyLineStatusDisplayItem(n.id, 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);
});
if(n.status!=null){
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n);
items.add(0, titleItem);
return items;
}else{
return Collections.singletonList(titleItem);
}
}
@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);
}
}

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.os.Bundle;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.model.Account;
@@ -25,14 +24,7 @@ public class ProfileFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
String maxID;
if(offset>0 && !preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).id;
else if(offset>0 && !data.isEmpty())
maxID=data.get(data.size()-1).id;
else
maxID=null;
currentRequest=new GetAccountStatuses(user.id, maxID, null, count)
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){

View File

@@ -1,233 +1,12 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.PhotoStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public abstract class StatusListFragment extends BaseRecyclerFragment<Status> implements PhotoViewerHost{
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
protected DisplayItemsAdapter adapter;
protected String accountID;
protected PhotoViewer currentPhotoViewer;
public StatusListFragment(){
super(20);
}
@Override
protected RecyclerView.Adapter getAdapter(){
return adapter=new DisplayItemsAdapter();
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
accountID=getArguments().getString("account");
}
@Override
public void onAppendItems(List<Status> items){
super.onAppendItems(items);
for(Status s:items){
displayItems.addAll(StatusDisplayItem.buildItems(this, s, accountID));
}
}
@Override
public void onClearItems(){
super.onClearItems();
displayItems.clear();
}
protected void prependItems(List<Status> items){
data.addAll(0, items);
int offset=0;
for(Status s:items){
List<StatusDisplayItem> toAdd=StatusDisplayItem.buildItems(this, s, accountID);
displayItems.addAll(offset, toAdd);
offset+=toAdd.size();
}
adapter.notifyItemRangeInserted(0, offset);
}
@Override
protected void onHidden(){
super.onHidden();
imgLoader.deactivate();
UsableRecyclerView list=(UsableRecyclerView) this.list;
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof ImageLoaderViewHolder){
for(int j=0;j<list.getImageCountForItem(holder.getAbsoluteAdapterPosition());j++){
((ImageLoaderViewHolder) holder).clearImage(j);
}
}
}
}
@Override
protected void onShown(){
super.onShown();
imgLoader.activate();
}
@Override
public void openPhotoViewer(Status _status, int attachmentIndex){
final Status status=_status.reblog!=null ? _status.reblog : _status;
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
private PhotoStatusDisplayItem.Holder transitioningHolder;
@Override
public void setPhotoViewVisibility(int index, boolean visible){
PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index);
if(holder!=null)
holder.photo.setAlpha(visible ? 1f : 0f);
}
@Override
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index);
if(holder!=null){
transitioningHolder=holder;
View view=transitioningHolder.photo;
int[] pos={0, 0};
view.getLocationOnScreen(pos);
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
list.setClipChildren(false);
transitioningHolder.itemView.setElevation(1f);
return true;
}
return false;
}
@Override
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
View view=transitioningHolder.photo;
view.setTranslationX(translateX);
view.setTranslationY(translateY);
view.setScaleX(scale);
view.setScaleY(scale);
}
@Override
public void endPhotoViewTransition(){
View view=transitioningHolder.photo;
view.setTranslationX(0f);
view.setTranslationY(0f);
view.setScaleX(1f);
view.setScaleY(1f);
transitioningHolder.itemView.setElevation(0f);
list.setClipChildren(true);
transitioningHolder=null;
}
@Override
public Drawable getPhotoViewCurrentDrawable(int index){
PhotoStatusDisplayItem.Holder holder=findPhotoViewHolder(index);
if(holder!=null)
return holder.photo.getDrawable();
return null;
}
@Override
public void photoViewerDismissed(){
currentPhotoViewer=null;
}
private PhotoStatusDisplayItem.Holder findPhotoViewHolder(int index){
int offset=0;
for(StatusDisplayItem item:displayItems){
if(item.status==_status){
if(item instanceof PhotoStatusDisplayItem){
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index);
if(holder instanceof PhotoStatusDisplayItem.Holder){
return (PhotoStatusDisplayItem.Holder) holder;
}
return null;
}
}
offset++;
}
return null;
}
});
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
if(currentPhotoViewer!=null)
currentPhotoViewer.offsetView(-dx, -dy);
}
});
}
protected int getMainAdapterOffset(){
return 0;
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
public DisplayItemsAdapter(){
super(imgLoader);
}
@NonNull
@Override
public BindableViewHolder<StatusDisplayItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return (BindableViewHolder<StatusDisplayItem>) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType], getActivity(), parent);
}
@Override
public void onBindViewHolder(BindableViewHolder<StatusDisplayItem> holder, int position){
holder.bind(displayItems.get(position));
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return displayItems.size();
}
@Override
public int getItemViewType(int position){
return displayItems.get(position).getType().ordinal();
}
@Override
public int getImageCountForItem(int position){
return displayItems.get(position).getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
return displayItems.get(position).getImageRequest(image);
}
public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s);
}
}