From cab03bf0149344dc34b6b957027330a6086de5a8 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 22 May 2025 01:06:39 +0200 Subject: adapt UI to API 35 requirements: increase height of action bar, so that it draws below the status bar, status bar icon color depends on underlying background color of action bar. Improve navigation drawer gesture handling by excluding 200 dp next to the status image (mask) from Android's back gesture recognition --- .../bitmaskclient/base/fragments/EipFragment.java | 19 +++++ .../base/fragments/NavigationDrawerFragment.java | 6 +- .../leap/bitmaskclient/base/utils/ViewHelper.java | 93 +++++++++++++++++++--- 3 files changed, 106 insertions(+), 12 deletions(-) (limited to 'app/src/main/java/se') diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index 76834332..f618b22b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -30,8 +30,10 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; @@ -41,6 +43,7 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.Toast; import androidx.annotation.ColorRes; @@ -59,6 +62,7 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Collections; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.VpnStatus; @@ -150,6 +154,8 @@ public class EipFragment extends Fragment implements PropertyChangeListener { mainButton = binding.mainButton; locationButton = binding.gatewayLocationButton; locationButton.setTextColor(R.color.black800); + ViewHelper.applyInsetsToViewMargin(locationButton, false, false, false, true); + excludeBackGesture(binding); mainDescription = binding.mainDescription; subDescription = binding.subDescription; stateView = binding.stateView; @@ -189,6 +195,19 @@ public class EipFragment extends Fragment implements PropertyChangeListener { return binding.getRoot(); } + private void excludeBackGesture(FEipBinding binding) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return; + ViewTreeObserver treeObserver = binding.gestureSuppressView.getViewTreeObserver(); + treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + binding.gestureSuppressView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + Rect rect = new Rect(0, 0, binding.gestureSuppressView.getWidth(), binding.gestureSuppressView.getHeight()); + binding.gestureSuppressView.setSystemGestureExclusionRects(Collections.singletonList(rect)); + } + }); + } + @Override public void onStart() { super.onStart(); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index 41e102bb..376be389 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -46,11 +46,9 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.graphics.drawable.DrawerArrowDrawable; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; -import androidx.core.os.LocaleListCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; @@ -58,7 +56,6 @@ import androidx.fragment.app.Fragment; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Locale; -import java.util.Map; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; @@ -67,6 +64,7 @@ import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.base.utils.ViewHelper; import se.leap.bitmaskclient.base.views.IconSwitchEntry; import se.leap.bitmaskclient.base.views.IconTextEntry; import se.leap.bitmaskclient.eip.EipStatus; @@ -127,6 +125,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen restoreFromSavedInstance(savedInstanceState); TetheringObservable.getInstance().addObserver(this); EipStatus.getInstance().addObserver(this); + ViewHelper.applyInsetsToViewMargin(drawerView, true, true, true, false); return drawerView; } @@ -169,6 +168,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen // set a custom shadow that overlays the main content when the drawer opens this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); toolbar = this.drawerLayout.findViewById(R.id.toolbar); + ViewHelper.applyInsetsToViewPadding(toolbar, true, true, true, false); setupEntries(); setupActionBarDrawerToggle(activity); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java index e04ba70d..0c03a16f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java @@ -2,22 +2,28 @@ package se.leap.bitmaskclient.base.utils; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import android.animation.Animator; import android.animation.ValueAnimator; import android.app.Activity; import android.content.Context; import android.graphics.Color; +import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.view.Window; +import android.view.WindowInsetsController; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; @@ -29,9 +35,16 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; +import androidx.core.graphics.Insets; import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.views.ActionBarTitle; @@ -94,7 +107,7 @@ public class ViewHelper { setActivityBarColor(activity, R.color.colorPrimary, R.color.colorPrimaryDark, R.color.colorActionBarTitleFont); } - public static void setActivityBarColor(Activity activity, @ColorRes int primaryColor, @ColorRes int secondaryColor, @ColorRes int textColor) { + public static void setActivityBarColor(Activity activity, @ColorRes int primaryColorRes, @ColorRes int secondaryColorRes, @ColorRes int textColor) { if (!(activity instanceof AppCompatActivity)) { return; } @@ -103,16 +116,12 @@ public class ViewHelper { if (bar == null) { return; } - int color = ContextCompat.getColor(activity, secondaryColor); - bar.setBackgroundDrawable(new ColorDrawable(color)); - Window window = activity.getWindow(); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(ContextCompat.getColor(activity, primaryColor)); - + int secondaryColor = ContextCompat.getColor(activity, secondaryColorRes); + bar.setBackgroundDrawable(new ColorDrawable(secondaryColor)); + setStatusBarColor(activity, primaryColorRes); int actionBarTextColor; if (textColor == 0) { - actionBarTextColor = isBrightColor(color) ? R.color.actionbar_connectivity_state_text_color_dark : R.color.actionbar_connectivity_state_text_color_light; + actionBarTextColor = isBrightColor(secondaryColor) ? R.color.actionbar_connectivity_state_text_color_dark : R.color.actionbar_connectivity_state_text_color_light; } else { actionBarTextColor = textColor; } @@ -130,6 +139,72 @@ public class ViewHelper { } } + public static void applyInsetsToViewPadding(View view, boolean left, boolean top, boolean right, boolean bottom) { + ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> { + Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + if (left) { + view.setPadding(insets.left, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); + } + if (right) { + view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), insets.right, view.getPaddingBottom()); + } + if (bottom) { + view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), insets.bottom); + } + if (top) { + view.setPadding(view.getPaddingLeft(), insets.top, view.getPaddingRight(), view.getPaddingBottom()); + } + + // Return CONSUMED if you don't want the window insets to keep passing + // down to descendant views. + return WindowInsetsCompat.CONSUMED; + }); + } + + public static void applyInsetsToViewMargin(View view, boolean left, boolean top, boolean right, boolean bottom) { + ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> { + Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); + if (left) { + mlp.leftMargin = insets.left; + } + if (right) { + mlp.rightMargin = insets.right; + } + if (bottom) { + mlp.bottomMargin = insets.bottom; + } + if (top) { + mlp.topMargin = insets.top; + } + v.setLayoutParams(mlp); + + // Return CONSUMED if you don't want the window insets to keep passing + // down to descendant views. + return WindowInsetsCompat.CONSUMED; + }); + } + + private static void setStatusBarColor(Activity activity, int colorRes) { + Window window = activity.getWindow(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(ContextCompat.getColor(activity, colorRes)); + } else { + // in Androiid 15+, the status bar is transparent. Hence we need to make sure that the status bar icons + // have enough contrast to be visible above the the underlying UI + WindowInsetsController controller = window.getInsetsController(); + if (controller != null) { + int color = ContextCompat.getColor(activity, colorRes); + if (isBrightColor(color)) { + controller.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS); + } else { + controller.setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS); + } + } + } + } public static boolean isBrightColor(int color) { if (android.R.color.transparent == color) -- cgit v1.2.3