Repository: kiwicom/mobile
Pull request #1015 (2018-08-24 06:27:01)

MMB: Move split screen navigation active id to context
This is necessary since switchNavigator unmounts MMB stack, meaning that activeComponentId is lost. Moving it to context will persist it, so we are at correct navigation on tablet when we close Hotels modal.


Patch:
diff --git a/app/MMB/index.js b/app/MMB/index.js
index 73501d7f..58737837 100644
--- a/app/MMB/index.js
+++ b/app/MMB/index.js
@@ -15,6 +15,7 @@ import WalletContext from './src/context/WalletContext';
 import TravelDocumentFormContext from './src/scenes/travelDocument/form/TravelDocumentFormContext';
 import InsuranceOverviewContext from './src/scenes/tripServices/insurance/insuranceOverviewScene/InsuranceOverviewSceneContext';
 import HotelsContext from './src/context/HotelsContext';
+import SplitNavigationContext from './src/context/SplitNavigationContext';
 
 type Props = {|
   +currency: string,
@@ -45,9 +46,13 @@ class ManageMyBookingPackage extends React.Component<Props> {
                   dataSaverEnabled={this.props.dataSaverEnabled}
                   bookingComAffiliate={this.props.bookingComAffiliate}
                 >
-                  <NavigationStack
-                    onNavigationStateChange={this.props.onNavigationStateChange}
-                  />
+                  <SplitNavigationContext.Provider>
+                    <NavigationStack
+                      onNavigationStateChange={
+                        this.props.onNavigationStateChange
+                      }
+                    />
+                  </SplitNavigationContext.Provider>
                 </HotelsContext.Provider>
               </InsuranceOverviewContext.Provider>
             </TravelDocumentFormContext.Provider>
diff --git a/app/MMB/src/context/SplitNavigationContext.js b/app/MMB/src/context/SplitNavigationContext.js
new file mode 100644
index 00000000..68e96a22
--- /dev/null
+++ b/app/MMB/src/context/SplitNavigationContext.js
@@ -0,0 +1,66 @@
+// @flow
+
+import * as React from 'react';
+
+const defaultState = {
+  activeId: 'mmb.trip_overview',
+  actions: {
+    setActiveId: () => {},
+  },
+};
+
+const { Consumer, Provider: ContextProvider } = React.createContext({
+  ...defaultState,
+});
+
+type Props = {|
+  +children: React.Node,
+|};
+
+type State = {|
+  activeId: string,
+  +actions: {|
+    +setActiveId: (activeId: string) => void,
+  |},
+|};
+
+class Provider extends React.Component<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    this.state = {
+      ...defaultState,
+      actions: {
+        setActiveId: this.setActiveId,
+      },
+    };
+  }
+
+  setActiveId = (activeId: string) => {
+    this.setState({ activeId });
+  };
+
+  render = () => (
+    <ContextProvider value={this.state}>{this.props.children}</ContextProvider>
+  );
+}
+
+export default { Consumer, Provider };
+
+export const withSplitNavigationContext = (Component: React.ElementType) => {
+  const WithSplitNavigationContext = (props: Object) => {
+    return (
+      <Consumer>
+        {({ activeId, actions: { setActiveId } }) => (
+          <Component {...props} activeId={activeId} setActiveId={setActiveId} />
+        )}
+      </Consumer>
+    );
+  };
+
+  // $FlowExpectedError: We need to pass on the navigationOptions if any, flow does not know about it, but a react component might have it
+  if (Component.navigationOptions) {
+    WithSplitNavigationContext.navigationOptions = Component.navigationOptions;
+  }
+  return WithSplitNavigationContext;
+};
diff --git a/app/MMB/src/navigation/DetailScreen.js b/app/MMB/src/navigation/DetailScreen.js
index 73033878..38ef5c6a 100644
--- a/app/MMB/src/navigation/DetailScreen.js
+++ b/app/MMB/src/navigation/DetailScreen.js
@@ -21,6 +21,7 @@ import TripServices, {
 import PassengerDetailContainer from '../scenes/passenger/PassengerDetailContainer';
 import TripOverviewTablet from '../scenes/tripOverview/TripOverviewTablet';
 import Timeline, { TimelineSubmenuItems } from '../scenes/timeline/Timeline';
+import { withSplitNavigationContext } from '../context/SplitNavigationContext';
 
 export const MenuComponents = {
   'mmb.trip_overview': {
@@ -106,17 +107,11 @@ export const MenuComponents = {
 
 type Props = {|
   +navigation: NavigationType,
+  +activeId: string,
+  +setActiveId: (activeId: string) => void,
 |};
 
-type State = {|
-  activeContainerComponent: string,
-|};
-
-export default class DetailsScreen extends React.Component<Props, State> {
-  state = {
-    activeContainerComponent: 'mmb.trip_overview',
-  };
-
+class DetailsScreen extends React.Component<Props> {
   static navigationOptions = (props: {| navigation: NavigationType |}) => {
     const params = props.navigation.state.params;
 
@@ -153,9 +148,7 @@ export default class DetailsScreen extends React.Component<Props, State> {
    */
   changeContentOnTablet = (key: string) => {
     this.props.navigation.setParams({ activeContainerComponent: key });
-    this.setState({
-      activeContainerComponent: key,
-    });
+    this.props.setActiveId(key);
   };
 
   getContainerComponent = (key: string) => {
@@ -163,9 +156,8 @@ export default class DetailsScreen extends React.Component<Props, State> {
   };
 
   render = () => {
-    const ContainerComponent = this.getContainerComponent(
-      this.state.activeContainerComponent,
-    ).screen;
+    const ContainerComponent = this.getContainerComponent(this.props.activeId)
+      .screen;
 
     return (
       <LayoutDoubleColumn
@@ -186,3 +178,5 @@ export default class DetailsScreen extends React.Component<Props, State> {
     );
   };
 }
+
+export default withSplitNavigationContext(DetailsScreen);
diff --git a/app/MMB/src/scenes/tripServices/hotelMenuItem/HotelMenuItem.js b/app/MMB/src/scenes/tripServices/hotelMenuItem/HotelMenuItem.js
index 95ffba60..bb0ad054 100644
--- a/app/MMB/src/scenes/tripServices/hotelMenuItem/HotelMenuItem.js
+++ b/app/MMB/src/scenes/tripServices/hotelMenuItem/HotelMenuItem.js
@@ -88,7 +88,7 @@ export class HotelMenuItem extends React.Component<PropsWithContext, State> {
       checkin: sanitizedDates.checkin,
       checkout: sanitizedDates.checkout,
       onNavigationStateChange: () => {}, // TODO
-      onBackClicked: this.goBack, // TODO: Needs tuning on tablet
+      onBackClicked: this.goBack,
       dimensions: this.props.dimensions,
       version: this.props.version,
       cityName,
diff --git a/app/hotels/src/map/allHotels/NewAllHotelsMap.js b/app/hotels/src/map/allHotels/NewAllHotelsMap.js
index ba145c6b..7d306233 100644
--- a/app/hotels/src/map/allHotels/NewAllHotelsMap.js
+++ b/app/hotels/src/map/allHotels/NewAllHotelsMap.js
@@ -5,7 +5,10 @@ import { View } from 'react-native';
 import { graphql, PublicApiRenderer } from '@kiwicom/mobile-relay';
 import { DateFormatter } from '@kiwicom/mobile-localization';
 import { StyleSheet, AdaptableLayout } from '@kiwicom/mobile-shared';
-import type { NavigationType } from '@kiwicom/mobile-navigation';
+import {
+  type NavigationType,
+  withNavigation,
+} from '@kiwicom/mobile-navigation';
 
 import FilterStripe from '../../filter/FilterStripe';
 import {
@@ -107,4 +110,4 @@ const styles = StyleSheet.create({
   },
 });
 
-export default withHotelsContext(NewAllHotelsMap);
+export default withHotelsContext(withNavigation(NewAllHotelsMap));
diff --git a/app/hotels/src/navigation/NewNavigationStack.js b/app/hotels/src/navigation/NewNavigationStack.js
index 651ef086..0f68e569 100644
--- a/app/hotels/src/navigation/NewNavigationStack.js
+++ b/app/hotels/src/navigation/NewNavigationStack.js
@@ -14,7 +14,7 @@ import { Dimensions, Device } from '@kiwicom/mobile-shared';
 
 import SingleHotel from './singleHotel/SingleHotelNavigationScreen';
 import SearchResults from './allHotels/SearchResultsScreen';
-import AllHotelsMap from './allHotelsMap/AllHotelsMapNavigationScreen';
+import NewAllHotelsMap from './allHotelsMap/NewAllHotelsMapNavigationScreen';
 import AdditionalPropsInjector from './singleHotel/AdditionalPropsInjector';
 import SingleHotelMapNavigationScreen from './singleHotel/SingleHotelMapNavigationScreen';
 import Payment from '../singleHotel/PaymentScreen';
@@ -50,7 +50,7 @@ export default StackNavigator(
       },
     },
     AllHotelsMap: {
-      screen: withMappedProps(AllHotelsMap),
+      screen: withMappedProps(NewAllHotelsMap),
       navigationOptions: {
         headerTitle: (
           <HeaderTitle>
diff --git a/app/hotels/src/navigation/allHotelsMap/NewAllHotelsMapNavigationScreen.js b/app/hotels/src/navigation/allHotelsMap/NewAllHotelsMapNavigationScreen.js
new file mode 100644
index 00000000..00d1f6ad
--- /dev/null
+++ b/app/hotels/src/navigation/allHotelsMap/NewAllHotelsMapNavigationScreen.js
@@ -0,0 +1,32 @@
+// @flow strict
+
+import * as React from 'react';
+import { HeaderBackButton } from 'react-navigation';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+import { type NavigationType } from '@kiwicom/mobile-navigation';
+
+import NewAllHotelsMap from '../../map/allHotels/NewAllHotelsMap';
+
+type Props = {|
+  +navigation: NavigationType,
+|};
+
+export default class NewAllHotelsMapNavigationScreen extends React.Component<
+  Props,
+> {
+  static navigationOptions = ({ navigation }: Props) => {
+    function goBack() {
+      navigation.goBack(null);
+    }
+    return {
+      headerLeft: (
+        <HeaderBackButton
+          tintColor={defaultTokens.paletteProductNormal}
+          onPress={goBack}
+        />
+      ),
+    };
+  };
+
+  render = () => <NewAllHotelsMap />;
+}


Test patch:
diff --git a/app/MMB/src/context/__tests__/SplitNavigationContext.test.js b/app/MMB/src/context/__tests__/SplitNavigationContext.test.js
new file mode 100644
index 00000000..c602ad56
--- /dev/null
+++ b/app/MMB/src/context/__tests__/SplitNavigationContext.test.js
@@ -0,0 +1,34 @@
+// @flow
+
+import * as React from 'react';
+import renderer from 'react-test-renderer';
+
+import SplitNavigationContext from '../SplitNavigationContext';
+
+class ContextConsumer extends React.Component<{}> {
+  render = () => null;
+}
+
+const getWrapper = () =>
+  renderer.create(
+    <SplitNavigationContext.Provider>
+      <SplitNavigationContext.Consumer>
+        {({ actions: { setActiveId }, activeId }) => (
+          <ContextConsumer activeId={activeId} setActiveId={setActiveId} />
+        )}
+      </SplitNavigationContext.Consumer>
+    </SplitNavigationContext.Provider>,
+  );
+
+describe('SplitNavigationContext', () => {
+  it('sets active id', () => {
+    const wrapper = getWrapper();
+    const instance = wrapper.root.findByType(ContextConsumer);
+    const newActiveId = 'active-lol';
+
+    expect(instance.props.activeId).not.toEqual(newActiveId);
+
+    instance.props.setActiveId(newActiveId);
+    expect(instance.props.activeId).toEqual(newActiveId);
+  });
+});