Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Expensify.cash to iOS and Android native share menus #1414

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:documentLaunchMode="never"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<!-- Any other mime types you want to support -->
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
Expand Down
246 changes: 246 additions & 0 deletions ios/ExpensifyCash.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions ios/ExpensifyCash/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import <Firebase.h>

#import <UserNotifications/UserNotifications.h>
#import <RNShareMenu/ShareMenuManager.h>

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
Expand Down Expand Up @@ -66,4 +67,11 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
#endif
}

- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [ShareMenuManager application:app openURL:url options:options];
}

@end
4 changes: 4 additions & 0 deletions ios/ExpensifyCash/Chat.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.chat.expensify.chat</string>
</array>
</dict>
</plist>
11 changes: 11 additions & 0 deletions ios/ExpensifyCash/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@
</dict>
</dict>
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>expensify-cash</string>
</array>
</dict>
</array>
<key>NSCameraUsageDescription</key>
<string>Your camera is used to create chat attachments.</string>
<key>NSLocationWhenInUseUsageDescription</key>
Expand Down
14 changes: 12 additions & 2 deletions ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@ target 'ExpensifyCash' do
post_install do |installer|
flipper_post_install(installer)

installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
end
end
end
end

target 'ShareExtension' do
use_react_native!

pod 'RNShareMenu', :path => '../node_modules/react-native-share-menu'
# Manually link packages here to keep your extension bundle size minimal
end
8 changes: 7 additions & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ PODS:
- Firebase/Crashlytics (~> 6.34.0)
- React-Core
- RNFBApp
- RNShareMenu (5.0.3):
- React
- RNSVG (12.1.0):
- React
- urbanairship-react-native (10.0.0):
Expand Down Expand Up @@ -485,6 +487,7 @@ DEPENDENCIES:
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
- RNShareMenu (from `../node_modules/react-native-share-menu`)
- RNSVG (from `../node_modules/react-native-svg`)
- urbanairship-react-native (from `../node_modules/urbanairship-react-native`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
Expand Down Expand Up @@ -595,6 +598,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-firebase/app"
RNFBCrashlytics:
:path: "../node_modules/@react-native-firebase/crashlytics"
RNShareMenu:
:path: "../node_modules/react-native-share-menu"
RNSVG:
:path: "../node_modules/react-native-svg"
urbanairship-react-native:
Expand Down Expand Up @@ -665,11 +670,12 @@ SPEC CHECKSUMS:
RNFBAnalytics: 2dc4dd9e2445faffca041b10447a23a71dcdabf8
RNFBApp: 7eacc7da7ab19f96c05e434017d44a9f09410da8
RNFBCrashlytics: 4870c14cf8833053b6b5648911abefe1923854d2
RNShareMenu: 555df30344da0d60947ccc781b0447ee9e5cd48f
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
urbanairship-react-native: dfb6dc22b2f41ccaadd636b73d51b448cd1b2bbc
Yoga: 7d13633d129fd179e01b8953d38d47be90db185a
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

PODFILE CHECKSUM: 41b806c7f131f87b716be1f1f9377532d6c9e43a
PODFILE CHECKSUM: dc731cc579bb3ba93e3ed267d05524db4ca67bc6

COCOAPODS: 1.10.0
24 changes: 24 additions & 0 deletions ios/ShareExtension/Base.lproj/MainInterface.storyboard
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
60 changes: 60 additions & 0 deletions ios/ShareExtension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>ShareExtension</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>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<string>TRUEPREDICATE</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
<key>HostAppBundleIdentifier</key>
<string>com.chat.expensify.chat</string>
<key>HostAppURLScheme</key>
<string>expensify-cash://</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<!-- For a full list of available options, visit https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/AppExtensionKeys.html#//apple_ref/doc/uid/TP40014212-SW10 -->
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
</dict>
</plist>
4 changes: 4 additions & 0 deletions ios/ShareExtension/ShareExtension-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridgeDelegate.h>
#import <React/RCTRootView.h>
10 changes: 10 additions & 0 deletions ios/ShareExtension/ShareExtension.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?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>com.apple.security.application-groups</key>
<array>
<string>group.com.chat.expensify.chat</string>
</array>
</dict>
</plist>
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"react-native-pdf": "^6.2.2",
"react-native-render-html": "^6.0.0-alpha.10",
"react-native-safe-area-context": "^3.1.4",
"react-native-share-menu": "^5.0.3",
"react-native-svg": "^12.1.0",
"react-native-web": "^0.14.1",
"react-native-web-webview": "^1.0.2",
Expand Down
10 changes: 10 additions & 0 deletions src/Expensify.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import HomePage from './pages/home/HomePage';
import NotFoundPage from './pages/NotFound';
import SetPasswordPage from './pages/SetPasswordPage';
import SignInPage from './pages/signin/SignInPage';
import SharePage from './pages/SharePage';
import listenToStorageEvents from './libs/listenToStorageEvents';
import * as ActiveClientManager from './libs/ActiveClientManager';
import ShareManager from './libs/ShareManager';
import ONYXKEYS from './ONYXKEYS';

import styles from './styles/styles';
Expand Down Expand Up @@ -76,6 +78,9 @@ class Expensify extends PureComponent {
key: ONYXKEYS.SESSION,
callback: this.removeLoadingState,
});

