From b4c305d0940a1796d38749221f03b0ffbad501b7 Mon Sep 17 00:00:00 2001 From: Grishka Date: Wed, 2 Nov 2022 11:01:52 +0300 Subject: [PATCH] Add privacy policy step to the signup flow --- mastodon/build.gradle | 4 +- .../GoogleMadeMeAddThisFragment.java | 231 ++++++++++++++++++ .../onboarding/InstanceRulesFragment.java | 2 +- .../res/layout/fragment_onboarding_rules.xml | 2 +- .../res/layout/item_privacy_policy_link.xml | 44 ++++ mastodon/src/main/res/values/strings.xml | 3 + 6 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java create mode 100644 mastodon/src/main/res/layout/item_privacy_policy_link.xml diff --git a/mastodon/build.gradle b/mastodon/build.gradle index 07bb5f2e9..66296c4cf 100644 --- a/mastodon/build.gradle +++ b/mastodon/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.joinmastodon.android" minSdk 23 targetSdk 33 - versionCode 42 - versionName "1.1.3" + versionCode 43 + versionName "1.1.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } 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 new file mode 100644 index 000000000..23cfb2e5d --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/GoogleMadeMeAddThisFragment.java @@ -0,0 +1,231 @@ +package org.joinmastodon.android.fragments.onboarding; + +import android.app.Activity; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.MastodonAPIController; +import org.joinmastodon.android.model.Instance; +import org.joinmastodon.android.ui.OutlineProviders; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.parceler.Parcels; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Objects; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import me.grishka.appkit.Nav; +import me.grishka.appkit.fragments.AppKitFragment; +import me.grishka.appkit.imageloader.ViewImageLoader; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +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.UsableRecyclerView; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class GoogleMadeMeAddThisFragment extends AppKitFragment{ + private UsableRecyclerView list; + private MergeRecyclerAdapter adapter; + private Button btn; + private View buttonBar; + private Instance instance; + private ArrayList items=new ArrayList<>(); + private Call currentRequest; + private ItemsAdapter itemsAdapter; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Override + public void onAttach(Activity activity){ + super.onAttach(activity); + 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")); + loadServerPrivacyPolicy(); + } + + @Override + public void onDestroy(){ + super.onDestroy(); + if(currentRequest!=null){ + currentRequest.cancel(); + currentRequest=null; + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ + View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false); + + list=view.findViewById(R.id.list); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + View headerView=inflater.inflate(R.layout.item_list_header, list, false); + TextView title=headerView.findViewById(R.id.title); + TextView subtitle=headerView.findViewById(R.id.subtitle); + headerView.findViewById(R.id.step_counter).setVisibility(View.GONE); + title.setText(R.string.privacy_policy_title); + subtitle.setText(R.string.privacy_policy_subtitle); + + adapter=new MergeRecyclerAdapter(); + adapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); + adapter.addAdapter(itemsAdapter=new ItemsAdapter()); + list.setAdapter(adapter); + list.setSelector(null); + list.addItemDecoration(new RecyclerView.ItemDecoration(){ + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){ + if(parent.getChildViewHolder(view) instanceof ItemViewHolder){ + outRect.left=outRect.right=V.dp(18.5f); + outRect.top=V.dp(16); + } + } + }); + + btn=view.findViewById(R.id.btn_next); + btn.setOnClickListener(v->onButtonClick()); + buttonBar=view.findViewById(R.id.button_bar); + view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this)); + + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight)); + } + + protected void onButtonClick(){ + Bundle args=new Bundle(); + args.putParcelable("instance", Parcels.wrap(instance)); + Nav.go(getActivity(), SignupFragment.class, args); + } + + @Override + public void onApplyWindowInsets(WindowInsets insets){ + if(Build.VERSION.SDK_INT>=27){ + int inset=insets.getSystemWindowInsetBottom(); + buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0); + super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0)); + }else{ + super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); + } + } + + private void loadServerPrivacyPolicy(){ + Request req=new Request.Builder() + .url("https://"+instance.uri+"/terms") + .addHeader("Accept-Language", Locale.getDefault().toLanguageTag()) + .build(); + currentRequest=MastodonAPIController.getHttpClient().newCall(req); + currentRequest.enqueue(new Callback(){ + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e){ + currentRequest=null; + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{ + currentRequest=null; + try(ResponseBody body=response.body()){ + 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"); + Activity activity=getActivity(); + if(activity!=null){ + activity.runOnUiThread(()->{ + items.add(item); + itemsAdapter.notifyItemInserted(items.size()-1); + }); + } + } + } + }); + } + + private class ItemsAdapter extends RecyclerView.Adapter{ + + @NonNull + @Override + public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new ItemViewHolder(); + } + + @Override + public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){ + holder.bind(items.get(position)); + } + + @Override + public int getItemCount(){ + return items.size(); + } + } + + private class ItemViewHolder extends BindableViewHolder implements UsableRecyclerView.Clickable{ + private final TextView domain, title; + private final ImageView favicon; + + public ItemViewHolder(){ + super(getActivity(), R.layout.item_privacy_policy_link, list); + domain=findViewById(R.id.domain); + title=findViewById(R.id.title); + favicon=findViewById(R.id.favicon); + itemView.setOutlineProvider(OutlineProviders.roundedRect(10)); + itemView.setClipToOutline(true); + } + + @Override + public void onBind(Item item){ + domain.setText(item.domain); + title.setText(item.title); + + ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl)); + } + + @Override + public void onClick(){ + UiUtils.launchWebBrowser(getActivity(), item.url); + } + } + + private static class Item{ + public String title, domain, url, faviconUrl; + + public Item(String title, String domain, String url, String faviconUrl){ + this.title=title; + this.domain=domain; + this.url=url; + this.faviconUrl=faviconUrl; + } + } +} 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 07eacd658..edf124502 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 @@ -84,7 +84,7 @@ public class InstanceRulesFragment extends AppKitFragment{ protected void onButtonClick(){ Bundle args=new Bundle(); args.putParcelable("instance", Parcels.wrap(instance)); - Nav.go(getActivity(), SignupFragment.class, args); + Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args); } @Override diff --git a/mastodon/src/main/res/layout/fragment_onboarding_rules.xml b/mastodon/src/main/res/layout/fragment_onboarding_rules.xml index 9426a0f51..3c4a43ed7 100644 --- a/mastodon/src/main/res/layout/fragment_onboarding_rules.xml +++ b/mastodon/src/main/res/layout/fragment_onboarding_rules.xml @@ -41,7 +41,7 @@ android:layout_margin="16dp" android:minWidth="145dp" style="?primaryLargeButtonStyle" - android:text="@string/next" /> + android:text="@string/i_agree" /> diff --git a/mastodon/src/main/res/layout/item_privacy_policy_link.xml b/mastodon/src/main/res/layout/item_privacy_policy_link.xml new file mode 100644 index 000000000..c8c2155b2 --- /dev/null +++ b/mastodon/src/main/res/layout/item_privacy_policy_link.xml @@ -0,0 +1,44 @@ + + + + + + + + + + \ 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 66e8072f9..2ee4d9975 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -384,4 +384,7 @@ Download (%s) Install + Mastodon and 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. + I Agree \ No newline at end of file