Skip to content

Commit

Permalink
V7 (#6422)
Browse files Browse the repository at this point in the history
* Support Reanimated 2
* Turbo Modules support
* New layout system on iOS. Layout insets are now handled via constraints and `safeAreaLayoutGuide` instead of margins and `edgesForExtendedLayout` as per Apple's recommendations. This change should be transparent to you and won't affect your apps.
* Android development is now done on Hermes. While JSC is still supported, we will no longer support it officially and encourage you to migrate your projects to Hermes.
* Deprecate `registerComponentWithRedux`. Use [registerComponent](https://wix.github.io/react-native-navigation/api/component#registering-a-component-wrapped-with-providers) with providers instead.
* Switch to Hermes to support TurboModules + bump compile and target sdk

Closes #6454
  • Loading branch information
guyca committed Aug 31, 2020
1 parent 3e7c31c commit 7f505e2
Show file tree
Hide file tree
Showing 85 changed files with 1,187 additions and 910 deletions.
8 changes: 6 additions & 2 deletions README.md
Expand Up @@ -18,13 +18,17 @@
React Native Navigation provides 100% native platform navigation on both iOS and Android for React Native apps. The JavaScript API is simple and cross-platform - just install it in your app and give your users the native feel they deserve. Ready to get started? Check out the [docs](https://wix.github.io/react-native-navigation/).

# Quick Links

- [Documentation](https://wix.github.io/react-native-navigation/)
- [Changelog](https://github.com/wix/react-native-navigation/blob/master/CHANGELOG.md)
- [Stack Overflow](http://stackoverflow.com/questions/tagged/wix-react-native-navigation)
- [Chat with us](https://discord.gg/DhkZjq2)
- [Contributing](https://wix.github.io/react-native-navigation/docs/meta-contributing)

# Installation
As `react-native-navigation` is a native navigation library - integrating it into your app will require editing native files. Follow the installation guides in the [documentation](https://wix.github.io/react-native-navigation/).
# Requirements

Apps using React Native Navigation may target iOS 11 and Android 5.0 (API 21). You may use Windows, macOS or Linux as your development operating system.

# Installation

As `react-native-navigation` is a native navigation library - integrating it into your app will require editing native files. Follow the installation guides in the [documentation](https://wix.github.io/react-native-navigation/).
91 changes: 65 additions & 26 deletions autolink/postlink/appDelegateLinker.js
@@ -1,7 +1,7 @@
// @ts-check
var fs = require("fs");
var path = require("./path");
var { warnn, logn, infon, debugn, errorn } = require("./log");
var fs = require('fs');
var path = require('./path');
var { warnn, logn, infon, debugn, errorn } = require('./log');

class AppDelegateLinker {
constructor() {
Expand All @@ -13,64 +13,68 @@ class AppDelegateLinker {
link() {
if (!this.appDelegatePath) {
errorn(
" AppDelegate not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
' AppDelegate not found! Does the file exist in the correct folder?\n Please check the manual installation docs:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation'
);
return;
}

logn("Linking AppDelegate...");
logn('Linking AppDelegate...');

var appDelegateContents = fs.readFileSync(this.appDelegatePath, "utf8");
var appDelegateContents = fs.readFileSync(this.appDelegatePath, 'utf8');

try {
appDelegateContents = this._removeUnneededImports(appDelegateContents);
this.removeUnneededImportsSuccess = true;
} catch (e) {
errorn(" " + e.message);
errorn(' ' + e.message);
}

appDelegateContents = this._importNavigation(appDelegateContents);

appDelegateContents = this._bootstrapNavigation(appDelegateContents);

appDelegateContents = this._extraModulesForBridge(appDelegateContents);

try {
appDelegateContents = this._removeApplicationLaunchContent(appDelegateContents);
this.removeApplicationLaunchContentSuccess = true;
} catch (e) {
errorn(" " + e.message);
errorn(' ' + e.message);
}

fs.writeFileSync(this.appDelegatePath, appDelegateContents);

if (this.removeUnneededImportsSuccess && this.removeApplicationLaunchContentSuccess) {
infon("AppDelegate linked successfully!\n");
infon('AppDelegate linked successfully!\n');
} else {
warnn("AppDelegate was partially linked, please check the details above and proceed with the manual installation documentation to complete the linking process.!\n");
warnn(
'AppDelegate was partially linked, please check the details above and proceed with the manual installation documentation to complete the linking process.!\n'
);
}
}

_removeUnneededImports(content) {
debugn(" Removing Unneeded imports");
debugn(' Removing Unneeded imports');

const unneededImports = [/#import\s+\<React\/RCTRootView.h>\s/];
let elementsRemovedCount = 0;

unneededImports.forEach((unneededImport) => {
if (unneededImport.test(content)) {
content = content.replace(unneededImport, "");
content = content.replace(unneededImport, '');
elementsRemovedCount++;
}
});

if (unneededImports.length === elementsRemovedCount) {
debugn(" All imports have been removed");
debugn(' All imports have been removed');
} else if (elementsRemovedCount === 0) {
warnn(
" No imports could be removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
' No imports could be removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation'
);
} else {
throw new Error(
"Some imports were removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
'Some imports were removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation'
);
}

Expand All @@ -79,30 +83,65 @@ class AppDelegateLinker {

_importNavigation(content) {
if (!this._doesImportNavigation(content)) {
debugn(" Importing ReactNativeNavigation.h");
return content.replace(/#import\s+"AppDelegate.h"/, '#import "AppDelegate.h"\n#import <ReactNativeNavigation/ReactNativeNavigation.h>');
debugn(' Importing ReactNativeNavigation.h');
return content.replace(
/#import\s+"AppDelegate.h"/,
'#import "AppDelegate.h"\n#import <ReactNativeNavigation/ReactNativeNavigation.h>'
);
}

warnn(" AppDelegate already imports ReactNativeNavigation.h");
warnn(' AppDelegate already imports ReactNativeNavigation.h');
return content;
}

_bootstrapNavigation(content) {
if (this._doesBootstrapNavigation(content)) {
warnn(" Navigation Bootstrap already present.");
warnn(' Navigation Bootstrap already present.');
return content;
}

debugn(" Bootstrapping Navigation");
return content.replace(/RCTBridge.*];/, "[ReactNativeNavigation bootstrapWithDelegate:self launchOptions:launchOptions];");
debugn(' Bootstrapping Navigation');
return content.replace(
/RCTBridge.*];/,
'[ReactNativeNavigation bootstrapWithDelegate:self launchOptions:launchOptions];'
);
}

_doesBootstrapNavigation(content) {
return /ReactNativeNavigation\s+bootstrap/.test(content);
}

_extraModulesForBridge(content) {
if (this._doesImplementsRNNExtraModulesForBridge(content)) {
warnn(' extraModulesForBridge already present.');
return content;
} else if (this._doesImplementsExtraModulesForBridge(content)) {
throw new Error(
'extraModulesForBridge implemented for a different module and needs manual linking. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation'
);
}

debugn(' Implementing extraModulesForBridge');
return content.replace(
/-.*\(NSURL.*\*\)sourceURLForBridge:\(RCTBridge.*\*\)bridge/,
'- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge {\n\
return [ReactNativeNavigation extraModulesForBridge:bridge];\n\
}\n\
\n\
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge'
);
}

_doesImplementsExtraModulesForBridge(content) {
return /-.*\(NSArray.*\*\)extraModulesForBridge:\(RCTBridge.*\*\)bridge/.test(content);
}

_doesImplementsRNNExtraModulesForBridge(content) {
return /ReactNativeNavigation\s+extraModulesForBridge/.test(content);
}

_removeApplicationLaunchContent(content) {
debugn(" Removing Application launch content");
debugn(' Removing Application launch content');

const toRemove = [
/RCTRootView\s+\*rootView((.|\r|\s)*?)];\s+/,
Expand All @@ -117,20 +156,20 @@ class AppDelegateLinker {

toRemove.forEach((element) => {
if (element.test(content)) {
content = content.replace(element, "");
content = content.replace(element, '');
elementsRemovedCount++;
}
});

if (toRemove.length === elementsRemovedCount) {
debugn(" Application Launch content has been removed");
debugn(' Application Launch content has been removed');
} else if (elementsRemovedCount === 0) {
warnn(
" No elements could be removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
' No elements could be removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation'
);
} else {
throw new Error(
"Some elements were removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation"
'Some elements were removed. Check the manual installation docs to verify that everything is properly setup:\n https://wix.github.io/react-native-navigation/docs/installing#native-installation'
);
}

Expand Down
15 changes: 15 additions & 0 deletions e2e/Modals.test.js
Expand Up @@ -143,4 +143,19 @@ describe('modal', () => {
await sleep(100);
await expect(elementById(TestIDs.NAVIGATION_TAB)).toBeVisible();
});

it('dismissModal promise is resolved with root ViewController id', async () => {
await elementById(TestIDs.MODAL_COMMANDS_BTN).tap();
await elementById(TestIDs.MODAL_BTN).tap();

await expect(element(by.id(TestIDs.SHOW_MODAL_PROMISE_RESULT))).toHaveText(
'showModal promise resolved with: UniqueStackId'
);
await expect(element(by.id(TestIDs.MODAL_DISMISSED_LISTENER_RESULT))).toHaveText(
'modalDismissed listener called with with: UniqueStackId'
);
await expect(element(by.id(TestIDs.DISMISS_MODAL_PROMISE_RESULT))).toHaveText(
'dismissModal promise resolved with: UniqueStackId'
);
});
});
12 changes: 12 additions & 0 deletions e2e/Stack.test.js
Expand Up @@ -110,4 +110,16 @@ describe('Stack', () => {
await elementByLabel('Start Typing').typeText(query);
await expect(elementById(TestIDs.SEARCH_RESULT_ITEM)).toHaveText(`Item ${query}`);
});

it('push promise is resolved with pushed ViewController id', async () => {
await elementById(TestIDs.STACK_COMMANDS_BTN).tap();
await elementById(TestIDs.PUSH_BTN).tap();

await expect(element(by.id(TestIDs.PUSH_PROMISE_RESULT))).toHaveText(
'push promise resolved with: ChildId'
);
await expect(element(by.id(TestIDs.POP_PROMISE_RESULT))).toHaveText(
'pop promise resolved with: ChildId'
);
});
});
7 changes: 5 additions & 2 deletions lib/android/app/build.gradle
Expand Up @@ -8,11 +8,12 @@ def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_COMPILE_SDK_VERSION = 29
def DEFAULT_MIN_SDK_VERSION = 19
def DEFAULT_TARGET_SDK_VERSION = 28
def DEFAULT_TARGET_SDK_VERSION = 29
def kotlinVersion = rootProject.ext.get("RNNKotlinVersion")
def kotlinStdlib = safeExtGet('RNNKotlinStdlib', 'kotlin-stdlib-jdk8')
def kotlinCoroutinesCore = safeExtGet('RNNKotlinCoroutinesCore', '1.3.7')

android {
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
Expand Down Expand Up @@ -165,6 +166,8 @@ allprojects { p ->
dependencies {
implementation "androidx.core:core-ktx:1.3.0"
implementation "org.jetbrains.kotlin:$kotlinStdlib:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesCore"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesCore"

implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0'
Expand Down
Expand Up @@ -55,21 +55,13 @@ open class AnimationOptions(json: JSONObject?) {
if (valueOptions.isEmpty()) valueOptions = defaultOptions.valueOptions
}

fun hasValue(): Boolean {
return id.hasValue() || enabled.hasValue() || waitForRender.hasValue()
}
fun hasValue() = id.hasValue() || enabled.hasValue() || waitForRender.hasValue()

fun getAnimation(view: View): AnimatorSet {
return getAnimation(view, AnimatorSet())
}
fun getAnimation(view: View) = getAnimation(view, AnimatorSet())

fun getAnimation(view: View, defaultAnimation: AnimatorSet): AnimatorSet {
if (!hasAnimation()) return defaultAnimation
val animationSet = AnimatorSet()
val animators: MutableList<Animator> = ArrayList()
forEach(valueOptions) { options: ValueAnimationOptions -> animators.add(options.getAnimation(view)) }
animationSet.playTogether(animators)
return animationSet
return AnimatorSet().apply { playTogether(valueOptions.map { it.getAnimation(view) }) }
}

val duration: Int
Expand Down
Expand Up @@ -54,4 +54,8 @@ void mergeWithDefault(NestedAnimationsOptions defaultOptions) {
public boolean hasValue() {
return topBar.hasValue() || content.hasValue() || bottomTabs.hasValue() || waitForRender.hasValue();
}

public boolean hasElementsTransition() {
return sharedElements.hasValue() || elementTransitions.hasValue();
}
}
@@ -0,0 +1,14 @@
package com.reactnativenavigation.utils

import android.view.View
import androidx.core.view.doOnLayout
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

suspend fun View.awaitPost() = suspendCoroutine<Unit> { cont ->
post { cont.resume(Unit) }
}

inline fun View.doOnLayoutCompat(crossinline action: (view: View) -> Unit) {
if (isLaidOut || (width > 0 && height > 0)) action(this) else doOnLayout { action(this) }
}
Expand Up @@ -132,7 +132,8 @@ public static int getBackgroundColor(View view) {
throw new RuntimeException(view.getBackground().getClass().getSimpleName() + " is not ReactViewBackgroundDrawable");
}

public static void removeFromParent(View view) {
public static void removeFromParent(@Nullable View view) {
if (view == null) return;
ViewParent parent = view.getParent();
if (parent != null) {
((ViewManager) parent).removeView(view);
Expand Down
Expand Up @@ -70,8 +70,8 @@ public boolean dismissModal(String componentId, @Nullable ViewController root, C
presenter.dismissModal(toDismiss, toAdd, root, new CommandListenerAdapter(listener) {
@Override
public void onSuccess(String childId) {
eventEmitter.emitModalDismissed(componentId, toDismiss.getCurrentComponentName(), 1);
super.onSuccess(componentId);
eventEmitter.emitModalDismissed(toDismiss.getId(), toDismiss.getCurrentComponentName(), 1);
super.onSuccess(toDismiss.getId());
}
});
return true;
Expand Down
Expand Up @@ -176,9 +176,9 @@ public void popToRoot(final String id, Options mergeOptions, CommandListener lis
}

public void popTo(final String id, Options mergeOptions, CommandListener listener) {
ViewController target = findController(id);
ViewController<?> target = findController(id);
if (target != null) {
target.performOnParentStack(stack -> ((StackController) stack).popTo(target, mergeOptions, listener));
target.performOnParentStack(stack -> stack.popTo(target, mergeOptions, listener));
} else {
listener.onError("Failed to execute stack command. Stack by " + id + " not found.");
}
Expand Down Expand Up @@ -222,12 +222,12 @@ public ViewController findController(String id) {
}

private void applyOnStack(String fromId, CommandListener listener, Func1<StackController> task) {
ViewController from = findController(fromId);
ViewController<?> from = findController(fromId);
if (from != null) {
if (from instanceof StackController) {
task.run((StackController) from);
} else {
from.performOnParentStack(stack -> task.run((StackController) stack) );
from.performOnParentStack(task);
}
} else {
listener.onError("Failed to execute stack command. Stack " + fromId + " not found.");
Expand Down
Expand Up @@ -21,6 +21,7 @@
import static com.github.clans.fab.FloatingActionButton.SIZE_MINI;
import static com.github.clans.fab.FloatingActionButton.SIZE_NORMAL;
import static com.reactnativenavigation.utils.ObjectUtils.perform;
import static com.reactnativenavigation.utils.ViewUtils.removeFromParent;

public class FabPresenter {
private static final int DURATION = 200;
Expand Down Expand Up @@ -71,6 +72,8 @@ public void mergeOptions(FabOptions options, @NonNull ViewController component,
}

private void createFab(ViewController component, FabOptions options) {
removeFromParent(fabMenu);
removeFromParent(fab);
if (options.actionsArray.size() > 0) {
fabMenu = new FabMenu(viewGroup.getContext(), options.id.get());
setParams(component, fabMenu, options);
Expand Down

0 comments on commit 7f505e2

Please sign in to comment.