diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/utils/EdgeToEdgeDelegate.java b/app/src/main/java/com/d4rk/androidtutorials/java/utils/EdgeToEdgeDelegate.java index bc22b9e2..75c5bb07 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/utils/EdgeToEdgeDelegate.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/utils/EdgeToEdgeDelegate.java @@ -5,13 +5,21 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; import androidx.core.graphics.Insets; +import androidx.core.view.MarginLayoutParamsCompat; import androidx.core.view.ViewCompat; import androidx.core.view.ViewGroupCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import com.d4rk.androidtutorials.java.R; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.bottomappbar.BottomAppBar; +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.navigation.NavigationBarView; public final class EdgeToEdgeDelegate { @@ -21,30 +29,49 @@ private EdgeToEdgeDelegate() { public static void apply(Activity activity, View view) { enableEdgeToEdge(activity); - applyInsetsAsPadding( + applyInsetsInternal( view, - WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), - true, - true + null, + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() ); } public static void applyBottomBar(Activity activity, View container, View bottomNavigationView) { enableEdgeToEdge(activity); - applyInsetsAsPadding( + applyInsetsInternal( container, - WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), - true, - false - ); - applyInsetsAsPadding( bottomNavigationView, - WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), - false, - true + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() ); } + private static void applyInsetsInternal(View root, @Nullable View explicitBottomBar, int insetTypes) { + if (root == null) { + return; + } + + View topBar = null; + View bottomBar = explicitBottomBar; + if (root instanceof ViewGroup viewGroup) { + topBar = findTopBar(viewGroup); + if (bottomBar == null) { + bottomBar = findBottomBar(viewGroup); + } + applyInsetsToFloatingButtons(viewGroup, insetTypes); + } + + if (topBar != null && topBar != root) { + applyInsetsAsPadding(topBar, insetTypes, true, false); + } + if (bottomBar != null && bottomBar != root) { + applyInsetsAsPadding(bottomBar, insetTypes, false, true); + } + + boolean applyTop = topBar == null || topBar == root; + boolean applyBottom = bottomBar == null || bottomBar == root; + applyInsetsAsPadding(root, insetTypes, applyTop, applyBottom); + } + private static void enableEdgeToEdge(Activity activity) { if (activity == null) { return; @@ -52,6 +79,83 @@ private static void enableEdgeToEdge(Activity activity) { WindowCompat.enableEdgeToEdge(activity.getWindow()); } + @Nullable + private static View findTopBar(View view) { + if (!isVisible(view)) { + return null; + } + if (isTopBar(view)) { + return view; + } + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View child = viewGroup.getChildAt(i); + View topBar = findTopBar(child); + if (topBar != null) { + return topBar; + } + } + } + return null; + } + + @Nullable + private static View findBottomBar(View view) { + if (!isVisible(view)) { + return null; + } + if (isBottomBar(view)) { + return view; + } + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View child = viewGroup.getChildAt(i); + View bottomBar = findBottomBar(child); + if (bottomBar != null) { + return bottomBar; + } + } + } + return null; + } + + private static void applyInsetsToFloatingButtons(View view, int insetTypes) { + if (!isVisible(view)) { + return; + } + if (view instanceof ExtendedFloatingActionButton || view instanceof FloatingActionButton) { + applyInsetsAsMargin(view, insetTypes, false, true); + return; + } + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + applyInsetsToFloatingButtons(viewGroup.getChildAt(i), insetTypes); + } + } + } + + private static boolean isTopBar(View view) { + int id = view.getId(); + return view instanceof AppBarLayout + || view instanceof Toolbar + || id == R.id.app_bar_layout + || id == R.id.toolbar + || id == R.id.top_app_bar; + } + + private static boolean isBottomBar(View view) { + int id = view.getId(); + return view instanceof NavigationBarView + || view instanceof BottomAppBar + || id == R.id.nav_view + || id == R.id.bottom_nav + || id == R.id.bottomBar; + } + + private static boolean isVisible(View view) { + return view.getVisibility() == View.VISIBLE; + } + private static void applyInsetsAsPadding(View view, int insetTypes, boolean applyTop, boolean applyBottom) { @@ -95,6 +199,43 @@ private static void applyInsetsAsPadding(View view, int insetTypes, requestApplyInsetsWhenAttached(view); } + private static void applyInsetsAsMargin(View view, int insetTypes, + boolean applyTop, + boolean applyBottom) { + if (view == null) { + return; + } + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (!(layoutParams instanceof ViewGroup.MarginLayoutParams marginLayoutParams)) { + return; + } + + InsetsMargin baseMargin = (InsetsMargin) view.getTag(R.id.tag_edge_to_edge_margin); + if (baseMargin == null) { + baseMargin = new InsetsMargin( + MarginLayoutParamsCompat.getMarginStart(marginLayoutParams), + marginLayoutParams.topMargin, + MarginLayoutParamsCompat.getMarginEnd(marginLayoutParams), + marginLayoutParams.bottomMargin + ); + view.setTag(R.id.tag_edge_to_edge_margin, baseMargin); + } + + InsetsMargin margin = baseMargin; + ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> { + Insets insets = windowInsets.getInsets(insetTypes); + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) v.getLayoutParams(); + MarginLayoutParamsCompat.setMarginStart(lp, margin.start + insets.left); + lp.topMargin = margin.top + (applyTop ? insets.top : 0); + MarginLayoutParamsCompat.setMarginEnd(lp, margin.end + insets.right); + lp.bottomMargin = margin.bottom + (applyBottom ? insets.bottom : 0); + v.setLayoutParams(lp); + return windowInsets; + }); + + requestApplyInsetsWhenAttached(view); + } + private static void requestApplyInsetsWhenAttached(@NonNull View view) { if (ViewCompat.isAttachedToWindow(view)) { // FIXME: 'isAttachedToWindow(android.view.@org.jspecify.annotations.NonNull View)' is deprecated ViewCompat.requestApplyInsets(view); @@ -116,4 +257,7 @@ public void onViewDetachedFromWindow(@NonNull View v) { private record InsetsPadding(int start, int top, int end, int bottom) { } + + private record InsetsMargin(int start, int top, int end, int bottom) { + } } diff --git a/app/src/main/res/layout/activity_help.xml b/app/src/main/res/layout/activity_help.xml index 6259d3a6..a270204b 100644 --- a/app/src/main/res/layout/activity_help.xml +++ b/app/src/main/res/layout/activity_help.xml @@ -4,9 +4,8 @@ android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginHorizontal="24dp" - android:layout_marginVertical="24dp" - android:fitsSystemWindows="true"> + android:paddingHorizontal="24dp" + android:paddingVertical="24dp"> + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a17244c1..f11f85fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ agp = "8.13.0" firebaseCrashlyticsPlugin = "3.0.6" googleServices = "4.4.3" -ossLicensesPlugin = "0.10.6" +ossLicensesPlugin = "0.10.9" appcompat = "1.7.1" appUpdate = "2.1.0" billing = "8.0.0" diff --git a/settings.gradle b/settings.gradle index 77d4c0a7..c8bd590f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,7 +8,7 @@ pluginManagement { resolutionStrategy { eachPlugin { if (requested.id.id == 'com.google.android.gms.oss-licenses-plugin') { - useModule('com.google.android.gms:oss-licenses-plugin:0.10.6') + useModule('com.google.android.gms:oss-licenses-plugin:0.10.9') } } }