From e253d8f4f3b3e8479f98c1834436260afa9fe165 Mon Sep 17 00:00:00 2001 From: Grishka Date: Thu, 11 May 2023 03:45:23 +0300 Subject: [PATCH] Pull user row into a separate view holder & update its design --- .../account_list/BaseAccountListFragment.java | 229 +--------------- .../PaginatedAccountListFragment.java | 3 +- .../model/viewmodel/AccountViewModel.java | 34 +++ .../android/ui/text/CustomEmojiSpan.java | 5 +- .../android/ui/text/HtmlParser.java | 8 + .../ui/viewholders/AccountViewHolder.java | 256 ++++++++++++++++++ .../main/res/drawable/ic_check_small_16px.xml | 9 + .../src/main/res/drawable/ic_help_16px.xml | 9 + .../main/res/drawable/image_placeholder.xml | 4 + .../src/main/res/layout/item_account_list.xml | 61 +++-- mastodon/src/main/res/values/strings.xml | 1 + 11 files changed, 378 insertions(+), 241 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java create mode 100644 mastodon/src/main/res/drawable/ic_check_small_16px.xml create mode 100644 mastodon/src/main/res/drawable/ic_help_16px.xml create mode 100644 mastodon/src/main/res/drawable/image_placeholder.xml 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 0b7b81f6..9c39219c 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 @@ -1,39 +1,21 @@ package org.joinmastodon.android.fragments.account_list; -import android.app.ProgressDialog; -import android.content.Intent; import android.content.res.Configuration; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.PopupMenu; -import android.widget.TextView; import android.widget.Toolbar; -import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; -import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; -import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.MastodonRecyclerFragment; -import org.joinmastodon.android.fragments.ProfileFragment; -import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; -import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Relationship; +import org.joinmastodon.android.model.viewmodel.AccountViewModel; import org.joinmastodon.android.ui.DividerItemDecoration; -import org.joinmastodon.android.ui.OutlineProviders; -import org.joinmastodon.android.ui.text.HtmlParser; -import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.UiUtils; -import org.parceler.Parcels; +import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import java.util.ArrayList; import java.util.HashMap; @@ -44,19 +26,15 @@ import java.util.stream.Collectors; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import me.grishka.appkit.Nav; import me.grishka.appkit.api.APIRequest; import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter; -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.BindableViewHolder; import me.grishka.appkit.utils.V; import me.grishka.appkit.views.UsableRecyclerView; -public abstract class BaseAccountListFragment extends MastodonRecyclerFragment{ +public abstract class BaseAccountListFragment extends MastodonRecyclerFragment{ protected HashMap relationships=new HashMap<>(); protected String accountID; protected ArrayList> relationshipsRequests=new ArrayList<>(); @@ -72,7 +50,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment d, boolean more){ + protected void onDataLoaded(List d, boolean more){ if(refreshing){ relationships.clear(); } @@ -89,7 +67,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment accounts){ + protected void loadRelationships(List accounts){ Set ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet()); GetAccountRelationships req=new GetAccountRelationships(ids); relationshipsRequests.add(req); @@ -125,9 +103,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment implements ImageLoaderViewHolder, UsableRecyclerView.Clickable, UsableRecyclerView.LongClickable{ - private final TextView name, username; - private final ImageView avatar; - private final Button button; - private final PopupMenu contextMenu; - private final View menuAnchor; - - public AccountViewHolder(){ - super(getActivity(), R.layout.item_account_list, list); - name=findViewById(R.id.name); - username=findViewById(R.id.username); - avatar=findViewById(R.id.avatar); - button=findViewById(R.id.button); - menuAnchor=findViewById(R.id.menu_anchor); - - avatar.setOutlineProvider(OutlineProviders.roundedRect(12)); - avatar.setClipToOutline(true); - - button.setOnClickListener(this::onButtonClick); - - contextMenu=new PopupMenu(getActivity(), menuAnchor); - contextMenu.inflate(R.menu.profile); - contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected); - } - - @Override - public void onBind(AccountItem item){ - name.setText(item.parsedName); - username.setText("@"+item.account.acct); - bindRelationship(); - } - - public void bindRelationship(){ - Relationship rel=relationships.get(item.account.id); - if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){ - button.setVisibility(View.GONE); - }else{ - button.setVisibility(View.VISIBLE); - UiUtils.setRelationshipToActionButton(rel, button); - } - } - - @Override - public void setImage(int index, Drawable image){ - if(index==0){ - avatar.setImageDrawable(image); - }else{ - item.emojiHelper.setImageDrawable(index-1, image); - name.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); - } - - @Override - public boolean onLongClick(){ - return false; - } - - @Override - public boolean onLongClick(float x, float y){ - Relationship relationship=relationships.get(item.account.id); - if(relationship==null) - return false; - Menu menu=contextMenu.getMenu(); - Account account=item.account; - - menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername())); - menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername())); - menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername())); - menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername())); - MenuItem hideBoosts=menu.findItem(R.id.hide_boosts); - if(relationship.following){ - hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername())); - hideBoosts.setVisible(true); - }else{ - hideBoosts.setVisible(false); - } - MenuItem blockDomain=menu.findItem(R.id.block_domain); - if(!account.isLocal()){ - blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain())); - blockDomain.setVisible(true); - }else{ - blockDomain.setVisible(false); - } - - menuAnchor.setTranslationX(x); - menuAnchor.setTranslationY(y); - contextMenu.show(); - - return true; - } - - private void onButtonClick(View v){ - ProgressDialog progress=new ProgressDialog(getActivity()); - progress.setMessage(getString(R.string.loading)); - progress.setCancelable(false); - UiUtils.performAccountAction(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); - bindRelationship(); - }); - } - - private boolean onContextMenuItemSelected(MenuItem item){ - Relationship relationship=relationships.get(this.item.account.id); - if(relationship==null) - return false; - Account account=this.item.account; - - int id=item.getItemId(); - if(id==R.id.share){ - Intent intent=new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, account.url); - startActivity(Intent.createChooser(intent, item.getTitle())); - }else if(id==R.id.mute){ - UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship); - }else if(id==R.id.block){ - UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship); - }else if(id==R.id.report){ - Bundle args=new Bundle(); - args.putString("account", accountID); - args.putParcelable("reportAccount", Parcels.wrap(account)); - Nav.go(getActivity(), ReportReasonChoiceFragment.class, args); - }else if(id==R.id.open_in_browser){ - UiUtils.launchWebBrowser(getActivity(), account.url); - }else if(id==R.id.block_domain){ - UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{ - relationship.domainBlocking=!relationship.domainBlocking; - bindRelationship(); - }); - }else if(id==R.id.hide_boosts){ - new SetAccountFollowed(account.id, true, !relationship.showingReblogs) - .setCallback(new Callback<>(){ - @Override - public void onSuccess(Relationship result){ - relationships.put(AccountViewHolder.this.item.account.id, result); - bindRelationship(); - } - - @Override - public void onError(ErrorResponse error){ - error.showToast(getActivity()); - } - }) - .wrapProgress(getActivity(), R.string.loading, false) - .exec(accountID); - } - return true; - } - - private void updateRelationship(Relationship r){ - relationships.put(item.account.id, r); - bindRelationship(); - } - } - - protected static class AccountItem{ - public final Account account; - public final ImageLoaderRequest avaRequest; - public final CustomEmojiHelper emojiHelper; - public final CharSequence parsedName; - - public AccountItem(Account account){ - this.account=account; - avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50)); - emojiHelper=new CustomEmojiHelper(); - emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis)); - } - } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/PaginatedAccountListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/PaginatedAccountListFragment.java index a89a7dcd..0c4a0c43 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/PaginatedAccountListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/PaginatedAccountListFragment.java @@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments.account_list; import org.joinmastodon.android.api.requests.HeaderPaginationRequest; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.HeaderPaginationList; +import org.joinmastodon.android.model.viewmodel.AccountViewModel; import java.util.stream.Collectors; @@ -23,7 +24,7 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme nextMaxID=result.nextPageUri.getQueryParameter("max_id"); else nextMaxID=null; - onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null); + onDataLoaded(result.stream().map(AccountViewModel::new).collect(Collectors.toList()), nextMaxID!=null); } }) .exec(accountID); 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 new file mode 100644 index 00000000..3472cc08 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java @@ -0,0 +1,34 @@ +package org.joinmastodon.android.model.viewmodel; + +import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.AccountField; +import org.joinmastodon.android.ui.text.HtmlParser; +import org.joinmastodon.android.ui.utils.CustomEmojiHelper; + +import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +import me.grishka.appkit.utils.V; + +public class AccountViewModel{ + public final Account account; + public final ImageLoaderRequest avaRequest; + public final CustomEmojiHelper emojiHelper; + public final CharSequence parsedName; + public final String verifiedLink; + + public AccountViewModel(Account account){ + this.account=account; + avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50)); + emojiHelper=new CustomEmojiHelper(); + emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis)); + String verifiedLink=null; + for(AccountField fld:account.fields){ + if(fld.verifiedAt!=null){ + verifiedLink=HtmlParser.stripAndRemoveInvisibleSpans(fld.value); + break; + } + } + this.verifiedLink=verifiedLink; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/CustomEmojiSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/CustomEmojiSpan.java index 16cd7303..b6732b31 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/CustomEmojiSpan.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/CustomEmojiSpan.java @@ -31,7 +31,10 @@ public class CustomEmojiSpan extends ReplacementSpan{ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){ int size=Math.round(paint.descent()-paint.ascent()); if(drawable==null){ - canvas.drawRect(x, top, x+size, top+size, paint); + int alpha=paint.getAlpha(); + paint.setAlpha(alpha >> 1); + canvas.drawRoundRect(x, top, x+size, top+size, V.dp(2), V.dp(2), paint); + paint.setAlpha(alpha); }else{ // AnimatedImageDrawable doesn't like when its bounds don't start at (0, 0) Rect bounds=drawable.getBounds(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java index fdd97698..3de79f4c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java @@ -12,6 +12,7 @@ import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Mention; import org.joinmastodon.android.ui.utils.UiUtils; import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.nodes.TextNode; @@ -191,6 +192,13 @@ public class HtmlParser{ return Jsoup.clean(html, Safelist.none()); } + public static String stripAndRemoveInvisibleSpans(String html){ + Document doc=Jsoup.parseBodyFragment(html); + doc.body().select("span.invisible").remove(); + Cleaner cleaner=new Cleaner(Safelist.none()); + return cleaner.clean(doc).body().html(); + } + public static CharSequence parseLinks(String text){ Matcher matcher=URL_PATTERN.matcher(text); if(!matcher.find()) // Return the original string if there are no URLs 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 new file mode 100644 index 00000000..f8901178 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java @@ -0,0 +1,256 @@ +package org.joinmastodon.android.ui.viewholders; + +import android.annotation.SuppressLint; +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; +import android.text.style.TypefaceSpan; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; +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.parceler.Parcels; + +import java.util.HashMap; +import java.util.Objects; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; +import me.grishka.appkit.imageloader.ImageLoaderViewHolder; +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 ImageView avatar; + private final Button button; + private final PopupMenu contextMenu; + private final View menuAnchor; + private final TypefaceSpan mediumSpan=new TypefaceSpan("sans-serif-medium"); + + private final String accountID; + private final Fragment fragment; + private final HashMap relationships; + + public AccountViewHolder(Fragment fragment, ViewGroup list, HashMap relationships){ + super(fragment.getActivity(), R.layout.item_account_list, list); + this.fragment=fragment; + this.accountID=Objects.requireNonNull(fragment.getArguments().getString("account")); + this.relationships=relationships; + + name=findViewById(R.id.name); + username=findViewById(R.id.username); + avatar=findViewById(R.id.avatar); + button=findViewById(R.id.button); + menuAnchor=findViewById(R.id.menu_anchor); + followers=findViewById(R.id.followers_count); + verifiedLink=findViewById(R.id.verified_link); + + avatar.setOutlineProvider(OutlineProviders.roundedRect(16)); + avatar.setClipToOutline(true); + + button.setOnClickListener(this::onButtonClick); + + contextMenu=new PopupMenu(fragment.getActivity(), menuAnchor); + contextMenu.inflate(R.menu.profile); + contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected); + } + + @SuppressLint("SetTextI18n") + @Override + 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); + } + 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(); + } + + public void bindRelationship(){ + Relationship rel=relationships.get(item.account.id); + if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){ + button.setVisibility(View.GONE); + }else{ + button.setVisibility(View.VISIBLE); + UiUtils.setRelationshipToActionButtonM3(rel, button); + } + } + + @Override + public void setImage(int index, Drawable image){ + if(index==0){ + avatar.setImageDrawable(image); + }else{ + item.emojiHelper.setImageDrawable(index-1, image); + name.invalidate(); + } + + if(image instanceof Animatable a && !a.isRunning()) + a.start(); + } + + @Override + public void clearImage(int index){ + if(index==0){ + avatar.setImageResource(R.drawable.image_placeholder); + }else{ + setImage(index, null); + } + } + + @Override + public void onClick(){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("profileAccount", Parcels.wrap(item.account)); + Nav.go(fragment.getActivity(), ProfileFragment.class, args); + } + + @Override + public boolean onLongClick(){ + return false; + } + + @Override + public boolean onLongClick(float x, float y){ + Relationship relationship=relationships.get(item.account.id); + if(relationship==null) + return false; + 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.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())); + MenuItem hideBoosts=menu.findItem(R.id.hide_boosts); + if(relationship.following){ + hideBoosts.setTitle(fragment.getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername())); + hideBoosts.setVisible(true); + }else{ + hideBoosts.setVisible(false); + } + MenuItem blockDomain=menu.findItem(R.id.block_domain); + if(!account.isLocal()){ + blockDomain.setTitle(fragment.getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain())); + blockDomain.setVisible(true); + }else{ + blockDomain.setVisible(false); + } + + menuAnchor.setTranslationX(x); + menuAnchor.setTranslationY(y); + contextMenu.show(); + + return true; + } + + private void onButtonClick(View v){ + 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); + bindRelationship(); + }); + } + + private boolean onContextMenuItemSelected(MenuItem item){ + Relationship relationship=relationships.get(this.item.account.id); + if(relationship==null) + return false; + Account account=this.item.account; + + int id=item.getItemId(); + if(id==R.id.share){ + Intent intent=new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, account.url); + fragment.startActivity(Intent.createChooser(intent, item.getTitle())); + }else if(id==R.id.mute){ + UiUtils.confirmToggleMuteUser(fragment.getActivity(), accountID, account, relationship.muting, this::updateRelationship); + }else if(id==R.id.block){ + UiUtils.confirmToggleBlockUser(fragment.getActivity(), accountID, account, relationship.blocking, this::updateRelationship); + }else if(id==R.id.report){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("reportAccount", Parcels.wrap(account)); + Nav.go(fragment.getActivity(), ReportReasonChoiceFragment.class, args); + }else if(id==R.id.open_in_browser){ + UiUtils.launchWebBrowser(fragment.getActivity(), account.url); + }else if(id==R.id.block_domain){ + UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{ + relationship.domainBlocking=!relationship.domainBlocking; + bindRelationship(); + }); + }else if(id==R.id.hide_boosts){ + new SetAccountFollowed(account.id, true, !relationship.showingReblogs) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(Relationship result){ + relationships.put(AccountViewHolder.this.item.account.id, result); + bindRelationship(); + } + + @Override + public void onError(ErrorResponse error){ + error.showToast(fragment.getActivity()); + } + }) + .wrapProgress(fragment.getActivity(), R.string.loading, false) + .exec(accountID); + } + return true; + } + + private void updateRelationship(Relationship r){ + relationships.put(item.account.id, r); + bindRelationship(); + } +} diff --git a/mastodon/src/main/res/drawable/ic_check_small_16px.xml b/mastodon/src/main/res/drawable/ic_check_small_16px.xml new file mode 100644 index 00000000..44bf1680 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_check_small_16px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_help_16px.xml b/mastodon/src/main/res/drawable/ic_help_16px.xml new file mode 100644 index 00000000..7890fdb8 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_help_16px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/image_placeholder.xml b/mastodon/src/main/res/drawable/image_placeholder.xml new file mode 100644 index 00000000..7cc3cf14 --- /dev/null +++ b/mastodon/src/main/res/drawable/image_placeholder.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/item_account_list.xml b/mastodon/src/main/res/layout/item_account_list.xml index f8a0fc18..0df902d9 100644 --- a/mastodon/src/main/res/layout/item_account_list.xml +++ b/mastodon/src/main/res/layout/item_account_list.xml @@ -2,55 +2,82 @@