Follow requests list is available. Closes #222

This commit is contained in:
Vavassor 2017-04-28 23:52:14 -04:00
commit 7d83a9aaba
18 changed files with 403 additions and 77 deletions

View file

@ -19,4 +19,5 @@ interface AccountActionListener {
void onViewAccount(String id);
void onMute(final boolean mute, final String id, final int position);
void onBlock(final boolean block, final String id, final int position);
void onRespondToFollowRequest(final boolean accept, final String id, final int position);
}

View file

@ -24,16 +24,17 @@ import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
public class BlocksActivity extends BaseActivity {
public class AccountListActivity extends BaseActivity {
enum Type {
BLOCKS,
MUTES
MUTES,
FOLLOW_REQUESTS,
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blocks);
setContentView(R.layout.activity_account_list);
Type type;
Intent intent = getIntent();
@ -48,21 +49,29 @@ public class BlocksActivity extends BaseActivity {
ActionBar bar = getSupportActionBar();
if (bar != null) {
switch (type) {
case MUTES: { bar.setTitle(getString(R.string.title_mutes)); break; }
case BLOCKS: { bar.setTitle(getString(R.string.title_blocks)); break; }
case MUTES: { bar.setTitle(getString(R.string.title_mutes)); break; }
case FOLLOW_REQUESTS: {
bar.setTitle(getString(R.string.title_follow_requests));
break;
}
}
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowHomeEnabled(true);
}
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
AccountFragment.Type fragmentType;
AccountListFragment.Type fragmentType;
switch (type) {
case MUTES: { fragmentType = AccountFragment.Type.MUTES; break; }
default:
case BLOCKS: { fragmentType = AccountFragment.Type.BLOCKS; break; }
case BLOCKS: { fragmentType = AccountListFragment.Type.BLOCKS; break; }
case MUTES: { fragmentType = AccountListFragment.Type.MUTES; break; }
case FOLLOW_REQUESTS: {
fragmentType = AccountListFragment.Type.FOLLOW_REQUESTS;
break;
}
}
Fragment fragment = AccountFragment.newInstance(fragmentType);
Fragment fragment = AccountListFragment.newInstance(fragmentType);
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}

View file

@ -38,16 +38,15 @@ import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AccountFragment extends BaseFragment implements AccountActionListener {
private static final String TAG = "Account"; // logging tag
private Call<List<Account>> listCall;
public class AccountListFragment extends BaseFragment implements AccountActionListener {
private static final String TAG = "AccountList"; // logging tag
public enum Type {
FOLLOWS,
FOLLOWERS,
BLOCKS,
MUTES,
FOLLOW_REQUESTS,
}
private Type type;
@ -59,17 +58,17 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
private TabLayout.OnTabSelectedListener onTabSelectedListener;
private MastodonAPI api;
public static AccountFragment newInstance(Type type) {
public static AccountListFragment newInstance(Type type) {
Bundle arguments = new Bundle();
AccountFragment fragment = new AccountFragment();
AccountListFragment fragment = new AccountListFragment();
arguments.putSerializable("type", type);
fragment.setArguments(arguments);
return fragment;
}
public static AccountFragment newInstance(Type type, String accountId) {
public static AccountListFragment newInstance(Type type, String accountId) {
Bundle arguments = new Bundle();
AccountFragment fragment = new AccountFragment();
AccountListFragment fragment = new AccountListFragment();
arguments.putSerializable("type", type);
arguments.putString("accountId", accountId);
fragment.setArguments(arguments);
@ -90,7 +89,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_account, container, false);
View rootView = inflater.inflate(R.layout.fragment_account_list, container, false);
Context context = getContext();
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
@ -108,6 +107,8 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
adapter = new BlocksAdapter(this);
} else if (type == Type.MUTES) {
adapter = new MutesAdapter(this);
} else if (type == Type.FOLLOW_REQUESTS) {
adapter = new FollowRequestsAdapter(this);
} else {
adapter = new FollowAdapter(this);
}
@ -157,12 +158,6 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
recyclerView.addOnScrollListener(scrollListener);
}
@Override
public void onDestroy() {
super.onDestroy();
if (listCall != null) listCall.cancel();
}
@Override
public void onDestroyView() {
if (jumpToTopAllowed()) {
@ -189,6 +184,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
}
};
Call<List<Account>> listCall;
switch (type) {
default:
case FOLLOWS: {
@ -207,6 +203,10 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
listCall = api.mutes(fromId, uptoId, null);
break;
}
case FOLLOW_REQUESTS: {
listCall = api.followRequests(fromId, uptoId, null);
break;
}
}
callList.add(listCall);
listCall.enqueue(cb);
@ -239,12 +239,14 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
Log.e(TAG, "Fetch failure: " + exception.getMessage());
}
@Override
public void onViewAccount(String id) {
Intent intent = new Intent(getContext(), AccountActivity.class);
intent.putExtra("id", id);
startActivity(intent);
}
@Override
public void onMute(final boolean mute, final String id, final int position) {
if (api == null) {
/* If somehow an unmute button is clicked after onCreateView but before
@ -308,6 +310,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
}
@Override
public void onBlock(final boolean block, final String id, final int position) {
if (api == null) {
/* If somehow an unblock button is clicked after onCreateView but before
@ -371,6 +374,54 @@ public class AccountFragment extends BaseFragment implements AccountActionListen
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
}
@Override
public void onRespondToFollowRequest(final boolean accept, final String accountId,
final int position) {
if (api == null) {
/* If somehow an response button is clicked after onCreateView but before
* onActivityCreated, then this would get called with a null api object, so this eats
* that input. */
Log.d(TAG, "MastodonAPI isn't initialised, so follow requests can't be responded to.");
return;
}
Callback<Relationship> callback = new Callback<Relationship>() {
@Override
public void onResponse(Call<Relationship> call, Response<Relationship> response) {
if (response.isSuccessful()) {
onRespondToFollowRequestSuccess(position);
} else {
onRespondToFollowRequestFailure(accept, accountId);
}
}
@Override
public void onFailure(Call<Relationship> call, Throwable t) {
onRespondToFollowRequestFailure(accept, accountId);
}
};
Call<Relationship> call;
if (accept) {
call = api.authorizeFollowRequest(accountId);
} else {
call = api.rejectFollowRequest(accountId);
}
callList.add(call);
call.enqueue(callback);
}
private void onRespondToFollowRequestSuccess(int position) {
FollowRequestsAdapter followRequestsAdapter = (FollowRequestsAdapter) adapter;
followRequestsAdapter.removeItem(position);
}
private void onRespondToFollowRequestFailure(boolean accept, String accountId) {
String verb = (accept) ? "accept" : "reject";
String message = String.format("Failed to %s account id %s.", verb, accountId);
Log.e(TAG, message);
}
private boolean jumpToTopAllowed() {
return type == Type.FOLLOWS || type == Type.FOLLOWERS;
}

View file

@ -51,10 +51,10 @@ class AccountPagerAdapter extends FragmentPagerAdapter {
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
}
case 1: {
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWS, accountId);
return AccountListFragment.newInstance(AccountListFragment.Type.FOLLOWS, accountId);
}
case 2: {
return AccountFragment.newInstance(AccountFragment.Type.FOLLOWERS, accountId);
return AccountListFragment.newInstance(AccountListFragment.Type.FOLLOWERS, accountId);
}
default: {
return null;

View file

@ -0,0 +1,129 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import com.keylesspalace.tusky.entity.Account;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
import butterknife.BindView;
import butterknife.ButterKnife;
class FollowRequestsAdapter extends AccountAdapter {
private static final int VIEW_TYPE_FOLLOW_REQUEST = 0;
private static final int VIEW_TYPE_FOOTER = 1;
FollowRequestsAdapter(AccountActionListener accountActionListener) {
super(accountActionListener);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
default:
case VIEW_TYPE_FOLLOW_REQUEST: {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_follow_request, parent, false);
return new FollowRequestViewHolder(view);
}
case VIEW_TYPE_FOOTER: {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer, parent, false);
return new FooterViewHolder(view);
}
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (position < accountList.size()) {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener);
}
}
@Override
public int getItemViewType(int position) {
if (position == accountList.size()) {
return VIEW_TYPE_FOOTER;
} else {
return VIEW_TYPE_FOLLOW_REQUEST;
}
}
static class FollowRequestViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.follow_request_avatar) CircularImageView avatar;
@BindView(R.id.follow_request_username) TextView username;
@BindView(R.id.follow_request_display_name) TextView displayName;
@BindView(R.id.follow_request_accept) ImageButton accept;
@BindView(R.id.follow_request_reject) ImageButton reject;
private String id;
FollowRequestViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void setupWithAccount(Account account) {
id = account.id;
displayName.setText(account.getDisplayName());
String format = username.getContext().getString(R.string.status_username_format);
String formattedUsername = String.format(format, account.username);
username.setText(formattedUsername);
Picasso.with(avatar.getContext())
.load(account.avatar)
.error(R.drawable.avatar_error)
.placeholder(R.drawable.avatar_default)
.into(avatar);
}
void setupActionListener(final AccountActionListener listener) {
accept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onRespondToFollowRequest(true, id, position);
}
}
});
reject.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onRespondToFollowRequest(false, id, position);
}
}
});
avatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onViewAccount(id);
}
});
}
}
}

