Skip to content

Commit

Permalink
Fix android top bar not extending behind status bar (wix#6991)
Browse files Browse the repository at this point in the history
* Fix android top bar not extending behind status bar

Status bar now extends top portion behind status bar when
drawbehind true instead of leaving default white area

* condensed top bar top extension logic

* added tests

* test for status bar draw behind

* add mock status bar uitls

* Update UiUtils.java

* Update StatusBarUtils.java

* Update StackPresenter.java

Co-authored-by: bma <bma@icims.com>
Co-authored-by: Ward Abbass <warda@wix.com>
Co-authored-by: Ward Abbass <swabbass@gmail.com>
  • Loading branch information
4 people authored Aug 9, 2021
1 parent a71b917 commit e63bfd2
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 23 deletions.
4 changes: 2 additions & 2 deletions lib/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ dependencies {
testImplementation "org.robolectric:robolectric:4.4"
testImplementation 'org.assertj:assertj-core:3.8.0'
testImplementation 'com.squareup.assertj:assertj-android:1.1.1'
testImplementation 'org.mockito:mockito-core:2.28.2'

testImplementation 'org.mockito:mockito-core:3.4.0'
testImplementation 'org.mockito:mockito-inline:3.4.0'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static com.reactnativenavigation.utils.UiUtils.dpToPx;

import androidx.annotation.VisibleForTesting;

public class StatusBarUtils {
private static final int STATUS_BAR_HEIGHT_M = 24;
private static final int STATUS_BAR_HEIGHT_L = 25;
Expand All @@ -30,6 +32,10 @@ public static int getStatusBarHeight(Context context) {
return statusBarHeight;
}

public static int getStatusBarHeightDp(Context context) {
return (int) UiUtils.pxToDp(context, getStatusBarHeight(context));
}

public static boolean isTranslucent(Window window) {
WindowManager.LayoutParams lp = window.getAttributes();
return lp != null && (lp.flags & FLAG_TRANSLUCENT_STATUS) == FLAG_TRANSLUCENT_STATUS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

public class UiUtils {
private static final int DEFAULT_TOOLBAR_HEIGHT = 56;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ public void applyInitialChildLayoutOptions(Options options) {
applyTopBarVisibility(withDefault.topBar);
}

public void applyChildOptions(Options options, StackController stack, ViewController child) {
Options withDefault = options.copy().withDefaultOptions(defaultOptions);
applyOrientation(withDefault.layout.orientation);
applyButtons(withDefault.topBar, child);
applyTopBarOptions(withDefault, stack, child);
applyTopTabsOptions(withDefault.topTabs);
applyTopTabOptions(withDefault.topTabOptions);
public void applyChildOptions(Options currentChildOptions, StackController stack, ViewController child) {
Options finalChildOptions = currentChildOptions.copy().withDefaultOptions(defaultOptions);
applyOrientation(finalChildOptions.layout.orientation);
applyButtons(finalChildOptions.topBar, child);
applyTopBarOptions(finalChildOptions, stack, child);
applyTopTabsOptions(finalChildOptions.topTabs);
applyTopTabOptions(finalChildOptions.topTabOptions);
}

public void applyOrientation(OrientationOptions options) {
Expand All @@ -196,9 +196,11 @@ private void applyTopBarOptions(Options options, StackController stack, ViewCont
final View component = child.getView();
TopBarOptions topBarOptions = options.topBar;

Options withDefault = stack.resolveChildOptions(child).withDefaultOptions(defaultOptions);

topBar.setTestId(topBarOptions.testId.get(""));
topBar.setLayoutDirection(options.layout.direction);
topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)));
applyStatusBarDrawBehindOptions(topBarOptions, withDefault);
topBar.setElevation(topBarOptions.elevation.get(DEFAULT_ELEVATION));
if (topBarOptions.topMargin.hasValue() && topBar.getLayoutParams() instanceof MarginLayoutParams) {
((MarginLayoutParams) topBar.getLayoutParams()).topMargin = UiUtils.dpToPx(activity, topBarOptions.topMargin.get(0));
Expand Down Expand Up @@ -254,6 +256,16 @@ private void applyTopBarOptions(Options options, StackController stack, ViewCont
}
}

private void applyStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options withDefault) {
if(withDefault.statusBar.visible.isTrueOrUndefined() && withDefault.statusBar.drawBehind.isTrue()){
topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity));
topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity));
} else {
topBar.setTopPadding(0);
topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)));
}
}

