diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 4c557886c2f6..458d7504a9d9 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -39,7 +39,8 @@ BDB853621F354EBB84E619C2 /* ExpensifyNewKansas-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = D2AFB39EC1D44BF9B91D3227 /* ExpensifyNewKansas-MediumItalic.otf */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.m in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.m */; }; DDCB2E57F334C143AC462B43 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D20D83B0E39BA6D21761E72 /* ExpoModulesProvider.swift */; }; - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + E5647A2C2B88F2000047156F /* RCTShareExtensionHandlerModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E5647A2B2B88F2000047156F /* RCTShareExtensionHandlerModule.m */; }; E5CB2F112B7F834000B63003 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5CB2F102B7F834000B63003 /* ShareViewController.swift */; }; E5CB2F142B7F834000B63003 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E5CB2F122B7F834000B63003 /* MainInterface.storyboard */; }; E5CB2F182B7F834000B63003 /* ShareMenuExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = E5CB2F0E2B7F834000B63003 /* ShareMenuExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -157,6 +158,8 @@ E2C8555C607612465A7473F8 /* Pods-NewExpensify-NewExpensifyTests.releaseadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.releaseadhoc.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.releaseadhoc.xcconfig"; sourceTree = ""; }; E2F1036F70CBFE39E9352674 /* Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.debug development.xcconfig"; sourceTree = ""; }; E2F78D2A9B3DB96F0524690B /* Pods-NewExpensify-NewExpensifyTests.releaseproduction.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NewExpensify-NewExpensifyTests.releaseproduction.xcconfig"; path = "Target Support Files/Pods-NewExpensify-NewExpensifyTests/Pods-NewExpensify-NewExpensifyTests.releaseproduction.xcconfig"; sourceTree = ""; }; + E5647A2A2B88F1990047156F /* ios:NewExpensify:RCTShareExtensionHandlerModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ios:NewExpensify:RCTShareExtensionHandlerModule.h"; sourceTree = ""; }; + E5647A2B2B88F2000047156F /* RCTShareExtensionHandlerModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTShareExtensionHandlerModule.m; sourceTree = ""; }; E5CB2F0E2B7F834000B63003 /* ShareMenuExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareMenuExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E5CB2F102B7F834000B63003 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; E5CB2F132B7F834000B63003 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; @@ -188,9 +191,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, 976CCB5F8C921482E6AEAE71 /* libPods-NewExpensify.a in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, EEAE4F8907465429AA5B5520 /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -297,6 +300,8 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + E5647A2B2B88F2000047156F /* RCTShareExtensionHandlerModule.m */, + E5647A2A2B88F1990047156F /* ios:NewExpensify:RCTShareExtensionHandlerModule.h */, 374FB8D528A133A7000D84EF /* OriginImageRequestHandler.h */, 374FB8D628A133FE000D84EF /* OriginImageRequestHandler.mm */, F0C450E92705020500FD2970 /* colors.json */, @@ -922,6 +927,7 @@ buildActionMask = 2147483647; files = ( 18D050E0262400AF000D658B /* BridgingFile.swift in Sources */, + E5647A2C2B88F2000047156F /* RCTShareExtensionHandlerModule.m in Sources */, 0F5E5350263B73FD004CA14F /* EnvironmentChecker.m in Sources */, 374FB8D728A133FE000D84EF /* OriginImageRequestHandler.mm in Sources */, 7041848526A8E47D00E09F4D /* RCTStartupTimer.m in Sources */, diff --git a/ios/RCTShareExtensionHandlerModule.m b/ios/RCTShareExtensionHandlerModule.m new file mode 100644 index 000000000000..3891cecf4c33 --- /dev/null +++ b/ios/RCTShareExtensionHandlerModule.m @@ -0,0 +1,85 @@ +// +// RCTShareExtensionHandlerModule.m +// NewExpensify +// +// Created by Bartek Krasoń on 23/02/2024. +// + +#import +#import "ios:NewExpensify:RCTShareExtensionHandlerModule.h" +#import + +NSString *const ShareExtensionGroupIdentifier = @"group.com.new-expensify"; +NSString *const ShareExtensionFilesKey = @"ShareFiles"; + +@implementation RCTShareExtensionHandlerModule + +// To export a module named RCTCalendarModule +RCT_EXPORT_MODULE(RCTShareExtensionHandlerModule); + +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(@"handleShareExtension Failed to find images in 'sharedImagesFolderPath'"); + return; + } + NSLog(@"handleShareExtension 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(@"handleShareExtension Invalid image in position %d, imageSrcPath[i] is nil", i); + continue; + } + NSLog(@"handleShareExtension Valid image in position %d", i); + NSString *srcImageAbsolutePath = [sharedImagesFolderPath stringByAppendingPathComponent:imageSrcPath[i]]; + UIImage *smartScanImage = [[UIImage alloc] initWithContentsOfFile:srcImageAbsolutePath]; + if (smartScanImage == NULL) { + NSLog(@"handleShareExtension 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 diff --git a/ios/ShareMenuExtension/ShareViewController.swift b/ios/ShareMenuExtension/ShareViewController.swift index b3caaa8cbc38..b9b1eb1b3668 100644 --- a/ios/ShareMenuExtension/ShareViewController.swift +++ b/ios/ShareMenuExtension/ShareViewController.swift @@ -21,6 +21,9 @@ class ShareViewController: UIViewController { 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 diff --git a/ios/ios:NewExpensify:RCTShareExtensionHandlerModule.h b/ios/ios:NewExpensify:RCTShareExtensionHandlerModule.h new file mode 100644 index 000000000000..161bb3496766 --- /dev/null +++ b/ios/ios:NewExpensify:RCTShareExtensionHandlerModule.h @@ -0,0 +1,15 @@ +// +// ios:NewExpensify:RCTShareExtensionHandlerModule.h +// NewExpensify +// +// Created by Bartek Krasoń on 23/02/2024. +// + +#ifndef ios_NewExpensify_RCTShareExtensionHandlerModule_h +#define ios_NewExpensify_RCTShareExtensionHandlerModule_h + +#import +@interface RCTShareExtensionHandlerModule : NSObject +@end + +#endif /* ios_NewExpensify_RCTShareExtensionHandlerModule_h */ diff --git a/src/modules/ShareExtensionHandlerModule.ts b/src/modules/ShareExtensionHandlerModule.ts new file mode 100644 index 000000000000..a283189ca9c6 --- /dev/null +++ b/src/modules/ShareExtensionHandlerModule.ts @@ -0,0 +1,9 @@ +import {NativeModules} from 'react-native'; + +const {ShareExtensionHandlerModule} = NativeModules; + +type ShareExtensionHandlerType = { + processFiles(callback: (array: string[]) => void): void; +}; + +export default ShareExtensionHandlerModule as ShareExtensionHandlerType; diff --git a/src/pages/share/ShareRootPage.tsx b/src/pages/share/ShareRootPage.tsx index b0485ba2e53e..26da0df7f290 100644 --- a/src/pages/share/ShareRootPage.tsx +++ b/src/pages/share/ShareRootPage.tsx @@ -1,9 +1,18 @@ +import React, {useEffect} from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; +import ShareExtensionHandlerModule from '@src/modules/ShareExtensionHandlerModule'; // type ShareRootPageProps = {}; export default function ShareRootPage() { + useEffect(() => { + ShareExtensionHandlerModule?.processFiles((processedFiles) => { + // eslint-disable-next-line no-console + console.warn('PROCESSED FILES ', processedFiles); + }); + }, []); + return ( share root