Browse Source

Implement changing title dynamically on Android #1071 (#1483)

* Upgrade Gradle version

* Allow playground app to run on x86_64 emulator

* #1071 Allow changing navigation title

* #1071 Add tests for JavaScript
Juozas Kontvainis 7 years ago
parent
commit
fd665d71ef

+ 8
- 0
AndroidE2E/app/src/androidTest/java/com/reactnativenavigation/e2e/androide2e/ScreenStyleStaticTest.java View File

@@ -11,4 +11,12 @@ public class ScreenStyleStaticTest extends BaseTest {
11 11
 		elementByText("PUSH OPTIONS SCREEN").click();
12 12
 		assertExists(By.text("Static Title"));
13 13
 	}
14
+
15
+	@Test
16
+	public void setTitleDynamically() throws Exception {
17
+		elementByText("PUSH OPTIONS SCREEN").click();
18
+		assertExists(By.text("Static Title"));
19
+		elementByText("DYNAMIC OPTIONS").click();
20
+		assertExists(By.text("Dynamic Title"));
21
+	}
14 22
 }

+ 12
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java View File

@@ -10,6 +10,7 @@ import com.facebook.react.bridge.ReadableMap;
10 10
 import com.reactnativenavigation.NavigationActivity;
11 11
 import com.reactnativenavigation.layout.LayoutFactory;
12 12
 import com.reactnativenavigation.layout.LayoutNode;
13
+import com.reactnativenavigation.layout.NavigationOptions;
13 14
 import com.reactnativenavigation.parse.JSONParser;
14 15
 import com.reactnativenavigation.parse.LayoutNodeParser;
15 16
 import com.reactnativenavigation.utils.UiThread;
@@ -42,6 +43,17 @@ public class NavigationModule extends ReactContextBaseJavaModule {
42 43
 		});
43 44
 	}
44 45
 
46
+	@ReactMethod
47
+	public void setOptions(final String onContainerId, final ReadableMap options) {
48
+		final NavigationOptions navOptions = NavigationOptions.parse(JSONParser.parse(options));
49
+		handle(new Runnable() {
50
+			@Override
51
+			public void run() {
52
+				navigator().setOptions(onContainerId, navOptions);
53
+			}
54
+		});
55
+	}
56
+
45 57
 	@ReactMethod
46 58
 	public void push(final String onContainerId, final ReadableMap rawLayoutTree) {
47 59
 		final LayoutNode layoutTree = LayoutNodeParser.parse(JSONParser.parse(rawLayoutTree));

+ 6
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java View File

@@ -5,6 +5,7 @@ import android.support.annotation.NonNull;
5 5
 import android.view.ViewGroup;
6 6
 import android.widget.FrameLayout;
7 7
 
8
+import com.reactnativenavigation.layout.NavigationOptions;
8 9
 import com.reactnativenavigation.utils.CompatUtils;
9 10
 
10 11
 import java.util.Collection;
@@ -54,6 +55,11 @@ public class Navigator extends ParentController {
54 55
 		getView().addView(viewController.getView());
55 56
 	}
56 57
 
58
+	public void setOptions(final String containerId, NavigationOptions options) {
59
+		ViewController target = findControllerById(containerId);
60
+		target.getParentStackController().setTitle(options.title);
61
+	}
62
+
57 63
 	public void push(final String fromId, final ViewController viewController) {
58 64
 		ViewController from = findControllerById(fromId);
59 65
 		if (from != null) {

+ 4
- 0
lib/src/Navigation.js View File

@@ -33,6 +33,10 @@ class Navigation {
33 33
     return this.commands.setRoot(params);
34 34
   }
35 35
 
36
+  setOptions(containerId, options) {
37
+    this.commands.setOptions(containerId, options);
38
+  }
39
+
36 40
   showModal(params) {
37 41
     return this.commands.showModal(params);
38 42
   }

+ 8
- 0
lib/src/Navigation.test.js View File

@@ -30,6 +30,14 @@ describe('Navigation', () => {
30 30
     expect(Navigation.commands.setRoot).toHaveBeenCalledWith(params);
31 31
   });
32 32
 
33
+  it('setOptions delegates to Commands', async () => {
34
+    const theContainerId = "7";
35
+    const params = { title: "T" };
36
+    Navigation.setOptions(theContainerId, params);
37
+    expect(Navigation.commands.setOptions).toHaveBeenCalledTimes(1);
38
+    expect(Navigation.commands.setOptions).toHaveBeenCalledWith(theContainerId, params);
39
+  });
40
+
33 41
   it('showModal delegates to Commands', async () => {
34 42
     Navigation.commands.showModal.mockReturnValue(Promise.resolve('result'));
35 43
     const params = {};

+ 4
- 0
lib/src/adapters/NativeCommandsSender.js View File

@@ -10,6 +10,10 @@ export default class NativeCommandsSender {
10 10
     return Promise.resolve(layoutTree);
11 11
   }
12 12
 
13
+  setOptions(containerId, options) {
14
+    this.nativeCommandsModule.setOptions(containerId, options);
15
+  }
16
+
13 17
   push(onContainerId, layout) {
14 18
     this.nativeCommandsModule.push(onContainerId, layout);
15 19
     return Promise.resolve(layout);

+ 6
- 0
lib/src/adapters/NativeCommandsSender.test.js View File

@@ -7,6 +7,7 @@ describe('NativeCommandsSender', () => {
7 7
   beforeEach(() => {
8 8
     mockNativeModule = {
9 9
       setRoot: jest.fn(),
10
+      setOptions: jest.fn(),
10 11
       push: jest.fn(),
11 12
       pop: jest.fn(),
12 13
       popTo: jest.fn(),
@@ -29,6 +30,11 @@ describe('NativeCommandsSender', () => {
29 30
     expect(result).toBeDefined();
30 31
   });
31 32
 
33
+  it('setOptions sends to native with containerId and params', () => {
34
+    uut.setOptions('theContainerId', { title: "Title" });
35
+    expect(mockNativeModule.setOptions).toHaveBeenCalledTimes(1);
36
+  });
37
+
32 38
   it('push sends to native and resolves with promise', async () => {
33 39
     const theNewContainer = {};
34 40
     const result = await uut.push('theContainerId', theNewContainer);

+ 5
- 0
lib/src/commands/Commands.js View File

@@ -14,6 +14,11 @@ export default class Commands {
14 14
     return this.nativeCommandsSender.setRoot(layout);
15 15
   }
16 16
 
17
+  setOptions(containerId, options) {
18
+    const input = _.cloneDeep(options);
19
+    this.nativeCommandsSender.setOptions(containerId, input);
20
+  }
21
+
17 22
   showModal(simpleApi) {
18 23
     const input = _.cloneDeep(simpleApi);
19 24
     const layout = this.layoutTreeParser.parseFromSimpleJSON(input);

+ 14
- 0
lib/src/commands/Commands.test.js View File

@@ -64,6 +64,20 @@ describe('Commands', () => {
64 64
     });
65 65
   });
66 66
 
67
+  describe('setOptions', () => {
68
+    it('deep clones input to avoid mutation errors', () => {
69
+      const obj = { title: "test" };
70
+      uut.setOptions('theContainerId', obj);
71
+      expect(mockCommandsSender.setOptions.mock.calls[0][1]).not.toBe(obj);
72
+    });
73
+
74
+    it('passes options for container', () => {
75
+      uut.setOptions('theContainerId', { title: "1" });
76
+      expect(mockCommandsSender.setOptions).toHaveBeenCalledTimes(1);
77
+      expect(mockCommandsSender.setOptions).toHaveBeenCalledWith('theContainerId', { title: "1" });
78
+    });
79
+  });
80
+
67 81
   describe('showModal', () => {
68 82
     it('sends command to native after parsing into a correct layout tree', () => {
69 83
       uut.showModal({

+ 3
- 0
playground/android/app/build.gradle View File

@@ -17,6 +17,9 @@ android {
17 17
         targetSdkVersion 25
18 18
         versionCode 1
19 19
         versionName "1.0"
20
+        ndk {
21
+            abiFilters "armeabi-v7a", "x86"
22
+        }
20 23
     }
21 24
     signingConfigs {
22 25
         release {

+ 1
- 1
playground/android/build.gradle View File

@@ -5,7 +5,7 @@ buildscript {
5 5
         jcenter()
6 6
     }
7 7
     dependencies {
8
-        classpath 'com.android.tools.build:gradle:2.3.2'
8
+        classpath 'com.android.tools.build:gradle:2.3.3'
9 9
 
10 10
         // NOTE: Do not place your application dependencies here; they belong
11 11
         // in the individual module build.gradle files

+ 3
- 3
playground/src/containers/OptionsScreen.js View File

@@ -30,9 +30,9 @@ class OptionsScreen extends Component {
30 30
   }
31 31
 
32 32
   onClickDynamicOptions() {
33
-    // Navigation.setOptions({
34
-    //   title: 'Dynamic Title'
35
-    // });
33
+    Navigation.setOptions(this.props.containerId, {
34
+      title: 'Dynamic Title'
35
+    });
36 36
   }
37 37
 }
38 38