Repository: kiwicom/mobile
Pull request #719 (2018-06-07 05:19:37)

MMB: Share e-ticket-pdf


Patch:
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 90c77c3b..02fd97ce 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,38 +1,25 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.reactnativeapp"
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.reactnativeapp" 
     xmlns:tools="http://schemas.android.com/tools">
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 
-    <application
-      android:name=".MainApplication"
-      android:allowBackup="true"
-      android:label="@string/app_name"
-      android:icon="@mipmap/ic_launcher"
-      android:theme="@style/AppTheme"
-      tools:replace="android:allowBackup">
+    <application android:name=".MainApplication" android:allowBackup="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme" tools:replace="android:allowBackup">
+        <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:grantUriPermissions="true" android:exported="false">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
+        </provider>
+        <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id" />
 
-      <meta-data
-         android:name="com.facebook.sdk.ApplicationId"
-         android:value="@string/facebook_app_id" />
+        <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" />
 
-      <meta-data
-         android:name="com.google.android.geo.API_KEY"
-         android:value="@string/google_maps_key" />
-
-        <activity
-        android:name=".MainActivity"
-        android:label="@string/app_name"
-        android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
-        android:windowSoftInputMode="adjustResize">
-        <intent-filter>
-            <action android:name="android.intent.action.MAIN" />
-            <category android:name="android.intent.category.LAUNCHER" />
-        </intent-filter>
-      </activity>
-      <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
+        <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
     </application>
 
 </manifest>
diff --git a/android/app/src/main/java/com/reactnativeapp/MainApplication.java b/android/app/src/main/java/com/reactnativeapp/MainApplication.java
index 418e51a2..6c00947d 100644
--- a/android/app/src/main/java/com/reactnativeapp/MainApplication.java
+++ b/android/app/src/main/java/com/reactnativeapp/MainApplication.java
@@ -5,6 +5,7 @@ import android.support.multidex.MultiDexApplication;
 
 import com.facebook.react.ReactApplication;
 import cl.json.RNSharePackage;
+import cl.json.ShareApplication;
 import org.wonday.pdf.RCTPdfView;
 import com.RNFetchBlob.RNFetchBlobPackage;
 import com.reactlibrary.RNTooltipsPackage;
@@ -23,7 +24,7 @@ import com.trinerdis.skypicker.translation.*;
 import java.util.Arrays;
 import java.util.List;
 
-public class MainApplication extends MultiDexApplication implements ReactApplication {
+public class MainApplication extends MultiDexApplication implements ShareApplication, ReactApplication {
 
     private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
         @Override
@@ -65,4 +66,9 @@ public class MainApplication extends MultiDexApplication implements ReactApplica
         super.onCreate();
         SoLoader.init(this, /* native exopackage */ false);
     }
+
+    @Override
+    public String getFileProviderAuthority() {
+        return "com.reactnativeapp.provider";
+    }
 }
diff --git a/android/app/src/main/res/xml/filepaths.xml b/android/app/src/main/res/xml/filepaths.xml
new file mode 100644
index 00000000..29d554a5
--- /dev/null
+++ b/android/app/src/main/res/xml/filepaths.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+  <cache-path name="pdfs" path="." />
+</paths>
\ No newline at end of file
diff --git a/app/MMB/src/__generated__/MainMenuContainerQuery.graphql.js b/app/MMB/src/__generated__/MainMenuContainerQuery.graphql.js
index 43df7f36..93ccb390 100644
--- a/app/MMB/src/__generated__/MainMenuContainerQuery.graphql.js
+++ b/app/MMB/src/__generated__/MainMenuContainerQuery.graphql.js
@@ -1,6 +1,6 @@
 /**
  * @flow
- * @relayHash a5489f281de09e76e2c7c3c0500d784c
+ * @relayHash 58903e435a2591e9f6f6ccfa4d5ae20d
  */
 
 /* eslint-disable */
@@ -40,7 +40,6 @@ fragment Header on Booking {
   isPastBooking
   ...StatusBar
   ...TripInfo
-  ...HeaderImage
   ...TripOverview
 }
 
@@ -96,10 +95,6 @@ fragment TripInfo on Booking {
   }
 }
 
-fragment HeaderImage on Booking {
-  destinationImageUrl(dimensions: _375x165)
-}
-
 fragment TripOverview on Booking {
   type
   oneWay {
@@ -411,7 +406,7 @@ return {
   "operationKind": "query",
   "name": "MainMenuContainerQuery",
   "id": null,
-  "text": "query MainMenuContainerQuery(\n  $bookingId: ID!\n) {\n  booking(id: $bookingId) {\n    ...MainMenu\n    id\n  }\n}\n\nfragment MainMenu on Booking {\n  ...Header\n  ...PassengerMenuGroup\n}\n\nfragment Header on Booking {\n  isPastBooking\n  ...StatusBar\n  ...TripInfo\n  ...HeaderImage\n  ...TripOverview\n}\n\nfragment PassengerMenuGroup on Booking {\n  ...PassengerMenuDetail\n}\n\nfragment PassengerMenuDetail on Booking {\n  ...Visa\n  passengers {\n    databaseId\n    ...Passenger\n  }\n}\n\nfragment Visa on Booking {\n  passengers {\n    visaInformation {\n      requiredIn {\n        name\n      }\n      warningIn {\n        name\n      }\n    }\n  }\n}\n\nfragment Passenger on Passenger {\n  fullName\n  title\n  birthday\n}\n\nfragment StatusBar on Booking {\n  ...StatusBarIcon\n  databaseId\n}\n\nfragment TripInfo on Booking {\n  type\n  oneWay {\n    ...TripInfoOneWay\n    id\n  }\n  return {\n    ...TripInfoReturn\n    id\n  }\n  multicity {\n    ...TripInfoMulticity\n    id\n  }\n}\n\nfragment HeaderImage on Booking {\n  destinationImageUrl(dimensions: _375x165)\n}\n\nfragment TripOverview on Booking {\n  type\n  oneWay {\n    ...OneWayTimeline\n    id\n  }\n  return {\n    ...ReturnTimeline\n    id\n  }\n  multicity {\n    ...MulticityTimeline\n    id\n  }\n}\n\nfragment OneWayTimeline on BookingOneWay {\n  trip {\n    ...Timeline\n  }\n}\n\nfragment ReturnTimeline on BookingReturn {\n  outbound {\n    ...Timeline\n  }\n  inbound {\n    ...Timeline\n  }\n}\n\nfragment MulticityTimeline on BookingMulticity {\n  trips {\n    ...Timeline\n  }\n}\n\nfragment Timeline on Trip {\n  legs {\n    departure {\n      ...TimelineDeparture\n    }\n    arrival {\n      ...TimelineArrival\n    }\n    id\n  }\n}\n\nfragment TimelineDeparture on RouteStop {\n  localTime\n  airport {\n    locationId\n    city {\n      name\n    }\n  }\n}\n\nfragment TimelineArrival on RouteStop {\n  localTime\n  airport {\n    locationId\n    city {\n      name\n    }\n  }\n}\n\nfragment TripInfoOneWay on BookingOneWay {\n  trip {\n    ...TripCities\n    ...TripTimes\n  }\n}\n\nfragment TripInfoReturn on BookingReturn {\n  outbound {\n    ...TripCities\n    ...TripTimes\n  }\n  inbound {\n    ...TripTimes\n  }\n}\n\nfragment TripInfoMulticity on BookingMulticity {\n  trips {\n    ...TripCities\n    ...TripTimes\n  }\n}\n\nfragment TripCities on Trip {\n  departure {\n    ...Location\n  }\n  arrival {\n    ...Location\n  }\n}\n\nfragment TripTimes on Trip {\n  ...Duration\n  departure {\n    ...DateTime\n  }\n  arrival {\n    ...DateTime\n  }\n}\n\nfragment Duration on Trip {\n  duration\n}\n\nfragment DateTime on RouteStop {\n  localTime\n}\n\nfragment Location on RouteStop {\n  airport {\n    city {\n      name\n    }\n    ...CountryFlag\n  }\n}\n\nfragment CountryFlag on Location {\n  countryFlagURL\n}\n\nfragment StatusBarIcon on Booking {\n  status\n  isPastBooking\n}\n",
+  "text": "query MainMenuContainerQuery(\n  $bookingId: ID!\n) {\n  booking(id: $bookingId) {\n    ...MainMenu\n    id\n  }\n}\n\nfragment MainMenu on Booking {\n  ...Header\n  ...PassengerMenuGroup\n}\n\nfragment Header on Booking {\n  isPastBooking\n  ...StatusBar\n  ...TripInfo\n  ...TripOverview\n}\n\nfragment PassengerMenuGroup on Booking {\n  ...PassengerMenuDetail\n}\n\nfragment PassengerMenuDetail on Booking {\n  ...Visa\n  passengers {\n    databaseId\n    ...Passenger\n  }\n}\n\nfragment Visa on Booking {\n  passengers {\n    visaInformation {\n      requiredIn {\n        name\n      }\n      warningIn {\n        name\n      }\n    }\n  }\n}\n\nfragment Passenger on Passenger {\n  fullName\n  title\n  birthday\n}\n\nfragment StatusBar on Booking {\n  ...StatusBarIcon\n  databaseId\n}\n\nfragment TripInfo on Booking {\n  type\n  oneWay {\n    ...TripInfoOneWay\n    id\n  }\n  return {\n    ...TripInfoReturn\n    id\n  }\n  multicity {\n    ...TripInfoMulticity\n    id\n  }\n}\n\nfragment TripOverview on Booking {\n  type\n  oneWay {\n    ...OneWayTimeline\n    id\n  }\n  return {\n    ...ReturnTimeline\n    id\n  }\n  multicity {\n    ...MulticityTimeline\n    id\n  }\n}\n\nfragment OneWayTimeline on BookingOneWay {\n  trip {\n    ...Timeline\n  }\n}\n\nfragment ReturnTimeline on BookingReturn {\n  outbound {\n    ...Timeline\n  }\n  inbound {\n    ...Timeline\n  }\n}\n\nfragment MulticityTimeline on BookingMulticity {\n  trips {\n    ...Timeline\n  }\n}\n\nfragment Timeline on Trip {\n  legs {\n    departure {\n      ...TimelineDeparture\n    }\n    arrival {\n      ...TimelineArrival\n    }\n    id\n  }\n}\n\nfragment TimelineDeparture on RouteStop {\n  localTime\n  airport {\n    locationId\n    city {\n      name\n    }\n  }\n}\n\nfragment TimelineArrival on RouteStop {\n  localTime\n  airport {\n    locationId\n    city {\n      name\n    }\n  }\n}\n\nfragment TripInfoOneWay on BookingOneWay {\n  trip {\n    ...TripCities\n    ...TripTimes\n  }\n}\n\nfragment TripInfoReturn on BookingReturn {\n  outbound {\n    ...TripCities\n    ...TripTimes\n  }\n  inbound {\n    ...TripTimes\n  }\n}\n\nfragment TripInfoMulticity on BookingMulticity {\n  trips {\n    ...TripCities\n    ...TripTimes\n  }\n}\n\nfragment TripCities on Trip {\n  departure {\n    ...Location\n  }\n  arrival {\n    ...Location\n  }\n}\n\nfragment TripTimes on Trip {\n  ...Duration\n  departure {\n    ...DateTime\n  }\n  arrival {\n    ...DateTime\n  }\n}\n\nfragment Duration on Trip {\n  duration\n}\n\nfragment DateTime on RouteStop {\n  localTime\n}\n\nfragment Location on RouteStop {\n  airport {\n    city {\n      name\n    }\n    ...CountryFlag\n  }\n}\n\nfragment CountryFlag on Location {\n  countryFlagURL\n}\n\nfragment StatusBarIcon on Booking {\n  status\n  isPastBooking\n}\n",
   "metadata": {},
   "fragment": {
     "kind": "Fragment",
@@ -573,20 +568,6 @@ return {
               v9
             ]
           },
-          {
-            "kind": "ScalarField",
-            "alias": null,
-            "name": "destinationImageUrl",
-            "args": [
-              {
-                "kind": "Literal",
-                "name": "dimensions",
-                "value": "_375x165",
-                "type": "BookingDestinationImageDimensions"
-              }
-            ],
-            "storageKey": "destinationImageUrl(dimensions:\"_375x165\")"
-          },
           {
             "kind": "LinkedField",
             "alias": null,
diff --git a/app/MMB/src/components/Invoice.js b/app/MMB/src/components/Invoice.js
index 8c95f21c..8a44956f 100644
--- a/app/MMB/src/components/Invoice.js
+++ b/app/MMB/src/components/Invoice.js
@@ -22,14 +22,7 @@ export default class Invoice extends React.Component<{||}> {
       );
     }
 
-    return (
-      <PdfViewer
-        source={{
-          uri,
-          cache: true, // no expiration - store as long as possible
-        }}
-      />
-    );
+    return <PdfViewer uri={uri} />;
   };
 
   render = () => (
diff --git a/app/MMB/src/components/header/Header.js b/app/MMB/src/components/header/Header.js
index 6ce60e58..4bcd835d 100644
--- a/app/MMB/src/components/header/Header.js
+++ b/app/MMB/src/components/header/Header.js
@@ -16,8 +16,6 @@ import idx from 'idx';
 
 import StatusBar from './StatusBar';
 import TripInfo from './TripInfo';
-import HeaderImage from './HeaderImage';
-import HeaderPlaceholder from './HeaderPlaceholder';
 import TripOverview from '../../scenes/tripOverview/TripOverview';
 import type { Header as HeaderType } from './__generated__/Header.graphql';
 
@@ -28,11 +26,9 @@ type HeaderSharedProps = {|
 
 function HeaderShared(props: HeaderSharedProps) {
   const booking = idx(props, _ => _.data);
-  const isPastBooking = idx(booking, _ => _.isPastBooking);
 
   return (
     <React.Fragment>
-      {isPastBooking ? <HeaderImage data={booking} /> : <HeaderPlaceholder />}
       <View style={styleSheet.wrapper}>
         <StatusBar data={booking} />
         <View style={styleSheet.separator}>
@@ -116,7 +112,6 @@ export default createFragmentContainer(
       isPastBooking
       ...StatusBar
       ...TripInfo
-      ...HeaderImage
       ...TripOverview
     }
   `,
diff --git a/app/MMB/src/components/header/HeaderImage.js b/app/MMB/src/components/header/HeaderImage.js
deleted file mode 100644
index 065cc4b8..00000000
--- a/app/MMB/src/components/header/HeaderImage.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// @flow
-
-import * as React from 'react';
-import { StyleSheet, NetworkImage } from '@kiwicom/mobile-shared';
-import { graphql, createFragmentContainer } from '@kiwicom/mobile-relay';
-import idx from 'idx';
-
-import HeaderPlaceholder from './HeaderPlaceholder';
-import type { HeaderImage as HeaderImageType } from './__generated__/HeaderImage.graphql';
-
-type Props = {|
-  +data: HeaderImageType,
-|};
-
-const HeaderImage = (props: Props) => (
-  <HeaderPlaceholder>
-    <NetworkImage
-      source={{
-        uri: idx(props.data, _ => _.destinationImageUrl),
-      }}
-      style={styles.image}
-      resizeMode="cover"
-    />
-  </HeaderPlaceholder>
-);
-
-export default createFragmentContainer(
-  HeaderImage,
-  graphql`
-    fragment HeaderImage on Booking {
-      destinationImageUrl(dimensions: _375x165)
-    }
-  `,
-);
-
-const styles = StyleSheet.create({
-  image: StyleSheet.absoluteFillObject,
-});
diff --git a/app/MMB/src/components/header/HeaderPlaceholder.js b/app/MMB/src/components/header/HeaderPlaceholder.js
deleted file mode 100644
index 98a3d45a..00000000
--- a/app/MMB/src/components/header/HeaderPlaceholder.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// @flow
-
-import * as React from 'react';
-import { View } from 'react-native';
-import { StyleSheet, Color } from '@kiwicom/mobile-shared';
-
-type Props = {|
-  +children?: React.Node,
-|};
-
-export default function HeaderPlaceholder(props: Props) {
-  return <View style={styles.container}>{props.children}</View>;
-}
-
-const styles = StyleSheet.create({
-  container: {
-    backgroundColor: Color.brandLight,
-    height: 140,
-    width: '100%',
-    overflow: 'hidden',
-  },
-});
diff --git a/app/MMB/src/components/header/__generated__/Header.graphql.js b/app/MMB/src/components/header/__generated__/Header.graphql.js
index 15d6d561..10bd64c0 100644
--- a/app/MMB/src/components/header/__generated__/Header.graphql.js
+++ b/app/MMB/src/components/header/__generated__/Header.graphql.js
@@ -8,7 +8,6 @@
 
 /*::
 import type { ConcreteFragment } from 'relay-runtime';
-type HeaderImage$ref = any;
 type StatusBar$ref = any;
 type TripInfo$ref = any;
 type TripOverview$ref = any;
@@ -16,7 +15,7 @@ import type { FragmentReference } from "relay-runtime";
 declare export opaque type Header$ref: FragmentReference;
 export type Header = {|
   +isPastBooking: ?boolean,
-  +$fragmentRefs: StatusBar$ref & TripInfo$ref & HeaderImage$ref & TripOverview$ref,
+  +$fragmentRefs: StatusBar$ref & TripInfo$ref & TripOverview$ref,
   +$refType: Header$ref,
 |};
 */
@@ -46,11 +45,6 @@ const node/*: ConcreteFragment*/ = {
       "name": "TripInfo",
       "args": null
     },
-    {
-      "kind": "FragmentSpread",
-      "name": "HeaderImage",
-      "args": null
-    },
     {
       "kind": "FragmentSpread",
       "name": "TripOverview",
@@ -59,5 +53,5 @@ const node/*: ConcreteFragment*/ = {
   ]
 };
 // prettier-ignore
-(node/*: any*/).hash = '7b1b7bb9b7febca207a21bb2091b4f8a';
+(node/*: any*/).hash = '5ca0cba5575549b024a72f8ac27bf242';
 module.exports = node;
diff --git a/app/MMB/src/components/header/__generated__/HeaderImage.graphql.js b/app/MMB/src/components/header/__generated__/HeaderImage.graphql.js
deleted file mode 100644
index 81961fdb..00000000
--- a/app/MMB/src/components/header/__generated__/HeaderImage.graphql.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * @flow
- */
-
-/* eslint-disable */
-
-'use strict';
-
-/*::
-import type { ConcreteFragment } from 'relay-runtime';
-import type { FragmentReference } from "relay-runtime";
-declare export opaque type HeaderImage$ref: FragmentReference;
-export type HeaderImage = {|
-  +destinationImageUrl: ?string,
-  +$refType: HeaderImage$ref,
-|};
-*/
-
-
-const node/*: ConcreteFragment*/ = {
-  "kind": "Fragment",
-  "name": "HeaderImage",
-  "type": "Booking",
-  "metadata": null,
-  "argumentDefinitions": [],
-  "selections": [
-    {
-      "kind": "ScalarField",
-      "alias": null,
-      "name": "destinationImageUrl",
-      "args": [
-        {
-          "kind": "Literal",
-          "name": "dimensions",
-          "value": "_375x165",
-          "type": "BookingDestinationImageDimensions"
-        }
-      ],
-      "storageKey": "destinationImageUrl(dimensions:\"_375x165\")"
-    }
-  ]
-};
-// prettier-ignore
-(node/*: any*/).hash = 'd6500bf34da54b1db99d93a6fb23de9f';
-module.exports = node;
diff --git a/app/MMB/src/context/BookingDetailContext.js b/app/MMB/src/context/BookingDetailContext.js
index c8a446d8..51be0144 100644
--- a/app/MMB/src/context/BookingDetailContext.js
+++ b/app/MMB/src/context/BookingDetailContext.js
@@ -16,8 +16,10 @@ const defaultState = {
   arrivalCityId: '',
   arrivalTime: new Date(),
   departureTime: new Date(),
+  eTicketPath: null,
   actions: {
     setBookingDetail: () => {},
+    setETicketPath: () => {},
   },
 };
 
@@ -31,8 +33,10 @@ type Props = {|
 
 type State = {|
   ...BookingDetail,
+  +eTicketPath: null | string,
   +actions: {|
     +setBookingDetail: (booking: BookingDetail) => void,
+    +setETicketPath: (path: string) => void,
   |},
 |};
 
@@ -42,12 +46,18 @@ class Provider extends React.Component<Props, State> {
 
     this.state = {
       ...defaultState,
+      eTicketPath: null,
       actions: {
         setBookingDetail: this.setBookingDetail,
+        setETicketPath: this.setETicketPath,
       },
     };
   }
 
+  setETicketPath = (eTicketPath: string | null) => {
+    this.setState({ eTicketPath });
+  };
+
   setBookingDetail = (booking: BookingDetail) => {
     this.setState(booking);
   };
diff --git a/app/MMB/src/scenes/tickets/ETicketPdf.js b/app/MMB/src/scenes/tickets/ETicketPdf.js
deleted file mode 100644
index c2a14afd..00000000
--- a/app/MMB/src/scenes/tickets/ETicketPdf.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// @flow
-
-import * as React from 'react';
-import { GeneralError } from '@kiwicom/mobile-shared';
-import { Translation } from '@kiwicom/mobile-localization';
-import PdfViewer from '@kiwicom/mobile-pdf';
-
-type Props = {|
-  +ticketUrl: ?string,
-|};
-
-export default function EticketPdf({ ticketUrl }: Props) {
-  if (!ticketUrl) {
-    return (
-      <GeneralError
-        errorMessage={<Translation id="mmb.tickets.not_available" />}
-      />
-    );
-  }
-
-  return (
-    <PdfViewer
-      source={{
-        uri: ticketUrl,
-        cache: true, // no expiration - store as long as possible
-      }}
-    />
-  );
-}
diff --git a/app/MMB/src/scenes/tickets/TicketScene.js b/app/MMB/src/scenes/tickets/TicketScene.js
index ad656e2e..5623871a 100644
--- a/app/MMB/src/scenes/tickets/TicketScene.js
+++ b/app/MMB/src/scenes/tickets/TicketScene.js
@@ -3,10 +3,10 @@
 import * as React from 'react';
 import { PrivateApiRenderer, graphql } from '@kiwicom/mobile-relay';
 
-import ETicketPdf from './ETicketPdf';
 import BookingDetailContext from '../../context/BookingDetailContext';
 import type { TicketSceneQueryResponse } from './__generated__/TicketSceneQuery.graphql';
 import ETicketRefetch from './ETicketRefetch';
+import ETicketPdfScreen from './eTicketPdf/ETicketPdfScreen';
 
 type Props = {|
   +data: TicketSceneQueryResponse,
@@ -38,8 +38,6 @@ export default class TicketScene extends React.Component<Props> {
 
 export const TicketSceneSubMenus = {
   'mmb.tickets.e_ticket': {
-    screen: function OpenEticketPdf({ ticketUrl }: {| +ticketUrl: ?string |}) {
-      return <ETicketPdf ticketUrl={ticketUrl} />;
-    },
+    screen: ETicketPdfScreen,
   },
 };
diff --git a/app/MMB/src/scenes/tickets/eTicketPdf/ETicketPdf.js b/app/MMB/src/scenes/tickets/eTicketPdf/ETicketPdf.js
new file mode 100644
index 00000000..a89d2f3c
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/eTicketPdf/ETicketPdf.js
@@ -0,0 +1,46 @@
+// @flow
+
+import * as React from 'react';
+import { GeneralError } from '@kiwicom/mobile-shared';
+import { Translation } from '@kiwicom/mobile-localization';
+import PdfViewer from '@kiwicom/mobile-pdf';
+
+import BookingDetailContext from '../../../context/BookingDetailContext';
+
+type PropsWithContext = {|
+  ...Props,
+  +setETicketPath: (path: string) => void,
+|};
+
+class ETicketPdf extends React.Component<PropsWithContext> {
+  onPdfLoaded = (pages: number, path: string) => {
+    this.props.setETicketPath(path);
+  };
+
+  render = () => {
+    const { ticketUrl } = this.props;
+    if (!ticketUrl) {
+      return (
+        <GeneralError
+          errorMessage={<Translation id="mmb.tickets.not_available" />}
+        />
+      );
+    }
+
+    return <PdfViewer uri={ticketUrl} onLoad={this.onPdfLoaded} />;
+  };
+}
+
+type Props = {|
+  +ticketUrl: ?string,
+|};
+
+export default function ETicketPdfWithContext(props: Props) {
+  return (
+    <BookingDetailContext.Consumer>
+      {({ actions: { setETicketPath } }) => (
+        <ETicketPdf {...props} setETicketPath={setETicketPath} />
+      )}
+    </BookingDetailContext.Consumer>
+  );
+}
diff --git a/app/MMB/src/scenes/tickets/eTicketPdf/ETicketPdfScreen.js b/app/MMB/src/scenes/tickets/eTicketPdf/ETicketPdfScreen.js
new file mode 100644
index 00000000..f0632f26
--- /dev/null
+++ b/app/MMB/src/scenes/tickets/eTicketPdf/ETicketPdfScreen.js
@@ -0,0 +1,45 @@
+// @flow strict
+
+import * as React from 'react';
+import Share from 'react-native-share';
+
+import ETicketPdf from './ETicketPdf';
+import ShareButton from '../../../components/ShareButton';
+import BookingDetailContext from '../../../context/BookingDetailContext';
+
+type ShareButtonProps = {|
+  +eTicketPath: string | null,
+|};
+
+class ETicketShareButton extends React.Component<ShareButtonProps> {
+  onPress = () => {
+    if (this.props.eTicketPath !== null) {
+      const eTicketPath = `file:///${this.props.eTicketPath}`;
+      Share.open({ url: eTicketPath });
+    }
+  };
+
+  render = () => <ShareButton onPress={this.onPress} />;
+}
+
+function ShareButtonWithContext() {
+  return (
+    <BookingDetailContext.Consumer>
+      {({ eTicketPath }) => <ETicketShareButton eTicketPath={eTicketPath} />}
+    </BookingDetailContext.Consumer>
+  );
+}
+
+type Props = {|
+  +ticketUrl: ?string,
+|};
+
+const ETicketPdfScreen = ({ ticketUrl }: Props) => (
+  <ETicketPdf ticketUrl={ticketUrl} />
+);
+
+ETicketPdfScreen.navigationOptions = () => ({
+  headerRight: <ShareButtonWithContext />,
+});
+
+export default ETicketPdfScreen;
diff --git a/ios/reactNativeApp.xcodeproj/project.pbxproj b/ios/reactNativeApp.xcodeproj/project.pbxproj
index dbacc93d..11c039ec 100644
--- a/ios/reactNativeApp.xcodeproj/project.pbxproj
+++ b/ios/reactNativeApp.xcodeproj/project.pbxproj
@@ -5,7 +5,6 @@
 	};
 	objectVersion = 46;
 	objects = {
-
 /* Begin PBXBuildFile section */
 		012E4C1BA8444C8182992EEB /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B11306CBC6D24D3C990EAC4B /* Ionicons.ttf */; };
 		038BD3170181472CA3083F7C /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6603DB7CD9F34D7AB1AD5B21 /* SimpleLineIcons.ttf */; };
@@ -53,6 +52,9 @@
 		27E931E820ADA06C0053AE19 /* RNKiwiMobile.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27E931B020AD96780053AE19 /* RNKiwiMobile.framework */; };
 		37478C9C708E472FBA98598C /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 21C03BEA6DA8479DBFFA62F0 /* Roboto-Regular.ttf */; };
 		3B029E80AD3948E19D4E6814 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F477A1D455304A4EA37DF899 /* MaterialIcons.ttf */; };
+		60854A62F6F281394EE676B5 /* libPods-reactNativeApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0520FD3BC77D396F74CF8285 /* libPods-reactNativeApp.a */; };
+		63A8419D20C853840093183F /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63A8419C20C853840093183F /* Social.framework */; };
+		63A8419F20C8538A0093183F /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63A8419E20C8538A0093183F /* MessageUI.framework */; };
 		686E004B20B2E1360013512F /* RNKiwiConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 686E004A20B2E1360013512F /* RNKiwiConstants.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		686E005120B2E24B0013512F /* RNKiwiConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 686E005020B2E24B0013512F /* RNKiwiConstants.m */; };
 		7EEEE5BA4A6944DCBDECD4C3 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 929CCF0BD6A142DEAB8DA917 /* Octicons.ttf */; };
@@ -118,6 +120,8 @@
 		463A493B8DDC4A86A32B73D4 /* SFProText-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "SFProText-Regular.ttf"; path = "../assets/fonts/SFProText-Regular.ttf"; sourceTree = "<group>"; };
 		46C66C53E2B841209A9370D0 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; };
 		5377C27743964C6D8EF1FA05 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
+		63A8419C20C853840093183F /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
+		63A8419E20C8538A0093183F /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
 		645A06359A464CB0B63845A2 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; };
 		6603DB7CD9F34D7AB1AD5B21 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
 		686E004A20B2E1360013512F /* RNKiwiConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNKiwiConstants.h; sourceTree = "<group>"; };
@@ -143,7 +147,9 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				196AE49557919C763304B087 /* libPods-reactNativeApp.a in Frameworks */,
+				63A8419F20C8538A0093183F /* MessageUI.framework in Frameworks */,
+				63A8419D20C853840093183F /* Social.framework in Frameworks */,
+				60854A62F6F281394EE676B5 /* libPods-reactNativeApp.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -280,6 +286,8 @@
 		A2CAAD3C4D5834AF8638A878 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				63A8419E20C8538A0093183F /* MessageUI.framework */,
+				63A8419C20C853840093183F /* Social.framework */,
 				27E931CC20AD9EF60053AE19 /* libPods-RNKiwiMobile.a */,
 				27E931CA20AD9EE70053AE19 /* libPods-RNPlayground.a */,
 				0520FD3BC77D396F74CF8285 /* libPods-reactNativeApp.a */,
diff --git a/ios/reactNativeApp/Info.plist b/ios/reactNativeApp/Info.plist
index e88fe8fd..ecbce401 100644
--- a/ios/reactNativeApp/Info.plist
+++ b/ios/reactNativeApp/Info.plist
@@ -1,76 +1,81 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
-<dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>en</string>
-	<key>CFBundleDisplayName</key>
-	<string>RNPlayground</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>$(PRODUCT_NAME)</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
-	<key>CFBundleSignature</key>
-	<string>????</string>
-	<key>CFBundleVersion</key>
-	<string>18</string>
-	<key>ITSAppUsesNonExemptEncryption</key>
-	<false/>
-	<key>LSRequiresIPhoneOS</key>
-	<true/>
-	<key>NSAppTransportSecurity</key>
 	<dict>
-		<key>NSExceptionDomains</key>
+		<key>CFBundleDevelopmentRegion</key>
+		<string>en</string>
+		<key>CFBundleDisplayName</key>
+		<string>RNPlayground</string>
+		<key>CFBundleExecutable</key>
+		<string>$(EXECUTABLE_NAME)</string>
+		<key>CFBundleIdentifier</key>
+		<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+		<key>CFBundleInfoDictionaryVersion</key>
+		<string>6.0</string>
+		<key>CFBundleName</key>
+		<string>$(PRODUCT_NAME)</string>
+		<key>CFBundlePackageType</key>
+		<string>APPL</string>
+		<key>CFBundleShortVersionString</key>
+		<string>1.0</string>
+		<key>CFBundleSignature</key>
+		<string>????</string>
+		<key>CFBundleVersion</key>
+		<string>18</string>
+		<key>ITSAppUsesNonExemptEncryption</key>
+		<false/>
+		<key>LSRequiresIPhoneOS</key>
+		<true/>
+		<key>NSAppTransportSecurity</key>
 		<dict>
-			<key>localhost</key>
+			<key>NSExceptionDomains</key>
 			<dict>
-				<key>NSExceptionAllowsInsecureHTTPLoads</key>
-				<true/>
+				<key>localhost</key>
+				<dict>
+					<key>NSExceptionAllowsInsecureHTTPLoads</key>
+					<true/>
+				</dict>
 			</dict>
 		</dict>
+		<key>NSLocationWhenInUseUsageDescription</key>
+		<string/>
+		<key>UIAppFonts</key>
+		<array>
+			<string>Entypo.ttf</string>
+			<string>EvilIcons.ttf</string>
+			<string>Feather.ttf</string>
+			<string>FontAwesome.ttf</string>
+			<string>Foundation.ttf</string>
+			<string>Ionicons.ttf</string>
+			<string>MaterialCommunityIcons.ttf</string>
+			<string>MaterialIcons.ttf</string>
+			<string>Octicons.ttf</string>
+			<string>SimpleLineIcons.ttf</string>
+			<string>Zocial.ttf</string>
+			<string>spfont.ttf</string>
+			<string>Roboto-Regular.ttf</string>
+			<string>SFProText-Semibold.ttf</string>
+			<string>SFProText-Regular.ttf</string>
+		</array>
+		<key>UILaunchStoryboardName</key>
+		<string>LaunchScreen</string>
+		<key>UIRequiredDeviceCapabilities</key>
+		<array>
+			<string>armv7</string>
+		</array>
+		<key>LSApplicationQueriesSchemes</key>
+		<array>
+			<string>whatsapp</string>
+			<string>mailto</string>
+		</array>
+		<key>UISupportedInterfaceOrientations</key>
+		<array>
+			<string>UIInterfaceOrientationPortrait</string>
+			<string>UIInterfaceOrientationLandscapeLeft</string>
+			<string>UIInterfaceOrientationLandscapeRight</string>
+			<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		</array>
+		<key>UIViewControllerBasedStatusBarAppearance</key>
+		<false/>
 	</dict>
-	<key>NSLocationWhenInUseUsageDescription</key>
-	<string/>
-	<key>UIAppFonts</key>
-	<array>
-		<string>Entypo.ttf</string>
-		<string>EvilIcons.ttf</string>
-		<string>Feather.ttf</string>
-		<string>FontAwesome.ttf</string>
-		<string>Foundation.ttf</string>
-		<string>Ionicons.ttf</string>
-		<string>MaterialCommunityIcons.ttf</string>
-		<string>MaterialIcons.ttf</string>
-		<string>Octicons.ttf</string>
-		<string>SimpleLineIcons.ttf</string>
-		<string>Zocial.ttf</string>
-		<string>spfont.ttf</string>
-		<string>Roboto-Regular.ttf</string>
-		<string>SFProText-Semibold.ttf</string>
-		<string>SFProText-Regular.ttf</string>
-	</array>
-	<key>UILaunchStoryboardName</key>
-	<string>LaunchScreen</string>
-	<key>UIRequiredDeviceCapabilities</key>
-	<array>
-		<string>armv7</string>
-	</array>
-	<key>UISupportedInterfaceOrientations</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-		<string>UIInterfaceOrientationPortraitUpsideDown</string>
-	</array>
-	<key>UIViewControllerBasedStatusBarAppearance</key>
-	<false/>
-</dict>
 </plist>
diff --git a/packages/pdf/src/PdfViewer.js b/packages/pdf/src/PdfViewer.js
index 523c5f37..08a1a4cb 100644
--- a/packages/pdf/src/PdfViewer.js
+++ b/packages/pdf/src/PdfViewer.js
@@ -6,12 +6,11 @@ import { Translation } from '@kiwicom/mobile-localization';
 import { StyleSheet, GeneralError } from '@kiwicom/mobile-shared';
 
 type Props = {|
-  source: {|
-    uri: string,
-    cache?: boolean,
-  |},
-  onError?: () => void,
-  onLoad?: () => void,
+  +uri: string,
+  +cache?: boolean,
+  +expiration?: number,
+  +onError?: () => void,
+  +onLoad?: (numberOfPages: number, filePath: string) => void,
 |};
 
 type State = {|
@@ -23,9 +22,14 @@ export default class PdfViewer extends React.Component<Props, State> {
     hasLoadingFailed: false,
   };
 
-  onLoad = () => {
+  static defaultProps = {
+    cache: true,
+    expiration: Infinity,
+  };
+
+  onLoad = (numberOfPages: number, filePath: string) => {
     if (this.props.onLoad) {
-      this.props.onLoad();
+      this.props.onLoad(numberOfPages, filePath);
     }
   };
 
@@ -43,7 +47,11 @@ export default class PdfViewer extends React.Component<Props, State> {
           style={StyleSheet.absoluteFill}
           onError={this.onError}
           onLoadComplete={this.onLoad}
-          source={this.props.source}
+          source={{
+            uri: this.props.uri,
+            cache: this.props.cache,
+            expiration: this.props.expiration,
+          }}
         />
         {this.state.hasLoadingFailed && (
           <GeneralError


Test patch:
diff --git a/app/MMB/src/context/__tests__/BookingDetailContext.test.js b/app/MMB/src/context/__tests__/BookingDetailContext.test.js
index cb8d960e..4e2250e0 100644
--- a/app/MMB/src/context/__tests__/BookingDetailContext.test.js
+++ b/app/MMB/src/context/__tests__/BookingDetailContext.test.js
@@ -14,8 +14,12 @@ const getWrapper = () =>
   renderer.create(
     <BookingDetailContext.Provider>
       <BookingDetailContext.Consumer>
-        {({ actions: { setBookingDetail }, ...rest }) => (
-          <ContextConsumer {...rest} setBookingDetail={setBookingDetail} />
+        {({ actions: { setBookingDetail, setETicketPath }, ...rest }) => (
+          <ContextConsumer
+            {...rest}
+            setBookingDetail={setBookingDetail}
+            setETicketPath={setETicketPath}
+          />
         )}
       </BookingDetailContext.Consumer>
     </BookingDetailContext.Provider>,
@@ -51,4 +55,13 @@ describe('BookingDetailContext', () => {
     expect(instance.props.arrivalCityId).toBe('oslo_no');
     expect(instance.props.arrivalTime).toEqual(new Date(1));
   });
+
+  it('it sets eTicketPath', () => {
+    const wrapper = getWrapper();
+    const instance = wrapper.root.findByType(ContextConsumer);
+
+    instance.props.setETicketPath('path/is/test');
+
+    expect(instance.props.eTicketPath).toBe('path/is/test');
+  });
 });
diff --git a/packages/pdf/src/__tests__/PdfViewer.test.js b/packages/pdf/src/__tests__/PdfViewer.test.js
index a5ed6c71..d9f23277 100644
--- a/packages/pdf/src/__tests__/PdfViewer.test.js
+++ b/packages/pdf/src/__tests__/PdfViewer.test.js
@@ -9,12 +9,7 @@ import PdfViewer from '../PdfViewer';
 it('renders', () => {
   PlaygroundRenderer.render(
     <View style={{ height: 500, width: '100%' }}>
-      <PdfViewer
-        source={{
-          uri:
-            'https://images.kiwi.com/content-media/kiwicom_brand_colours.pdf',
-        }}
-      />
+      <PdfViewer uri="https://images.kiwi.com/content-media/kiwicom_brand_colours.pdf" />
     </View>,
     false,
     'PdfViewer',
@@ -23,7 +18,7 @@ it('renders', () => {
 
 it('handles errors', () => {
   const onError = jest.fn();
-  const Component = new PdfViewer({ source: { uri: 'lol' }, onError });
+  const Component = new PdfViewer({ uri: 'lol', onError });
   // $FlowExpectedError: Intentionally overriting method to be able to test it without rendering component
   Component.setState = jest.fn();
   Component.onError();
diff --git a/packages/pdf/src/__tests__/__snapshots__/PdfViewer.test.js.snap b/packages/pdf/src/__tests__/__snapshots__/PdfViewer.test.js.snap
index 52279024..e72bc37e 100644
--- a/packages/pdf/src/__tests__/__snapshots__/PdfViewer.test.js.snap
+++ b/packages/pdf/src/__tests__/__snapshots__/PdfViewer.test.js.snap
@@ -10,11 +10,9 @@ exports[`renders 1`] = `
   }
 >
   <PdfViewer
-    source={
-      Object {
-        "uri": "https://images.kiwi.com/content-media/kiwicom_brand_colours.pdf",
-      }
-    }
+    cache={true}
+    expiration={Infinity}
+    uri="https://images.kiwi.com/content-media/kiwicom_brand_colours.pdf"
   />
 </View>
 `;