Skip to content

Commit

Permalink
V3 (wix#5372)
Browse files Browse the repository at this point in the history
# Changes
* Support RN 0.60
* Migrate to AndroidX
* Improve draw behind StatusBar (Preparation for wix#4258)
* Don't push BottomTabs when keyboard is displayed (Fixes wix#4005, wix#3424)
    - It won't be needed to toggle the BottomTabs when Keyboard is visible
* BottomTab badge and dot indicator are not animated by default on Android (parity with iOS)

# Updating from v2
v3 is currently in alpha. To update simply npm install `3.0.0-alpha.11` - `npm install --save react-native-navigation@3.0.0-alpha.11`.
Breaking changes are outlined below.

## Layout system changes on **Android**
* Parent layouts (BottomsTabs, Stack, SideMenu) are always laid out behind the StatusBar.
* Components (`component` and `externalComponent`) are measured and offset according to the StatusBar.

In this release, We're changing the layout system in order to provide better support for immersive and full screen apps. In this release we've improved support for drawing behind the StatusBar,  next we'll address drawing behind the NavigationBar. 

Use the `drawBehind` and `translucent` options to control the StatusBar
```js
statusBar: {
  drawBehind: true,  // will draw a screen behind the StatusBar
  translucent: true // Usually you'll want to have drawBehind: true when this is true
}
```

While this isn't a breaking API change - there are a few breaking side effects.

### How will my app be effected
1. When the keyboard is opened, BottomTabs will now be drawn behind the keyboard and won't shift upwards. This is in parity with the current behaviour in iOS. For the most part, this isn't a breaking change. Toggling BottomTabs when TextInput's focus changes won't be needed anymore.

2. While parent controllers are drawn behind the StatusBar, their background isn't.
This means that when transitioning from a destinations drawn under the StatusBar to a destination drawn behind it, the application's default background color will be visible behind the StatusBar.
If you application's theme is dark, you might want to change the `windowBackground` property to mitigate this:

Add the following to your application's `style.xml`
```xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@color/backgroundColor</item>
    </style>

    <!--This is your application's default background color.
    It will be visible when the app is first opened (while the splash layout is visible)
    and when transitioning between a destination a screen drawn under the StatusBar to
    a destination drawn behind it-->
    <item name="backgroundColor" type="color">#f00</item>
</resources>
```

## AndroidX migration
We've migrated RNN to AndroidX, please follow migration instructions in the react-native repo.

## Removed SyncUiImplementation
[SyncUiImplementation](https://github.com/wix/react-native-navigation/blob/master/lib/android/app/src/reactNative57WixFork/java/com/reactnativenavigation/react/SyncUiImplementation.java) was used to overcome a bug in RN's UiImplementation. This workaround was added to RN's `UiImplementation` in RN 0.60 (thanks @SudoPlz) and can be removed from RNN.

If you're using `SyncUiImplementation` your app will fail to compile after upgrading to v3. Simply remove the following code from your `MainApplication.java`
```diff
- import com.facebook.react.uimanager.UIImplementationProvider;
- import com.reactnativenavigation.react.SyncUiImplementation;


- @OverRide
- protected UIImplementationProvider getUIImplementationProvider() {
-     return new SyncUiImplementation.Provider();
- }
```

## BottomTab badge and dot indicator are not animated by default on Android (parity with iOS)
Showing and hiding badge and dot indicator are now not animated by default. Badge animation is now controlled with the `bottomTab.animateBadge` property and dot indicator with `bottomTab.dotIndicator.animate` property.

#### The following option will show a badge with animation
```js
bottomTab: {
  badge: 'new,
  animateBadge: true
}
```

#### The following option will show a dot indicator with animation
```js
bottomTab: {
  dotIndicator: {
    visible: true,
    animate: true
  }
}
```

closes wix#5228
  • Loading branch information
guyca authored and vshkl committed Feb 5, 2020
1 parent bd366e5 commit a2dc8f1
Show file tree
Hide file tree
Showing 242 changed files with 2,704 additions and 4,244 deletions.
27 changes: 10 additions & 17 deletions docs/docs/Installing.md
Expand Up @@ -45,18 +45,18 @@ $(SRCROOT)/../node_modules/react-native-navigation/lib/ios
{
NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
[ReactNativeNavigation bootstrap:jsCodeLocation launchOptions:launchOptions];

return YES;
}

@end
```

3a. If, in Xcode, you see the following error message in `AppDelegate.m` next to `#import "RCTBundleURLProvider.h"`:
3a. If, in Xcode, you see the following error message in `AppDelegate.m` next to `#import "RCTBundleURLProvider.h"`:
```
! 'RCTBundleURLProvider.h' file not found
```
This is because the `React` scheme is missing from your project. You can verify this by opening the `Product` menu and the `Scheme` submenu.
This is because the `React` scheme is missing from your project. You can verify this by opening the `Product` menu and the `Scheme` submenu.

To make the `React` scheme available to your project, run `npm install -g react-native-git-upgrade` followed by `react-native-git-upgrade`. Once this is done, you can click back to the menu in Xcode: `Product -> Scheme -> Manage Schemes`, then click '+' to add a new scheme. From the `Target` menu, select "React", and click the checkbox to make the scheme `shared`. This should make the error disappear.

Expand Down Expand Up @@ -117,7 +117,6 @@ buildscript {
allprojects {
repositories {
+ google()
+ mavenCentral()
mavenLocal()
jcenter()
maven {
Expand All @@ -133,8 +132,6 @@ allprojects {
}

ext {
- buildToolsVersion = "26.0.3"
+ buildToolsVersion = "27.0.3"
- minSdkVersion = 16
+ minSdkVersion = 19
compileSdkVersion = 26
Expand Down Expand Up @@ -171,17 +168,14 @@ android {

dependencies {
- compile fileTree(dir: "libs", include: ["*.jar"])
- compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
- compile "com.facebook.react:react-native:+" // From node_modules
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
+ implementation "com.facebook.react:react-native:+" // From node_modules
+ implementation project(':react-native-navigation')
}
```

### 5 RNN and React Native version

react-native-navigation supports multiple React Native versions. Target the React Native version required by your project by specifying the RNN build flavor in `android/app/build.gradle`.

```diff
Expand All @@ -205,11 +199,12 @@ android {
>`reactNative55` - RN 0.55.x<Br>
>`reactNative56` - RN 0.56.x<Br>
>`reactNative57` - RN 0.57.0 - 0.57.4<Br>
>`reactNative57_5` - RN 0.57.5 and above<Br>
>`reactNative57_5` - RN 0.57.5 - 0.59.9<Br>
>`reactNative60` - RN 0.60.0 and above
Now we need to instruct gradle how to build that flavor. To do so here two solutions:

#### 5.1 Build app with gradle command
#### 5.1 Build app with gradle command

**prefered solution** The RNN flavor you would like to build is specified in `app/build.gradle`. Therefore in order to compile only that flavor, instead of building your entire project using `./gradlew assembleDebug`, you should instruct gradle to build the app module: `./gradlew app:assembleDebug`. The easiest way is to add a package.json command to build and install your debug Android APK .

Expand Down Expand Up @@ -258,7 +253,7 @@ This file is located in `android/app/src/main/java/com/<yourproject>/MainActivit
-import com.facebook.react.ReactActivity;
+import com.reactnativenavigation.NavigationActivity;

-public class MainActivity extends ReactActivity {
-public class MainActivity extends ReactActivity {
+public class MainActivity extends NavigationActivity {
- @Override
- protected String getMainComponentName() {
Expand All @@ -272,7 +267,7 @@ If you have any **react-native** related methods, you can safely delete them.
### 7. Update `MainApplication.java`

This file is located in `android/app/src/main/java/com/<yourproject>/MainApplication.java`.

```diff
...
import android.app.Application;
Expand All @@ -292,7 +287,7 @@ import java.util.List;

-public class MainApplication extends Application implements ReactApplication {
+public class MainApplication extends NavigationApplication {
+
+
+ @Override
+ protected ReactGateway createReactGateway() {
+ ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
Expand All @@ -316,7 +311,7 @@ import java.util.List;
+ // eg. new VectorIconsPackage()
+ );
+ }
+
+
+ @Override
+ public List<ReactPackage> createAdditionalReactPackages() {
+ return getPackages();
Expand Down Expand Up @@ -365,8 +360,6 @@ dependencies {
## You can use react-native-navigation \o/

Update `index.js` file


```diff
+import { Navigation } from "react-native-navigation";
-import {AppRegistry} from 'react-native';
Expand Down
22 changes: 12 additions & 10 deletions lib/android/app/build.gradle
@@ -1,13 +1,11 @@
apply plugin: 'com.android.library'
apply from: '../prepare-robolectric.gradle'

def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_MIN_SDK_VERSION = 19
def DEFAULT_SUPPORT_LIB_VERSION = '28.0.0'
def DEFAULT_TARGET_SDK_VERSION = 28

android {
Expand All @@ -32,6 +30,7 @@ android {
}

testOptions {
unitTests.includeAndroidResources = true
unitTests.all { t ->
reports {
html.enabled true
Expand Down Expand Up @@ -76,6 +75,10 @@ android {
dimension "RNN.reactNativeVersion"
buildConfigField("int", "REACT_NATVE_VERSION_MINOR", "57")
}
reactNative60 {
dimension "RNN.reactNativeVersion"
buildConfigField("int", "REACT_NATVE_VERSION_MINOR", "60")
}
}
}

Expand All @@ -89,14 +92,13 @@ allprojects { p ->
}
}

def supportLibVersion = safeExtGet('supportLibVersion', DEFAULT_SUPPORT_LIB_VERSION)

dependencies {
implementation "com.android.support:design:${supportLibVersion}"
implementation "com.android.support:appcompat-v7:${supportLibVersion}"
implementation "com.android.support:support-v4:${supportLibVersion}"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha08'

implementation 'com.github.wix-playground:ahbottomnavigation:2.4.15'
implementation 'com.github.wix-playground:ahbottomnavigation:3.0.2'
implementation 'com.github.wix-playground:reflow-animator:1.0.4'
implementation 'com.github.clans:fab:1.6.4'

Expand All @@ -105,8 +107,8 @@ dependencies {

// tests
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:3.5.1'
testImplementation "org.robolectric:robolectric:4.3-beta-1"
testImplementation 'org.assertj:assertj-core:3.8.0'
testImplementation 'com.squareup.assertj:assertj-android:1.1.1'
testImplementation 'org.mockito:mockito-core:2.13.0'
testImplementation 'org.mockito:mockito-core:2.28.2'
}
Expand Up @@ -5,9 +5,9 @@
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.View;

Expand Down
@@ -1,8 +1,8 @@
package com.reactnativenavigation;

import android.app.Application;
import android.support.annotation.Nullable;
import android.support.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
Expand Down
Expand Up @@ -5,7 +5,7 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
Expand Down Expand Up @@ -34,6 +34,8 @@ AnimatorSet getDefaultPushAnimation(View view) {
set.setDuration(DURATION);
ObjectAnimator translationY = ObjectAnimator.ofFloat(view, TRANSLATION_Y, this.translationY, 0);
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, ALPHA, 0, 1);
translationY.setDuration(DURATION);
alpha.setDuration(DURATION);
set.playTogether(translationY, alpha);
return set;
}
Expand Down
@@ -1,7 +1,7 @@
package com.reactnativenavigation.anim;


import android.support.annotation.NonNull;
import androidx.annotation.NonNull;

import com.reactnativenavigation.interfaces.ScrollEventListener;

Expand Down
Expand Up @@ -4,7 +4,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.support.annotation.RestrictTo;
import androidx.annotation.RestrictTo;
import android.view.View;
import android.view.ViewGroup;

Expand Down
Expand Up @@ -7,91 +7,84 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;

import com.reactnativenavigation.parse.AnimationOptions;
import com.reactnativenavigation.utils.ViewUtils;
import com.reactnativenavigation.views.StackLayout;
import com.reactnativenavigation.views.topbar.TopBar;

import javax.annotation.Nullable;

import static android.view.View.TRANSLATION_Y;
import static com.reactnativenavigation.utils.ObjectUtils.perform;
import static com.reactnativenavigation.utils.ViewUtils.getHeight;

public class TopBarAnimator {

private static final int DEFAULT_COLLAPSE_DURATION = 100;
private static final int DURATION = 300;
private static final TimeInterpolator DECELERATE = new DecelerateInterpolator();
private static final TimeInterpolator LINEAR = new LinearInterpolator();
private static final TimeInterpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();

private TopBar topBar;
private String stackId;
private Animator hideAnimator;
private Animator showAnimator;

public TopBarAnimator(TopBar topBar) {
public TopBarAnimator() {
}

TopBarAnimator(TopBar topBar) {
this.topBar = topBar;
}

public TopBarAnimator(TopBar topBar, @Nullable String stackId) {
public void bindView(TopBar topBar, StackLayout stack) {
this.topBar = topBar;
this.stackId = stackId;
stackId = stack.getStackId();
}

public void show(AnimationOptions options) {
public void show(AnimationOptions options, int translationStartDy) {
topBar.setVisibility(View.VISIBLE);
if (options.hasValue() && (!options.id.hasValue() || options.id.get().equals(stackId))) {
options.setValueDy(TRANSLATION_Y, -translationStartDy, 0);
showAnimator = options.getAnimation(topBar);
} else {
showAnimator = getDefaultShowAnimator(-1 * ViewUtils.getHeight(topBar), DECELERATE, DURATION);
showAnimator = getDefaultShowAnimator(translationStartDy, DECELERATE, DURATION);
}
show();
showInternal();
}

public void show(float startTranslation) {
showAnimator = getDefaultShowAnimator(startTranslation, LINEAR, DEFAULT_COLLAPSE_DURATION);
show();
showInternal();
}

private void show() {
private void showInternal() {
showAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
topBar.setVisibility(View.VISIBLE);
}
});
topBar.resetAnimationOptions();
if (isAnimatingHide()) hideAnimator.cancel();
showAnimator.start();
}

private AnimatorSet getDefaultShowAnimator(float startTranslation, TimeInterpolator interpolator, int duration) {
ObjectAnimator showAnimator = ObjectAnimator.ofFloat(topBar, TRANSLATION_Y, startTranslation, 0);
showAnimator.setInterpolator(interpolator);
showAnimator.setDuration(duration);
AnimatorSet set = new AnimatorSet();
set.play(showAnimator);
return set;
}

public void hide(AnimationOptions options, Runnable onAnimationEnd) {
public void hide(AnimationOptions options, Runnable onAnimationEnd, float translationStartDy, float translationEndDy) {
if (options.hasValue() && (!options.id.hasValue() || options.id.get().equals(stackId))) {
options.setValueDy(TRANSLATION_Y, translationStartDy, -translationEndDy);
hideAnimator = options.getAnimation(topBar);
} else {
hideAnimator = getDefaultHideAnimator(0, ACCELERATE_DECELERATE, DURATION);
hideAnimator = getDefaultHideAnimator(translationStartDy, translationEndDy, DECELERATE, DURATION);
}
hide(onAnimationEnd);
hideInternal(onAnimationEnd);
}

void hide(float startTranslation) {
hideAnimator = getDefaultHideAnimator(startTranslation, LINEAR, DEFAULT_COLLAPSE_DURATION);
hide(() -> {});
public void hide(float translationStart, float translationEndDy) {
hideAnimator = getDefaultHideAnimator(translationStart, translationEndDy, LINEAR, DEFAULT_COLLAPSE_DURATION);
hideInternal(() -> {});
}

private void hide(Runnable onAnimationEnd) {
private void hideInternal(Runnable onAnimationEnd) {
hideAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Expand All @@ -103,18 +96,32 @@ public void onAnimationEnd(Animator animation) {
hideAnimator.start();
}

private Animator getDefaultHideAnimator(float startTranslation, TimeInterpolator interpolator, int duration) {
ObjectAnimator hideAnimator = ObjectAnimator.ofFloat(topBar, TRANSLATION_Y, startTranslation, -1 * topBar.getMeasuredHeight());
hideAnimator.setInterpolator(interpolator);
hideAnimator.setDuration(duration);
return hideAnimator;
}

public boolean isAnimatingHide() {
return hideAnimator != null && hideAnimator.isRunning();
}

public boolean isAnimatingShow() {
return showAnimator != null && showAnimator.isRunning();
}

public boolean isAnimating() {
return perform(showAnimator, false, Animator::isRunning) ||
perform(hideAnimator, false, Animator::isRunning);
}

private AnimatorSet getDefaultShowAnimator(float translationStart, TimeInterpolator interpolator, int duration) {
ObjectAnimator showAnimator = ObjectAnimator.ofFloat(topBar, TRANSLATION_Y, -getHeight(topBar) - translationStart, 0);
showAnimator.setInterpolator(interpolator);
showAnimator.setDuration(duration);
AnimatorSet set = new AnimatorSet();
set.play(showAnimator);
return set;
}

private Animator getDefaultHideAnimator(float translationStart, float translationEndDy, TimeInterpolator interpolator, int duration) {
ObjectAnimator hideAnimator = ObjectAnimator.ofFloat(topBar, TRANSLATION_Y, translationStart, -topBar.getMeasuredHeight() - translationEndDy);
hideAnimator.setInterpolator(interpolator);
hideAnimator.setDuration(duration);
return hideAnimator;
}
}

0 comments on commit a2dc8f1

Please sign in to comment.