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(); } 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); 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;