From 0952d975576ec29985c75a57c1ac5ccbf4e4c4d1 Mon Sep 17 00:00:00 2001 From: Grishka Date: Sun, 25 Jun 2023 01:18:38 +0300 Subject: [PATCH] Unified account row --- .../fragments/BaseStatusListFragment.java | 20 +- .../account_list/BaseAccountListFragment.java | 4 + .../ComposeAccountSearchFragment.java | 1 + .../discover/SearchQueryFragment.java | 1 + .../OnboardingFollowSuggestionsFragment.java | 193 +----------------- .../model/viewmodel/AccountViewModel.java | 6 +- .../AccountStatusDisplayItem.java | 69 ++----- .../displayitems/HeaderStatusDisplayItem.java | 4 + .../ui/displayitems/StatusDisplayItem.java | 6 +- .../android/ui/utils/UiUtils.java | 8 +- .../ui/viewholders/AccountViewHolder.java | 97 +++++++-- .../ui/views/CheckableRelativeLayout.java | 8 +- .../main/res/layout/display_item_account.xml | 42 ---- .../main/res/layout/display_item_header.xml | 2 +- .../src/main/res/layout/item_account_list.xml | 175 ++++++++++------ .../main/res/layout/item_discover_account.xml | 187 ----------------- .../src/main/res/layout/item_user_row_m3.xml | 11 - mastodon/src/main/res/values/strings.xml | 6 + 18 files changed, 275 insertions(+), 565 deletions(-) delete mode 100644 mastodon/src/main/res/layout/display_item_account.xml delete mode 100644 mastodon/src/main/res/layout/item_discover_account.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index 42dd3fc36..a9152c67b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -24,8 +24,10 @@ import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.BetterItemAnimator; +import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem; @@ -577,7 +579,7 @@ public abstract class BaseStatusListFragment exten @NonNull @Override public BindableViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - BindableViewHolder holder=(BindableViewHolder) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType & (~0x80000000)], getActivity(), parent); + BindableViewHolder holder=(BindableViewHolder) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType & (~0x80000000)], getActivity(), parent, BaseStatusListFragment.this); onModifyItemViewHolder(holder); return holder; } @@ -625,12 +627,22 @@ public abstract class BaseStatusListFragment exten View bottomSibling=parent.getChildAt(i+1); RecyclerView.ViewHolder holder=parent.getChildViewHolder(child); RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling); - if((holder instanceof StatusDisplayItem.Holder ih && siblingHolder instanceof StatusDisplayItem.Holder sh - && (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP) - || needDividerForExtraItem(child, bottomSibling, holder, siblingHolder)){ + if(needDrawDivider(holder, siblingHolder)){ drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint); } } } + + private boolean needDrawDivider(RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){ + if(needDividerForExtraItem(holder.itemView, siblingHolder.itemView, holder, siblingHolder)) + return true; + if(holder instanceof StatusDisplayItem.Holder ih && siblingHolder instanceof StatusDisplayItem.Holder sh){ + // Do not draw dividers between hashtag and/or account rows + if((ih instanceof HashtagStatusDisplayItem.Holder || ih instanceof AccountStatusDisplayItem.Holder) && (sh instanceof HashtagStatusDisplayItem.Holder || sh instanceof AccountStatusDisplayItem.Holder)) + return false; + return (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP; + } + return false; + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java index fb4ca4a52..46c5c49b1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/BaseAccountListFragment.java @@ -43,6 +43,10 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment relationships){ super(fragment, list, relationships); + setStyle(AccessoryType.NONE, false); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/OnboardingFollowSuggestionsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/OnboardingFollowSuggestionsFragment.java index 30c4741ea..7fe7a6396 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/OnboardingFollowSuggestionsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/OnboardingFollowSuggestionsFragment.java @@ -1,61 +1,33 @@ package org.joinmastodon.android.fragments.onboarding; import android.app.ProgressDialog; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Bundle; -import android.text.TextUtils; import android.view.View; -import android.view.ViewGroup; import android.view.WindowInsets; -import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; import org.joinmastodon.android.R; -import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; -import org.joinmastodon.android.fragments.HomeFragment; -import org.joinmastodon.android.fragments.ProfileFragment; +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.views.ProgressBarButton; +import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import org.joinmastodon.android.utils.ElevationOnScrollListener; -import org.parceler.Parcels; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; -import androidx.annotation.NonNull; -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.fragments.BaseRecyclerFragment; -import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter; -import me.grishka.appkit.imageloader.ImageLoaderViewHolder; -import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; -import me.grishka.appkit.utils.BindableViewHolder; -import me.grishka.appkit.utils.V; import me.grishka.appkit.views.FragmentRootLinearLayout; -import me.grishka.appkit.views.UsableRecyclerView; -public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment{ +public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment{ private String accountID; - private Map relationships=Collections.emptyMap(); - private GetAccountRelationships relationshipsRequest; private View buttonBar; private ElevationOnScrollListener onScrollListener; private int numRunningFollowRequests=0; @@ -98,46 +70,16 @@ public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment result){ onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID)).collect(Collectors.toList()), false); - loadRelationships(); } }) .exec(accountID); } - private void loadRelationships(){ - relationships=Collections.emptyMap(); - relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList())); - relationshipsRequest.setCallback(new Callback<>(){ - @Override - public void onSuccess(List result){ - relationshipsRequest=null; - relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity())); - if(list==null) - return; - for(int i=0;i implements ImageLoaderRecyclerAdapter{ - - public SuggestionsAdapter(){ - super(imgLoader); - } - - @NonNull - @Override - public SuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ - return new SuggestionViewHolder(); - } - - @Override - public int getItemCount(){ - return data.size(); - } - - @Override - public void onBindViewHolder(SuggestionViewHolder holder, int position){ - holder.bind(data.get(position)); - super.onBindViewHolder(holder, position); - } - - @Override - public int getImageCountForItem(int position){ - return data.get(position).emojiHelper.getImageCount()+1; - } - - @Override - public ImageLoaderRequest getImageRequest(int position, int image){ - AccountViewModel account=data.get(position); - if(image==0) - return account.avaRequest; - return account.emojiHelper.getImageRequest(image-1); - } - } - - private class SuggestionViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{ - private final TextView name, username, bio; - private final ImageView avatar; - private final ProgressBarButton actionButton; - private final ProgressBar actionProgress; - private final View actionWrap; - - private Relationship relationship; - - public SuggestionViewHolder(){ - super(getActivity(), R.layout.item_user_row_m3, list); - name=findViewById(R.id.name); - username=findViewById(R.id.username); - bio=findViewById(R.id.bio); - avatar=findViewById(R.id.avatar); - actionButton=findViewById(R.id.action_btn); - actionProgress=findViewById(R.id.action_progress); - actionWrap=findViewById(R.id.action_btn_wrap); - - avatar.setOutlineProvider(OutlineProviders.roundedRect(10)); - avatar.setClipToOutline(true); - actionButton.setOnClickListener(UiUtils.rateLimitedClickListener(this::onActionButtonClick)); - } - - @Override - public void onBind(AccountViewModel item){ - name.setText(item.parsedName); - username.setText(item.account.getDisplayUsername()); - if(TextUtils.isEmpty(item.parsedBio)){ - bio.setVisibility(View.GONE); - }else{ - bio.setVisibility(View.VISIBLE); - bio.setText(item.parsedBio); - } - - relationship=relationships.get(item.account.id); - if(relationship==null){ - actionWrap.setVisibility(View.GONE); - }else{ - actionWrap.setVisibility(View.VISIBLE); - UiUtils.setRelationshipToActionButtonM3(relationship, actionButton); - } - } - - @Override - public void setImage(int index, Drawable image){ - if(index==0){ - avatar.setImageDrawable(image); - }else{ - item.emojiHelper.setImageDrawable(index-1, image); - name.invalidate(); - bio.invalidate(); - } - if(image instanceof Animatable a && !a.isRunning()) - a.start(); - } - - @Override - public void clearImage(int index){ - setImage(index, null); - } - - @Override - public void onClick(){ - Bundle args=new Bundle(); - args.putString("account", accountID); - args.putParcelable("profileAccount", Parcels.wrap(item.account)); - Nav.go(getActivity(), ProfileFragment.class, args); - } - - private void onActionButtonClick(View v){ - itemView.setHasTransientState(true); - UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{ - itemView.setHasTransientState(false); - relationships.put(item.account.id, rel); - rebind(); - }); - } - - private void setActionProgressVisible(boolean visible){ - if(visible) - actionProgress.setIndeterminateTintList(actionButton.getTextColors()); - actionButton.setTextVisible(!visible); - actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE); - actionButton.setClickable(!visible); - } + @Override + protected void onConfigureViewHolder(AccountViewHolder holder){ + super.onConfigureViewHolder(holder); + holder.setStyle(AccountViewHolder.AccessoryType.BUTTON, true); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java b/mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java index 9e710ffcf..d11ca46ae 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java @@ -1,5 +1,7 @@ package org.joinmastodon.android.model.viewmodel; +import android.text.SpannableStringBuilder; + import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.model.Account; @@ -29,7 +31,9 @@ public class AccountViewModel{ else parsedName=account.displayName; parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); - emojiHelper.setText(parsedName); + SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName); + ssb.append(parsedBio); + emojiHelper.setText(ssb); String verifiedLink=null; for(AccountField fld:account.fields){ if(fld.verifiedAt!=null){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AccountStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AccountStatusDisplayItem.java index b4666100f..4b76635d8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AccountStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AccountStatusDisplayItem.java @@ -1,42 +1,21 @@ package org.joinmastodon.android.ui.displayitems; -import android.content.Context; -import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import org.joinmastodon.android.R; -import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.model.Account; -import org.joinmastodon.android.ui.OutlineProviders; -import org.joinmastodon.android.ui.text.HtmlParser; -import org.joinmastodon.android.ui.utils.CustomEmojiHelper; +import org.joinmastodon.android.model.viewmodel.AccountViewModel; +import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; -import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; -import me.grishka.appkit.utils.V; public class AccountStatusDisplayItem extends StatusDisplayItem{ - public final Account account; - private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); - private CharSequence parsedName; - public ImageLoaderRequest avaRequest; + public final AccountViewModel account; public AccountStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account){ super(parentID, parentFragment); - this.account=account; - if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames) - parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis); - else - parsedName=account.displayName; - emojiHelper.setText(parsedName); - if(!TextUtils.isEmpty(account.avatar)) - avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50)); + this.account=new AccountViewModel(account, parentFragment.getAccountID()); } @Override @@ -46,51 +25,43 @@ public class AccountStatusDisplayItem extends StatusDisplayItem{ @Override public int getImageCount(){ - return 1+emojiHelper.getImageCount(); + return 1+account.emojiHelper.getImageCount(); } @Override public ImageLoaderRequest getImageRequest(int index){ if(index==0) - return avaRequest; - return emojiHelper.getImageRequest(index-1); + return account.avaRequest; + return account.emojiHelper.getImageRequest(index-1); } public static class Holder extends StatusDisplayItem.Holder implements ImageLoaderViewHolder{ - private final TextView name, username; - private final ImageView photo; + private final AccountViewHolder realHolder; - public Holder(Context context, ViewGroup parent){ - super(context, R.layout.display_item_account, parent); - name=findViewById(R.id.name); - username=findViewById(R.id.username); - photo=findViewById(R.id.photo); - - photo.setOutlineProvider(OutlineProviders.roundedRect(12)); - photo.setClipToOutline(true); + public Holder(AccountViewHolder realHolder){ + super(realHolder.itemView); + this.realHolder=realHolder; + realHolder.setStyle(AccountViewHolder.AccessoryType.NONE, false); } @Override public void onBind(AccountStatusDisplayItem item){ - name.setText(item.parsedName); - username.setText("@"+item.account.acct); + realHolder.bind(item.account); } @Override public void setImage(int index, Drawable image){ - if(image instanceof Animatable && !((Animatable) image).isRunning()) - ((Animatable) image).start(); - if(index==0){ - photo.setImageDrawable(image); - }else{ - item.emojiHelper.setImageDrawable(index-1, image); - name.invalidate(); - } + realHolder.setImage(index, image); } @Override public void clearImage(int index){ - setImage(index, null); + realHolder.clearImage(index); + } + + @Override + public void onClick(){ + realHolder.onClick(); } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 27006c8d2..86bdac1cc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -241,6 +241,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ @Override public void clearImage(int index){ + if(index==0){ + avatar.setImageResource(R.drawable.image_placeholder); + return; + } setImage(index, null); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index b003790a9..73cb85d4f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -1,6 +1,7 @@ package org.joinmastodon.android.ui.displayitems; import android.app.Activity; +import android.app.Fragment; import android.content.Context; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -19,6 +20,7 @@ import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.text.HtmlParser; +import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import java.util.ArrayList; import java.util.List; @@ -57,7 +59,7 @@ public abstract class StatusDisplayItem{ return null; } - public static BindableViewHolder createViewHolder(Type type, Activity activity, ViewGroup parent){ + public static BindableViewHolder createViewHolder(Type type, Activity activity, ViewGroup parent, Fragment parentFragment){ return switch(type){ case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent); case HEADER_CHECKABLE -> new CheckableHeaderStatusDisplayItem.Holder(activity, parent); @@ -68,7 +70,7 @@ public abstract class StatusDisplayItem{ case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent); case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent); case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent); - case ACCOUNT -> new AccountStatusDisplayItem.Holder(activity, parent); + case ACCOUNT -> new AccountStatusDisplayItem.Holder(new AccountViewHolder(parentFragment, parent, null)); case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent); case GAP -> new GapStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index c1d1eecdb..ae1c1d5af 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -127,11 +127,11 @@ public class UiUtils{ if(diff<1000L){ return context.getString(R.string.time_now); }else if(diff<60_000L){ - return context.getString(R.string.time_seconds, diff/1000L); + return context.getString(R.string.time_seconds_ago_short, diff/1000L); }else if(diff<3600_000L){ - return context.getString(R.string.time_minutes, diff/60_000L); + return context.getString(R.string.time_minutes_ago_short, diff/60_000L); }else if(diff<3600_000L*24L){ - return context.getString(R.string.time_hours, diff/3600_000L); + return context.getString(R.string.time_hours_ago_short, diff/3600_000L); }else{ int days=(int)(diff/(3600_000L*24L)); if(days>30){ @@ -142,7 +142,7 @@ public class UiUtils{ return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt); } } - return context.getString(R.string.time_days, days); + return context.getString(R.string.time_days_ago_short, days); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java index cff88a684..dc4e99b1b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java @@ -1,12 +1,12 @@ package org.joinmastodon.android.ui.viewholders; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.Fragment; import android.app.ProgressDialog; import android.content.Intent; import android.content.res.ColorStateList; import android.graphics.drawable.Animatable; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.SpannableStringBuilder; @@ -16,8 +16,11 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.CheckBox; import android.widget.ImageView; import android.widget.PopupMenu; +import android.widget.ProgressBar; +import android.widget.RadioButton; import android.widget.TextView; import org.joinmastodon.android.R; @@ -26,12 +29,12 @@ import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.model.Account; -import org.joinmastodon.android.model.AccountField; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.viewmodel.AccountViewModel; import org.joinmastodon.android.ui.OutlineProviders; -import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.CheckableRelativeLayout; +import org.joinmastodon.android.ui.views.ProgressBarButton; import org.parceler.Parcels; import java.util.HashMap; @@ -46,18 +49,24 @@ import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.views.UsableRecyclerView; public class AccountViewHolder extends BindableViewHolder implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{ - private final TextView name, username, followers, verifiedLink; + private final TextView name, username, followers, verifiedLink, bio; private final ImageView avatar; - private final Button button; + private final ProgressBarButton button; private final PopupMenu contextMenu; private final View menuAnchor; private final TypefaceSpan mediumSpan=new TypefaceSpan("sans-serif-medium"); + private final CheckableRelativeLayout view; + private final View checkbox; + private final ProgressBar actionProgress; private final String accountID; private final Fragment fragment; private final HashMap relationships; private Consumer onClick; + private AccessoryType accessoryType=AccessoryType.BUTTON; + private boolean showBio; + private boolean checked; public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap relationships){ super(fragment.getActivity(), R.layout.item_account_list, list); @@ -65,6 +74,7 @@ public class AccountViewHolder extends BindableViewHolder impl this.accountID=Objects.requireNonNull(fragment.getArguments().getString("account")); this.relationships=relationships; + view=(CheckableRelativeLayout) itemView; name=findViewById(R.id.name); username=findViewById(R.id.username); avatar=findViewById(R.id.avatar); @@ -72,8 +82,11 @@ public class AccountViewHolder extends BindableViewHolder impl menuAnchor=findViewById(R.id.menu_anchor); followers=findViewById(R.id.followers_count); verifiedLink=findViewById(R.id.verified_link); + bio=findViewById(R.id.bio); + checkbox=findViewById(R.id.checkbox); + actionProgress=findViewById(R.id.action_progress); - avatar.setOutlineProvider(OutlineProviders.roundedRect(16)); + avatar.setOutlineProvider(OutlineProviders.roundedRect(10)); avatar.setClipToOutline(true); button.setOnClickListener(this::onButtonClick); @@ -81,6 +94,8 @@ public class AccountViewHolder extends BindableViewHolder impl contextMenu=new PopupMenu(fragment.getActivity(), menuAnchor); contextMenu.inflate(R.menu.profile); contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected); + + setStyle(AccessoryType.BUTTON, false); } @SuppressLint("SetTextI18n") @@ -107,10 +122,13 @@ public class AccountViewHolder extends BindableViewHolder impl verifiedLink.setTextColor(tintColor); verifiedLink.setCompoundDrawableTintList(ColorStateList.valueOf(tintColor)); bindRelationship(); + if(showBio){ + bio.setText(item.parsedBio); + } } public void bindRelationship(){ - if(relationships==null) + if(relationships==null || accessoryType!=AccessoryType.BUTTON) return; Relationship rel=relationships.get(item.account.id); if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){ @@ -128,6 +146,7 @@ public class AccountViewHolder extends BindableViewHolder impl }else{ item.emojiHelper.setImageDrawable(index-1, image); name.invalidate(); + bio.invalidate(); } if(image instanceof Animatable a && !a.isRunning()) @@ -199,21 +218,22 @@ public class AccountViewHolder extends BindableViewHolder impl private void onButtonClick(View v){ if(relationships==null) return; - ProgressDialog progress=new ProgressDialog(fragment.getActivity()); - progress.setMessage(fragment.getString(R.string.loading)); - progress.setCancelable(false); - UiUtils.performAccountAction(fragment.getActivity(), item.account, accountID, relationships.get(item.account.id), button, progressShown->{ - itemView.setHasTransientState(progressShown); - if(progressShown) - progress.show(); - else - progress.dismiss(); - }, result->{ - relationships.put(item.account.id, result); + itemView.setHasTransientState(true); + UiUtils.performAccountAction((Activity) v.getContext(), item.account, accountID, relationships.get(item.account.id), button, this::setActionProgressVisible, rel->{ + itemView.setHasTransientState(false); + relationships.put(item.account.id, rel); bindRelationship(); }); } + private void setActionProgressVisible(boolean visible){ + if(visible) + actionProgress.setIndeterminateTintList(button.getTextColors()); + button.setTextVisible(!visible); + actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE); + button.setClickable(!visible); + } + private boolean onContextMenuItemSelected(MenuItem item){ Relationship relationship=relationships.get(this.item.account.id); if(relationship==null) @@ -271,4 +291,45 @@ public class AccountViewHolder extends BindableViewHolder impl public void setOnClickListener(Consumer listener){ onClick=listener; } + + public void setStyle(AccessoryType accessoryType, boolean showBio){ + if(accessoryType!=this.accessoryType){ + this.accessoryType=accessoryType; + switch(accessoryType){ + case NONE -> { + button.setVisibility(View.GONE); + checkbox.setVisibility(View.GONE); + } + case CHECKBOX -> { + button.setVisibility(View.GONE); + checkbox.setVisibility(View.VISIBLE); + checkbox.setBackground(new CheckBox(checkbox.getContext()).getButtonDrawable()); + } + case RADIOBUTTON -> { + button.setVisibility(View.GONE); + checkbox.setVisibility(View.VISIBLE); + checkbox.setBackground(new RadioButton(checkbox.getContext()).getButtonDrawable()); + } + case BUTTON -> { + button.setVisibility(View.VISIBLE); + checkbox.setVisibility(View.GONE); + } + } + view.setCheckable(accessoryType==AccessoryType.CHECKBOX || accessoryType==AccessoryType.RADIOBUTTON); + } + this.showBio=showBio; + bio.setVisibility(showBio ? View.VISIBLE : View.GONE); + } + + public void setChecked(boolean checked){ + this.checked=checked; + view.setChecked(checked); + } + + public enum AccessoryType{ + NONE, + BUTTON, + CHECKBOX, + RADIOBUTTON + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java index 6e7f250bd..5e697f8dd 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/CheckableRelativeLayout.java @@ -7,7 +7,7 @@ import android.widget.Checkable; import android.widget.RelativeLayout; public class CheckableRelativeLayout extends RelativeLayout implements Checkable{ - private boolean checked; + private boolean checked, checkable=true; private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked }; @@ -40,6 +40,10 @@ public class CheckableRelativeLayout extends RelativeLayout implements Checkable setChecked(!checked); } + public void setCheckable(boolean checkable){ + this.checkable=checkable; + } + @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); @@ -52,7 +56,7 @@ public class CheckableRelativeLayout extends RelativeLayout implements Checkable @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info){ super.onInitializeAccessibilityNodeInfo(info); - info.setCheckable(true); + info.setCheckable(checkable); info.setChecked(checked); } } diff --git a/mastodon/src/main/res/layout/display_item_account.xml b/mastodon/src/main/res/layout/display_item_account.xml deleted file mode 100644 index 7d6eec4b3..000000000 --- a/mastodon/src/main/res/layout/display_item_account.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/mastodon/src/main/res/layout/display_item_header.xml b/mastodon/src/main/res/layout/display_item_header.xml index be70fede6..e5d7cc31f 100644 --- a/mastodon/src/main/res/layout/display_item_header.xml +++ b/mastodon/src/main/res/layout/display_item_header.xml @@ -72,7 +72,7 @@ android:layout_toEndOf="@id/avatar" android:singleLine="true" android:ellipsize="end" - android:textAppearance="@style/m3_title_small" + android:textAppearance="@style/m3_body_medium" android:gravity="center_vertical" android:textColor="?colorM3OnSurfaceVariant" tools:text="9h ago · \@Gargron@mastodon.social"/> diff --git a/mastodon/src/main/res/layout/item_account_list.xml b/mastodon/src/main/res/layout/item_account_list.xml index 0df902d96..3f5528c35 100644 --- a/mastodon/src/main/res/layout/item_account_list.xml +++ b/mastodon/src/main/res/layout/item_account_list.xml @@ -1,82 +1,139 @@ - -