Repository: kiwicom/mobile
Pull request #1237 (2018-10-19 13:06:40)

Add: Hotels map available on tablet
Small refactor to avoid 5-level prop-drilling

/!\ We need to agree on a Hotels Context /!\


RobinCsl: Fixes #1229 

Patch:
diff --git a/app/hotels/src/allHotels/AllHotelsSearchList.js b/app/hotels/src/allHotels/AllHotelsSearchList.js
index 0a6ebeb6..505b6164 100644
--- a/app/hotels/src/allHotels/AllHotelsSearchList.js
+++ b/app/hotels/src/allHotels/AllHotelsSearchList.js
@@ -8,45 +8,66 @@ import idx from 'idx';
 
 import AllHotelsSearchRow from './AllHotelsSearchRow';
 import type { AllHotelsSearchList as AllHotelsSearchListProps } from './__generated__/AllHotelsSearchList.graphql';
+import SingleHotelContext from '../navigation/singleHotel/SingleHotelContext';
 
-type Props = {|
-  +openSingleHotel: (id: string) => void,
-  +data: AllHotelsSearchListProps,
+type PropsWithContext = {|
+  ...Props,
+  setHotelId: (id: string) => void,
 |};
 
-export const AllHotelsSearchList = (props: Props) => {
-  const hotels = props.data || [];
+export class AllHotelsSearchList extends React.Component<PropsWithContext> {
+  componentDidMount = () => {
+    const hotelId = idx(this.props.data, _ => _[0].hotelId);
+    if (hotelId != null) {
+      this.props.setHotelId(hotelId);
+    }
+  };
+  render = () => {
+    const hotels = this.props.data || [];
+
+    if (hotels.length === 0) {
+      return (
+        <GeneralError
+          errorMessage={
+            <Translation id="hotels_search.all_hotels_search_list.no_hotels_found" />
+          }
+        />
+      );
+    }
 
-  if (hotels.length === 0) {
     return (
-      <GeneralError
-        errorMessage={
-          <Translation id="hotels_search.all_hotels_search_list.no_hotels_found" />
-        }
-      />
+      <React.Fragment>
+        {hotels.map((hotel, index) => (
+          <AllHotelsSearchRow
+            key={idx(hotel, _ => _.id)}
+            data={hotel}
+            testID={index === 0 ? 'firstHotelResult' : ''}
+          />
+        ))}
+      </React.Fragment>
     );
-  }
-
-  return (
-    <React.Fragment>
-      {hotels.map((hotel, index) => (
-        <AllHotelsSearchRow
-          key={idx(hotel, _ => _.id)}
-          data={hotel}
-          openSingleHotel={props.openSingleHotel}
-          testID={index === 0 ? 'firstHotelResult' : ''}
-        />
-      ))}
-    </React.Fragment>
-  );
-};
+  };
+}
+
+type Props = {|
+  +data: AllHotelsSearchListProps,
+|};
+
+const AllHotelsSearchListWithContext = (props: Props) => (
+  <SingleHotelContext.Consumer>
+    {({ setHotelId }) => (
+      <AllHotelsSearchList {...props} setHotelId={setHotelId} />
+    )}
+  </SingleHotelContext.Consumer>
+);
 
 export default createFragmentContainer(
-  AllHotelsSearchList,
+  AllHotelsSearchListWithContext,
   graphql`
     fragment AllHotelsSearchList on AllHotelsInterface @relay(plural: true) {
       id
       ...AllHotelsSearchRow
+      hotelId
     }
   `,
 );
diff --git a/app/hotels/src/allHotels/AllHotelsSearchRow.js b/app/hotels/src/allHotels/AllHotelsSearchRow.js
index 6946d173..669c3bfc 100644
--- a/app/hotels/src/allHotels/AllHotelsSearchRow.js
+++ b/app/hotels/src/allHotels/AllHotelsSearchRow.js
@@ -11,7 +11,12 @@ import {
   AdaptableLayout,
 } from '@kiwicom/mobile-shared';
 import { defaultTokens } from '@kiwicom/mobile-orbit';
+import {
+  withNavigation,
+  type NavigationType,
+} from '@kiwicom/mobile-navigation';
 
+import { type RoomConfigurationType } from '../HotelsContext';
 import HotelTitle from './HotelTitle';
 import HotelReviewScore from '../components/HotelReviewScore';
 import type { AllHotelsSearchRow as AllHotelsSearchRowProps } from './__generated__/AllHotelsSearchRow.graphql';
@@ -20,13 +25,22 @@ import SingleHotelContext from '../navigation/singleHotel/SingleHotelContext';
 type PropsWithContext = {|
   ...Props,
   +setHotelId: (hotelId: string) => void,
+  +hotelId: string,
+  +checkin: Date,
+  +checkout: Date,
+  +roomsConfiguration: $ReadOnlyArray<RoomConfigurationType>,
 |};
 
 class AllHotelsSearchRow extends React.Component<PropsWithContext> {
   onGoToSingleHotel = () => {
     const hotelId = idx(this.props, _ => _.data.hotelId);
     if (hotelId != null) {
-      this.props.openSingleHotel(hotelId);
+      this.props.navigation.navigate('SingleHotel', {
+        hotelId,
+        checkin: this.props.checkin,
+        checkout: this.props.checkout,
+        roomsConfiguration: this.props.roomsConfiguration,
+      });
     }
   };
 
@@ -88,21 +102,19 @@ class AllHotelsSearchRow extends React.Component<PropsWithContext> {
 }
 
 type Props = {|
-  +openSingleHotel: (id: string) => void,
+  +navigation: NavigationType,
   +data: AllHotelsSearchRowProps,
   +testID?: string,
 |};
 
 const AllHotelsSearchRowWithContext = (props: Props) => (
   <SingleHotelContext.Consumer>
-    {({ setHotelId }) => (
-      <AllHotelsSearchRow {...props} setHotelId={setHotelId} />
-    )}
+    {context => <AllHotelsSearchRow {...props} {...context} />}
   </SingleHotelContext.Consumer>
 );
 
 export default createFragmentContainer(
-  AllHotelsSearchRowWithContext,
+  withNavigation(AllHotelsSearchRowWithContext),
   graphql`
     fragment AllHotelsSearchRow on AllHotelsInterface {
       ...HotelTitle
diff --git a/app/hotels/src/allHotels/NewAllHotelsSearch.js b/app/hotels/src/allHotels/NewAllHotelsSearch.js
index ef192923..d613aaa0 100644
--- a/app/hotels/src/allHotels/NewAllHotelsSearch.js
+++ b/app/hotels/src/allHotels/NewAllHotelsSearch.js
@@ -3,10 +3,6 @@
 import * as React from 'react';
 import { graphql, PublicApiRenderer } from '@kiwicom/mobile-relay';
 import { DateFormatter, Translation } from '@kiwicom/mobile-localization';
-import {
-  withNavigation,
-  type NavigationType,
-} from '@kiwicom/mobile-navigation';
 import { GeneralError } from '@kiwicom/mobile-shared';
 
 import HotelsFilterContext from '../HotelsFilterContext';
@@ -19,7 +15,6 @@ import type { NewAllHotelsSearchQueryResponse } from './__generated__/NewAllHote
 import RenderSearchResults from './RenderSearchResults';
 
 type Props = {|
-  +navigation: NavigationType,
   +checkin: Date | null,
   +checkout: Date | null,
   +currency: string,
@@ -28,24 +23,10 @@ type Props = {|
 |};
 
 class NewAllHotelsSearch extends React.Component<Props> {
-  openSingleHotel = (hotelId: string) => {
-    this.props.navigation.navigate('SingleHotel', {
-      hotelId,
-      checkin: this.props.checkin,
-      checkout: this.props.checkout,
-      roomsConfiguration: this.props.roomsConfiguration,
-    });
-  };
-
   renderAllHotelsSearchList = (
     propsFromRenderer: NewAllHotelsSearchQueryResponse,
   ) => {
-    return (
-      <RenderSearchResults
-        data={propsFromRenderer}
-        openSingleHotel={this.openSingleHotel}
-      />
-    );
+    return <RenderSearchResults data={propsFromRenderer} />;
   };
 
   render = () => {
@@ -107,12 +88,10 @@ class NewAllHotelsSearch extends React.Component<Props> {
   };
 }
 
-export default withNavigation(
-  withHotelsContext(state => ({
-    checkin: state.checkin,
-    checkout: state.checkout,
-    currency: state.currency,
-    roomsConfiguration: state.roomsConfiguration,
-    cityId: state.cityId,
-  }))(NewAllHotelsSearch),
-);
+export default withHotelsContext(state => ({
+  checkin: state.checkin,
+  checkout: state.checkout,
+  currency: state.currency,
+  roomsConfiguration: state.roomsConfiguration,
+  cityId: state.cityId,
+}))(NewAllHotelsSearch);
diff --git a/app/hotels/src/allHotels/RenderSearchResults.js b/app/hotels/src/allHotels/RenderSearchResults.js
index 112d6735..0fbceaca 100644
--- a/app/hotels/src/allHotels/RenderSearchResults.js
+++ b/app/hotels/src/allHotels/RenderSearchResults.js
@@ -7,12 +7,7 @@ import {
   createPaginationContainer,
   type RelayPaginationProp,
 } from '@kiwicom/mobile-relay';
-import {
-  StyleSheet,
-  AdaptableLayout,
-  Logger,
-  Device,
-} from '@kiwicom/mobile-shared';
+import { StyleSheet, Logger, Device } from '@kiwicom/mobile-shared';
 import idx from 'idx';
 
 import MapScreen from '../map/allHotels/MapScreen';
@@ -143,10 +138,7 @@ export class RenderSearchResults extends React.Component<
         >
           {/*  Note: it's not possible to use FlatList here because it's wrapped with ScrollView and it causes performance issues.*/}
           <ScrollView contentContainerStyle={styles.content}>
-            <AllHotelsSearchList
-              data={data}
-              openSingleHotel={this.props.openSingleHotel}
-            />
+            <AllHotelsSearchList data={data} />
             {this.props.relay.hasMore() && (
               <LoadMoreButton
                 isLoading={this.state.isLoading}
@@ -155,22 +147,15 @@ export class RenderSearchResults extends React.Component<
             )}
           </ScrollView>
         </Animated.View>
-        <AdaptableLayout
-          renderOnNarrow={
-            <Animated.View
-              style={{
-                ...StyleSheet.absoluteFillObject,
-                top: 56,
-                transform: [{ translateY: this.mapAnimation }],
-              }}
-            >
-              <MapScreen
-                data={data}
-                onOpenSingleHotel={this.props.openSingleHotel}
-              />
-            </Animated.View>
-          }
-        />
+        <Animated.View
+          style={{
+            ...StyleSheet.absoluteFillObject,
+            top: 56,
+            transform: [{ translateY: this.mapAnimation }],
+          }}
+        >
+          <MapScreen data={data} />
+        </Animated.View>
       </React.Fragment>
     );
   };
@@ -178,7 +163,6 @@ export class RenderSearchResults extends React.Component<
 
 type Props = {|
   +data: RenderResultsType,
-  +openSingleHotel: () => void,
   +relay: RelayPaginationProp,
 |};
 
diff --git a/app/hotels/src/allHotels/__generated__/AllHotelsSearchList.graphql.js b/app/hotels/src/allHotels/__generated__/AllHotelsSearchList.graphql.js
index 36e91eed..61d1ccf5 100644
--- a/app/hotels/src/allHotels/__generated__/AllHotelsSearchList.graphql.js
+++ b/app/hotels/src/allHotels/__generated__/AllHotelsSearchList.graphql.js
@@ -13,6 +13,7 @@ import type { FragmentReference } from "relay-runtime";
 declare export opaque type AllHotelsSearchList$ref: FragmentReference;
 export type AllHotelsSearchList = $ReadOnlyArray<{|
   +id: string,
+  +hotelId: ?string,
   +$fragmentRefs: AllHotelsSearchRow$ref,
   +$refType: AllHotelsSearchList$ref,
 |}>;
@@ -39,9 +40,16 @@ const node/*: ConcreteFragment*/ = {
       "kind": "FragmentSpread",
       "name": "AllHotelsSearchRow",
       "args": null
+    },
+    {
+      "kind": "ScalarField",
+      "alias": null,
+      "name": "hotelId",
+      "args": null,
+      "storageKey": null
     }
   ]
 };
 // prettier-ignore
-(node/*: any*/).hash = '3eee76d3f577bf07e2be1c15ad778705';
+(node/*: any*/).hash = 'a5c6871335e7fdaa11bfe7a769eb8a88';
 module.exports = node;
diff --git a/app/hotels/src/allHotels/__generated__/NewAllHotelsSearchQuery.graphql.js b/app/hotels/src/allHotels/__generated__/NewAllHotelsSearchQuery.graphql.js
index 296aa5ac..c559c673 100644
--- a/app/hotels/src/allHotels/__generated__/NewAllHotelsSearchQuery.graphql.js
+++ b/app/hotels/src/allHotels/__generated__/NewAllHotelsSearchQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash da3f8d13267066dc2f42252bb59bff1f
+ * @relayHash 8838e45c72ad7302c0eedddb91e39723
  */
 
 /* eslint-disable */
@@ -109,10 +109,12 @@ fragment RenderSearchResults on RootQuery {
 fragment AllHotelsSearchList on AllHotelsInterface {
   id
   ...AllHotelsSearchRow
+  hotelId
 }
 
 fragment MapScreen on AllHotelsInterface {
   id
+  hotelId
   ...MapView
   ...HotelSwipeList
 }
@@ -272,7 +274,7 @@ return {
   "operationKind": "query",
   "name": "NewAllHotelsSearchQuery",
   "id": null,
-  "text": "query NewAllHotelsSearchQuery(\n  $search: HotelsSearchInput!\n  $filter: HotelsFilterInput!\n  $options: AvailableHotelOptionsInput\n  $first: Int\n  $after: String\n) {\n  ...RenderSearchResults\n}\n\nfragment RenderSearchResults on RootQuery {\n  allAvailableBookingComHotels(search: $search, filter: $filter, options: $options, first: $first, after: $after) {\n    pageInfo {\n      hasNextPage\n      hasPreviousPage\n      startCursor\n      endCursor\n    }\n    stats {\n      maxPrice\n      minPrice\n    }\n    edges {\n      node {\n        ...AllHotelsSearchList\n        ...MapScreen\n        id\n        __typename\n      }\n      cursor\n    }\n  }\n}\n\nfragment AllHotelsSearchList on AllHotelsInterface {\n  id\n  ...AllHotelsSearchRow\n}\n\nfragment MapScreen on AllHotelsInterface {\n  id\n  ...MapView\n  ...HotelSwipeList\n}\n\nfragment MapView on AllHotelsInterface {\n  id\n  price {\n    ...PriceMarker\n  }\n  coordinates {\n    lat\n    lng\n  }\n}\n\nfragment HotelSwipeList on AllHotelsInterface {\n  id\n  ...HotelSwipeItem\n  address {\n    ...Address_address\n  }\n}\n\nfragment HotelSwipeItem on AllHotelAvailabilityHotel {\n  hotelId\n  name\n  price {\n    currency\n    amount\n  }\n  mainPhoto {\n    thumbnailUrl\n    id\n  }\n  rating {\n    stars\n  }\n  review {\n    score\n  }\n}\n\nfragment Address_address on Address {\n  street\n  city\n  zip\n}\n\nfragment PriceMarker on Price {\n  amount\n  currency\n}\n\nfragment AllHotelsSearchRow on AllHotelsInterface {\n  ...HotelTitle\n  hotelId\n  mainPhoto {\n    lowResUrl\n    id\n  }\n  review {\n    score\n  }\n}\n\nfragment HotelTitle on AllHotelAvailabilityHotel {\n  price {\n    amount\n    currency\n  }\n  ...HotelDistance_hotel\n  name\n  rating {\n    stars\n  }\n}\n\nfragment HotelDistance_hotel on AllHotelAvailabilityHotel {\n  distanceFromCenter\n}\n",
+  "text": "query NewAllHotelsSearchQuery(\n  $search: HotelsSearchInput!\n  $filter: HotelsFilterInput!\n  $options: AvailableHotelOptionsInput\n  $first: Int\n  $after: String\n) {\n  ...RenderSearchResults\n}\n\nfragment RenderSearchResults on RootQuery {\n  allAvailableBookingComHotels(search: $search, filter: $filter, options: $options, first: $first, after: $after) {\n    pageInfo {\n      hasNextPage\n      hasPreviousPage\n      startCursor\n      endCursor\n    }\n    stats {\n      maxPrice\n      minPrice\n    }\n    edges {\n      node {\n        ...AllHotelsSearchList\n        ...MapScreen\n        id\n        __typename\n      }\n      cursor\n    }\n  }\n}\n\nfragment AllHotelsSearchList on AllHotelsInterface {\n  id\n  ...AllHotelsSearchRow\n  hotelId\n}\n\nfragment MapScreen on AllHotelsInterface {\n  id\n  hotelId\n  ...MapView\n  ...HotelSwipeList\n}\n\nfragment MapView on AllHotelsInterface {\n  id\n  price {\n    ...PriceMarker\n  }\n  coordinates {\n    lat\n    lng\n  }\n}\n\nfragment HotelSwipeList on AllHotelsInterface {\n  id\n  ...HotelSwipeItem\n  address {\n    ...Address_address\n  }\n}\n\nfragment HotelSwipeItem on AllHotelAvailabilityHotel {\n  hotelId\n  name\n  price {\n    currency\n    amount\n  }\n  mainPhoto {\n    thumbnailUrl\n    id\n  }\n  rating {\n    stars\n  }\n  review {\n    score\n  }\n}\n\nfragment Address_address on Address {\n  street\n  city\n  zip\n}\n\nfragment PriceMarker on Price {\n  amount\n  currency\n}\n\nfragment AllHotelsSearchRow on AllHotelsInterface {\n  ...HotelTitle\n  hotelId\n  mainPhoto {\n    lowResUrl\n    id\n  }\n  review {\n    score\n  }\n}\n\nfragment HotelTitle on AllHotelAvailabilityHotel {\n  price {\n    amount\n    currency\n  }\n  ...HotelDistance_hotel\n  name\n  rating {\n    stars\n  }\n}\n\nfragment HotelDistance_hotel on AllHotelAvailabilityHotel {\n  distanceFromCenter\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
diff --git a/app/hotels/src/allHotels/__generated__/RenderSearchResultsQuery.graphql.js b/app/hotels/src/allHotels/__generated__/RenderSearchResultsQuery.graphql.js
index fa123411..41f1cf42 100644
--- a/app/hotels/src/allHotels/__generated__/RenderSearchResultsQuery.graphql.js
+++ b/app/hotels/src/allHotels/__generated__/RenderSearchResultsQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash 6a08dd47bb75ac984ecb7091b61042bd
+ * @relayHash a1476a79ab6c3d94a6363b74052d141f
  */
 
 /* eslint-disable */
@@ -109,10 +109,12 @@ fragment RenderSearchResults on RootQuery {
 fragment AllHotelsSearchList on AllHotelsInterface {
   id
   ...AllHotelsSearchRow
+  hotelId
 }
 
 fragment MapScreen on AllHotelsInterface {
   id
+  hotelId
   ...MapView
   ...HotelSwipeList
 }
@@ -272,7 +274,7 @@ return {
   "operationKind": "query",
   "name": "RenderSearchResultsQuery",
   "id": null,
-  "text": "query RenderSearchResultsQuery(\n  $search: HotelsSearchInput!\n  $filter: HotelsFilterInput!\n  $options: AvailableHotelOptionsInput\n  $after: String\n  $first: Int\n) {\n  ...RenderSearchResults\n}\n\nfragment RenderSearchResults on RootQuery {\n  allAvailableBookingComHotels(search: $search, filter: $filter, options: $options, first: $first, after: $after) {\n    pageInfo {\n      hasNextPage\n      hasPreviousPage\n      startCursor\n      endCursor\n    }\n    stats {\n      maxPrice\n      minPrice\n    }\n    edges {\n      node {\n        ...AllHotelsSearchList\n        ...MapScreen\n        id\n        __typename\n      }\n      cursor\n    }\n  }\n}\n\nfragment AllHotelsSearchList on AllHotelsInterface {\n  id\n  ...AllHotelsSearchRow\n}\n\nfragment MapScreen on AllHotelsInterface {\n  id\n  ...MapView\n  ...HotelSwipeList\n}\n\nfragment MapView on AllHotelsInterface {\n  id\n  price {\n    ...PriceMarker\n  }\n  coordinates {\n    lat\n    lng\n  }\n}\n\nfragment HotelSwipeList on AllHotelsInterface {\n  id\n  ...HotelSwipeItem\n  address {\n    ...Address_address\n  }\n}\n\nfragment HotelSwipeItem on AllHotelAvailabilityHotel {\n  hotelId\n  name\n  price {\n    currency\n    amount\n  }\n  mainPhoto {\n    thumbnailUrl\n    id\n  }\n  rating {\n    stars\n  }\n  review {\n    score\n  }\n}\n\nfragment Address_address on Address {\n  street\n  city\n  zip\n}\n\nfragment PriceMarker on Price {\n  amount\n  currency\n}\n\nfragment AllHotelsSearchRow on AllHotelsInterface {\n  ...HotelTitle\n  hotelId\n  mainPhoto {\n    lowResUrl\n    id\n  }\n  review {\n    score\n  }\n}\n\nfragment HotelTitle on AllHotelAvailabilityHotel {\n  price {\n    amount\n    currency\n  }\n  ...HotelDistance_hotel\n  name\n  rating {\n    stars\n  }\n}\n\nfragment HotelDistance_hotel on AllHotelAvailabilityHotel {\n  distanceFromCenter\n}\n",
+  "text": "query RenderSearchResultsQuery(\n  $search: HotelsSearchInput!\n  $filter: HotelsFilterInput!\n  $options: AvailableHotelOptionsInput\n  $after: String\n  $first: Int\n) {\n  ...RenderSearchResults\n}\n\nfragment RenderSearchResults on RootQuery {\n  allAvailableBookingComHotels(search: $search, filter: $filter, options: $options, first: $first, after: $after) {\n    pageInfo {\n      hasNextPage\n      hasPreviousPage\n      startCursor\n      endCursor\n    }\n    stats {\n      maxPrice\n      minPrice\n    }\n    edges {\n      node {\n        ...AllHotelsSearchList\n        ...MapScreen\n        id\n        __typename\n      }\n      cursor\n    }\n  }\n}\n\nfragment AllHotelsSearchList on AllHotelsInterface {\n  id\n  ...AllHotelsSearchRow\n  hotelId\n}\n\nfragment MapScreen on AllHotelsInterface {\n  id\n  hotelId\n  ...MapView\n  ...HotelSwipeList\n}\n\nfragment MapView on AllHotelsInterface {\n  id\n  price {\n    ...PriceMarker\n  }\n  coordinates {\n    lat\n    lng\n  }\n}\n\nfragment HotelSwipeList on AllHotelsInterface {\n  id\n  ...HotelSwipeItem\n  address {\n    ...Address_address\n  }\n}\n\nfragment HotelSwipeItem on AllHotelAvailabilityHotel {\n  hotelId\n  name\n  price {\n    currency\n    amount\n  }\n  mainPhoto {\n    thumbnailUrl\n    id\n  }\n  rating {\n    stars\n  }\n  review {\n    score\n  }\n}\n\nfragment Address_address on Address {\n  street\n  city\n  zip\n}\n\nfragment PriceMarker on Price {\n  amount\n  currency\n}\n\nfragment AllHotelsSearchRow on AllHotelsInterface {\n  ...HotelTitle\n  hotelId\n  mainPhoto {\n    lowResUrl\n    id\n  }\n  review {\n    score\n  }\n}\n\nfragment HotelTitle on AllHotelAvailabilityHotel {\n  price {\n    amount\n    currency\n  }\n  ...HotelDistance_hotel\n  name\n  rating {\n    stars\n  }\n}\n\nfragment HotelDistance_hotel on AllHotelAvailabilityHotel {\n  distanceFromCenter\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
diff --git a/app/hotels/src/map/allHotels/HotelSwipeItem.js b/app/hotels/src/map/allHotels/HotelSwipeItem.js
index ccfb5613..0fc92a2d 100644
--- a/app/hotels/src/map/allHotels/HotelSwipeItem.js
+++ b/app/hotels/src/map/allHotels/HotelSwipeItem.js
@@ -1,32 +1,58 @@
-// @flow
+// @flow strict
 
 import * as React from 'react';
 import { View } from 'react-native';
 import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
 import idx from 'idx';
-import { TouchableWithoutFeedback } from '@kiwicom/mobile-shared';
+import {
+  withNavigation,
+  type NavigationType,
+} from '@kiwicom/mobile-navigation';
+import {
+  TouchableWithoutFeedback,
+  Dimensions,
+  Device,
+} from '@kiwicom/mobile-shared';
 
+import { type RoomConfigurationType } from '../../HotelsContext';
+import SingleHotelContext from '../../navigation/singleHotel/SingleHotelContext';
 import HotelDetailPreview from '../hotelDetailPreview/HotelDetailPreview';
 import type { HotelSwipeItem as HotelSwipeItemData } from './__generated__/HotelSwipeItem.graphql';
 
-type ContainerProps = {|
-  width: number,
-  onPress: (hotelId: string) => void,
-  data: $FlowFixMeProps,
+type PropsWithContext = {|
+  ...Props,
+  +deviceWidth: number,
+  +setHotelId: (hotelId: string) => void,
+  +hotelId: string,
+  +checkin: Date,
+  +checkout: Date,
+  +roomsConfiguration: $ReadOnlyArray<RoomConfigurationType>,
 |};
 
-type Props = {
-  ...ContainerProps,
-  data: ?HotelSwipeItemData,
-};
+export class HotelSwipeItemWithContext extends React.Component<
+  PropsWithContext,
+> {
+  openSingleHotel = (hotelId: string) => {
+    this.props.navigation.navigate('SingleHotel', {
+      hotelId,
+      checkin: this.props.checkin,
+      checkout: this.props.checkout,
+      roomsConfiguration: this.props.roomsConfiguration,
+    });
+  };
+
+  isNarrowLayout = () => {
+    return this.props.deviceWidth < Device.DEVICE_THRESHOLD;
+  };
 
-export class HotelSwipeItem extends React.Component<Props> {
   handlePress = () => {
     const { data } = this.props;
     const id = idx(data, _ => _.hotelId);
 
-    if (id) {
-      this.props.onPress(id);
+    if (id != null) {
+      this.isNarrowLayout()
+        ? this.openSingleHotel(id)
+        : this.props.setHotelId(id);
     }
   };
 
@@ -53,8 +79,32 @@ export class HotelSwipeItem extends React.Component<Props> {
   };
 }
 
-export default (createFragmentContainer(
-  HotelSwipeItem,
+type Props = {|
+  +width: number,
+  +navigation: NavigationType,
+  +data: ?HotelSwipeItemData,
+|};
+
+const HotelSwipeItem = (props: Props) => (
+  <Dimensions.Consumer>
+    {({ width }) => (
+      <SingleHotelContext.Consumer>
+        {context => {
+          return (
+            <HotelSwipeItemWithContext
+              {...props}
+              {...context}
+              deviceWidth={width}
+            />
+          );
+        }}
+      </SingleHotelContext.Consumer>
+    )}
+  </Dimensions.Consumer>
+);
+
+export default createFragmentContainer(
+  withNavigation(HotelSwipeItem),
   graphql`
     fragment HotelSwipeItem on AllHotelAvailabilityHotel {
       hotelId
@@ -74,4 +124,4 @@ export default (createFragmentContainer(
       }
     }
   `,
-): React.ComponentType<ContainerProps>);
+);
diff --git a/app/hotels/src/map/allHotels/HotelSwipeList.js b/app/hotels/src/map/allHotels/HotelSwipeList.js
index 5acc4048..41c6c348 100644
--- a/app/hotels/src/map/allHotels/HotelSwipeList.js
+++ b/app/hotels/src/map/allHotels/HotelSwipeList.js
@@ -24,7 +24,6 @@ type Props = {|
   +data: HotelSwipeListData,
   +selectedIndex: number,
   +onSnapToItem: (index: number) => void,
-  +onOpenSingleHotel: (hotelId: string) => void,
 |};
 
 type State = {|
@@ -54,15 +53,7 @@ class HotelSwipeList extends React.Component<Props, State> {
   };
 
   renderItem = ({ item }: { item: Object, index: number }) => {
-    const { onOpenSingleHotel } = this.props;
-
-    return (
-      <HotelSwipeItem
-        width={CARD_ITEM_WIDTH}
-        data={item}
-        onPress={onOpenSingleHotel}
-      />
-    );
+    return <HotelSwipeItem width={CARD_ITEM_WIDTH} data={item} />;
   };
 
   storeRef = (ref: React.ElementRef<typeof Carousel>) => {
diff --git a/app/hotels/src/map/allHotels/MapScreen.js b/app/hotels/src/map/allHotels/MapScreen.js
index 3f0d047c..0b0cd8f8 100644
--- a/app/hotels/src/map/allHotels/MapScreen.js
+++ b/app/hotels/src/map/allHotels/MapScreen.js
@@ -3,35 +3,49 @@
 import * as React from 'react';
 import { View } from 'react-native';
 import { createFragmentContainer, graphql } from '@kiwicom/mobile-relay';
-import { StyleSheet, StretchedImage } from '@kiwicom/mobile-shared';
+import {
+  StyleSheet,
+  StretchedImage,
+  Dimensions,
+  Device,
+} from '@kiwicom/mobile-shared';
 import idx from 'idx';
 
+import SingleHotelContext from '../../navigation/singleHotel/SingleHotelContext';
 import MapView from './MapView';
 import HotelSwipeList from './HotelSwipeList';
 import type { MapScreen as MapScreenData } from './__generated__/MapScreen.graphql';
 import gradient from '../gradient.png';
 
 type Props = {|
-  data: MapScreenData,
-  onOpenSingleHotel: (hotelId: string) => void,
+  +data: MapScreenData,
+|};
+
+type PropsWithContext = {|
+  ...Props,
+  +setHotelId: (hotelId: string) => void,
+  +deviceWidth: number,
 |};
 
 type State = {|
   selectedHotelIndex: number,
 |};
 
-class MapScreen extends React.Component<Props, State> {
+class MapScreenWithContext extends React.Component<PropsWithContext, State> {
   state = {
     selectedHotelIndex: 0,
   };
 
+  isNarrowLayout = () => {
+    return this.props.deviceWidth < Device.DEVICE_THRESHOLD;
+  };
+
   getHotels = () => this.props.data || [];
 
   selectMarker = (selectedHotelIndex: number) => {
     if (this.state.selectedHotelIndex === selectedHotelIndex) {
       return;
     }
-
     this.setState({ selectedHotelIndex });
   };
 
@@ -40,20 +54,29 @@ class MapScreen extends React.Component<Props, State> {
     const index = hotels.findIndex(hotel => idx(hotel, _ => _.id) === hotelId);
 
     if (index !== -1) {
-      this.selectMarker(index);
+      this.handleActiveIndex(index, hotels);
     }
   };
 
-  onSnapToItem = (index: number) => {
+  handleActiveIndex = (index: number, hotels: MapScreenData) => {
+    if (!this.isNarrowLayout()) {
+      const hotelHotelId = idx(hotels[index], _ => _.hotelId);
+      hotelHotelId != null && this.props.setHotelId(hotelHotelId);
+    }
     this.selectMarker(index);
   };
 
+  onSnapToItem = (index: number) => {
+    const hotels = this.getHotels();
+    this.handleActiveIndex(index, hotels);
+  };
+
   render = () => {
     const { selectedHotelIndex } = this.state;
     const hotels = this.getHotels();
 
     return (
-      <View style={styles.container}>
+      <View style={styles.container} testID="allHotels-mapScreen">
         <MapView
           data={hotels}
           selectedIndex={selectedHotelIndex}
@@ -65,7 +88,6 @@ class MapScreen extends React.Component<Props, State> {
         <HotelSwipeList
           data={hotels}
           selectedIndex={selectedHotelIndex}
-          onOpenSingleHotel={this.props.onOpenSingleHotel}
           onSnapToItem={this.onSnapToItem}
         />
       </View>
@@ -81,11 +103,30 @@ const styles = StyleSheet.create({
   underlay: { height: 132 },
 });
 
+const MapScreen = (props: Props) => (
+  <Dimensions.Consumer>
+    {({ width }) => (
+      <SingleHotelContext.Consumer>
+        {({ setHotelId }) => {
+          return (
+            <MapScreenWithContext
+              {...props}
+              setHotelId={setHotelId}
+              deviceWidth={width}
+            />
+          );
+        }}
+      </SingleHotelContext.Consumer>
+    )}
+  </Dimensions.Consumer>
+);
+
 export default createFragmentContainer(
   MapScreen,
   graphql`
     fragment MapScreen on AllHotelsInterface @relay(plural: true) {
       id
+      hotelId
       ...MapView
       ...HotelSwipeList
     }
diff --git a/app/hotels/src/map/allHotels/__generated__/MapScreen.graphql.js b/app/hotels/src/map/allHotels/__generated__/MapScreen.graphql.js
index b8e6d41c..780c88af 100644
--- a/app/hotels/src/map/allHotels/__generated__/MapScreen.graphql.js
+++ b/app/hotels/src/map/allHotels/__generated__/MapScreen.graphql.js
@@ -14,6 +14,7 @@ import type { FragmentReference } from "relay-runtime";
 declare export opaque type MapScreen$ref: FragmentReference;
 export type MapScreen = $ReadOnlyArray<{|
   +id: string,
+  +hotelId: ?string,
   +$fragmentRefs: MapView$ref & HotelSwipeList$ref,
   +$refType: MapScreen$ref,
 |}>;
@@ -36,6 +37,13 @@ const node/*: ConcreteFragment*/ = {
       "args": null,
       "storageKey": null
     },
+    {
+      "kind": "ScalarField",
+      "alias": null,
+      "name": "hotelId",
+      "args": null,
+      "storageKey": null
+    },
     {
       "kind": "FragmentSpread",
       "name": "MapView",
@@ -49,5 +57,5 @@ const node/*: ConcreteFragment*/ = {
   ]
 };
 // prettier-ignore
-(node/*: any*/).hash = '198cfa902f41b93273553afc4ced731d';
+(node/*: any*/).hash = '6ee118ef2da5858b66aa07648b7ae804';
 module.exports = node;
diff --git a/app/hotels/src/navigation/allHotels/MapHeaderButton.android.js b/app/hotels/src/navigation/allHotels/MapHeaderButton.android.js
index 9ed095e4..8cb9c7a2 100644
--- a/app/hotels/src/navigation/allHotels/MapHeaderButton.android.js
+++ b/app/hotels/src/navigation/allHotels/MapHeaderButton.android.js
@@ -8,6 +8,7 @@ import RNTooltips from 'react-native-tooltips';
 type Props = {|
   +onPress: () => void,
   +icon: React.Element<typeof TextIcon>,
+  +testID?: string,
 |};
 
 type State = {|
@@ -42,7 +43,11 @@ export default class MapHeaderButton extends React.Component<Props, State> {
 
   render = () => (
     <React.Fragment>
-      <HeaderButton.Right onPress={this.onPress} onLongPress={this.onLongPress}>
+      <HeaderButton.Right
+        onPress={this.onPress}
+        onLongPress={this.onLongPress}
+        testID={this.props.testID}
+      >
         {this.props.icon}
       </HeaderButton.Right>
       <RNTooltips
diff --git a/app/hotels/src/navigation/allHotels/MapHeaderButton.ios.js b/app/hotels/src/navigation/allHotels/MapHeaderButton.ios.js
index 37670316..27607d28 100644
--- a/app/hotels/src/navigation/allHotels/MapHeaderButton.ios.js
+++ b/app/hotels/src/navigation/allHotels/MapHeaderButton.ios.js
@@ -7,11 +7,12 @@ import { TextIcon } from '@kiwicom/mobile-shared';
 type Props = {|
   +onPress: () => void,
   +icon: React.Element<typeof TextIcon>,
+  +testID?: string,
 |};
 
 export default function MapHeaderButton(props: Props) {
   return (
-    <HeaderButton.Right onPress={props.onPress}>
+    <HeaderButton.Right onPress={props.onPress} testID={props.testID}>
       {props.icon}
     </HeaderButton.Right>
   );
diff --git a/app/hotels/src/navigation/allHotels/SearchResultsScreen.js b/app/hotels/src/navigation/allHotels/SearchResultsScreen.js
index 612d664a..1ed599b8 100644
--- a/app/hotels/src/navigation/allHotels/SearchResultsScreen.js
+++ b/app/hotels/src/navigation/allHotels/SearchResultsScreen.js
@@ -1,7 +1,7 @@
 // @flow strict
 
 import * as React from 'react';
-import { View } from 'react-native';
+import { View, Platform } from 'react-native';
 import { type NavigationType } from '@kiwicom/mobile-navigation';
 import {
   LayoutDoubleColumn,
@@ -64,12 +64,47 @@ class SearchResultsScreen extends React.Component<Props> {
           {checkin !== null && (
             <AdaptableLayout
               renderOnNarrow={
-                <MapHeaderButton onPress={goToAllHotelsMap} icon={icon} />
+                <MapHeaderButton
+                  onPress={goToAllHotelsMap}
+                  icon={icon}
+                  testID="map-header-button"
+                />
               }
+              renderOnWide={Platform.select({
+                android: (
+                  <MapHeaderButton
+                    onPress={goToAllHotelsMap}
+                    icon={icon}
+                    testID="map-header-button"
+                  />
+                ),
+                ios: null,
+              })}
             />
           )}
         </React.Fragment>
       ),
+      headerTitle: Platform.select({
+        ios: (
+          <AdaptableLayout
+            renderOnWide={
+              <View style={styles.headerTitleContainer}>
+                <View style={styles.headerTitleLeft}>
+                  {checkin !== null && (
+                    <MapHeaderButton
+                      onPress={goToAllHotelsMap}
+                      icon={icon}
+                      testID="map-header-button"
+                    />
+                  )}
+                </View>
+                <View style={styles.headerTitleRight} />
+              </View>
+            }
+          />
+        ),
+        android: null,
+      }),
     };
   };
 
@@ -136,6 +171,22 @@ const styles = StyleSheet.create({
     marginEnd: 2,
     fontSize: 22,
   },
+  headerTitleContainer: {
+    flex: 1,
+    flexDirection: 'row',
+    height: '100%',
+  },
+  headerTitleLeft: {
+    flex: 1,
+    alignItems: 'flex-end',
+    justifyContent: 'center',
+  },
+  headerTitleRight: {
+    flex: 1,
+    borderStartColor: defaultTokens.paletteInkLight,
+    borderStartWidth: 0.5,
+    paddingStart: 0.5,
+  },
 });
 
 export default withSearchResultsContext(state => ({
diff --git a/packages/navigation/src/HeaderButton.js b/packages/navigation/src/HeaderButton.js
index a3ab1336..3163ea1e 100644
--- a/packages/navigation/src/HeaderButton.js
+++ b/packages/navigation/src/HeaderButton.js
@@ -26,6 +26,7 @@ type Props = {|
   +left?: boolean,
   +style?: StylePropType,
   +disabled?: boolean,
+  +testID?: string,
 |};
 
 type State = {|
@@ -99,7 +100,7 @@ export default class HeaderButton extends React.PureComponent<Props, State> {
   };
 
   render = () => {
-    const { disabled, onPress, style } = this.props;
+    const { disabled, onPress, style, testID } = this.props;
 
     return (
       <Touchable
@@ -112,6 +113,7 @@ export default class HeaderButton extends React.PureComponent<Props, State> {
         onLongPress={this.onLongPress}
         ref={this.storeReference}
         disabled={disabled}
+        testID={testID}
       >
         <View style={styles.container}>
           <View style={[styles.innerContainer, style]}>
diff --git a/packages/shared/src/TouchableWithoutFeedback.js b/packages/shared/src/TouchableWithoutFeedback.js
index 83c15143..0c7319f0 100644
--- a/packages/shared/src/TouchableWithoutFeedback.js
+++ b/packages/shared/src/TouchableWithoutFeedback.js
@@ -12,6 +12,7 @@ type Props = {|
   +disabled: boolean,
   +style?: StylePropType,
   +onLongPress?: () => void,
+  +testID?: string,
   ...AccessibilityProps,
 |};
 


Test patch:
diff --git a/app/hotels/src/allHotels/__tests__/AllHotelsSearchList.test.js b/app/hotels/src/allHotels/__tests__/AllHotelsSearchList.test.js
index f6cf815d..e3696358 100644
--- a/app/hotels/src/allHotels/__tests__/AllHotelsSearchList.test.js
+++ b/app/hotels/src/allHotels/__tests__/AllHotelsSearchList.test.js
@@ -6,7 +6,6 @@ import ShallowRenderer from 'react-test-renderer/shallow';
 import { AllHotelsSearchList } from '../AllHotelsSearchList';
 
 const renderer = new ShallowRenderer();
-const voidCallback = () => {};
 
 const $fragmentRefs: any = null;
 const $refType: any = null;
@@ -14,28 +13,26 @@ it('renders found hotels', () => {
   const data = [
     {
       id: 'hotel1',
+      hotelId: 'lol1',
       $fragmentRefs,
       $refType,
     },
     {
       id: 'hotel2',
+      hotelId: 'lol2',
       $fragmentRefs,
       $refType,
     },
   ];
 
-  renderer.render(
-    <AllHotelsSearchList data={data} openSingleHotel={voidCallback} />,
-  );
+  renderer.render(<AllHotelsSearchList data={data} setHotelId={jest.fn()} />);
   expect(renderer.getRenderOutput()).toMatchSnapshot();
 });
 
 it('renders a "not found" message when no hotel is found', () => {
   const data = [];
 
-  renderer.render(
-    <AllHotelsSearchList data={data} openSingleHotel={voidCallback} />,
-  );
+  renderer.render(<AllHotelsSearchList data={data} setHotelId={jest.fn()} />);
   expect(renderer.getRenderOutput()).toMatchSnapshot();
 });
 
@@ -44,7 +41,7 @@ it('renders a "not found" message when data is missing', () => {
 
   renderer.render(
     // $FlowExpectedError: Testing bad input
-    <AllHotelsSearchList data={data} openSingleHotel={voidCallback} />,
+    <AllHotelsSearchList data={data} />,
   );
   expect(renderer.getRenderOutput()).toMatchSnapshot();
 });
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 2723c7ae..318dccd9 100644
--- a/app/hotels/src/allHotels/__tests__/__snapshots__/AllHotelsSearchList.test.js.snap
+++ b/app/hotels/src/allHotels/__tests__/__snapshots__/AllHotelsSearchList.test.js.snap
@@ -22,26 +22,26 @@ exports[`renders a "not found" message when no hotel is found 1`] = `
 
 exports[`renders found hotels 1`] = `
 <React.Fragment>
-  <ForwardRef(Relay(AllHotelsSearchRowWithContext))
+  <ForwardRef(Relay(withNavigation(AllHotelsSearchRowWithContext)))
     data={
       Object {
         "$fragmentRefs": null,
         "$refType": null,
+        "hotelId": "lol1",
         "id": "hotel1",
       }
     }
-    openSingleHotel={[Function]}
     testID="firstHotelResult"
   />
-  <ForwardRef(Relay(AllHotelsSearchRowWithContext))
+  <ForwardRef(Relay(withNavigation(AllHotelsSearchRowWithContext)))
     data={
       Object {
         "$fragmentRefs": null,
         "$refType": null,
+        "hotelId": "lol2",
         "id": "hotel2",
       }
     }
-    openSingleHotel={[Function]}
     testID=""
   />
 </React.Fragment>
diff --git a/e2e/hotels.spec.js b/e2e/hotels.spec.js
index 09a4cdfe..b1d070b5 100644
--- a/e2e/hotels.spec.js
+++ b/e2e/hotels.spec.js
@@ -30,4 +30,14 @@ describe('Hotels', () => {
     await element(by.id('bookNowButton')).tap();
     await expect(element(by.id('paymentScreenSingleHotel'))).toExist();
   });
+
+  it('should be possible to see the results on the map', async () => {
+    await element(by.id('hotelsTab')).tap();
+    await expect(element(by.id('homePage'))).toBeVisible();
+    await element(by.id('homePage__Hotels-button')).tap();
+    await expect(element(by.id('firstHotelResult'))).toBeVisible();
+    await expect(element(by.id('map-header-button'))).toBeVisible();
+    await element(by.id('map-header-button')).tap();
+    await expect(element(by.id('allHotels-mapScreen'))).toBeVisible();
+  });
 });