From ef58a6cdeb1c4ea90ff528af50d6d2dc572f9f28 Mon Sep 17 00:00:00 2001 From: Guy Carmeli Date: Tue, 3 Mar 2020 14:11:54 +0200 Subject: [PATCH 1/3] Support tabs without icons on Android (#5978) This commit introduces parity with iOS which allows the user to show tabs without icons. While there's no real production use case for this feature, it's helpful when getting started with a new project. --- lib/android/app/build.gradle | 2 +- .../presentation/BottomTabsPresenter.java | 12 ++- .../bottomtabs/BottomTabsController.java | 2 +- .../bottomtabs/BottomTabsControllerTest.java | 74 +++++++++++++------ 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/lib/android/app/build.gradle b/lib/android/app/build.gradle index cb32f1a8026..0ec25726328 100644 --- a/lib/android/app/build.gradle +++ b/lib/android/app/build.gradle @@ -161,7 +161,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.1.0' implementation 'com.google.android.material:material:1.2.0-alpha03' - implementation 'com.github.wix-playground:ahbottomnavigation:3.1.2' + implementation 'com.github.wix-playground:ahbottomnavigation:3.2.0' 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/presentation/BottomTabsPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/presentation/BottomTabsPresenter.java index 13f83ea5955..a9e0e751adb 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/presentation/BottomTabsPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/presentation/BottomTabsPresenter.java @@ -13,6 +13,8 @@ import com.reactnativenavigation.viewcontrollers.bottomtabs.TabSelector; import com.reactnativenavigation.views.BottomTabs; +import org.jetbrains.annotations.NotNull; + import java.util.List; import androidx.annotation.IntRange; @@ -118,7 +120,7 @@ private void applyBottomTabsOptions(Options options) { bottomTabs.setLayoutDirection(options.layout.direction); bottomTabs.setPreferLargeIcons(options.bottomTabsOptions.preferLargeIcons.get(false)); - bottomTabs.setTitleState(bottomTabsOptions.titleDisplayMode.get(TitleState.SHOW_WHEN_ACTIVE)); + bottomTabs.setTitleState(bottomTabsOptions.titleDisplayMode.get(getDefaultTitleState())); bottomTabs.setBackgroundColor(bottomTabsOptions.backgroundColor.get(Color.WHITE)); if (bottomTabsOptions.currentTabIndex.hasValue()) { int tabIndex = bottomTabsOptions.currentTabIndex.get(); @@ -148,6 +150,14 @@ private void applyBottomTabsOptions(Options options) { } } + @NotNull + private TitleState getDefaultTitleState() { + for (int i = 0; i < bottomTabs.getItemsCount(); i++) { + if (bottomTabs.getItem(i).hasIcon()) return TitleState.SHOW_WHEN_ACTIVE; + } + return TitleState.ALWAYS_SHOW; + } + public void applyBottomInset(int bottomInset) { ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) bottomTabs.getLayoutParams(); lp.bottomMargin = bottomInset; 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 80e98d4be04..781039587c1 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 @@ -164,7 +164,7 @@ private List createTabs() { BottomTabOptions options = tab.resolveCurrentOptions().bottomTabOptions; return new AHBottomNavigationItem( options.text.get(""), - imageLoader.loadIcon(getActivity(), options.icon.get()), + imageLoader.loadIcon(getActivity(), options.icon.get(null)), imageLoader.loadIcon(getActivity(), options.selectedIcon.get(null)), options.testId.get("") ); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java index c28d44378ac..e40b23e6699 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsControllerTest.java @@ -6,6 +6,7 @@ import android.view.View; import android.view.ViewGroup.MarginLayoutParams; +import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; import com.reactnativenavigation.BaseTest; import com.reactnativenavigation.TestUtils; import com.reactnativenavigation.mocks.ImageLoaderMock; @@ -13,6 +14,7 @@ import com.reactnativenavigation.parse.Options; import com.reactnativenavigation.parse.params.Bool; import com.reactnativenavigation.parse.params.Colour; +import com.reactnativenavigation.parse.params.NullText; import com.reactnativenavigation.parse.params.Number; import com.reactnativenavigation.parse.params.Text; import com.reactnativenavigation.presentation.BottomTabPresenter; @@ -43,6 +45,7 @@ import edu.emory.mathcs.backport.java.util.Collections; import static com.reactnativenavigation.TestUtils.hideBackButton; +import static com.reactnativenavigation.utils.ObjectUtils.perform; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -74,32 +77,9 @@ public class BottomTabsControllerTest extends BaseTest { @Override public void beforeEach() { activity = newActivity(); - bottomTabs = spy(new BottomTabs(activity) { - @Override - public void superCreateItems() { - - } - }); childRegistry = new ChildControllersRegistry(); eventEmitter = Mockito.mock(EventEmitter.class); - - child1 = spy(new SimpleViewController(activity, childRegistry, "child1", tabOptions)); - child2 = spy(new SimpleViewController(activity, childRegistry, "child2", tabOptions)); - child3 = spy(new SimpleViewController(activity, childRegistry, "child3", tabOptions)); - child4 = spy(createStack()); - child5 = spy(new SimpleViewController(activity, childRegistry, "child5", tabOptions)); - when(child5.handleBack(any())).thenReturn(true); - tabs = createTabs(); - presenter = spy(new BottomTabsPresenter(tabs, new Options())); - bottomTabPresenter = spy(new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())); - tabsAttacher = spy(new BottomTabsAttacher(tabs, presenter)); - uut = createBottomTabs(); - - uut.setParentController(Mockito.mock(ParentController.class)); - CoordinatorLayout parent = new CoordinatorLayout(activity); - parent.addView(uut.getView()); - activity.setContentView(parent); - + prepareViewsForTests(); StatusBarUtils.saveStatusBarHeight(63); } @@ -110,6 +90,22 @@ public void createView_checkProperStructure() { assertThat(((CoordinatorLayout.LayoutParams) uut.getBottomTabs().getLayoutParams()).gravity).isEqualTo(Gravity.BOTTOM); } + @Test + public void createView_tabsWithoutIconsAreAccepted() { + tabOptions.bottomTabOptions.icon = new NullText(); + prepareViewsForTests(); + assertThat(uut.getBottomTabs().getItemsCount()).isEqualTo(tabs.size()); + } + + @Test + public void createView_showTitlesWhenAllTabsDontHaveIcons() { + tabOptions.bottomTabOptions.icon = new NullText(); + assertThat(tabOptions.bottomTabsOptions.titleDisplayMode.hasValue()).isFalse(); + prepareViewsForTests(); + presenter.applyOptions(Options.EMPTY); + assertThat(bottomTabs.getTitleState()).isEqualTo(AHBottomNavigation.TitleState.ALWAYS_SHOW); + } + @Test(expected = RuntimeException.class) public void setTabs_ThrowWhenMoreThan5() { tabs.add(new SimpleViewController(activity, childRegistry, "6", tabOptions)); @@ -393,6 +389,36 @@ public void destroy() { verify(tabsAttacher).destroy(); } + private void prepareViewsForTests() { + perform(uut, ViewController::destroy); + bottomTabs = spy(new BottomTabs(activity) { + @Override + public void superCreateItems() { + + } + }); + createChildren(); + tabs = createTabs(); + presenter = spy(new BottomTabsPresenter(tabs, new Options())); + bottomTabPresenter = spy(new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new Options())); + tabsAttacher = spy(new BottomTabsAttacher(tabs, presenter)); + uut = createBottomTabs(); + + uut.setParentController(Mockito.mock(ParentController.class)); + CoordinatorLayout parent = new CoordinatorLayout(activity); + parent.addView(uut.getView()); + activity.setContentView(parent); + } + + private void createChildren() { + child1 = spy(new SimpleViewController(activity, childRegistry, "child1", tabOptions)); + child2 = spy(new SimpleViewController(activity, childRegistry, "child2", tabOptions)); + child3 = spy(new SimpleViewController(activity, childRegistry, "child3", tabOptions)); + child4 = spy(createStack()); + child5 = spy(new SimpleViewController(activity, childRegistry, "child5", tabOptions)); + when(child5.handleBack(any())).thenReturn(true); + } + @NonNull private List createTabs() { return Arrays.asList(child1, child2, child3, child4, child5); From a2f5dbd3131f2cc158a650a01a1b9e271c2952f2 Mon Sep 17 00:00:00 2001 From: Yogev Ben David Date: Tue, 3 Mar 2020 15:09:57 +0200 Subject: [PATCH 2/3] Fix statusBar.visible option (#5992) Co-authored-by: Guy Carmeli --- lib/ios/RNNComponentViewController.m | 4 ---- lib/ios/UIViewController+LayoutProtocol.m | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ios/RNNComponentViewController.m b/lib/ios/RNNComponentViewController.m index 28ac2f8d18d..17169897299 100644 --- a/lib/ios/RNNComponentViewController.m +++ b/lib/ios/RNNComponentViewController.m @@ -76,10 +76,6 @@ - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { [self.eventEmitter sendOnSearchBarCancelPressed:self.layoutInfo.componentId]; } -- (BOOL)prefersStatusBarHidden { - return [_presenter isStatusBarVisibility:self.navigationController resolvedOptions:self.resolveOptions]; -} - - (UIStatusBarStyle)preferredStatusBarStyle { return [_presenter getStatusBarStyle:[self resolveOptions]]; } diff --git a/lib/ios/UIViewController+LayoutProtocol.m b/lib/ios/UIViewController+LayoutProtocol.m index db21c800ca6..91e7caa32e2 100644 --- a/lib/ios/UIViewController+LayoutProtocol.m +++ b/lib/ios/UIViewController+LayoutProtocol.m @@ -60,6 +60,10 @@ - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return interfaceOrientationMask; } +- (BOOL)prefersStatusBarHidden { + return [self.presenter isStatusBarVisibility:self.navigationController resolvedOptions:self.resolveOptions]; +} + - (UINavigationController *)stack { if ([self isKindOfClass:UINavigationController.class]) { return (UINavigationController *)self; From 4ce0e89b06b9ab29d4be5d2eb0d11419deaade7a Mon Sep 17 00:00:00 2001 From: Jin Shin Date: Tue, 3 Mar 2020 23:39:35 +1000 Subject: [PATCH 3/3] Fix autolink script (#5990) This PR fixes the autolink script including: * Specifying the minSdkVersion for Android. Closes #5983. * Removing the RNN Pod added by the react-native link script. --- autolink/postlink/gradleLinker.js | 34 ++++++++++++++++++++++++++ autolink/postlink/path.js | 3 ++- autolink/postlink/podfileLinker.js | 39 ++++++++++++++++++++++++++++++ autolink/postlink/postLinkIOS.js | 2 ++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 autolink/postlink/podfileLinker.js diff --git a/autolink/postlink/gradleLinker.js b/autolink/postlink/gradleLinker.js index f65f20ec187..71212b4b0df 100644 --- a/autolink/postlink/gradleLinker.js +++ b/autolink/postlink/gradleLinker.js @@ -4,6 +4,8 @@ var fs = require('fs'); var { warnn, errorn, logn, infon, debugn } = require('./log'); var { insertString } = require('./stringUtils'); var DEFAULT_KOTLIN_VERSION = '1.3.61'; +// This should be the minSdkVersion required for RNN. +var DEFAULT_MIN_SDK_VERSION = 19 class GradleLinker { constructor() { @@ -16,6 +18,7 @@ class GradleLinker { var contents = fs.readFileSync(this.gradlePath, 'utf8'); contents = this._setKotlinVersion(contents); contents = this._setKotlinPluginDependency(contents); + contents = this._setMinSdkVersion(contents); fs.writeFileSync(this.gradlePath, contents); infon('Root build.gradle linked successfully!\n'); } else { @@ -54,6 +57,22 @@ class GradleLinker { return contents; } + /** + * Check the current minSdkVersion specified and if it's lower than + * the required version, set it to the required version otherwise leave as it is. + */ + _setMinSdkVersion(contents) { + var minSdkVersion = this._getMinSdkVersion(contents) + // If user entered minSdkVersion is lower than the default, set it to default. + if (minSdkVersion < DEFAULT_MIN_SDK_VERSION) { + debugn(` Updating minSdkVersion to ${DEFAULT_MIN_SDK_VERSION}`) + return contents.replace(/minSdkVersion\s{0,}=\s{0,}\d*/, `minSdkVersion = ${DEFAULT_MIN_SDK_VERSION}`) + } + + debugn(` Already specified minSdkVersion ${minSdkVersion}`) + return contents.replace(/minSdkVersion\s{0,}=\s{0,}\d*/, `minSdkVersion = ${minSdkVersion}`) + } + /** * @param { string } contents */ @@ -69,6 +88,21 @@ class GradleLinker { return `"${DEFAULT_KOTLIN_VERSION}"`; } + /** + * Get the minSdkVersion value. + * @param { string } contents + */ + _getMinSdkVersion(contents) { + var minSdkVersion = contents.match(/minSdkVersion\s{0,}=\s{0,}(\d*)/) + + if (minSdkVersion && minSdkVersion[1]) { + // It'd be something like 16 for a fresh React Native project. + return +minSdkVersion[1] + } + + return DEFAULT_MIN_SDK_VERSION + } + /** * @param {string} contents */ diff --git a/autolink/postlink/path.js b/autolink/postlink/path.js index d01bd74ab8d..c719b2bb0f3 100644 --- a/autolink/postlink/path.js +++ b/autolink/postlink/path.js @@ -9,4 +9,5 @@ var mainApplicationJava = glob.sync("**/MainApplication.java", ignoreFolders)[0] exports.mainApplicationJava = mainApplicationJava; exports.rootGradle = mainApplicationJava.replace(/android\/app\/.*\.java/, "android/build.gradle");; -exports.appDelegate = glob.sync("**/AppDelegate.m", ignoreFolders)[0]; \ No newline at end of file +exports.appDelegate = glob.sync("**/AppDelegate.m", ignoreFolders)[0]; +exports.podFile = glob.sync("**/Podfile", ignoreFolders)[0] \ No newline at end of file diff --git a/autolink/postlink/podfileLinker.js b/autolink/postlink/podfileLinker.js new file mode 100644 index 00000000000..a7cda620a5f --- /dev/null +++ b/autolink/postlink/podfileLinker.js @@ -0,0 +1,39 @@ +// @ts-check +var path = require('./path'); +var fs = require("fs"); +var {logn, debugn, infon} = require("./log"); + +class PodfileLinker { + constructor() { + this.podfilePath = path.podFile; + } + + link() { + if (this.podfilePath) { + logn("Updating Podfile...") + var podfileContent = fs.readFileSync(this.podfilePath, "utf8"); + + podfileContent = this._removeRNNPodLink(podfileContent); + + fs.writeFileSync(this.podfilePath, podfileContent); + infon("Podfile updated successfully!\n") + } + } + + /** + * Removes the RNN pod added by react-native link script. + */ + _removeRNNPodLink(contents) { + const rnnPodLink = contents.match(/\s+.*pod 'ReactNativeNavigation'.+react-native-navigation'/) + + if (!rnnPodLink) { + debugn(" RNN Pod has not been added to Podfile") + return contents + } + + debugn(" Removing RNN Pod from Podfile") + return contents.replace(rnnPodLink, "") + } +} + +module.exports = PodfileLinker \ No newline at end of file diff --git a/autolink/postlink/postLinkIOS.js b/autolink/postlink/postLinkIOS.js index 64373857d9a..9d160b1936a 100644 --- a/autolink/postlink/postLinkIOS.js +++ b/autolink/postlink/postLinkIOS.js @@ -1,7 +1,9 @@ // @ts-check var AppDelegateLinker = require("./appDelegateLinker"); +var PodfileLinker = require('./podfileLinker'); module.exports = () => { console.log("Running iOS postlink script\n"); new AppDelegateLinker().link(); + new PodfileLinker().link(); }