Repository: kiwicom/mobile
Pull request #1113 (2018-09-20 09:13:10)

Hotels: Show hotel detail screen on split view tablet

tbergq: @RobinCsl 🚀 


Patch:
diff --git a/app/hotels/src/allHotels/AllHotelsSearchRow.js b/app/hotels/src/allHotels/AllHotelsSearchRow.js
index 042372b0..30971a4d 100644
--- a/app/hotels/src/allHotels/AllHotelsSearchRow.js
+++ b/app/hotels/src/allHotels/AllHotelsSearchRow.js
@@ -4,19 +4,25 @@ import * as React from 'react';
 import idx from 'idx';
 import { View } from 'react-native';
 import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
-import { NetworkImage, StyleSheet, Touchable } from '@kiwicom/mobile-shared';
+import {
+  NetworkImage,
+  StyleSheet,
+  Touchable,
+  AdaptableLayout,
+} from '@kiwicom/mobile-shared';
 import { defaultTokens } from '@kiwicom/mobile-orbit';
 
 import HotelTitle from './HotelTitle';
 import HotelReviewScore from './HotelReviewScore';
 import type { AllHotelsSearchRow as AllHotelsSearchRowProps } from './__generated__/AllHotelsSearchRow.graphql';
+import SingleHotelContext from '../navigation/singleHotel/SingleHotelContext';
 
