From 410748b37a556a33774611fc60b8494b2f50911d Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 28 Aug 2024 13:19:44 +0200 Subject: [PATCH 001/130] setup share --- ios/NewExpensify.xcodeproj/project.pbxproj | 16 +++- ios/RCTShareActionHandlerModule.h | 16 ++++ ios/RCTShareActionHandlerModule.m | 85 +++++++++++++++++ src/NAVIGATORS.ts | 1 + src/ROUTES.ts | 1 + src/SCREENS.ts | 3 + .../Navigation/AppNavigator/AuthScreens.tsx | 7 ++ .../ModalStackNavigators/index.tsx | 5 + src/libs/Navigation/linkingConfig/config.ts | 7 ++ src/libs/Navigation/types.ts | 6 ++ src/modules/ShareActionHandlerModule.ts | 9 ++ src/pages/Share/ShareRootPage.tsx | 91 +++++++++++++++++++ 12 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 ios/RCTShareActionHandlerModule.h create mode 100644 ios/RCTShareActionHandlerModule.m create mode 100644 src/modules/ShareActionHandlerModule.ts create mode 100644 src/pages/Share/ShareRootPage.tsx diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 0f9edeab601d..375f8e91ddbe 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -41,7 +41,9 @@ D27CE6B77196EF3EF450EEAC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.mm */; }; DDCB2E57F334C143AC462B43 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D20D83B0E39BA6D21761E72 /* ExpoModulesProvider.swift */; }; - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + E5A27B912C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */; }; + E5A27B922C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */; }; E9DF872D2525201700607FDC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9DF872C2525201700607FDC /* AirshipConfig.plist */; }; ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; @@ -89,7 +91,7 @@ 083353EA2B5AB22900C603C0 /* success.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = success.mp3; path = ../assets/sounds/success.mp3; sourceTree = ""; }; 0CDA8E33287DD650004ECBEC /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = NewExpensify/AppDelegate.mm; sourceTree = ""; }; 0CDA8E36287DD6A0004ECBEC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = NewExpensify/Images.xcassets; sourceTree = ""; }; - 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = NewExpensify/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = NewExpensify/PrivacyInfo.xcprivacy; sourceTree = ""; }; 0F5BE0CD252686320097D869 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 0F5E534E263B73D5004CA14F /* EnvironmentChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EnvironmentChecker.h; sourceTree = ""; }; 0F5E534F263B73FD004CA14F /* EnvironmentChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EnvironmentChecker.m; sourceTree = ""; }; @@ -140,6 +142,8 @@ DD7904292792E76D004484B4 /* RCTBootSplash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTBootSplash.h; path = NewExpensify/RCTBootSplash.h; sourceTree = ""; }; DD79042A2792E76D004484B4 /* RCTBootSplash.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RCTBootSplash.mm; path = NewExpensify/RCTBootSplash.mm; sourceTree = ""; }; E5428460BDBED9E1BA8B3599 /* Pods-NewExpensify-NewExpensifyTests.debugdevelopment.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debugdevelopment.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debugdevelopment.xcconfig"; sourceTree = ""; }; + E5A27B8F2C7F2F51002C36BF /* RCTShareActionHandlerModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTShareActionHandlerModule.h; sourceTree = ""; }; + E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTShareActionHandlerModule.m; sourceTree = ""; }; E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Regular.otf"; path = "../assets/fonts/native/ExpensifyMono-Regular.otf"; sourceTree = ""; }; E9DF872C2525201700607FDC /* AirshipConfig.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = AirshipConfig.plist; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -165,8 +169,8 @@ buildActionMask = 2147483647; files = ( 383643682B6D4AE2005BB9AE /* DeviceCheck.framework in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, 8744C5400E24E379441C04A4 /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -267,6 +271,8 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + E5A27B8F2C7F2F51002C36BF /* RCTShareActionHandlerModule.h */, + E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */, 499B0DA92BE2A1C000CABFB0 /* PrivacyInfo.xcprivacy */, 374FB8D528A133A7000D84EF /* OriginImageRequestHandler.h */, 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */, @@ -879,6 +885,7 @@ 0CDA8E35287DD650004ECBEC /* AppDelegate.mm in Sources */, 7041848626A8E47D00E09F4D /* RCTStartupTimer.m in Sources */, 7F5E81F06BCCF61AD02CEA06 /* ExpoModulesProvider.swift in Sources */, + E5A27B922C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -886,6 +893,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E5A27B912C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */, 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */, 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */, 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */, diff --git a/ios/RCTShareActionHandlerModule.h b/ios/RCTShareActionHandlerModule.h new file mode 100644 index 000000000000..0dc50ae51a96 --- /dev/null +++ b/ios/RCTShareActionHandlerModule.h @@ -0,0 +1,16 @@ +// +// RCTShareActionHandlerModule.h +// NewExpensify +// +// Created by Bartek Krasoń on 28/08/2024. +// + +#ifndef RCTShareActionHandlerModule_h +#define RCTShareActionHandlerModule_h + +#import + +@interface RCTShareActionHandlerModule : NSObject +@end + +#endif /* RCTShareActionHandlerModule_h */ diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m new file mode 100644 index 000000000000..ef25fac4806d --- /dev/null +++ b/ios/RCTShareActionHandlerModule.m @@ -0,0 +1,85 @@ +// +// RCTShareActionHandlerModule.m +// NewExpensify +// +// Created by Bartek Krasoń on 28/08/2024. +// + +#import +#import "RCTShareActionHandlerModule.h" +#import + +NSString *const ShareExtensionGroupIdentifier = @"group.com.new-expensify"; +NSString *const ShareExtensionFilesKey = @"ShareFiles"; +NSString *const ShareImageFileExtension = @".png"; + +@implementation RCTShareActionHandlerModule + +RCT_EXPORT_MODULE(RCTShareActionHandlerModule); + +RCT_EXPORT_METHOD(processFiles:(RCTResponseSenderBlock)callback) +{ + RCTLogInfo(@"Processing share extension files"); + NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:ShareExtensionGroupIdentifier]; + NSString *sharedImagesFolderPath = [defaults objectForKey:ShareExtensionFilesKey]; + + // Set default to NULL so it is not used when app is launched regularly. + [defaults setObject:NULL forKey:ShareExtensionFilesKey]; + [defaults synchronize]; + + if (sharedImagesFolderPath == NULL) { + NSLog(@"handleShareExtension Missing 'folder' in shareExtensionData"); + return; + } + + // Get image file names + NSError *error = nil; + NSArray *imageSrcPath = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:sharedImagesFolderPath error:&error]; + + if (imageSrcPath.count == 0) { + NSLog(@"handleShareAction Failed to find images in 'sharedImagesFolderPath'"); + return; + } + NSLog(@"handleShareAction shared %lu images", imageSrcPath.count); + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSMutableArray *imageFinalPaths = [NSMutableArray array]; + + for (int i = 0; i < imageSrcPath.count; i++) { + if (imageSrcPath[i] == NULL) { + NSLog(@"handleShareAction Invalid image in position %d, imageSrcPath[i] is nil", i); + continue; + } + NSLog(@"handleShareAction Valid image in position %d", i); + NSString *srcImageAbsolutePath = [sharedImagesFolderPath stringByAppendingPathComponent:imageSrcPath[i]]; + UIImage *smartScanImage = [[UIImage alloc] initWithContentsOfFile:srcImageAbsolutePath]; + if (smartScanImage == NULL) { + NSLog(@"handleShareAction Failed to load image %@", srcImageAbsolutePath); + continue; + } + + // Correct image orientation so it displays correctly on expenses. + UIGraphicsBeginImageContext(smartScanImage.size); + [smartScanImage drawAtPoint:CGPointMake(0, 0)]; + CGContextRotateCTM (UIGraphicsGetCurrentContext(), 90 * M_PI/180); + smartScanImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + // Save image to file. + NSString *pathName = [NSString stringWithFormat:@"%@%@", [[NSUUID UUID] UUIDString], ShareImageFileExtension]; + NSString *path = [documentsDirectory stringByAppendingPathComponent:pathName]; + NSData *data = UIImagePNGRepresentation(smartScanImage); + [data writeToFile:path atomically:YES]; + [imageFinalPaths addObject:path]; + } + + // Delete shared image folder + if (![[NSFileManager defaultManager] removeItemAtPath:sharedImagesFolderPath error:&error]) { + NSLog(@"Failed to delete shared image folder: %@, error: %@", sharedImagesFolderPath, error); + } + + callback(@[@[imageFinalPaths]]); +} + +@end \ No newline at end of file diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index 0b4a86c99247..d7cf704d00ae 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -12,4 +12,5 @@ export default { WELCOME_VIDEO_MODAL_NAVIGATOR: 'WelcomeVideoModalNavigator', EXPLANATION_MODAL_NAVIGATOR: 'ExplanationModalNavigator', FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator', + SHARE_MODAL_NAVIGATOR: 'ShareModalNavigator', } as const; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 97a86d272530..4d0d245ce6e1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -989,6 +989,7 @@ const ROUTES = { getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo), }, PROCESS_MONEY_REQUEST_HOLD: 'hold-expense-educational', + SHARE_ROOT: 'share/root', TRAVEL_MY_TRIPS: 'travel', TRAVEL_TCS: 'travel/terms', TRACK_TRAINING_MODAL: 'track-training', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 9634cc1b02ac..0bf737caa4e2 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -508,6 +508,9 @@ const SCREENS = { GET_ASSISTANCE: 'GetAssistance', REFERRAL_DETAILS: 'Referral_Details', KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', + SHARE: { + ROOT: 'Share_Root', + }, TRANSACTION_RECEIPT: 'TransactionReceipt', FEATURE_TRAINING_ROOT: 'FeatureTraining_Root', RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root', diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index d63d52ab7365..40caf8dcb724 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -52,6 +52,7 @@ import CENTRAL_PANE_SCREENS from './CENTRAL_PANE_SCREENS'; import createCustomStackNavigator from './createCustomStackNavigator'; import defaultScreenOptions from './defaultScreenOptions'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; +import {ShareModalStackNavigator} from './ModalStackNavigators'; import BottomTabNavigator from './Navigators/BottomTabNavigator'; import ExplanationModalNavigator from './Navigators/ExplanationModalNavigator'; import FeatureTrainingModalNavigator from './Navigators/FeatureTrainingModalNavigator'; @@ -490,6 +491,12 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie options={screenOptions.fullScreen} component={DesktopSignInRedirectPage} /> + require('../../../../pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage').default, }); +const ShareModalStackNavigator = createModalStackNavigator({ + [SCREENS.SHARE.ROOT]: () => require('@pages/Share/ShareRootPage').default, +}); + export { AddPersonalBankAccountModalStackNavigator, EditRequestStackNavigator, @@ -570,4 +574,5 @@ export { SearchReportModalStackNavigator, RestrictedActionModalStackNavigator, SearchAdvancedFiltersModalStackNavigator, + ShareModalStackNavigator, }; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6ee3b14b64ed..b77110dd4eee 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1122,6 +1122,13 @@ const config: LinkingOptions['config'] = { }, }, }, + [NAVIGATORS.SHARE_MODAL_NAVIGATOR]: { + path: ROUTES.SHARE_ROOT, + initialRouteName: SCREENS.SHARE.ROOT, + screens: { + [SCREENS.SHARE.ROOT]: ROUTES.SHARE_ROOT, + }, + }, }, }; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 28544da90423..d17922e30189 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1266,6 +1266,10 @@ type SharedScreensParamList = { }; }; +type ShareNavigatorParamList = { + [SCREENS.SHARE.ROOT]: undefined; +}; + type PublicScreensParamList = SharedScreensParamList & { [SCREENS.UNLINK_LOGIN]: { accountID?: string; @@ -1317,6 +1321,7 @@ type AuthScreensParamList = CentralPaneScreensParamList & readonly?: boolean; }; [SCREENS.CONNECTION_COMPLETE]: undefined; + [NAVIGATORS.SHARE_MODAL_NAVIGATOR]: NavigatorScreenParams; }; type SearchReportParamList = { @@ -1408,4 +1413,5 @@ export type { SearchReportParamList, SearchAdvancedFiltersParamList, RestrictedActionParamList, + ShareNavigatorParamList, }; diff --git a/src/modules/ShareActionHandlerModule.ts b/src/modules/ShareActionHandlerModule.ts new file mode 100644 index 000000000000..34add7ae6e6a --- /dev/null +++ b/src/modules/ShareActionHandlerModule.ts @@ -0,0 +1,9 @@ +import {NativeModules} from 'react-native'; + +const {ShareActionHandlerModule} = NativeModules; + +type ShareActionHandlerType = { + processFiles(callback: (array: string[]) => void): void; +}; + +export default ShareActionHandlerModule as ShareActionHandlerType; diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx new file mode 100644 index 000000000000..10c549895fe6 --- /dev/null +++ b/src/pages/Share/ShareRootPage.tsx @@ -0,0 +1,91 @@ +import React, {useEffect, useRef} from 'react'; +import type {AppStateStatus} from 'react-native'; +import {AppState, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import Navigation from '@navigation/Navigation'; +import ShareActionHandlerModule from '@src/modules/ShareActionHandlerModule'; + +type ShareRootPageOnyxProps = { + selectedTab: OnyxEntry; +}; + +type ShareRootPageProps = ShareRootPageOnyxProps; + +function ShareRootPage() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const fileIsScannable = false; + const appState = useRef(AppState.currentState); + + const handleProcessFiles = () => { + console.log('PROCESS FILES ATTEMPT'); + ShareActionHandlerModule.processFiles((processedFiles) => { + // eslint-disable-next-line no-console + console.log('PROCESSED FILES ', processedFiles); + }); + }; + + const handleAppStateChange = (nextAppState: AppStateStatus) => { + if (appState.current.match(/inactive|background/) && nextAppState === 'active') { + handleProcessFiles(); + } + appState.current = nextAppState; + }; + + useEffect(() => { + const changeSubscription = AppState.addEventListener('change', handleAppStateChange); + + handleProcessFiles(); + + return () => { + changeSubscription.remove(); + }; + }, []); + + const navigateBack = () => { + Navigation.dismissModal(); + }; + + return ( + + + + {/* ( + + )} + > + {() => } + {() => } + */} + + + ); +} + +ShareRootPage.displayName = 'ShareRootPage'; + +export default ShareRootPage; From 3cbb982d4e1f31f41bf1d46aaca7264768e19c54 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 28 Aug 2024 13:46:04 +0200 Subject: [PATCH 002/130] wire up groups, update group id --- ios/NewExpensify.xcodeproj/project.pbxproj | 514 +++++++++++++++++- ios/RCTShareActionHandlerModule.m | 2 +- .../Base.lproj/MainInterface.storyboard | 24 + ios/ShareViewController/Info.plist | 18 + .../ShareViewController.swift | 164 ++++++ src/pages/Share/ShareRootPage.tsx | 1 - 6 files changed, 720 insertions(+), 3 deletions(-) create mode 100644 ios/ShareViewController/Base.lproj/MainInterface.storyboard create mode 100644 ios/ShareViewController/Info.plist create mode 100644 ios/ShareViewController/ShareViewController.swift diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 375f8e91ddbe..71d8413326df 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -44,6 +44,9 @@ E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; E5A27B912C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */; }; E5A27B922C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */; }; + E5A27B9A2C7F427F002C36BF /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B992C7F427F002C36BF /* ShareViewController.swift */; }; + E5A27B9D2C7F427F002C36BF /* Base in Resources */ = {isa = PBXBuildFile; fileRef = E5A27B9C2C7F427F002C36BF /* Base */; }; + E5A27BA12C7F427F002C36BF /* ShareViewController.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E5A27B972C7F427F002C36BF /* ShareViewController.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E9DF872D2525201700607FDC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9DF872C2525201700607FDC /* AirshipConfig.plist */; }; ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; @@ -65,6 +68,13 @@ remoteGlobalIDString = 7FD73C9A2B23CE9500420AF3; remoteInfo = NotificationServiceExtension; }; + E5A27B9F2C7F427F002C36BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E5A27B962C7F427F002C36BF; + remoteInfo = ShareViewController; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -75,6 +85,7 @@ dstSubfolderSpec = 13; files = ( 7FD73CA22B23CE9500420AF3 /* NotificationServiceExtension.appex in Embed Foundation Extensions */, + E5A27BA12C7F427F002C36BF /* ShareViewController.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -144,6 +155,10 @@ E5428460BDBED9E1BA8B3599 /* Pods-NewExpensify-NewExpensifyTests.debugdevelopment.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debugdevelopment.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debugdevelopment.xcconfig"; sourceTree = ""; }; E5A27B8F2C7F2F51002C36BF /* RCTShareActionHandlerModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTShareActionHandlerModule.h; sourceTree = ""; }; E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTShareActionHandlerModule.m; sourceTree = ""; }; + E5A27B972C7F427F002C36BF /* ShareViewController.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareViewController.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + E5A27B992C7F427F002C36BF /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; + E5A27B9C2C7F427F002C36BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + E5A27B9E2C7F427F002C36BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E704648954784DDFBAADF568 /* ExpensifyMono-Regular.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyMono-Regular.otf"; path = "../assets/fonts/native/ExpensifyMono-Regular.otf"; sourceTree = ""; }; E9DF872C2525201700607FDC /* AirshipConfig.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = AirshipConfig.plist; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -183,6 +198,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E5A27B942C7F427F002C36BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -286,6 +308,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* NewExpensifyTests */, 7FD73C9C2B23CE9500420AF3 /* NotificationServiceExtension */, + E5A27B982C7F427F002C36BF /* ShareViewController */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, EC29677F0A49C2946A495A33 /* Pods */, @@ -303,6 +326,7 @@ 13B07F961A680F5B00A75B9A /* New Expensify Dev.app */, 00E356EE1AD99517003FC87E /* NewExpensifyTests.xctest */, 7FD73C9B2B23CE9500420AF3 /* NotificationServiceExtension.appex */, + E5A27B972C7F427F002C36BF /* ShareViewController.appex */, ); name = Products; sourceTree = ""; @@ -334,6 +358,16 @@ name = Resources; sourceTree = ""; }; + E5A27B982C7F427F002C36BF /* ShareViewController */ = { + isa = PBXGroup; + children = ( + E5A27B992C7F427F002C36BF /* ShareViewController.swift */, + E5A27B9B2C7F427F002C36BF /* MainInterface.storyboard */, + E5A27B9E2C7F427F002C36BF /* Info.plist */, + ); + path = ShareViewController; + sourceTree = ""; + }; EC29677F0A49C2946A495A33 /* Pods */ = { isa = PBXGroup; children = ( @@ -409,6 +443,7 @@ ); dependencies = ( 7FD73CA12B23CE9500420AF3 /* PBXTargetDependency */, + E5A27BA02C7F427F002C36BF /* PBXTargetDependency */, ); name = NewExpensify; productName = NewExpensify; @@ -433,13 +468,30 @@ productReference = 7FD73C9B2B23CE9500420AF3 /* NotificationServiceExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + E5A27B962C7F427F002C36BF /* ShareViewController */ = { + isa = PBXNativeTarget; + buildConfigurationList = E5A27BA22C7F4280002C36BF /* Build configuration list for PBXNativeTarget "ShareViewController" */; + buildPhases = ( + E5A27B932C7F427F002C36BF /* Sources */, + E5A27B942C7F427F002C36BF /* Frameworks */, + E5A27B952C7F427F002C36BF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ShareViewController; + productName = ShareViewController; + productReference = E5A27B972C7F427F002C36BF /* ShareViewController.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1500; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1130; TargetAttributes = { 00E356ED1AD99517003FC87E = { @@ -458,6 +510,9 @@ DevelopmentTeam = 368M544MTT; ProvisioningStyle = Manual; }; + E5A27B962C7F427F002C36BF = { + CreatedOnToolsVersion = 15.4; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "NewExpensify" */; @@ -476,6 +531,7 @@ 13B07F861A680F5B00A75B9A /* NewExpensify */, 00E356ED1AD99517003FC87E /* NewExpensifyTests */, 7FD73C9A2B23CE9500420AF3 /* NotificationServiceExtension */, + E5A27B962C7F427F002C36BF /* ShareViewController */, ); }; /* End PBXProject section */ @@ -521,6 +577,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E5A27B952C7F427F002C36BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E5A27B9D2C7F427F002C36BF /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -914,6 +978,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E5A27B932C7F427F002C36BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E5A27B9A2C7F427F002C36BF /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -927,8 +999,24 @@ target = 7FD73C9A2B23CE9500420AF3 /* NotificationServiceExtension */; targetProxy = 7FD73CA02B23CE9500420AF3 /* PBXContainerItemProxy */; }; + E5A27BA02C7F427F002C36BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E5A27B962C7F427F002C36BF /* ShareViewController */; + targetProxy = E5A27B9F2C7F427F002C36BF /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + E5A27B9B2C7F427F002C36BF /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E5A27B9C2C7F427F002C36BF /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* DebugDevelopment */ = { isa = XCBuildConfiguration; @@ -1072,6 +1160,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 707151F8A56D2431696273AB /* Pods-NewExpensify.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -1228,6 +1317,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 46B1FE4DE317D30C25A74C15 /* Pods-NewExpensify.debugdevelopment.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -1268,6 +1358,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = F8839E9820F4C312BD1C9339 /* Pods-NewExpensify.releasedevelopment.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -2033,6 +2124,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = EFA5CA89CC675CA3370CF89E /* Pods-NewExpensify.debugproduction.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -2182,6 +2274,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 289D101B1119F719AAC9EB8B /* Pods-NewExpensify.debugadhoc.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconAdHoc; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -2325,6 +2418,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 417E30386DDC804B3693037A /* Pods-NewExpensify.releaseproduction.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -2464,6 +2558,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8EFE0319D586C1078DB926FD /* Pods-NewExpensify.releaseadhoc.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconAdHoc; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CLANG_CXX_LANGUAGE_STANDARD = "c++20"; @@ -2525,6 +2620,409 @@ }; name = ReleaseAdHoc; }; + E5A27BA32C7F4280002C36BF /* DebugDevelopment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugDevelopment; + }; + E5A27BA42C7F4280002C36BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E5A27BA52C7F4280002C36BF /* DebugAdHoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugAdHoc; + }; + E5A27BA62C7F4280002C36BF /* DebugProduction */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugProduction; + }; + E5A27BA72C7F4280002C36BF /* ReleaseDevelopment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseDevelopment; + }; + E5A27BA82C7F4280002C36BF /* ReleaseAdHoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseAdHoc; + }; + E5A27BA92C7F4280002C36BF /* ReleaseProduction */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 368M544MTT; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareViewController/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareViewController; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = ReleaseProduction; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2584,6 +3082,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = DebugDevelopment; }; + E5A27BA22C7F4280002C36BF /* Build configuration list for PBXNativeTarget "ShareViewController" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E5A27BA32C7F4280002C36BF /* DebugDevelopment */, + E5A27BA42C7F4280002C36BF /* Debug */, + E5A27BA52C7F4280002C36BF /* DebugAdHoc */, + E5A27BA62C7F4280002C36BF /* DebugProduction */, + E5A27BA72C7F4280002C36BF /* ReleaseDevelopment */, + E5A27BA82C7F4280002C36BF /* ReleaseAdHoc */, + E5A27BA92C7F4280002C36BF /* ReleaseProduction */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = DebugDevelopment; + }; /* End XCConfigurationList section */ }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m index ef25fac4806d..4a1c0f1f8774 100644 --- a/ios/RCTShareActionHandlerModule.m +++ b/ios/RCTShareActionHandlerModule.m @@ -9,7 +9,7 @@ #import "RCTShareActionHandlerModule.h" #import -NSString *const ShareExtensionGroupIdentifier = @"group.com.new-expensify"; +NSString *const ShareExtensionGroupIdentifier = @"group.com.chat.expensify.chat"; NSString *const ShareExtensionFilesKey = @"ShareFiles"; NSString *const ShareImageFileExtension = @".png"; diff --git a/ios/ShareViewController/Base.lproj/MainInterface.storyboard b/ios/ShareViewController/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000000..286a50894d87 --- /dev/null +++ b/ios/ShareViewController/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist new file mode 100644 index 000000000000..4b1f7e7057cc --- /dev/null +++ b/ios/ShareViewController/Info.plist @@ -0,0 +1,18 @@ + + + + + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + TRUEPREDICATE + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/ios/ShareViewController/ShareViewController.swift b/ios/ShareViewController/ShareViewController.swift new file mode 100644 index 000000000000..07df3c44082d --- /dev/null +++ b/ios/ShareViewController/ShareViewController.swift @@ -0,0 +1,164 @@ +import UIKit +import Social +import MobileCoreServices +import UniformTypeIdentifiers +import Intents +import os + +class ShareViewController: UIViewController { + let APP_GROUP_ID = "group.com.chat.expensify.chat" + + enum ImageSaveError: String { + case IncorrectType + case CouldNotLoad + case URLError + case GroupSharedFolderNotFound + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + let content = extensionContext!.inputItems[0] as! NSExtensionItem + let contentType = UTType.image.identifier + + // TODO: Remove when app group setup is done + self.openMainApp() + + os_log("ShareViewController.viewDidAppear contentType: \(contentType)") + os_log("ShareViewController.viewDidAppear content: \(content)") + saveImageToAppGroup(content: content, contentType: contentType) { (error) in + guard error == nil else { + os_log("Sharing error: \(error!.rawValue)") + self.extensionContext!.cancelRequest(withError: NSError(domain: "", code: 0, userInfo: nil)) + return + } + self.openMainApp() + } + + } + + func saveImageToFolder(folder: URL, filename: String, imageData: NSData) -> URL? { + let filePath = folder.appendingPathComponent(filename) + do { + try imageData.write(to: filePath, options: .completeFileProtection) + return filePath + } catch { + os_log("Unexpected saveImageToFolder error: \(error).") + return nil + } + } + + + private func saveImageToAppGroup(content: NSExtensionItem, contentType: String, completion: @escaping (ImageSaveError?) -> Void) { + guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: self.APP_GROUP_ID) else { + completion(.GroupSharedFolderNotFound) + os_log("ShareViewController.saveImageToAppGroup failed to get group shared folder") + return + } + let sharedImageFolder = groupURL.appendingPathComponent("sharedImages", isDirectory: true) + + // Try to create folder to share images if it doesn't exist + do { + try FileManager.default.createDirectory(atPath: sharedImageFolder.path, withIntermediateDirectories: true, attributes: nil) + } catch { + os_log("Failed to create folder: \(sharedImageFolder.path), error: \(error)") + return + } + + // Clear any image that was in the folder (in case it already existed) + do { + let filePaths = try FileManager.default.contentsOfDirectory(atPath: sharedImageFolder.path) + for filePath in filePaths { + try FileManager.default.removeItem(atPath: sharedImageFolder.appendingPathComponent(filePath).path) + } + } catch { + os_log("Could not clear temp folder: \(error)") + return + } + + // Process shared images and put them in the shared folder + var imagePaths = [String]() + let group = DispatchGroup() + + guard let attachments = content.attachments else { + completion(.CouldNotLoad) + os_log("ShareViewController.saveImageToAppGroup Could not load") + return + } + + // This is ran for each image that is selected. + for attatchment in attachments { + group.enter() + + guard attatchment.hasItemConformingToTypeIdentifier(contentType) else { + completion(.IncorrectType) + group.leave() + continue + } + attatchment.loadItem(forTypeIdentifier: contentType, options: nil) { (data, error) in + guard error == nil else { + DispatchQueue.main.async { + completion(.CouldNotLoad) + group.leave() + } + return + } + + // Try to get image as URL. Usually the case when extension is run from photos or messaging app. + if let url = data as? NSURL, let imageData = NSData(contentsOf: url as URL) { + // Add filename to path + guard let filename = url.lastPathComponent else { + // Provide some filename? + os_log("Skipping image, no filename.") + group.leave() + return + } + if let imageFinalPath = self.saveImageToFolder(folder: sharedImageFolder, filename: filename, imageData: imageData) { + imagePaths.append(imageFinalPath.path) + } else { + os_log("Skipping image \(String(describing: filename)), failed to save") + } + group.leave() + // Try to get image as UIImage. This is the case when extension is run from screenshot editor. + } else if let image = data as? UIImage, let imageData = image.pngData() as NSData? { + let filename = "image_name_\(imagePaths.count).png" + if let imageFinalPath = self.saveImageToFolder(folder: sharedImageFolder, filename: filename, imageData: imageData) { + imagePaths.append(imageFinalPath.path) + } else { + os_log("Skipping image \(String(describing: filename)), failed to save") + } + group.leave() + } else { + DispatchQueue.main.async { + completion(.URLError) + group.leave() + } + return + } + } + } + } + + private func openMainApp() { + let url = URL(string: "new-expensify://share/root")! + if launchApp(customURL: url) { + self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) + } else { + self.extensionContext!.cancelRequest(withError: NSError(domain: "", code: 0, userInfo: nil)) + } + } + + private func launchApp(customURL: URL?) -> Bool { + guard let url = customURL else { return false } + let selectorOpenURL = sel_registerName("openURL:") + var responder: UIResponder? = self + while responder != nil { + if responder!.responds(to: selectorOpenURL) { + responder!.perform(selectorOpenURL, with: url) + return true + } + responder = responder!.next + } + return false + } +} \ No newline at end of file diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx index 10c549895fe6..38e3a9441d38 100644 --- a/src/pages/Share/ShareRootPage.tsx +++ b/src/pages/Share/ShareRootPage.tsx @@ -2,7 +2,6 @@ import React, {useEffect, useRef} from 'react'; import type {AppStateStatus} from 'react-native'; import {AppState, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; From 38ec09448024ebb16a18a68ad2ed5dc47c2285b6 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Wed, 28 Aug 2024 19:17:23 +0200 Subject: [PATCH 003/130] update app group id --- ios/RCTShareActionHandlerModule.m | 2 +- ios/ShareViewController/ShareViewController.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m index 4a1c0f1f8774..827c96c4f7c9 100644 --- a/ios/RCTShareActionHandlerModule.m +++ b/ios/RCTShareActionHandlerModule.m @@ -9,7 +9,7 @@ #import "RCTShareActionHandlerModule.h" #import -NSString *const ShareExtensionGroupIdentifier = @"group.com.chat.expensify.chat"; +NSString *const ShareExtensionGroupIdentifier = @"group.com.expensify.chat"; NSString *const ShareExtensionFilesKey = @"ShareFiles"; NSString *const ShareImageFileExtension = @".png"; diff --git a/ios/ShareViewController/ShareViewController.swift b/ios/ShareViewController/ShareViewController.swift index 07df3c44082d..008bd3f6eb2a 100644 --- a/ios/ShareViewController/ShareViewController.swift +++ b/ios/ShareViewController/ShareViewController.swift @@ -6,7 +6,7 @@ import Intents import os class ShareViewController: UIViewController { - let APP_GROUP_ID = "group.com.chat.expensify.chat" + let APP_GROUP_ID = "group.com.expensify.chat" enum ImageSaveError: String { case IncorrectType From b5e1641b9b2df37daaa240b36f1e2ae7d1e052dc Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 1 Oct 2024 22:47:36 +0200 Subject: [PATCH 004/130] fix single image share --- ios/NewExpensify.xcodeproj/project.pbxproj | 22 +++++- ios/NewExpensify/Chat.entitlements | 4 + ios/Podfile.lock | 4 +- ios/RCTShareActionHandlerModule.m | 77 +++++++++---------- .../ShareViewController.entitlements | 10 +++ .../ShareViewController.swift | 21 +++-- src/pages/Share/ShareRootPage.tsx | 13 +++- 7 files changed, 97 insertions(+), 54 deletions(-) create mode 100644 ios/ShareViewController/ShareViewController.entitlements diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 71d8413326df..5784dddc466a 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -41,7 +41,7 @@ D27CE6B77196EF3EF450EEAC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.mm */; }; DDCB2E57F334C143AC462B43 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D20D83B0E39BA6D21761E72 /* ExpoModulesProvider.swift */; }; - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; E5A27B912C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */; }; E5A27B922C7F2FA8002C36BF /* RCTShareActionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B902C7F2FA8002C36BF /* RCTShareActionHandlerModule.m */; }; E5A27B9A2C7F427F002C36BF /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A27B992C7F427F002C36BF /* ShareViewController.swift */; }; @@ -123,6 +123,7 @@ 41D2EDB009CF19BA59C5181C /* Pods-NotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debug.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; 44BF435285B94E5B95F90994 /* ExpensifyNewKansas-Medium.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "ExpensifyNewKansas-Medium.otf"; path = "../assets/fonts/native/ExpensifyNewKansas-Medium.otf"; sourceTree = ""; }; 46B1FE4DE317D30C25A74C15 /* Pods-NewExpensify.debugdevelopment.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify.debugdevelopment.xcconfig"; path = "Target Support Files/Pods-NewExpensify/Pods-NewExpensify.debugdevelopment.xcconfig"; sourceTree = ""; }; + 46CFEEC62CA1C2E0003D36CC /* ShareViewController.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareViewController.entitlements; sourceTree = ""; }; 48E7775E0D42D3E3F53A5B99 /* Pods-NotificationServiceExtension.releaseadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.releaseadhoc.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.releaseadhoc.xcconfig"; sourceTree = ""; }; 499B0DA92BE2A1C000CABFB0 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 4A39BBFB1A6AA6A0EB08878C /* Pods-NotificationServiceExtension.debugproduction.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.debugproduction.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.debugproduction.xcconfig"; sourceTree = ""; }; @@ -184,8 +185,8 @@ buildActionMask = 2147483647; files = ( 383643682B6D4AE2005BB9AE /* DeviceCheck.framework in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, 8744C5400E24E379441C04A4 /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -361,6 +362,7 @@ E5A27B982C7F427F002C36BF /* ShareViewController */ = { isa = PBXGroup; children = ( + 46CFEEC62CA1C2E0003D36CC /* ShareViewController.entitlements */, E5A27B992C7F427F002C36BF /* ShareViewController.swift */, E5A27B9B2C7F427F002C36BF /* MainInterface.storyboard */, E5A27B9E2C7F427F002C36BF /* Info.plist */, @@ -2631,6 +2633,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; @@ -2653,6 +2656,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2675,6 +2679,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; @@ -2697,6 +2702,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2719,6 +2725,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -2740,6 +2747,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2762,6 +2770,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -2783,6 +2792,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2827,6 +2837,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; @@ -2859,6 +2870,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2904,6 +2916,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; @@ -2935,6 +2948,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -2980,6 +2994,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = ShareViewController/ShareViewController.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; @@ -3011,6 +3026,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.expensify.chat.dev.ShareViewController; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/ios/NewExpensify/Chat.entitlements b/ios/NewExpensify/Chat.entitlements index 165745a3c1a0..831c5b2d5c8f 100644 --- a/ios/NewExpensify/Chat.entitlements +++ b/ios/NewExpensify/Chat.entitlements @@ -16,5 +16,9 @@ com.apple.developer.usernotifications.communication + com.apple.security.application-groups + + group.com.expensify.new + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b3fb491d614a..a7e77587a0d7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3057,7 +3057,7 @@ SPEC CHECKSUMS: AirshipFrameworkProxy: dbd862dc6fb21b13e8b196458d626123e2a43a50 AirshipServiceExtension: 9c73369f426396d9fb9ff222d86d842fac76ba46 AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa - boost: 26992d1adf73c1c7676360643e687aee6dda994b + boost: 4cb898d0bf20404aab1850c656dcea009429d6c1 BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 EXAV: 62e66b067185d630fe4cb4aa6eb0e48f72e67e0f @@ -3208,7 +3208,7 @@ SPEC CHECKSUMS: SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Turf: aa2ede4298009639d10db36aba1a7ebaad072a5e VisionCamera: c6c8aa4b028501fc87644550fbc35a537d4da3fb - Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae + Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 PODFILE CHECKSUM: e479ec84cb53e5fd463486d71dfee91708d3fd9a diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m index 827c96c4f7c9..273b6ea3f062 100644 --- a/ios/RCTShareActionHandlerModule.m +++ b/ios/RCTShareActionHandlerModule.m @@ -9,9 +9,9 @@ #import "RCTShareActionHandlerModule.h" #import -NSString *const ShareExtensionGroupIdentifier = @"group.com.expensify.chat"; -NSString *const ShareExtensionFilesKey = @"ShareFiles"; -NSString *const ShareImageFileExtension = @".png"; +NSString *const ShareExtensionGroupIdentifier = @"group.com.expensify.new"; +NSString *const ShareExtensionFilesKey = @"sharedImages"; +NSString *const ShareImageFileExtension = @".jpg"; @implementation RCTShareActionHandlerModule @@ -21,25 +21,31 @@ @implementation RCTShareActionHandlerModule { RCTLogInfo(@"Processing share extension files"); NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:ShareExtensionGroupIdentifier]; - NSString *sharedImagesFolderPath = [defaults objectForKey:ShareExtensionFilesKey]; - // Set default to NULL so it is not used when app is launched regularly. - [defaults setObject:NULL forKey:ShareExtensionFilesKey]; - [defaults synchronize]; + NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:ShareExtensionGroupIdentifier]; - if (sharedImagesFolderPath == NULL) { - NSLog(@"handleShareExtension Missing 'folder' in shareExtensionData"); + if (groupURL == NULL) { + NSLog(@"handleShareExtension Missing app group url"); return; } + NSURL *sharedImagesFolderPathURL = [groupURL URLByAppendingPathComponent:ShareExtensionFilesKey]; + NSString *sharedImagesFolderPath = [sharedImagesFolderPathURL path]; + + // Set default to NULL so it is not used when app is launched regularly. + [defaults setObject:NULL forKey:ShareExtensionFilesKey]; + [defaults synchronize]; + // Get image file names NSError *error = nil; NSArray *imageSrcPath = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:sharedImagesFolderPath error:&error]; if (imageSrcPath.count == 0) { - NSLog(@"handleShareAction Failed to find images in 'sharedImagesFolderPath'"); + NSLog(@"handleShareAction Failed to find images in 'sharedImagesFolderPath' %@", sharedImagesFolderPath); return; } + + NSLog(@"handleShareAction shared %lu images", imageSrcPath.count); NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); @@ -47,39 +53,30 @@ @implementation RCTShareActionHandlerModule NSMutableArray *imageFinalPaths = [NSMutableArray array]; for (int i = 0; i < imageSrcPath.count; i++) { - if (imageSrcPath[i] == NULL) { - NSLog(@"handleShareAction Invalid image in position %d, imageSrcPath[i] is nil", i); - continue; - } - NSLog(@"handleShareAction Valid image in position %d", i); - NSString *srcImageAbsolutePath = [sharedImagesFolderPath stringByAppendingPathComponent:imageSrcPath[i]]; - UIImage *smartScanImage = [[UIImage alloc] initWithContentsOfFile:srcImageAbsolutePath]; - if (smartScanImage == NULL) { - NSLog(@"handleShareAction Failed to load image %@", srcImageAbsolutePath); - continue; - } - - // Correct image orientation so it displays correctly on expenses. - UIGraphicsBeginImageContext(smartScanImage.size); - [smartScanImage drawAtPoint:CGPointMake(0, 0)]; - CGContextRotateCTM (UIGraphicsGetCurrentContext(), 90 * M_PI/180); - smartScanImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - // Save image to file. - NSString *pathName = [NSString stringWithFormat:@"%@%@", [[NSUUID UUID] UUIDString], ShareImageFileExtension]; - NSString *path = [documentsDirectory stringByAppendingPathComponent:pathName]; - NSData *data = UIImagePNGRepresentation(smartScanImage); - [data writeToFile:path atomically:YES]; - [imageFinalPaths addObject:path]; - } + if (imageSrcPath[i] == NULL) { + NSLog(@"handleShareAction Invalid image in position %d, imageSrcPath[i] is nil", i); + continue; + } + NSLog(@"handleShareAction Valid image in position %d", i); + NSString *srcImageAbsolutePath = [sharedImagesFolderPath stringByAppendingPathComponent:imageSrcPath[i]]; - // Delete shared image folder - if (![[NSFileManager defaultManager] removeItemAtPath:sharedImagesFolderPath error:&error]) { - NSLog(@"Failed to delete shared image folder: %@, error: %@", sharedImagesFolderPath, error); + // Save image to sharedImagesFolderPath. + NSString *imageName = [NSString stringWithFormat:@"%@%@", [[NSUUID UUID] UUIDString], ShareImageFileExtension]; + NSString *path = [sharedImagesFolderPath stringByAppendingPathComponent:imageName]; + NSLog(@"handleShareAction Native module target path %@", srcImageAbsolutePath); + + // Add the file URI to imageFinalPaths + [imageFinalPaths addObject:srcImageAbsolutePath]; } + +// // Delete shared image folder +// if (![[NSFileManager defaultManager] removeItemAtPath:sharedImagesFolderPath error:&error]) { +// NSLog(@"Failed to delete shared image folder: %@, error: %@", sharedImagesFolderPath, error); +// } + + NSLog(@"handleShareAction TEST THREE %@", imageFinalPaths); - callback(@[@[imageFinalPaths]]); + callback(@[imageFinalPaths]); } @end \ No newline at end of file diff --git a/ios/ShareViewController/ShareViewController.entitlements b/ios/ShareViewController/ShareViewController.entitlements new file mode 100644 index 000000000000..f52d3207d6e3 --- /dev/null +++ b/ios/ShareViewController/ShareViewController.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.expensify.new + + + diff --git a/ios/ShareViewController/ShareViewController.swift b/ios/ShareViewController/ShareViewController.swift index 008bd3f6eb2a..66adcce9923d 100644 --- a/ios/ShareViewController/ShareViewController.swift +++ b/ios/ShareViewController/ShareViewController.swift @@ -6,8 +6,9 @@ import Intents import os class ShareViewController: UIViewController { - let APP_GROUP_ID = "group.com.expensify.chat" - + let APP_GROUP_ID = "group.com.expensify.new" + let FILES_DIRECTORY_NAME = "sharedImages" + enum ImageSaveError: String { case IncorrectType case CouldNotLoad @@ -41,6 +42,7 @@ class ShareViewController: UIViewController { let filePath = folder.appendingPathComponent(filename) do { try imageData.write(to: filePath, options: .completeFileProtection) + os_log("TEST SAVE PATH: \(filePath).") return filePath } catch { os_log("Unexpected saveImageToFolder error: \(error).") @@ -55,7 +57,7 @@ class ShareViewController: UIViewController { os_log("ShareViewController.saveImageToAppGroup failed to get group shared folder") return } - let sharedImageFolder = groupURL.appendingPathComponent("sharedImages", isDirectory: true) + let sharedImageFolder = groupURL.appendingPathComponent(FILES_DIRECTORY_NAME, isDirectory: true) // Try to create folder to share images if it doesn't exist do { @@ -65,6 +67,7 @@ class ShareViewController: UIViewController { return } + os_log("sharedImageFolder.path \(sharedImageFolder.path)") // Clear any image that was in the folder (in case it already existed) do { let filePaths = try FileManager.default.contentsOfDirectory(atPath: sharedImageFolder.path) @@ -86,7 +89,7 @@ class ShareViewController: UIViewController { return } - // This is ran for each image that is selected. + // This is ran : NSItemProvider: NSItemProviderfor each image that is selected. for attatchment in attachments { group.enter() @@ -95,9 +98,10 @@ class ShareViewController: UIViewController { group.leave() continue } - attatchment.loadItem(forTypeIdentifier: contentType, options: nil) { (data, error) in + attatchment.loadItem(forTypeIdentifier: contentType, options: nil, completionHandler: { (data, error) in guard error == nil else { DispatchQueue.main.async { + os_log("Sharing error: \(error)") completion(.CouldNotLoad) group.leave() } @@ -113,10 +117,12 @@ class ShareViewController: UIViewController { group.leave() return } + os_log("handleShareAction FILE NAME \(filename)") if let imageFinalPath = self.saveImageToFolder(folder: sharedImageFolder, filename: filename, imageData: imageData) { imagePaths.append(imageFinalPath.path) + os_log("handleShareAction Saving image \(imageFinalPath.path)") } else { - os_log("Skipping image \(String(describing: filename)), failed to save") + os_log("handleShareAction Skipping image \(String(describing: filename)), failed to save") } group.leave() // Try to get image as UIImage. This is the case when extension is run from screenshot editor. @@ -135,7 +141,8 @@ class ShareViewController: UIViewController { } return } - } + + }) } } diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx index 38e3a9441d38..c7aba9c92c57 100644 --- a/src/pages/Share/ShareRootPage.tsx +++ b/src/pages/Share/ShareRootPage.tsx @@ -1,6 +1,6 @@ -import React, {useEffect, useRef} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import type {AppStateStatus} from 'react-native'; -import {AppState, View} from 'react-native'; +import {AppState, Image, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -20,6 +20,7 @@ function ShareRootPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); const fileIsScannable = false; + const [imageURIs, setImageURIs] = useState([]); const appState = useRef(AppState.currentState); const handleProcessFiles = () => { @@ -27,6 +28,7 @@ function ShareRootPage() { ShareActionHandlerModule.processFiles((processedFiles) => { // eslint-disable-next-line no-console console.log('PROCESSED FILES ', processedFiles); + setImageURIs(processedFiles); }); }; @@ -63,6 +65,13 @@ function ShareRootPage() { title="Share" onBackButtonPress={navigateBack} /> + {imageURIs.map((uri) => ( + + ))} {/* Date: Thu, 3 Oct 2024 12:20:42 +0200 Subject: [PATCH 005/130] add tabs layout, wire up transaction setup --- src/CONST.ts | 1 + src/components/TabSelector/TabSelector.tsx | 4 ++ src/languages/en.ts | 3 + src/languages/es.ts | 3 + src/pages/Share/ShareRootPage.tsx | 47 ++++++------ src/pages/Share/ShareTab.tsx | 71 +++++++++++++++++++ src/pages/Share/SubmitTab.tsx | 64 +++++++++++++++++ .../FloatingActionButtonAndPopover.tsx | 13 ++-- 8 files changed, 180 insertions(+), 26 deletions(-) create mode 100644 src/pages/Share/ShareTab.tsx create mode 100644 src/pages/Share/SubmitTab.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 4820bbb62b52..ca20c7941d15 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4165,6 +4165,7 @@ const CONST = { NEW_ROOM: 'room', RECEIPT_TAB_ID: 'ReceiptTab', IOU_REQUEST_TYPE: 'iouRequestType', + SHARE: {NAVIGATOR_ID: 'ShareNavigatorID', SHARE: 'ShareTab', SUBMIT: 'SubmitTab'}, }, TAB_REQUEST: { MANUAL: 'manual', diff --git a/src/components/TabSelector/TabSelector.tsx b/src/components/TabSelector/TabSelector.tsx index 1bf753cd4aa4..876675a7dd0e 100644 --- a/src/components/TabSelector/TabSelector.tsx +++ b/src/components/TabSelector/TabSelector.tsx @@ -45,6 +45,10 @@ function getIconAndTitle(route: string, translate: LocaleContextProps['translate return {icon: Expensicons.Hashtag, title: translate('tabSelector.room')}; case CONST.TAB_REQUEST.DISTANCE: return {icon: Expensicons.Car, title: translate('common.distance')}; + case CONST.TAB.SHARE.SHARE: + return {icon: Expensicons.UploadAlt, title: translate('common.share')}; + case CONST.TAB.SHARE.SUBMIT: + return {icon: Expensicons.Receipt, title: translate('common.submit')}; default: throw new Error(`Route ${route} has no icon nor title set.`); } diff --git a/src/languages/en.ts b/src/languages/en.ts index 0446a3c9e86a..3b53f360f934 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1022,6 +1022,9 @@ const translations = { bookingArchived: 'This booking is archived', bookingArchivedDescription: 'This booking is archived because the trip date has passed. Add an expense for the final amount if needed.', justTrackIt: 'Just track it (don’t submit it)', + shareRoot: { + shareToExpensify: 'Share to expensify', + }, }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6f9a056e727a..79c2e225c852 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1016,6 +1016,9 @@ const translations = { bookingArchived: 'Esta reserva está archivada', bookingArchivedDescription: 'Esta reserva está archivada porque la fecha del viaje ha pasado. Agregue un gasto por el monto final si es necesario.', justTrackIt: 'Solo guardarlo (no enviarlo)', + shareRoot: { + shareToExpensify: 'Share to expensify', + }, }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx index c7aba9c92c57..6765a7501ec3 100644 --- a/src/pages/Share/ShareRootPage.tsx +++ b/src/pages/Share/ShareRootPage.tsx @@ -1,14 +1,23 @@ import React, {useEffect, useRef, useState} from 'react'; import type {AppStateStatus} from 'react-native'; import {AppState, Image, View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import TabSelector from '@components/TabSelector/TabSelector'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as TransactionEdit from '@libs/actions/TransactionEdit'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import Navigation from '@navigation/Navigation'; +import CONST from '@src/CONST'; import ShareActionHandlerModule from '@src/modules/ShareActionHandlerModule'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import ShareTab from './ShareTab'; +import SubmitTab from './SubmitTab'; type ShareRootPageOnyxProps = { selectedTab: OnyxEntry; @@ -17,8 +26,10 @@ type ShareRootPageOnyxProps = { type ShareRootPageProps = ShareRootPageOnyxProps; function ShareRootPage() { + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`); const styles = useThemeStyles(); const {translate} = useLocalize(); + const fileIsScannable = false; const [imageURIs, setImageURIs] = useState([]); const appState = useRef(AppState.currentState); @@ -41,17 +52,17 @@ function ShareRootPage() { useEffect(() => { const changeSubscription = AppState.addEventListener('change', handleAppStateChange); + TransactionEdit.createDraftTransaction(transaction); handleProcessFiles(); return () => { changeSubscription.remove(); + TransactionEdit.removeDraftTransaction(transaction?.transactionID ?? '-1'); }; }, []); - const navigateBack = () => { - Navigation.dismissModal(); - }; + console.log('transaction test ', transaction); return ( - {imageURIs.map((uri) => ( + {/* {imageURIs.map((uri) => ( - ))} - {/* ( - - )} + tabBar={TabSelector} > - {() => } - {() => } - */} + {() => } + {() => } + ); diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx new file mode 100644 index 000000000000..780cc42c4778 --- /dev/null +++ b/src/pages/Share/ShareTab.tsx @@ -0,0 +1,71 @@ +import React, {useCallback, useRef} from 'react'; +import {useOnyx, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import * as IOU from '@libs/actions/IOU'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; +import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {Transaction} from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; + +// type ShareTabOnyxProps = { +// transaction?: OnyxEntry; +// }; + +// type ShareTabProps = ShareTabOnyxProps; + +// eslint-disable-next-line rulesdir/no-negated-variables +function ShareTab({transaction}: {transaction: OnyxEntry}) { + const optimisticReportID = ReportUtils.generateReportID(); + const selectedReportID = useRef(optimisticReportID); + // const transactionID = route.params.transactionID ?? 0; + // const numberOfParticipants = useRef(transaction?.participants?.length); + + // const goToNextStep = useCallback(() => { + // const nextStepIOUType = numberOfParticipants.current === 1 ? CONST.IOU.TYPE.REQUEST : CONST.IOU.TYPE.SPLIT; + // IOU.initMoneyRequest(optimisticReportID, false, CONST.IOU.REQUEST_TYPE.SCAN); + // IOU.setMoneyRequestTag(transactionID, ''); + // IOU.setMoneyRequestCategory(transactionID, ''); + // IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, transaction?.participants ?? []); + // Navigation.navigate(ROUTES.SHARE_SCAN_CONFIRM.getRoute(nextStepIOUType, transactionID, selectedReportID.current || optimisticReportID)); + // }, [transactionID, optimisticReportID]); + + // const addParticipant = useCallback( + // (val: Participant[]) => { + // IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); + // numberOfParticipants.current = val.length; + + // // When multiple participants are selected, the reportID is generated at the end of the confirmation step. + // // So we are resetting selectedReportID ref to the reportID coming from params. + // if (val.length !== 1) { + // selectedReportID.current = optimisticReportID; + // return; + // } + + // // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. + // selectedReportID.current = val?.[0].reportID ?? optimisticReportID; + // }, + // [optimisticReportID, transactionID], + // ); + + console.log('PARTICIPANTS ', transaction); + return ( + + ); +} + +export default ShareTab; diff --git a/src/pages/Share/SubmitTab.tsx b/src/pages/Share/SubmitTab.tsx new file mode 100644 index 000000000000..8f9de0d9f84c --- /dev/null +++ b/src/pages/Share/SubmitTab.tsx @@ -0,0 +1,64 @@ +import React, {useRef} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import * as ReportUtils from '@libs/ReportUtils'; +import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector'; +import CONST from '@src/CONST'; +import type {Transaction} from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; + +// type ShareTabOnyxProps = { +// transaction?: OnyxEntry; +// }; + +// type ShareTabProps = ShareTabOnyxProps; + +// eslint-disable-next-line rulesdir/no-negated-variables +function SubmitTab({transaction}: {transaction: OnyxEntry}) { + const optimisticReportID = ReportUtils.generateReportID(); + const selectedReportID = useRef(optimisticReportID); + const numberOfParticipants = useRef(transaction?.participants?.length); + + // const goToNextStep = useCallback(() => { + // const nextStepIOUType = numberOfParticipants.current === 1 ? CONST.IOU.TYPE.REQUEST : CONST.IOU.TYPE.SPLIT; + // IOU.initMoneyRequest(optimisticReportID, false, CONST.IOU.REQUEST_TYPE.SCAN); + // IOU.setMoneyRequestTag(transactionID, ''); + // IOU.setMoneyRequestCategory(transactionID, ''); + // IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, transaction?.participants ?? []); + // Navigation.navigate(ROUTES.SHARE_SCAN_CONFIRM.getRoute(nextStepIOUType, transactionID, selectedReportID.current || optimisticReportID)); + // }, [transactionID, optimisticReportID]); + + // const addParticipant = useCallback( + // (val: Participant[]) => { + // IOU.setMoneyRequestParticipants_temporaryForRefactor(transactionID, val); + // numberOfParticipants.current = val.length; + + // // When multiple participants are selected, the reportID is generated at the end of the confirmation step. + // // So we are resetting selectedReportID ref to the reportID coming from params. + // if (val.length !== 1) { + // selectedReportID.current = optimisticReportID; + // return; + // } + + // // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. + // selectedReportID.current = val?.[0].reportID ?? optimisticReportID; + // }, + // [optimisticReportID, transactionID], + // ); + + return ( + + ); +} + +export default SubmitTab; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index a49b474b185e..6b23d8fc24ed 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -343,11 +343,14 @@ function FloatingActionButtonAndPopover( })); const toggleCreateMenu = () => { - if (isCreateMenuActive) { - hideCreateMenu(); - } else { - showCreateMenu(); - } + // if (isCreateMenuActive) { + // hideCreateMenu(); + // } else { + // showCreateMenu(); + // } + + IOU.clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, false); + Navigation.navigate(ROUTES.SHARE_ROOT); }; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps From 5871fa02254a5c0d488a3dacf46e7dd6789f9722 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Fri, 4 Oct 2024 10:27:30 +0200 Subject: [PATCH 006/130] initial onyx setup --- src/ONYXKEYS.ts | 4 ++ src/ROUTES.ts | 5 ++ src/SCREENS.ts | 1 + .../ModalStackNavigators/index.tsx | 4 +- src/libs/Navigation/types.ts | 1 + src/libs/actions/Share.ts | 13 ++++ src/pages/Share/ShareDetailsPage.tsx | 72 +++++++++++++++++++ src/pages/Share/ShareRootPage.tsx | 22 +++--- src/pages/Share/ShareTab.tsx | 13 ++-- src/pages/Share/SubmitDetailsPage.tsx | 0 src/pages/Share/SubmitTab.tsx | 6 +- .../FloatingActionButtonAndPopover.tsx | 16 ++--- src/types/onyx/TempShareFile.ts | 18 +++++ src/types/onyx/index.ts | 2 + 14 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 src/libs/actions/Share.ts create mode 100644 src/pages/Share/ShareDetailsPage.tsx create mode 100644 src/pages/Share/SubmitDetailsPage.tsx create mode 100644 src/types/onyx/TempShareFile.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index cb8bf2fdb5d3..f1e5d4f1f49b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -504,6 +504,9 @@ const ONYXKEYS = { /** Currently displaying feed */ LAST_SELECTED_FEED: 'lastSelectedFeed_', + + /** Temporary files to be shared from outside the app */ + TEMP_SHARE_FILES: 'tempShareFiles_', }, /** List of Form ids */ @@ -837,6 +840,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: boolean; [ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: string; + [ONYXKEYS.COLLECTION.TEMP_SHARE_FILES]: OnyxTypes.TempShareFile; }; type OnyxValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 6a9474a19602..69c7dbd08d75 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1120,6 +1120,11 @@ const ROUTES = { getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo), }, SHARE_ROOT: 'share/root', + SHARE_DETAILS: { + route: 'share/share-details/:reportID', + getRoute: (reportID: string) => `share/share-details/${reportID}`, + }, + PROCESS_MONEY_REQUEST_HOLD: { route: 'hold-expense-educational', getRoute: (backTo?: string) => getUrlWithBackToParam('hold-expense-educational', backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index ee4060f0a41d..e43a2033c013 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -551,6 +551,7 @@ const SCREENS = { KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', SHARE: { ROOT: 'Share_Root', + SHARE_DETAILS: 'Share_Details', }, TRANSACTION_RECEIPT: 'TransactionReceipt', FEATURE_TRAINING_ROOT: 'FeatureTraining_Root', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 67cae89daac7..cb445dcaa8c2 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -25,6 +25,7 @@ import type { SearchReportParamList, SearchSavedSearchParamList, SettingsNavigatorParamList, + ShareNavigatorParamList, SignInNavigatorParamList, SplitDetailsNavigatorParamList, TaskDetailsNavigatorParamList, @@ -575,8 +576,9 @@ const RestrictedActionModalStackNavigator = createModalStackNavigator require('../../../../pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage').default, }); -const ShareModalStackNavigator = createModalStackNavigator({ +const ShareModalStackNavigator = createModalStackNavigator({ [SCREENS.SHARE.ROOT]: () => require('@pages/Share/ShareRootPage').default, + [SCREENS.SHARE.SHARE_DETAILS]: () => require('@pages/Share/ShareDetailsPage').default, }); const MissingPersonalDetailsModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 9cb5337ab1ca..f0282ccc43ad 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1420,6 +1420,7 @@ type SharedScreensParamList = { type ShareNavigatorParamList = { [SCREENS.SHARE.ROOT]: undefined; + [SCREENS.SHARE.SHARE_DETAILS]: {reportID: string}; }; type PublicScreensParamList = SharedScreensParamList & { diff --git a/src/libs/actions/Share.ts b/src/libs/actions/Share.ts new file mode 100644 index 000000000000..3fcb925692f8 --- /dev/null +++ b/src/libs/actions/Share.ts @@ -0,0 +1,13 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {TempShareFile} from '@src/types/onyx'; + +function addTempShareFile(file: TempShareFile) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}${file.path}`, file); +} + +function markTempShareFileUploaded(filePath: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}${filePath}`, {readyForRemoval: true}); +} + +export {addTempShareFile, markTempShareFileUploaded}; diff --git a/src/pages/Share/ShareDetailsPage.tsx b/src/pages/Share/ShareDetailsPage.tsx new file mode 100644 index 000000000000..d0c3f4048452 --- /dev/null +++ b/src/pages/Share/ShareDetailsPage.tsx @@ -0,0 +1,72 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import type {ShareNavigatorParamList} from '@libs/Navigation/types'; +import type SCREENS from '@src/SCREENS'; + +type ShareDetailsPageProps = StackScreenProps; + +function ShareDetailsPage({ + route: { + params: {reportID}, + }, +}: ShareDetailsPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + // const submitForm = useCallback( + // (newComment: string) => { + // playSound(SOUNDS.DONE); + + // const newCommentTrimmed = newComment.trim(); + + // if (attachmentFileRef.current) { + // Report.addAttachment(reportID, attachmentFileRef.current, newCommentTrimmed); + // attachmentFileRef.current = null; + // } else { + // Performance.markStart(CONST.TIMING.MESSAGE_SENT, {message: newCommentTrimmed}); + // onSubmit(newCommentTrimmed); + // } + // }, + // [onSubmit, reportID], + // ); + + return ( + + + + {/* {}} + pressableStyle={styles.flexRow} + shouldSyncFocus={false} + /> */} + {/* {imageURIs.map((uri) => ( + + ))} */} + + + ); +} + +ShareDetailsPage.displayName = 'ShareDetailsPage'; +export default ShareDetailsPage; diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx index 6765a7501ec3..fc84570c4bfb 100644 --- a/src/pages/Share/ShareRootPage.tsx +++ b/src/pages/Share/ShareRootPage.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useRef, useState} from 'react'; import type {AppStateStatus} from 'react-native'; -import {AppState, Image, View} from 'react-native'; +import {AppState, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -11,11 +11,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as TransactionEdit from '@libs/actions/TransactionEdit'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; -import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ShareActionHandlerModule from '@src/modules/ShareActionHandlerModule'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import ShareTab from './ShareTab'; import SubmitTab from './SubmitTab'; @@ -43,15 +41,13 @@ function ShareRootPage() { }); }; - const handleAppStateChange = (nextAppState: AppStateStatus) => { - if (appState.current.match(/inactive|background/) && nextAppState === 'active') { - handleProcessFiles(); - } - appState.current = nextAppState; - }; - useEffect(() => { - const changeSubscription = AppState.addEventListener('change', handleAppStateChange); + const changeSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => { + if (appState.current.match(/inactive|background/) && nextAppState === 'active') { + handleProcessFiles(); + } + appState.current = nextAppState; + }); TransactionEdit.createDraftTransaction(transaction); handleProcessFiles(); @@ -87,11 +83,11 @@ function ShareRootPage() { id={CONST.TAB.SHARE.NAVIGATOR_ID} // @ts-expect-error I think that OnyxTabNavigator is going to be refactored in terms of types // selectedTab={fileIsScannable && selectedTab ? selectedTab : CONST.TAB.SHARE} - selectedTab={CONST.TAB.SHARE} + hideTabBar={!fileIsScannable} tabBar={TabSelector} > - {() => } + {() => } {() => } diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index 780cc42c4778..fd797d23a121 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -18,7 +18,7 @@ import type {Participant} from '@src/types/onyx/IOU'; // type ShareTabProps = ShareTabOnyxProps; // eslint-disable-next-line rulesdir/no-negated-variables -function ShareTab({transaction}: {transaction: OnyxEntry}) { +function ShareTab() { const optimisticReportID = ReportUtils.generateReportID(); const selectedReportID = useRef(optimisticReportID); // const transactionID = route.params.transactionID ?? 0; @@ -51,17 +51,12 @@ function ShareTab({transaction}: {transaction: OnyxEntry}) { // [optimisticReportID, transactionID], // ); - console.log('PARTICIPANTS ', transaction); return ( console.warn('TEST')} + onParticipantsAdded={() => console.warn('ADDED')} iouRequestType="manual" action="create" // isScanRequest /> diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pages/Share/SubmitTab.tsx b/src/pages/Share/SubmitTab.tsx index 8f9de0d9f84c..dfe2f4ad042e 100644 --- a/src/pages/Share/SubmitTab.tsx +++ b/src/pages/Share/SubmitTab.tsx @@ -13,10 +13,10 @@ import type {Participant} from '@src/types/onyx/IOU'; // type ShareTabProps = ShareTabOnyxProps; // eslint-disable-next-line rulesdir/no-negated-variables -function SubmitTab({transaction}: {transaction: OnyxEntry}) { +function SubmitTab() { const optimisticReportID = ReportUtils.generateReportID(); const selectedReportID = useRef(optimisticReportID); - const numberOfParticipants = useRef(transaction?.participants?.length); + // const numberOfParticipants = useRef(transaction?.participants?.length); // const goToNextStep = useCallback(() => { // const nextStepIOUType = numberOfParticipants.current === 1 ? CONST.IOU.TYPE.REQUEST : CONST.IOU.TYPE.SPLIT; @@ -47,7 +47,7 @@ function SubmitTab({transaction}: {transaction: OnyxEntry}) { return ( { - // if (isCreateMenuActive) { - // hideCreateMenu(); - // } else { - // showCreateMenu(); - // } - - IOU.clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, false); - Navigation.navigate(ROUTES.SHARE_ROOT); + if (isCreateMenuActive) { + hideCreateMenu(); + } else { + showCreateMenu(); + } + + // IOU.clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, false); + // Navigation.navigate(ROUTES.SHARE_ROOT); }; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps diff --git a/src/types/onyx/TempShareFile.ts b/src/types/onyx/TempShareFile.ts new file mode 100644 index 000000000000..d29b6c35cc17 --- /dev/null +++ b/src/types/onyx/TempShareFile.ts @@ -0,0 +1,18 @@ +/** Model of the file shared from the external source */ +type TempShareFile = { + /** Path to the file copy in the app folder */ + path?: string; + + /** Mime type of the file */ + mimeType?: string; + + /** Whether to remove the file. Necessary for the offline handling */ + readyForRemoval?: boolean; +}; + +export default TempShareFile; + +// { +// contnent: 'path' | 'string' +// mime: '' +// } diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 420ccf884f91..ae076ccccadb 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -89,6 +89,7 @@ import type SelectedTabRequest from './SelectedTabRequest'; import type Session from './Session'; import type StripeCustomerID from './StripeCustomerID'; import type Task from './Task'; +import type TempShareFile from './TempShareFile'; import type Transaction from './Transaction'; import type {TransactionViolation, ViolationName} from './TransactionViolation'; import type TransactionViolations from './TransactionViolation'; @@ -232,4 +233,5 @@ export type { SaveSearch, ImportedSpreadsheet, ValidateMagicCodeAction, + TempShareFile, }; From 4562d4e4ab01221a012678b253abbe1d6ae79ead Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Fri, 4 Oct 2024 10:56:50 +0200 Subject: [PATCH 007/130] add mime type handling in the ios native module return object --- ios/RCTShareActionHandlerModule.m | 25 +++++++++++++++++++------ src/modules/ShareActionHandlerModule.ts | 7 ++++++- src/pages/Share/ShareRootPage.tsx | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m index 273b6ea3f062..e189dd30d767 100644 --- a/ios/RCTShareActionHandlerModule.m +++ b/ios/RCTShareActionHandlerModule.m @@ -8,6 +8,7 @@ #import #import "RCTShareActionHandlerModule.h" #import +#import NSString *const ShareExtensionGroupIdentifier = @"group.com.expensify.new"; NSString *const ShareExtensionFilesKey = @"sharedImages"; @@ -69,14 +70,26 @@ @implementation RCTShareActionHandlerModule [imageFinalPaths addObject:srcImageAbsolutePath]; } -// // Delete shared image folder -// if (![[NSFileManager defaultManager] removeItemAtPath:sharedImagesFolderPath error:&error]) { -// NSLog(@"Failed to delete shared image folder: %@, error: %@", sharedImagesFolderPath, error); -// } + NSMutableArray *imageObjectsArray = [[NSMutableArray alloc] init]; - NSLog(@"handleShareAction TEST THREE %@", imageFinalPaths); + for (NSString *imagePath in imageFinalPaths) { + NSString *extension = [imagePath pathExtension]; - callback(@[imageFinalPaths]); + UTType *type = [UTType typeWithFilenameExtension:extension conformingToType:UTTypeData]; + NSString *mimeType = type.preferredMIMEType; + + // If MIME type can't be inferred, set "application/octet-stream" as default + mimeType = mimeType ? mimeType : @"application/octet-stream"; + + NSDictionary *dict = @{ + @"content" : imagePath, + @"mimeType" : mimeType + }; + + [imageObjectsArray addObject:dict]; + } + + callback(@[imageObjectsArray]); } @end \ No newline at end of file diff --git a/src/modules/ShareActionHandlerModule.ts b/src/modules/ShareActionHandlerModule.ts index 34add7ae6e6a..00cf8d430d12 100644 --- a/src/modules/ShareActionHandlerModule.ts +++ b/src/modules/ShareActionHandlerModule.ts @@ -2,8 +2,13 @@ import {NativeModules} from 'react-native'; const {ShareActionHandlerModule} = NativeModules; +type ShareActionContent = { + content: string; + mimeType: string; +}; + type ShareActionHandlerType = { - processFiles(callback: (array: string[]) => void): void; + processFiles(callback: (array: ShareActionContent[]) => void): void; }; export default ShareActionHandlerModule as ShareActionHandlerType; diff --git a/src/pages/Share/ShareRootPage.tsx b/src/pages/Share/ShareRootPage.tsx index fc84570c4bfb..015b8dd2cbca 100644 --- a/src/pages/Share/ShareRootPage.tsx +++ b/src/pages/Share/ShareRootPage.tsx @@ -37,7 +37,7 @@ function ShareRootPage() { ShareActionHandlerModule.processFiles((processedFiles) => { // eslint-disable-next-line no-console console.log('PROCESSED FILES ', processedFiles); - setImageURIs(processedFiles); + // setImageURIs(processedFiles); }); }; From 06ac2327a3b4aa4e46171b23b4dbb488c6a70376 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Mon, 7 Oct 2024 13:02:28 +0200 Subject: [PATCH 008/130] attachment wip --- ios/RCTShareActionHandlerModule.m | 26 +++++-- src/components/AttachmentPreview.tsx | 25 +++++++ src/libs/Navigation/linkingConfig/config.ts | 4 +- src/libs/OptionsListUtils.ts | 34 +++++++++ src/libs/actions/Share.ts | 18 ++++- src/modules/ShareActionHandlerModule.ts | 2 + src/pages/Share/ShareDetailsPage.tsx | 73 ++++++++++++++++--- src/pages/Share/ShareRootPage.tsx | 35 +-------- src/pages/Share/ShareTab.tsx | 12 ++- .../FloatingActionButtonAndPopover.tsx | 16 ++-- src/types/onyx/TempShareFile.ts | 13 +++- 11 files changed, 191 insertions(+), 67 deletions(-) create mode 100644 src/components/AttachmentPreview.tsx diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m index e189dd30d767..07d8ad1107f8 100644 --- a/ios/RCTShareActionHandlerModule.m +++ b/ios/RCTShareActionHandlerModule.m @@ -55,17 +55,22 @@ @implementation RCTShareActionHandlerModule for (int i = 0; i < imageSrcPath.count; i++) { if (imageSrcPath[i] == NULL) { - NSLog(@"handleShareAction Invalid image in position %d, imageSrcPath[i] is nil", i); - continue; + NSLog(@"handleShareAction Invalid image in position %d, imageSrcPath[i] is nil", i); + continue; } NSLog(@"handleShareAction Valid image in position %d", i); - NSString *srcImageAbsolutePath = [sharedImagesFolderPath stringByAppendingPathComponent:imageSrcPath[i]]; - + NSString *source = imageSrcPath[i]; // Store image source path + NSString *srcImageAbsolutePath = [sharedImagesFolderPath stringByAppendingPathComponent:source]; + + // Get dynamic file extension from the source file + NSString *fileExtension = [source pathExtension]; + NSLog(@"handleShareAction File Extension %@", fileExtension); + // Save image to sharedImagesFolderPath. - NSString *imageName = [NSString stringWithFormat:@"%@%@", [[NSUUID UUID] UUIDString], ShareImageFileExtension]; + NSString *imageName = [NSString stringWithFormat:@"%@.%@", [[NSUUID UUID] UUIDString], fileExtension]; // Append dynamic extension NSString *path = [sharedImagesFolderPath stringByAppendingPathComponent:imageName]; NSLog(@"handleShareAction Native module target path %@", srcImageAbsolutePath); - + // Add the file URI to imageFinalPaths [imageFinalPaths addObject:srcImageAbsolutePath]; } @@ -80,10 +85,17 @@ @implementation RCTShareActionHandlerModule // If MIME type can't be inferred, set "application/octet-stream" as default mimeType = mimeType ? mimeType : @"application/octet-stream"; + + // Generate an ID based on current timestamp and file path + NSTimeInterval timestampInterval = [[NSDate date] timeIntervalSince1970] * 1000; + NSString *timestamp = [NSString stringWithFormat:@"%.0f", timestampInterval]; + NSString *identifier = [NSString stringWithFormat:@"%@_%@", (unsigned long)timestamp, imagePath]; NSDictionary *dict = @{ + @"id" : identifier, @"content" : imagePath, - @"mimeType" : mimeType + @"mimeType" : mimeType, + @"processedAt" : timestamp }; [imageObjectsArray addObject:dict]; diff --git a/src/components/AttachmentPreview.tsx b/src/components/AttachmentPreview.tsx new file mode 100644 index 000000000000..14853ffb7c37 --- /dev/null +++ b/src/components/AttachmentPreview.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {Image, View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type AttachmentPreviewProps = {uri: string}; + +function AttachmentPreview({uri}: AttachmentPreviewProps) { + // const theme = useTheme(); + const styles = useThemeStyles(); + // const {isOffline} = useNetwork(); + // const {translate} = useLocalize(); + + return ( + + + + ); +} + +AttachmentPreview.displayName = 'AttachmentPreview'; + +export default AttachmentPreview; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 8514acfd8365..502f8cdd6323 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1284,10 +1284,10 @@ const config: LinkingOptions['config'] = { }, }, [NAVIGATORS.SHARE_MODAL_NAVIGATOR]: { - path: ROUTES.SHARE_ROOT, initialRouteName: SCREENS.SHARE.ROOT, screens: { - [SCREENS.SHARE.ROOT]: ROUTES.SHARE_ROOT, + [SCREENS.SHARE.ROOT]: {path: ROUTES.SHARE_ROOT}, + [SCREENS.SHARE.SHARE_DETAILS]: {path: ROUTES.SHARE_DETAILS.route}, }, }, }, diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index cf0b31ef3267..deb40b39a232 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -874,6 +874,39 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { return option; } +/** + * Get the display option for a given report. + */ +function getReportDisplayOption(report: OnyxEntry): ReportUtils.OptionData { + const visibleParticipantAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report, true); + + const option = createOption( + visibleParticipantAccountIDs, + allPersonalDetails ?? {}, + !isEmptyObject(report) ? report : undefined, + {}, + { + showChatPreviewLine: false, + forcePolicyNamePreview: false, + }, + ); + + // Update text & alternateText because createOption returns workspace name only if report is owned by the user + if (option.isSelfDM) { + option.alternateText = Localize.translateLocal('reportActionsView.yourSpace'); + } else if (option.isInvoiceRoom) { + option.text = ReportUtils.getReportName(report); + option.alternateText = Localize.translateLocal('workspace.common.invoices'); + } else { + option.text = ReportUtils.getPolicyName(report); + option.alternateText = Localize.translateLocal('workspace.common.workspace'); + } + option.isDisabled = true; + option.selected = false; + option.isSelected = false; + return option; +} + /** * Get the option for a policy expense report. */ @@ -2619,6 +2652,7 @@ export { getEmptyOptions, shouldUseBoldText, getAlternateText, + getReportDisplayOption, }; export type {MemberForList, CategorySection, CategoryTreeSection, Options, OptionList, SearchOption, PayeePersonalDetails, Category, Tax, TaxRatesOption, Option, OptionTree}; diff --git a/src/libs/actions/Share.ts b/src/libs/actions/Share.ts index 3fcb925692f8..a8b87737b987 100644 --- a/src/libs/actions/Share.ts +++ b/src/libs/actions/Share.ts @@ -2,12 +2,24 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; import type {TempShareFile} from '@src/types/onyx'; +// const allTempShareFiles: OnyxCollection = {}; +// Onyx.connect({ +// key: ONYXKEYS.COLLECTION.TEMP_SHARE_FILES, +// waitForCollectionCallback: true, +// callback: (value) => { +// if (!value) { +// return; +// } +// allTempShareFiles = Object.fromEntries(Object.entries(value).filter(([, shareFiles]) => !!shareFiles)); +// }, +// }); + function addTempShareFile(file: TempShareFile) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}${file.path}`, file); + Onyx.merge(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}${file.id}`, file); } -function markTempShareFileUploaded(filePath: string) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}${filePath}`, {readyForRemoval: true}); +function markTempShareFileUploaded(fileID: string, reportID: string) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}${fileID}`, {readyForRemoval: true}); } export {addTempShareFile, markTempShareFileUploaded}; diff --git a/src/modules/ShareActionHandlerModule.ts b/src/modules/ShareActionHandlerModule.ts index 00cf8d430d12..98c0fadf64f9 100644 --- a/src/modules/ShareActionHandlerModule.ts +++ b/src/modules/ShareActionHandlerModule.ts @@ -3,8 +3,10 @@ import {NativeModules} from 'react-native'; const {ShareActionHandlerModule} = NativeModules; type ShareActionContent = { + id: string; content: string; mimeType: string; + processedAt: string; }; type ShareActionHandlerType = { diff --git a/src/pages/Share/ShareDetailsPage.tsx b/src/pages/Share/ShareDetailsPage.tsx index d0c3f4048452..4a84037f9d79 100644 --- a/src/pages/Share/ShareDetailsPage.tsx +++ b/src/pages/Share/ShareDetailsPage.tsx @@ -1,12 +1,20 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import AttachmentPreview from '@components/AttachmentPreview'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import type {ShareNavigatorParamList} from '@libs/Navigation/types'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ShareActions from '@userActions/Share'; +import UserListItem from '@src/components/SelectionList/UserListItem'; +import ShareActionHandlerModule from '@src/modules/ShareActionHandlerModule'; +import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; type ShareDetailsPageProps = StackScreenProps; @@ -19,6 +27,32 @@ function ShareDetailsPage({ const styles = useThemeStyles(); const {translate} = useLocalize(); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); + const [tempShareFiles] = useOnyx(`${ONYXKEYS.COLLECTION.TEMP_SHARE_FILES}`); + + const handleProcessFiles = () => { + ShareActionHandlerModule.processFiles((processedFiles) => { + // eslint-disable-next-line no-console + const tempFile = processedFiles.at(0); + if (tempFile) { + ShareActions.addTempShareFile({...tempFile, reportID, readyForRemoval: false}); + } + }); + }; + + useEffect(() => { + handleProcessFiles(); + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const currentAttachment = useMemo(() => { + return Object.values(tempShareFiles ?? {}) + ?.filter((file) => file?.reportID === reportID) + .sort((a, b) => Number(b?.processedAt) - Number(a?.processedAt)) + .at(0); + }, [reportID, tempShareFiles]); + // const submitForm = useCallback( // (newComment: string) => { // playSound(SOUNDS.DONE); @@ -36,6 +70,15 @@ function ShareDetailsPage({ // [onSubmit, reportID], // ); + // const formattedSelectedParticipants = Object.values(report?.participants?).map((participant) => ({ + // ...participant, + // isSelected: false, + // isInteractive: false, + // })); + + const reportTest = OptionsListUtils.getReportDisplayOption(report); + + console.log('CURRENT ATTACHMENT ', currentAttachment); return ( - {/* {}} - pressableStyle={styles.flexRow} - shouldSyncFocus={false} - /> */} + {report && ( + <> + + {translate('common.to')} + + {}} + pressableStyle={[styles.flexRow]} + shouldSyncFocus={false} + /> + + )} + + + {translate('common.to')} + + {/* {imageURIs.map((uri) => ( ; -}; - -type ShareRootPageProps = ShareRootPageOnyxProps; - function ShareRootPage() { const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`); + const [tempShareFiles, setTempShareFiles] = useState([]); const styles = useThemeStyles(); const {translate} = useLocalize(); const fileIsScannable = false; - const [imageURIs, setImageURIs] = useState([]); const appState = useRef(AppState.currentState); - const handleProcessFiles = () => { - console.log('PROCESS FILES ATTEMPT'); - ShareActionHandlerModule.processFiles((processedFiles) => { - // eslint-disable-next-line no-console - console.log('PROCESSED FILES ', processedFiles); - // setImageURIs(processedFiles); - }); - }; - - useEffect(() => { - const changeSubscription = AppState.addEventListener('change', (nextAppState: AppStateStatus) => { - if (appState.current.match(/inactive|background/) && nextAppState === 'active') { - handleProcessFiles(); - } - appState.current = nextAppState; - }); - TransactionEdit.createDraftTransaction(transaction); - - handleProcessFiles(); - - return () => { - changeSubscription.remove(); - TransactionEdit.removeDraftTransaction(transaction?.transactionID ?? '-1'); - }; - }, []); - console.log('transaction test ', transaction); return ( diff --git a/src/pages/Share/ShareTab.tsx b/src/pages/Share/ShareTab.tsx index fd797d23a121..44b25f72b4d1 100644 --- a/src/pages/Share/ShareTab.tsx +++ b/src/pages/Share/ShareTab.tsx @@ -55,8 +55,16 @@ function ShareTab() { console.warn('TEST')} - onParticipantsAdded={() => console.warn('ADDED')} + onFinish={(value) => console.log('TEST ', value)} + onParticipantsAdded={(value) => { + const reportID = value.at(0)?.reportID; + + console.log('REPORT ID ', reportID); + if (!reportID) { + return; + } + Navigation.navigate(ROUTES.SHARE_DETAILS.getRoute(reportID)); + }} iouRequestType="manual" action="create" // isScanRequest /> diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index c594257df526..6b23d8fc24ed 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -343,14 +343,14 @@ function FloatingActionButtonAndPopover( })); const toggleCreateMenu = () => { - if (isCreateMenuActive) { - hideCreateMenu(); - } else { - showCreateMenu(); - } - - // IOU.clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, false); - // Navigation.navigate(ROUTES.SHARE_ROOT); + // if (isCreateMenuActive) { + // hideCreateMenu(); + // } else { + // showCreateMenu(); + // } + + IOU.clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, false); + Navigation.navigate(ROUTES.SHARE_ROOT); }; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps diff --git a/src/types/onyx/TempShareFile.ts b/src/types/onyx/TempShareFile.ts index d29b6c35cc17..bc8cc0c62b89 100644 --- a/src/types/onyx/TempShareFile.ts +++ b/src/types/onyx/TempShareFile.ts @@ -1,13 +1,22 @@ /** Model of the file shared from the external source */ type TempShareFile = { - /** Path to the file copy in the app folder */ - path?: string; + /** ID of the share file */ + id?: string; + + /** Path to the file copy in the app folder, or text content */ + content?: string; /** Mime type of the file */ mimeType?: string; /** Whether to remove the file. Necessary for the offline handling */ readyForRemoval?: boolean; + + /** ID of the report this share file is, or will be attached to */ + reportID?: string; + + /** Timestamp of when the share attempt started */ + processedAt?: string; }; export default TempShareFile; From 3c6946c6230f0a8a9f5ba915dc94b599dfd42ff0 Mon Sep 17 00:00:00 2001 From: BrtqKr Date: Tue, 8 Oct 2024 01:25:30 +0200 Subject: [PATCH 009/130] wire up share attachment, wire up message wip --- ios/RCTShareActionHandlerModule.m | 17 ++++- src/components/AttachmentPreview.tsx | 16 +++-- src/languages/en.ts | 7 +- src/languages/es.ts | 7 +- src/modules/ShareActionHandlerModule.ts | 1 + src/pages/Share/ShareDetailsPage.tsx | 90 +++++++++++++++---------- src/pages/Share/ShareRootPage.tsx | 2 +- src/styles/utils/spacing.ts | 4 ++ src/types/onyx/TempShareFile.ts | 7 +- 9 files changed, 100 insertions(+), 51 deletions(-) diff --git a/ios/RCTShareActionHandlerModule.m b/ios/RCTShareActionHandlerModule.m index 07d8ad1107f8..cdfe0f7b9e82 100644 --- a/ios/RCTShareActionHandlerModule.m +++ b/ios/RCTShareActionHandlerModule.m @@ -90,12 +90,27 @@ @implementation RCTShareActionHandlerModule NSTimeInterval timestampInterval = [[NSDate date] timeIntervalSince1970] * 1000; NSString *timestamp = [NSString stringWithFormat:@"%.0f", timestampInterval]; NSString *identifier = [NSString stringWithFormat:@"%@_%@", (unsigned long)timestamp, imagePath]; + + // Get the image aspcect ratio + CGFloat aspectRatio = 1.0; + UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; + if (image) { + CGFloat width = image.size.width; + CGFloat height = image.size.height; + + if (height != 0) { + aspectRatio = width / height; + } + } else { + NSLog(@"Failed to load image from path: %@", imagePath); + } NSDictionary *dict = @{ @"id" : identifier, @"content" : imagePath, @"mimeType" : mimeType, - @"processedAt" : timestamp + @"processedAt" : timestamp, + @"aspectRatio" : @(aspectRatio) }; [imageObjectsArray addObject:dict]; diff --git a/src/components/AttachmentPreview.tsx b/src/components/AttachmentPreview.tsx index 14853ffb7c37..b8b2740ec37c 100644 --- a/src/components/AttachmentPreview.tsx +++ b/src/components/AttachmentPreview.tsx @@ -2,19 +2,27 @@ import React from 'react'; import {Image, View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -type AttachmentPreviewProps = {uri: string}; +type AttachmentPreviewProps = {uri: string | undefined; aspectRatio: number | undefined}; -function AttachmentPreview({uri}: AttachmentPreviewProps) { +function AttachmentPreview({uri, aspectRatio}: AttachmentPreviewProps) { // const theme = useTheme(); const styles = useThemeStyles(); // const {isOffline} = useNetwork(); // const {translate} = useLocalize(); return ( - + ); diff --git a/src/languages/en.ts b/src/languages/en.ts index 3b53f360f934..d742fc088a4e 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1022,9 +1022,10 @@ const translations = { bookingArchived: 'This booking is archived', bookingArchivedDescription: 'This booking is archived because the trip date has passed. Add an expense for the final amount if needed.', justTrackIt: 'Just track it (don’t submit it)', - shareRoot: { - shareToExpensify: 'Share to expensify', - }, + }, + share: { + shareToExpensify: 'Share to expensify', + messageInputLabel: 'Message', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 79c2e225c852..f3dc1ad62f64 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1016,9 +1016,10 @@ const translations = { bookingArchived: 'Esta reserva está archivada', bookingArchivedDescription: 'Esta reserva está archivada porque la fecha del viaje ha pasado. Agregue un gasto por el monto final si es necesario.', justTrackIt: 'Solo guardarlo (no enviarlo)', - shareRoot: { - shareToExpensify: 'Share to expensify', - }, + }, + share: { + shareToExpensify: 'Share to expensify', + messageInputLabel: 'Message', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/modules/ShareActionHandlerModule.ts b/src/modules/ShareActionHandlerModule.ts index 98c0fadf64f9..d01443d87c2d 100644 --- a/src/modules/ShareActionHandlerModule.ts +++ b/src/modules/ShareActionHandlerModule.ts @@ -7,6 +7,7 @@ type ShareActionContent = { content: string; mimeType: string; processedAt: string; + aspectRatio: number; }; type ShareActionHandlerType = { diff --git a/src/pages/Share/ShareDetailsPage.tsx b/src/pages/Share/ShareDetailsPage.tsx index 4a84037f9d79..6dd8870a89b0 100644 --- a/src/pages/Share/ShareDetailsPage.tsx +++ b/src/pages/Share/ShareDetailsPage.tsx @@ -3,18 +3,24 @@ import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import AttachmentPreview from '@components/AttachmentPreview'; +import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; +import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import * as FileUtils from '@libs/fileDownload/FileUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {ShareNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as Report from '@userActions/Report'; import * as ShareActions from '@userActions/Share'; import UserListItem from '@src/components/SelectionList/UserListItem'; import ShareActionHandlerModule from '@src/modules/ShareActionHandlerModule'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; type ShareDetailsPageProps = StackScreenProps; @@ -53,42 +59,35 @@ function ShareDetailsPage({ .at(0); }, [reportID, tempShareFiles]); - // const submitForm = useCallback( - // (newComment: string) => { - // playSound(SOUNDS.DONE); + const reportDisplay = OptionsListUtils.getReportDisplayOption(report); - // const newCommentTrimmed = newComment.trim(); + const handleShare = () => { + if (!currentAttachment) { + return; + } + FileUtils.readFileAsync( + currentAttachment.content, + currentAttachment.id, + (file) => { + Report.addAttachment(reportID, file); - // if (attachmentFileRef.current) { - // Report.addAttachment(reportID, attachmentFileRef.current, newCommentTrimmed); - // attachmentFileRef.current = null; - // } else { - // Performance.markStart(CONST.TIMING.MESSAGE_SENT, {message: newCommentTrimmed}); - // onSubmit(newCommentTrimmed); - // } - // }, - // [onSubmit, reportID], - // ); - - // const formattedSelectedParticipants = Object.values(report?.participants?).map((participant) => ({ - // ...participant, - // isSelected: false, - // isInteractive: false, - // })); - - const reportTest = OptionsListUtils.getReportDisplayOption(report); + const routeToNavigate = ROUTES.REPORT_WITH_ID.getRoute(reportID); + Navigation.navigate(routeToNavigate, 'replace'); + }, + () => {}, + ); + }; - console.log('CURRENT ATTACHMENT ', currentAttachment); return ( - + {report && ( @@ -97,27 +96,44 @@ function ShareDetailsPage({ {translate('common.to')} {}} pressableStyle={[styles.flexRow]} shouldSyncFocus={false} + isDisabled /> )} - - {translate('common.to')} + + + + + + + {translate('common.attachment')} + + + + + +