Repository: kiwicom/mobile
Pull request #904 (2018-07-23 11:06:40)

fix: Nested routes object is optional


Patch:
diff --git a/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js b/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js
index b65540a2..48f82e74 100644
--- a/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js
+++ b/app/MMB/src/scenes/tickets/__generated__/TicketRefetchQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash eac9ee82045c76563965e57847337f5d
+ * @relayHash 456871a5d17ba273cc932f287c337fdf
  */
 
 /* eslint-disable */
@@ -115,6 +115,7 @@ fragment FlightFromTo on Leg {
   }
   boardingPass {
     ...DownloadButton
+    ...AppleWallet
   }
 }
 
@@ -123,6 +124,22 @@ fragment DownloadButton on BoardingPass {
   ...BoardingPassInformation
 }
 
+fragment AppleWallet on BoardingPass {
+  pkpasses {
+    ...AppleWalletPassenger
+    passenger {
+      databaseId
+    }
+  }
+}
+
+fragment AppleWalletPassenger on Pkpass {
+  url
+  passenger {
+    fullName
+  }
+}
+
 fragment BoardingPassInformation on BoardingPass {
   availableAt
   boardingPassUrl
@@ -252,6 +269,49 @@ v5 = {
           "name": "availableAt",
           "args": null,
           "storageKey": null
+        },
+        {
+          "kind": "LinkedField",
+          "alias": null,
+          "name": "pkpasses",
+          "storageKey": null,
+          "args": null,
+          "concreteType": "Pkpass",
+          "plural": true,
+          "selections": [
+            {
+              "kind": "ScalarField",
+              "alias": null,
+              "name": "url",
+              "args": null,
+              "storageKey": null
+            },
+            {
+              "kind": "LinkedField",
+              "alias": null,
+              "name": "passenger",
+              "storageKey": null,
+              "args": null,
+              "concreteType": "Passenger",
+              "plural": false,
+              "selections": [
+                {
+                  "kind": "ScalarField",
+                  "alias": null,
+                  "name": "fullName",
+                  "args": null,
+                  "storageKey": null
+                },
+                {
+                  "kind": "ScalarField",
+                  "alias": null,
+                  "name": "databaseId",
+                  "args": null,
+                  "storageKey": null
+                }
+              ]
+            }
+          ]
         }
       ]
     }
@@ -265,7 +325,7 @@ return {
   "operationKind": "query",
   "name": "TicketRefetchQuery",
   "id": null,
-  "text": "query TicketRefetchQuery(\n  $id: ID!\n) {\n  node(id: $id) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n  ...FutureBookingInformation\n}\n\nfragment FutureBookingInformation on BoardingPass {\n  boardingPassUrl\n}\n",
+  "text": "query TicketRefetchQuery(\n  $id: ID!\n) {\n  node(id: $id) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n    ...AppleWallet\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment AppleWallet on BoardingPass {\n  pkpasses {\n    ...AppleWalletPassenger\n    passenger {\n      databaseId\n    }\n  }\n}\n\nfragment AppleWalletPassenger on Pkpass {\n  url\n  passenger {\n    fullName\n  }\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n  ...FutureBookingInformation\n}\n\nfragment FutureBookingInformation on BoardingPass {\n  boardingPassUrl\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
diff --git a/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js b/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js
index fa0bb782..f851453c 100644
--- a/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js
+++ b/app/MMB/src/scenes/tickets/__generated__/TicketSceneQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash 104673d1b873f5cde374acbbcf33630f
+ * @relayHash 1db9a3db35800bab9ad8ad6ac8c7f428
  */
 
 /* eslint-disable */
@@ -115,6 +115,7 @@ fragment FlightFromTo on Leg {
   }
   boardingPass {
     ...DownloadButton
+    ...AppleWallet
   }
 }
 
@@ -123,6 +124,22 @@ fragment DownloadButton on BoardingPass {
   ...BoardingPassInformation
 }
 
+fragment AppleWallet on BoardingPass {
+  pkpasses {
+    ...AppleWalletPassenger
+    passenger {
+      databaseId
+    }
+  }
+}
+
+fragment AppleWalletPassenger on Pkpass {
+  url
+  passenger {
+    fullName
+  }
+}
+
 fragment BoardingPassInformation on BoardingPass {
   availableAt
   boardingPassUrl
@@ -252,6 +269,49 @@ v5 = {
           "name": "availableAt",
           "args": null,
           "storageKey": null
+        },
+        {
+          "kind": "LinkedField",
+          "alias": null,
+          "name": "pkpasses",
+          "storageKey": null,
+          "args": null,
+          "concreteType": "Pkpass",
+          "plural": true,
+          "selections": [
+            {
+              "kind": "ScalarField",
+              "alias": null,
+              "name": "url",
+              "args": null,
+              "storageKey": null
+            },
+            {
+              "kind": "LinkedField",
+              "alias": null,
+              "name": "passenger",
+              "storageKey": null,
+              "args": null,
+              "concreteType": "Passenger",
+              "plural": false,
+              "selections": [
+                {
+                  "kind": "ScalarField",
+                  "alias": null,
+                  "name": "fullName",
+                  "args": null,
+                  "storageKey": null
+                },
+                {
+                  "kind": "ScalarField",
+                  "alias": null,
+                  "name": "databaseId",
+                  "args": null,
+                  "storageKey": null
+                }
+              ]
+            }
+          ]
         }
       ]
     }
@@ -265,7 +325,7 @@ return {
   "operationKind": "query",
   "name": "TicketSceneQuery",
   "id": null,
-  "text": "query TicketSceneQuery(\n  $bookingId: ID!\n) {\n  node(id: $bookingId) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n  ...FutureBookingInformation\n}\n\nfragment FutureBookingInformation on BoardingPass {\n  boardingPassUrl\n}\n",
+  "text": "query TicketSceneQuery(\n  $bookingId: ID!\n) {\n  node(id: $bookingId) {\n    __typename\n    ... on BookingInterface {\n      ...TicketRefetch\n    }\n    id\n  }\n}\n\nfragment TicketRefetch on BookingInterface {\n  id\n  ...BoardingPasses\n  assets {\n    ...ETicket\n  }\n}\n\nfragment BoardingPasses on Node {\n  __typename\n  ... on BookingReturn {\n    ...BoardingPassReturn\n  }\n  ... on BookingOneWay {\n    ...BoardingPassOneWay\n  }\n  ... on BookingMulticity {\n    ...BoardingPassMultiCity\n  }\n}\n\nfragment ETicket on BookingAssets {\n  ticketUrl\n}\n\nfragment BoardingPassReturn on BookingReturn {\n  outbound {\n    ...FlightSegments\n  }\n  inbound {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassOneWay on BookingOneWay {\n  trip {\n    ...FlightSegments\n  }\n}\n\nfragment BoardingPassMultiCity on BookingMulticity {\n  trips {\n    arrival {\n      airport {\n        city {\n          name\n        }\n        id\n      }\n    }\n    ...FlightSegments\n  }\n}\n\nfragment FlightSegments on Trip {\n  legs {\n    id\n    ...FlightFromTo\n  }\n}\n\nfragment FlightFromTo on Leg {\n  departure {\n    localTime\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  arrival {\n    airport {\n      city {\n        name\n      }\n      id\n    }\n  }\n  boardingPass {\n    ...DownloadButton\n    ...AppleWallet\n  }\n}\n\nfragment DownloadButton on BoardingPass {\n  boardingPassUrl\n  ...BoardingPassInformation\n}\n\nfragment AppleWallet on BoardingPass {\n  pkpasses {\n    ...AppleWalletPassenger\n    passenger {\n      databaseId\n    }\n  }\n}\n\nfragment AppleWalletPassenger on Pkpass {\n  url\n  passenger {\n    fullName\n  }\n}\n\nfragment BoardingPassInformation on BoardingPass {\n  availableAt\n  boardingPassUrl\n  ...FutureBookingInformation\n}\n\nfragment FutureBookingInformation on BoardingPass {\n  boardingPassUrl\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/FlightFromTo.js b/app/MMB/src/scenes/tickets/boardingPasses/FlightFromTo.js
index 84ac1df1..56dfa3e5 100644
--- a/app/MMB/src/scenes/tickets/boardingPasses/FlightFromTo.js
+++ b/app/MMB/src/scenes/tickets/boardingPasses/FlightFromTo.js
@@ -1,12 +1,13 @@
 // @flow strict
 
 import * as React from 'react';
-import { View } from 'react-native';
+import { View, Platform } from 'react-native';
 import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
 import { Translation, DateFormatter } from '@kiwicom/mobile-localization';
 import { TextIcon, StyleSheet, Color, Text } from '@kiwicom/mobile-shared';
 import idx from 'idx';
 
+import AppleWallet from './appleWallet/AppleWallet';
 import DownloadButton from './DownloadButton';
 import type { FlightFromTo as RouteStopType } from './__generated__/FlightFromTo.graphql';
 
@@ -14,7 +15,7 @@ type Props = {|
   +data: RouteStopType,
 |};
 
-const FlightFromTo = (props: Props) => {
+export const FlightFromTo = (props: Props) => {
   const date = idx(props.data, _ => _.departure.localTime);
   const shortDate = date
     ? DateFormatter(new Date(date)).formatToShortDate()
@@ -47,6 +48,11 @@ const FlightFromTo = (props: Props) => {
         <View style={styles.buttonContainer}>
           <DownloadButton data={idx(props.data, _ => _.boardingPass)} />
         </View>
+        {Platform.OS === 'ios' && (
+          <View style={styles.appleWalletContainer}>
+            <AppleWallet data={idx(props.data, _ => _.boardingPass)} />
+          </View>
+        )}
       </View>
     </View>
   );
@@ -72,6 +78,7 @@ export default createFragmentContainer(
       }
       boardingPass {
         ...DownloadButton
+        ...AppleWallet
       }
     }
   `,
@@ -109,4 +116,7 @@ const styles = StyleSheet.create({
     marginEnd: 9,
     flex: 1,
   },
+  appleWalletContainer: {
+    marginTop: 20,
+  },
 });
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/__generated__/FlightFromTo.graphql.js b/app/MMB/src/scenes/tickets/boardingPasses/__generated__/FlightFromTo.graphql.js
index 683b8fb0..173855c7 100644
--- a/app/MMB/src/scenes/tickets/boardingPasses/__generated__/FlightFromTo.graphql.js
+++ b/app/MMB/src/scenes/tickets/boardingPasses/__generated__/FlightFromTo.graphql.js
@@ -8,6 +8,7 @@
 
 /*::
 import type { ConcreteFragment } from 'relay-runtime';
+type AppleWallet$ref = any;
 type DownloadButton$ref = any;
 import type { FragmentReference } from "relay-runtime";
 declare export opaque type FlightFromTo$ref: FragmentReference;
@@ -28,7 +29,7 @@ export type FlightFromTo = {|
     |}
   |},
   +boardingPass: ?{|
-    +$fragmentRefs: DownloadButton$ref
+    +$fragmentRefs: DownloadButton$ref & AppleWallet$ref
   |},
   +$refType: FlightFromTo$ref,
 |};
@@ -116,6 +117,11 @@ return {
           "kind": "FragmentSpread",
           "name": "DownloadButton",
           "args": null
+        },
+        {
+          "kind": "FragmentSpread",
+          "name": "AppleWallet",
+          "args": null
         }
       ]
     }
@@ -123,5 +129,5 @@ return {
 };
 })();
 // prettier-ignore
-(node/*: any*/).hash = 'c859af7174ec2dc0bfd08cafb26f8d4a';
+(node/*: any*/).hash = 'fa049004b6ac398c3ce6798121384d21';
 module.exports = node;
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/AppleWallet.js b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/AppleWallet.js
new file mode 100644
index 00000000..079405f7
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/AppleWallet.js
@@ -0,0 +1,71 @@
+// @flow strict
+
+import * as React from 'react';
+import { View } from 'react-native';
+import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
+import { Translation } from '@kiwicom/mobile-localization';
+import { Text, StyleSheet, Color } from '@kiwicom/mobile-shared';
+import idx from 'idx';
+
+import AppleWalletPassenger from './AppleWalletPassenger';
+import type { AppleWallet as Pkpasses } from './__generated__/AppleWallet.graphql';
+
+type Props = {|
+  +data: Pkpasses,
+|};
+
+const AppleWallet = (props: Props) => {
+  const pkpasses = idx(props.data, _ => _.pkpasses) || [];
+
+  if (pkpasses.length === 0) {
+    return null;
+  }
+  return (
+    <React.Fragment>
+      <Text style={[styles.title, styles.text]}>
+        <Translation id="mmb.boarding_passes.apple_wallet.title" />
+      </Text>
+      <Text style={[styles.text, styles.infoText]}>
+        <Translation id="mmb.boarding_passes.apple_wallet.information_text" />
+      </Text>
+      <View style={styles.passengerContainer}>
+        {pkpasses.map(item => (
+          <AppleWalletPassenger
+            key={idx(item, _ => _.passenger.databaseId)}
+            data={item}
+          />
+        ))}
+      </View>
+    </React.Fragment>
+  );
+};
+
+export default createFragmentContainer(
+  AppleWallet,
+  graphql`
+    fragment AppleWallet on BoardingPass {
+      pkpasses {
+        ...AppleWalletPassenger
+        passenger {
+          databaseId
+        }
+      }
+    }
+  `,
+);
+
+const styles = StyleSheet.create({
+  title: {
+    color: Color.textLight,
+  },
+  infoText: {
+    marginVertical: 10,
+  },
+  text: {
+    fontSize: 12,
+  },
+  passengerContainer: {
+    borderTopWidth: 1,
+    borderTopColor: Color.backgroundGray,
+  },
+});
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/AppleWalletPassenger.js b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/AppleWalletPassenger.js
new file mode 100644
index 00000000..4b459f04
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/AppleWalletPassenger.js
@@ -0,0 +1,53 @@
+// @flow strict
+
+import * as React from 'react';
+import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
+import { Translation } from '@kiwicom/mobile-localization';
+import { Icon, StyleSheet, Color, Touchable } from '@kiwicom/mobile-shared';
+import idx from 'idx';
+
+import type { AppleWalletPassenger as AppleWalletType } from './__generated__/AppleWalletPassenger.graphql';
+
+type Props = {|
+  +data: AppleWalletType,
+|};
+
+class AppleWalletPassenger extends React.Component<Props> {
+  onPress = () => {
+    console.warn('TODO');
+  };
+
+  render = () => (
+    <Touchable onPress={this.onPress} style={styles.row}>
+      <React.Fragment>
+        <Translation
+          passThrough={idx(this.props.data, _ => _.passenger.fullName)}
+        />
+        <Icon name="chevron-right" size={26} color={Color.brand} />
+      </React.Fragment>
+    </Touchable>
+  );
+}
+
+export default createFragmentContainer(
+  AppleWalletPassenger,
+  graphql`
+    fragment AppleWalletPassenger on Pkpass {
+      url
+      passenger {
+        fullName
+      }
+    }
+  `,
+);
+
+const styles = StyleSheet.create({
+  row: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    borderBottomWidth: 1,
+    borderBottomColor: Color.backgroundGray,
+    paddingVertical: 20,
+  },
+});
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/__generated__/AppleWallet.graphql.js b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/__generated__/AppleWallet.graphql.js
new file mode 100644
index 00000000..93f2e7a7
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/__generated__/AppleWallet.graphql.js
@@ -0,0 +1,71 @@
+/**
+ * @flow
+ */
+
+/* eslint-disable */
+
+'use strict';
+
+/*::
+import type { ConcreteFragment } from 'relay-runtime';
+type AppleWalletPassenger$ref = any;
+import type { FragmentReference } from "relay-runtime";
+declare export opaque type AppleWallet$ref: FragmentReference;
+export type AppleWallet = {|
+  +pkpasses: ?$ReadOnlyArray<?{|
+    +passenger: ?{|
+      +databaseId: ?number
+    |},
+    +$fragmentRefs: AppleWalletPassenger$ref,
+  |}>,
+  +$refType: AppleWallet$ref,
+|};
+*/
+
+
+const node/*: ConcreteFragment*/ = {
+  "kind": "Fragment",
+  "name": "AppleWallet",
+  "type": "BoardingPass",
+  "metadata": null,
+  "argumentDefinitions": [],
+  "selections": [
+    {
+      "kind": "LinkedField",
+      "alias": null,
+      "name": "pkpasses",
+      "storageKey": null,
+      "args": null,
+      "concreteType": "Pkpass",
+      "plural": true,
+      "selections": [
+        {
+          "kind": "FragmentSpread",
+          "name": "AppleWalletPassenger",
+          "args": null
+        },
+        {
+          "kind": "LinkedField",
+          "alias": null,
+          "name": "passenger",
+          "storageKey": null,
+          "args": null,
+          "concreteType": "Passenger",
+          "plural": false,
+          "selections": [
+            {
+              "kind": "ScalarField",
+              "alias": null,
+              "name": "databaseId",
+              "args": null,
+              "storageKey": null
+            }
+          ]
+        }
+      ]
+    }
+  ]
+};
+// prettier-ignore
+(node/*: any*/).hash = '2cb43254bbc6c734f085a85c7b264914';
+module.exports = node;
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/__generated__/AppleWalletPassenger.graphql.js b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/__generated__/AppleWalletPassenger.graphql.js
new file mode 100644
index 00000000..81170769
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/appleWallet/__generated__/AppleWalletPassenger.graphql.js
@@ -0,0 +1,59 @@
+/**
+ * @flow
+ */
+
+/* eslint-disable */
+
+'use strict';
+
+/*::
+import type { ConcreteFragment } from 'relay-runtime';
+import type { FragmentReference } from "relay-runtime";
+declare export opaque type AppleWalletPassenger$ref: FragmentReference;
+export type AppleWalletPassenger = {|
+  +url: ?string,
+  +passenger: ?{|
+    +fullName: ?string
+  |},
+  +$refType: AppleWalletPassenger$ref,
+|};
+*/
+
+
+const node/*: ConcreteFragment*/ = {
+  "kind": "Fragment",
+  "name": "AppleWalletPassenger",
+  "type": "Pkpass",
+  "metadata": null,
+  "argumentDefinitions": [],
+  "selections": [
+    {
+      "kind": "ScalarField",
+      "alias": null,
+      "name": "url",
+      "args": null,
+      "storageKey": null
+    },
+    {
+      "kind": "LinkedField",
+      "alias": null,
+      "name": "passenger",
+      "storageKey": null,
+      "args": null,
+      "concreteType": "Passenger",
+      "plural": false,
+      "selections": [
+        {
+          "kind": "ScalarField",
+          "alias": null,
+          "name": "fullName",
+          "args": null,
+          "storageKey": null
+        }
+      ]
+    }
+  ]
+};
+// prettier-ignore
+(node/*: any*/).hash = '61c60dd20c79385de68ba4be12f67a04';
+module.exports = node;
diff --git a/packages/localization/src/vocabularies/ManageMyBooking.js b/packages/localization/src/vocabularies/ManageMyBooking.js
index 473d1d60..8fc2eb76 100644
--- a/packages/localization/src/vocabularies/ManageMyBooking.js
+++ b/packages/localization/src/vocabularies/ManageMyBooking.js
@@ -42,6 +42,9 @@ export default {
     'We need your passport/ID details to check you in online.',
   'mmb.boarding_passes.future_booking_information.available':
     'You have been provided with digital boarding pass, but you should print it out because some airlines require a physical copy',
+  'mmb.boarding_passes.apple_wallet.title': 'Apple Wallet boarding pass',
+  'mmb.boarding_passes.apple_wallet.information_text':
+    'Board the plane with an electronic boarding pass from your Apple Wallet',
 
   // MMB Flight overview
   'mmb.flight_overview.title': 'Flight Overview',
diff --git a/packages/shared/src/WithNativeNavigation.js b/packages/shared/src/WithNativeNavigation.js
index 35918d20..89660a29 100644
--- a/packages/shared/src/WithNativeNavigation.js
+++ b/packages/shared/src/WithNativeNavigation.js
@@ -5,7 +5,7 @@ import { GestureController } from '@kiwicom/mobile-shared';
 
 type NavigationState = {|
   +routes: $ReadOnlyArray<{
-    +routes: $ReadOnlyArray<mixed>,
+    +routes?: $ReadOnlyArray<mixed>,
   }>,
 |};
 
@@ -33,9 +33,11 @@ function withNativeNavigation<Props: {}>(
     ) => {
       // Native gesture should only be enabled in the very first screen
       // Also, there should not be modal (like first screen + modal)
+
       const isDisabled =
         currentState.routes.length > 1 ||
-        currentState.routes[0].routes.length > 1;
+        (currentState.routes[0].routes &&
+          currentState.routes[0].routes.length > 1);
 
       if (!isDisabled && this.lastCall !== 'enabled') {
         GestureController.enableGestures(moduleName);
diff --git a/schema.graphql b/schema.graphql
index 0f596131..ea6102f6 100644
--- a/schema.graphql
+++ b/schema.graphql
@@ -107,6 +107,9 @@ type BoardingPass {
 
   """The date when the boarding pass will be available for download"""
   availableAt: Date
+
+  """pkpasses connected to the boarding pass"""
+  pkpasses: [Pkpass]
 }
 
 type BoardingTimelineEvent implements TimelineEvent {
@@ -1786,6 +1789,19 @@ type Passenger {
 
   """Visa information for the passenger"""
   visaInformation: Visa
+
+  """Url needed for apple wallet integration"""
+  pkpasses: [Pkpass] @deprecated(reason: "Use pkpass under boarding pass instead")
+}
+
+input PassengerInput {
+  passengerId: Int!
+
+  """Expiry date of travel document"""
+  documentExpiry: Date!
+
+  """Number of travel document"""
+  documentNumber: String!
 }
 
 input PassengersInput {
@@ -1798,6 +1814,15 @@ type PaymentConfirmedTimelineEvent implements TimelineEvent {
   timestamp: DateTime
 }
 
+"""Url needed for apple wallet integration"""
+type Pkpass {
+  flightNumber: String
+
+  """The url to the pkpass file"""
+  url: String
+  passenger: Passenger
+}
+
 type Price {
   amount: Float
 
@@ -1843,10 +1868,19 @@ type RootMutation {
   ): FAQArticle
   login(email: String!, password: String!): User
   resetPassword(email: String!): ResetPasswordResponse
+  updatePassenger(
+    """The global booking id"""
+    id: ID!
+    simpleToken: String
+    passengers: [PassengerInput!]!
+  ): UpdatePassenger
 }
 
 """Root Query"""
 type RootQuery {
+  """AirHelp+ offer that can be purchased with booking"""
+  airHelpPlusOffer(bookingId: String!): AirHelpPlusOffer
+
   """
   Search for all available hotels in one location. It's necessary to send
   checkin and checkout dates as well as rooms configuration to get availability info.
@@ -1956,6 +1990,12 @@ type RootQuery {
     id: ID!
   ): Booking @deprecated(reason: "Use \"node\" query instead.")
 
+  """Get the timeline for your booking."""
+  bookingTimeline(
+    """Only Booking id can be used as ID."""
+    id: ID!
+  ): BookingTimeline
+
   """Currencies used on Kiwi.com frontend"""
   currencies(after: String, first: Int, before: String, last: Int): CurrencyDetailConnection
 
@@ -2039,20 +2079,22 @@ type RootQuery {
   """
   nearestBooking: BookingInterface
 
-  """Get the timeline for your booking."""
-  bookingTimeline(
-    """Only Booking id can be used as ID."""
-    id: ID!
-  ): BookingTimeline
-
   """Fetches an object given its ID"""
   node(
     """The ID of an object"""
     id: ID!
   ): Node
 
-  """AirHelp+ offer that can be purchased with booking"""
-  airHelpPlusOffer(bookingId: String!): AirHelpPlusOffer
+  """Find booking by its id and its simple access token."""
+  singleBooking(
+    """
+    Database ID (human readable ID) of the booking. You should use "node" query if you want to use opaque ID.
+    """
+    id: ID!
+
+    """Simple Token that lets you fetch a unique booking."""
+    simpleToken: String!
+  ): Booking
 }
 
 type RouteStop {
@@ -2111,6 +2153,10 @@ type Trip {
   legs: [Leg]
 }
 
+type UpdatePassenger {
+  success: Boolean
+}
+
 type User {
   token: String
   isLoggedIn: Boolean


Test patch:
diff --git a/app/MMB/src/scenes/tickets/boardingPasses/__tests__/FlightFromTo.test.js b/app/MMB/src/scenes/tickets/boardingPasses/__tests__/FlightFromTo.test.js
new file mode 100644
index 00000000..9b8ca338
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/boardingPasses/__tests__/FlightFromTo.test.js
@@ -0,0 +1,245 @@
+// @flow strict
+
+import * as React from 'react';
+import { Platform } from 'react-native';
+import ShallowRenderer from 'react-test-renderer/shallow';
+
+import { FlightFromTo } from '../FlightFromTo';
+
+const renderer = new ShallowRenderer();
+
+describe('FlightFromTo', () => {
+  it('renders', () => {
+    // $FlowExpectedError: Props not needed for this test
+    const wrapper = renderer.render(<FlightFromTo />);
+    // $FlowExpectedError: https://github.com/flow-typed/flow-typed/issues/2264
+    expect(wrapper).toMatchInlineSnapshot(`
+<Component
+  style={
+    Object {
+      "flexDirection": "row",
+    }
+  }
+>
+  <Component
+    style={
+      Object {
+        "marginEnd": 10,
+      }
+    }
+  >
+    <Text
+      style={
+        Object {
+          "color": "#7f91a8",
+          "fontSize": 12,
+          "fontWeight": "600",
+        }
+      }
+    >
+      <Translation
+        passThrough=""
+      />
+    </Text>
+    <Text
+      style={
+        Object {
+          "color": "#7f91a8",
+          "fontSize": 12,
+          "fontWeight": "600",
+        }
+      }
+    >
+      <Translation
+        passThrough=""
+      />
+    </Text>
+  </Component>
+  <Component
+    style={
+      Object {
+        "flex": 1,
+        "marginEnd": 9,
+      }
+    }
+  >
+    <Component
+      style={
+        Array [
+          Object {
+            "flexDirection": "row",
+          },
+          Object {
+            "alignSelf": "flex-start",
+          },
+        ]
+      }
+    >
+      <Text
+        style={
+          Object {
+            "fontSize": 16,
+            "fontWeight": "600",
+          }
+        }
+      >
+        <Translation />
+      </Text>
+      <TextIcon
+        code=""
+        style={
+          Object {
+            "alignSelf": "center",
+            "color": "#7f91a8",
+            "fontSize": 8,
+            "marginHorizontal": 8,
+          }
+        }
+      />
+      <Text
+        style={
+          Object {
+            "fontSize": 16,
+            "fontWeight": "600",
+          }
+        }
+      >
+        <Translation />
+      </Text>
+    </Component>
+    <Component
+      style={
+        Object {
+          "marginTop": 15,
+        }
+      }
+    >
+      <Relay(withNavigation(DownloadButton)) />
+    </Component>
+    <Component
+      style={
+        Object {
+          "marginTop": 20,
+        }
+      }
+    >
+      <Relay(AppleWallet) />
+    </Component>
+  </Component>
+</Component>
+`);
+  });
+
+  it('Renders correctly with Platform = Android', () => {
+    const OriginalPlatform = Platform.OS;
+    Platform.OS = 'android';
+    // $FlowExpectedError: https://github.com/flow-typed/flow-typed/issues/2264
+    expect(renderer.render(<FlightFromTo />)).toMatchInlineSnapshot(`
+<Component
+  style={
+    Object {
+      "flexDirection": "row",
+    }
+  }
+>
+  <Component
+    style={
+      Object {
+        "marginEnd": 10,
+      }
+    }
+  >
+    <Text
+      style={
+        Object {
+          "color": "#7f91a8",
+          "fontSize": 12,
+          "fontWeight": "600",
+        }
+      }
+    >
+      <Translation
+        passThrough=""
+      />
+    </Text>
+    <Text
+      style={
+        Object {
+          "color": "#7f91a8",
+          "fontSize": 12,
+          "fontWeight": "600",
+        }
+      }
+    >
+      <Translation
+        passThrough=""
+      />
+    </Text>
+  </Component>
+  <Component
+    style={
+      Object {
+        "flex": 1,
+        "marginEnd": 9,
+      }
+    }
+  >
+    <Component
+      style={
+        Array [
+          Object {
+            "flexDirection": "row",
+          },
+          Object {
+            "alignSelf": "flex-start",
+          },
+        ]
+      }
+    >
+      <Text
+        style={
+          Object {
+            "fontSize": 16,
+            "fontWeight": "600",
+          }
+        }
+      >
+        <Translation />
+      </Text>
+      <TextIcon
+        code=""
+        style={
+          Object {
+            "alignSelf": "center",
+            "color": "#7f91a8",
+            "fontSize": 8,
+            "marginHorizontal": 8,
+          }
+        }
+      />
+      <Text
+        style={
+          Object {
+            "fontSize": 16,
+            "fontWeight": "600",
+          }
+        }
+      >
+        <Translation />
+      </Text>
+    </Component>
+    <Component
+      style={
+        Object {
+          "marginTop": 15,
+        }
+      }
+    >
+      <Relay(withNavigation(DownloadButton)) />
+    </Component>
+  </Component>
+</Component>
+`);
+    Platform.OS = OriginalPlatform;
+  });
+});