@Nullable
private View findBackgroundComponent(ComponentOptions component) {
for (TopBarBackgroundViewController controller : backgroundControllers.values()) {
Expand Down Expand Up @@ -492,7 +504,7 @@ private void mergeTopBarOptions(TopBarOptions resolveOptions, Options options, S
if (topBarOptions.topMargin.hasValue() && topBar.getLayoutParams() instanceof MarginLayoutParams) {
((MarginLayoutParams) topBar.getLayoutParams()).topMargin = UiUtils.dpToPx(activity, topBarOptions.topMargin.get());
}

applyStatusBarDrawBehindOptions(resolveOptions,options);
if (topBarOptions.title.height.hasValue()) topBar.setTitleHeight(topBarOptions.title.height.get());
if (topBarOptions.title.topMargin.hasValue()) topBar.setTitleTopMargin(topBarOptions.title.topMargin.get());
if (topBarOptions.animateLeftButtons.hasValue())
Expand Down Expand Up @@ -653,7 +665,7 @@ private int getTopBarTranslationAnimationDelta(StackController stack, ViewContro
private int getTopBarTopMargin(StackController stack, ViewController child) {
Options withDefault = stack.resolveChildOptions(child).withDefaultOptions(defaultOptions);
int topMargin = UiUtils.dpToPx(activity, withDefault.topBar.topMargin.get(0));
int statusBarInset = withDefault.statusBar.visible.isTrueOrUndefined() ? StatusBarUtils.getStatusBarHeight(child.getActivity()) : 0;
int statusBarInset = withDefault.statusBar.visible.isTrueOrUndefined() && !withDefault.statusBar.drawBehind.isTrue() ? StatusBarUtils.getStatusBarHeight(child.getActivity()) : 0;
return topMargin + statusBarInset;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,6 @@ private void mergeStatusBarVisible(View view, Bool visible, Bool drawBehind) {
}
if (flags != view.getSystemUiVisibility()) view.requestLayout();
view.setSystemUiVisibility(flags);
} else if (drawBehind.hasValue()) {
if (drawBehind.isTrue()) {
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else {
view.setSystemUiVisibility(~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ public void setHeight(int height) {
setLayoutParams(lp);
}

public void setTopPadding(int padding) {
setPadding(0, padding, 0, 0);
}

public void setTitleHeight(int height) {
int pixelHeight = UiUtils.dpToPx(getContext(), height);
ViewGroup.LayoutParams layoutParams = titleAndButtonsContainer.getLayoutParams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
import android.view.ViewGroup;

import com.reactnativenavigation.options.params.Bool;
import com.reactnativenavigation.utils.Functions;
import com.reactnativenavigation.utils.StatusBarUtils;
import com.reactnativenavigation.utils.ViewUtils;
import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;

import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
Expand All @@ -34,9 +37,12 @@

import static com.reactnativenavigation.utils.CollectionUtils.*;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import kotlin.Function;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 28, application = TestApplication.class)
public abstract class BaseTest {
Expand All @@ -54,6 +60,18 @@ public void beforeEach() {
when(NavigationApplication.instance.getResources()).thenReturn(res);
}

public void mockStatusBarUtils(int statusBarHeight,int statusBarHeightDp, Functions.Func block) {
try (MockedStatic<StatusBarUtils> theMock = Mockito.mockStatic(StatusBarUtils.class)) {
theMock.when(() -> {
StatusBarUtils.getStatusBarHeight(any());
}).thenReturn(statusBarHeight);
theMock.when(() -> {
StatusBarUtils.getStatusBarHeightDp(any());
}).thenReturn(statusBarHeightDp);
block.run();
}
}

@After
@CallSuper
public void afterEach() {
Expand All @@ -69,7 +87,7 @@ public <T extends AppCompatActivity> ActivityController<T> newActivityController
}

public void assertIsChild(ViewGroup parent, ViewController... children) {
forEach(Arrays.asList(children),c -> assertIsChild(parent, c.getView()));
forEach(Arrays.asList(children), c -> assertIsChild(parent, c.getView()));
}

public void assertIsChild(ViewGroup parent, View child) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import com.reactnativenavigation.options.params.*
import com.reactnativenavigation.options.params.Number
import com.reactnativenavigation.options.parsers.TypefaceLoader
import com.reactnativenavigation.react.CommandListenerAdapter
import com.reactnativenavigation.utils.CollectionUtils
import com.reactnativenavigation.utils.RenderChecker
import com.reactnativenavigation.utils.TitleBarHelper
import com.reactnativenavigation.utils.UiUtils
import com.reactnativenavigation.utils.*
import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry
import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarController
import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController
Expand Down Expand Up @@ -933,6 +930,59 @@ class StackPresenterTest : BaseTest() {
assertThat(buttons[0].isDestroyed).isTrue()
}

@Test
fun applyChildOptions_topBarShouldExtendBehindStatusBarWhenDrawBehind() {
val statusBarHeight = 10
val statusBarHeightDp = 20
val topBarHeightDp = 100
val options = Options().apply {
statusBar.drawBehind = Bool(true)
}
Mockito.`when`(child.resolveCurrentOptions()).thenReturn(options)
mockStatusBarUtils(statusBarHeight, statusBarHeightDp) {
uut.applyChildOptions(Options.EMPTY.copy().apply {
topBar.height = Number(topBarHeightDp)
}, parent, child)
assertThat(topBar.paddingTop).isEqualTo(statusBarHeight)
assertThat(topBar.y).isEqualTo(0f)
assertThat(topBar.layoutParams.height).isEqualTo(statusBarHeightDp + topBarHeightDp)
}
}

@Test
fun mergeChildOptions_topBarShouldExtendBehindStatusBarWhenDrawBehind() {
val statusBarHeight = 10
val statusBarHeightDp = 20
val topBarHeightDp = 100

mockStatusBarUtils(statusBarHeight, statusBarHeightDp) {
uut.mergeChildOptions(Options.EMPTY.copy().apply {
topBar.height = Number(topBarHeightDp)
statusBar.drawBehind = Bool(true)
}, Options.EMPTY, parent, child)
assertThat(topBar.paddingTop).isEqualTo(statusBarHeight)
assertThat(topBar.y).isEqualTo(0f)
assertThat(topBar.layoutParams.height).isEqualTo(statusBarHeightDp + topBarHeightDp)
}
}

@Test
fun mergeChildOptions_topBarShouldNotExtendBehindStatusBarWhenNoDrawBehind() {
val statusBarHeight = 10
val statusBarHeightDp = 20
val topBarHeightDp = 100

mockStatusBarUtils(statusBarHeight, statusBarHeightDp) {
uut.mergeChildOptions(Options.EMPTY.copy().apply {
topBar.height = Number(topBarHeightDp)
statusBar.drawBehind = Bool(false)
}, Options.EMPTY, parent, child)
assertThat(topBar.paddingTop).isEqualTo(0)
assertThat(topBar.y).isEqualTo(0f)
assertThat(topBar.layoutParams.height).isEqualTo( topBarHeightDp)
}
}

@Test
fun applyTopInsets_topBarIsDrawnUnderStatusBarIfDrawBehindIsTrue() {
val options = Options()
Expand Down

0 comments on commit e63bfd2

Please sign in to comment.