// Subscribe to share events
ShareManager.register();
}

componentDidUpdate(prevProps, prevState) {
Expand All @@ -84,6 +89,10 @@ class Expensify extends PureComponent {
}
}

componentWillUnmount() {
ShareManager.deregister();
}

/**
* When the authToken is updated, the app should remove the loading state and handle the authToken
*
Expand Down Expand Up @@ -129,6 +138,7 @@ class Expensify extends PureComponent {

<Route path={[ROUTES.SET_PASSWORD]} component={SetPasswordPage} />
<Route path={[ROUTES.NOT_FOUND]} component={NotFoundPage} />
<Route path={[ROUTES.SHARE]} component={SharePage} />
<Route path={[ROUTES.SIGNIN]} component={SignInPage} />
<Route
path={[ROUTES.HOME, ROUTES.ROOT]}
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export default {
IS_SIDEBAR_SHOWN: 'isSidebarShown',
BETAS: 'betas',

// Data shared to the app from other apps
SHARED_ITEM: 'sharedItem',

// NVP keys
NVP_PAYPAL_ME_ADDRESS: 'nvp_paypalMeAddress',

Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
NEW_CHAT: '/new/chat',
REPORT: '/r/:reportID',
getReportRoute: reportID => `/r/${reportID}`,
SHARE: '/share',
ROOT: '/',
SEARCH: '/search',
SET_PASSWORD: '/setpassword/:validateCode',
Expand Down
20 changes: 20 additions & 0 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,29 @@ function getSidebarOptions(reports, personalDetails, draftComments, activeReport
});
}

/**
* Build the options for the Share Page
*
* @param {Object} reports
* @param {Object} personalDetails
* @param {String} searchValue
* @returns {Object}
*/
function getShareOptions(reports, personalDetails, searchValue) {
return getOptions(reports, personalDetails, {}, 0, {
searchValue,
includeRecentReports: true,
includePersonalDetails: true,
includeMultipleParticipantReports: false,
maxRecentReportsToShow: 5,
showChatPreviewLine: true,
});
}

export {
getSearchOptions,
getNewChatOptions,
getNewGroupOptions,
getSidebarOptions,
getShareOptions,
};
30 changes: 30 additions & 0 deletions src/libs/ShareManager/PropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import PropTypes from 'prop-types';

/**
* SharedItem PropTypes.
*/
export default {
// Shared item object
sharedItem: PropTypes.shape({
// Shared item type (one of the `ShareType.*`)
type: PropTypes.string.isRequired,

// Shared item data. Differs depending on type
data: PropTypes.oneOfType([
// Text or HTML for `ShareType.TEXT`
PropTypes.string,

// File data structure for `ShareType.FILE`
PropTypes.shape({
// File name
name: PropTypes.string.isRequired,

// File MIME type
type: PropTypes.string.isRequired,

// File URI
uri: PropTypes.string.isRequired,
}),
]).isRequired,
}),
};
7 changes: 7 additions & 0 deletions src/libs/ShareManager/ShareType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Types of shared content.
*/
export default {
FILE: 'file',
TEXT: 'text',
nickmurray47 marked this conversation as resolved.
Show resolved Hide resolved
};
14 changes: 14 additions & 0 deletions src/libs/ShareManager/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This is a no-op component for desktop and web,
// because sharing to the app is only supported on mobile
import ShareType from './ShareType';
import sharedItemPropTypes from './PropTypes';

export {
sharedItemPropTypes,
};

export default {
register: () => {},
deregister: () => {},
TYPE: ShareType,
};
Loading