Repository: kiwicom/mobile
Pull request #1032 (2018-08-28 09:51:39)

Adding transportation in trip services
Add transportation menu item

Add current location item

Remove wrong icons

Add deeplinking

tbergq: Also could you rewrite the commit message, reading the commit history, this message will not tell me what has changed. 

Patch:
diff --git a/app/MMB/index.js b/app/MMB/index.js
index 7eff25f6..0daf6657 100644
--- a/app/MMB/index.js
+++ b/app/MMB/index.js
@@ -16,6 +16,7 @@ import TravelDocumentFormContext from './src/scenes/travelDocument/form/TravelDo
 import InsuranceOverviewContext from './src/scenes/tripServices/insurance/insuranceOverviewScene/InsuranceOverviewSceneContext';
 import HotelsContext from './src/context/HotelsContext';
 import SplitNavigationContext from './src/context/SplitNavigationContext';
+import GoogleMapsContext from './src/context/GoogleMapsContext';
 
 type Props = {|
   +currency: string,
@@ -27,6 +28,7 @@ type Props = {|
   +version: string,
   +dataSaverEnabled: boolean,
   +bookingComAffiliate: string,
+  +googleMapsAPIKey: string,
 |};
 
 class ManageMyBookingPackage extends React.Component<Props> {
@@ -48,11 +50,15 @@ class ManageMyBookingPackage extends React.Component<Props> {
                   onNavigationStateChange={this.props.onNavigationStateChange}
                 >
                   <SplitNavigationContext.Provider>
-                    <NavigationStack
-                      onNavigationStateChange={
-                        this.props.onNavigationStateChange
-                      }
-                    />
+                    <GoogleMapsContext.Provider
+                      googleMapsAPIKey={this.props.googleMapsAPIKey}
+                    >
+                      <NavigationStack
+                        onNavigationStateChange={
+                          this.props.onNavigationStateChange
+                        }
+                      />
+                    </GoogleMapsContext.Provider>
                   </SplitNavigationContext.Provider>
                 </HotelsContext.Provider>
               </InsuranceOverviewContext.Provider>
diff --git a/app/MMB/src/context/GoogleMapsContext.js b/app/MMB/src/context/GoogleMapsContext.js
new file mode 100644
index 00000000..3a7324e0
--- /dev/null
+++ b/app/MMB/src/context/GoogleMapsContext.js
@@ -0,0 +1,48 @@
+// @flow strict
+
+import * as React from 'react';
+
+const defaultState = {
+  googleMapsAPIKey: '',
+};
+
+const { Consumer, Provider: ContextProvider } = React.createContext({
+  ...defaultState,
+});
+
+type Props = {|
+  +children: React.Node,
+  +googleMapsAPIKey: string,
+|};
+
+type State = {|
+  +googleMapsAPIKey: string,
+|};
+
+class Provider extends React.Component<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    this.state = {
+      googleMapsAPIKey: props.googleMapsAPIKey,
+    };
+  }
+  render = () => (
+    <ContextProvider value={this.state}>{this.props.children}</ContextProvider>
+  );
+}
+
+export default { Consumer, Provider };
+
+type PropsWithContext = {};
+
+export function withGoogleMapsContext(Component: React.ElementType) {
+  const WithGoogleMapsContext = (props: PropsWithContext) => (
+    <Consumer>{context => <Component {...props} {...context} />}</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) {
+    WithGoogleMapsContext.navigationOptions = Component.navigationOptions;
+  }
+  return WithGoogleMapsContext;
+}
diff --git a/app/MMB/src/navigation/AddressPickerScreen.js b/app/MMB/src/navigation/AddressPickerScreen.js
new file mode 100644
index 00000000..33f2682b
--- /dev/null
+++ b/app/MMB/src/navigation/AddressPickerScreen.js
@@ -0,0 +1,42 @@
+// @flow
+
+import * as React from 'react';
+import { View } from 'react-native';
+import { Translation } from '@kiwicom/mobile-localization';
+import { HeaderButton, type NavigationType } from '@kiwicom/mobile-navigation';
+
+type Props = {|
+  +navigation: NavigationType,
+  +currentAddress: ?string,
+|};
+
+export default class AddressPickerScreen extends React.Component<Props> {
+  static navigationOptions = (props: Props) => {
+    function goBack() {
+      props.navigation.goBack();
+    }
+
+    return {
+      headerLeft: (
+        <HeaderButton.CloseModal
+          onPress={goBack}
+          text={
+            <Translation id="mmb.trip_services.transportation.address_picker.cancel_button" />
+          }
+        />
+      ),
+      headerTitle: (
+        <View>
+          <Translation passThrough="TODO" />
+        </View>
+      ),
+    };
+  };
+  render() {
+    return (
+      <View>
+        <Translation passThrough="TODO" />
+      </View>
+    );
+  }
+}
diff --git a/app/MMB/src/navigation/NavigationStack.js b/app/MMB/src/navigation/NavigationStack.js
index 7a49a6de..02cf8b3e 100644
--- a/app/MMB/src/navigation/NavigationStack.js
+++ b/app/MMB/src/navigation/NavigationStack.js
@@ -6,6 +6,7 @@ import {
 } from '@kiwicom/mobile-navigation';
 import { createSwitchNavigator } from 'react-navigation';
 import { withMappedNavigationAndConfigProps as withMappedProps } from 'react-navigation-props-mapper';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
 
 import { NewHotelsStandAlonePackage } from '../../../hotels';
 import DetailScreen, { MenuComponents } from './DetailScreen';
@@ -13,17 +14,23 @@ import ListScreen from './ListScreen';
 import FillTravelDocumentScreen from './FillTravelDocumentScreen';
 import TravelDocumentModalScreen from './TravelDocumentModalScreen';
 import AppleWalletScreen from './AppleWalletScreen';
+import AddressPickerScreen from './AddressPickerScreen';
+import TransportationMap from '../scenes/tripServices/transportation/TransportationMap';
 
 // THIS IS ONLY FOR MOBILE DEVICES!
 const Screens = {};
 Object.entries(MenuComponents).forEach(
   // $FlowIssue: https://github.com/facebook/flow/issues/2221
-  ([routeName, { screen, headerTitle, headerRight }]) => {
+  ([routeName, { screen, headerTitle, headerRight, headerStyle }]) => {
     Screens[routeName] = {
       screen: withMappedProps(screen),
       navigationOptions: {
         headerTitle,
         headerRight,
+        headerStyle: {
+          ...headerStyle,
+          backgroundColor: defaultTokens.paletteWhite,
+        },
       },
     };
   },
@@ -80,6 +87,19 @@ const SwitchStack = createSwitchNavigator(
   },
 );
 
+const TransportationStack = StackNavigator(
+  {
+    AddressPickerScreen: withMappedProps(AddressPickerScreen),
+    TransportationMap: withMappedProps(TransportationMap),
+    // TODO add another webview screen to this stack as navigation behaviour looks weird otherwise.
+  },
+  {
+    ...StackNavigatorOptions,
+    initialRouteName: 'TransportationMap',
+    mode: 'modal',
+  },
+);
+
 export default StackNavigator(
   {
     MMBMainStack: {
@@ -88,7 +108,9 @@ export default StackNavigator(
     TravelDocumentModalStack: {
       screen: TravelDocumentModalStack,
     },
-    // This is not good enough, it is complicated, needs to be improved later
+    TransportationMap: {
+      screen: TransportationStack,
+    },
   },
   {
     ...StackNavigatorOptions,
diff --git a/app/MMB/src/scenes/tickets/TicketDeleteButton.js b/app/MMB/src/scenes/tickets/TicketDeleteButton.js
index 07684cae..5c1dc890 100644
--- a/app/MMB/src/scenes/tickets/TicketDeleteButton.js
+++ b/app/MMB/src/scenes/tickets/TicketDeleteButton.js
@@ -63,9 +63,9 @@ export class TicketDeleteButton extends React.Component<
   };
 
   deleteFilesPromise = async () => {
-    // TODO: Delete boarding passes
     try {
       await RNFetchBlob.fs.unlink(this.getEticketPath());
+      await this.deleteBoardingPasses();
       this.setState({ hasLocalFiles: false });
     } catch (error) {
       Alert.translatedAlert(null, {
@@ -75,11 +75,34 @@ export class TicketDeleteButton extends React.Component<
     }
   };
 
+  deleteBoardingPasses = async () => {
+    try {
+      const boardingPasses = await RNFetchBlob.fs.ls(
+        this.getBoardingPassPath(),
+      );
+      boardingPasses.forEach(async boardingPass => {
+        if (boardingPass.includes(`-bookingId:${this.props.bookingId}`)) {
+          await RNFetchBlob.fs.unlink(
+            `${this.getBoardingPassPath()}/${boardingPass}`,
+          );
+        }
+      });
+    } catch (error) {
+      throw error;
+    }
+  };
+
   hasLocalFiles = async () => {
-    // TODO: Check for boardingPasses
     const existsEticket = await RNFetchBlob.fs.exists(this.getEticketPath());
+    const existsBoardingPasses = await this.hasLocalBoardingPasses();
+    this.setState({ hasLocalFiles: existsEticket || existsBoardingPasses });
+  };
 
-    this.setState({ hasLocalFiles: existsEticket });
+  hasLocalBoardingPasses = async () => {
+    const boardingPasses = await RNFetchBlob.fs.ls(this.getBoardingPassPath());
+    return boardingPasses.some(file =>
+      file.includes(`-bookingId:${this.props.bookingId}`),
+    );
   };
 
   getEticketPath = () => {
@@ -89,6 +112,11 @@ export class TicketDeleteButton extends React.Component<
     return `${DocumentDir}/${eTicketPath}`;
   };
 
+  getBoardingPassPath = () => {
+    const { DocumentDir } = RNFetchBlob.fs.dirs;
+    return `${DocumentDir}/boardingPasses`;
+  };
+
   render = () => {
     if (this.state.hasLocalFiles === false) {
       return null;
diff --git a/app/MMB/src/scenes/tripServices/LocationPopupButton.js b/app/MMB/src/scenes/tripServices/LocationPopupButton.js
index 0d4b3f0e..7a9072ae 100644
--- a/app/MMB/src/scenes/tripServices/LocationPopupButton.js
+++ b/app/MMB/src/scenes/tripServices/LocationPopupButton.js
@@ -16,6 +16,7 @@ type Props = {|
   +data: LocationPopupButtonType,
   +onPress: string => void,
   +displayIata?: boolean,
+  +displayDate?: boolean,
   +date?: ?string,
 |};
 
@@ -33,6 +34,7 @@ class LocationPopupButton extends React.Component<Props> {
     const cityName = idx(this.props, _ => _.data.city.name);
     const locationId = idx(this.props, _ => _.data.locationId);
     const date = idx(this.props, _ => _.date);
+    const displayDate = idx(this.props, _ => _.displayDate);
 
     return (
       <Touchable onPress={this.openWhitelabel} style={styleSheet.wrapper}>
@@ -41,16 +43,18 @@ class LocationPopupButton extends React.Component<Props> {
           <View style={styleSheet.locationWrapper}>
             {this.props.displayIata ? (
               <Translation
-                passThrough={`in ${cityName || ''} (${locationId || ''})`}
+                passThrough={`${cityName || ''} (${locationId || ''})`}
               />
             ) : (
               <Translation passThrough={cityName || ''} />
             )}
-            {this.props.date != null && (
-              <Text style={styleSheet.date}>
-                <Translation passThrough={date || ''} />
-              </Text>
-            )}
+            {date != null &&
+              displayDate != null &&
+              displayDate && (
+                <Text style={styleSheet.date}>
+                  <Translation passThrough={date} />
+                </Text>
+              )}
           </View>
         </React.Fragment>
       </Touchable>
diff --git a/app/MMB/src/scenes/tripServices/TripServiceRefreshContainer.js b/app/MMB/src/scenes/tripServices/TripServiceRefreshContainer.js
index c9dffc5a..57f3bfcc 100644
--- a/app/MMB/src/scenes/tripServices/TripServiceRefreshContainer.js
+++ b/app/MMB/src/scenes/tripServices/TripServiceRefreshContainer.js
@@ -21,7 +21,7 @@ import LoungeMenuItem from './LoungeMenuItem';
 import ParkingMenuItem from './ParkingMenuItem';
 import InsuranceMenuItemContainer from './insuranceMenuItem/InsuranceMenuItemContainer';
 import CarRentalMenuItem from './CarRentalMenuItem';
-import TransportationMenuItem from './TransportationMenuItem';
+import TransportationMenuItem from './transportationMenuItem/TransportationMenuItem';
 import type { TripServiceRefreshContainer as BookingType } from './__generated__/TripServiceRefreshContainer.graphql';
 
 type Props = {|
diff --git a/app/MMB/src/scenes/tripServices/__generated__/TripServiceRefreshContainerQuery.graphql.js b/app/MMB/src/scenes/tripServices/__generated__/TripServiceRefreshContainerQuery.graphql.js
index 4e731c2c..62a7a28a 100644
--- a/app/MMB/src/scenes/tripServices/__generated__/TripServiceRefreshContainerQuery.graphql.js
+++ b/app/MMB/src/scenes/tripServices/__generated__/TripServiceRefreshContainerQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash 9c95dcfc92d8ad0c376aa71e5f40b2c8
+ * @relayHash 110d04de7aeafb6356d32ebf0fe55014
  */
 
 /* eslint-disable */
@@ -133,7 +133,14 @@ fragment TransportationMenuItem on WhitelabeledServices {
     relevantLocations {
       whitelabelURL
       location {
-        ...LocationPopupButton
+        ...TransportLocationItem
+        location {
+          lat
+          lng
+        }
+        city {
+          name
+        }
         id
       }
       date
@@ -141,6 +148,14 @@ fragment TransportationMenuItem on WhitelabeledServices {
   }
 }
 
+fragment TransportLocationItem on Location {
+  ...LocationPopupButton
+  location {
+    lat
+    lng
+  }
+}
+
 fragment LocationPopupButton on Location {
   city {
     name
@@ -217,13 +232,39 @@ v3 = {
   "storageKey": null
 },
 v4 = {
+  "kind": "LinkedField",
+  "alias": null,
+  "name": "city",
+  "storageKey": null,
+  "args": null,
+  "concreteType": "LocationArea",
+  "plural": false,
+  "selections": [
+    v3
+  ]
+},
+v5 = {
+  "kind": "ScalarField",
+  "alias": null,
+  "name": "locationId",
+  "args": null,
+  "storageKey": null
+},
+v6 = {
+  "kind": "ScalarField",
+  "alias": null,
+  "name": "countryFlagURL",
+  "args": null,
+  "storageKey": null
+},
+v7 = {
   "kind": "ScalarField",
   "alias": null,
   "name": "id",
   "args": null,
   "storageKey": null
 },
-v5 = {
+v8 = {
   "kind": "LinkedField",
   "alias": null,
   "name": "location",
@@ -232,40 +273,17 @@ v5 = {
   "concreteType": "Location",
   "plural": false,
   "selections": [
-    {
-      "kind": "LinkedField",
-      "alias": null,
-      "name": "city",
-      "storageKey": null,
-      "args": null,
-      "concreteType": "LocationArea",
-      "plural": false,
-      "selections": [
-        v3
-      ]
-    },
-    {
-      "kind": "ScalarField",
-      "alias": null,
-      "name": "locationId",
-      "args": null,
-      "storageKey": null
-    },
-    {
-      "kind": "ScalarField",
-      "alias": null,
-      "name": "countryFlagURL",
-      "args": null,
-      "storageKey": null
-    },
-    v4
+    v4,
+    v5,
+    v6,
+    v7
   ]
 },
-v6 = [
+v9 = [
   v2,
-  v5
+  v8
 ],
-v7 = [
+v10 = [
   {
     "kind": "LinkedField",
     "alias": null,
@@ -290,7 +308,7 @@ return {
   "operationKind": "query",
   "name": "TripServiceRefreshContainerQuery",
   "id": null,
-  "text": "query TripServiceRefreshContainerQuery(\n  $bookingId: Int!\n  $authToken: String!\n) {\n  singleBooking(id: $bookingId, authToken: $authToken) {\n    __typename\n    ...TripServiceRefreshContainer\n    id\n  }\n}\n\nfragment TripServiceRefreshContainer on BookingInterface {\n  databaseId\n  authToken\n  ...InsuranceMenuItemContainer\n  availableWhitelabeledServices {\n    ...CarRentalMenuItem\n    ...LoungeMenuItem\n    ...ParkingMenuItem\n    ...HotelMenuItem\n    ...TransportationMenuItem\n  }\n}\n\nfragment InsuranceMenuItemContainer on BookingInterface {\n  __typename\n  status\n  passengers {\n    nationality\n  }\n  isPastBooking\n  ... on BookingOneWay {\n    trip {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingReturn {\n    outbound {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingMulticity {\n    trips {\n      ...InsuranceMenuItem\n    }\n  }\n}\n\nfragment CarRentalMenuItem on WhitelabeledServices {\n  carRental {\n    relevantCities {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment LoungeMenuItem on WhitelabeledServices {\n  lounge {\n    relevantAirports {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment ParkingMenuItem on WhitelabeledServices {\n  parking {\n    whitelabelURL\n  }\n}\n\nfragment HotelMenuItem on WhitelabeledServices {\n  hotel {\n    roomsConfiguration {\n      adultsCount\n      children {\n        age\n      }\n    }\n    relevantLocations {\n      ...LocationItem\n      checkin\n      checkout\n      location {\n        id\n      }\n      hotelCity {\n        id\n        name\n      }\n    }\n  }\n}\n\nfragment TransportationMenuItem on WhitelabeledServices {\n  transportation {\n    relevantLocations {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n      date\n    }\n  }\n}\n\nfragment LocationPopupButton on Location {\n  city {\n    name\n  }\n  locationId\n  ...CountryFlag\n}\n\nfragment CountryFlag on Location {\n  countryFlagURL\n}\n\nfragment LocationItem on HotelServiceRelevantLocation {\n  checkin\n  checkout\n  hotelCity {\n    id\n    name\n  }\n  location {\n    ...LocationPopupButton\n    id\n  }\n}\n\nfragment InsuranceMenuItem on Trip {\n  departure {\n    time\n  }\n}\n",
+  "text": "query TripServiceRefreshContainerQuery(\n  $bookingId: Int!\n  $authToken: String!\n) {\n  singleBooking(id: $bookingId, authToken: $authToken) {\n    __typename\n    ...TripServiceRefreshContainer\n    id\n  }\n}\n\nfragment TripServiceRefreshContainer on BookingInterface {\n  databaseId\n  authToken\n  ...InsuranceMenuItemContainer\n  availableWhitelabeledServices {\n    ...CarRentalMenuItem\n    ...LoungeMenuItem\n    ...ParkingMenuItem\n    ...HotelMenuItem\n    ...TransportationMenuItem\n  }\n}\n\nfragment InsuranceMenuItemContainer on BookingInterface {\n  __typename\n  status\n  passengers {\n    nationality\n  }\n  isPastBooking\n  ... on BookingOneWay {\n    trip {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingReturn {\n    outbound {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingMulticity {\n    trips {\n      ...InsuranceMenuItem\n    }\n  }\n}\n\nfragment CarRentalMenuItem on WhitelabeledServices {\n  carRental {\n    relevantCities {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment LoungeMenuItem on WhitelabeledServices {\n  lounge {\n    relevantAirports {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment ParkingMenuItem on WhitelabeledServices {\n  parking {\n    whitelabelURL\n  }\n}\n\nfragment HotelMenuItem on WhitelabeledServices {\n  hotel {\n    roomsConfiguration {\n      adultsCount\n      children {\n        age\n      }\n    }\n    relevantLocations {\n      ...LocationItem\n      checkin\n      checkout\n      location {\n        id\n      }\n      hotelCity {\n        id\n        name\n      }\n    }\n  }\n}\n\nfragment TransportationMenuItem on WhitelabeledServices {\n  transportation {\n    relevantLocations {\n      whitelabelURL\n      location {\n        ...TransportLocationItem\n        location {\n          lat\n          lng\n        }\n        city {\n          name\n        }\n        id\n      }\n      date\n    }\n  }\n}\n\nfragment TransportLocationItem on Location {\n  ...LocationPopupButton\n  location {\n    lat\n    lng\n  }\n}\n\nfragment LocationPopupButton on Location {\n  city {\n    name\n  }\n  locationId\n  ...CountryFlag\n}\n\nfragment CountryFlag on Location {\n  countryFlagURL\n}\n\nfragment LocationItem on HotelServiceRelevantLocation {\n  checkin\n  checkout\n  hotelCity {\n    id\n    name\n  }\n  location {\n    ...LocationPopupButton\n    id\n  }\n}\n\nfragment InsuranceMenuItem on Trip {\n  departure {\n    time\n  }\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
@@ -410,7 +428,7 @@ return {
                     "args": null,
                     "concreteType": "CarRentalServiceRelevantCities",
                     "plural": true,
-                    "selections": v6
+                    "selections": v9
                   }
                 ]
               },
@@ -431,7 +449,7 @@ return {
                     "args": null,
                     "concreteType": "LoungeServiceRelevantAirports",
                     "plural": true,
-                    "selections": v6
+                    "selections": v9
                   }
                 ]
               },
@@ -524,11 +542,11 @@ return {
                         "concreteType": "HotelCity",
                         "plural": false,
                         "selections": [
-                          v4,
+                          v7,
                           v3
                         ]
                       },
-                      v5
+                      v8
                     ]
                   }
                 ]
@@ -552,7 +570,46 @@ return {
                     "plural": true,
                     "selections": [
                       v2,
-                      v5,
+                      {
+                        "kind": "LinkedField",
+                        "alias": null,
+                        "name": "location",
+                        "storageKey": null,
+                        "args": null,
+                        "concreteType": "Location",
+                        "plural": false,
+                        "selections": [
+                          v4,
+                          v5,
+                          v6,
+                          {
+                            "kind": "LinkedField",
+                            "alias": null,
+                            "name": "location",
+                            "storageKey": null,
+                            "args": null,
+                            "concreteType": "Coordinates",
+                            "plural": false,
+                            "selections": [
+                              {
+                                "kind": "ScalarField",
+                                "alias": null,
+                                "name": "lat",
+                                "args": null,
+                                "storageKey": null
+                              },
+                              {
+                                "kind": "ScalarField",
+                                "alias": null,
+                                "name": "lng",
+                                "args": null,
+                                "storageKey": null
+                              }
+                            ]
+                          },
+                          v7
+                        ]
+                      },
                       {
                         "kind": "ScalarField",
                         "alias": null,
@@ -566,7 +623,7 @@ return {
               }
             ]
           },
-          v4,
+          v7,
           {
             "kind": "InlineFragment",
             "type": "BookingOneWay",
@@ -579,7 +636,7 @@ return {
                 "args": null,
                 "concreteType": "Trip",
                 "plural": false,
-                "selections": v7
+                "selections": v10
               }
             ]
           },
@@ -595,7 +652,7 @@ return {
                 "args": null,
                 "concreteType": "Trip",
                 "plural": true,
-                "selections": v7
+                "selections": v10
               }
             ]
           },
@@ -611,7 +668,7 @@ return {
                 "args": null,
                 "concreteType": "Trip",
                 "plural": false,
-                "selections": v7
+                "selections": v10
               }
             ]
           }
diff --git a/app/MMB/src/scenes/tripServices/__generated__/TripServicesQuery.graphql.js b/app/MMB/src/scenes/tripServices/__generated__/TripServicesQuery.graphql.js
index f89d9218..2fadc646 100644
--- a/app/MMB/src/scenes/tripServices/__generated__/TripServicesQuery.graphql.js
+++ b/app/MMB/src/scenes/tripServices/__generated__/TripServicesQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash 6f7f7493b9bdb0051c87afef16d425ab
+ * @relayHash e09ac756552e487c6abf9161bdfc055b
  */
 
 /* eslint-disable */
@@ -133,7 +133,14 @@ fragment TransportationMenuItem on WhitelabeledServices {
     relevantLocations {
       whitelabelURL
       location {
-        ...LocationPopupButton
+        ...TransportLocationItem
+        location {
+          lat
+          lng
+        }
+        city {
+          name
+        }
         id
       }
       date
@@ -141,6 +148,14 @@ fragment TransportationMenuItem on WhitelabeledServices {
   }
 }
 
+fragment TransportLocationItem on Location {
+  ...LocationPopupButton
+  location {
+    lat
+    lng
+  }
+}
+
 fragment LocationPopupButton on Location {
   city {
     name
@@ -217,13 +232,39 @@ v3 = {
   "storageKey": null
 },
 v4 = {
+  "kind": "LinkedField",
+  "alias": null,
+  "name": "city",
+  "storageKey": null,
+  "args": null,
+  "concreteType": "LocationArea",
+  "plural": false,
+  "selections": [
+    v3
+  ]
+},
+v5 = {
+  "kind": "ScalarField",
+  "alias": null,
+  "name": "locationId",
+  "args": null,
+  "storageKey": null
+},
+v6 = {
+  "kind": "ScalarField",
+  "alias": null,
+  "name": "countryFlagURL",
+  "args": null,
+  "storageKey": null
+},
+v7 = {
   "kind": "ScalarField",
   "alias": null,
   "name": "id",
   "args": null,
   "storageKey": null
 },
-v5 = {
+v8 = {
   "kind": "LinkedField",
   "alias": null,
   "name": "location",
@@ -232,40 +273,17 @@ v5 = {
   "concreteType": "Location",
   "plural": false,
   "selections": [
-    {
-      "kind": "LinkedField",
-      "alias": null,
-      "name": "city",
-      "storageKey": null,
-      "args": null,
-      "concreteType": "LocationArea",
-      "plural": false,
-      "selections": [
-        v3
-      ]
-    },
-    {
-      "kind": "ScalarField",
-      "alias": null,
-      "name": "locationId",
-      "args": null,
-      "storageKey": null
-    },
-    {
-      "kind": "ScalarField",
-      "alias": null,
-      "name": "countryFlagURL",
-      "args": null,
-      "storageKey": null
-    },
-    v4
+    v4,
+    v5,
+    v6,
+    v7
   ]
 },
-v6 = [
+v9 = [
   v2,
-  v5
+  v8
 ],
-v7 = [
+v10 = [
   {
     "kind": "LinkedField",
     "alias": null,
@@ -290,7 +308,7 @@ return {
   "operationKind": "query",
   "name": "TripServicesQuery",
   "id": null,
-  "text": "query TripServicesQuery(\n  $bookingId: Int!\n  $authToken: String!\n) {\n  singleBooking(id: $bookingId, authToken: $authToken) {\n    __typename\n    ...TripServiceRefreshContainer\n    id\n  }\n}\n\nfragment TripServiceRefreshContainer on BookingInterface {\n  databaseId\n  authToken\n  ...InsuranceMenuItemContainer\n  availableWhitelabeledServices {\n    ...CarRentalMenuItem\n    ...LoungeMenuItem\n    ...ParkingMenuItem\n    ...HotelMenuItem\n    ...TransportationMenuItem\n  }\n}\n\nfragment InsuranceMenuItemContainer on BookingInterface {\n  __typename\n  status\n  passengers {\n    nationality\n  }\n  isPastBooking\n  ... on BookingOneWay {\n    trip {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingReturn {\n    outbound {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingMulticity {\n    trips {\n      ...InsuranceMenuItem\n    }\n  }\n}\n\nfragment CarRentalMenuItem on WhitelabeledServices {\n  carRental {\n    relevantCities {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment LoungeMenuItem on WhitelabeledServices {\n  lounge {\n    relevantAirports {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment ParkingMenuItem on WhitelabeledServices {\n  parking {\n    whitelabelURL\n  }\n}\n\nfragment HotelMenuItem on WhitelabeledServices {\n  hotel {\n    roomsConfiguration {\n      adultsCount\n      children {\n        age\n      }\n    }\n    relevantLocations {\n      ...LocationItem\n      checkin\n      checkout\n      location {\n        id\n      }\n      hotelCity {\n        id\n        name\n      }\n    }\n  }\n}\n\nfragment TransportationMenuItem on WhitelabeledServices {\n  transportation {\n    relevantLocations {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n      date\n    }\n  }\n}\n\nfragment LocationPopupButton on Location {\n  city {\n    name\n  }\n  locationId\n  ...CountryFlag\n}\n\nfragment CountryFlag on Location {\n  countryFlagURL\n}\n\nfragment LocationItem on HotelServiceRelevantLocation {\n  checkin\n  checkout\n  hotelCity {\n    id\n    name\n  }\n  location {\n    ...LocationPopupButton\n    id\n  }\n}\n\nfragment InsuranceMenuItem on Trip {\n  departure {\n    time\n  }\n}\n",
+  "text": "query TripServicesQuery(\n  $bookingId: Int!\n  $authToken: String!\n) {\n  singleBooking(id: $bookingId, authToken: $authToken) {\n    __typename\n    ...TripServiceRefreshContainer\n    id\n  }\n}\n\nfragment TripServiceRefreshContainer on BookingInterface {\n  databaseId\n  authToken\n  ...InsuranceMenuItemContainer\n  availableWhitelabeledServices {\n    ...CarRentalMenuItem\n    ...LoungeMenuItem\n    ...ParkingMenuItem\n    ...HotelMenuItem\n    ...TransportationMenuItem\n  }\n}\n\nfragment InsuranceMenuItemContainer on BookingInterface {\n  __typename\n  status\n  passengers {\n    nationality\n  }\n  isPastBooking\n  ... on BookingOneWay {\n    trip {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingReturn {\n    outbound {\n      ...InsuranceMenuItem\n    }\n  }\n  ... on BookingMulticity {\n    trips {\n      ...InsuranceMenuItem\n    }\n  }\n}\n\nfragment CarRentalMenuItem on WhitelabeledServices {\n  carRental {\n    relevantCities {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment LoungeMenuItem on WhitelabeledServices {\n  lounge {\n    relevantAirports {\n      whitelabelURL\n      location {\n        ...LocationPopupButton\n        id\n      }\n    }\n  }\n}\n\nfragment ParkingMenuItem on WhitelabeledServices {\n  parking {\n    whitelabelURL\n  }\n}\n\nfragment HotelMenuItem on WhitelabeledServices {\n  hotel {\n    roomsConfiguration {\n      adultsCount\n      children {\n        age\n      }\n    }\n    relevantLocations {\n      ...LocationItem\n      checkin\n      checkout\n      location {\n        id\n      }\n      hotelCity {\n        id\n        name\n      }\n    }\n  }\n}\n\nfragment TransportationMenuItem on WhitelabeledServices {\n  transportation {\n    relevantLocations {\n      whitelabelURL\n      location {\n        ...TransportLocationItem\n        location {\n          lat\n          lng\n        }\n        city {\n          name\n        }\n        id\n      }\n      date\n    }\n  }\n}\n\nfragment TransportLocationItem on Location {\n  ...LocationPopupButton\n  location {\n    lat\n    lng\n  }\n}\n\nfragment LocationPopupButton on Location {\n  city {\n    name\n  }\n  locationId\n  ...CountryFlag\n}\n\nfragment CountryFlag on Location {\n  countryFlagURL\n}\n\nfragment LocationItem on HotelServiceRelevantLocation {\n  checkin\n  checkout\n  hotelCity {\n    id\n    name\n  }\n  location {\n    ...LocationPopupButton\n    id\n  }\n}\n\nfragment InsuranceMenuItem on Trip {\n  departure {\n    time\n  }\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
@@ -410,7 +428,7 @@ return {
                     "args": null,
                     "concreteType": "CarRentalServiceRelevantCities",
                     "plural": true,
-                    "selections": v6
+                    "selections": v9
                   }
                 ]
               },
@@ -431,7 +449,7 @@ return {
                     "args": null,
                     "concreteType": "LoungeServiceRelevantAirports",
                     "plural": true,
-                    "selections": v6
+                    "selections": v9
                   }
                 ]
               },
@@ -524,11 +542,11 @@ return {
                         "concreteType": "HotelCity",
                         "plural": false,
                         "selections": [
-                          v4,
+                          v7,
                           v3
                         ]
                       },
-                      v5
+                      v8
                     ]
                   }
                 ]
@@ -552,7 +570,46 @@ return {
                     "plural": true,
                     "selections": [
                       v2,
-                      v5,
+                      {
+                        "kind": "LinkedField",
+                        "alias": null,
+                        "name": "location",
+                        "storageKey": null,
+                        "args": null,
+                        "concreteType": "Location",
+                        "plural": false,
+                        "selections": [
+                          v4,
+                          v5,
+                          v6,
+                          {
+                            "kind": "LinkedField",
+                            "alias": null,
+                            "name": "location",
+                            "storageKey": null,
+                            "args": null,
+                            "concreteType": "Coordinates",
+                            "plural": false,
+                            "selections": [
+                              {
+                                "kind": "ScalarField",
+                                "alias": null,
+                                "name": "lat",
+                                "args": null,
+                                "storageKey": null
+                              },
+                              {
+                                "kind": "ScalarField",
+                                "alias": null,
+                                "name": "lng",
+                                "args": null,
+                                "storageKey": null
+                              }
+                            ]
+                          },
+                          v7
+                        ]
+                      },
                       {
                         "kind": "ScalarField",
                         "alias": null,
@@ -566,7 +623,7 @@ return {
               }
             ]
           },
-          v4,
+          v7,
           {
             "kind": "InlineFragment",
             "type": "BookingOneWay",
@@ -579,7 +636,7 @@ return {
                 "args": null,
                 "concreteType": "Trip",
                 "plural": false,
-                "selections": v7
+                "selections": v10
               }
             ]
           },
@@ -595,7 +652,7 @@ return {
                 "args": null,
                 "concreteType": "Trip",
                 "plural": true,
-                "selections": v7
+                "selections": v10
               }
             ]
           },
@@ -611,7 +668,7 @@ return {
                 "args": null,
                 "concreteType": "Trip",
                 "plural": false,
-                "selections": v7
+                "selections": v10
               }
             ]
           }
diff --git a/app/MMB/src/scenes/tripServices/transportation/AddressLocationLegend.js b/app/MMB/src/scenes/tripServices/transportation/AddressLocationLegend.js
new file mode 100644
index 00000000..57df34e4
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/AddressLocationLegend.js
@@ -0,0 +1,71 @@
+// @flow
+
+import * as React from 'react';
+import { View } from 'react-native';
+import { StyleSheet, TextIcon, Color, Text } from '@kiwicom/mobile-shared';
+import { Translation } from '@kiwicom/mobile-localization';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+
+export default function AddressLocationLegend() {
+  return (
+    <View style={styles.container}>
+      <View style={styles.row}>
+        <Text style={styles.text}>
+          <Translation id="mmb.trip_service.transportation.map.legend.long_tap" />
+        </Text>
+        <TextIcon code="j" style={[styles.icon, styles.markerA]} />
+        <Text style={styles.text}>
+          <Translation id="mmb.trip_service.transportation.map.legend.markerA" />
+        </Text>
+      </View>
+      <View style={styles.row}>
+        <Text style={styles.text}>
+          <Translation id="mmb.trip_service.transportation.map.legend.tap" />
+        </Text>
+        <TextIcon code="k" style={[styles.icon, styles.markerB]} />
+        <Text style={styles.text}>
+          <Translation id="mmb.trip_service.transportation.map.legend.markerB" />
+        </Text>
+      </View>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  text: {
+    color: defaultTokens.paletteInkLight,
+    fontSize: 16,
+  },
+  markerA: {
+    color: defaultTokens.paletteProductNormal,
+  },
+  markerB: {
+    color: defaultTokens.colorAlertIconWarning,
+  },
+  row: {
+    flexDirection: 'row',
+  },
+  container: {
+    height: 40,
+    borderColor: defaultTokens.paletteWhite,
+    flexDirection: 'row',
+    borderRadius: 3,
+    justifyContent: 'space-around',
+    alignItems: 'center',
+    backgroundColor: defaultTokens.paletteWhite,
+    position: 'absolute',
+    bottom: 20,
+    start: 0,
+    end: 0,
+    marginHorizontal: 45,
+    shadowOpacity: 0.1,
+    shadowRadius: 4,
+    shadowColor: Color.black, //TODO
+    shadowOffset: { height: 0, width: 0 },
+  },
+  icon: {
+    fontSize: 15,
+    alignSelf: 'center',
+    paddingHorizontal: 5,
+  },
+});
diff --git a/app/MMB/src/scenes/tripServices/transportation/CurrentPositionButton.js b/app/MMB/src/scenes/tripServices/transportation/CurrentPositionButton.js
new file mode 100644
index 00000000..f5c66841
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/CurrentPositionButton.js
@@ -0,0 +1,46 @@
+// @flow
+
+import * as React from 'react';
+import { View, TouchableWithoutFeedback } from 'react-native';
+import { StyleSheet, TextIcon } from '@kiwicom/mobile-shared';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+
+type Props = {|
+  +onPress: () => void,
+|};
+
+export default function CurrentPositionButton(props: Props) {
+  return (
+    <View style={styles.roundButton}>
+      <TouchableWithoutFeedback onPress={props.onPress}>
+        <View>
+          <TextIcon code="&quot;" style={styles.icon} />
+        </View>
+      </TouchableWithoutFeedback>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  roundButton: {
+    borderWidth: 1,
+    width: 40,
+    height: 40,
+    borderColor: defaultTokens.paletteWhite,
+    borderRadius: 40,
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: defaultTokens.paletteWhite,
+    shadowOpacity: 0.3,
+    shadowRadius: 2,
+    shadowColor: defaultTokens.paletteInkDark,
+    shadowOffset: { height: 0, width: 0 },
+    position: 'absolute',
+    bottom: 80,
+    end: 20,
+  },
+  icon: {
+    color: defaultTokens.paletteProductNormal,
+    fontSize: 20,
+  },
+});
diff --git a/app/MMB/src/scenes/tripServices/transportation/MarkerA.js b/app/MMB/src/scenes/tripServices/transportation/MarkerA.js
new file mode 100644
index 00000000..1d4b6331
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/MarkerA.js
@@ -0,0 +1,16 @@
+// @flow
+
+import * as React from 'react';
+
+import TransportationMapMarker from './TransportationMapMarker';
+
+type Props = {|
+  +coordinate: ?{|
+    +latitude: number,
+    +longitude: number,
+  |},
+|};
+
+export default function MarkerA({ coordinate }: Props) {
+  return <TransportationMapMarker coordinate={coordinate} code="j" />;
+}
diff --git a/app/MMB/src/scenes/tripServices/transportation/MarkerB.js b/app/MMB/src/scenes/tripServices/transportation/MarkerB.js
new file mode 100644
index 00000000..8191f930
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/MarkerB.js
@@ -0,0 +1,23 @@
+// @flow
+
+import * as React from 'react';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+
+import TransportationMapMarker from './TransportationMapMarker';
+
+type Props = {|
+  +coordinate: ?{|
+    +latitude: number,
+    +longitude: number,
+  |},
+|};
+
+export default function MarkerB(props: Props) {
+  return (
+    <TransportationMapMarker
+      coordinate={props.coordinate}
+      code="k"
+      color={defaultTokens.colorAlertIconWarning}
+    />
+  );
+}
diff --git a/app/MMB/src/scenes/tripServices/transportation/MarkerLocationButton.js b/app/MMB/src/scenes/tripServices/transportation/MarkerLocationButton.js
new file mode 100644
index 00000000..a651ce8c
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/MarkerLocationButton.js
@@ -0,0 +1,70 @@
+// @flow
+
+import * as React from 'react';
+import { View, TouchableWithoutFeedback } from 'react-native';
+import { Text, StyleSheet, TextIcon } from '@kiwicom/mobile-shared';
+import { Translation } from '@kiwicom/mobile-localization';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+
+type Props = {|
+  +onPress: () => Promise<void>,
+  +destination: ?string,
+|};
+
+export default function MarkerLocationButton(props: Props) {
+  return (
+    <View style={styles.container}>
+      <TouchableWithoutFeedback onPress={props.onPress}>
+        <View style={styles.locationButton}>
+          <TextIcon code="B" style={styles.icon} />
+          <Text
+            numberOfLines={1}
+            style={
+              props.destination ? styles.locationText : styles.placeholderText
+            }
+          >
+            {props.destination ? (
+              <Translation passThrough={props.destination} />
+            ) : (
+              <Translation id="mmb.trip_services.transportation.map.marker_location_button" />
+            )}
+          </Text>
+        </View>
+      </TouchableWithoutFeedback>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    backgroundColor: defaultTokens.paletteWhite,
+    padding: 10,
+  },
+  locationButton: {
+    flexDirection: 'row',
+    backgroundColor: defaultTokens.backgroundInputDisabled,
+    alignItems: 'center',
+    android: {
+      borderRadius: 2,
+      elevation: 1,
+      height: 48,
+    },
+    ios: {
+      height: 47,
+      borderRadius: 6,
+    },
+  },
+  placeholderText: {
+    color: defaultTokens.paletteInkLight,
+    flex: 1,
+  },
+  locationText: {
+    color: defaultTokens.paletteInkDark,
+    flex: 1,
+  },
+  icon: {
+    marginHorizontal: 10,
+    alignSelf: 'center',
+    color: defaultTokens.paletteInkLight,
+  },
+});
diff --git a/app/MMB/src/scenes/tripServices/transportation/TransportationMap.js b/app/MMB/src/scenes/tripServices/transportation/TransportationMap.js
new file mode 100644
index 00000000..60bcc992
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/TransportationMap.js
@@ -0,0 +1,364 @@
+// @flow strict
+
+import * as React from 'react';
+import { View, Linking } from 'react-native';
+import MapView from 'react-native-maps';
+import { HeaderBackButton } from 'react-navigation';
+import { StyleSheet } from '@kiwicom/mobile-shared';
+import {
+  HeaderButton,
+  HeaderTitle,
+  type NavigationType,
+} from '@kiwicom/mobile-navigation';
+import {
+  Translation,
+  Alert,
+  DeviceInfo,
+  DateFormatter,
+} from '@kiwicom/mobile-localization';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+import querystring from 'querystring';
+
+import { withGoogleMapsContext } from '../../../context/GoogleMapsContext';
+import MarkerLocationButton from './MarkerLocationButton';
+import CurrentPositionButton from './CurrentPositionButton';
+import AddressLocationLegend from './AddressLocationLegend';
+import MarkerA from './MarkerA';
+import MarkerB from './MarkerB';
+
+type State = {|
+  region: Region,
+  markers: Markers,
+  destination: string,
+  showUserLocation: boolean,
+  currentLocation: ?Region,
+|};
+
+type Region = {|
+  ...Coordinate,
+  +latitudeDelta: number,
+  +longitudeDelta: number,
+|};
+
+type Markers = {|
+  +markerA: ?Coordinate,
+  +markerB: ?Coordinate,
+|};
+
+type Coordinate = {|
+  +latitude: number,
+  +longitude: number,
+|};
+
+type NativeEvent = {|
+  +nativeEvent: {|
+    +coordinate: Coordinate,
+  |},
+|};
+
+type Props = {|
+  +navigation: NavigationType,
+  +googleMapsAPIKey: string,
+  +params: {|
+    +location: {| +lat: number, +lng: number |},
+    +whitelabelURL: string,
+    +date: ?string,
+  |},
+  +openLink: () => void,
+  +disabled: boolean,
+|};
+export class TransportationMap extends React.Component<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    this.state = {
+      region: {
+        latitude: props.params.location.lat,
+        longitude: props.params.location.lng,
+        latitudeDelta: 0.0375, // TODO fine tune these deltas
+        longitudeDelta: 0.0375, // TODO fine tune these deltas
+      },
+      markers: {
+        markerA: null,
+        markerB: null,
+      },
+      destination: '',
+      showUserLocation: false,
+      currentLocation: null,
+    };
+  }
+
+  static navigationOptions = (props: Props) => {
+    function goBack() {
+      props.navigation.goBack(null);
+    }
+
+    return {
+      headerLeft: (
+        <HeaderBackButton
+          tintColor={defaultTokens.paletteProductNormal}
+          onPress={goBack}
+        />
+      ),
+      headerTitle: (
+        <HeaderTitle>
+          <Translation id="mmb.trip_services.transportation.map.title" />
+        </HeaderTitle>
+      ),
+      headerRight: (
+        <HeaderButton.Right onPress={props.openLink} disabled={props.disabled}>
+          <HeaderButton.Text>
+            <Translation id="mmb.trip_services.transportation.map.right_button" />
+          </HeaderButton.Text>
+        </HeaderButton.Right>
+      ),
+      headerStyle: {
+        backgroundColor: defaultTokens.paletteWhite,
+        borderBottomWidth: 0,
+      },
+    };
+  };
+
+  componentDidMount() {
+    this.props.navigation.setParams({
+      disabled: true,
+      openLink: this.openLink,
+    });
+  }
+
+  componentDidUpdate() {
+    const { navigation } = this.props;
+    const { markerA, markerB } = this.state.markers;
+    if (
+      markerA != null &&
+      markerB != null &&
+      navigation.state.params.disabled
+    ) {
+      navigation.setParams({ disabled: false });
+    }
+  }
+
+  openLocationPicker = async () => {
+    const { currentLocation } = this.state;
+    let currentAddress;
+
+    if (currentLocation != null) {
+      const { latitude, longitude } = currentLocation;
+      currentAddress = await this.getFormattedAddress({
+        latitude,
+        longitude,
+      });
+    }
+
+    this.props.navigation.navigate('AddressPickerScreen', {
+      currentAddress,
+    });
+  };
+
+  openSettings = () => {
+    Linking.openURL('app-settings:');
+  };
+
+  alertOpenSettings = () => {
+    Alert.translatedAlert(
+      undefined,
+      {
+        id: 'mmb.trip_services.transportation.map.current_position_alert',
+      },
+      [
+        {
+          text: { id: 'mmb.alert.button.ok' },
+          undefined,
+          style: 'default',
+        },
+        {
+          text: { id: 'mmb.alert.button.settings' },
+          onPress: this.openSettings,
+          style: 'default',
+        },
+      ],
+    );
+  };
+
+  onRegionChange = (region: Region) => {
+    this.setState({ region });
+  };
+
+  buildWhitelabelURL = () => {
+    const { markerA, markerB } = this.state.markers;
+    const whitelabelURL = this.props.params.whitelabelURL;
+    if (markerA != null && markerB != null) {
+      let date;
+      let time = '';
+      if (this.props.params.date != null) {
+        const transportationDate = new Date(this.props.params.date);
+        time = DateFormatter(transportationDate).formatTimeForMachine();
+        date = DateFormatter(transportationDate).formatForMachine();
+      }
+
+      const pickup = `${markerA.latitude},${markerA.longitude}`;
+      const dropoff = `${markerB.latitude},${markerB.longitude}`;
+      const language = DeviceInfo.getLanguage() || 'en-gb';
+      const currency = 'EUR';
+      const query = querystring.stringify({
+        utm_source: 'kiwi',
+        utm_medium: 'startpart',
+        utm_campaign: 'mobileappconfpage',
+        pickup,
+        dropoff,
+        language,
+        currency,
+        date: date,
+      });
+
+      // querystring.stringify uses encodeURIComponent which turns HH:mm into HH%3Amm which rideways does not understand
+      return `${whitelabelURL}?${query}&time=${time}`;
+    }
+    return whitelabelURL;
+  };
+
+  openLink = () => {
+    const { markerA, markerB } = this.state.markers;
+    if (markerA != null && markerB != null) {
+      const url = this.buildWhitelabelURL();
+
+      this.props.navigation.navigate('mmb.trip_services.webview', {
+        url,
+      });
+    }
+  };
+
+  renderMarkerA = (e: NativeEvent) => {
+    const { latitude, longitude } = e.nativeEvent.coordinate;
+    this.setState(state => ({
+      markers: {
+        ...state.markers,
+        markerA: {
+          latitude,
+          longitude,
+        },
+      },
+    }));
+  };
+
+  renderMarkerB = (e: NativeEvent) => {
+    const { latitude, longitude } = e.nativeEvent.coordinate;
+    this.setDestination({ latitude, longitude });
+    this.setState(state => ({
+      markers: {
+        ...state.markers,
+        markerB: {
+          latitude,
+          longitude,
+        },
+      },
+    }));
+  };
+
+  getFormattedAddress = async ({ latitude, longitude }: Coordinate) => {
+    const address = await fetch(
+      `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${
+        this.props.googleMapsAPIKey
+      }`,
+    )
+      .then(res => res.json())
+      .then(json => json.results[0].address_components);
+
+    const city = (address.find(x => x.types.includes('locality')) || {})
+      .long_name;
+    const route = (address.find(x => x.types.includes('route')) || {})
+      .long_name;
+    const streetNumber = (
+      address.find(x => x.types.includes('street_number')) || {}
+    ).long_name;
+
+    const area = (
+      address.find(x => x.types.includes('administrative_area_level_1')) || {}
+    ).long_name;
+
+    const streetAddress =
+      route !== undefined && streetNumber !== undefined
+        ? route.concat(' ', streetNumber)
+        : route;
+
+    const formattedAddress = [city, streetAddress, area]
+      .filter(item => item !== undefined)
+      .join(', ');
+
+    return formattedAddress;
+  };
+
+  setDestination = async ({ latitude, longitude }: Coordinate) => {
+    const destination = await this.getFormattedAddress({ latitude, longitude });
+    this.setState({
+      destination,
+    });
+  };
+
+  showUserLocation = () => {
+    navigator.geolocation.getCurrentPosition(
+      position => {
+        const currentLocation = {
+          latitude: position.coords.latitude,
+          longitude: position.coords.longitude,
+          latitudeDelta: 0.0375,
+          longitudeDelta: 0.0375,
+        };
+        this.setState({
+          currentLocation,
+          region: currentLocation,
+          showUserLocation: true,
+        });
+      },
+      () => {
+        // TODO: Deal with Native Alert coming on top of this alert.
+        this.alertOpenSettings();
+      },
+    );
+  };
+
+  render() {
+    const { destination, showUserLocation, markers } = this.state;
+    const { markerA, markerB } = markers;
+
+    return (
+      <View style={styles.wrapper}>
+        <MarkerLocationButton
+          destination={destination}
+          onPress={this.openLocationPicker}
+        />
+        <View style={styles.wrapper}>
+          <MapView
+            region={this.state.region}
+            onRegionChangeComplete={this.onRegionChange}
+            onPress={this.renderMarkerB}
+            onLongPress={this.renderMarkerA}
+            scrollEnabled={true}
+            showsUserLocation={showUserLocation}
+            userLocationAnnotationTitle=""
+            style={styles.mapBottom}
+          >
+            <MarkerA coordinate={markerA} />
+            <MarkerB coordinate={markerB} />
+          </MapView>
+        </View>
+        <AddressLocationLegend />
+        <CurrentPositionButton onPress={this.showUserLocation} />
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  mapBottom: {
+    android: {
+      bottom: -25,
+    },
+    ...StyleSheet.absoluteFillObject,
+  },
+  wrapper: {
+    flex: 1,
+  },
+});
+
+export default withGoogleMapsContext(TransportationMap);
diff --git a/app/MMB/src/scenes/tripServices/transportation/TransportationMapMarker.js b/app/MMB/src/scenes/tripServices/transportation/TransportationMapMarker.js
new file mode 100644
index 00000000..611c84aa
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/TransportationMapMarker.js
@@ -0,0 +1,29 @@
+// @flow
+
+import * as React from 'react';
+import MapView from 'react-native-maps';
+import { PositionMarker } from '@kiwicom/mobile-shared';
+
+type Props = {|
+  +coordinate: ?{|
+    +latitude: number,
+    +longitude: number,
+  |},
+  +color?: string,
+  +code: string,
+|};
+
+export default function TransportationMapMarker({
+  coordinate,
+  color,
+  code,
+}: Props) {
+  if (coordinate) {
+    return (
+      <MapView.Marker coordinate={coordinate}>
+        <PositionMarker code={code} color={color} />
+      </MapView.Marker>
+    );
+  }
+  return null;
+}
diff --git a/app/MMB/src/scenes/tripServices/transportationMenuItem/TransportLocationItem.js b/app/MMB/src/scenes/tripServices/transportationMenuItem/TransportLocationItem.js
new file mode 100644
index 00000000..28b9830b
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportationMenuItem/TransportLocationItem.js
@@ -0,0 +1,65 @@
+// @flow strict
+
+import * as React from 'react';
+import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
+import { Alert } from '@kiwicom/mobile-localization';
+import idx from 'idx';
+
+import LocationPopupButton from '../LocationPopupButton';
+import type { LocationItem as LocationItemType } from './__generated__/TransportLocationItem.graphql';
+
+type Props = {|
+  +data: LocationItemType,
+  +onPress: ({
+    location: { lat: number, lng: number },
+    whitelabelURL: string,
+  }) => void,
+  +whitelabelURL: string,
+  +date?: ?string,
+  +displayDate: boolean,
+|};
+
+class LocationItem extends React.Component<Props> {
+  onPress = () => {
+    const lat = idx(this.props.data, _ => _.location.lat);
+    const lng = idx(this.props.data, _ => _.location.lng);
+    if (lat != null && lng != null) {
+      const whitelabelURL = this.props.whitelabelURL;
+      const date = this.props.date;
+      this.props.onPress({ location: { lat, lng }, whitelabelURL, date });
+    } else {
+      Alert.translatedAlert(null, { id: 'mmb.alert.something_went_wrong' });
+    }
+  };
+
+  render = () => {
+    const location = idx(this.props.data, _ => _.location);
+    if (!location) {
+      return null;
+    }
+
+    return (
+      <LocationPopupButton
+        whitelabelURL={this.props.whitelabelURL}
+        data={this.props.data}
+        onPress={this.onPress}
+        displayIata={false}
+        date={this.props.date}
+        displayDate={this.props.displayDate}
+      />
+    );
+  };
+}
+
+export default createFragmentContainer(
+  LocationItem,
+  graphql`
+    fragment TransportLocationItem on Location {
+      ...LocationPopupButton
+      location {
+        lat
+        lng
+      }
+    }
+  `,
+);
diff --git a/app/MMB/src/scenes/tripServices/TransportationMenuItem.js b/app/MMB/src/scenes/tripServices/transportationMenuItem/TransportationMenuItem.js
similarity index 55%
rename from app/MMB/src/scenes/tripServices/TransportationMenuItem.js
rename to app/MMB/src/scenes/tripServices/transportationMenuItem/TransportationMenuItem.js
index 4fdf2a16..a0d052db 100644
--- a/app/MMB/src/scenes/tripServices/TransportationMenuItem.js
+++ b/app/MMB/src/scenes/tripServices/transportationMenuItem/TransportationMenuItem.js
@@ -4,44 +4,69 @@ import * as React from 'react';
 import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
 import { TextIcon } from '@kiwicom/mobile-shared';
 import { Translation } from '@kiwicom/mobile-localization';
-import { MenuItem } from '@kiwicom/mobile-navigation';
+import {
+  MenuItem,
+  withNavigation,
+  type NavigationType,
+} from '@kiwicom/mobile-navigation';
 import idx from 'idx';
 
-import LocationsPopup from './LocationsPopup';
-import LocationPopupButton from './LocationPopupButton';
+import LocationsPopup from '../LocationsPopup';
+import TransportLocationItem from './TransportLocationItem';
 import type { TransportationMenuItem as TransportationMenuItemType } from './__generated__/TransportationMenuItem.graphql';
 
 type Props = {|
   +data: TransportationMenuItemType,
   +onOpenWebview: string => void,
+  +navigation: NavigationType,
 |};
 
 type State = {|
   isPopupVisible: boolean,
 |};
 
+type MapParams = {|
+  +location: {| +lat: ?number, +lng: ?number |},
+  +whitelabelURL: string,
+|};
+
 class TransportationMenuItem extends React.Component<Props, State> {
   state = {
     isPopupVisible: false,
   };
 
-  openTransportationLink = (whitelabelURL: string) => {
+  openTransportationMap = (params: MapParams) => {
     this.setState(
       {
         isPopupVisible: false,
       },
-      () => this.props.onOpenWebview(whitelabelURL),
+      () => this.props.navigation.navigate('TransportationMap', { params }),
     );
   };
 
   openPopup = () => {
     const relevantLocations =
       idx(this.props, _ => _.data.transportation.relevantLocations) || [];
-
     if (relevantLocations.length === 1) {
       // do not open the modal for only one whitelabel URL (open it directly)
-      const whitelabelURL = idx(relevantLocations, _ => _[0].whitelabelURL);
-      this.openTransportationLink(whitelabelURL || '');
+      const whitelabelURL =
+        idx(relevantLocations, _ => _[0].whitelabelURL) || '';
+
+      let location = idx(relevantLocations, _ => _[0].location.location);
+
+      if (location != null) {
+        this.openTransportationMap({ location, whitelabelURL });
+      } else {
+        navigator.geolocation.getCurrentPosition(position => {
+          this.openTransportationMap({
+            location: {
+              lat: position.coords.latitude,
+              lng: position.coords.longitude,
+            },
+            whitelabelURL,
+          });
+        });
+      }
       return;
     }
 
@@ -71,22 +96,32 @@ class TransportationMenuItem extends React.Component<Props, State> {
           isVisible={this.state.isPopupVisible}
           onClose={this.hidePopup}
         >
-          {relevantLocations.map((relevantLocation, index) => {
+          {relevantLocations.map(relevantLocation => {
             if (!relevantLocation) {
               return null;
             }
             const { whitelabelURL, location, date } = relevantLocation;
+            const currentCityName = idx(location, _ => _.city.name);
             return (
-              <LocationPopupButton
+              <TransportLocationItem
                 key={whitelabelURL}
                 data={location}
-                date={
-                  index > 0 && index < relevantLocations.length - 1
-                    ? null
-                    : date
+                date={date}
+                displayDate={
+                  relevantLocations.filter(_relevantLocation => {
+                    const cityName = idx(
+                      _relevantLocation,
+                      _ => _.location.city.name,
+                    );
+                    return (
+                      cityName &&
+                      currentCityName &&
+                      cityName === currentCityName
+                    );
+                  }).length !== 1
                 }
                 whitelabelURL={whitelabelURL}
-                onPress={this.openTransportationLink}
+                onPress={this.openTransportationMap}
                 displayIata={false}
               />
             );
@@ -106,14 +141,21 @@ class TransportationMenuItem extends React.Component<Props, State> {
 }
 
 export default createFragmentContainer(
-  TransportationMenuItem,
+  withNavigation(TransportationMenuItem),
   graphql`
     fragment TransportationMenuItem on WhitelabeledServices {
       transportation {
         relevantLocations {
           whitelabelURL
           location {
-            ...LocationPopupButton
+            ...TransportLocationItem
+            location {
+              lat
+              lng
+            }
+            city {
+              name
+            }
           }
           date
         }
diff --git a/app/MMB/src/scenes/tripServices/transportationMenuItem/__generated__/TransportLocationItem.graphql.js b/app/MMB/src/scenes/tripServices/transportationMenuItem/__generated__/TransportLocationItem.graphql.js
new file mode 100644
index 00000000..218f865d
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportationMenuItem/__generated__/TransportLocationItem.graphql.js
@@ -0,0 +1,66 @@
+/**
+ * @flow
+ */
+
+/* eslint-disable */
+
+'use strict';
+
+/*::
+import type { ConcreteFragment } from 'relay-runtime';
+type LocationPopupButton$ref = any;
+import type { FragmentReference } from "relay-runtime";
+declare export opaque type TransportLocationItem$ref: FragmentReference;
+export type TransportLocationItem = {|
+  +location: ?{|
+    +lat: ?number,
+    +lng: ?number,
+  |},
+  +$fragmentRefs: LocationPopupButton$ref,
+  +$refType: TransportLocationItem$ref,
+|};
+*/
+
+
+const node/*: ConcreteFragment*/ = {
+  "kind": "Fragment",
+  "name": "TransportLocationItem",
+  "type": "Location",
+  "metadata": null,
+  "argumentDefinitions": [],
+  "selections": [
+    {
+      "kind": "FragmentSpread",
+      "name": "LocationPopupButton",
+      "args": null
+    },
+    {
+      "kind": "LinkedField",
+      "alias": null,
+      "name": "location",
+      "storageKey": null,
+      "args": null,
+      "concreteType": "Coordinates",
+      "plural": false,
+      "selections": [
+        {
+          "kind": "ScalarField",
+          "alias": null,
+          "name": "lat",
+          "args": null,
+          "storageKey": null
+        },
+        {
+          "kind": "ScalarField",
+          "alias": null,
+          "name": "lng",
+          "args": null,
+          "storageKey": null
+        }
+      ]
+    }
+  ]
+};
+// prettier-ignore
+(node/*: any*/).hash = 'bf137d98958b06eba8a722734618896a';
+module.exports = node;
diff --git a/app/MMB/src/scenes/tripServices/__generated__/TransportationMenuItem.graphql.js b/app/MMB/src/scenes/tripServices/transportationMenuItem/__generated__/TransportationMenuItem.graphql.js
similarity index 54%
rename from app/MMB/src/scenes/tripServices/__generated__/TransportationMenuItem.graphql.js
rename to app/MMB/src/scenes/tripServices/transportationMenuItem/__generated__/TransportationMenuItem.graphql.js
index 8555efbb..e814432c 100644
--- a/app/MMB/src/scenes/tripServices/__generated__/TransportationMenuItem.graphql.js
+++ b/app/MMB/src/scenes/tripServices/transportationMenuItem/__generated__/TransportationMenuItem.graphql.js
@@ -8,7 +8,7 @@
 
 /*::
 import type { ConcreteFragment } from 'relay-runtime';
-type LocationPopupButton$ref = any;
+type TransportLocationItem$ref = any;
 import type { FragmentReference } from "relay-runtime";
 declare export opaque type TransportationMenuItem$ref: FragmentReference;
 export type TransportationMenuItem = {|
@@ -16,7 +16,14 @@ export type TransportationMenuItem = {|
     +relevantLocations: ?$ReadOnlyArray<?{|
       +whitelabelURL: ?string,
       +location: ?{|
-        +$fragmentRefs: LocationPopupButton$ref
+        +location: ?{|
+          +lat: ?number,
+          +lng: ?number,
+        |},
+        +city: ?{|
+          +name: ?string
+        |},
+        +$fragmentRefs: TransportLocationItem$ref,
       |},
       +date: ?any,
     |}>
@@ -69,8 +76,51 @@ const node/*: ConcreteFragment*/ = {
               "selections": [
                 {
                   "kind": "FragmentSpread",
-                  "name": "LocationPopupButton",
+                  "name": "TransportLocationItem",
                   "args": null
+                },
+                {
+                  "kind": "LinkedField",
+                  "alias": null,
+                  "name": "location",
+                  "storageKey": null,
+                  "args": null,
+                  "concreteType": "Coordinates",
+                  "plural": false,
+                  "selections": [
+                    {
+                      "kind": "ScalarField",
+                      "alias": null,
+                      "name": "lat",
+                      "args": null,
+                      "storageKey": null
+                    },
+                    {
+                      "kind": "ScalarField",
+                      "alias": null,
+                      "name": "lng",
+                      "args": null,
+                      "storageKey": null
+                    }
+                  ]
+                },
+                {
+                  "kind": "LinkedField",
+                  "alias": null,
+                  "name": "city",
+                  "storageKey": null,
+                  "args": null,
+                  "concreteType": "LocationArea",
+                  "plural": false,
+                  "selections": [
+                    {
+                      "kind": "ScalarField",
+                      "alias": null,
+                      "name": "name",
+                      "args": null,
+                      "storageKey": null
+                    }
+                  ]
                 }
               ]
             },
@@ -88,5 +138,5 @@ const node/*: ConcreteFragment*/ = {
   ]
 };
 // prettier-ignore
-(node/*: any*/).hash = 'ca50877455fd4c1e03885ed32bd28e8f';
+(node/*: any*/).hash = 'f8796db8c8a942834c038508976b30f6';
 module.exports = node;
diff --git a/app/core/config/application.js b/app/core/config/application.js
index 974308c8..6319e890 100644
--- a/app/core/config/application.js
+++ b/app/core/config/application.js
@@ -2,6 +2,7 @@
 
 import {
   AFFILIATE_BOOKINGCOM,
+  API_KEY_GOOGLE_MAPS,
   AUTH_ANDROID_GOOGLE_CLIENTID,
   AUTH_IOS_GOOGLE_CLIENTID,
   AUTH_KIWI_BACKEND,
@@ -15,6 +16,9 @@ export default {
   animations: {
     duration: 100, // ms
   },
+  apiKey: {
+    googleMaps: sanitize(API_KEY_GOOGLE_MAPS),
+  },
   auth: {
     android: {
       google: {
diff --git a/app/core/src/screens/bookingsStack/MMBPackageWrapper.js b/app/core/src/screens/bookingsStack/MMBPackageWrapper.js
index b605e709..91447952 100644
--- a/app/core/src/screens/bookingsStack/MMBPackageWrapper.js
+++ b/app/core/src/screens/bookingsStack/MMBPackageWrapper.js
@@ -85,6 +85,7 @@ export default class MMBPackageWrapper extends React.Component<Props, State> {
           version="rn-development"
           bookingComAffiliate={String(Config.affiliate.bookingCom)}
           dataSaverEnabled={false}
+          googleMapsAPIKey={String(Config.apiKey.googleMaps)}
         />
       );
     }
@@ -99,6 +100,7 @@ export default class MMBPackageWrapper extends React.Component<Props, State> {
           version="rn-development"
           bookingComAffiliate={String(Config.affiliate.bookingCom)}
           dataSaverEnabled={false}
+          googleMapsAPIKey={String(Config.apiKey.googleMaps)}
         />
       );
     }
diff --git a/packages/localization/src/DateFormatter.js b/packages/localization/src/DateFormatter.js
index 087cd7b1..cfd38b44 100644
--- a/packages/localization/src/DateFormatter.js
+++ b/packages/localization/src/DateFormatter.js
@@ -94,6 +94,13 @@ function DateFormatter(rawDate: Date = DateUtils.getUTCNow()) {
       );
     },
 
+    /**
+     * Always returns HH:mm at this moment.
+     */
+    formatTimeForMachine: () => {
+      return pad(rawDate.getUTCHours()) + ':' + pad(rawDate.getUTCMinutes());
+    },
+
     // Pass in your own configuration
     formatCustom: (config: FormatterConfig) => custom(rawDate, config),
   };
diff --git a/packages/localization/src/vocabularies/ManageMyBooking.js b/packages/localization/src/vocabularies/ManageMyBooking.js
index 2f7abfd8..44df2305 100644
--- a/packages/localization/src/vocabularies/ManageMyBooking.js
+++ b/packages/localization/src/vocabularies/ManageMyBooking.js
@@ -27,6 +27,11 @@ export default {
   'mmb.fill_travel_document_screen.title': 'Fill travel document details',
   'mmb.fill_travel_document_screen_modal.title': 'Fill travel document details',
 
+  'mmb.alert.button.settings': 'Settings',
+  'mmb.alert.button.ok': 'OK',
+  'mmb.alert.something_went_wrong':
+    'Something went wrong; please try again later.',
+
   // MMB Boarding passes
   'mmb.boarding_passes.boarding_pass': 'Boarding Pass',
   'mmb.boarding_passes.boarding_pass_return.departure': 'Departure',
@@ -238,6 +243,22 @@ export default {
   'mmb.trip_services.insurance.payment.remember_card':
     'Remember this card for next time',
 
+  // MMB Transportation
+  'mmb.trip_services.transportation.map.title': 'Get transportation',
+  'mmb.trip_services.transportation.map.right_button': 'Book ride',
+  'mmb.trip_services.transportation.map.marker_location_button':
+    'Tap to select location or start writing add...',
+  'mmb.trip_services.transportation.map.current_position_alert':
+    'Location services are not available. You can enable them in settings.',
+  'mmb.trip_services.transportation.address_picker.cancel_button': 'Cancel',
+  'mmb.trip_services.transportation.address_picker.input.placeholder': 'To: ',
+  'mmb.trip_service.transportation.map.legend.long_tap': 'Long tap =',
+  'mmb.trip_service.transportation.map.legend.tap': 'Tap =',
+  'mmb.trip_service.transportation.map.legend.markerA': 'from',
+  'mmb.trip_service.transportation.map.legend.markerB': 'to',
+  'mmb.trip_service.transportation.address_picker.current_location_title':
+    'Your current location',
+
   // MMB Support
   'mmb.support.phone.suggested': 'Suggested',
   'mmb.support.phone.all_numbers': 'All Numbers',
diff --git a/packages/navigation/types/Navigation.js b/packages/navigation/types/Navigation.js
index 0efb4545..130fb717 100644
--- a/packages/navigation/types/Navigation.js
+++ b/packages/navigation/types/Navigation.js
@@ -48,7 +48,9 @@ type MMBStackNavigatorNames =
   | 'TravelDocumentModalScreen'
   | 'AppleWalletScreen'
   | 'MMBHotelsStack'
-  | 'MMBMainSwitchStack';
+  | 'MMBMainSwitchStack'
+  | 'AddressPickerScreen'
+  | 'TransportationMap';
 
 type HomepageStackNavigatorRouteNames = 'Home';
 type PlaygroundNavigationRouteNames = 'Playground';
diff --git a/packages/shared/index.js b/packages/shared/index.js
index 226060ca..3972c23c 100644
--- a/packages/shared/index.js
+++ b/packages/shared/index.js
@@ -38,6 +38,7 @@ export { default as Modal } from './src/Modal';
 
 export { default as AdaptableBadge } from './src/AdaptableBadge';
 export { default as DropMarker } from './src/DropMarker';
+export { default as PositionMarker } from './src/PositionMarker';
 export { default as Price } from './src/Price';
 export { default as Stars } from './src/Stars';
 export { default as Text } from './src/Text';
diff --git a/packages/shared/src/DropMarker.js b/packages/shared/src/DropMarker.js
index c6c2f598..fc6927ab 100644
--- a/packages/shared/src/DropMarker.js
+++ b/packages/shared/src/DropMarker.js
@@ -8,6 +8,7 @@ import Icon from './icons/Icon';
 
 type Props = {|
   +size?: number,
+  +color?: string,
 |};
 
 /**
@@ -24,16 +25,12 @@ type Props = {|
  *   |       |
  *   `-------`
  */
-export default function DropMarker({ size = 50 }: Props) {
+export default function DropMarker({
+  size = 50,
+  color = defaultTokens.paletteProductNormal,
+}: Props) {
   const styles = createStyles(size);
-  return (
-    <Icon
-      name="place"
-      size={size}
-      color={defaultTokens.paletteProductNormal}
-      style={styles.icon}
-    />
-  );
+  return <Icon name="place" size={size} color={color} style={styles.icon} />;
 }
 
 const createStyles = (size: number) =>
diff --git a/packages/shared/src/PositionMarker.js b/packages/shared/src/PositionMarker.js
new file mode 100644
index 00000000..50e34f90
--- /dev/null
+++ b/packages/shared/src/PositionMarker.js
@@ -0,0 +1,39 @@
+// @flow
+
+import * as React from 'react';
+import { defaultTokens } from '@kiwicom/mobile-orbit';
+
+import StyleSheet from './PlatformStyleSheet';
+import TextIcon from './icons/TextIcon';
+
+type Props = {|
+  size?: number,
+  color?: string,
+  code?: string,
+|};
+
+export default class PositionMarker extends React.Component<Props> {
+  createStyles = (
+    size: number = 25,
+    color: string = defaultTokens.paletteProductNormal,
+  ) =>
+    StyleSheet.create({
+      icon: {
+        color: color,
+        fontSize: size,
+        ios: {
+          position: 'absolute',
+          left: -size / 2,
+          top: -size,
+        },
+        android: {},
+      },
+    });
+
+  render() {
+    const { size, code, color } = this.props;
+
+    const styles = this.createStyles(size, color);
+    return <TextIcon code={code || 'B'} style={styles.icon} />;
+  }
+}
diff --git a/schema.graphql b/schema.graphql
index 2d91cf10..ce2ace56 100644
--- a/schema.graphql
+++ b/schema.graphql
@@ -96,6 +96,13 @@ type Baggage {
   width: Int
   weight: Int
   note: String
+  category: BaggageCategory
+}
+
+enum BaggageCategory {
+  CHECKED
+  PERSONAL_ITEM
+  CABIN_BAG
 }
 
 type BoardingPass {
@@ -186,6 +193,7 @@ type Booking {
   """Date of the booking creation"""
   bookingDate: Date
   allowedBaggage: AllowedBaggage
+  unstable_baggage: [BookingBaggage]
 
   """Static assets related to this booking."""
   assets: BookingAssets
@@ -233,6 +241,12 @@ type BookingAssets {
   boardingPasses: [BoardingPass]
 }
 
+type BookingBaggage {
+  quantity: Int
+  passengers: [Passenger]
+  bag: Baggage
+}
+
 type BookingConfirmedTimelineEvent implements TimelineEvent {
   """Time of appearance in the timeline."""
   timestamp: DateTime
@@ -289,6 +303,7 @@ interface BookingInterface {
   """Date of the booking creation"""
   bookingDate: Date
   allowedBaggage: AllowedBaggage
+  unstable_baggage: [BookingBaggage]
 
   """Static assets related to this booking."""
   assets: BookingAssets
@@ -353,6 +368,7 @@ type BookingMulticity implements BookingInterface & Node {
   """Date of the booking creation"""
   bookingDate: Date
   allowedBaggage: AllowedBaggage
+  unstable_baggage: [BookingBaggage]
 
   """Static assets related to this booking."""
   assets: BookingAssets
@@ -411,6 +427,7 @@ type BookingOneWay implements BookingInterface & Node {
   """Date of the booking creation"""
   bookingDate: Date
   allowedBaggage: AllowedBaggage
+  unstable_baggage: [BookingBaggage]
 
   """Static assets related to this booking."""
   assets: BookingAssets
@@ -459,6 +476,7 @@ type BookingReturn implements BookingInterface & Node {
   """Date of the booking creation"""
   bookingDate: Date
   allowedBaggage: AllowedBaggage
+  unstable_baggage: [BookingBaggage]
 
   """Static assets related to this booking."""
   assets: BookingAssets


Test patch:
diff --git a/app/MMB/src/scenes/tickets/__tests__/TicketDeleteButton.test.js b/app/MMB/src/scenes/tickets/__tests__/TicketDeleteButton.test.js
index 22fe081f..73e771cc 100644
--- a/app/MMB/src/scenes/tickets/__tests__/TicketDeleteButton.test.js
+++ b/app/MMB/src/scenes/tickets/__tests__/TicketDeleteButton.test.js
@@ -2,11 +2,16 @@
 
 import * as React from 'react';
 import renderer from 'react-test-renderer';
+import RNFetchBlob from 'react-native-fetch-blob';
 
 import { TicketDeleteButton } from '../TicketDeleteButton';
 
 jest.mock('react-native-fetch-blob', () => {
+  let lsReturn = [];
   return {
+    setLsReturn: input => {
+      lsReturn = input;
+    },
     config: () => ({
       fetch: async () => ({
         path: () => {},
@@ -18,6 +23,8 @@ jest.mock('react-native-fetch-blob', () => {
         DocumentDir: 'ait',
       },
       exists: async () => true,
+      unlink: async () => true,
+      ls: async () => lsReturn,
     },
   };
 });
@@ -73,4 +80,37 @@ describe('TicketDeleteButton', () => {
     Component.componentDidUpdate({ isFocused: true, bookingId: 123456 });
     expect(spy).not.toHaveBeenCalled();
   });
+
+  describe('hasLocalBoardingPasses', () => {
+    it('should return false if no files has bookingId in filename', async () => {
+      const Component = new TicketDeleteButton({
+        bookingId: 123456,
+        isFocused: true,
+      });
+
+      RNFetchBlob.setLsReturn([
+        'test-bookingId:123.pdf',
+        'lol-bookingId:321.pdf',
+      ]);
+
+      const hasLocalBoardingPasses = await Component.hasLocalBoardingPasses();
+      expect(hasLocalBoardingPasses).toEqual(false);
+    });
+
+    it('should return true if any file has bookingId in filename', async () => {
+      const Component = new TicketDeleteButton({
+        bookingId: 123456,
+        isFocused: true,
+      });
+
+      RNFetchBlob.setLsReturn([
+        'test-bookingId:123.pdf',
+        'lol-bookingId:321.pdf',
+        'lol-bookingId:123456.pdf',
+      ]);
+
+      const hasLocalBoardingPasses = await Component.hasLocalBoardingPasses();
+      expect(hasLocalBoardingPasses).toEqual(true);
+    });
+  });
 });
diff --git a/app/MMB/src/scenes/tripServices/transportation/__tests__/TransportationMap.test.js b/app/MMB/src/scenes/tripServices/transportation/__tests__/TransportationMap.test.js
new file mode 100644
index 00000000..d35047e8
--- /dev/null
+++ b/app/MMB/src/scenes/tripServices/transportation/__tests__/TransportationMap.test.js
@@ -0,0 +1,165 @@
+//@flow
+
+import { TransportationMap } from '../TransportationMap';
+
+describe('TransportationMap', () => {
+  test('buildWhitelabelURL: it returns the whitelabelURL unchanged when neither markers have been placed', () => {
+    // $FlowExpectedError: passing just necessary props
+    const transportationMap = new TransportationMap({
+      params: {
+        location: { lat: 50, lng: 14 },
+        whitelabelURL: 'https://some.url.without.params/',
+        date: new Date('2018-08-30T14:40:00'),
+      },
+    });
+
+    expect(transportationMap.buildWhitelabelURL()).toBe(
+      'https://some.url.without.params/',
+    );
+  });
+  test('buildWhitelabelURL: it returns the whitelabelURL with correct params when both markers have been placed', () => {
+    // $FlowExpectedError: passing just necessary props
+    const transportationMap = new TransportationMap({
+      params: {
+        location: { lat: 50, lng: 14 },
+        whitelabelURL: 'https://some.url.without.params/',
+        date: new Date('2018-08-30T14:40:00+00:00'),
+      },
+    });
+
+    transportationMap.state = {
+      ...transportationMap.state,
+      markers: {
+        ...transportationMap.state.markers,
+        markerA: {
+          latitude: 51,
+          longitude: 16,
+        },
+        markerB: {
+          latitude: 51.5,
+          longitude: 15.5,
+        },
+      },
+    };
+
+    expect(transportationMap.buildWhitelabelURL()).toBe(
+      'https://some.url.without.params/?utm_source=kiwi&utm_medium=startpart&utm_campaign=mobileappconfpage&pickup=51%2C16&dropoff=51.5%2C15.5&language=en&currency=EUR&date=2018-08-30&time=14:40',
+    );
+  });
+  test('buildWhitelabelURL: it returns the whitelabelURL with correct params when both markers have been placed (without date)', () => {
+    // $FlowExpectedError: passing just necessary props
+    const transportationMap = new TransportationMap({
+      params: {
+        location: { lat: 50, lng: 14 },
+        whitelabelURL: 'https://some.url.without.params/',
+        date: null,
+      },
+    });
+
+    transportationMap.state = {
+      ...transportationMap.state,
+      markers: {
+        ...transportationMap.state.markers,
+        markerA: {
+          latitude: 51,
+          longitude: 16,
+        },
+        markerB: {
+          latitude: 51.5,
+          longitude: 15.5,
+        },
+      },
+    };
+
+    expect(transportationMap.buildWhitelabelURL()).toBe(
+      'https://some.url.without.params/?utm_source=kiwi&utm_medium=startpart&utm_campaign=mobileappconfpage&pickup=51%2C16&dropoff=51.5%2C15.5&language=en&currency=EUR&date=&time=',
+    );
+  });
+
+  test("componentDidUpdate: it should not set 'disabled' to false if either marker is still not placed", () => {
+    const navigation = {
+      navigate: jest.fn(),
+      setParams: jest.fn(),
+      state: {
+        params: { disabled: true },
+      },
+    };
+    // $FlowExpectedError: passing just necessary props
+    const transportationMap = new TransportationMap({
+      navigation,
+      params: {
+        location: { lat: 50, lng: 14 },
+        whitelabelURL: 'https://some.url.without.params/',
+        date: new Date('2018-08-30T14:40:00'),
+      },
+    });
+
+    const spy = jest.spyOn(navigation, 'setParams');
+
+    transportationMap.componentDidUpdate();
+    expect(spy).not.toHaveBeenCalled();
+  });
+
+  test("componentDidUpdate: it should not set 'disabled' to false if 'disabled' is already false", () => {
+    const navigation = {
+      navigate: jest.fn(),
+      setParams: jest.fn(),
+      state: {
+        params: { disabled: false },
+      },
+    };
+    // $FlowExpectedError: passing just necessary props
+    const transportationMap = new TransportationMap({
+      navigation,
+      params: {
+        location: { lat: 50, lng: 14 },
+        whitelabelURL: 'https://some.url.without.params/',
+        date: new Date('2018-08-30T14:40:00'),
+      },
+    });
+
+    const spy = jest.spyOn(navigation, 'setParams');
+
+    transportationMap.componentDidUpdate();
+    expect(spy).not.toHaveBeenCalled();
+  });
+
+  test("componentDidUpdate: it should set 'disabled' to true if both markers are placed and 'disabled' is still true", () => {
+    const navigation = {
+      navigate: jest.fn(),
+      setParams: jest.fn(),
+      state: {
+        params: { disabled: true },
+      },
+    };
+    // $FlowExpectedError: passing just necessary props
+    const transportationMap = new TransportationMap({
+      navigation,
+      params: {
+        location: { lat: 50, lng: 14 },
+        whitelabelURL: 'https://some.url.without.params/',
+        date: new Date('2018-08-30T14:40:00'),
+      },
+    });
+
+    transportationMap.state = {
+      ...transportationMap.state,
+      markers: {
+        ...transportationMap.state.markers,
+        markerA: {
+          latitude: 51,
+          longitude: 16,
+        },
+        markerB: {
+          latitude: 51.5,
+          longitude: 15.5,
+        },
+      },
+    };
+
+    const spy = jest.spyOn(navigation, 'setParams');
+
+    transportationMap.componentDidUpdate();
+    expect(spy).toHaveBeenCalledWith({ disabled: false });
+  });
+});