From 813a2f4e127c087744587f585c49456b6fb95799 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 24 May 2022 19:16:09 +0200 Subject: [PATCH] Add follow request support for locked accounts --- app/src/main/AndroidManifest.xml | 7 +- .../app/fedilab/android/BaseMainActivity.java | 8 +- .../activities/FollowRequestActivity.java | 127 ++++++++++++++++++ .../endpoints/MastodonAccountsService.java | 3 +- .../drawer/AccountFollowRequestAdapter.java | 124 +++++++++++++++++ .../viewmodel/mastodon/AccountsVM.java | 14 +- .../res/drawable/ic_baseline_group_add_24.xml | 22 +++ .../main/res/layout/activity_status_info.xml | 20 +++ .../main/res/menu/activity_main_drawer.xml | 5 + 9 files changed, 320 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/activities/FollowRequestActivity.java create mode 100644 app/src/main/java/app/fedilab/android/ui/drawer/AccountFollowRequestAdapter.java create mode 100644 app/src/main/res/drawable/ic_baseline_group_add_24.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4bf6ef199..9299f85f5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,8 +55,8 @@ android:name=".activities.ContextActivity" android:configChanges="keyboardHidden|orientation|screenSize" /> + android:name=".activities.DraftActivity" + android:configChanges="keyboardHidden|orientation|screenSize" /> + diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index ed5282207..e1ec26b50 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -83,6 +83,7 @@ import app.fedilab.android.activities.ComposeActivity; import app.fedilab.android.activities.ContextActivity; import app.fedilab.android.activities.DraftActivity; import app.fedilab.android.activities.FilterActivity; +import app.fedilab.android.activities.FollowRequestActivity; import app.fedilab.android.activities.InstanceActivity; import app.fedilab.android.activities.InstanceHealthActivity; import app.fedilab.android.activities.LoginActivity; @@ -333,6 +334,9 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } else if (id == R.id.nav_scheduled) { Intent intent = new Intent(this, ScheduledActivity.class); startActivity(intent); + } else if (id == R.id.nav_follow_requests) { + Intent intent = new Intent(this, FollowRequestActivity.class); + startActivity(intent); } binding.drawerLayout.close(); return false; @@ -564,7 +568,9 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt return; } bottomMenu = new BottomMenu(BaseMainActivity.this).hydrate(account, binding.bottomNavView); - + if (account.mastodon_account.locked) { + binding.navView.getMenu().findItem(R.id.nav_follow_requests).setVisible(true); + } if (bottomMenu != null) { //ManageClick on bottom menu items if (binding.bottomNavView.findViewById(R.id.nav_home) != null) { diff --git a/app/src/main/java/app/fedilab/android/activities/FollowRequestActivity.java b/app/src/main/java/app/fedilab/android/activities/FollowRequestActivity.java new file mode 100644 index 000000000..f869073ad --- /dev/null +++ b/app/src/main/java/app/fedilab/android/activities/FollowRequestActivity.java @@ -0,0 +1,127 @@ +package app.fedilab.android.activities; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + + +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Accounts; +import app.fedilab.android.databinding.ActivityStatusInfoBinding; +import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.drawer.AccountFollowRequestAdapter; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + + +public class FollowRequestActivity extends BaseActivity { + + private ActivityStatusInfoBinding binding; + private List accountList; + private AccountFollowRequestAdapter accountAdapter; + private String max_id; + private boolean flagLoading; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ThemeHelper.applyTheme(this); + binding = ActivityStatusInfoBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + setSupportActionBar(binding.toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); + } + accountList = new ArrayList<>(); + flagLoading = false; + max_id = null; + binding.title.setText(R.string.follow_request); + AccountsVM accountsVM = new ViewModelProvider(FollowRequestActivity.this).get(AccountsVM.class); + accountAdapter = new AccountFollowRequestAdapter(accountList); + LinearLayoutManager mLayoutManager = new LinearLayoutManager(FollowRequestActivity.this); + binding.lvAccounts.setLayoutManager(mLayoutManager); + binding.lvAccounts.setAdapter(accountAdapter); + + binding.lvAccounts.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flagLoading) { + flagLoading = true; + binding.loadingNextAccounts.setVisibility(View.VISIBLE); + accountsVM.getFollowRequests(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id, MastodonHelper.accountsPerCall(FollowRequestActivity.this)) + .observe(FollowRequestActivity.this, accounts -> manageView(accounts)); + } + } else { + binding.loadingNextAccounts.setVisibility(View.GONE); + } + } + } + }); + accountsVM.getFollowRequests(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null, MastodonHelper.accountsPerCall(FollowRequestActivity.this)) + .observe(FollowRequestActivity.this, this::manageView); + } + + private void manageView(Accounts accounts) { + flagLoading = false; + binding.loadingNextAccounts.setVisibility(View.GONE); + if (accountList != null && accounts != null && accounts.accounts != null && accounts.accounts.size() > 0) { + int startId = 0; + //There are some statuses present in the timeline + if (accountList.size() > 0) { + startId = accountList.size(); + } + accountList.addAll(accounts.accounts); + max_id = accounts.pagination.max_id; + accountAdapter.notifyItemRangeInserted(startId, accounts.accounts.size()); + binding.noAction.setVisibility(View.GONE); + binding.lvAccounts.setVisibility(View.VISIBLE); + } else if (accountList == null || accountList.size() == 0) { + binding.noActionText.setText(R.string.no_follow_request); + binding.noAction.setVisibility(View.VISIBLE); + binding.lvAccounts.setVisibility(View.GONE); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return true; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java index cfef921db..9f13b25f6 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java @@ -373,7 +373,8 @@ public interface MastodonAccountsService { @GET("follow_requests") Call> getFollowRequests( @Header("Authorization") String token, - @Path("limit") String limit); + @Query("max_id") String max_id, + @Query("limit") int limit); //Accept follow request @POST("follow_requests/{id}/authorize") diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AccountFollowRequestAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AccountFollowRequestAdapter.java new file mode 100644 index 000000000..f1009ab97 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AccountFollowRequestAdapter.java @@ -0,0 +1,124 @@ +package app.fedilab.android.ui.drawer; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityOptionsCompat; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.activities.ProfileActivity; +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.databinding.DrawerFollowBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + + +public class AccountFollowRequestAdapter extends RecyclerView.Adapter { + private final List accountList; + private Context context; + + public AccountFollowRequestAdapter(List accountList) { + this.accountList = accountList; + } + + public int getCount() { + return accountList.size(); + } + + public Account getItem(int position) { + return accountList.get(position); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + context = parent.getContext(); + DrawerFollowBinding itemBinding = DrawerFollowBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new ViewHolderFollow(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + Account account = accountList.get(position); + ViewHolderFollow holderFollow = (ViewHolderFollow) viewHolder; + MastodonHelper.loadPPMastodon(holderFollow.binding.avatar, account); + holderFollow.binding.displayName.setText(account.display_name); + holderFollow.binding.username.setText(String.format("@%s", account.acct)); + holderFollow.binding.rejectButton.setVisibility(View.VISIBLE); + holderFollow.binding.acceptButton.setVisibility(View.VISIBLE); + holderFollow.binding.title.setText(R.string.follow_request); + AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class); + holderFollow.binding.acceptButton.setOnClickListener(v -> { + accountsVM.acceptFollow(MainActivity.currentInstance, MainActivity.currentToken, account.id) + .observe((LifecycleOwner) context, relationShip -> { + accountList.remove(position); + notifyItemRemoved(position); + }); + }); + holderFollow.binding.rejectButton.setOnClickListener(v -> { + accountsVM.rejectFollow(MainActivity.currentInstance, MainActivity.currentToken, account.id) + .observe((LifecycleOwner) context, relationShip -> { + accountList.remove(position); + notifyItemRemoved(position); + }); + }); + holderFollow.binding.avatar.setOnClickListener(v -> { + Intent intent = new Intent(context, ProfileActivity.class); + Bundle b = new Bundle(); + b.putSerializable(Helper.ARG_ACCOUNT, account); + intent.putExtras(b); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation((Activity) context, holderFollow.binding.avatar, context.getString(R.string.activity_porfile_pp)); + // start the new activity + context.startActivity(intent, options.toBundle()); + }); + } + + + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return accountList.size(); + } + + + static class ViewHolderFollow extends RecyclerView.ViewHolder { + DrawerFollowBinding binding; + + ViewHolderFollow(DrawerFollowBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java index e5bfc3b9a..c20b983f8 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java @@ -1288,28 +1288,30 @@ public class AccountsVM extends AndroidViewModel { * @param limit Maximum number of results to return. Defaults to 40. * @return {@link LiveData} containing a {@link List} of {@link Account}s */ - public LiveData> getFollowRequests(@NonNull String instance, String token, String limit) { - accountListMutableLiveData = new MutableLiveData<>(); + public LiveData getFollowRequests(@NonNull String instance, String token, String max_id, int limit) { + accountsMutableLiveData = new MutableLiveData<>(); MastodonAccountsService mastodonAccountsService = init(instance); new Thread(() -> { List accountList = null; - Call> followRequestsCall = mastodonAccountsService.getFollowRequests(token, limit); + Accounts accounts = new Accounts(); + Call> followRequestsCall = mastodonAccountsService.getFollowRequests(token, max_id, limit); if (followRequestsCall != null) { try { Response> followRequestsResponse = followRequestsCall.execute(); if (followRequestsResponse.isSuccessful()) { accountList = followRequestsResponse.body(); + accounts.accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), accountList); + accounts.pagination = MastodonHelper.getPagination(followRequestsResponse.headers()); } } catch (IOException e) { e.printStackTrace(); } Handler mainHandler = new Handler(Looper.getMainLooper()); - List finalAccountList = accountList; - Runnable myRunnable = () -> accountListMutableLiveData.setValue(finalAccountList); + Runnable myRunnable = () -> accountsMutableLiveData.setValue(accounts); mainHandler.post(myRunnable); } }).start(); - return accountListMutableLiveData; + return accountsMutableLiveData; } /** diff --git a/app/src/main/res/drawable/ic_baseline_group_add_24.xml b/app/src/main/res/drawable/ic_baseline_group_add_24.xml new file mode 100644 index 000000000..485dcc215 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_group_add_24.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_status_info.xml b/app/src/main/res/layout/activity_status_info.xml index 67019446f..b7ccb65b5 100644 --- a/app/src/main/res/layout/activity_status_info.xml +++ b/app/src/main/res/layout/activity_status_info.xml @@ -40,6 +40,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:fitsSystemWindows="true"> + + + + + + + + +