From 71dd974d192c53581d3960eac465a62d362f793d Mon Sep 17 00:00:00 2001 From: LucasGGamerM Date: Mon, 4 Mar 2024 16:29:57 -0300 Subject: [PATCH] chore: readd all the new bottom sheet code --- .../android/ExternalShareActivity.java | 2 +- .../android/fragments/HomeFragment.java | 4 +- .../onboarding/AccountActivationFragment.java | 2 +- .../settings/SettingsMainFragment.java | 3 +- .../AccountRestrictionConfirmationSheet.java | 90 +++++++++ .../ui/{ => sheets}/AccountSwitcherSheet.java | 7 +- .../sheets/BlockAccountConfirmationSheet.java | 24 +++ .../DecentralizationExplainerSheet.java | 101 ++++++++++ .../sheets/MuteAccountConfirmationSheet.java | 24 +++ .../ui/sheets/NonMutualPreReplySheet.java | 131 +++++++++++++ .../ui/sheets/OldPostPreReplySheet.java | 23 +++ .../android/ui/sheets/PreReplySheet.java | 54 ++++++ .../android/ui/utils/UiUtils.java | 4 + .../android/ui/views/ProgressBarButton.java | 40 +++- .../ui/views/RippleAnimationTextView.java | 170 +++++++++++++++++ .../src/main/res/drawable/bg_handle_help.xml | 17 ++ .../src/main/res/drawable/ic_badge_24px.xml | 9 + .../layout/sheet_decentralization_info.xml | 178 ++++++++++++++++++ .../res/layout/sheet_restrict_account.xml | 124 ++++++++++++ mastodon/src/main/res/values/attrs.xml | 9 + mastodon/src/main/res/values/strings.xml | 95 +++++++--- 21 files changed, 1075 insertions(+), 36 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountRestrictionConfirmationSheet.java rename mastodon/src/main/java/org/joinmastodon/android/ui/{ => sheets}/AccountSwitcherSheet.java (98%) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/BlockAccountConfirmationSheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DecentralizationExplainerSheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/MuteAccountConfirmationSheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/NonMutualPreReplySheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/OldPostPreReplySheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/sheets/PreReplySheet.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/RippleAnimationTextView.java create mode 100644 mastodon/src/main/res/drawable/bg_handle_help.xml create mode 100644 mastodon/src/main/res/drawable/ic_badge_24px.xml create mode 100644 mastodon/src/main/res/layout/sheet_decentralization_info.xml create mode 100644 mastodon/src/main/res/layout/sheet_restrict_account.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java index 382456256..9c2aef514 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java @@ -13,7 +13,7 @@ import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.ComposeFragment; -import org.joinmastodon.android.ui.AccountSwitcherSheet; +import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet; import org.joinmastodon.android.ui.utils.UiUtils; import org.jsoup.internal.StringUtil; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index 78b8a3394..1217ee88a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -5,8 +5,6 @@ import android.app.Fragment; import android.app.NotificationManager; import android.app.assist.AssistContent; import android.graphics.drawable.RippleDrawable; -import android.content.Intent; -import android.graphics.Outline; import android.os.Build; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -37,7 +35,7 @@ import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestions import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.PaginatedResponse; -import org.joinmastodon.android.ui.AccountSwitcherSheet; +import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet; import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.TabBar; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java index 75325ad4a..c3d36b9f2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/AccountActivationFragment.java @@ -24,7 +24,7 @@ import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.settings.SettingsMainFragment; import org.joinmastodon.android.model.Account; -import org.joinmastodon.android.ui.AccountSwitcherSheet; +import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet; import org.joinmastodon.android.ui.utils.UiUtils; import java.io.File; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java index c126c3986..20142ebbd 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java @@ -1,6 +1,5 @@ package org.joinmastodon.android.fragments.settings; -import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; @@ -18,7 +17,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.viewmodel.ListItem; -import org.joinmastodon.android.ui.AccountSwitcherSheet; +import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.UiUtils; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountRestrictionConfirmationSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountRestrictionConfirmationSheet.java new file mode 100644 index 000000000..accda39a0 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountRestrictionConfirmationSheet.java @@ -0,0 +1,90 @@ +package org.joinmastodon.android.ui.sheets; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.InsetDrawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.ui.drawables.EmptyDrawable; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.ProgressBarButton; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import me.grishka.appkit.utils.V; +import me.grishka.appkit.views.BottomSheet; + +public abstract class AccountRestrictionConfirmationSheet extends BottomSheet{ + private LinearLayout contentWrap; + protected Button cancelBtn; + protected ProgressBarButton confirmBtn, secondaryBtn; + protected TextView titleView, subtitleView; + protected ImageView icon; + protected boolean loading; + + public AccountRestrictionConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){ + super(context); + View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_restrict_account, null); + setContentView(content); + setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface), + UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme()); + + contentWrap=findViewById(R.id.content_wrap); + titleView=findViewById(R.id.title); + subtitleView=findViewById(R.id.text); + cancelBtn=findViewById(R.id.btn_cancel); + confirmBtn=findViewById(R.id.btn_confirm); + secondaryBtn=findViewById(R.id.btn_secondary); + icon=findViewById(R.id.icon); + + contentWrap.setDividerDrawable(new EmptyDrawable(1, V.dp(8))); + contentWrap.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + confirmBtn.setOnClickListener(v->{ + if(loading) + return; + loading=true; + confirmBtn.setProgressBarVisible(true); + confirmCallback.onConfirmed(this::dismiss, ()->{ + confirmBtn.setProgressBarVisible(false); + loading=false; + }); + }); + cancelBtn.setOnClickListener(v->{ + if(!loading) + dismiss(); + }); + } + + protected void addRow(@DrawableRes int icon, CharSequence text){ + TextView tv=new TextView(getContext()); + tv.setTextAppearance(R.style.m3_body_large); + tv.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3OnSurfaceVariant)); + tv.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary))); + tv.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); + tv.setText(text); + InsetDrawable drawable=new InsetDrawable(getContext().getResources().getDrawable(icon, getContext().getTheme()), V.dp(8)); + drawable.setBounds(0, 0, V.dp(40), V.dp(40)); + tv.setCompoundDrawablesRelative(drawable, null, null, null); + tv.setCompoundDrawablePadding(V.dp(16)); + contentWrap.addView(tv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } + + protected void addRow(@DrawableRes int icon, @StringRes int text){ + addRow(icon, getContext().getString(text)); + } + + public interface ConfirmCallback{ + void onConfirmed(Runnable onSuccess, Runnable onError); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountSwitcherSheet.java similarity index 98% rename from mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java rename to mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountSwitcherSheet.java index 8c7b2b73d..460db1ec1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/AccountSwitcherSheet.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountSwitcherSheet.java @@ -1,4 +1,4 @@ -package org.joinmastodon.android.ui; +package org.joinmastodon.android.ui.sheets; import android.annotation.SuppressLint; import android.app.Activity; @@ -8,7 +8,6 @@ import android.graphics.drawable.Animatable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; -import android.text.SpannableStringBuilder; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -27,8 +26,10 @@ import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.SplashFragment; import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment; +import org.joinmastodon.android.ui.ClickableSingleViewRecyclerAdapter; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; +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.joinmastodon.android.ui.views.CheckableRelativeLayout; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/BlockAccountConfirmationSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/BlockAccountConfirmationSheet.java new file mode 100644 index 000000000..3097cf3f5 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/BlockAccountConfirmationSheet.java @@ -0,0 +1,24 @@ +package org.joinmastodon.android.ui.sheets; + +import android.content.Context; +import android.view.View; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.model.Account; + +import androidx.annotation.NonNull; + +public class BlockAccountConfirmationSheet extends AccountRestrictionConfirmationSheet{ + public BlockAccountConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){ + super(context, user, confirmCallback); + titleView.setText(R.string.block_user_confirm_title); + confirmBtn.setText(R.string.do_block); + secondaryBtn.setVisibility(View.GONE); + icon.setImageResource(R.drawable.ic_fluent_shield_24_regular); + subtitleView.setText(user.getDisplayUsername()); + addRow(R.drawable.ic_campaign_24px, R.string.user_can_see_blocked); + addRow(R.drawable.ic_fluent_eye_off_24_regular, R.string.user_cant_see_each_other_posts); + addRow(R.drawable.ic_fluent_mention_24_regular, R.string.you_wont_see_user_mentions); + addRow(R.drawable.ic_fluent_arrow_reply_24_regular, R.string.user_cant_mention_or_follow_you); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DecentralizationExplainerSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DecentralizationExplainerSheet.java new file mode 100644 index 000000000..c15f201b1 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/DecentralizationExplainerSheet.java @@ -0,0 +1,101 @@ +package org.joinmastodon.android.ui.sheets; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; +import org.joinmastodon.android.ui.Snackbar; +import org.joinmastodon.android.ui.text.LinkSpan; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.RippleAnimationTextView; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; +import org.jsoup.select.NodeVisitor; + +import androidx.annotation.NonNull; +import me.grishka.appkit.views.BottomSheet; + +public class DecentralizationExplainerSheet extends BottomSheet{ + private final String handleStr; + + public DecentralizationExplainerSheet(@NonNull Context context, String accountID, Account account){ + super(context); + View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_decentralization_info, null); + setContentView(content); + setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface), + UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme()); + + TextView handleTitle=findViewById(R.id.handle_title); + RippleAnimationTextView handle=findViewById(R.id.handle); + TextView usernameExplanation=findViewById(R.id.username_text); + TextView serverExplanation=findViewById(R.id.server_text); + TextView handleExplanation=findViewById(R.id.handle_explanation); + findViewById(R.id.btn_cancel).setOnClickListener(v->dismiss()); + + String domain=account.getDomain(); + if(TextUtils.isEmpty(domain)) + domain=AccountSessionManager.get(accountID).domain; + handleStr="@"+account.username+"@"+domain; + boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account); + + handleTitle.setText(isSelf ? R.string.handle_title_own : R.string.handle_title); + handle.setText(handleStr); + usernameExplanation.setText(isSelf ? R.string.handle_username_explanation_own : R.string.handle_username_explanation); + serverExplanation.setText(isSelf ? R.string.handle_server_explanation_own : R.string.handle_server_explanation); + + String explanation=context.getString(isSelf ? R.string.handle_explanation_own : R.string.handle_explanation); + SpannableStringBuilder ssb=new SpannableStringBuilder(); + Jsoup.parseBodyFragment(explanation).body().traverse(new NodeVisitor(){ + private int spanStart; + @Override + public void head(Node node, int depth){ + if(node instanceof TextNode tn){ + ssb.append(tn.text()); + }else if(node instanceof Element){ + spanStart=ssb.length(); + } + } + + @Override + public void tail(Node node, int depth){ + if(node instanceof Element){ + ssb.setSpan(new LinkSpan("", DecentralizationExplainerSheet.this::showActivityPubAlert, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + }); + handleExplanation.setText(ssb); + + findViewById(R.id.handle_wrap).setOnClickListener(v->{ + context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, handleStr)); + if(UiUtils.needShowClipboardToast()){ + new Snackbar.Builder(context) + .setText(R.string.handle_copied) + .show(); + } + }); + String _domain=domain; + findViewById(R.id.username_row).setOnClickListener(v->handle.animate(1, account.username.length()+1)); + findViewById(R.id.server_row).setOnClickListener(v->handle.animate(handleStr.length()-_domain.length(), handleStr.length())); + } + + private void showActivityPubAlert(LinkSpan s){ + new M3AlertDialogBuilder(getContext()) + .setTitle(R.string.what_is_activitypub_title) + .setMessage(R.string.what_is_activitypub) + .setPositiveButton(R.string.ok, null) + .show(); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/MuteAccountConfirmationSheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/MuteAccountConfirmationSheet.java new file mode 100644 index 000000000..71c845d9b --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/MuteAccountConfirmationSheet.java @@ -0,0 +1,24 @@ +package org.joinmastodon.android.ui.sheets; + +import android.content.Context; +import android.view.View; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.model.Account; + +import androidx.annotation.NonNull; + +public class MuteAccountConfirmationSheet extends AccountRestrictionConfirmationSheet{ + public MuteAccountConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback){ + super(context, user, confirmCallback); + titleView.setText(R.string.mute_user_confirm_title); + confirmBtn.setText(R.string.do_mute); + secondaryBtn.setVisibility(View.GONE); + icon.setImageResource(R.drawable.ic_fluent_speaker_mute_24_regular); + subtitleView.setText(user.getDisplayUsername()); + addRow(R.drawable.ic_campaign_24px, R.string.user_wont_know_muted); + addRow(R.drawable.ic_fluent_eye_off_24_regular, R.string.user_can_still_see_your_posts); + addRow(R.drawable.ic_fluent_mention_24_regular, R.string.you_wont_see_user_mentions); + addRow(R.drawable.ic_fluent_arrow_reply_24_regular, R.string.user_can_mention_and_follow_you); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/NonMutualPreReplySheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/NonMutualPreReplySheet.java new file mode 100644 index 000000000..9e358be17 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/NonMutualPreReplySheet.java @@ -0,0 +1,131 @@ +package org.joinmastodon.android.ui.sheets; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.session.AccountSessionManager; +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.UiUtils; + +import androidx.annotation.NonNull; +import me.grishka.appkit.imageloader.ViewImageLoader; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +import me.grishka.appkit.utils.V; + +public class NonMutualPreReplySheet extends PreReplySheet{ + private boolean fullBioShown=false; + + @SuppressLint("DefaultLocale") + public NonMutualPreReplySheet(@NonNull Context context, ResultListener resultListener, Account account, String accountID){ + super(context, resultListener); + icon.setImageResource(R.drawable.ic_waving_hand_24px); + title.setText(R.string.non_mutual_sheet_title); + text.setText(R.string.non_mutual_sheet_text); + + LinearLayout userInfo=new LinearLayout(context); + userInfo.setOrientation(LinearLayout.HORIZONTAL); + userInfo.setBackgroundResource(R.drawable.bg_user_info); + UiUtils.setAllPaddings(userInfo, 12); + + ImageView ava=new ImageView(context); + ava.setScaleType(ImageView.ScaleType.CENTER_CROP); + ava.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + ava.setOutlineProvider(OutlineProviders.roundedRect(12)); + ava.setClipToOutline(true); + ava.setForeground(context.getResources().getDrawable(R.drawable.fg_user_info_ava, context.getTheme())); + userInfo.addView(ava, UiUtils.makeLayoutParams(56, 56, 0, 0, 12, 0)); + ViewImageLoader.loadWithoutAnimation(ava, context.getResources().getDrawable(R.drawable.image_placeholder), new UrlImageLoaderRequest(account.avatarStatic, V.dp(56), V.dp(56))); + + LinearLayout nameAndFields=new LinearLayout(context); + nameAndFields.setOrientation(LinearLayout.VERTICAL); + nameAndFields.setMinimumHeight(V.dp(56)); + nameAndFields.setGravity(Gravity.CENTER_VERTICAL); + userInfo.addView(nameAndFields, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + TextView name=new TextView(context); + name.setSingleLine(); + name.setEllipsize(TextUtils.TruncateAt.END); + name.setTextAppearance(R.style.m3_title_medium); + name.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface)); + if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames){ + name.setText(HtmlParser.parseCustomEmoji(account.displayName, account.emojis)); + UiUtils.loadCustomEmojiInTextView(name); + }else{ + name.setText(account.displayName); + } + name.setGravity(Gravity.CENTER_VERTICAL | Gravity.START); + nameAndFields.addView(name, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(24))); + if(!TextUtils.isEmpty(account.note)){ + CharSequence strippedBio=HtmlParser.parseCustomEmoji(HtmlParser.stripAndRemoveInvisibleSpans(account.note), account.emojis); + TextView bioShort=new TextView(context); + bioShort.setTextAppearance(R.style.m3_body_medium); + bioShort.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3Secondary)); + bioShort.setMaxLines(2); + bioShort.setEllipsize(TextUtils.TruncateAt.END); + bioShort.setText(strippedBio); + nameAndFields.addView(bioShort, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + TextView bioFull=new TextView(context); + bioFull.setTextAppearance(R.style.m3_body_medium); + bioFull.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3Secondary)); + bioFull.setText(strippedBio); + bioFull.setVisibility(View.GONE); + nameAndFields.addView(bioFull, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + nameAndFields.setOnClickListener(v->{ + UiUtils.beginLayoutTransition((ViewGroup) getWindow().getDecorView()); + fullBioShown=!fullBioShown; + if(fullBioShown){ + bioFull.setVisibility(View.VISIBLE); + bioShort.setVisibility(View.GONE); + }else{ + bioFull.setVisibility(View.GONE); + bioShort.setVisibility(View.VISIBLE); + } + }); + UiUtils.loadCustomEmojiInTextView(bioShort); + UiUtils.loadCustomEmojiInTextView(bioFull); + }else{ + TextView username=new TextView(context); + username.setTextAppearance(R.style.m3_body_medium); + username.setTextColor(UiUtils.getThemeColor(context, R.attr.colorM3Secondary)); + username.setSingleLine(); + username.setEllipsize(TextUtils.TruncateAt.END); + username.setText(account.getDisplayUsername()); + username.setGravity(Gravity.CENTER_VERTICAL | Gravity.START); + nameAndFields.addView(username, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(20))); + } + + contentWrap.addView(userInfo, UiUtils.makeLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, 0, 8)); + + for(int i=0;i<3;i++){ + View item=context.getSystemService(LayoutInflater.class).inflate(R.layout.item_other_numbered_rule, contentWrap, false); + TextView number=item.findViewById(R.id.number); + number.setText(String.format("%d", i+1)); + TextView title=item.findViewById(R.id.title); + TextView text=item.findViewById(R.id.text); + title.setText(switch(i){ + case 0 -> R.string.non_mutual_title1; + case 1 -> R.string.non_mutual_title2; + case 2 -> R.string.non_mutual_title3; + default -> throw new IllegalStateException("Unexpected value: "+i); + }); + text.setText(switch(i){ + case 0 -> R.string.non_mutual_text1; + case 1 -> R.string.non_mutual_text2; + case 2 -> R.string.non_mutual_text3; + default -> throw new IllegalStateException("Unexpected value: "+i); + }); + contentWrap.addView(item); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/OldPostPreReplySheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/OldPostPreReplySheet.java new file mode 100644 index 000000000..644ae1865 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/OldPostPreReplySheet.java @@ -0,0 +1,23 @@ +package org.joinmastodon.android.ui.sheets; + +import android.content.Context; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.model.Status; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +import androidx.annotation.NonNull; + +public class OldPostPreReplySheet extends PreReplySheet{ + public OldPostPreReplySheet(@NonNull Context context, ResultListener resultListener, Status status){ + super(context, resultListener); + int months=(int)status.createdAt.atZone(ZoneId.systemDefault()).until(ZonedDateTime.now(), ChronoUnit.MONTHS); + String monthsStr=months>24 ? context.getString(R.string.more_than_two_years) : context.getResources().getQuantityString(R.plurals.x_months, months, months); + title.setText(context.getString(R.string.old_post_sheet_title, monthsStr)); + text.setText(R.string.old_post_sheet_text); + icon.setImageResource(R.drawable.ic_fluent_history_24_regular); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/PreReplySheet.java b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/PreReplySheet.java new file mode 100644 index 000000000..cae173a5e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/sheets/PreReplySheet.java @@ -0,0 +1,54 @@ +package org.joinmastodon.android.ui.sheets; + +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.ui.utils.UiUtils; + +import androidx.annotation.NonNull; +import me.grishka.appkit.views.BottomSheet; + +public abstract class PreReplySheet extends BottomSheet{ + protected ImageView icon; + protected TextView title, text; + protected Button gotItButton, dontRemindButton; + protected LinearLayout contentWrap; + + public PreReplySheet(@NonNull Context context, ResultListener resultListener){ + super(context); + + View content=context.getSystemService(LayoutInflater.class).inflate(R.layout.sheet_pre_reply, null); + setContentView(content); + + setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Surface), + UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme()); + + icon=findViewById(R.id.icon); + title=findViewById(R.id.title); + text=findViewById(R.id.text); + gotItButton=findViewById(R.id.btn_got_it); + dontRemindButton=findViewById(R.id.btn_dont_remind_again); + contentWrap=findViewById(R.id.content_wrap); + + gotItButton.setOnClickListener(v->{ + dismiss(); + resultListener.onButtonClicked(false); + }); + dontRemindButton.setOnClickListener(v->{ + dismiss(); + resultListener.onButtonClicked(true); + }); + } + + @FunctionalInterface + public interface ResultListener{ + void onButtonClicked(boolean notAgain); + } +} 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 12d2b501c..f8c25b682 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 @@ -1847,6 +1847,10 @@ public class UiUtils { } } + public static boolean needShowClipboardToast(){ + return Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2; + } + public static void setAllPaddings(View view, int paddingDp){ int pad=V.dp(paddingDp); view.setPadding(pad, pad, pad, pad); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ProgressBarButton.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ProgressBarButton.java index ac0e4c9da..ae9497bdc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ProgressBarButton.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ProgressBarButton.java @@ -1,23 +1,42 @@ package org.joinmastodon.android.ui.views; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; import android.widget.Button; +import android.widget.ProgressBar; + +import org.joinmastodon.android.R; public class ProgressBarButton extends Button{ private boolean textVisible=true; + private ProgressBar progressBar; + private int progressBarID; public ProgressBarButton(Context context){ - super(context); + this(context, null); } public ProgressBarButton(Context context, AttributeSet attrs){ - super(context, attrs); + this(context, attrs, 0); } - public ProgressBarButton(Context context, AttributeSet attrs, int defStyleAttr){ - super(context, attrs, defStyleAttr); + public ProgressBarButton(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.ProgressBarButton); + progressBarID=ta.getResourceId(R.styleable.ProgressBarButton_progressBar, 0); + ta.recycle(); + } + + @Override + protected void onAttachedToWindow(){ + super.onAttachedToWindow(); + if(progressBarID!=0){ + progressBar=((ViewGroup)getParent()).findViewById(progressBarID); + } } public void setTextVisible(boolean textVisible){ @@ -29,6 +48,19 @@ public class ProgressBarButton extends Button{ return textVisible; } + public void setProgressBarVisible(boolean visible){ + if(progressBar==null) + throw new IllegalStateException("progressBar is not set"); + if(visible){ + setTextVisible(false); + progressBar.setIndeterminateTintList(getTextColors()); + progressBar.setVisibility(View.VISIBLE); + }else{ + setTextVisible(true); + progressBar.setVisibility(View.GONE); + } + } + @Override protected void onDraw(Canvas canvas){ if(textVisible){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/RippleAnimationTextView.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/RippleAnimationTextView.java new file mode 100644 index 000000000..b44db321a --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/RippleAnimationTextView.java @@ -0,0 +1,170 @@ +package org.joinmastodon.android.ui.views; + +import android.animation.ArgbEvaluator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Layout; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.dynamicanimation.animation.FloatValueHolder; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; +import me.grishka.appkit.utils.CustomViewHelper; + +public class RippleAnimationTextView extends TextView implements CustomViewHelper{ + private final Paint animationPaint=new Paint(Paint.ANTI_ALIAS_FLAG); + private CharacterAnimationState[] charStates; + private final ArgbEvaluator colorEvaluator=new ArgbEvaluator(); + private int runningAnimCount=0; + private Runnable[] delayedAnimations1, delayedAnimations2; + + public RippleAnimationTextView(Context context){ + this(context, null); + } + + public RippleAnimationTextView(Context context, AttributeSet attrs){ + this(context, attrs, 0); + } + + public RippleAnimationTextView(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + } + + @Override + protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter){ + super.onTextChanged(text, start, lengthBefore, lengthAfter); + if(charStates!=null){ + for(CharacterAnimationState state:charStates){ + state.colorAnimation.cancel(); + state.shadowAnimation.cancel(); + state.scaleAnimation.cancel(); + } + for(Runnable r:delayedAnimations1){ + if(r!=null) + removeCallbacks(r); + } + for(Runnable r:delayedAnimations2){ + if(r!=null) + removeCallbacks(r); + } + } + charStates=new CharacterAnimationState[lengthAfter]; + delayedAnimations1=new Runnable[lengthAfter]; + delayedAnimations2=new Runnable[lengthAfter]; + } + + @Override + protected void onDraw(Canvas canvas){ + if(runningAnimCount==0 && !areThereDelayedAnimations()){ + super.onDraw(canvas); + return; + } + Layout layout=getLayout(); + animationPaint.set(getPaint()); + CharSequence text=layout.getText(); + for(int i=0;i{ + if(!state.colorAnimation.isRunning()) + runningAnimCount++; + state.colorAnimation.animateToFinalPosition(1f); + if(!state.shadowAnimation.isRunning()) + runningAnimCount++; + state.shadowAnimation.animateToFinalPosition(0.3f); + if(!state.scaleAnimation.isRunning()) + runningAnimCount++; + state.scaleAnimation.animateToFinalPosition(1.2f); + invalidate(); + + if(delayedAnimations1[finalI]!=null) + removeCallbacks(delayedAnimations1[finalI]); + if(delayedAnimations2[finalI]!=null) + removeCallbacks(delayedAnimations2[finalI]); + Runnable delay1=()->{ + if(!state.colorAnimation.isRunning()) + runningAnimCount++; + state.colorAnimation.animateToFinalPosition(0f); + if(!state.shadowAnimation.isRunning()) + runningAnimCount++; + state.shadowAnimation.animateToFinalPosition(0f); + invalidate(); + delayedAnimations1[finalI]=null; + }; + Runnable delay2=()->{ + if(!state.scaleAnimation.isRunning()) + runningAnimCount++; + state.scaleAnimation.animateToFinalPosition(1f); + delayedAnimations2[finalI]=null; + }; + delayedAnimations1[finalI]=delay1; + delayedAnimations2[finalI]=delay2; + postOnAnimationDelayed(delay1, 2000); + postOnAnimationDelayed(delay2, 100); + }, 20L*(i-startIndex)); + } + } + + private boolean areThereDelayedAnimations(){ + for(Runnable r:delayedAnimations1){ + if(r!=null) + return true; + } + for(Runnable r:delayedAnimations2){ + if(r!=null) + return true; + } + return false; + } + + private class CharacterAnimationState extends FloatValueHolder{ + private final SpringAnimation scaleAnimation, colorAnimation, shadowAnimation; + private final FloatValueHolder scale=new FloatValueHolder(1), color=new FloatValueHolder(), shadowAlpha=new FloatValueHolder(); + + public CharacterAnimationState(){ + scaleAnimation=new SpringAnimation(scale); + colorAnimation=new SpringAnimation(color); + shadowAnimation=new SpringAnimation(shadowAlpha); + setupSpring(scaleAnimation); + setupSpring(colorAnimation); + setupSpring(shadowAnimation); + } + + private void setupSpring(SpringAnimation anim){ + anim.setMinimumVisibleChange(0.01f); + anim.setSpring(new SpringForce().setStiffness(500f).setDampingRatio(0.175f)); + anim.addEndListener((animation, canceled, value, velocity)->runningAnimCount--); + } + } +} diff --git a/mastodon/src/main/res/drawable/bg_handle_help.xml b/mastodon/src/main/res/drawable/bg_handle_help.xml new file mode 100644 index 000000000..ebfec2f2a --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_handle_help.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_badge_24px.xml b/mastodon/src/main/res/drawable/ic_badge_24px.xml new file mode 100644 index 000000000..489eff0b5 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_badge_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/layout/sheet_decentralization_info.xml b/mastodon/src/main/res/layout/sheet_decentralization_info.xml new file mode 100644 index 000000000..9460b1a9e --- /dev/null +++ b/mastodon/src/main/res/layout/sheet_decentralization_info.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +