From bcb3e217cd7b6f6addf6129b46c6e40c494facd9 Mon Sep 17 00:00:00 2001 From: Grishka Date: Thu, 26 Jan 2023 01:38:29 +0300 Subject: [PATCH] More onboarding updates --- .../GoogleMadeMeAddThisFragment.java | 38 ++++-- .../InstanceCatalogSignupFragment.java | 51 +------- .../onboarding/InstanceRulesFragment.java | 21 +++- .../android/ui/utils/UiUtils.java | 8 ++ .../utils/ElevationOnScrollListener.java | 116 ++++++++++++++++++ .../res/layout/fragment_onboarding_rules.xml | 5 +- .../res/layout/item_privacy_policy_link.xml | 41 +++++-- mastodon/src/main/res/values/strings.xml | 5 +- 8 files changed, 214 insertions(+), 71 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/utils/ElevationOnScrollListener.java diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java index 3cf0767c..f4793b8c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java @@ -5,6 +5,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -19,6 +20,7 @@ import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.utils.ElevationOnScrollListener; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.parceler.Parcels; @@ -42,6 +44,7 @@ import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.V; +import me.grishka.appkit.views.FragmentRootLinearLayout; import me.grishka.appkit.views.UsableRecyclerView; import okhttp3.Call; import okhttp3.Callback; @@ -58,6 +61,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ private ArrayList items=new ArrayList<>(); private Call currentRequest; private ItemsAdapter itemsAdapter; + private ElevationOnScrollListener onScrollListener; @Override public void onCreate(Bundle savedInstanceState){ @@ -72,7 +76,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)); instance=Parcels.unwrap(getArguments().getParcelable("instance")); - items.add(new Item("Mastodon for Android Privacy Policy", "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png")); + items.add(new Item("Mastodon for Android Privacy Policy", getString(R.string.privacy_policy_explanation), "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png")); loadServerPrivacyPolicy(); } @@ -93,18 +97,24 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ list.setLayoutManager(new LinearLayoutManager(getActivity())); View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false); TextView text=headerView.findViewById(R.id.text); - text.setText(R.string.privacy_policy_subtitle); + text.setText(getString(R.string.privacy_policy_subtitle, instance.uri)); adapter=new MergeRecyclerAdapter(); adapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); adapter.addAdapter(itemsAdapter=new ItemsAdapter()); list.setAdapter(adapter); - list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST)); btn=view.findViewById(R.id.btn_next); btn.setOnClickListener(v->onButtonClick()); buttonBar=view.findViewById(R.id.button_bar); + Button backBtn=view.findViewById(R.id.btn_back); + backBtn.setText(getString(R.string.server_policy_disagree, instance.uri)); + backBtn.setOnClickListener(v->{ + setResult(false, null); + Nav.finish(this); + }); + return view; } @@ -113,13 +123,17 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ super.onViewCreated(view, savedInstanceState); setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar())); } @Override protected void onUpdateToolbar(){ super.onUpdateToolbar(); - getToolbar().setBackground(null); + getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel); getToolbar().setElevation(0); + if(onScrollListener!=null){ + onScrollListener.setViews(buttonBar, getToolbar()); + } } protected void onButtonClick(){ @@ -158,7 +172,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ if(!response.isSuccessful()) return; Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString()); - final Item item=new Item(doc.title(), instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico"); + final Item item=new Item(doc.title(), null, instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico"); Activity activity=getActivity(); if(activity!=null){ activity.runOnUiThread(()->{ @@ -192,16 +206,23 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ private class ItemViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ private final TextView title; + private final TextView subtitle; public ItemViewHolder(){ super(getActivity(), R.layout.item_privacy_policy_link, list); title=findViewById(R.id.title); - title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + subtitle=findViewById(R.id.subtitle); } @Override public void onBind(Item item){ title.setText(item.title); + if(TextUtils.isEmpty(item.subtitle)){ + subtitle.setVisibility(View.GONE); + }else{ + subtitle.setVisibility(View.VISIBLE); + subtitle.setText(item.subtitle); + } } @Override @@ -211,10 +232,11 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{ } private static class Item{ - public String title, domain, url, faviconUrl; + public String title, subtitle, domain, url, faviconUrl; - public Item(String title, String domain, String url, String faviconUrl){ + public Item(String title, String subtitle, String domain, String url, String faviconUrl){ this.title=title; + this.subtitle=subtitle; this.domain=domain; this.url=url; this.faviconUrl=faviconUrl; diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogSignupFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogSignupFragment.java index 5fe8149d..e147ceac 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogSignupFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceCatalogSignupFragment.java @@ -1,14 +1,8 @@ package org.joinmastodon.android.fragments.onboarding; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; @@ -37,6 +31,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.FilterChipView; +import org.joinmastodon.android.utils.ElevationOnScrollListener; import org.parceler.Parcels; import java.util.ArrayList; @@ -56,7 +51,6 @@ import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.utils.BindableViewHolder; -import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.V; @@ -211,47 +205,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple setStatusBarColor(0); topBar=view.findViewById(R.id.top_bar); - LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate(); - topBar.setBackground(topBg); - Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay); - topOverlay.setAlpha(0); - - LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate(); - buttonBar.setBackground(btmBg); - Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay); - btmOverlay.setAlpha(0); - - list.addOnScrollListener(new RecyclerView.OnScrollListener(){ - private boolean isAtTop=true; - private Animator currentPanelsAnim; - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ - boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop()); - if(newAtTop!=isAtTop){ - isAtTop=newAtTop; - if(currentPanelsAnim!=null) - currentPanelsAnim.cancel(); - - AnimatorSet set=new AnimatorSet(); - set.playTogether( - ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20), - ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20), - ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)), - ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)) - ); - set.setDuration(150); - set.setInterpolator(CubicBezierInterpolator.DEFAULT); - set.addListener(new AnimatorListenerAdapter(){ - @Override - public void onAnimationEnd(Animator animation){ - currentPanelsAnim=null; - } - }); - set.start(); - currentPanelsAnim=set; - } - } - }); + list.addOnScrollListener(new ElevationOnScrollListener(null, topBar, buttonBar)); searchEdit=view.findViewById(R.id.search_edit); searchEdit.setOnEditorActionListener(this::onSearchEnterPressed); @@ -684,4 +638,5 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple return (this==GENERAL)==isGeneral; } } + } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java index 260e1c2d..224a002c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/InstanceRulesFragment.java @@ -17,6 +17,7 @@ import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.utils.ElevationOnScrollListener; import org.parceler.Parcels; import androidx.annotation.NonNull; @@ -28,6 +29,7 @@ import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.V; +import me.grishka.appkit.views.FragmentRootLinearLayout; import me.grishka.appkit.views.UsableRecyclerView; public class InstanceRulesFragment extends ToolbarFragment{ @@ -36,6 +38,9 @@ public class InstanceRulesFragment extends ToolbarFragment{ private Button btn; private View buttonBar; private Instance instance; + private ElevationOnScrollListener onScrollListener; + + private static final int RULES_REQUEST=376; @Override public void onCreate(Bundle savedInstanceState){ @@ -81,19 +86,31 @@ public class InstanceRulesFragment extends ToolbarFragment{ super.onViewCreated(view, savedInstanceState); setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); + list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar())); } @Override protected void onUpdateToolbar(){ super.onUpdateToolbar(); - getToolbar().setBackground(null); + getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel); getToolbar().setElevation(0); + if(onScrollListener!=null){ + onScrollListener.setViews(buttonBar, getToolbar()); + } } protected void onButtonClick(){ Bundle args=new Bundle(); args.putParcelable("instance", Parcels.wrap(instance)); - Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args); + Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this); + } + + @Override + public void onFragmentResult(int reqCode, boolean success, Bundle result){ + super.onFragmentResult(reqCode, success, result); + if(reqCode==RULES_REQUEST && !success){ + Nav.finish(this); + } } @Override 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 2672c749..fc2987f6 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 @@ -601,4 +601,12 @@ public class UiUtils{ public static boolean isMIUI(){ return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code")); } + + public static int alphaBlendColors(int color1, int color2, float alpha){ + float alpha0=1f-alpha; + int r=Math.round(((color1 >> 16) & 0xFF)*alpha0+((color2 >> 16) & 0xFF)*alpha); + int g=Math.round(((color1 >> 8) & 0xFF)*alpha0+((color2 >> 8) & 0xFF)*alpha); + int b=Math.round((color1 & 0xFF)*alpha0+(color2 & 0xFF)*alpha); + return 0xFF000000 | (r << 16) | (g << 8) | b; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/utils/ElevationOnScrollListener.java b/mastodon/src/main/java/org/joinmastodon/android/utils/ElevationOnScrollListener.java new file mode 100644 index 00000000..7fabbafd --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/utils/ElevationOnScrollListener.java @@ -0,0 +1,116 @@ +package org.joinmastodon.android.utils; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.view.View; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.ui.utils.UiUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import me.grishka.appkit.utils.CubicBezierInterpolator; +import me.grishka.appkit.utils.V; +import me.grishka.appkit.views.FragmentRootLinearLayout; + +public class ElevationOnScrollListener extends RecyclerView.OnScrollListener implements View.OnScrollChangeListener{ + private boolean isAtTop; + private Animator currentPanelsAnim; + private View[] views; + private FragmentRootLinearLayout fragmentRootLayout; + + public ElevationOnScrollListener(FragmentRootLinearLayout fragmentRootLayout, View... views){ + isAtTop=true; + this.fragmentRootLayout=fragmentRootLayout; + this.views=views; + for(View v:views){ + Drawable bg=v.getBackground().mutate(); + v.setBackground(bg); + if(bg instanceof LayerDrawable ld){ + Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay); + if(overlay!=null){ + overlay.setAlpha(0); + } + } + } + } + + public void setViews(View... views){ + List oldViews=Arrays.asList(this.views); + this.views=views; + for(View v:views){ + if(oldViews.contains(v)) + continue; + Drawable bg=v.getBackground().mutate(); + v.setBackground(bg); + if(bg instanceof LayerDrawable ld){ + Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay); + if(overlay!=null){ + overlay.setAlpha(isAtTop ? 0 : 20); + } + } + v.setTranslationZ(isAtTop ? 0 : V.dp(3)); + } + } + + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ + boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop()); + handleScroll(recyclerView.getContext(), newAtTop); + } + + @Override + public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){ + handleScroll(v.getContext(), scrollY==0); + } + + private void handleScroll(Context context, boolean newAtTop){ + if(newAtTop!=isAtTop){ + isAtTop=newAtTop; + if(currentPanelsAnim!=null) + currentPanelsAnim.cancel(); + + AnimatorSet set=new AnimatorSet(); + ArrayList anims=new ArrayList<>(); + for(View v:views){ + if(v.getBackground() instanceof LayerDrawable ld){ + Drawable overlay=ld.findDrawableByLayerId(R.id.color_overlay); + if(overlay!=null){ + anims.add(ObjectAnimator.ofInt(overlay, "alpha", isAtTop ? 0 : 20)); + } + } + anims.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3))); + } + if(fragmentRootLayout!=null){ + int color; + if(isAtTop){ + color=UiUtils.getThemeColor(context, R.attr.colorM3Background); + }else{ + color=UiUtils.alphaBlendColors(UiUtils.getThemeColor(context, R.attr.colorM3Background), UiUtils.getThemeColor(context, R.attr.colorM3Primary), 0.07843137f); + } + anims.add(ObjectAnimator.ofArgb(fragmentRootLayout, "statusBarColor", color)); + } + set.playTogether(anims); + set.setDuration(150); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + currentPanelsAnim=null; + } + }); + set.start(); + currentPanelsAnim=set; + } + } +} diff --git a/mastodon/src/main/res/layout/fragment_onboarding_rules.xml b/mastodon/src/main/res/layout/fragment_onboarding_rules.xml index 2eb19348..c7d90cb3 100644 --- a/mastodon/src/main/res/layout/fragment_onboarding_rules.xml +++ b/mastodon/src/main/res/layout/fragment_onboarding_rules.xml @@ -1,5 +1,5 @@ - @@ -11,6 +11,7 @@ android:layout_weight="1"/> - \ No newline at end of file + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/item_privacy_policy_link.xml b/mastodon/src/main/res/layout/item_privacy_policy_link.xml index 1a00dde8..3e864f01 100644 --- a/mastodon/src/main/res/layout/item_privacy_policy_link.xml +++ b/mastodon/src/main/res/layout/item_privacy_policy_link.xml @@ -1,18 +1,39 @@ - + android:paddingEnd="24dp"> + + + + - \ No newline at end of file + + + \ No newline at end of file diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index 6fefd9ea..0a057066 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -389,7 +389,7 @@ Download (%s) Install Your Privacy - Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy. + Although the Mastodon app does not collect any data, the server you sign up through may have a different policy.\n\nIf you disagree with the policy for %s, you can go back and pick a different server. I Agree This list is empty This server does not accept new registrations. @@ -429,4 +429,7 @@ Popular on Mastodon Follow all Disagree + TL;DR: We don\'t collect or process anything. + + Disagree with %s \ No newline at end of file