parent
dba8bd1862
commit
521ba8766c
12 changed files with 505 additions and 9 deletions
|
@ -1,4 +1,4 @@
|
||||||
package org.joinmastodon.android.api.requests.follow_requests;
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
|
@ -0,0 +1,50 @@
|
||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.FollowSuggestion;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class GetFollowRequests extends MastodonAPIRequest<List<Account>>{
|
||||||
|
private String maxId;
|
||||||
|
|
||||||
|
public GetFollowRequests(String maxID, String minID, int limit){
|
||||||
|
super(HttpMethod.GET, "/follow_requests", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(minID!=null)
|
||||||
|
addQueryParameter("min_id", minID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", ""+limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateAndPostprocessResponse(List<Account> respObj, Response httpResponse) throws IOException {
|
||||||
|
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||||
|
// <https://mastodon.social/api/v1/follow_requests?max_id=268962>; rel="next",
|
||||||
|
// <https://mastodon.social/api/v1/follow_requests?min_id=268981>; rel="prev"
|
||||||
|
String link=httpResponse.header("link");
|
||||||
|
// parsing link header by hand; using a library would be cleaner
|
||||||
|
// (also, the functionality should be part of the max id logics and implemented in MastodonAPIRequest)
|
||||||
|
if(link==null) return;
|
||||||
|
String maxIdEq="max_id=";
|
||||||
|
for(String s : link.split(",")) {
|
||||||
|
if(s.contains("rel=\"next\"")) {
|
||||||
|
int start=s.indexOf(maxIdEq)+maxIdEq.length();
|
||||||
|
int end=s.indexOf('>');
|
||||||
|
if(start<0 || start>end) return;
|
||||||
|
this.maxId=s.substring(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMaxId() {
|
||||||
|
return maxId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.joinmastodon.android.api.requests.follow_requests;
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
|
||||||
|
public class FollowRequestHandledEvent {
|
||||||
|
public String accountID;
|
||||||
|
public boolean accepted;
|
||||||
|
public Account account;
|
||||||
|
public Relationship relationship;
|
||||||
|
|
||||||
|
public FollowRequestHandledEvent(String accountID, boolean accepted, Account account, Relationship rel){
|
||||||
|
this.accountID=accountID;
|
||||||
|
this.accepted=accepted;
|
||||||
|
this.account=account;
|
||||||
|
this.relationship=rel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,344 @@
|
||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.api.requests.accounts.GetAccountRelationships;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
|
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 org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
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.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
||||||
|
private String accountID;
|
||||||
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
|
private GetAccountRelationships relationshipsRequest;
|
||||||
|
private String lastMaxId=null;
|
||||||
|
|
||||||
|
public FollowRequestsListFragment(){
|
||||||
|
super(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
setTitle(R.string.follow_requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
if(relationshipsRequest!=null){
|
||||||
|
relationshipsRequest.cancel();
|
||||||
|
relationshipsRequest=null;
|
||||||
|
}
|
||||||
|
currentRequest=new GetFollowRequests(offset>0 ? lastMaxId : null, null, count)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Account> result){
|
||||||
|
onDataLoaded(result.stream().map(AccountWrapper::new).collect(Collectors.toList()), false);
|
||||||
|
loadRelationships();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
|
return new AccountsAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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){
|
||||||
|
outRect.bottom=outRect.left=outRect.right=V.dp(16);
|
||||||
|
if(parent.getChildAdapterPosition(view)==0)
|
||||||
|
outRect.top=V.dp(16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
((UsableRecyclerView)list).setDrawSelectorOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRelationships(){
|
||||||
|
relationships=Collections.emptyMap();
|
||||||
|
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
|
||||||
|
relationshipsRequest.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Relationship> result){
|
||||||
|
relationshipsRequest=null;
|
||||||
|
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||||
|
if(list==null)
|
||||||
|
return;
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
if(holder instanceof AccountViewHolder avh)
|
||||||
|
avh.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
relationshipsRequest=null;
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView(){
|
||||||
|
super.onDestroyView();
|
||||||
|
if(relationshipsRequest!=null){
|
||||||
|
relationshipsRequest.cancel();
|
||||||
|
relationshipsRequest=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToTop(){
|
||||||
|
smoothScrollRecyclerViewToTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
|
public AccountsAdapter(){
|
||||||
|
super(imgLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(AccountViewHolder holder, int position){
|
||||||
|
holder.bind(data.get(position));
|
||||||
|
super.onBindViewHolder(holder, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new AccountViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageCountForItem(int position){
|
||||||
|
return 2+data.get(position).emojiHelper.getImageCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
|
AccountWrapper item=data.get(position);
|
||||||
|
if(image==0)
|
||||||
|
return item.avaRequest;
|
||||||
|
else if(image==1)
|
||||||
|
return item.coverRequest;
|
||||||
|
else
|
||||||
|
return item.emojiHelper.getImageRequest(image-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// literally the same as AccountCardStatusDisplayItem and DiscoverAccountsFragment. code should be generalized
|
||||||
|
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||||
|
private final ImageView cover, avatar;
|
||||||
|
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
||||||
|
private final ProgressBarButton actionButton, acceptButton, rejectButton;
|
||||||
|
private final ProgressBar actionProgress, acceptProgress, rejectProgress;
|
||||||
|
private final View actionWrap, acceptWrap, rejectWrap;
|
||||||
|
|
||||||
|
private Relationship relationship;
|
||||||
|
|
||||||
|
public AccountViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_discover_account, list);
|
||||||
|
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);
|
||||||
|
acceptButton=findViewById(R.id.accept_btn);
|
||||||
|
acceptProgress=findViewById(R.id.accept_progress);
|
||||||
|
acceptWrap=findViewById(R.id.accept_btn_wrap);
|
||||||
|
rejectButton=findViewById(R.id.reject_btn);
|
||||||
|
rejectProgress=findViewById(R.id.reject_progress);
|
||||||
|
rejectWrap=findViewById(R.id.reject_btn_wrap);
|
||||||
|
|
||||||
|
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
|
||||||
|
itemView.setClipToOutline(true);
|
||||||
|
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||||
|
avatar.setClipToOutline(true);
|
||||||
|
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||||
|
cover.setClipToOutline(true);
|
||||||
|
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||||
|
acceptButton.setOnClickListener(this::onFollowRequestButtonClick);
|
||||||
|
rejectButton.setOnClickListener(this::onFollowRequestButtonClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(AccountWrapper 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(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||||
|
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||||
|
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||||
|
relationship=relationships.get(item.account.id);
|
||||||
|
if(relationship == null || !relationship.followedBy){
|
||||||
|
actionWrap.setVisibility(View.GONE);
|
||||||
|
acceptWrap.setVisibility(View.VISIBLE);
|
||||||
|
rejectWrap.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// i hate that i wasn't able to do this in xml
|
||||||
|
acceptButton.setCompoundDrawableTintList(acceptButton.getTextColors());
|
||||||
|
acceptProgress.setIndeterminateTintList(acceptButton.getTextColors());
|
||||||
|
rejectButton.setCompoundDrawableTintList(rejectButton.getTextColors());
|
||||||
|
rejectProgress.setIndeterminateTintList(rejectButton.getTextColors());
|
||||||
|
}else if(relationship==null){
|
||||||
|
actionWrap.setVisibility(View.GONE);
|
||||||
|
acceptWrap.setVisibility(View.GONE);
|
||||||
|
rejectWrap.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
actionWrap.setVisibility(View.VISIBLE);
|
||||||
|
acceptWrap.setVisibility(View.GONE);
|
||||||
|
rejectWrap.setVisibility(View.GONE);
|
||||||
|
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 a && !a.isRunning())
|
||||||
|
a.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearImage(int index){
|
||||||
|
setImage(index, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||||
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onFollowRequestButtonClick(View v) {
|
||||||
|
itemView.setHasTransientState(true);
|
||||||
|
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, accountID, null, v == acceptButton, relationship, rel -> {
|
||||||
|
itemView.setHasTransientState(false);
|
||||||
|
relationships.put(item.account.id, rel);
|
||||||
|
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
||||||
|
if (!rel.requested && !rel.followedBy && adapter != null) {
|
||||||
|
data.remove(item);
|
||||||
|
adapter.notifyItemRemoved(getBindingAdapterPosition());
|
||||||
|
} else {
|
||||||
|
rebind();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionButtonClick(View v){
|
||||||
|
itemView.setHasTransientState(true);
|
||||||
|
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||||
|
itemView.setHasTransientState(false);
|
||||||
|
relationships.put(item.account.id, rel);
|
||||||
|
rebind();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActionProgressVisible(boolean visible){
|
||||||
|
actionButton.setTextVisible(!visible);
|
||||||
|
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
|
actionButton.setClickable(!visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class AccountWrapper{
|
||||||
|
public Account account;
|
||||||
|
public ImageLoaderRequest avaRequest, coverRequest;
|
||||||
|
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||||
|
public CharSequence parsedName, parsedBio;
|
||||||
|
|
||||||
|
public AccountWrapper(Account account){
|
||||||
|
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(), accountID);
|
||||||
|
if(account.emojis.isEmpty()){
|
||||||
|
parsedName=account.displayName;
|
||||||
|
}else{
|
||||||
|
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||||
|
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,22 @@ package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
|
@ -20,8 +26,15 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop{
|
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop{
|
||||||
|
@ -42,14 +55,36 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
E.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
E.unregister(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
setTitle(R.string.notifications);
|
setTitle(R.string.notifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
inflater.inflate(R.menu.notifications, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() != R.id.follow_requests) return false;
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
LinearLayout view=(LinearLayout) inflater.inflate(R.layout.fragment_notifications, container, false);
|
LinearLayout view=(LinearLayout) inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||||
|
@ -123,12 +158,30 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refreshFollowRequestsBadge() {
|
||||||
|
new GetFollowRequests(null, null, 1).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Account> accounts) {
|
||||||
|
getToolbar().getMenu().findItem(R.id.follow_requests).setIcon(accounts.isEmpty() ? R.drawable.ic_fluent_person_add_24_regular : R.drawable.ic_follow_requests_24_badged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse errorResponse) {}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onFollowRequestHandled(FollowRequestHandledEvent ev) {
|
||||||
|
refreshFollowRequestsBadge();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
|
refreshFollowRequestsBadge();
|
||||||
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
||||||
allNotificationsFragment.loadData();
|
allNotificationsFragment.loadData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,14 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||||
onlyMentions=getArguments().getBoolean("onlyMentions", false);
|
onlyMentions=getArguments().getBoolean("onlyMentions", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
super.onRefresh();
|
||||||
|
if (getParentFragment() instanceof NotificationsFragment notificationsFragment) {
|
||||||
|
notificationsFragment.refreshFollowRequestsBadge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||||
String extraText=switch(n.type){
|
String extraText=switch(n.type){
|
||||||
|
|
|
@ -11,7 +11,6 @@ import android.content.res.TypedArray;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Typeface;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.InsetDrawable;
|
import android.graphics.drawable.InsetDrawable;
|
||||||
|
@ -40,11 +39,12 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
||||||
import org.joinmastodon.android.api.requests.follow_requests.AuthorizeFollowRequest;
|
import org.joinmastodon.android.api.requests.accounts.AuthorizeFollowRequest;
|
||||||
import org.joinmastodon.android.api.requests.follow_requests.RejectFollowRequest;
|
import org.joinmastodon.android.api.requests.accounts.RejectFollowRequest;
|
||||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
|
@ -74,6 +74,7 @@ import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.AttrRes;
|
import androidx.annotation.AttrRes;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.browser.customtabs.CustomTabsIntent;
|
import androidx.browser.customtabs.CustomTabsIntent;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
@ -464,11 +465,12 @@ public class UiUtils{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void handleFollowRequest(Activity activity, Account account, String accountID, String notificationID, boolean accepted, Relationship relationship, Consumer<Relationship> resultCallback) {
|
public static void handleFollowRequest(Activity activity, Account account, String accountID, @Nullable String notificationID, boolean accepted, Relationship relationship, Consumer<Relationship> resultCallback) {
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
new AuthorizeFollowRequest(account.id).setCallback(new Callback<>() {
|
new AuthorizeFollowRequest(account.id).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Relationship rel) {
|
public void onSuccess(Relationship rel) {
|
||||||
|
E.post(new FollowRequestHandledEvent(accountID, true, account, rel));
|
||||||
resultCallback.accept(rel);
|
resultCallback.accept(rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +484,8 @@ public class UiUtils{
|
||||||
new RejectFollowRequest(account.id).setCallback(new Callback<>() {
|
new RejectFollowRequest(account.id).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Relationship rel) {
|
public void onSuccess(Relationship rel) {
|
||||||
E.post(new NotificationDeletedEvent(notificationID));
|
E.post(new FollowRequestHandledEvent(accountID, false, account, rel));
|
||||||
|
if (notificationID != null) E.post(new NotificationDeletedEvent(notificationID));
|
||||||
resultCallback.accept(rel);
|
resultCallback.accept(rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_fluent_person_add_24_regular" android:left="2dp" android:right="2dp" android:top="2dp" android:bottom="2dp"/>
|
||||||
|
<item android:width="14dp" android:height="14dp" android:gravity="top|right">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<stroke android:color="?android:colorPrimary" android:width="2dp"/>
|
||||||
|
<solid android:color="@color/primary_600"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
8
mastodon/src/main/res/menu/notifications.xml
Normal file
8
mastodon/src/main/res/menu/notifications.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/follow_requests"
|
||||||
|
android:icon="@drawable/ic_fluent_person_add_24_regular"
|
||||||
|
android:showAsAction="always"
|
||||||
|
android:title="@string/follow_requests" />
|
||||||
|
</menu>
|
|
@ -377,6 +377,7 @@
|
||||||
<!-- %s is file size -->
|
<!-- %s is file size -->
|
||||||
<string name="download_update">Download (%s)</string>
|
<string name="download_update">Download (%s)</string>
|
||||||
<string name="install_update">Installieren</string>
|
<string name="install_update">Installieren</string>
|
||||||
|
<string name="follow_requests">Folgeanfragen</string>
|
||||||
<string name="accept_follow_request">Folgeanfrage akzeptieren</string>
|
<string name="accept_follow_request">Folgeanfrage akzeptieren</string>
|
||||||
<string name="reject_follow_request">Folgeanfrage ablehnen</string>
|
<string name="reject_follow_request">Folgeanfrage ablehnen</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -387,6 +387,7 @@
|
||||||
<string name="privacy_policy_title">Mastodon and your privacy</string>
|
<string name="privacy_policy_title">Mastodon and your privacy</string>
|
||||||
<string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string>
|
<string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string>
|
||||||
<string name="i_agree">I Agree</string>
|
<string name="i_agree">I Agree</string>
|
||||||
|
<string name="follow_requests">Follow requests</string>
|
||||||
<string name="accept_follow_request">Accept follow request</string>
|
<string name="accept_follow_request">Accept follow request</string>
|
||||||
<string name="reject_follow_request">Reject follow request</string>
|
<string name="reject_follow_request">Reject follow request</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Reference in a new issue