Improve follow recommendations screen (AND-101)

This commit is contained in:
Grishka
2023-11-29 02:09:59 +03:00
parent e797d8a1c2
commit a2ea8e76fb
10 changed files with 218 additions and 36 deletions

View File

@@ -38,6 +38,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
protected HashMap<String, Relationship> relationships=new HashMap<>();
protected String accountID;
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
protected int itemLayoutRes=R.layout.item_account_list;
public BaseAccountListFragment(){
super(40);
@@ -151,7 +152,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
@NonNull
@Override
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships);
AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships, itemLayoutRes);
onConfigureViewHolder(holder);
return holder;
}

View File

@@ -4,6 +4,7 @@ import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
@@ -12,35 +13,38 @@ import org.joinmastodon.android.fragments.account_list.BaseAccountListFragment;
import org.joinmastodon.android.model.FollowSuggestion;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
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.views.FragmentRootLinearLayout;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment{
private String accountID;
private View buttonBar;
private ElevationOnScrollListener onScrollListener;
private int numRunningFollowRequests=0;
public OnboardingFollowSuggestionsFragment(){
super(R.layout.fragment_onboarding_follow_suggestions, 40);
itemLayoutRes=R.layout.item_account_list_onboarding;
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
setTitle(R.string.popular_on_mastodon);
setTitle(R.string.onboarding_recommendations_title);
accountID=getArguments().getString("account");
loadData();
}
@@ -49,7 +53,6 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
buttonBar=view.findViewById(R.id.button_bar);
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
@@ -58,9 +61,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
getToolbar().setContentInsetsRelative(V.dp(56), 0);
}
@Override
@@ -69,7 +70,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowSuggestion> result){
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID)).collect(Collectors.toList()), false);
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID).stripLinksFromBio()).collect(Collectors.toList()), false);
}
})
.exec(accountID);
@@ -80,6 +81,19 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
}
@Override
protected RecyclerView.Adapter<?> getAdapter(){
TextView introText=new TextView(getActivity());
introText.setTextAppearance(R.style.m3_body_large);
introText.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
introText.setPaddingRelative(V.dp(56), 0, V.dp(24), V.dp(8));
introText.setText(R.string.onboarding_recommendations_intro);
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(introText));
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
private void onFollowAllClick(View v){
if(!loaded || relationships.isEmpty())
return;
@@ -155,5 +169,6 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.BUTTON, true);
holder.avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
}
}

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.model.viewmodel;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import org.joinmastodon.android.GlobalUserPreferences;
@@ -7,6 +8,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.text.LinkSpan;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import java.util.Collections;
@@ -43,4 +45,13 @@ public class AccountViewModel{
}
this.verifiedLink=verifiedLink;
}
public AccountViewModel stripLinksFromBio(){
if(parsedBio instanceof Spannable spannable){
for(LinkSpan span:spannable.getSpans(0, spannable.length(), LinkSpan.class)){
spannable.removeSpan(span);
}
}
return this;
}
}

View File

@@ -44,6 +44,7 @@ import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import androidx.annotation.LayoutRes;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -53,7 +54,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
public class AccountViewHolder extends BindableViewHolder<AccountViewModel> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{
private final TextView name, username, followers, verifiedLink, bio;
private final ImageView avatar;
public final ImageView avatar;
private final ProgressBarButton button;
private final PopupMenu contextMenu;
private final View menuAnchor;
@@ -75,7 +76,11 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
private boolean checked;
public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap<String, Relationship> relationships){
super(fragment.getActivity(), R.layout.item_account_list, list);
this(fragment, list, relationships, R.layout.item_account_list);
}
public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap<String, Relationship> relationships, @LayoutRes int layout){
super(fragment.getActivity(), layout, list);
this.fragment=fragment;
this.accountID=Objects.requireNonNull(fragment.getArguments().getString("account"));
this.relationships=relationships;
@@ -111,24 +116,28 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
public void onBind(AccountViewModel item){
name.setText(item.parsedName);
username.setText("@"+item.account.acct);
String followersStr=fragment.getResources().getQuantityString(R.plurals.x_followers, item.account.followersCount>1000 ? 999 : (int)item.account.followersCount);
String followersNum=UiUtils.abbreviateNumber(item.account.followersCount);
int index=followersStr.indexOf("%,d");
followersStr=followersStr.replace("%,d", followersNum);
SpannableStringBuilder followersFormatted=new SpannableStringBuilder(followersStr);
if(index!=-1){
followersFormatted.setSpan(mediumSpan, index, index+followersNum.length(), 0);
if(followers!=null){
String followersStr=fragment.getResources().getQuantityString(R.plurals.x_followers, item.account.followersCount>1000 ? 999 : (int)item.account.followersCount);
String followersNum=UiUtils.abbreviateNumber(item.account.followersCount);
int index=followersStr.indexOf("%,d");
followersStr=followersStr.replace("%,d", followersNum);
SpannableStringBuilder followersFormatted=new SpannableStringBuilder(followersStr);
if(index!=-1){
followersFormatted.setSpan(mediumSpan, index, index+followersNum.length(), 0);
}
followers.setText(followersFormatted);
}
if(verifiedLink!=null){
boolean hasVerifiedLink=item.verifiedLink!=null;
if(!hasVerifiedLink)
verifiedLink.setText(R.string.no_verified_link);
else
verifiedLink.setText(item.verifiedLink);
verifiedLink.setCompoundDrawablesRelativeWithIntrinsicBounds(hasVerifiedLink ? R.drawable.ic_check_small_16px : R.drawable.ic_help_16px, 0, 0, 0);
int tintColor=UiUtils.getThemeColor(fragment.getActivity(), hasVerifiedLink ? R.attr.colorM3Primary : R.attr.colorM3Secondary);
verifiedLink.setTextColor(tintColor);
verifiedLink.setCompoundDrawableTintList(ColorStateList.valueOf(tintColor));
}
followers.setText(followersFormatted);
boolean hasVerifiedLink=item.verifiedLink!=null;
if(!hasVerifiedLink)
verifiedLink.setText(R.string.no_verified_link);
else
verifiedLink.setText(item.verifiedLink);
verifiedLink.setCompoundDrawablesRelativeWithIntrinsicBounds(hasVerifiedLink ? R.drawable.ic_check_small_16px : R.drawable.ic_help_16px, 0, 0, 0);
int tintColor=UiUtils.getThemeColor(fragment.getActivity(), hasVerifiedLink ? R.attr.colorM3Primary : R.attr.colorM3Secondary);
verifiedLink.setTextColor(tintColor);
verifiedLink.setCompoundDrawableTintList(ColorStateList.valueOf(tintColor));
bindRelationship();
if(showBio){
bio.setText(item.parsedBio);
@@ -338,7 +347,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
Menu menu=contextMenu.getMenu();
Account account=item.account;
menu.findItem(R.id.share).setTitle(fragment.getString(R.string.share_user, account.getDisplayUsername()));
menu.findItem(R.id.share).setTitle(R.string.share_user);
menu.findItem(R.id.mute).setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
menu.findItem(R.id.block).setTitle(fragment.getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
menu.findItem(R.id.report).setTitle(fragment.getString(R.string.report_user, account.getDisplayUsername()));