From c37f4bb643789d220c74bf204719080d1f69aaaf Mon Sep 17 00:00:00 2001
From: Thomas <tschneider.ac@gmail.com>
Date: Thu, 12 Jan 2023 11:13:52 +0100
Subject: [PATCH] Familiar followers on profiles

---
 .../android/activities/ProfileActivity.java   | 32 ++++++++++++
 .../endpoints/MastodonAccountsService.java    |  8 +++
 .../entities/api/FamiliarFollowers.java       | 28 +++++++++++
 .../android/helper/MastodonHelper.java        | 49 +++++++++++++++++++
 .../viewmodel/mastodon/AccountsVM.java        | 34 +++++++++++++
 app/src/main/res/layout/activity_profile.xml  | 40 ++++++++++++++-
 app/src/main/res/values/strings.xml           |  1 +
 .../metadata/android/en/changelogs/464.txt    |  2 +-
 8 files changed, 192 insertions(+), 2 deletions(-)
 create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java

diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java
index 1ab57d6d..27e59b80 100644
--- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java
+++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java
@@ -34,6 +34,7 @@ import android.text.method.LinkMovementMethod;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.UnderlineSpan;
 import android.util.TypedValue;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -76,6 +77,7 @@ 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.Attachment;
+import app.fedilab.android.client.entities.api.FamiliarFollowers;
 import app.fedilab.android.client.entities.api.Field;
 import app.fedilab.android.client.entities.api.IdentityProof;
 import app.fedilab.android.client.entities.api.MastodonList;
@@ -86,6 +88,7 @@ import app.fedilab.android.client.entities.app.RemoteInstance;
 import app.fedilab.android.client.entities.app.Timeline;
 import app.fedilab.android.client.entities.app.WellKnownNodeinfo;
 import app.fedilab.android.databinding.ActivityProfileBinding;
+import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding;
 import app.fedilab.android.exception.DBException;
 import app.fedilab.android.helper.CrossActionHelper;
 import app.fedilab.android.helper.Helper;
@@ -106,6 +109,7 @@ public class ProfileActivity extends BaseActivity {
 
 
     private RelationShip relationship;
+    private FamiliarFollowers familiarFollowers;
     private Account account;
     private ScheduledExecutorService scheduledExecutorService;
     private action doAction;
@@ -255,6 +259,13 @@ public class ProfileActivity extends BaseActivity {
                 updateAccount();
             }
         });
