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 3cf0767c9..f4793b8c1 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 5fe8149db..e147ceac6 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 7a3b0ce08..6dd47f224 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 860e33b7c..aa162967e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -127,47 +127,48 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.utils.V; import okhttp3.MediaType; -public class UiUtils{ - private static Handler mainHandler=new Handler(Looper.getMainLooper()); - private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR=DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT=DateTimeFormatter.ofPattern("d MMM"); - public static final DateTimeFormatter DATE_TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); +public class UiUtils { + private static Handler mainHandler = new Handler(Looper.getMainLooper()); + private static final DateTimeFormatter DATE_FORMATTER_SHORT_WITH_YEAR = DateTimeFormatter.ofPattern("d MMM uuuu"), DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("d MMM"); + public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); - private UiUtils(){} + private UiUtils() { + } - public static void launchWebBrowser(Context context, String url){ - try{ - if(GlobalUserPreferences.useCustomTabs){ + public static void launchWebBrowser(Context context, String url) { + try { + if (GlobalUserPreferences.useCustomTabs) { new CustomTabsIntent.Builder() .setShowTitle(true) .build() .launchUrl(context, Uri.parse(url)); - }else{ + } else { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); } - }catch(ActivityNotFoundException x){ + } catch (ActivityNotFoundException x) { Toast.makeText(context, R.string.no_app_to_handle_action, Toast.LENGTH_SHORT).show(); } } - public static String formatRelativeTimestamp(Context context, Instant instant){ - long t=instant.toEpochMilli(); - long now=System.currentTimeMillis(); - long diff=now-t; - if(diff<1000L){ + public static String formatRelativeTimestamp(Context context, Instant instant) { + long t = instant.toEpochMilli(); + long now = System.currentTimeMillis(); + long diff = now - t; + if (diff < 1000L) { return context.getString(R.string.time_now); - }else if(diff<60_000L){ - return context.getString(R.string.time_seconds, diff/1000L); - }else if(diff<3600_000L){ - return context.getString(R.string.time_minutes, diff/60_000L); - }else if(diff<3600_000L*24L){ - return context.getString(R.string.time_hours, diff/3600_000L); - }else{ - int days=(int)(diff/(3600_000L*24L)); - if(days>30){ - ZonedDateTime dt=instant.atZone(ZoneId.systemDefault()); - if(dt.getYear()==ZonedDateTime.now().getYear()){ + } else if (diff < 60_000L) { + return context.getString(R.string.time_seconds, diff / 1000L); + } else if (diff < 3600_000L) { + return context.getString(R.string.time_minutes, diff / 60_000L); + } else if (diff < 3600_000L * 24L) { + return context.getString(R.string.time_hours, diff / 3600_000L); + } else { + int days = (int) (diff / (3600_000L * 24L)); + if (days > 30) { + ZonedDateTime dt = instant.atZone(ZoneId.systemDefault()); + if (dt.getYear() == ZonedDateTime.now().getYear()) { return DATE_FORMATTER_SHORT.format(dt); - }else{ + } else { return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt); } } @@ -175,216 +176,220 @@ public class UiUtils{ } } - public static String formatRelativeTimestampAsMinutesAgo(Context context, Instant instant){ - long t=instant.toEpochMilli(); - long now=System.currentTimeMillis(); - long diff=now-t; - if(diff<1000L){ + public static String formatRelativeTimestampAsMinutesAgo(Context context, Instant instant) { + long t = instant.toEpochMilli(); + long now = System.currentTimeMillis(); + long diff = now - t; + if (diff < 1000L) { return context.getString(R.string.time_just_now); - }else if(diff<60_000L){ - int secs=(int)(diff/1000L); + } else if (diff < 60_000L) { + int secs = (int) (diff / 1000L); return context.getResources().getQuantityString(R.plurals.x_seconds_ago, secs, secs); - }else if(diff<3600_000L){ - int mins=(int)(diff/60_000L); + } else if (diff < 3600_000L) { + int mins = (int) (diff / 60_000L); return context.getResources().getQuantityString(R.plurals.x_minutes_ago, mins, mins); - }else{ + } else { return DATE_TIME_FORMATTER.format(instant.atZone(ZoneId.systemDefault())); } } - public static String formatTimeLeft(Context context, Instant instant){ - long t=instant.toEpochMilli(); - long now=System.currentTimeMillis(); - long diff=t-now; - if(diff<60_000L){ - int secs=(int)(diff/1000L); + public static String formatTimeLeft(Context context, Instant instant) { + long t = instant.toEpochMilli(); + long now = System.currentTimeMillis(); + long diff = t - now; + if (diff < 60_000L) { + int secs = (int) (diff / 1000L); return context.getResources().getQuantityString(R.plurals.x_seconds_left, secs, secs); - }else if(diff<3600_000L){ - int mins=(int)(diff/60_000L); + } else if (diff < 3600_000L) { + int mins = (int) (diff / 60_000L); return context.getResources().getQuantityString(R.plurals.x_minutes_left, mins, mins); - }else if(diff<3600_000L*24L){ - int hours=(int)(diff/3600_000L); + } else if (diff < 3600_000L * 24L) { + int hours = (int) (diff / 3600_000L); return context.getResources().getQuantityString(R.plurals.x_hours_left, hours, hours); - }else{ - int days=(int)(diff/(3600_000L*24L)); + } else { + int days = (int) (diff / (3600_000L * 24L)); return context.getResources().getQuantityString(R.plurals.x_days_left, days, days); } } @SuppressLint("DefaultLocale") - public static String abbreviateNumber(int n){ - if(n<1000){ + public static String abbreviateNumber(int n) { + if (n < 1000) { return String.format("%,d", n); - }else if(n<1_000_000){ - float a=n/1000f; - return a>99f ? String.format("%,dK", (int)Math.floor(a)) : String.format("%,.1fK", a); - }else{ - float a=n/1_000_000f; - return a>99f ? String.format("%,dM", (int)Math.floor(a)) : String.format("%,.1fM", n/1_000_000f); + } else if (n < 1_000_000) { + float a = n / 1000f; + return a > 99f ? String.format("%,dK", (int) Math.floor(a)) : String.format("%,.1fK", a); + } else { + float a = n / 1_000_000f; + return a > 99f ? String.format("%,dM", (int) Math.floor(a)) : String.format("%,.1fM", n / 1_000_000f); } } @SuppressLint("DefaultLocale") - public static String abbreviateNumber(long n){ - if(n<1_000_000_000L) - return abbreviateNumber((int)n); + public static String abbreviateNumber(long n) { + if (n < 1_000_000_000L) + return abbreviateNumber((int) n); - double a=n/1_000_000_000.0; - return a>99f ? String.format("%,dB", (int)Math.floor(a)) : String.format("%,.1fB", n/1_000_000_000.0); + double a = n / 1_000_000_000.0; + return a > 99f ? String.format("%,dB", (int) Math.floor(a)) : String.format("%,.1fB", n / 1_000_000_000.0); } /** * Android 6.0 has a bug where start and end compound drawables don't get tinted. * This works around it by setting the tint colors directly to the drawables. + * * @param textView */ - public static void fixCompoundDrawableTintOnAndroid6(TextView textView){ - Drawable[] drawables=textView.getCompoundDrawablesRelative(); - for(int i=0;i> spansByEmoji=Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji)); - for(Map.Entry> emoji:spansByEmoji.entrySet()){ - ViewImageLoader.load(new ViewImageLoader.Target(){ + int emojiSize = V.dp(20); + Map> spansByEmoji = Arrays.stream(spans).collect(Collectors.groupingBy(s -> s.emoji)); + for (Map.Entry> emoji : spansByEmoji.entrySet()) { + ViewImageLoader.load(new ViewImageLoader.Target() { @Override - public void setImageDrawable(Drawable d){ - if(d==null) + public void setImageDrawable(Drawable d) { + if (d == null) return; - for(CustomEmojiSpan span:emoji.getValue()){ + for (CustomEmojiSpan span : emoji.getValue()) { span.setDrawable(d); } view.invalidate(); } @Override - public View getView(){ + public View getView() { return view; } }, null, new UrlImageLoaderRequest(emoji.getKey().url, emojiSize, emojiSize), null, false, true); } } - public static int getThemeColor(Context context, @AttrRes int attr){ - TypedArray ta=context.obtainStyledAttributes(new int[]{attr}); - int color=ta.getColor(0, 0xff00ff00); + public static int getThemeColor(Context context, @AttrRes int attr) { + TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); + int color = ta.getColor(0, 0xff00ff00); ta.recycle(); return color; } - public static void openProfileByID(Context context, String selfID, String id){ - Bundle args=new Bundle(); + public static void openProfileByID(Context context, String selfID, String id) { + Bundle args = new Bundle(); args.putString("account", selfID); args.putString("profileAccountID", id); - Nav.go((Activity)context, ProfileFragment.class, args); + Nav.go((Activity) context, ProfileFragment.class, args); } - public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following){ - Bundle args=new Bundle(); + public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) { + Bundle args = new Bundle(); args.putString("account", accountID); args.putString("hashtag", hashtag); if (following != null) args.putBoolean("following", following); - Nav.go((Activity)context, HashtagTimelineFragment.class, args); + Nav.go((Activity) context, HashtagTimelineFragment.class, args); } - public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){ + public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) { showConfirmationAlert(context, title, message, confirmButton, 0, onConfirmed); } - public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed){ + public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed) { showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), icon, onConfirmed); } - public static void showConfirmationAlert(Context context, CharSequence title, CharSequence message, CharSequence confirmButton, int icon, Runnable onConfirmed){ + public static void showConfirmationAlert(Context context, CharSequence title, CharSequence message, CharSequence confirmButton, int icon, Runnable onConfirmed) { new M3AlertDialogBuilder(context) .setTitle(title) .setMessage(message) - .setPositiveButton(confirmButton, (dlg, i)->onConfirmed.run()) + .setPositiveButton(confirmButton, (dlg, i) -> onConfirmed.run()) .setNegativeButton(R.string.cancel, null) .setIcon(icon) .show(); } - public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer resultCallback){ + public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer resultCallback) { showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title), activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName), activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), R.drawable.ic_fluent_person_prohibited_28_regular, - ()->{ + () -> { new SetAccountBlocked(account.id, !currentlyBlocked) - .setCallback(new Callback<>(){ + .setCallback(new Callback<>() { @Override - public void onSuccess(Relationship result){ + public void onSuccess(Relationship result) { if (activity == null) return; resultCallback.accept(result); - if(!currentlyBlocked){ + if (!currentlyBlocked) { E.post(new RemoveAccountPostsEvent(accountID, account.id, false)); } } @Override - public void onError(ErrorResponse error){ + public void onError(ErrorResponse error) { error.showToast(activity); } }) @@ -393,55 +398,55 @@ public class UiUtils{ }); } - public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer resultCallback){ + public static void confirmSoftBlockUser(Activity activity, String accountID, Account account, Consumer resultCallback) { showConfirmationAlert(activity, activity.getString(R.string.sk_remove_follower), activity.getString(R.string.sk_remove_follower_confirm, account.displayName), activity.getString(R.string.sk_do_remove_follower), R.drawable.ic_fluent_person_delete_24_regular, () -> new SetAccountBlocked(account.id, true).setCallback(new Callback<>() { - @Override - public void onSuccess(Relationship relationship) { - new SetAccountBlocked(account.id, false).setCallback(new Callback<>() { + @Override + public void onSuccess(Relationship relationship) { + new SetAccountBlocked(account.id, false).setCallback(new Callback<>() { + @Override + public void onSuccess(Relationship relationship) { + if (activity == null) return; + Toast.makeText(activity, R.string.sk_remove_follower_success, Toast.LENGTH_SHORT).show(); + resultCallback.accept(relationship); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(activity); + resultCallback.accept(relationship); + } + }).exec(accountID); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(activity); + } + }).exec(accountID) + ); + } + + public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback) { + showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title), + activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain), + activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), + R.drawable.ic_fluent_shield_28_regular, + () -> { + new SetDomainBlocked(domain, !currentlyBlocked) + .setCallback(new Callback<>() { @Override - public void onSuccess(Relationship relationship) { - if (activity == null) return; - Toast.makeText(activity, R.string.sk_remove_follower_success, Toast.LENGTH_SHORT).show(); - resultCallback.accept(relationship); + public void onSuccess(Object result) { + resultCallback.run(); } @Override public void onError(ErrorResponse error) { error.showToast(activity); - resultCallback.accept(relationship); - } - }).exec(accountID); - } - - @Override - public void onError(ErrorResponse error) { - error.showToast(activity); - } - }).exec(accountID) - ); - } - - public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){ - showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title), - activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain), - activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), - R.drawable.ic_fluent_shield_28_regular, - ()->{ - new SetDomainBlocked(domain, !currentlyBlocked) - .setCallback(new Callback<>(){ - @Override - public void onSuccess(Object result){ - resultCallback.run(); - } - - @Override - public void onError(ErrorResponse error){ - error.showToast(activity); } }) .wrapProgress(activity, R.string.loading, false) @@ -449,24 +454,24 @@ public class UiUtils{ }); } - public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer resultCallback){ + public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer resultCallback) { showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title), activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName), activity.getString(currentlyMuted ? R.string.do_unmute : R.string.do_mute), currentlyMuted ? R.drawable.ic_fluent_speaker_0_28_regular : R.drawable.ic_fluent_speaker_off_28_regular, - ()->{ + () -> { new SetAccountMuted(account.id, !currentlyMuted) - .setCallback(new Callback<>(){ + .setCallback(new Callback<>() { @Override - public void onSuccess(Relationship result){ + public void onSuccess(Relationship result) { resultCallback.accept(result); - if(!currentlyMuted){ + if (!currentlyMuted) { E.post(new RemoveAccountPostsEvent(accountID, account.id, false)); } } @Override - public void onError(ErrorResponse error){ + public void onError(ErrorResponse error) { error.showToast(activity); } }) @@ -474,27 +479,28 @@ public class UiUtils{ .exec(accountID); }); } - public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer resultCallback){ + + public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer resultCallback) { confirmDeletePost(activity, accountID, status, resultCallback, false); } - public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer resultCallback, boolean forRedraft){ + public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer resultCallback, boolean forRedraft) { showConfirmationAlert(activity, forRedraft ? R.string.sk_confirm_delete_and_redraft_title : R.string.confirm_delete_title, forRedraft ? R.string.sk_confirm_delete_and_redraft : R.string.confirm_delete, forRedraft ? R.string.sk_delete_and_redraft : R.string.delete, forRedraft ? R.drawable.ic_fluent_arrow_clockwise_28_regular : R.drawable.ic_fluent_delete_28_regular, () -> new DeleteStatus(status.id) - .setCallback(new Callback<>(){ + .setCallback(new Callback<>() { @Override - public void onSuccess(Status result){ + public void onSuccess(Status result) { resultCallback.accept(result); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id); E.post(new StatusDeletedEvent(status.id, accountID)); } @Override - public void onError(ErrorResponse error){ + public void onError(ErrorResponse error) { error.showToast(activity); } }) @@ -503,7 +509,7 @@ public class UiUtils{ ); } - public static void confirmDeleteScheduledPost(Activity activity, String accountID, ScheduledStatus status, Runnable resultCallback){ + public static void confirmDeleteScheduledPost(Activity activity, String accountID, ScheduledStatus status, Runnable resultCallback) { boolean isDraft = status.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT); showConfirmationAlert(activity, isDraft ? R.string.sk_confirm_delete_draft_title : R.string.sk_confirm_delete_scheduled_post_title, @@ -511,15 +517,15 @@ public class UiUtils{ R.string.delete, R.drawable.ic_fluent_delete_28_regular, () -> new DeleteStatus.Scheduled(status.id) - .setCallback(new Callback<>(){ + .setCallback(new Callback<>() { @Override - public void onSuccess(Object o){ + public void onSuccess(Object o) { resultCallback.run(); E.post(new ScheduledStatusDeletedEvent(status.id, accountID)); } @Override - public void onError(ErrorResponse error){ + public void onError(ErrorResponse error) { error.showToast(activity); } }) @@ -528,13 +534,13 @@ public class UiUtils{ ); } - public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer resultCallback){ + public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer resultCallback) { showConfirmationAlert(activity, pinned ? R.string.sk_confirm_pin_post_title : R.string.sk_confirm_unpin_post_title, pinned ? R.string.sk_confirm_pin_post : R.string.sk_confirm_unpin_post, pinned ? R.string.sk_pin_post : R.string.sk_unpin_post, pinned ? R.drawable.ic_fluent_pin_28_regular : R.drawable.ic_fluent_pin_off_28_regular, - ()->{ + () -> { new SetStatusPinned(status.id, pinned) .setCallback(new Callback<>() { @Override @@ -597,42 +603,42 @@ public class UiUtils{ .exec(accountID)); } - public static void setRelationshipToActionButton(Relationship relationship, Button button){ + public static void setRelationshipToActionButton(Relationship relationship, Button button) { setRelationshipToActionButton(relationship, button, false); } - public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText){ + public static void setRelationshipToActionButton(Relationship relationship, Button button, boolean keepText) { CharSequence textBefore = keepText ? button.getText() : null; boolean secondaryStyle; - if(relationship.blocking){ + if (relationship.blocking) { button.setText(R.string.button_blocked); - secondaryStyle=true; - }else if(relationship.blockedBy){ + secondaryStyle = true; + } else if (relationship.blockedBy) { button.setText(R.string.button_follow); - secondaryStyle=false; - }else if(relationship.requested){ + secondaryStyle = false; + } else if (relationship.requested) { button.setText(R.string.button_follow_pending); - secondaryStyle=true; - }else if(!relationship.following){ + secondaryStyle = true; + } else if (!relationship.following) { button.setText(relationship.followedBy ? R.string.follow_back : R.string.button_follow); - secondaryStyle=false; - }else{ + secondaryStyle = false; + } else { button.setText(R.string.button_following); - secondaryStyle=true; + secondaryStyle = true; } if (keepText) button.setText(textBefore); button.setEnabled(!relationship.blockedBy); - int attr=secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle; - TypedArray ta=button.getContext().obtainStyledAttributes(new int[]{attr}); - int styleRes=ta.getResourceId(0, 0); + int attr = secondaryStyle ? R.attr.secondaryButtonStyle : android.R.attr.buttonStyle; + TypedArray ta = button.getContext().obtainStyledAttributes(new int[]{attr}); + int styleRes = ta.getResourceId(0, 0); ta.recycle(); - ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background}); + ta = button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background}); button.setBackground(ta.getDrawable(0)); ta.recycle(); - ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor}); - if(relationship.blocking) + ta = button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor}); + if (relationship.blocking) button.setTextColor(button.getResources().getColorStateList(R.color.error_600)); else button.setTextColor(ta.getColorStateList(0)); @@ -647,7 +653,7 @@ public class UiUtils{ public void onSuccess(Relationship result) { resultCallback.accept(result); progressCallback.accept(false); - Toast.makeText(activity, activity.getString(result.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username), Toast.LENGTH_SHORT).show(); + Toast.makeText(activity, activity.getString(result.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@' + account.username), Toast.LENGTH_SHORT).show(); } @Override @@ -658,26 +664,26 @@ public class UiUtils{ }).exec(accountID); } - public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer progressCallback, Consumer resultCallback){ - if(relationship.blocking){ + public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer progressCallback, Consumer resultCallback) { + if (relationship.blocking) { confirmToggleBlockUser(activity, accountID, account, true, resultCallback); - }else if(relationship.muting){ + } else if (relationship.muting) { confirmToggleMuteUser(activity, accountID, account, true, resultCallback); - }else{ + } else { progressCallback.accept(true); new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true, false) - .setCallback(new Callback<>(){ + .setCallback(new Callback<>() { @Override - public void onSuccess(Relationship result){ + public void onSuccess(Relationship result) { resultCallback.accept(result); progressCallback.accept(false); - if(!result.following){ + if (!result.following) { E.post(new RemoveAccountPostsEvent(accountID, account.id, true)); } } @Override - public void onError(ErrorResponse error){ + public void onError(ErrorResponse error) { error.showToast(activity); progressCallback.accept(false); } @@ -707,7 +713,8 @@ public class UiUtils{ @Override public void onSuccess(Relationship rel) { E.post(new FollowRequestHandledEvent(accountID, false, account, rel)); - if (notificationID != null) E.post(new NotificationDeletedEvent(notificationID)); + if (notificationID != null) + E.post(new NotificationDeletedEvent(notificationID)); resultCallback.accept(rel); } @@ -720,34 +727,34 @@ public class UiUtils{ } } - public static void updateList(List oldList, List newList, RecyclerView list, RecyclerView.Adapter adapter, BiPredicate areItemsSame){ + public static void updateList(List oldList, List newList, RecyclerView list, RecyclerView.Adapter adapter, BiPredicate areItemsSame) { // Save topmost item position and offset because for some reason RecyclerView would scroll the list to weird places when you insert items at the top int topItem, topItemOffset; - if(list.getChildCount()==0){ - topItem=topItemOffset=0; - }else{ - View child=list.getChildAt(0); - topItem=list.getChildAdapterPosition(child); - topItemOffset=child.getTop(); + if (list.getChildCount() == 0) { + topItem = topItemOffset = 0; + } else { + View child = list.getChildAt(0); + topItem = list.getChildAdapterPosition(child); + topItemOffset = child.getTop(); } - DiffUtil.calculateDiff(new DiffUtil.Callback(){ + DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override - public int getOldListSize(){ + public int getOldListSize() { return oldList.size(); } @Override - public int getNewListSize(){ + public int getNewListSize() { return newList.size(); } @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){ + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return areItemsSame.test(oldList.get(oldItemPosition), newList.get(newItemPosition)); } @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){ + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return true; } }).dispatchUpdatesTo(adapter); @@ -755,77 +762,80 @@ public class UiUtils{ list.scrollBy(0, topItemOffset); } - public static Bitmap getBitmapFromDrawable(Drawable d){ - if(d instanceof BitmapDrawable) + public static Bitmap getBitmapFromDrawable(Drawable d) { + if (d instanceof BitmapDrawable) return ((BitmapDrawable) d).getBitmap(); - Bitmap bitmap=Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); d.draw(new Canvas(bitmap)); return bitmap; } public static void insetPopupMenuIcon(Context context, MenuItem item) { - ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary)); + ColorStateList iconTint = ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary)); insetPopupMenuIcon(item, iconTint); } + public static void insetPopupMenuIcon(MenuItem item, ColorStateList iconTint) { - Drawable icon=item.getIcon().mutate(); - if(Build.VERSION.SDK_INT>=26) item.setIconTintList(iconTint); + Drawable icon = item.getIcon().mutate(); + if (Build.VERSION.SDK_INT >= 26) item.setIconTintList(iconTint); else icon.setTintList(iconTint); - icon=new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0); + icon = new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0); item.setIcon(icon); - SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle()); + SpannableStringBuilder ssb = new SpannableStringBuilder(item.getTitle()); item.setTitle(ssb); } public static void resetPopupItemTint(MenuItem item) { - if(Build.VERSION.SDK_INT>=26) { + if (Build.VERSION.SDK_INT >= 26) { item.setIconTintList(null); } else { - Drawable icon=item.getIcon().mutate(); + Drawable icon = item.getIcon().mutate(); icon.setTintList(null); item.setIcon(icon); } } public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) { - if(menu.getClass().getSimpleName().equals("MenuBuilder")){ + if (menu.getClass().getSimpleName().equals("MenuBuilder")) { try { Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE); m.setAccessible(true); m.invoke(menu, true); enableMenuIcons(context, menu, asAction); + } catch (Exception ignored) { } - catch(Exception ignored){} } } public static void enableMenuIcons(Context context, Menu m, @IdRes int... exclude) { - ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary)); - for(int i=0;i id == item.getItemId())) continue; + if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId())) + continue; insetPopupMenuIcon(item, iconTint); } } - public static void enablePopupMenuIcons(Context context, PopupMenu menu){ - Menu m=menu.getMenu(); - if(Build.VERSION.SDK_INT>=29){ + public static void enablePopupMenuIcons(Context context, PopupMenu menu) { + Menu m = menu.getMenu(); + if (Build.VERSION.SDK_INT >= 29) { menu.setForceShowIcon(true); - }else{ - try{ - Method setOptionalIconsVisible=m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class); + } else { + try { + Method setOptionalIconsVisible = m.getClass().getDeclaredMethod("setOptionalIconsVisible", boolean.class); setOptionalIconsVisible.setAccessible(true); setOptionalIconsVisible.invoke(m, true); - }catch(Exception ignore){} + } catch (Exception ignore) { + } } enableMenuIcons(context, m); } - public static void setUserPreferredTheme(Context context){ + public static void setUserPreferredTheme(Context context) { context.setTheme(switch (theme) { case LIGHT -> R.style.Theme_Mastodon_Light; case DARK -> trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack : R.style.Theme_Mastodon_Dark; @@ -835,10 +845,11 @@ public class UiUtils{ ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color); if (palette != null) palette.apply(context); } - public static boolean isDarkTheme(){ - if(theme==GlobalUserPreferences.ThemePreference.AUTO) - return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)==Configuration.UI_MODE_NIGHT_YES; - return theme==GlobalUserPreferences.ThemePreference.DARK; + + public static boolean isDarkTheme() { + if (theme == GlobalUserPreferences.ThemePreference.AUTO) + return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + return theme == GlobalUserPreferences.ThemePreference.DARK; } // https://mastodon.foo.bar/@User @@ -865,7 +876,8 @@ public class UiUtils{ return false; } - if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null) return false; + if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null) + return false; String it = uri.getPath(); return it.matches("^/@[^/]+$") || @@ -890,8 +902,8 @@ public class UiUtils{ } public static void pickAccount(Context context, String exceptFor, @StringRes int titleRes, @DrawableRes int iconRes, Consumer sessionConsumer, Consumer transformDialog) { - List sessions=AccountSessionManager.getInstance().getLoggedInAccounts() - .stream().filter(s->!s.getID().equals(exceptFor)).collect(Collectors.toList()); + List sessions = AccountSessionManager.getInstance().getLoggedInAccounts() + .stream().filter(s -> !s.getID().equals(exceptFor)).collect(Collectors.toList()); AlertDialog.Builder builder = new M3AlertDialogBuilder(context) .setItems( @@ -963,18 +975,19 @@ public class UiUtils{ } new GetSearchResults(queryStatus.url, GetSearchResults.Type.STATUSES, true).setCallback(new Callback<>() { - @Override - public void onSuccess(SearchResults results) { - if (!results.statuses.isEmpty()) statusConsumer.accept(results.statuses.get(0)); - else Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); - } + @Override + public void onSuccess(SearchResults results) { + if (!results.statuses.isEmpty()) statusConsumer.accept(results.statuses.get(0)); + else + Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); + } - @Override - public void onError(ErrorResponse error) { - error.showToast(context); - } - }) - .wrapProgress((Activity)context, R.string.loading, true, + @Override + public void onError(ErrorResponse error) { + error.showToast(context); + } + }) + .wrapProgress((Activity) context, R.string.loading, true, d -> transformDialogForLookup(context, targetAccountID, null, d)) .exec(targetAccountID); } @@ -998,28 +1011,28 @@ public class UiUtils{ } } - public static void openURL(Context context, String accountID, String url, boolean launchBrowser){ - Uri uri=Uri.parse(url); - List path=uri.getPathSegments(); - if(accountID!=null && "https".equals(uri.getScheme())){ - if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){ + public static void openURL(Context context, String accountID, String url, boolean launchBrowser) { + Uri uri = Uri.parse(url); + List path = uri.getPathSegments(); + if (accountID != null && "https".equals(uri.getScheme())) { + if (path.size() == 2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())) { new GetStatusByID(path.get(1)) - .setCallback(new Callback<>(){ + .setCallback(new Callback<>() { @Override - public void onSuccess(Status result){ - Bundle args=new Bundle(); + public void onSuccess(Status result) { + Bundle args = new Bundle(); args.putString("account", accountID); args.putParcelable("status", Parcels.wrap(result)); Nav.go((Activity) context, ThreadFragment.class, args); } @Override - public void onError(ErrorResponse error){ + public void onError(ErrorResponse error) { error.showToast(context); if (launchBrowser) launchWebBrowser(context, url); } }) - .wrapProgress((Activity)context, R.string.loading, true, + .wrapProgress((Activity) context, R.string.loading, true, d -> transformDialogForLookup(context, accountID, url, d)) .exec(accountID); return; @@ -1028,7 +1041,7 @@ public class UiUtils{ .setCallback(new Callback<>() { @Override public void onSuccess(SearchResults results) { - Bundle args=new Bundle(); + Bundle args = new Bundle(); args.putString("account", accountID); if (!results.statuses.isEmpty()) { args.putParcelable("status", Parcels.wrap(results.statuses.get(0))); @@ -1038,7 +1051,8 @@ public class UiUtils{ Nav.go((Activity) context, ProfileFragment.class, args); } else { if (launchBrowser) launchWebBrowser(context, url); - else Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); + else + Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); } } @@ -1048,7 +1062,7 @@ public class UiUtils{ if (launchBrowser) launchWebBrowser(context, url); } }) - .wrapProgress((Activity)context, R.string.loading, true, + .wrapProgress((Activity) context, R.string.loading, true, d -> transformDialogForLookup(context, accountID, url, d)) .exec(accountID); return; @@ -1060,36 +1074,45 @@ public class UiUtils{ public static void copyText(View v, String text) { Context context = v.getContext(); context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, text)); - if(Build.VERSION.SDK_INT props=Class.forName("android.os.SystemProperties"); - Method get=props.getMethod("get", String.class); - return (String)get.invoke(null, key); - }catch(Exception ignore){} + private static String getSystemProperty(String key) { + try { + Class props = Class.forName("android.os.SystemProperties"); + Method get = props.getMethod("get", String.class); + return (String) get.invoke(null, key); + } catch (Exception ignore) { + } return null; } - public static boolean isMIUI(){ + public static boolean isMIUI() { return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code")); } - public static boolean pickAccountForCompose(Activity activity, String accountID, String prefilledText){ + 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; + } + + public static boolean pickAccountForCompose(Activity activity, String accountID, String prefilledText) { Bundle args = new Bundle(); if (prefilledText != null) args.putString("prefilledText", prefilledText); return pickAccountForCompose(activity, accountID, args); } - public static boolean pickAccountForCompose(Activity activity, String accountID){ + public static boolean pickAccountForCompose(Activity activity, String accountID) { return pickAccountForCompose(activity, accountID, (String) null); } - public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args){ + public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args) { if (AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1) { UiUtils.pickAccount(activity, accountID, 0, 0, session -> { args.putString("account", session.getID()); 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 000000000..7fabbafd2 --- /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 214efd93a..2f1b2da91 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 1a00dde81..3e864f010 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 4aa3077b8..069391bfc 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