Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions __mocks__/react-native-webview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const React = require('react');

const WebView = (props) => React.createElement('WebView', props);

module.exports = { WebView };
30 changes: 26 additions & 4 deletions ios/BottomTabsTogetherAttacher.mm
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,39 @@ @implementation BottomTabsTogetherAttacher

- (void)attach:(RNNBottomTabsController *)bottomTabsController {
dispatch_group_t ready = dispatch_group_create();


UIWindow *preloadWindow = [[UIWindow alloc] initWithFrame:CGRectZero];
preloadWindow.hidden = NO;

NSMapTable *reactViewToParent = [NSMapTable strongToStrongObjectsMapTable];

for (UIViewController *vc in bottomTabsController.childViewControllers) {
dispatch_group_enter(ready);
[vc setReactViewReadyCallback:^{
dispatch_group_leave(ready);
dispatch_group_leave(ready);
}];

[vc render];

if ([vc isKindOfClass:[UINavigationController class]]) {
UIView *containerView = [(UINavigationController *)vc topViewController].view;
UIView *reactView = containerView.subviews.firstObject;

if (reactView && !reactView.window) {
[reactViewToParent setObject:containerView forKey:reactView];
[preloadWindow addSubview:reactView];
}
}
}

dispatch_notify(ready, dispatch_get_main_queue(), ^{
[bottomTabsController readyForPresentation];
for (UIView *reactView in reactViewToParent) {
UIView *parent = [reactViewToParent objectForKey:reactView];
reactView.frame = parent.bounds;
[parent addSubview:reactView];
}
preloadWindow.hidden = YES; //Keep preloadWindow reference alive to this point
[bottomTabsController readyForPresentation];
});
}

Expand Down
1 change: 1 addition & 0 deletions jest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jest.mock('react-native-gesture-handler', () => {
};
});


mockDetox(() => require('./playground/index'));

beforeEach(() => {
Expand Down
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
preset: 'react-native',
transformIgnorePatterns: [
'node_modules/(?!(@react-native|react-native|react-native-ui-lib|react-native-animatable|react-native-reanimated)/)',
'node_modules/(?!(@react-native|react-native|react-native-ui-lib|react-native-animatable|react-native-reanimated|react-native-webview)/)',
],
transform: {
'\\.[jt]sx?$': 'babel-jest',
Expand All @@ -20,6 +20,7 @@ module.exports = {
moduleNameMapper: {
'^react-native$': '<rootDir>/node_modules/react-native',
'^react-native-gesture-handler$': '<rootDir>/node_modules/react-native-gesture-handler',
'^react-native-webview$': '<rootDir>/__mocks__/react-native-webview.js',
'react-native-navigation/Mock': '<rootDir>/Mock/index',
'react-native-navigation': '<rootDir>/src',
'^src$': '<rootDir>/src',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,4 @@
]
]
}
}
}
22 changes: 22 additions & 0 deletions playground/e2e/TabbedWebViewScreen.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Utils from './Utils';
import TestIDs from '../src/testIDs';

const { elementById, elementByLabel } = Utils;

describe.e2e(':ios: Tabs with Together flag', () => {
beforeEach(async () => {
await device.launchApp({ newInstance: true });
await elementById(TestIDs.BOTTOM_TABS_BTN).tap();
await expect(elementByLabel('First Tab')).toBeVisible();
});

it('should load all tabs when tabsAttachMode is together', async () => {
await elementById(TestIDs.TABS_TOGETHER_BTN).tap();
await waitFor(element(by.text(/\d→\d→\d/)))
.toExist()
.withTimeout(5000);

await elementById(TestIDs.TABS_TOGETHER_DISMISS).tap();
await expect(elementByLabel('First Tab')).toBeVisible();
});
});
1 change: 1 addition & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"prop-types": "15.x.x",
"react-lifecycles-compat": "^3.0.4",
"react-native-redash": "^12.6.1",
"react-native-webview": "^13.12.5",
"reanimated-color-picker": "^3.0.6",
"ssim.js": "^3.5.0",
"tslib": "1.9.3"
Expand Down
49 changes: 42 additions & 7 deletions playground/src/screens/FirstBottomTabScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React, { Component } from 'react';
import { Text } from 'react-native';
import { EmitterSubscription, Platform, Text } from 'react-native';
import { NavigationProps, Options } from 'react-native-navigation';

