diff --git a/e2e/BottomTabs.test.js b/e2e/BottomTabs.test.js index 97ddb33bef6..d9b33410ec7 100644 --- a/e2e/BottomTabs.test.js +++ b/e2e/BottomTabs.test.js @@ -2,7 +2,7 @@ import Utils from './Utils'; import TestIDs from '../playground/src/testIDs'; import Android from './AndroidUtils'; -const { elementByLabel, elementById } = Utils; +const { elementByLabel, elementById, expectImagesToBeEqual } = Utils; describe('BottomTabs', () => { beforeEach(async () => { @@ -79,6 +79,24 @@ describe('BottomTabs', () => { await expect(elementById(TestIDs.BOTTOM_TABS)).toBeVisible(); }); + it.e2e(':android: should set special stylizing options in root bottom-tabs', async () => { + await elementById(TestIDs.SCREEN_ROOT_LIST).scrollTo('bottom'); + await elementById(TestIDs.SET_ROOT_BTN).tap(); + const snapshotImagePath = `./e2e/assets/bottom_tabs.stylized-root.png`; + const actual = + await elementById('RNN.BottomTabsLayoutRoot').takeScreenshot(`bottom_tabs_stylized-root`); + expectImagesToBeEqual(actual, snapshotImagePath); + }); + + it.e2e(':android: should merge special stylizing options', async () => { + await elementById(TestIDs.SCREEN_ROOT_LIST).scrollTo('bottom'); + await elementById(TestIDs.STYLIZE_TABS_BTN).tap(); + const snapshotImagePath = `./e2e/assets/bottom_tabs.stylized.png`; + const actual = + await elementById('RNN.BottomTabsLayoutRoot').takeScreenshot(`bottom_tabs_stylized`); + expectImagesToBeEqual(actual, snapshotImagePath); + }); + it('hide Tab Bar on push', async () => { await elementById(TestIDs.HIDE_TABS_PUSH_BTN).tap(); await expect(elementById(TestIDs.BOTTOM_TABS)).toBeNotVisible(); diff --git a/e2e/assets/bottom_tabs.stylized-root.png b/e2e/assets/bottom_tabs.stylized-root.png new file mode 100644 index 00000000000..2f9c3116e64 Binary files /dev/null and b/e2e/assets/bottom_tabs.stylized-root.png differ diff --git a/e2e/assets/bottom_tabs.stylized.png b/e2e/assets/bottom_tabs.stylized.png new file mode 100644 index 00000000000..3d9fa4554c1 Binary files /dev/null and b/e2e/assets/bottom_tabs.stylized.png differ diff --git a/lib/android/app/build.gradle b/lib/android/app/build.gradle index a2bdded3a6c..23b8e720813 100644 --- a/lib/android/app/build.gradle +++ b/lib/android/app/build.gradle @@ -94,7 +94,8 @@ dependencies { implementation 'androidx.annotation:annotation:1.2.0' implementation 'com.google.android.material:material:1.2.0-alpha03' - implementation 'com.github.wix-playground:ahbottomnavigation:3.3.0' + implementation 'com.github.wix-playground:ahbottomnavigation:4.0.0' + implementation 'com.github.Dimezis:BlurView:version-3.0.0' // implementation project(':AHBottomNavigation') implementation 'com.github.wix-playground:reflow-animator:1.0.6' implementation 'com.github.clans:fab:1.6.4' diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/FeatureToggles.kt b/lib/android/app/src/main/java/com/reactnativenavigation/FeatureToggles.kt index 94db1c69c93..34c85fe9a9c 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/FeatureToggles.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/FeatureToggles.kt @@ -5,11 +5,13 @@ import androidx.annotation.VisibleForTesting enum class RNNToggles { TOP_BAR_COLOR_ANIMATION__PUSH, TOP_BAR_COLOR_ANIMATION__TABS, + TAB_BAR_TRANSLUCENCE, } private val ToggleDefaults = mapOf( RNNToggles.TOP_BAR_COLOR_ANIMATION__PUSH to false, RNNToggles.TOP_BAR_COLOR_ANIMATION__TABS to false, + RNNToggles.TAB_BAR_TRANSLUCENCE to false, ) object RNNFeatureToggles { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/BottomTabsOptions.java b/lib/android/app/src/main/java/com/reactnativenavigation/options/BottomTabsOptions.java index 6bc684508b3..ea2ab52f239 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/BottomTabsOptions.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/BottomTabsOptions.java @@ -3,6 +3,7 @@ import android.content.Context; import com.reactnativenavigation.options.params.Bool; +import com.reactnativenavigation.options.params.BottomTabsLayoutStyle; import com.reactnativenavigation.options.params.Fraction; import com.reactnativenavigation.options.params.NullBool; import com.reactnativenavigation.options.params.NullFraction; @@ -27,6 +28,11 @@ public static BottomTabsOptions parse(Context context, JSONObject json) { if (json == null) return options; options.backgroundColor = ThemeColour.parse(context, json.optJSONObject("backgroundColor")); + options.layoutStyle = BottomTabsLayoutStyle.fromString(json.optString("layoutStyle")); + options.bottomMargin = FractionParser.parse(json, "bottomMargin"); + options.cornerRadius = FractionParser.parse(json, "cornerRadius"); + options.translucent = BoolParser.parse(json, "translucent"); + options.blurRadius = FractionParser.parse(json, "blurRadius"); options.currentTabId = TextParser.parse(json, "currentTabId"); options.currentTabIndex = NumberParser.parse(json, "currentTabIndex"); options.hideOnScroll = BoolParser.parse(json, "hideOnScroll"); @@ -46,6 +52,11 @@ public static BottomTabsOptions parse(Context context, JSONObject json) { } public ThemeColour backgroundColor = new NullThemeColour(); + public BottomTabsLayoutStyle layoutStyle = BottomTabsLayoutStyle.LAYOUT_STYLE_UNDEFINED; + public Fraction bottomMargin = new NullFraction(); + public Fraction cornerRadius = new NullFraction(); + public Bool translucent = new NullBool(); + public Fraction blurRadius = new NullFraction(); public Bool hideOnScroll = new NullBool(); public Bool visible = new NullBool(); public Bool drawBehind = new NullBool(); @@ -79,12 +90,21 @@ void mergeWith(final BottomTabsOptions other) { if (other.shadowOptions.hasValue()) shadowOptions = shadowOptions.copy().mergeWith(other.shadowOptions); if (other.borderColor.hasValue()) borderColor = other.borderColor; if (other.backgroundColor.hasValue()) backgroundColor = other.backgroundColor; - + if (other.translucent.hasValue()) translucent = other.translucent; + if (other.blurRadius.hasValue()) blurRadius = other.blurRadius; + if (other.layoutStyle.hasValue()) layoutStyle = other.layoutStyle; + if (other.bottomMargin.hasValue()) bottomMargin = other.bottomMargin; + if (other.cornerRadius.hasValue()) cornerRadius = other.cornerRadius; } void mergeWithDefault(final BottomTabsOptions defaultOptions) { if (!borderColor.hasValue()) borderColor = defaultOptions.borderColor; if (!backgroundColor.hasValue()) backgroundColor = defaultOptions.backgroundColor; + if (!translucent.hasValue()) translucent = defaultOptions.translucent; + if (!blurRadius.hasValue()) blurRadius = defaultOptions.blurRadius; + if (!layoutStyle.hasValue()) layoutStyle = defaultOptions.layoutStyle; + if (!bottomMargin.hasValue()) bottomMargin = defaultOptions.bottomMargin; + if (!cornerRadius.hasValue()) cornerRadius = defaultOptions.cornerRadius; if (!currentTabId.hasValue()) currentTabId = defaultOptions.currentTabId; if (!currentTabIndex.hasValue()) currentTabIndex = defaultOptions.currentTabIndex; if (!hideOnScroll.hasValue()) hideOnScroll = defaultOptions.hideOnScroll; diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutDirection.java b/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutDirection.java index 2c0206a86f9..80ebe02d42c 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutDirection.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/LayoutDirection.java @@ -35,7 +35,12 @@ public boolean hasValue() { } public int get() { - return direction; + return switch (direction) { + case View.LAYOUT_DIRECTION_RTL -> RTL.direction; + case View.LAYOUT_DIRECTION_LTR -> LTR.direction; + case View.LAYOUT_DIRECTION_LOCALE -> LOCALE.direction; + default -> DEFAULT.direction; + }; } public int inverse() { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/options/params/BottomTabsLayoutStyle.kt b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/BottomTabsLayoutStyle.kt new file mode 100644 index 00000000000..8a0d287913a --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/options/params/BottomTabsLayoutStyle.kt @@ -0,0 +1,20 @@ +package com.reactnativenavigation.options.params + +enum class BottomTabsLayoutStyle { + STRETCH, + COMPACT, + LAYOUT_STYLE_UNDEFINED; + + fun hasValue(): Boolean = (this != LAYOUT_STYLE_UNDEFINED) + + companion object { + @JvmStatic + fun fromString(mode: String?): BottomTabsLayoutStyle { + return when (mode?.lowercase()) { + "stretch" -> STRETCH + "compact" -> COMPACT + else -> LAYOUT_STYLE_UNDEFINED + } + } + } +} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/ColorUtils.java b/lib/android/app/src/main/java/com/reactnativenavigation/utils/ColorUtils.java index 99cab710c7a..b0f15b6f26d 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/utils/ColorUtils.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/ColorUtils.java @@ -21,4 +21,8 @@ public static boolean isColorLight(int color) { public static int setAlpha(int color, int alpha) { return (color & 0x00FFFFFF) | (alpha << 24); } + + public static boolean isOpaque(int color) { + return (color & 0xFF000000) == 0xFF000000; + } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java index a10201e5db4..78be5f656c2 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java @@ -63,7 +63,15 @@ public void onConfigurationChanged(Configuration newConfig) { tabPresenter.onConfigurationChanged(resolveCurrentOptions()); } - public BottomTabsController(Activity activity, List> tabs, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, ImageLoader imageLoader, String id, Options initialOptions, Presenter presenter, BottomTabsAttacher tabsAttacher, BottomTabsPresenter bottomTabsPresenter, BottomTabPresenter bottomTabPresenter) { + public BottomTabsController(Activity activity, + List> tabs, + ChildControllersRegistry childRegistry, + EventEmitter eventEmitter, + ImageLoader imageLoader, + String id, Options initialOptions, + Presenter presenter, + BottomTabsAttacher tabsAttacher, + BottomTabsPresenter bottomTabsPresenter, BottomTabPresenter bottomTabPresenter) { super(activity, childRegistry, id, presenter, initialOptions); this.tabs = tabs; this.eventEmitter = eventEmitter; @@ -86,15 +94,17 @@ public void setDefaultOptions(Options defaultOptions) { @Override public BottomTabsLayout createView() { BottomTabsLayout root = new BottomTabsLayout(getActivity()); + root.setTag("RNN.BottomTabsLayoutRoot"); this.bottomTabsContainer = createBottomTabsContainer(); this.bottomTabs = bottomTabsContainer.getBottomTabs(); Options resolveCurrentOptions = resolveCurrentOptions(); tabsAttacher.init(root, resolveCurrentOptions); - presenter.bindView(bottomTabsContainer, this); + presenter.bindView(bottomTabsContainer, root, this); tabPresenter.bindView(bottomTabs); bottomTabs.setOnTabSelectedListener(this); root.addBottomTabsContainer(bottomTabsContainer); + bottomTabs.addItems(createTabs()); setInitialTab(resolveCurrentOptions); tabsAttacher.attach(); diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt index 7d288dc1a43..c2e435e8497 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt @@ -3,13 +3,20 @@ package com.reactnativenavigation.viewcontrollers.bottomtabs import android.animation.Animator import android.graphics.Color import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.annotation.IntRange import androidx.core.view.updateMargins import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState +import com.reactnativenavigation.RNNFeatureToggles +import com.reactnativenavigation.RNNToggles.TAB_BAR_TRANSLUCENCE import com.reactnativenavigation.options.Options +import com.reactnativenavigation.options.params.BottomTabsLayoutStyle +import com.reactnativenavigation.options.params.Fraction +import com.reactnativenavigation.utils.UiUtils import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController import com.reactnativenavigation.views.bottomtabs.BottomTabs import com.reactnativenavigation.views.bottomtabs.BottomTabsContainer +import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout import kotlin.math.max import kotlin.math.roundToInt @@ -20,12 +27,13 @@ class BottomTabsPresenter( ) { private val bottomTabFinder: BottomTabFinder = BottomTabFinder(tabs) private lateinit var bottomTabsContainer: BottomTabsContainer + private lateinit var bottomTabsLayout: BottomTabsLayout private lateinit var bottomTabs: BottomTabs private lateinit var tabSelector: TabSelector private val defaultTitleState: TitleState get() { for (i in 0 until bottomTabs.itemsCount) { - if (bottomTabs.getItem(i).hasIcon()) return TitleState.SHOW_WHEN_ACTIVE + if (bottomTabs.getItem(i)?.hasIcon() == true) return TitleState.SHOW_WHEN_ACTIVE } return TitleState.ALWAYS_SHOW } @@ -34,8 +42,9 @@ class BottomTabsPresenter( this.defaultOptions = defaultOptions } - fun bindView(bottomTabsContainer: BottomTabsContainer, tabSelector: TabSelector) { + fun bindView(bottomTabsContainer: BottomTabsContainer, bottomTabsLayout: BottomTabsLayout, tabSelector: TabSelector) { this.bottomTabsContainer = bottomTabsContainer + this.bottomTabsLayout = bottomTabsLayout this.bottomTabs = bottomTabsContainer.bottomTabs this.tabSelector = tabSelector animator.bindView(this.bottomTabs) @@ -65,14 +74,51 @@ class BottomTabsPresenter( private fun mergeBottomTabsOptions(options: Options, view: ViewController<*>) { val bottomTabsOptions = options.bottomTabsOptions - if (options.layout.direction.hasValue()) bottomTabs.setLayoutDirection(options.layout.direction) - if (bottomTabsOptions.preferLargeIcons.hasValue()) bottomTabs.setPreferLargeIcons(bottomTabsOptions.preferLargeIcons.get()) - if (bottomTabsOptions.titleDisplayMode.hasValue()) { - bottomTabs.titleState = bottomTabsOptions.titleDisplayMode.toState() + + if (bottomTabsOptions.layoutStyle == BottomTabsLayoutStyle.COMPACT) { + bottomTabs.layoutParams.width = WRAP_CONTENT + bottomTabs.refresh() + } + + if (bottomTabsOptions.bottomMargin.hasValue()) { + val margin = extractBottomMarginPx(bottomTabsOptions.bottomMargin) + bottomTabsLayout.setBottomMargin(margin) + } + + if (bottomTabsOptions.cornerRadius.hasValue()) { + val radius = extractCornerRadius(bottomTabsOptions.cornerRadius) + bottomTabsContainer.setRoundedCorners(radius) } + + // Keep this before the translucent check below if (bottomTabsOptions.backgroundColor.hasValue()) { bottomTabsContainer.setBackgroundColor(bottomTabsOptions.backgroundColor.get()) } + + if (RNNFeatureToggles.isEnabled(TAB_BAR_TRANSLUCENCE)) { + if (bottomTabsOptions.translucent.isTrue) { + if (bottomTabsOptions.blurRadius.hasValue()) { + bottomTabsContainer.setBlurRadius(bottomTabsOptions.blurRadius.get().toFloat()) + } + if (bottomTabsOptions.backgroundColor.hasValue()) { + bottomTabsContainer.setBlurColor(bottomTabsOptions.backgroundColor.get()) + } + bottomTabsContainer.enableBackgroundBlur() + } else if (bottomTabsOptions.translucent.isFalse) { + bottomTabsContainer.disableBackgroundBlur() + } + } + + if (bottomTabsOptions.elevation.hasValue()) { + bottomTabsContainer.setElevation(bottomTabsOptions.elevation) + } + + if (options.layout.direction.hasValue()) bottomTabs.setLayoutDirection(options.layout.direction) + if (bottomTabsOptions.preferLargeIcons.hasValue()) bottomTabs.setPreferLargeIcons(bottomTabsOptions.preferLargeIcons.get()) + if (bottomTabsOptions.titleDisplayMode.hasValue()) { + bottomTabs.setTitleState(bottomTabsOptions.titleDisplayMode.toState()) + } + if (bottomTabsOptions.animateTabSelection.hasValue()) { bottomTabs.setAnimateTabSelection(bottomTabsOptions.animateTabSelection.get()) } @@ -88,7 +134,7 @@ class BottomTabsPresenter( if (tabIndex >= 0) tabSelector.selectTab(tabIndex) } if (bottomTabsOptions.hideOnScroll.hasValue()) { - bottomTabs.isBehaviorTranslationEnabled = bottomTabsOptions.hideOnScroll.get() + bottomTabs.setBehaviorTranslationEnabled(bottomTabsOptions.hideOnScroll.get()) } if (bottomTabsOptions.borderColor.hasValue()) { @@ -140,10 +186,39 @@ class BottomTabsPresenter( private fun applyBottomTabsOptions(options: Options) { val bottomTabsOptions = options.bottomTabsOptions + if (bottomTabsOptions.layoutStyle == BottomTabsLayoutStyle.COMPACT) { + bottomTabs.layoutParams.width = WRAP_CONTENT + } + + if (bottomTabsOptions.bottomMargin.hasValue()) { + val margin = extractBottomMarginPx(bottomTabsOptions.bottomMargin) + bottomTabsLayout.setBottomMargin(margin) + } + + if (bottomTabsOptions.cornerRadius.hasValue()) { + val radius = extractCornerRadius(bottomTabsOptions.cornerRadius) + bottomTabsContainer.setRoundedCorners(radius) + } else { + bottomTabsContainer.clearRoundedCorners() + } + + if (RNNFeatureToggles.isEnabled(TAB_BAR_TRANSLUCENCE) && bottomTabsOptions.translucent.isTrue) { + if (bottomTabsOptions.blurRadius.hasValue()) { + bottomTabsContainer.setBlurRadius(bottomTabsOptions.blurRadius.get().toFloat()) + } + if (bottomTabsOptions.backgroundColor.hasValue()) { + bottomTabsContainer.setBlurColor(bottomTabsOptions.backgroundColor.get()) + } + bottomTabsContainer.enableBackgroundBlur() + } else { + bottomTabsContainer.disableBackgroundBlur() + bottomTabsContainer.setBackgroundColor(bottomTabsOptions.backgroundColor.get(Color.WHITE)!!) + } + bottomTabs.setLayoutDirection(options.layout.direction) bottomTabs.setPreferLargeIcons(options.bottomTabsOptions.preferLargeIcons[false]) - bottomTabs.titleState = bottomTabsOptions.titleDisplayMode[defaultTitleState] - bottomTabsContainer.setBackgroundColor(bottomTabsOptions.backgroundColor.get(Color.WHITE)!!) + bottomTabs.setTitleState(bottomTabsOptions.titleDisplayMode[defaultTitleState]) + bottomTabs.setAnimateTabSelection(bottomTabsOptions.animateTabSelection.get(true)) if (bottomTabsOptions.currentTabIndex.hasValue()) { val tabIndex = bottomTabsOptions.currentTabIndex.get() @@ -174,6 +249,7 @@ class BottomTabsPresenter( bottomTabs.hideBottomNavigation(false) } } + if (bottomTabsOptions.elevation.hasValue()) { bottomTabsContainer.setElevation(bottomTabsOptions.elevation) } @@ -203,7 +279,7 @@ class BottomTabsPresenter( } else { bottomTabsContainer.clearShadow() } - bottomTabs.isBehaviorTranslationEnabled = bottomTabsOptions.hideOnScroll[false] + bottomTabs.setBehaviorTranslationEnabled(bottomTabsOptions.hideOnScroll[false]) } fun applyBottomInset(bottomInset: Int) { @@ -246,7 +322,37 @@ class BottomTabsPresenter( fun onConfigurationChanged(options: Options) { val bottomTabsOptions = options.withDefaultOptions(defaultOptions).bottomTabsOptions - bottomTabs.setBackgroundColor(bottomTabsOptions.backgroundColor.get(Color.WHITE)!!) + + if (bottomTabsOptions.layoutStyle == BottomTabsLayoutStyle.COMPACT) { + bottomTabs.layoutParams.width = WRAP_CONTENT + } + + if (bottomTabsOptions.bottomMargin.hasValue()) { + val margin = extractBottomMarginPx(bottomTabsOptions.bottomMargin) + bottomTabsLayout.setBottomMargin(margin) + } + + if (bottomTabsOptions.cornerRadius.hasValue()) { + val radius = extractCornerRadius(bottomTabsOptions.cornerRadius) + bottomTabsContainer.setRoundedCorners(radius) + } else { + bottomTabsContainer.clearRoundedCorners() + } + + if (RNNFeatureToggles.isEnabled(TAB_BAR_TRANSLUCENCE) && bottomTabsOptions.translucent.isTrue) { + if (bottomTabsOptions.blurRadius.hasValue()) { + bottomTabsContainer.setBlurRadius(bottomTabsOptions.blurRadius.get().toFloat()) + } + if (bottomTabsOptions.backgroundColor.hasValue()) { + bottomTabsContainer.setBlurColor(bottomTabsOptions.backgroundColor.get()) + } + bottomTabsContainer.enableBackgroundBlur() + } else { + bottomTabsContainer.disableBackgroundBlur() + + // TODO Change to bottomTabsContainer.setBackgroundColor()? + bottomTabs.setBackgroundColor(bottomTabsOptions.backgroundColor.get(Color.WHITE)!!) + } if (bottomTabsOptions.shadowOptions.hasValue()) { if (bottomTabsOptions.shadowOptions.color.hasValue()) @@ -258,4 +364,16 @@ class BottomTabsPresenter( bottomTabsContainer.showTopLine() } } + + private fun extractCornerRadius(cornerRadius: Fraction): Float { + val radiusDp = cornerRadius.get() + val radius = UiUtils.dpToPx(bottomTabsContainer.context, radiusDp.toFloat()) + return radius + } + + private fun extractBottomMarginPx(bottomMargin: Fraction): Int { + val marginDp = bottomMargin.get() + val margin = UiUtils.dpToPx(bottomTabsContainer.context, marginDp.toFloat()).roundToInt() + return margin + } } \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java index 8e63201baf4..e5730af4ef5 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java @@ -84,8 +84,9 @@ public ScrollEventListener getScrollEventListener() { @Override public void onViewWillAppear() { super.onViewWillAppear(); - if (view != null) + if (view != null) { view.sendComponentWillStart(); + } } @Override diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java index feef12ac12d..c4e530cd981 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java @@ -1,5 +1,8 @@ package com.reactnativenavigation.views.bottomtabs; +import static com.reactnativenavigation.utils.CollectionUtils.forEach; +import static com.reactnativenavigation.utils.ViewUtils.findChildByClass; + import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Color; @@ -7,6 +10,8 @@ import android.view.View; import android.widget.LinearLayout; +import androidx.annotation.IntRange; + import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; import com.reactnativenavigation.R; @@ -15,11 +20,6 @@ import java.util.ArrayList; import java.util.List; -import androidx.annotation.IntRange; - -import static com.reactnativenavigation.utils.CollectionUtils.*; -import static com.reactnativenavigation.utils.ViewUtils.findChildByClass; - @SuppressLint("ViewConstructor") public class BottomTabs extends AHBottomNavigation { private boolean itemsCreationEnabled = true; @@ -60,6 +60,7 @@ protected void createItems() { } } + // TODO Find a better way to do this public void superCreateItems() { super.createItems(); } @@ -77,7 +78,6 @@ public void setCurrentItem(@IntRange(from = 0) int position, boolean useCallback onItemCreationEnabled.add(() -> super.setCurrentItem(position, useCallback)); } } - @Override public void setTitleState(TitleState titleState) { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainer.kt b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainer.kt index 52eb14c376c..d992f567313 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainer.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainer.kt @@ -3,12 +3,20 @@ package com.reactnativenavigation.views.bottomtabs import android.annotation.SuppressLint import android.content.Context import android.graphics.Color +import android.graphics.Outline +import android.util.Log import android.view.View +import android.view.ViewOutlineProvider +import android.widget.FrameLayout.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout.LayoutParams.WRAP_CONTENT import android.widget.LinearLayout import androidx.annotation.RestrictTo import androidx.core.graphics.ColorUtils import com.reactnativenavigation.options.params.Fraction import com.reactnativenavigation.utils.UiUtils.dpToPx +import eightbitlab.com.blurview.BlurTarget +import eightbitlab.com.blurview.BlurView +import java.lang.ref.WeakReference import kotlin.math.roundToInt @@ -20,16 +28,24 @@ internal const val DEFAULT_SHADOW_ANGLE = 270f internal const val DEFAULT_TOP_OUTLINE_SIZE_PX = 1 internal const val DEFAULT_TOP_OUTLINE_COLOR = Color.DKGRAY +private const val LOG_TAG = "BottomTabs" + class TopOutlineView(context: Context) : View(context) @SuppressLint("ViewConstructor") class BottomTabsContainer(context: Context, val bottomTabs: BottomTabs) : ShadowLayout(context) { + private val blurringView: BlurView + private var blurringSurface = WeakReference(null) + private var blurEnabled: Boolean? = null + private var blurRadius: Float? = null + private var blurColor: Int? = null + var topOutLineView = TopOutlineView(context) @RestrictTo(RestrictTo.Scope.TESTS, RestrictTo.Scope.SUBCLASSES) get @RestrictTo(RestrictTo.Scope.TESTS) set(value) { this.removeView(field) - addView(value, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)) + addView(value, LayoutParams(MATCH_PARENT, WRAP_CONTENT)) field = value } @@ -39,17 +55,23 @@ class BottomTabsContainer(context: Context, val bottomTabs: BottomTabs) : Shadow shadowAngle = DEFAULT_SHADOW_ANGLE shadowDistance = DEFAULT_SHADOW_DISTANCE shadowColor = DEFAULT_SHADOW_COLOR + val linearLayout = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL - this.addView(topOutLineView, LayoutParams(LayoutParams.MATCH_PARENT, DEFAULT_TOP_OUTLINE_SIZE_PX)) - this.addView(bottomTabs, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + addView(topOutLineView, LayoutParams(MATCH_PARENT, DEFAULT_TOP_OUTLINE_SIZE_PX)) + addView(bottomTabs, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) } + this.clipChildren = false this.clipToPadding = false setTopOutLineColor(DEFAULT_TOP_OUTLINE_COLOR) this.topOutLineView.visibility = View.GONE - this.addView(linearLayout, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + blurringView = BlurView(context).apply { + setBlurEnabled(false) + addView(linearLayout, WRAP_CONTENT, WRAP_CONTENT) + } + addView(blurringView, WRAP_CONTENT, WRAP_CONTENT) } override var shadowRadius: Float @@ -70,6 +92,63 @@ class BottomTabsContainer(context: Context, val bottomTabs: BottomTabs) : Shadow shadowColor = ColorUtils.setAlphaComponent(shadowColor, (opacity * 0xFF).roundToInt()) } + fun setBlurSurface(blurSurface: BlurTarget) { + if (blurSurface == blurringSurface.get()) { + return + } + + blurringSurface = WeakReference(blurSurface) + blurringView + .setupWith(blurSurface) + .setBlurEnabled(blurEnabled == true).apply { + if (blurRadius != null) { + setBlurRadius(blurRadius!!) + } + + if (blurColor != null) { + setOverlayColor(blurColor!!) + } + } + } + + fun enableBackgroundBlur() { + blurringView.setBlurEnabled(true) + blurEnabled = true + } + + fun disableBackgroundBlur() { + blurringView.setBlurEnabled(false) + blurEnabled = false + } + + fun setBlurRadius(radius: Float) { + blurringView.setBlurRadius(radius) + blurRadius = radius + } + + fun setBlurColor(color: Int) { + if (com.reactnativenavigation.utils.ColorUtils.isOpaque(color)) { + Log.w(LOG_TAG, "Opaque color (#${Integer.toHexString(color)}) set alongside blur-effect in bottom-tabs, rendering blur futile") + } + + blurringView.setOverlayColor(color) + blurColor = color + } + + fun setRoundedCorners(radius: Float) { + this.outlineProvider = object: ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect(0, 0, view.width, view.height, radius) + } + } + this.clipToOutline = true + } + + fun clearRoundedCorners() { + this.outlineProvider = null + this.clipToOutline = true + } + fun showShadow() { isShadowed = true } @@ -98,4 +177,3 @@ class BottomTabsContainer(context: Context, val bottomTabs: BottomTabs) : Shadow setElevation(dpToPx(context, elevation.get().toFloat())) } } - diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsLayout.java b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsLayout.java index 926e4590d60..d5ddc72f205 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsLayout.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabsLayout.java @@ -1,18 +1,34 @@ package com.reactnativenavigation.views.bottomtabs; +import static android.view.Gravity.CENTER_HORIZONTAL; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + import android.content.Context; +import android.graphics.Color; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.widget.LinearLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import com.reactnativenavigation.RNNFeatureToggles; +import com.reactnativenavigation.RNNToggles; + +import eightbitlab.com.blurview.BlurTarget; +/** + * Implementation note: The space-view and its containing linear-layout is a trick meant to force + * a bottom margin layout which isn't based on a `MarginLayoutParams.bottomMargin`. That's because + * it is not always honored by the CoordinatorLayout. This appears to be an ok work-around (which + * BTW actually would have been an idiomatic solution in ComposeUI). + */ public class BottomTabsLayout extends CoordinatorLayout { private BottomTabsContainer bottomTabsContainer; + private View spaceView; + private BlurTarget blurSurface; public BottomTabsLayout(Context context) { super(context); @@ -21,16 +37,52 @@ public BottomTabsLayout(Context context) { @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (bottomTabsContainer != null && child != bottomTabsContainer) { - super.addView(child, getChildCount() - 1, params); + if (RNNFeatureToggles.isEnabled(RNNToggles.TAB_BAR_TRANSLUCENCE)) { + // As loosely explained in BlurView's README, the blur *view* and blur *target* must + // reside in different sub-sections of the view-hierarchy + lazyInitBlurSurface(); + blurSurface.addView(child, -1, params); + } else { + super.addView(child, getChildCount() - 1, params); + } } else { super.addView(child, 0, params); } + + if (bottomTabsContainer != null && blurSurface != null) { + bottomTabsContainer.setBlurSurface(blurSurface); + } } public void addBottomTabsContainer(BottomTabsContainer bottomTabsContainer) { - CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); - lp.gravity = Gravity.BOTTOM; - addView(bottomTabsContainer, lp); + View spaceView = new View(getContext()); + spaceView.setLayoutParams(new LinearLayout.LayoutParams(WRAP_CONTENT, 0)); + + LinearLayout surface = new LinearLayout(getContext()); + surface.setOrientation(LinearLayout.VERTICAL); + surface.setGravity(CENTER_HORIZONTAL); + surface.setBackgroundColor(Color.TRANSPARENT); + surface.addView(bottomTabsContainer, new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + surface.addView(spaceView); + + // Note: Width should always be WRAP_CONTENT so we could delegate the width-related decision + // making to the bottom-tabs view hierarchy itself (i.e. based on user properties). + CoordinatorLayout.LayoutParams lp = new CoordinatorLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); + lp.gravity = Gravity.BOTTOM | CENTER_HORIZONTAL; + addView(surface, -1, lp); + this.bottomTabsContainer = bottomTabsContainer; + this.spaceView = spaceView; + } + + public void setBottomMargin(int bottomMargin) { + ((MarginLayoutParams) spaceView.getLayoutParams()).bottomMargin = bottomMargin; + } + + private void lazyInitBlurSurface() { + if (blurSurface == null) { + blurSurface = new BlurTarget(getContext()); + super.addView(blurSurface, super.getChildCount() - 1, new CoordinatorLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt index d2a46fb043f..9d48a48fd1c 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/views/bottomtabs/ShadowLayout.kt @@ -13,7 +13,7 @@ private const val MAX_ANGLE = 360.0f private const val MIN_RADIUS = 0.1f private const val MIN_ANGLE = 0.0f -open class ShadowLayout constructor(context: Context) : FrameLayout(context) { +open class ShadowLayout(context: Context) : FrameLayout(context) { private val paint: Paint = Paint(ANTI_ALIAS_FLAG).apply { isDither = true isFilterBitmap = true @@ -43,7 +43,7 @@ open class ShadowLayout constructor(context: Context) : FrameLayout(context) { var isShadowed: Boolean = false set(isShadowed) { field = isShadowed - this.updatePadding() + updatePadding() postInvalidate() } @@ -108,11 +108,8 @@ open class ShadowLayout constructor(context: Context) : FrameLayout(context) { if (isShadowed) { if (invalidateShadow) { if (bounds.width() != 0 && bounds.height() != 0) { - bitmap = Bitmap.createBitmap( - bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888 - ) - bitmap?.let { - mainCanvas.setBitmap(bitmap) + bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888).also { + mainCanvas.setBitmap(it) invalidateShadow = false super.dispatchDraw(mainCanvas) @@ -135,5 +132,4 @@ open class ShadowLayout constructor(context: Context) : FrameLayout(context) { super.dispatchDraw(canvas) } - } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java b/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java index 577ce4ad44d..62f0966d0f4 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java @@ -1,5 +1,7 @@ package com.reactnativenavigation.mocks; +import static com.reactnativenavigation.utils.ObjectUtils.perform; + import android.app.Activity; import android.content.Context; import android.view.MotionEvent; @@ -18,8 +20,6 @@ import org.mockito.Mockito; -import static com.reactnativenavigation.utils.ObjectUtils.perform; - public class SimpleViewController extends ChildController { private final ComponentPresenterBase presenter = new ComponentPresenterBase(); @@ -91,14 +91,10 @@ public ReactView asView() { } @Override - public void destroy() { - - } + public void destroy() {} @Override - public void sendOnNavigationButtonPressed(String buttonId) { - - } + public void sendOnNavigationButtonPressed(String buttonId) {} @Override public ScrollEventListener getScrollEventListener() { @@ -106,8 +102,6 @@ public ScrollEventListener getScrollEventListener() { } @Override - public void dispatchTouchEventToJs(MotionEvent event) { - - } + public void dispatchTouchEventToJs(MotionEvent event) {} } } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.kt index 207e7768513..aa4636fe303 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.kt @@ -4,10 +4,11 @@ import android.app.Activity import android.graphics.Color import android.view.Gravity import android.view.View +import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams import android.widget.FrameLayout import androidx.coordinatorlayout.widget.CoordinatorLayout -import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState +import com.aurelhubert.ahbottomnavigation.AHBottomNavigation import com.reactnativenavigation.BaseTest import com.reactnativenavigation.TestUtils import com.reactnativenavigation.mocks.ImageLoaderMock.mock @@ -79,11 +80,15 @@ class BottomTabsControllerTest : BaseTest() { fun createView_checkProperStructure() { idleMainLooper() Java6Assertions.assertThat(uut.view).isInstanceOf(CoordinatorLayout::class.java) - Java6Assertions.assertThat(uut.view.getChildAt(uut.view.childCount - 1)).isInstanceOf( - BottomTabsContainer::class.java - ) - Java6Assertions.assertThat((uut.bottomTabsContainer.layoutParams as CoordinatorLayout.LayoutParams).gravity) - .isEqualTo(Gravity.BOTTOM) + + val tabContainerWrapper = uut.view.getChildAt(uut.view.childCount - 1) + Java6Assertions.assertThat(tabContainerWrapper).isInstanceOf(ViewGroup::class.java) + + val tabContainer = (tabContainerWrapper as ViewGroup).getChildAt(0) + Java6Assertions.assertThat(tabContainer).isInstanceOf(BottomTabsContainer::class.java) + + Java6Assertions.assertThat((tabContainerWrapper.layoutParams as CoordinatorLayout.LayoutParams).gravity) + .isEqualTo(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) } @Test @@ -99,7 +104,8 @@ class BottomTabsControllerTest : BaseTest() { Java6Assertions.assertThat(tabOptions.bottomTabsOptions.titleDisplayMode.hasValue()).isFalse prepareViewsForTests() presenter.applyOptions(Options.EMPTY) - Java6Assertions.assertThat(bottomTabsContainer.bottomTabs.titleState).isEqualTo(TitleState.ALWAYS_SHOW) + Java6Assertions.assertThat(bottomTabsContainer.bottomTabs.titleState).isEqualTo( + AHBottomNavigation.TitleState.ALWAYS_SHOW) } @Test(expected = RuntimeException::class) diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenterTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenterTest.kt index 8d85b72c5f9..d2ae5a3aedd 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenterTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsPresenterTest.kt @@ -16,6 +16,7 @@ import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController import com.reactnativenavigation.views.bottomtabs.BottomTabs import com.reactnativenavigation.views.bottomtabs.BottomTabsContainer +import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout import org.assertj.core.api.Java6Assertions.assertThat import org.junit.Ignore import org.junit.Test @@ -35,6 +36,7 @@ class BottomTabsPresenterTest : BaseTest() { private lateinit var uut: BottomTabsPresenter private lateinit var bottomTabs: BottomTabs private lateinit var bottomTabsContainer: BottomTabsContainer + private lateinit var bottomTabsLayout: BottomTabsLayout private lateinit var animator: BottomTabsAnimator private lateinit var tabSelector: TabSelector @@ -46,12 +48,13 @@ class BottomTabsPresenterTest : BaseTest() { val child2 = spy(SimpleViewController(activity, childRegistry, "child2", Options())) tabs = listOf(child1, child2) bottomTabsContainer = mock() + bottomTabsLayout = mock() bottomTabs = mock() whenever(bottomTabsContainer.bottomTabs).thenReturn(bottomTabs) animator = spy(BottomTabsAnimator(bottomTabs)) uut = BottomTabsPresenter(tabs, Options(), animator) tabSelector = mock() - uut.bindView(bottomTabsContainer, tabSelector) + uut.bindView(bottomTabsContainer, bottomTabsLayout, tabSelector) } @Test diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainerTest.kt index 134c3948d4a..c32204ad82d 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/views/bottomtabs/BottomTabsContainerTest.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.view.View +import android.view.ViewGroup import android.widget.LinearLayout import androidx.core.graphics.ColorUtils import org.mockito.kotlin.* @@ -26,12 +27,18 @@ class BottomTabsContainerTest : BaseTest() { @Test fun `init - should have only one child as vertical LinearLayout with border and bottom tabs`() { - assertThat(uut.childCount).isEqualTo(1) - val childAt = uut.getChildAt(0) + val blurWrapper = uut.getChildAt(0) as ViewGroup + assertThat(blurWrapper).isInstanceOf(ViewGroup::class.java) + + val childAt = blurWrapper.getChildAt(0) assertThat(childAt).isInstanceOf(LinearLayout::class.java) + val linearLayout = childAt as LinearLayout assertThat(linearLayout.getChildAt(0)).isInstanceOf(TopOutlineView::class.java) assertThat(linearLayout.getChildAt(1)).isInstanceOf(BottomTabs::class.java) + + assertThat(uut.childCount).isEqualTo(1) + assertThat(blurWrapper.childCount).isEqualTo(1) } @Test diff --git a/lib/src/interfaces/Options.ts b/lib/src/interfaces/Options.ts index c8cd19fbd76..05320abfcaa 100644 --- a/lib/src/interfaces/Options.ts +++ b/lib/src/interfaces/Options.ts @@ -855,7 +855,9 @@ export interface OptionsBottomTabs { */ drawBehind?: boolean; /** - * Set a background color for the bottom tabs + * Set a background color for the bottom tabs.
+ * On Android - also applicable when translucence is applied, but a semi-transparent + * color should be used (e.g. `rgba(255, 0, 0, 0.25)`). */ backgroundColor?: Color; /** @@ -871,10 +873,42 @@ export interface OptionsBottomTabs { */ barStyle?: 'default' | 'black'; /** - * Allows the Bottom Tabs to be translucent (blurred) - * #### (iOS specific) + * Control the way the bottom tabs are laid out. + * - `stretch`: Fill the entire width of the screen. + * - `compact`: Occupy the minimum width needed to hold tab buttons. Recommended for + * usage in conjunction with `drawBehind: true`. + * + * #### (Android specific) + * @default 'stretch' + */ + layoutStyle?: 'stretch' | 'compact'; + /** + * Specify a corner-radius (in dip) in order to apply round corners to the tabs container.
+ * Mainly suitable when used in conjunction with `layoutStyle: 'compact'` + * #### (Android specific) + */ + cornerRadius?: AndroidDensityNumber; + /** + * Bottom-margin to set in order to apply a "hover" effect. + * Works best when used in conjunction with `layoutStyle: 'compact'` and `drawBehind: true`. + * #### (Android specific) + */ + bottomMargin?: AndroidDensityNumber; + /** + * Allows the bottom tabs to be translucent (blurred). Doesn't necessarily play + * nice with shadow effects on Android. + * #### Android: experimental, turn on using native toggle `TAB_BAR_TRANSLUCENCE`. */ translucent?: boolean; + /** + * Set a custom radius to be used in the blur effect. Higher is blurrier, but + * also more CPU-intensive.
+ * Note: The blurring is performed following a bitmap downscale of x4.0, so + * ultimately the actual radius is (4*blurRadius). + * #### (Android specific) + * @defaultValue 1.0 + */ + blurRadius?: AndroidDensityNumber; /** * Hide the top line of the Tab Bar * #### (iOS specific) diff --git a/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.kt b/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.kt index 7cba406ff7b..c85832d45df 100644 --- a/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.kt +++ b/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.kt @@ -11,8 +11,9 @@ import com.reactnativenavigation.react.NavigationPackage import com.reactnativenavigation.react.NavigationReactNativeHost class MainApplication : NavigationApplication(mapOf( - RNNToggles.TOP_BAR_COLOR_ANIMATION__PUSH to true, - RNNToggles.TOP_BAR_COLOR_ANIMATION__TABS to true + RNNToggles.TOP_BAR_COLOR_ANIMATION__PUSH to true, + RNNToggles.TOP_BAR_COLOR_ANIMATION__TABS to true, + RNNToggles.TAB_BAR_TRANSLUCENCE to true, )) { override val reactNativeHost: ReactNativeHost = object : NavigationReactNativeHost(this) { override fun getJSMainModuleName(): String { diff --git a/playground/src/components/Root.tsx b/playground/src/components/Root.tsx index 780edcadddb..d51c00917fe 100644 --- a/playground/src/components/Root.tsx +++ b/playground/src/components/Root.tsx @@ -26,7 +26,10 @@ type RootProps = { const Root = ({ children, componentId, footer, style, testID, onLayout }: RootProps) => ( - + {children}