+        accountsVM.getFamiliarFollowers(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, familiarFollowersList -> {
+            if (familiarFollowersList != null && familiarFollowersList.size() > 0) {
+                this.familiarFollowers = familiarFollowersList.get(0);
+                updateAccount();
+            }
+        });
+
         //Retrieve identity proofs
         accountsVM.getIdentityProofs(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id).observe(ProfileActivity.this, identityProofs -> {
             this.identityProofList = identityProofs;
@@ -567,6 +578,27 @@ public class ProfileActivity extends BaseActivity {
             }
         }
 
+        if (familiarFollowers != null && familiarFollowers.accounts != null && familiarFollowers.accounts.size() > 0) {
+            binding.relatedAccounts.removeAllViews();
+            for (Account account : familiarFollowers.accounts) {
+                NotificationsRelatedAccountsBinding notificationsRelatedAccountsBinding = NotificationsRelatedAccountsBinding.inflate(LayoutInflater.from(ProfileActivity.this));
+                MastodonHelper.loadProfileMediaMastodonRound(ProfileActivity.this, notificationsRelatedAccountsBinding.profilePicture, account);
+                notificationsRelatedAccountsBinding.acc.setText(account.username);
+                notificationsRelatedAccountsBinding.relatedAccountContainer.setOnClickListener(v -> {
+                    Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class);
+                    Bundle b = new Bundle();
+                    b.putSerializable(Helper.ARG_ACCOUNT, account);
+                    intent.putExtras(b);
+                    ActivityOptionsCompat options = ActivityOptionsCompat
+                            .makeSceneTransitionAnimation(ProfileActivity.this, notificationsRelatedAccountsBinding.profilePicture, getString(R.string.activity_porfile_pp));
+                    // start the new activity
+                    startActivity(intent, options.toBundle());
+                });
+                binding.relatedAccounts.addView(notificationsRelatedAccountsBinding.getRoot());
+            }
+            binding.familiarFollowers.setVisibility(View.VISIBLE);
+        }
+
         binding.accountFollow.setEnabled(true);
         //Visibility depending of the relationship
         if (relationship != null) {
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 398c43dc..28b10231 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
@@ -18,6 +18,7 @@ package app.fedilab.android.client.endpoints;
 import java.util.List;
 
 import app.fedilab.android.client.entities.api.Account;
+import app.fedilab.android.client.entities.api.FamiliarFollowers;
 import app.fedilab.android.client.entities.api.FeaturedTag;
 import app.fedilab.android.client.entities.api.IdentityProof;
 import app.fedilab.android.client.entities.api.MastodonList;
@@ -253,6 +254,13 @@ public interface MastodonAccountsService {
             @Query("id[]") List<String> ids
     );
 
+    //Get familiar followers
+    @GET("accounts/familiar_followers ")
+    Call<List<FamiliarFollowers>> getFamiliarFollowers(
+            @Header("Authorization") String token,
+            @Query("id[]") List<String> ids
+    );
+
     //Get search
     @GET("accounts/search")
     Call<List<Account>> searchAccounts(
diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java b/app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java
new file mode 100644
index 00000000..1d753c55
--- /dev/null
+++ b/app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java
@@ -0,0 +1,28 @@
+package app.fedilab.android.client.entities.api;
+/* 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 <http://www.gnu.org/licenses>. */
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class FamiliarFollowers implements Serializable {
+
+    @SerializedName("id")
+    public String id;
+    @SerializedName("accounts")
+    public List<Account> accounts;
+}
diff --git a/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java b/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java
index b86a9ecd..8212dc74 100644
--- a/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java
+++ b/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java
@@ -40,6 +40,9 @@ import androidx.work.OneTimeWorkRequest;
 import androidx.work.WorkManager;
 
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
 import com.google.gson.annotations.SerializedName;
 
 import java.text.SimpleDateFormat;
@@ -268,6 +271,52 @@ public class MastodonHelper {
         }
     }
 
+    public static void loadProfileMediaMastodonRound(Activity activity, ImageView view, Account account) {
+        Context context = view.getContext();
+        SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
+        boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
+        @DrawableRes int placeholder = R.drawable.ic_person;
+        if (Helper.isValidContextForGlide(activity != null ? activity : context)) {
+            if (account == null) {
+                Glide.with(activity != null ? activity : context)
+                        .asDrawable()
+                        .load(placeholder)
+                        .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(16)))
+                        .thumbnail(0.1f)
+                        .placeholder(placeholder)
+                        .into(view);
+                return;
+            }
+            String targetedUrl = disableGif ? account.avatar_static : account.avatar;
+            if (targetedUrl != null) {
+                if (disableGif || (!targetedUrl.endsWith(".gif"))) {
+                    Glide.with(activity != null ? activity : context)
+                            .asDrawable()
+                            .load(targetedUrl)
+                            .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
+                            .thumbnail(0.1f)
+                            .placeholder(placeholder)
+                            .into(view);
+                } else {
+                    Glide.with(activity != null ? activity : context)
+                            .asGif()
+                            .load(targetedUrl)
+                            .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
+                            .thumbnail(0.1f)
+                            .placeholder(placeholder)
+                            .into(view);
+                }
+            } else {
+                Glide.with(activity != null ? activity : context)
+                        .asDrawable()
+                        .load(placeholder)
+                        .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
+                        .thumbnail(0.1f)
+                        .into(view);
+            }
+        }
+    }
+
     /**
      * Convert a date in String -> format yyyy-MM-dd HH:mm:ss
      *
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 fbf37746..e9f23ec1 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
@@ -38,6 +38,7 @@ import app.fedilab.android.client.endpoints.MastodonAccountsService;
 import app.fedilab.android.client.entities.api.Account;
 import app.fedilab.android.client.entities.api.Accounts;
 import app.fedilab.android.client.entities.api.Domains;
+import app.fedilab.android.client.entities.api.FamiliarFollowers;
 import app.fedilab.android.client.entities.api.FeaturedTag;
 import app.fedilab.android.client.entities.api.Field;
 import app.fedilab.android.client.entities.api.Filter;
@@ -90,6 +91,7 @@ public class AccountsVM extends AndroidViewModel {
     private MutableLiveData<List<IdentityProof>> identityProofListMutableLiveData;
     private MutableLiveData<RelationShip> relationShipMutableLiveData;
     private MutableLiveData<List<RelationShip>> relationShipListMutableLiveData;
+    private MutableLiveData<List<FamiliarFollowers>> familiarFollowersListMutableLiveData;
     private MutableLiveData<Filter> filterMutableLiveData;
     private MutableLiveData<List<Filter>> filterListMutableLiveData;
     private MutableLiveData<List<Tag>> tagListMutableLiveData;
@@ -1025,6 +1027,38 @@ public class AccountsVM extends AndroidViewModel {
         return relationShipListMutableLiveData;
     }
 
+
+    /**
+     * Obtain a list of all accounts that follow a given account, filtered for accounts you follow.
+     *
+     * @param ids {@link List} of account IDs to check
+     * @return {@link LiveData} containing a {@link List} of {@link FamiliarFollowers}s to given account(s)
+     */
+    public LiveData<List<FamiliarFollowers>> getFamiliarFollowers(@NonNull String instance, String token, @NonNull List<String> ids) {
+        familiarFollowersListMutableLiveData = new MutableLiveData<>();
+        MastodonAccountsService mastodonAccountsService = init(instance);
+        new Thread(() -> {
+            List<FamiliarFollowers> familiarFollowers = null;
+            Call<List<FamiliarFollowers>> familiarFollowersCall = mastodonAccountsService.getFamiliarFollowers(token, ids);
+
+            if (familiarFollowersCall != null) {
+                try {
+                    Response<List<FamiliarFollowers>> familiarFollowersResponse = familiarFollowersCall.execute();
+                    if (familiarFollowersResponse.isSuccessful()) {
+                        familiarFollowers = familiarFollowersResponse.body();
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            Handler mainHandler = new Handler(Looper.getMainLooper());
+            List<FamiliarFollowers> finalFamiliarFollowers = familiarFollowers;
+            Runnable myRunnable = () -> familiarFollowersListMutableLiveData.setValue(finalFamiliarFollowers);
+            mainHandler.post(myRunnable);
+        }).start();
+        return familiarFollowersListMutableLiveData;
+    }
+
     /**
      * Search for matching accounts by username or display name.
      *
diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml
index f98dcda6..79f8910c 100644
--- a/app/src/main/res/layout/activity_profile.xml
+++ b/app/src/main/res/layout/activity_profile.xml
@@ -341,6 +341,44 @@
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
                     app:layout_constraintTop_toBottomOf="@+id/info" />
+
+
+                <androidx.appcompat.widget.LinearLayoutCompat
+                    android:id="@+id/familiar_followers"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginHorizontal="6dp"
+                    android:gravity="center_vertical"
+                    android:orientation="horizontal"
+                    android:padding="10dp"
+                    android:visibility="gone"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@+id/fields_container"
+                    tools:visibility="visible">
+
+                    <androidx.appcompat.widget.AppCompatTextView
+                        android:id="@+id/type_of_concat"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center_vertical"
+                        android:text="@string/also_followed_by" />
+
+                    <HorizontalScrollView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginStart="10dp"
+                        android:layout_weight="1"
+                        android:gravity="center_vertical">
+
+                        <androidx.appcompat.widget.LinearLayoutCompat
+                            android:id="@+id/related_accounts"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:orientation="horizontal" />
+                    </HorizontalScrollView>
+                </androidx.appcompat.widget.LinearLayoutCompat>
+
                 <!-- End Fields container -->
                 <androidx.appcompat.widget.LinearLayoutCompat
                     android:id="@+id/warning_container"
@@ -350,7 +388,7 @@
                     android:visibility="gone"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintTop_toBottomOf="@+id/fields_container"
+                    app:layout_constraintTop_toBottomOf="@+id/familiar_followers"
                     tools:visibility="visible">
 
                     <androidx.appcompat.widget.AppCompatTextView
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e83cc5cc..52f4e228 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2205,4 +2205,5 @@
     <string name="set_pixelfed_presentation">Pixelfed presentation for media</string>
     <string name="set_display_compact_buttons">Compact action buttons</string>
     <string name="set_display_compact_buttons_description">Buttons at the bottom of messages will not take the whole width</string>
+    <string name="also_followed_by">Followed by:</string>
 </resources>
\ No newline at end of file
diff --git a/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt b/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt
index e53c4869..4ced9f60 100644
--- a/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt
+++ b/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt
@@ -1,5 +1,5 @@
 Added:
-
+- Display familiar followers on profiles
 
 Changed: