react-native-navigation 8.8.6 → 8.8.7-snapshot.2601
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativenavigation/NavigationApplication.java +3 -0
- package/android/src/main/java/com/reactnativenavigation/NavigationPackage.kt +27 -8
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRow.kt +262 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowAttacher.kt +205 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowConfigStore.kt +32 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowLayout.kt +139 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowModule.kt +37 -0
- package/android/src/main/java/com/reactnativenavigation/customrow/BottomTabsCustomRowOptions.kt +68 -0
- package/android/src/main/java/com/reactnativenavigation/options/BottomTabOptions.java +4 -1
- package/android/src/main/java/com/reactnativenavigation/react/ReactView.java +13 -0
- package/android/src/main/java/com/reactnativenavigation/react/events/ComponentType.java +2 -1
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabPresenter.java +28 -0
- package/android/src/main/java/com/reactnativenavigation/viewcontrollers/bottomtabs/BottomTabsController.java +59 -0
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/BottomTabs.java +76 -0
- package/android/src/main/java/com/reactnativenavigation/views/bottomtabs/CustomBottomTabItemView.kt +73 -0
- package/android/src/main/java/com/reactnativenavigation/views/stack/topbar/titlebar/TitleBarReactButtonView.java +54 -12
- package/android/src/test/java/com/reactnativenavigation/views/TitleAndButtonsContainerTest.kt +15 -1
- package/android/src/test/java/com/reactnativenavigation/views/TitleBarReactButtonViewTest.java +199 -0
- package/ios/ARCHITECTURE.md +5 -0
- package/ios/BottomTabPresenter.h +7 -0
- package/ios/BottomTabPresenter.mm +27 -0
- package/ios/RNNAppDelegate.h +16 -0
- package/ios/RNNAppDelegate.mm +73 -0
- package/ios/RNNBottomTabOptions.h +2 -0
- package/ios/RNNBottomTabOptions.mm +5 -1
- package/ios/RNNBottomTabsController.h +2 -0
- package/ios/RNNBottomTabsController.mm +209 -1
- package/ios/RNNBottomTabsCustomRow.h +57 -0
- package/ios/RNNBottomTabsCustomRow.mm +252 -0
- package/ios/RNNBottomTabsCustomRowOptions.h +42 -0
- package/ios/RNNBottomTabsCustomRowOptions.mm +37 -0
- package/ios/RNNBottomTabsOptions.h +2 -0
- package/ios/RNNBottomTabsOptions.mm +2 -0
- package/ios/RNNComponentViewCreator.h +2 -1
- package/ios/RNNCustomTabBarItemView.h +26 -0
- package/ios/RNNCustomTabBarItemView.mm +83 -0
- package/ios/RNNReactRootViewCreator.mm +1 -0
- package/ios/RNNViewControllerFactory.mm +1 -0
- package/ios/ReactNativeNavigation.xcodeproj/project.pbxproj +24 -0
- package/lib/module/ARCHITECTURE.md +30 -0
- package/lib/module/Navigation.js +34 -1
- package/lib/module/Navigation.js.map +1 -1
- package/lib/module/NavigationDelegate.js +21 -0
- package/lib/module/NavigationDelegate.js.map +1 -1
- package/lib/module/adapters/AndroidCustomRowForwarder.js +75 -0
- package/lib/module/adapters/AndroidCustomRowForwarder.js.map +1 -0
- package/lib/module/commands/Commands.js +8 -0
- package/lib/module/commands/Commands.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/interfaces/Options.js.map +1 -1
- package/lib/module/linking/DeferredLinkQueue.js +52 -0
- package/lib/module/linking/DeferredLinkQueue.js.map +1 -0
- package/lib/module/linking/DeferredLinkQueue.test.js +54 -0
- package/lib/module/linking/DeferredLinkQueue.test.js.map +1 -0
- package/lib/module/linking/LinkingHandler.js +139 -0
- package/lib/module/linking/LinkingHandler.js.map +1 -0
- package/lib/module/linking/LinkingHandler.test.js +384 -0
- package/lib/module/linking/LinkingHandler.test.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.js +56 -0
- package/lib/module/linking/ModalLayoutBuilder.js.map +1 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js +154 -0
- package/lib/module/linking/ModalLayoutBuilder.test.js.map +1 -0
- package/lib/module/linking/RouteMatcher.js +104 -0
- package/lib/module/linking/RouteMatcher.js.map +1 -0
- package/lib/module/linking/RouteMatcher.test.js +164 -0
- package/lib/module/linking/RouteMatcher.test.js.map +1 -0
- package/lib/module/linking/URLParser.js +56 -0
- package/lib/module/linking/URLParser.js.map +1 -0
- package/lib/module/linking/URLParser.test.js +100 -0
- package/lib/module/linking/URLParser.test.js.map +1 -0
- package/lib/module/linking/types.js +4 -0
- package/lib/module/linking/types.js.map +1 -0
- package/lib/typescript/Navigation.d.ts +22 -0
- package/lib/typescript/Navigation.d.ts.map +1 -1
- package/lib/typescript/NavigationDelegate.d.ts +13 -0
- package/lib/typescript/NavigationDelegate.d.ts.map +1 -1
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts +23 -0
- package/lib/typescript/adapters/AndroidCustomRowForwarder.d.ts.map +1 -0
- package/lib/typescript/commands/Commands.d.ts +1 -0
- package/lib/typescript/commands/Commands.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +85 -0
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/linking/DeferredLinkQueue.d.ts +26 -0
- package/lib/typescript/linking/DeferredLinkQueue.d.ts.map +1 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts +2 -0
- package/lib/typescript/linking/DeferredLinkQueue.test.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.d.ts +71 -0
- package/lib/typescript/linking/LinkingHandler.d.ts.map +1 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts +2 -0
- package/lib/typescript/linking/LinkingHandler.test.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts +21 -0
- package/lib/typescript/linking/ModalLayoutBuilder.d.ts.map +1 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts +2 -0
- package/lib/typescript/linking/ModalLayoutBuilder.test.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.d.ts +23 -0
- package/lib/typescript/linking/RouteMatcher.d.ts.map +1 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts +2 -0
- package/lib/typescript/linking/RouteMatcher.test.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.d.ts +16 -0
- package/lib/typescript/linking/URLParser.d.ts.map +1 -0
- package/lib/typescript/linking/URLParser.test.d.ts +2 -0
- package/lib/typescript/linking/URLParser.test.d.ts.map +1 -0
- package/lib/typescript/linking/types.d.ts +107 -0
- package/lib/typescript/linking/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +30 -0
- package/src/Navigation.ts +36 -1
- package/src/NavigationDelegate.ts +22 -0
- package/src/adapters/AndroidCustomRowForwarder.ts +83 -0
- package/src/commands/Commands.ts +15 -0
- package/src/index.ts +1 -0
- package/src/interfaces/Options.ts +87 -0
- package/src/linking/DeferredLinkQueue.test.ts +60 -0
- package/src/linking/DeferredLinkQueue.ts +55 -0
- package/src/linking/LinkingHandler.test.ts +332 -0
- package/src/linking/LinkingHandler.ts +169 -0
- package/src/linking/ModalLayoutBuilder.test.ts +105 -0
- package/src/linking/ModalLayoutBuilder.ts +60 -0
- package/src/linking/RouteMatcher.test.ts +128 -0
- package/src/linking/RouteMatcher.ts +126 -0
- package/src/linking/URLParser.test.ts +105 -0
- package/src/linking/URLParser.ts +62 -0
- package/src/linking/types.ts +115 -0
|
@@ -3,19 +3,22 @@ package com.reactnativenavigation.views.stack.topbar.titlebar;
|
|
|
3
3
|
import android.annotation.SuppressLint;
|
|
4
4
|
import android.content.Context;
|
|
5
5
|
import android.util.TypedValue;
|
|
6
|
+
import android.view.Gravity;
|
|
6
7
|
import android.view.View;
|
|
8
|
+
import android.widget.FrameLayout;
|
|
7
9
|
|
|
8
|
-
import com.facebook.react.ReactInstanceManager;
|
|
9
10
|
import com.reactnativenavigation.options.ComponentOptions;
|
|
10
11
|
import com.reactnativenavigation.options.params.Number;
|
|
11
12
|
import com.reactnativenavigation.react.ReactView;
|
|
12
13
|
|
|
14
|
+
import static android.view.View.MeasureSpec.AT_MOST;
|
|
13
15
|
import static android.view.View.MeasureSpec.EXACTLY;
|
|
14
16
|
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
|
15
17
|
import static com.reactnativenavigation.utils.UiUtils.dpToPx;
|
|
16
18
|
|
|
17
19
|
@SuppressLint("ViewConstructor")
|
|
18
20
|
public class TitleBarReactButtonView extends ReactView {
|
|
21
|
+
private static final float FINAL_WIDTH_PADDING_DP = 1f;
|
|
19
22
|
private final ComponentOptions component;
|
|
20
23
|
|
|
21
24
|
public TitleBarReactButtonView(Context context, ComponentOptions component) {
|
|
@@ -23,28 +26,67 @@ public class TitleBarReactButtonView extends ReactView {
|
|
|
23
26
|
this.component = component;
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
@Override
|
|
30
|
+
public void onViewAdded(View child) {
|
|
31
|
+
super.onViewAdded(child);
|
|
32
|
+
if (child.getLayoutParams() instanceof FrameLayout.LayoutParams) {
|
|
33
|
+
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) child.getLayoutParams();
|
|
34
|
+
layoutParams.gravity = layoutParams.gravity == -1
|
|
35
|
+
? Gravity.CENTER_VERTICAL
|
|
36
|
+
: (layoutParams.gravity & ~Gravity.VERTICAL_GRAVITY_MASK) | Gravity.CENTER_VERTICAL;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
@Override
|
|
27
41
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
28
|
-
|
|
29
|
-
//This is a workaround, ReactNative throws exception when views have ids, On android MenuItems
|
|
42
|
+
// This is a workaround, ReactNative throws exception when views have ids, On android MenuItems
|
|
30
43
|
// With ActionViews like this got an id, see #7253
|
|
31
44
|
if (!this.isAttachedToWindow()) {
|
|
32
45
|
this.setId(View.NO_ID);
|
|
33
46
|
}
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
int initialWidthSpec = component.width.hasValue()
|
|
49
|
+
? createExactSpec(component.width)
|
|
50
|
+
: makeMeasureSpec(resolveAvailableWidth(widthMeasureSpec), AT_MOST);
|
|
51
|
+
int initialHeightSpec = createHeightSpec(heightMeasureSpec, component.height);
|
|
52
|
+
|
|
53
|
+
// First discover the content size without forcing every custom button to actionBarSize.
|
|
54
|
+
super.onMeasure(initialWidthSpec, initialHeightSpec);
|
|
55
|
+
|
|
56
|
+
if (component.width.hasValue() && component.height.hasValue()) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Then give RN/Yoga a stable exact final box for compatibility with centered button layouts.
|
|
61
|
+
// A small allowance avoids clipping implicit RN padding/subpixel layout while staying content-based.
|
|
62
|
+
int finalWidth = component.width.hasValue()
|
|
63
|
+
? MeasureSpec.getSize(initialWidthSpec)
|
|
64
|
+
: resolveFinalWidth(getMeasuredWidth());
|
|
65
|
+
int finalHeight = component.height.hasValue()
|
|
66
|
+
? MeasureSpec.getSize(initialHeightSpec)
|
|
67
|
+
: Math.max(getMeasuredHeight(), 1);
|
|
68
|
+
super.onMeasure(makeMeasureSpec(finalWidth, EXACTLY), makeMeasureSpec(finalHeight, EXACTLY));
|
|
36
69
|
}
|
|
37
70
|
|
|
38
|
-
private int
|
|
71
|
+
private int createHeightSpec(int measureSpec, Number dimension) {
|
|
39
72
|
if (dimension.hasValue()) {
|
|
40
|
-
return
|
|
41
|
-
} else {
|
|
42
|
-
// When JS doesn't pass width/height, default to the theme's actionBarSize (48dp on Material).
|
|
43
|
-
// Yoga's intrinsic measurement of the React view collapses `paddingHorizontal` on the
|
|
44
|
-
// trailing edge in RTL (RN/Fabric measurement quirk), so we cannot trust UNSPECIFIED here -
|
|
45
|
-
// it produces a 0dp visible inset against the screen edge in RTL.
|
|
46
|
-
return makeMeasureSpec(resolveActionBarSize(), EXACTLY);
|
|
73
|
+
return createExactSpec(dimension);
|
|
47
74
|
}
|
|
75
|
+
int availableSize = MeasureSpec.getSize(measureSpec);
|
|
76
|
+
return makeMeasureSpec(availableSize > 0 ? availableSize : Math.max(resolveActionBarSize(), 1), AT_MOST);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private int resolveAvailableWidth(int measureSpec) {
|
|
80
|
+
int availableSize = MeasureSpec.getSize(measureSpec);
|
|
81
|
+
return availableSize > 0 ? availableSize : Math.max(getResources().getDisplayMetrics().widthPixels, 1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private int resolveFinalWidth(int measuredContentWidth) {
|
|
85
|
+
return Math.max(measuredContentWidth + (int) Math.ceil(dpToPx(getContext(), FINAL_WIDTH_PADDING_DP)), 1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private int createExactSpec(Number dimension) {
|
|
89
|
+
return makeMeasureSpec(MeasureSpec.getSize(dpToPx(getContext(), dimension.get())), EXACTLY);
|
|
48
90
|
}
|
|
49
91
|
|
|
50
92
|
private int resolveActionBarSize() {
|
package/android/src/test/java/com/reactnativenavigation/views/TitleAndButtonsContainerTest.kt
CHANGED
|
@@ -294,6 +294,20 @@ class TitleAndButtonsContainerTest : BaseTest() {
|
|
|
294
294
|
assertThat(uut.getTitleComponent().right).isEqualTo(UUT_WIDTH - rightBarWidth - DEFAULT_LEFT_MARGIN_PX)
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
@Test
|
|
298
|
+
fun `Component - title width shrinks by measured right buttons only`() {
|
|
299
|
+
val rightButtonsWidth = 48
|
|
300
|
+
setup(
|
|
301
|
+
rightBarWidth = rightButtonsWidth,
|
|
302
|
+
componentWidth = UUT_WIDTH,
|
|
303
|
+
alignment = Alignment.Default
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
idleMainLooper()
|
|
307
|
+
assertThat(uut.getTitleComponent().left).isEqualTo(DEFAULT_LEFT_MARGIN_PX)
|
|
308
|
+
assertThat(uut.getTitleComponent().right).isEqualTo(UUT_WIDTH - rightButtonsWidth - DEFAULT_LEFT_MARGIN_PX)
|
|
309
|
+
}
|
|
310
|
+
|
|
297
311
|
@Test
|
|
298
312
|
fun `Component - should place title between the toolbars`() {
|
|
299
313
|
val leftBarWidth = 50
|
|
@@ -475,4 +489,4 @@ class TitleAndButtonsContainerTest : BaseTest() {
|
|
|
475
489
|
}
|
|
476
490
|
|
|
477
491
|
private fun getTitleSubtitleView() = (uut.getTitleComponent() as TitleSubTitleLayout)
|
|
478
|
-
}
|
|
492
|
+
}
|
package/android/src/test/java/com/reactnativenavigation/views/TitleBarReactButtonViewTest.java
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
package com.reactnativenavigation.views;
|
|
2
|
+
|
|
3
|
+
import static android.view.View.MeasureSpec.AT_MOST;
|
|
4
|
+
import static android.view.View.MeasureSpec.EXACTLY;
|
|
5
|
+
import static android.view.View.MeasureSpec.getMode;
|
|
6
|
+
import static android.view.View.MeasureSpec.getSize;
|
|
7
|
+
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
|
8
|
+
import static org.assertj.core.api.Java6Assertions.assertThat;
|
|
9
|
+
|
|
10
|
+
import android.app.Activity;
|
|
11
|
+
import android.util.TypedValue;
|
|
12
|
+
import android.view.Gravity;
|
|
13
|
+
import android.view.View;
|
|
14
|
+
import android.view.ViewGroup;
|
|
15
|
+
import android.widget.FrameLayout;
|
|
16
|
+
|
|
17
|
+
import com.reactnativenavigation.BaseTest;
|
|
18
|
+
import com.reactnativenavigation.options.ComponentOptions;
|
|
19
|
+
import com.reactnativenavigation.options.params.Number;
|
|
20
|
+
import com.reactnativenavigation.options.params.Text;
|
|
21
|
+
import com.reactnativenavigation.utils.UiUtils;
|
|
22
|
+
import com.reactnativenavigation.views.stack.topbar.titlebar.TitleBarReactButtonView;
|
|
23
|
+
|
|
24
|
+
import org.junit.Test;
|
|
25
|
+
|
|
26
|
+
import java.util.ArrayList;
|
|
27
|
+
import java.util.List;
|
|
28
|
+
|
|
29
|
+
public class TitleBarReactButtonViewTest extends BaseTest {
|
|
30
|
+
private static final int PARENT_WIDTH = 200;
|
|
31
|
+
private static final int PARENT_HEIGHT = 100;
|
|
32
|
+
private static final int CHILD_WIDTH = 24;
|
|
33
|
+
private static final int CHILD_HEIGHT = 16;
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
public void missingDimensionsMeasureToContentThenRemeasureExactForStableAlignment() {
|
|
37
|
+
Activity activity = newActivity();
|
|
38
|
+
TitleBarReactButtonView uut = createView(activity, new ComponentOptions());
|
|
39
|
+
RecordingContentView child = new RecordingContentView(activity);
|
|
40
|
+
setContentView(uut, child);
|
|
41
|
+
|
|
42
|
+
uut.measure(makeMeasureSpec(PARENT_WIDTH, AT_MOST), makeMeasureSpec(PARENT_HEIGHT, AT_MOST));
|
|
43
|
+
|
|
44
|
+
assertThat(uut.getMeasuredWidth()).isEqualTo(finalWidth(activity));
|
|
45
|
+
assertThat(uut.getMeasuredHeight()).isEqualTo(CHILD_HEIGHT);
|
|
46
|
+
assertThat(child.widthMeasureSpecs.size()).isEqualTo(2);
|
|
47
|
+
assertThat(getMode(child.widthMeasureSpecs.get(0))).isEqualTo(AT_MOST);
|
|
48
|
+
assertThat(getSize(child.widthMeasureSpecs.get(0))).isEqualTo(PARENT_WIDTH);
|
|
49
|
+
assertThat(getMode(child.heightMeasureSpecs.get(0))).isEqualTo(AT_MOST);
|
|
50
|
+
assertThat(getSize(child.heightMeasureSpecs.get(0))).isEqualTo(PARENT_HEIGHT);
|
|
51
|
+
assertThat(getMode(child.widthMeasureSpecs.get(1))).isEqualTo(EXACTLY);
|
|
52
|
+
assertThat(getSize(child.widthMeasureSpecs.get(1))).isEqualTo(finalWidth(activity));
|
|
53
|
+
assertThat(getMode(child.heightMeasureSpecs.get(1))).isEqualTo(EXACTLY);
|
|
54
|
+
assertThat(getSize(child.heightMeasureSpecs.get(1))).isEqualTo(CHILD_HEIGHT);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test
|
|
58
|
+
public void explicitDimensionsMeasureExactly() {
|
|
59
|
+
Activity activity = newActivity();
|
|
60
|
+
ComponentOptions component = new ComponentOptions();
|
|
61
|
+
component.width = new Number(72);
|
|
62
|
+
component.height = new Number(32);
|
|
63
|
+
TitleBarReactButtonView uut = createView(activity, component);
|
|
64
|
+
RecordingContentView child = new RecordingContentView(activity);
|
|
65
|
+
setContentView(uut, child);
|
|
66
|
+
|
|
67
|
+
uut.measure(makeMeasureSpec(PARENT_WIDTH, AT_MOST), makeMeasureSpec(PARENT_HEIGHT, AT_MOST));
|
|
68
|
+
|
|
69
|
+
assertThat(uut.getMeasuredWidth()).isEqualTo(UiUtils.dpToPx(activity, 72));
|
|
70
|
+
assertThat(uut.getMeasuredHeight()).isEqualTo(UiUtils.dpToPx(activity, 32));
|
|
71
|
+
assertThat(child.widthMeasureSpecs.size()).isEqualTo(1);
|
|
72
|
+
assertThat(getMode(child.widthMeasureSpecs.get(0))).isEqualTo(EXACTLY);
|
|
73
|
+
assertThat(getSize(child.widthMeasureSpecs.get(0))).isEqualTo(UiUtils.dpToPx(activity, 72));
|
|
74
|
+
assertThat(getMode(child.heightMeasureSpecs.get(0))).isEqualTo(EXACTLY);
|
|
75
|
+
assertThat(getSize(child.heightMeasureSpecs.get(0))).isEqualTo(UiUtils.dpToPx(activity, 32));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Test
|
|
79
|
+
public void zeroParentSpecsFallbackToBoundedAtMostSpecs() {
|
|
80
|
+
Activity activity = newActivity();
|
|
81
|
+
TitleBarReactButtonView uut = createView(activity, new ComponentOptions());
|
|
82
|
+
RecordingContentView child = new RecordingContentView(activity);
|
|
83
|
+
setContentView(uut, child);
|
|
84
|
+
|
|
85
|
+
uut.measure(makeMeasureSpec(0, AT_MOST), makeMeasureSpec(0, AT_MOST));
|
|
86
|
+
|
|
87
|
+
assertThat(child.widthMeasureSpecs.size()).isEqualTo(2);
|
|
88
|
+
assertThat(getMode(child.widthMeasureSpecs.get(0))).isEqualTo(AT_MOST);
|
|
89
|
+
assertThat(getSize(child.widthMeasureSpecs.get(0)))
|
|
90
|
+
.isEqualTo(Math.max(activity.getResources().getDisplayMetrics().widthPixels, 1));
|
|
91
|
+
assertThat(getMode(child.widthMeasureSpecs.get(1))).isEqualTo(EXACTLY);
|
|
92
|
+
assertThat(getSize(child.widthMeasureSpecs.get(1))).isEqualTo(finalWidth(activity));
|
|
93
|
+
assertThat(getMode(child.heightMeasureSpecs.get(0))).isEqualTo(AT_MOST);
|
|
94
|
+
assertThat(getSize(child.heightMeasureSpecs.get(0))).isEqualTo(Math.max(resolveActionBarSize(activity), 1));
|
|
95
|
+
assertThat(getMode(child.heightMeasureSpecs.get(1))).isEqualTo(EXACTLY);
|
|
96
|
+
assertThat(getSize(child.heightMeasureSpecs.get(1))).isEqualTo(CHILD_HEIGHT);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
public void rtlMissingDimensionsUseBoundedSpecs() {
|
|
101
|
+
Activity activity = newActivity();
|
|
102
|
+
TitleBarReactButtonView uut = createView(activity, new ComponentOptions());
|
|
103
|
+
RecordingContentView child = new RecordingContentView(activity);
|
|
104
|
+
uut.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
|
|
105
|
+
setContentView(uut, child);
|
|
106
|
+
|
|
107
|
+
uut.measure(makeMeasureSpec(PARENT_WIDTH, AT_MOST), makeMeasureSpec(PARENT_HEIGHT, AT_MOST));
|
|
108
|
+
|
|
109
|
+
assertThat(child.widthMeasureSpecs.size()).isEqualTo(2);
|
|
110
|
+
assertThat(getMode(child.widthMeasureSpecs.get(0))).isEqualTo(AT_MOST);
|
|
111
|
+
assertThat(getSize(child.widthMeasureSpecs.get(0))).isEqualTo(PARENT_WIDTH);
|
|
112
|
+
assertThat(getMode(child.widthMeasureSpecs.get(1))).isEqualTo(EXACTLY);
|
|
113
|
+
assertThat(getSize(child.widthMeasureSpecs.get(1))).isEqualTo(finalWidth(activity));
|
|
114
|
+
assertThat(getMode(child.heightMeasureSpecs.get(1))).isEqualTo(EXACTLY);
|
|
115
|
+
assertThat(getSize(child.heightMeasureSpecs.get(1))).isEqualTo(CHILD_HEIGHT);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test
|
|
119
|
+
public void contentRemainsCenteredWhenMenuCellLaysButtonOutTallerThanMeasuredHeight() {
|
|
120
|
+
Activity activity = newActivity();
|
|
121
|
+
TitleBarReactButtonView uut = createView(activity, new ComponentOptions());
|
|
122
|
+
RecordingContentView child = new RecordingContentView(activity);
|
|
123
|
+
setContentView(uut, child);
|
|
124
|
+
|
|
125
|
+
uut.measure(makeMeasureSpec(PARENT_WIDTH, AT_MOST), makeMeasureSpec(PARENT_HEIGHT, AT_MOST));
|
|
126
|
+
uut.layout(0, 0, uut.getMeasuredWidth(), PARENT_HEIGHT);
|
|
127
|
+
|
|
128
|
+
assertThat(uut.getMeasuredHeight()).isEqualTo(CHILD_HEIGHT);
|
|
129
|
+
assertThat(child.getTop()).isEqualTo((PARENT_HEIGHT - CHILD_HEIGHT) / 2);
|
|
130
|
+
assertThat(child.getBottom()).isEqualTo((PARENT_HEIGHT + CHILD_HEIGHT) / 2);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
public void contentCenteringReplacesExistingVerticalGravityOnly() {
|
|
135
|
+
Activity activity = newActivity();
|
|
136
|
+
TitleBarReactButtonView uut = createView(activity, new ComponentOptions());
|
|
137
|
+
RecordingContentView child = new RecordingContentView(activity);
|
|
138
|
+
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
|
139
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
140
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
141
|
+
);
|
|
142
|
+
params.gravity = Gravity.TOP | Gravity.RIGHT;
|
|
143
|
+
|
|
144
|
+
uut.addView(child, params);
|
|
145
|
+
|
|
146
|
+
FrameLayout.LayoutParams updatedParams = (FrameLayout.LayoutParams) child.getLayoutParams();
|
|
147
|
+
assertThat(updatedParams.gravity & Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_VERTICAL);
|
|
148
|
+
assertThat(updatedParams.gravity & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.RIGHT);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private TitleBarReactButtonView createView(Activity activity, ComponentOptions component) {
|
|
152
|
+
component.name = new Text("ButtonComponent");
|
|
153
|
+
component.componentId = new Text("ButtonComponentId");
|
|
154
|
+
return new TitleBarReactButtonView(activity, component);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private void setContentView(TitleBarReactButtonView uut, View child) {
|
|
158
|
+
uut.removeAllViews();
|
|
159
|
+
uut.addView(child, new ViewGroup.LayoutParams(
|
|
160
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
161
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
162
|
+
));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private int resolveActionBarSize(Activity activity) {
|
|
166
|
+
TypedValue tv = new TypedValue();
|
|
167
|
+
if (activity.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
|
|
168
|
+
return TypedValue.complexToDimensionPixelSize(tv.data, activity.getResources().getDisplayMetrics());
|
|
169
|
+
}
|
|
170
|
+
return UiUtils.dpToPx(activity, 48);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private int finalWidth(Activity activity) {
|
|
174
|
+
return CHILD_WIDTH + (int) Math.ceil(UiUtils.dpToPx(activity, 1f));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static class RecordingContentView extends View {
|
|
178
|
+
final List<Integer> widthMeasureSpecs = new ArrayList<>();
|
|
179
|
+
final List<Integer> heightMeasureSpecs = new ArrayList<>();
|
|
180
|
+
|
|
181
|
+
RecordingContentView(Activity activity) {
|
|
182
|
+
super(activity);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@Override
|
|
186
|
+
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
187
|
+
widthMeasureSpecs.add(widthMeasureSpec);
|
|
188
|
+
heightMeasureSpecs.add(heightMeasureSpec);
|
|
189
|
+
|
|
190
|
+
int measuredWidth = getMode(widthMeasureSpec) == EXACTLY
|
|
191
|
+
? getSize(widthMeasureSpec)
|
|
192
|
+
: Math.min(CHILD_WIDTH, getSize(widthMeasureSpec));
|
|
193
|
+
int measuredHeight = getMode(heightMeasureSpec) == EXACTLY
|
|
194
|
+
? getSize(heightMeasureSpec)
|
|
195
|
+
: Math.min(CHILD_HEIGHT, getSize(heightMeasureSpec));
|
|
196
|
+
setMeasuredDimension(measuredWidth, measuredHeight);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
package/ios/ARCHITECTURE.md
CHANGED
|
@@ -28,6 +28,11 @@ Base class that user's AppDelegate must extend. Handles React Native and navigat
|
|
|
28
28
|
- Creates `RCTRootViewFactory` and `ReactHost`
|
|
29
29
|
- Calls `[ReactNativeNavigation bootstrapWithHost:]` to initialize navigation
|
|
30
30
|
- Handles RN version differences (0.77, 0.78, 0.79+) via compile-time macros
|
|
31
|
+
- **Deep linking plumbing**:
|
|
32
|
+
- Implements `application:openURL:options:` and `application:continueUserActivity:restorationHandler:`; both call `-dispatchDeepLinkURL:`.
|
|
33
|
+
- `-dispatchDeepLinkURL:` posts `RCTOpenURLNotification` directly if the React runtime is ready, otherwise enqueues the URL.
|
|
34
|
+
- Observes `RCTContentDidAppearNotification` (the Fabric/bridgeless signal) to flush the queue. This solves the cold-start race where URLs (push notifications, OS link launches) arrive before `RCTLinkingManager` is listening.
|
|
35
|
+
- Subclasses can call `-dispatchDeepLinkURL:` manually from notification delegates or any other URL source; the queueing behavior is reused automatically.
|
|
31
36
|
|
|
32
37
|
### ReactNativeNavigation Bootstrap
|
|
33
38
|
**File**: `ReactNativeNavigation.h/mm`
|
package/ios/BottomTabPresenter.h
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
@property(nonatomic, strong, readonly) RNNTabBarItemCreator *tabCreator;
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* When YES, tabs whose options declare `bottomTab.component` skip native
|
|
9
|
+
* icon/text/sfSymbol/role application. The accompanying
|
|
10
|
+
* `RNNCustomTabBarItemView` is responsible for visual rendering of the tab.
|
|
11
|
+
*/
|
|
12
|
+
@property(nonatomic, assign) BOOL useCustomItemViews;
|
|
13
|
+
|
|
7
14
|
- (instancetype)initWithDefaultOptions:(RNNNavigationOptions *)defaultOptions
|
|
8
15
|
tabCreator:(RNNTabBarItemCreator *)tabCreator;
|
|
9
16
|
|
|
@@ -36,10 +36,37 @@
|
|
|
36
36
|
|
|
37
37
|
- (void)createTabBarItem:(UIViewController *)child
|
|
38
38
|
bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
|
|
39
|
+
if (_useCustomItemViews && bottomTabOptions.component.name.hasValue) {
|
|
40
|
+
UITabBarItem *blankItem = [self createBlankTabBarItem:child
|
|
41
|
+
bottomTabOptions:bottomTabOptions];
|
|
42
|
+
if (blankItem != child.tabBarItem) {
|
|
43
|
+
child.tabBarItem = blankItem;
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
UITabBarItem *updatedItem = [_tabCreator createTabBarItem:bottomTabOptions mergeItem:child.tabBarItem];
|
|
40
49
|
if (updatedItem != child.tabBarItem) {
|
|
41
50
|
child.tabBarItem = updatedItem;
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
// Builds a truly blank `UITabBarItem` (nil image, nil title). When custom
|
|
55
|
+
// item views are active, `RNNBottomTabsController` hides the native tab bar
|
|
56
|
+
// visuals and renders the custom row on top. The bar item still needs to
|
|
57
|
+
// exist so that `UITabBarController` reserves the right number of slots and
|
|
58
|
+
// the bottom safe-area inset.
|
|
59
|
+
- (UITabBarItem *)createBlankTabBarItem:(UIViewController *)child
|
|
60
|
+
bottomTabOptions:(RNNBottomTabOptions *)bottomTabOptions {
|
|
61
|
+
UITabBarItem *item = child.tabBarItem ?: [UITabBarItem new];
|
|
62
|
+
item.image = nil;
|
|
63
|
+
item.selectedImage = nil;
|
|
64
|
+
item.title = nil;
|
|
65
|
+
item.tag = bottomTabOptions.tag;
|
|
66
|
+
item.accessibilityIdentifier = [bottomTabOptions.testID withDefault:nil];
|
|
67
|
+
item.accessibilityLabel = [bottomTabOptions.accessibilityLabel withDefault:nil];
|
|
68
|
+
item.imageInsets = UIEdgeInsetsZero;
|
|
69
|
+
return item;
|
|
70
|
+
}
|
|
71
|
+
|
|
45
72
|
@end
|
package/ios/RNNAppDelegate.h
CHANGED
|
@@ -56,4 +56,20 @@
|
|
|
56
56
|
@property(nonatomic) BOOL bridgelessEnabled;
|
|
57
57
|
#endif
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Dispatch a deep link URL through React Native's Linking module so JS
|
|
61
|
+
* subscribers (including RNN's built-in deep linking framework) receive it.
|
|
62
|
+
*
|
|
63
|
+
* Safe to call before the JS bridge is ready: URLs that arrive early
|
|
64
|
+
* (e.g. cold-start notification taps) are queued natively and flushed
|
|
65
|
+
* automatically once Fabric/React content first appears.
|
|
66
|
+
*
|
|
67
|
+
* Custom-scheme and universal-link openings dispatched by the OS are
|
|
68
|
+
* forwarded through this method automatically; call it manually only
|
|
69
|
+
* when your app receives a deep link from a source RNN can't intercept
|
|
70
|
+
* (e.g. a custom `UNUserNotificationCenterDelegate`, a third-party push
|
|
71
|
+
* SDK callback, etc.).
|
|
72
|
+
*/
|
|
73
|
+
- (void)dispatchDeepLinkURL:(NSURL *)url;
|
|
74
|
+
|
|
59
75
|
@end
|
package/ios/RNNAppDelegate.mm
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
#import <React/RCTCxxBridgeDelegate.h>
|
|
9
9
|
#endif
|
|
10
10
|
#import <React/RCTLegacyViewManagerInteropComponentView.h>
|
|
11
|
+
#import <React/RCTLinkingManager.h>
|
|
12
|
+
#import <React/RCTRootView.h>
|
|
11
13
|
#import <React/RCTSurfacePresenter.h>
|
|
12
14
|
#if __has_include(<React/RCTSurfacePresenterStub.h>)
|
|
13
15
|
#import <React/RCTSurfacePresenterStub.h>
|
|
@@ -36,6 +38,13 @@
|
|
|
36
38
|
|
|
37
39
|
#import <React/RCTComponentViewFactory.h>
|
|
38
40
|
|
|
41
|
+
// Deep-link URLs that arrive (openURL, universal link, or external dispatch)
|
|
42
|
+
// before the React runtime is ready are queued here and flushed when Fabric
|
|
43
|
+
// posts `RCTContentDidAppearNotification` — by which point
|
|
44
|
+
// `RCTLinkingManager` is instantiated and JS subscribers are listening.
|
|
45
|
+
static NSMutableArray<NSURL *> *gRNNPendingDeepLinkURLs = nil;
|
|
46
|
+
static BOOL gRNNReactRuntimeReady = NO;
|
|
47
|
+
|
|
39
48
|
|
|
40
49
|
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
|
|
41
50
|
|
|
@@ -92,9 +101,73 @@ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
|
92
101
|
[ReactNativeNavigation bootstrapWithHost:self.reactNativeFactory.rootViewFactory.reactHost];
|
|
93
102
|
#endif
|
|
94
103
|
|
|
104
|
+
[self rnn_installDeepLinkObservers];
|
|
105
|
+
|
|
95
106
|
return YES;
|
|
96
107
|
}
|
|
97
108
|
|
|
109
|
+
#pragma mark - Deep linking
|
|
110
|
+
|
|
111
|
+
// Forward OS-delivered custom-scheme URLs to React Native's Linking module.
|
|
112
|
+
- (BOOL)application:(UIApplication *)application
|
|
113
|
+
openURL:(NSURL *)url
|
|
114
|
+
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
|
115
|
+
[self dispatchDeepLinkURL:url];
|
|
116
|
+
return YES;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Forward universal links (associated domains) to React Native's Linking
|
|
120
|
+
// module by extracting the underlying https URL and routing it through the
|
|
121
|
+
// same pre-bridge queue as everything else.
|
|
122
|
+
- (BOOL)application:(UIApplication *)application
|
|
123
|
+
continueUserActivity:(NSUserActivity *)userActivity
|
|
124
|
+
restorationHandler:
|
|
125
|
+
(void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler {
|
|
126
|
+
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
|
127
|
+
[self dispatchDeepLinkURL:userActivity.webpageURL];
|
|
128
|
+
return YES;
|
|
129
|
+
}
|
|
130
|
+
return NO;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
- (void)dispatchDeepLinkURL:(NSURL *)url {
|
|
134
|
+
if (url == nil) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (gRNNReactRuntimeReady) {
|
|
138
|
+
[RCTLinkingManager application:[UIApplication sharedApplication]
|
|
139
|
+
openURL:url
|
|
140
|
+
options:@{}];
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (gRNNPendingDeepLinkURLs == nil) {
|
|
144
|
+
gRNNPendingDeepLinkURLs = [NSMutableArray array];
|
|
145
|
+
}
|
|
146
|
+
[gRNNPendingDeepLinkURLs addObject:url];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
- (void)rnn_installDeepLinkObservers {
|
|
150
|
+
// `RCTContentDidAppearNotification` is posted by Fabric's root view
|
|
151
|
+
// once content has rendered. RNN forces bridgeless/new-arch, so the
|
|
152
|
+
// legacy `RCTJavaScriptDidLoadNotification` never fires; we rely on
|
|
153
|
+
// this Fabric signal exclusively.
|
|
154
|
+
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
155
|
+
selector:@selector(rnn_handleReactRuntimeReady:)
|
|
156
|
+
name:RCTContentDidAppearNotification
|
|
157
|
+
object:nil];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
- (void)rnn_handleReactRuntimeReady:(NSNotification *)notification {
|
|
161
|
+
gRNNReactRuntimeReady = YES;
|
|
162
|
+
NSArray<NSURL *> *pending = [gRNNPendingDeepLinkURLs copy];
|
|
163
|
+
[gRNNPendingDeepLinkURLs removeAllObjects];
|
|
164
|
+
for (NSURL *url in pending) {
|
|
165
|
+
[RCTLinkingManager application:[UIApplication sharedApplication]
|
|
166
|
+
openURL:url
|
|
167
|
+
options:@{}];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
98
171
|
|
|
99
172
|
#if !RNN_RN_VERSION_79_OR_NEWER
|
|
100
173
|
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#import "RNNComponentOptions.h"
|
|
1
2
|
#import "RNNOptions.h"
|
|
2
3
|
|
|
3
4
|
@class DotIndicatorOptions;
|
|
@@ -5,6 +6,7 @@
|
|
|
5
6
|
@interface RNNBottomTabOptions : RNNOptions
|
|
6
7
|
|
|
7
8
|
@property(nonatomic) NSUInteger tag;
|
|
9
|
+
@property(nonatomic, strong) RNNComponentOptions *component;
|
|
8
10
|
@property(nonatomic, strong) Text *text;
|
|
9
11
|
@property(nonatomic, strong) Text *badge;
|
|
10
12
|
@property(nonatomic, strong) Color *badgeColor;
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
self = [super initWithDict:dict];
|
|
9
9
|
self.tag = arc4random();
|
|
10
10
|
|
|
11
|
+
self.component =
|
|
12
|
+
[[RNNComponentOptions alloc] initWithDict:[dict objectForKey:@"component"]];
|
|
13
|
+
|
|
11
14
|
self.text = [TextParser parse:dict key:@"text"];
|
|
12
15
|
self.badge = [TextParser parse:dict key:@"badge"];
|
|
13
16
|
self.fontFamily = [TextParser parse:dict key:@"fontFamily"];
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
|
|
39
42
|
- (void)mergeOptions:(RNNBottomTabOptions *)options {
|
|
40
43
|
[self.dotIndicator mergeOptions:options.dotIndicator];
|
|
44
|
+
[self.component mergeOptions:options.component];
|
|
41
45
|
|
|
42
46
|
if (options.text.hasValue)
|
|
43
47
|
self.text = options.text;
|
|
@@ -88,7 +92,7 @@
|
|
|
88
92
|
self.iconColor.hasValue || self.selectedIconColor.hasValue ||
|
|
89
93
|
self.selectedTextColor.hasValue || self.iconInsets.hasValue || self.textColor.hasValue ||
|
|
90
94
|
self.visible.hasValue || self.selectTabOnPress.hasValue || self.sfSymbol.hasValue ||
|
|
91
|
-
self.sfSelectedSymbol.hasValue || self.role.hasValue;
|
|
95
|
+
self.sfSelectedSymbol.hasValue || self.role.hasValue || self.component.hasValue;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
@end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "RNNBottomTabsPresenter.h"
|
|
4
4
|
#import "RNNDotIndicatorPresenter.h"
|
|
5
5
|
#import "RNNEventEmitter.h"
|
|
6
|
+
#import "RNNReactComponentRegistry.h"
|
|
6
7
|
#import "UIViewController+LayoutProtocol.h"
|
|
7
8
|
#import <UIKit/UIKit.h>
|
|
8
9
|
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
presenter:(RNNBasePresenter *)presenter
|
|
17
18
|
bottomTabPresenter:(BottomTabPresenter *)bottomTabPresenter
|
|
18
19
|
dotIndicatorPresenter:(RNNDotIndicatorPresenter *)dotIndicatorPresenter
|
|
20
|
+
componentRegistry:(RNNReactComponentRegistry *)componentRegistry
|
|
19
21
|
eventEmitter:(RNNEventEmitter *)eventEmitter
|
|
20
22
|
childViewControllers:(NSArray *)childViewControllers
|
|
21
23
|
bottomTabsAttacher:(BottomTabsBaseAttacher *)bottomTabsAttacher;
|