-type Props = {|
-  +openSingleHotel: (id: string) => void,
-  +data: AllHotelsSearchRowProps,
+type PropsWithContext = {|
+  ...Props,
+  +setHotelId: (hotelId: string) => void,
 |};
 
-class AllHotelsSearchRow extends React.Component<Props> {
+class AllHotelsSearchRow extends React.Component<PropsWithContext> {
   onGoToSingleHotel = () => {
     const hotelId = idx(this.props, _ => _.data.hotel.id);
     if (hotelId) {
@@ -24,41 +30,74 @@ class AllHotelsSearchRow extends React.Component<Props> {
     }
   };
 
+  setActiveHotelId = () => {
+    const hotelId = idx(this.props, _ => _.data.hotel.id) || '';
+    this.props.setHotelId(hotelId);
+  };
+
   render = () => {
     const lowResUrl = idx(this.props.data, _ => _.hotel.mainPhoto.lowResUrl);
-
-    return (
-      <Touchable
-        onPress={this.onGoToSingleHotel}
-        style={style.container}
-        delayPressIn={100}
-      >
-        <View style={style.row}>
-          <View style={style.imageContainer}>
-            <NetworkImage
-              style={style.image}
-              resizeMode="cover"
-              source={{ uri: lowResUrl }}
-            />
-          </View>
-          <View style={style.content}>
-            <View style={style.hotelTitleWrapper}>
-              <View style={style.hotelTitle}>
-                <HotelTitle data={this.props.data} />
-              </View>
-              <View style={style.hotelReviewScore}>
-                <HotelReviewScore hotel={idx(this.props.data, _ => _.hotel)} />
-              </View>
+    const children = (
+      <View style={style.row}>
+        <View style={style.imageContainer}>
+          <NetworkImage
+            style={style.image}
+            resizeMode="cover"
+            source={{ uri: lowResUrl }}
+          />
+        </View>
+        <View style={style.content}>
+          <View style={style.hotelTitleWrapper}>
+            <View style={style.hotelTitle}>
+              <HotelTitle data={this.props.data} />
+            </View>
+            <View style={style.hotelReviewScore}>
+              <HotelReviewScore hotel={idx(this.props.data, _ => _.hotel)} />
             </View>
           </View>
         </View>
-      </Touchable>
+      </View>
+    );
+    return (
+      <AdaptableLayout
+        renderOnNarrow={
+          <Touchable
+            onPress={this.onGoToSingleHotel}
+            style={style.container}
+            delayPressIn={100}
+          >
+            {children}
+          </Touchable>
+        }
+        renderOnWide={
+          <Touchable
+            onPress={this.setActiveHotelId}
+            style={style.container}
+            delayPressIn={100}
+          >
+            {children}
+          </Touchable>
+        }
+      />
     );
   };
 }
 
+type Props = {|
+  +openSingleHotel: (id: string) => void,
+  +data: AllHotelsSearchRowProps,
+|};
+
+const AllHotelsSearchRowWithContext = (props: Props) => (
+  <SingleHotelContext.Consumer>
+    {({ setHotelId }) => (
+      <AllHotelsSearchRow {...props} setHotelId={setHotelId} />
+    )}
+  </SingleHotelContext.Consumer>
+);
+
 export default createFragmentContainer(
-  AllHotelsSearchRow,
+  AllHotelsSearchRowWithContext,
   graphql`
     fragment AllHotelsSearchRow on HotelAvailability {
       ...HotelTitle
diff --git a/app/hotels/src/navigation/allHotels/SearchResultsScreen.js b/app/hotels/src/navigation/allHotels/SearchResultsScreen.js
index 8cc6abe5..c6eb6501 100644
--- a/app/hotels/src/navigation/allHotels/SearchResultsScreen.js
+++ b/app/hotels/src/navigation/allHotels/SearchResultsScreen.js
@@ -15,13 +15,15 @@ import { defaultTokens } from '@kiwicom/mobile-orbit';
 import { SafeAreaView } from 'react-navigation';
 
 import NewAllHotels from '../../allHotels/NewAllHotels';
-import NewAllHotelsMap from '../../map/allHotels/NewAllHotelsMap';
 import MapHeaderButton from './MapHeaderButton';
 import HotelsNavigationOptions from '../HotelsNavigationOptions';
 import {
   withSearchResultsContext,
   type ResultType,
 } from './SearchResultsContext';
+import SingleHotelContainer from '../../singleHotel/SingleHotelContainer';
+import type { RoomsConfiguration } from '../../singleHotel/AvailableHotelSearchInput';
+import SingleHotelContext from '../singleHotel/SingleHotelContext';
 
 type Props = {|
   +navigation: NavigationType,
@@ -29,11 +31,15 @@ type Props = {|
   +cityName: string,
   +checkin: string,
   +checkout: string,
+  +roomsConfiguration: RoomsConfiguration,
   +lastNavigationMode?: 'present' | 'push',
   +setResultType: (show: ResultType) => void,
   +show: ResultType,
+  +bookingComAffiliate: string,
 |};
 
+const noop = () => {};
+
 class SearchResultsScreen extends React.Component<Props> {
   static navigationOptions = ({
     checkin,
@@ -95,19 +101,27 @@ class SearchResultsScreen extends React.Component<Props> {
   };
 
   render = () => (
-    <LayoutDoubleColumn
-      menuComponent={
-        <SafeAreaView style={[styles.container, styles.safeArea]}>
-          <View style={styles.container}>
-            <NewAllHotels />
-            <View style={styles.button}>
-              <CloseButton onPress={this.onClosePress} />
+    <SingleHotelContext.Provider
+      hotelId={''}
+      checkin={new Date(this.props.checkin)}
+      checkout={new Date(this.props.checkout)}
+      roomsConfiguration={this.props.roomsConfiguration}
+      bookingComAffiliate={this.props.bookingComAffiliate}
+    >
+      <LayoutDoubleColumn
+        menuComponent={
+          <SafeAreaView style={[styles.container, styles.safeArea]}>
+            <View style={styles.container}>
+              <NewAllHotels />
+              <View style={styles.button}>
+                <CloseButton onPress={this.onClosePress} />
+              </View>
             </View>
-          </View>
-        </SafeAreaView>
-      }
-      containerComponent={<NewAllHotelsMap />}
-    />
+          </SafeAreaView>
+        }
+        containerComponent={<SingleHotelContainer goBack={noop} />}
+      />
+    </SingleHotelContext.Provider>
   );
 }
 
diff --git a/app/hotels/src/navigation/singleHotel/SingleHotelContext.js b/app/hotels/src/navigation/singleHotel/SingleHotelContext.js
index e510bfb4..a0a87a65 100644
--- a/app/hotels/src/navigation/singleHotel/SingleHotelContext.js
+++ b/app/hotels/src/navigation/singleHotel/SingleHotelContext.js
@@ -10,6 +10,7 @@ const defaultState = {
   checkout: new Date(),
   roomsConfiguration: [],
   bookingComAffiliate: '',
+  setHotelId: () => {},
 };
 
 const { Consumer, Provider: ContextProvider } = React.createContext({
@@ -26,11 +27,12 @@ type Props = {|
 |};
 
 type State = {|
-  +hotelId: string,
+  hotelId: string,
   +checkin: Date,
   +checkout: Date,
   +roomsConfiguration: $ReadOnlyArray<RoomConfigurationType>,
   +bookingComAffiliate: string,
+  +setHotelId: (hotelId: string) => void,
 |};
 
 class Provider extends React.Component<Props, State> {
@@ -43,9 +45,14 @@ class Provider extends React.Component<Props, State> {
       checkout: props.checkout,
       roomsConfiguration: props.roomsConfiguration,
       bookingComAffiliate: props.bookingComAffiliate,
+      setHotelId: this.setHotelId,
     };
   }
 
+  setHotelId = (hotelId: string) => {
+    this.setState({ hotelId });
+  };
+
   render = () => (
     <ContextProvider value={this.state}>{this.props.children}</ContextProvider>
   );
diff --git a/app/hotels/src/singleHotel/HotelDetailScreen.js b/app/hotels/src/singleHotel/HotelDetailScreen.js
index ea44d28a..599b0764 100644
--- a/app/hotels/src/singleHotel/HotelDetailScreen.js
+++ b/app/hotels/src/singleHotel/HotelDetailScreen.js
@@ -6,9 +6,11 @@ import {
   GeneralError,
   LayoutSingleColumn,
   Logger,
-  AdaptableLayout,
   StyleSheet,
   CloseButton,
+  AdaptableLayout,
+  Dimensions,
+  Device,
 } from '@kiwicom/mobile-shared';
 import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
 import { Translation } from '@kiwicom/mobile-localization';
@@ -27,10 +29,9 @@ import type { RoomsConfiguration } from '../singleHotel/AvailableHotelSearchInpu
 import type { HotelDetailScreen_availableHotel } from './__generated__/HotelDetailScreen_availableHotel.graphql';
 import countBookingPrice from './bookNow/countBookingPrice';
 
-type Props = {|
-  +availableHotel: HotelDetailScreen_availableHotel,
-  +roomsConfiguration: RoomsConfiguration,
-  +goBack: () => void,
+type PropsWithContext = {|
+  ...Props,
+  +width: number,
 |};
 
 type State = {|
@@ -48,7 +49,10 @@ type NativeEvent = {|
   |},
 |};
 
-export class HotelDetailScreen extends React.Component<Props, State> {
+export class HotelDetailScreen extends React.Component<
+  PropsWithContext,
+  State,
+> {
   state = {
     selected: {},
     maxPersons: 0,
@@ -56,12 +60,12 @@ export class HotelDetailScreen extends React.Component<Props, State> {
 
   componentDidMount = () => {
     Logger.ancillaryDisplayed(Logger.Type.ANCILLARY_STEP_DETAILS);
-    if (Platform.OS === 'ios') {
+    if (Platform.OS === 'ios' && this.isNarrowLayout()) {
       StatusBar.setBarStyle('light-content');
     }
   };
 
-  shouldComponentUpdate = (nextProps: Props, nextState: State) => {
+  shouldComponentUpdate = (nextProps: PropsWithContext, nextState: State) => {
     const isPropsEqual = isEqual(nextProps, this.props);
     const isStateEqual = isEqual(nextState, this.state);
 
@@ -69,15 +73,22 @@ export class HotelDetailScreen extends React.Component<Props, State> {
   };
 
   componentWillUnmount = () => {
-    if (Platform.OS === 'ios') {
+    if (Platform.OS === 'ios' && this.isNarrowLayout()) {
       StatusBar.setBarStyle('dark-content');
     }
   };
 
+  isNarrowLayout = () => {
+    return this.props.width < Device.DEVICE_THRESHOLD;
+  };
+
   onScroll = (e: NativeEvent) => {
-    if (Platform.OS === 'ios' && e.nativeEvent.contentOffset.y > 180) {
+    const isIOS = Platform.OS === 'ios';
+    const isNarrow = this.isNarrowLayout();
+    const hasScrolledPastImage = e.nativeEvent.contentOffset.y > 180;
+    if (isIOS && isNarrow && hasScrolledPastImage) {
       StatusBar.setBarStyle('dark-content');
-    } else {
+    } else if (isIOS && isNarrow && !hasScrolledPastImage) {
       StatusBar.setBarStyle('light-content');
     }
   };
@@ -153,9 +164,6 @@ export class HotelDetailScreen extends React.Component<Props, State> {
             scrollEventThrottle={500}
           >
             <LayoutSingleColumn>
-              <AdaptableLayout
-                renderOnWide={<View style={styles.marginView} />}
-              />
               <Header hotel={availableHotel.hotel} />
               <HotelInformation hotel={availableHotel.hotel} />
               <RoomList
@@ -174,9 +182,13 @@ export class HotelDetailScreen extends React.Component<Props, State> {
               price={price}
             />
             <View style={styles.row}>
-              <View style={styles.closeWrapper}>
-                <CloseButton onPress={this.props.goBack} />
-              </View>
+              <AdaptableLayout
+                renderOnNarrow={
+                  <View style={styles.closeWrapper}>
+                    <CloseButton onPress={this.props.goBack} />
+                  </View>
+                }
+              />
               {this.state.maxPersons > 0 && (
                 <View style={styles.bookNowWrapper}>
                   <BookNow selected={selected} hotel={availableHotel.hotel} />
@@ -190,8 +202,20 @@ export class HotelDetailScreen extends React.Component<Props, State> {
   }
 }
 
+type Props = {|
+  +availableHotel: HotelDetailScreen_availableHotel,
+  +roomsConfiguration: RoomsConfiguration,
+  +goBack: () => void,
+|};
+
+const HotelDetailScreenWithContext = (props: Props) => (
+  <Dimensions.Consumer>
+    {({ width }) => <HotelDetailScreen {...props} width={width} />}
+  </Dimensions.Consumer>
+);
+
 export default createFragmentContainer(
-  HotelDetailScreen,
+  HotelDetailScreenWithContext,
   graphql`
     fragment HotelDetailScreen_availableHotel on HotelAvailability {
       hotel {
@@ -212,9 +236,6 @@ export default createFragmentContainer(
 );
 
 const styles = StyleSheet.create({
-  marginView: {
-    marginBottom: 15,
-  },
   container: {
     paddingBottom: 64,
   },
diff --git a/app/hotels/src/singleHotel/SingleHotelContainer.js b/app/hotels/src/singleHotel/SingleHotelContainer.js
index 42b05446..24248d00 100644
--- a/app/hotels/src/singleHotel/SingleHotelContainer.js
+++ b/app/hotels/src/singleHotel/SingleHotelContainer.js
@@ -66,6 +66,9 @@ class SingleHotelContainer extends React.Component<PropsWithContext> {
       roomsConfiguration,
       hotelId,
     } = this.props;
+    if (hotelId === '') {
+      return null;
+    }
     return (
       <PublicApiRenderer
         query={graphql`


Test patch:
diff --git a/app/hotels/src/allHotels/__tests__/__snapshots__/AllHotelsSearchList.test.js.snap b/app/hotels/src/allHotels/__tests__/__snapshots__/AllHotelsSearchList.test.js.snap
index 5a6d860c..36294378 100644
--- a/app/hotels/src/allHotels/__tests__/__snapshots__/AllHotelsSearchList.test.js.snap
+++ b/app/hotels/src/allHotels/__tests__/__snapshots__/AllHotelsSearchList.test.js.snap
@@ -28,7 +28,7 @@ exports[`renders found hotels 1`] = `
     }
   }
 >
-  <ForwardRef(Relay(AllHotelsSearchRow))
+  <ForwardRef(Relay(AllHotelsSearchRowWithContext))
     data={
       Object {
         "id": "hotel1",
@@ -36,7 +36,7 @@ exports[`renders found hotels 1`] = `
     }
     openSingleHotel={[Function]}
   />
-  <ForwardRef(Relay(AllHotelsSearchRow))
+  <ForwardRef(Relay(AllHotelsSearchRowWithContext))
     data={
       Object {
         "id": "hotel2",