View file

@ -304,18 +304,22 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
Intent intent = new Intent(MainActivity.this, FavouritesActivity.class);
startActivity(intent);
} else if (drawerItemIdentifier == 2) {
Intent intent = new Intent(MainActivity.this, BlocksActivity.class);
intent.putExtra("type", BlocksActivity.Type.MUTES);
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
intent.putExtra("type", AccountListActivity.Type.MUTES);
startActivity(intent);
} else if (drawerItemIdentifier == 3) {
Intent intent = new Intent(MainActivity.this, BlocksActivity.class);
intent.putExtra("type", BlocksActivity.Type.BLOCKS);
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
intent.putExtra("type", AccountListActivity.Type.BLOCKS);
startActivity(intent);
} else if (drawerItemIdentifier == 4) {
Intent intent = new Intent(MainActivity.this, PreferencesActivity.class);
startActivity(intent);
} else if (drawerItemIdentifier == 5) {
logout();
} else if (drawerItemIdentifier == 6) {
Intent intent = new Intent(MainActivity.this, AccountListActivity.class);
intent.putExtra("type", AccountListActivity.Type.FOLLOW_REQUESTS);
startActivity(intent);
}
}
@ -443,36 +447,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
onFetchUserInfoFailure(new Exception(response.message()));
return;
}
headerResult.clear();
Account me = response.body();
ImageView background = headerResult.getHeaderBackgroundView();
int backgroundWidth = background.getWidth();
int backgroundHeight = background.getHeight();
if (backgroundWidth == 0 || backgroundHeight == 0) {
/* The header ImageView may not be layed out when the verify credentials call
* returns so measure the dimensions and use those. */
background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
backgroundWidth = background.getMeasuredWidth();
backgroundHeight = background.getMeasuredHeight();
}
Picasso.with(MainActivity.this)
.load(me.header)
.placeholder(R.drawable.account_header_missing)
.resize(backgroundWidth, backgroundHeight)
.centerCrop()
.into(background);
headerResult.addProfiles(
new ProfileDrawerItem()
.withName(me.getDisplayName())
.withEmail(String.format("%s@%s", me.username, domain))
.withIcon(me.avatar)
);
onFetchUserInfoSuccess(me.id, me.username);
onFetchUserInfoSuccess(response.body(), domain);
}
@Override
@ -482,9 +457,48 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove
});
}
private void onFetchUserInfoSuccess(String id, String username) {
loggedInAccountId = id;
loggedInAccountUsername = username;
private void onFetchUserInfoSuccess(Account me, String domain) {
// Add the header image and avatar from the account, into the navigation drawer header.
headerResult.clear();
ImageView background = headerResult.getHeaderBackgroundView();
int backgroundWidth = background.getWidth();
int backgroundHeight = background.getHeight();
if (backgroundWidth == 0 || backgroundHeight == 0) {
/* The header ImageView may not be layed out when the verify credentials call returns so
* measure the dimensions and use those. */
background.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
backgroundWidth = background.getMeasuredWidth();
backgroundHeight = background.getMeasuredHeight();
}
Picasso.with(MainActivity.this)
.load(me.header)
.placeholder(R.drawable.account_header_missing)
.resize(backgroundWidth, backgroundHeight)
.centerCrop()
.into(background);
headerResult.addProfiles(
new ProfileDrawerItem()
.withName(me.getDisplayName())
.withEmail(String.format("%s@%s", me.username, domain))
.withIcon(me.avatar)
);
// Show follow requests in the menu, if this is a locked account.
if (me.locked) {
PrimaryDrawerItem followRequestsItem = new PrimaryDrawerItem()
.withIdentifier(6)
.withName(R.string.action_view_follow_requests)
.withSelectable(false)
.withIcon(GoogleMaterial.Icon.gmd_person_add);
drawer.addItemAtPosition(followRequestsItem, 3);
}
// Update the current login information.
loggedInAccountId = me.id;
loggedInAccountUsername = me.username;
getPrivatePreferences().edit()
.putString("loggedInAccountId", loggedInAccountId)
.putString("loggedInAccountUsername", loggedInAccountUsername)