Show mutual followers in profile (AND-187)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.FamiliarFollowers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class GetAccountFamiliarFollowers extends MastodonAPIRequest<List<FamiliarFollowers>>{
|
||||
public GetAccountFamiliarFollowers(Collection<String> ids){
|
||||
super(HttpMethod.GET, "/accounts/familiar_followers", new TypeToken<>(){});
|
||||
for(String id:ids){
|
||||
addQueryParameter("id[]", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,18 +51,22 @@ import android.widget.Toolbar;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountFamiliarFollowers;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.account_list.FamiliarFollowerListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.FamiliarFollowers;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
@@ -92,6 +96,8 @@ import java.time.format.FormatStyle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -139,12 +145,16 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
private ImageButton qrCodeButton;
|
||||
private ProgressBar innerProgress;
|
||||
private View actions;
|
||||
private View familiarFollowersRow;
|
||||
private ImageView[] familiarFollowersAvatars;
|
||||
private TextView familiarFollowersLabel;
|
||||
|
||||
private Account account;
|
||||
private String accountID;
|
||||
private Relationship relationship;
|
||||
private boolean isOwnProfile;
|
||||
private ArrayList<AccountField> fields=new ArrayList<>();
|
||||
private List<Account> familiarFollowers=List.of();
|
||||
|
||||
private boolean isInEditMode, editDirty;
|
||||
private Uri editNewAvatar, editNewCover;
|
||||
@@ -225,6 +235,13 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
qrCodeButton=content.findViewById(R.id.qr_code);
|
||||
innerProgress=content.findViewById(R.id.profile_progress);
|
||||
actions=content.findViewById(R.id.profile_actions);
|
||||
familiarFollowersRow=content.findViewById(R.id.familiar_followers);
|
||||
familiarFollowersAvatars=new ImageView[]{
|
||||
content.findViewById(R.id.familiar_followers_ava1),
|
||||
content.findViewById(R.id.familiar_followers_ava2),
|
||||
content.findViewById(R.id.familiar_followers_ava3),
|
||||
};
|
||||
familiarFollowersLabel=content.findViewById(R.id.familiar_followers_label);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||
avatar.setClipToOutline(true);
|
||||
@@ -293,6 +310,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
cover.setOnClickListener(this::onCoverClick);
|
||||
refreshLayout.setOnRefreshListener(this);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
familiarFollowersRow.setOnClickListener(this::onFamiliarFollowersClick);
|
||||
|
||||
if(savedInstanceState!=null){
|
||||
featuredFragment=(ProfileFeaturedFragment) getChildFragmentManager().getFragment(savedInstanceState, "featured");
|
||||
@@ -352,6 +370,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
qf.setArguments(args);
|
||||
qf.show(getChildFragmentManager(), "qrDialog");
|
||||
});
|
||||
familiarFollowersRow.setVisibility(View.GONE);
|
||||
|
||||
return sizeWrapper;
|
||||
}
|
||||
@@ -790,6 +809,25 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
new GetAccountFamiliarFollowers(Set.of(account.id))
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<FamiliarFollowers> result){
|
||||
for(FamiliarFollowers ff:result){
|
||||
if(ff.id.equals(account.id)){
|
||||
familiarFollowers=ff.accounts;
|
||||
updateFamiliarFollowers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void updateRelationship(){
|
||||
@@ -800,6 +838,38 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void updateFamiliarFollowers(){
|
||||
if(!familiarFollowers.isEmpty()){
|
||||
familiarFollowersRow.setVisibility(View.VISIBLE);
|
||||
List<AccountViewModel> followers=familiarFollowers.stream().limit(3).map(a->new AccountViewModel(a, accountID, false, getActivity())).collect(Collectors.toList());
|
||||
String template=switch(familiarFollowers.size()){
|
||||
case 1 -> getString(R.string.familiar_followers_one, "{first}");
|
||||
case 2 -> getString(R.string.familiar_followers_two, "{first}", "{second}");
|
||||
default -> getResources().getQuantityString(R.plurals.familiar_followers_many, familiarFollowers.size()-2, "{first}", "{second}", familiarFollowers.size()-2);
|
||||
};
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(template);
|
||||
if(familiarFollowers.size()>1){
|
||||
int index=template.indexOf("{second}");
|
||||
ssb.replace(index, index+8, followers.get(1).parsedName);
|
||||
template=template.replace("{second}", "#".repeat(followers.get(1).parsedName.length()));
|
||||
}
|
||||
int index=template.indexOf("{first}");
|
||||
ssb.replace(index, index+7, followers.get(0).parsedName);
|
||||
familiarFollowersLabel.setText(ssb);
|
||||
UiUtils.loadCustomEmojiInTextView(familiarFollowersLabel);
|
||||
if(familiarFollowers.size()<3)
|
||||
familiarFollowersAvatars[2].setVisibility(View.GONE);
|
||||
if(familiarFollowers.size()<2)
|
||||
familiarFollowersAvatars[1].setVisibility(View.GONE);
|
||||
|
||||
int i=0;
|
||||
for(AccountViewModel avm:followers){
|
||||
ViewImageLoader.loadWithoutAnimation(familiarFollowersAvatars[i], getResources().getDrawable(R.drawable.image_placeholder, getActivity().getTheme()), avm.avaRequest);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||
if(scrollY>cover.getHeight()){
|
||||
cover.setTranslationY(scrollY-(cover.getHeight()));
|
||||
@@ -1168,6 +1238,14 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop,
|
||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||
}
|
||||
|
||||
private void onFamiliarFollowersClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
args.putInt("count", familiarFollowers.size());
|
||||
Nav.go(getActivity(), FamiliarFollowerListFragment.class, args);
|
||||
}
|
||||
|
||||
private void startImagePicker(int requestCode){
|
||||
Intent intent=UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1);
|
||||
startActivityForResult(intent, requestCode);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountFamiliarFollowers;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FamiliarFollowers;
|
||||
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class FamiliarFollowerListFragment extends BaseAccountListFragment{
|
||||
protected Account account;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||
setTitle("@"+account.acct);
|
||||
int count=getArguments().getInt("count");
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers_you_know, count, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAccountFamiliarFollowers(Set.of(account.id))
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FamiliarFollowers> result){
|
||||
onDataLoaded(result.get(0).accounts.stream().map(a->new AccountViewModel(a, accountID, getActivity())).collect(Collectors.toList()), false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
if(!loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class FamiliarFollowers extends BaseModel{
|
||||
public String id;
|
||||
public List<Account> accounts;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
for(Account acc:accounts){
|
||||
acc.postprocess();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
|
||||
public class FamiliarFollowersImageView extends ImageView implements CustomViewHelper{
|
||||
private Paint clearPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Path path=new Path(), rectPath=new Path();
|
||||
|
||||
public FamiliarFollowersImageView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public FamiliarFollowersImageView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FamiliarFollowersImageView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
UiUtils.setAllPaddings(this, 2);
|
||||
setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), getResources().getDisplayMetrics().density*8.5f);
|
||||
}
|
||||
});
|
||||
setClipToOutline(true);
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
float offset=dp(2);
|
||||
float radius=dp(6);
|
||||
rectPath.rewind();
|
||||
rectPath.addRoundRect(offset, offset, getWidth()-offset, getHeight()-offset, radius, radius, Path.Direction.CW);
|
||||
canvas.save();
|
||||
canvas.clipPath(rectPath); // Unless I do this, the corner pixels still end up dirty
|
||||
super.onDraw(canvas);
|
||||
canvas.restore();
|
||||
path.rewind();
|
||||
path.addRect(0, 0, getWidth(), getHeight(), Path.Direction.CW);
|
||||
path.op(rectPath, Path.Op.DIFFERENCE);
|
||||
canvas.drawPath(path, clearPaint);
|
||||
}
|
||||
}
|
||||
@@ -239,6 +239,62 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/familiar_followers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginVertical="-4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:clipToPadding="false"
|
||||
android:layerType="hardware">
|
||||
<org.joinmastodon.android.ui.views.FamiliarFollowersImageView
|
||||
android:id="@+id/familiar_followers_ava1"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:rotation="-4"
|
||||
tools:src="#f00"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FamiliarFollowersImageView
|
||||
android:id="@+id/familiar_followers_ava2"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginStart="-10dp"
|
||||
android:rotation="2"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FamiliarFollowersImageView
|
||||
android:id="@+id/familiar_followers_ava3"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginStart="-10dp"
|
||||
android:rotation="-2"
|
||||
tools:src="#00f"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/familiar_followers_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="10dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Followed by blah and blah"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
|
||||
android:id="@+id/name_edit_wrap"
|
||||
|
||||
@@ -827,4 +827,14 @@
|
||||
<item quantity="one">%1$s and %2$,d other followed you</item>
|
||||
<item quantity="other">%1$s and %2$,d others followed you</item>
|
||||
</plurals>
|
||||
<string name="familiar_followers_one">Followed by %s</string>
|
||||
<string name="familiar_followers_two">Followed by %s and %s</string>
|
||||
<plurals name="familiar_followers_many">
|
||||
<item quantity="one">Followed by %1$s, %2$s, and %3$,d other</item>
|
||||
<item quantity="other">Followed by %1$s, %2$s, and %3$,d others</item>
|
||||
</plurals>
|
||||
<plurals name="x_followers_you_know">
|
||||
<item quantity="one">%,d follower you know</item>
|
||||
<item quantity="other">%,d followers you know</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user