import Root from '../components/Root';
import Button from '../components/Button';
import Navigation from './../services/Navigation';
import Screens from './Screens';
import { component } from '../commons/Layouts';
import { stack, component } from '../commons/Layouts';
import testIDs from '../testIDs';
import bottomTabsStruct from './BottomTabsLayoutStructure';
import { resetWebViewLoadedOrder, TAB_SCREENS } from './TabbedWebViewScreen';

export class MountedBottomTabScreensState {
static mountedBottomTabScreens: string[] = [];
static callback: (mountedBottomTabScreens: string[]) => void = () => {};
static callback: (mountedBottomTabScreens: string[]) => void = () => { };

static addScreen(screen: string) {
this.mountedBottomTabScreens.push(screen);
Expand All @@ -34,6 +35,7 @@ const {
SCREEN_ROOT,
SET_ROOT_BTN,
BOTTOM_TABS,
TABS_TOGETHER_BTN,
} = testIDs;

interface NavigationState {
Expand Down Expand Up @@ -74,9 +76,19 @@ export default class FirstBottomTabScreen extends Component<NavigationProps, Nav
}

badgeVisible = true;
bottomTabPressedListener = Navigation.events().registerBottomTabPressedListener((event) => {
if (event.tabIndex == 2) {
alert('BottomTabPressed');

registerBottomTabListener = () => {
return Navigation.events().registerBottomTabPressedListener((event) => {
if (event.tabIndex == 2) {
alert('BottomTabPressed');
}
});
};

bottomTabPressedListener: EmitterSubscription | null = this.registerBottomTabListener();
modalDismissedListener = Navigation.events().registerModalDismissedListener((event) => {
if (event.componentId === 'TogetherFlagTabTest' && !this.bottomTabPressedListener) {
this.bottomTabPressedListener = this.registerBottomTabListener();
}
});

Expand All @@ -93,6 +105,13 @@ export default class FirstBottomTabScreen extends Component<NavigationProps, Nav
testID={SWITCH_TAB_BY_COMPONENT_ID_BTN}
onPress={this.switchTabByComponentId}
/>
{Platform.OS === 'ios' && (
<Button
label="Tabs loading with 'together' flag"
testID={TABS_TOGETHER_BTN}
onPress={this.launchTabbedWebViewScreen}
/>
)}
<Button label="Set Badge" testID={SET_BADGE_BTN} onPress={() => this.setBadge('NEW')} />
<Button label="Clear Badge" testID={CLEAR_BADGE_BTN} onPress={() => this.setBadge('')} />
<Button label="Show Notification Dot" onPress={() => this.setNotificationDot(true)} />
Expand All @@ -117,7 +136,8 @@ export default class FirstBottomTabScreen extends Component<NavigationProps, Nav
}

componentWillUnmount() {
this.bottomTabPressedListener.remove();
this.bottomTabPressedListener?.remove();
this.modalDismissedListener.remove();
}

modifyBottomTabs = () => {
Expand Down Expand Up @@ -214,4 +234,19 @@ export default class FirstBottomTabScreen extends Component<NavigationProps, Nav
);

push = () => Navigation.push(this, Screens.Pushed);

launchTabbedWebViewScreen = () => {
resetWebViewLoadedOrder();
this.bottomTabPressedListener?.remove();
this.bottomTabPressedListener = null;
Navigation.showModal({
bottomTabs: {
id: 'TogetherFlagTabTest',
options: { bottomTabs: { tabsAttachMode: 'together', titleDisplayMode: 'alwaysShow' } },
children: TAB_SCREENS.map((tab) =>
stack(component(tab.name, undefined, { tabIndex: tab.tabIndex }))
),
},
});
};
}
1 change: 1 addition & 0 deletions playground/src/screens/Screens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const Screens = {
SearchBarModal: 'SearchBarModal',
TopBar: 'TopBar',
TopBarTitleTest: 'TopBarTitleTest',
WebViewTab: 'TabbedWebViewScreen.WebViewTab',
};

export default Screens;
111 changes: 111 additions & 0 deletions playground/src/screens/TabbedWebViewScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { NavigationComponent, NavigationProps, Options } from 'react-native-navigation';
import { WebView } from 'react-native-webview';
import Navigation from '../services/Navigation';
import Screens from './Screens';
import testIDs from '../testIDs';

const { TABS_TOGETHER_DISMISS } = testIDs;

const webViewLoadedOrder: number[] = [];
const listeners: Set<() => void> = new Set();
const notifyListeners = () => listeners.forEach((fn) => fn());

const baseOptions = (title: string): Options => ({
topBar: {
title: { text: title },
leftButtons: [
{ id: 'dismiss', testID: TABS_TOGETHER_DISMISS, icon: require('../../img/clear.png') },
],
},
bottomTab: {
text: title,
icon: require('../../img/layouts.png'),
},
});

interface Props extends NavigationProps {
tabIndex: number;
}

interface State {
loadStartTimestamp: number | null;
}

class WebViewTab extends NavigationComponent<Props, State> {
state: State = { loadStartTimestamp: null };

static options(passProps: Props): Options {
return baseOptions(`Tab ${passProps.tabIndex + 1}`);
}

constructor(props: Props) {
super(props);
Navigation.events().bindComponent(this);
}

navigationButtonPressed({ buttonId }: { buttonId: string }) {
if (buttonId === 'dismiss') {
Navigation.dismissModal('TogetherFlagTabTest');
}
}

componentDidMount() {
const update = () => {
const text = webViewLoadedOrder.length > 0 ? webViewLoadedOrder.join('→') : '...';
Navigation.mergeOptions(this.props.componentId, {
topBar: {
subtitle: { text },
},
});
};
listeners.add(update);
update();
}

onLoadStart = () => {
if (!webViewLoadedOrder.includes(this.props.tabIndex)) {
webViewLoadedOrder.push(this.props.tabIndex);
this.setState({ loadStartTimestamp: Date.now() });
notifyListeners();
}
};

render() {
const { loadStartTimestamp } = this.state;
const timeString = loadStartTimestamp
? new Date(loadStartTimestamp).toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3,
})
: 'loading...';

return (
<WebView
source={{ html: `<html><body><h1>Tab ${this.props.tabIndex + 1}: started loading at ${timeString}</h1></body></html>` }}
style={styles.webview}
onLoadStart={this.onLoadStart}
/>
);
}
}

export { WebViewTab };

export const resetWebViewLoadedOrder = () => {
webViewLoadedOrder.length = 0;
};

export const TAB_SCREENS = [
{ name: Screens.WebViewTab, tabIndex: 0 },
{ name: Screens.WebViewTab, tabIndex: 1 },
{ name: Screens.WebViewTab, tabIndex: 2 },
];

const styles = StyleSheet.create({
webview: { flex: 1 },
});
4 changes: 4 additions & 0 deletions playground/src/screens/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ function registerScreens() {
);
Navigation.registerComponent('CustomTextButton', () => require('./CustomTextButton').default);
Navigation.registerComponent(Screens.KeyboardScreen, () => require('./KeyboardScreen').default);
Navigation.registerComponent(
Screens.WebViewTab,
() => require('./TabbedWebViewScreen').WebViewTab
);
Navigation.setLazyComponentRegistrator((componentName) => {
switch (componentName) {
case Screens.LazyTitleView:
Expand Down
4 changes: 4 additions & 0 deletions playground/src/testIDs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ const testIDs = {
LANDSCAPE_ELEMENT: 'LANDSCAPE_ELEMENT',
PORTRAIT_ELEMENT: 'PORTRAIT_ELEMENT',

// Tabs Together Test
TABS_TOGETHER_BTN: 'TABS_TOGETHER_BTN',
TABS_TOGETHER_DISMISS: 'TABS_TOGETHER_DISMISS',

// Headers
WELCOME_SCREEN_HEADER: 'WELCOME_SCREEN_HEADER',
PUSHED_SCREEN_HEADER: 'PUSHED_SCREEN_HEADER',
Expand Down
Loading