diff --git a/native/components/full-screen-view-modal.react.js b/native/components/full-screen-view-modal.react.js
--- a/native/components/full-screen-view-modal.react.js
+++ b/native/components/full-screen-view-modal.react.js
@@ -2,13 +2,7 @@
 
 import invariant from 'invariant';
 import * as React from 'react';
-import {
-  View,
-  Text,
-  StyleSheet,
-  TouchableOpacity,
-  Platform,
-} from 'react-native';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
 import {
   type PinchGestureEvent,
   type PanGestureEvent,
@@ -735,7 +729,7 @@
   }
 
   let copyButton;
-  if (Platform.OS === 'ios' && copyContentCallback) {
+  if (copyContentCallback) {
     copyButton = (
       <TouchableOpacity
         onPress={copyContentCallback}
diff --git a/native/media/image-modal.react.js b/native/media/image-modal.react.js
--- a/native/media/image-modal.react.js
+++ b/native/media/image-modal.react.js
@@ -1,12 +1,12 @@
 // @flow
 
-import Clipboard from '@react-native-clipboard/clipboard';
 import * as React from 'react';
+import { Platform } from 'react-native';
 
 import type { MediaInfo, Dimensions } from 'lib/types/media-types.js';
 
 import Multimedia from './multimedia.react.js';
-import { useIntentionalSaveMedia } from './save-media.js';
+import { useIntentionalSaveMedia, copyMediaIOS } from './save-media.js';
 import FullScreenViewModal from '../components/full-screen-view-modal.react.js';
 import { displayActionResultModal } from '../navigation/action-result-modal.js';
 import type { AppNavigationProp } from '../navigation/app-navigator.react.js';
@@ -47,12 +47,11 @@
     return intentionalSaveMedia(mediaInfo, ids);
   }, [intentionalSaveMedia, item.messageInfo, mediaInfo]);
 
-  const onPressCopy = React.useCallback(() => {
-    const { uri } = mediaInfo;
-    Clipboard.setImageFromURL(uri, success => {
-      displayActionResultModal(success ? 'copied!' : 'failed to copy :(');
-    });
+  const onPressCopyIOS = React.useCallback(async () => {
+    const { success } = await copyMediaIOS(mediaInfo);
+    displayActionResultModal(success ? 'copied!' : 'failed to copy :(');
   }, [mediaInfo]);
+  const onPressCopy = Platform.OS === 'ios' ? onPressCopyIOS : undefined;
 
   const imageDimensions: Dimensions = React.useMemo(() => {
     const frame = {
diff --git a/native/media/save-media.js b/native/media/save-media.js
--- a/native/media/save-media.js
+++ b/native/media/save-media.js
@@ -1,5 +1,6 @@
 // @flow
 
+import Clipboard from '@react-native-clipboard/clipboard';
 import * as MediaLibrary from 'expo-media-library';
 import invariant from 'invariant';
 import * as React from 'react';
@@ -311,36 +312,25 @@
 ): Promise<$ReadOnlyArray<MediaMissionStep>> {
   const steps: Array<MediaMissionStep> = [];
 
-  let uri = inputURI;
-  let tempFile;
-  if (uri.startsWith('http') || isBlobServiceURI(uri)) {
-    const { result: tempSaveResult, steps: tempSaveSteps } =
-      await saveRemoteMediaToDisk(uri, encryptionKey, temporaryDirectoryPath);
-    steps.push(...tempSaveSteps);
-    if (!tempSaveResult.success) {
-      sendResult(tempSaveResult);
-      return steps;
-    }
-    tempFile = tempSaveResult.path;
-    uri = `file://${tempFile}`;
-  } else if (!uri.startsWith('file://')) {
-    const mediaNativeID = getMediaLibraryIdentifier(uri);
-    if (mediaNativeID) {
-      const { result: fetchAssetInfoResult, steps: fetchAssetInfoSteps } =
-        await fetchAssetInfo(mediaNativeID);
-      steps.push(...fetchAssetInfoSteps);
-      const { localURI } = fetchAssetInfoResult;
-      if (localURI) {
-        uri = localURI;
-      }
-    }
-  }
+  const saveMediaToDiskIOSResult = await saveMediaToDiskIOS(
+    inputURI,
+    encryptionKey,
+  );
 
-  if (!uri.startsWith('file://')) {
-    sendResult({ success: false, reason: 'resolve_failed', uri });
+  steps.push(...saveMediaToDiskIOSResult.steps);
+  const { tempFilePath } = saveMediaToDiskIOSResult;
+
+  if (!saveMediaToDiskIOSResult.success) {
+    if (tempFilePath) {
+      const disposeStep = await disposeTempFile(tempFilePath);
+      steps.push(disposeStep);
+    }
+    sendResult(saveMediaToDiskIOSResult.result);
     return steps;
   }
 
+  const { uri } = saveMediaToDiskIOSResult;
+
   let success = false,
     exceptionMessage;
   const start = Date.now();
@@ -364,16 +354,82 @@
     sendResult({ success: false, reason: 'save_to_library_failed', uri });
   }
 
-  if (tempFile) {
-    const disposeStep = await disposeTempFile(tempFile);
+  if (tempFilePath) {
+    const disposeStep = await disposeTempFile(tempFilePath);
     steps.push(disposeStep);
   }
   return steps;
 }
 
+type SaveMediaToDiskIOSResult =
+  | {
+      +success: true,
+      +uri: string,
+      +tempFilePath: ?string,
+      +steps: $ReadOnlyArray<MediaMissionStep>,
+    }
+  | {
+      +success: false,
+      +result: MediaMissionResult,
+      +tempFilePath?: ?string,
+      +steps: $ReadOnlyArray<MediaMissionStep>,
+    };
+async function saveMediaToDiskIOS(
+  inputURI: string,
+  encryptionKey?: ?string,
+): Promise<SaveMediaToDiskIOSResult> {
+  const steps: Array<MediaMissionStep> = [];
+
+  let uri = inputURI;
+  let tempFilePath;
+  if (uri.startsWith('http') || isBlobServiceURI(uri)) {
+    const { result: tempSaveResult, steps: tempSaveSteps } =
+      await saveRemoteMediaToDisk(uri, encryptionKey, temporaryDirectoryPath);
+    steps.push(...tempSaveSteps);
+    if (!tempSaveResult.success) {
+      return {
+        success: false,
+        result: tempSaveResult,
+        steps,
+      };
+    }
+    tempFilePath = tempSaveResult.path;
+    uri = `file://${tempFilePath}`;
+  } else if (!uri.startsWith('file://')) {
+    const mediaNativeID = getMediaLibraryIdentifier(uri);
+    if (mediaNativeID) {
+      const { result: fetchAssetInfoResult, steps: fetchAssetInfoSteps } =
+        await fetchAssetInfo(mediaNativeID);
+      steps.push(...fetchAssetInfoSteps);
+      const { localURI } = fetchAssetInfoResult;
+      if (localURI) {
+        uri = localURI;
+      }
+    }
+  }
+
+  if (!uri.startsWith('file://')) {
+    return {
+      success: false,
+      result: { success: false, reason: 'resolve_failed', uri },
+      tempFilePath,
+      steps,
+    };
+  }
+
+  return {
+    success: true,
+    uri,
+    tempFilePath,
+    steps,
+  };
+}
+
 type IntermediateSaveResult = {
-  result: { success: true, path: string, mime: string } | MediaMissionFailure,
-  steps: $ReadOnlyArray<MediaMissionStep>,
+  +result:
+    | { +success: true, +path: string, +mime: string }
+    | MediaMissionFailure,
+  +steps: $ReadOnlyArray<MediaMissionStep>,
 };
 
 async function saveRemoteMediaToDisk(
@@ -550,4 +606,32 @@
   };
 }
 
-export { useIntentionalSaveMedia, saveMedia };
+async function copyMediaIOS(
+  mediaInfo: MediaInfo,
+): Promise<{ +success: boolean }> {
+  const { uri: mediaURI, blobURI, holder, encryptionKey } = mediaInfo;
+  const inputURI = mediaURI ?? blobURI ?? holder;
+  invariant(inputURI, 'mediaInfo should have a uri or a blobURI');
+
+  const saveMediaToDiskIOSResult = await saveMediaToDiskIOS(
+    inputURI,
+    encryptionKey,
+  );
+
+  if (!saveMediaToDiskIOSResult.success) {
+    const { tempFilePath } = saveMediaToDiskIOSResult;
+    if (tempFilePath) {
+      await disposeTempFile(tempFilePath);
+    }
+    return { success: false };
+  }
+
+  const { uri } = saveMediaToDiskIOSResult;
+  return new Promise<{ +success: boolean }>(resolve => {
+    Clipboard.setImageFromURL(uri, success => {
+      resolve({ success });
+    });
+  });
+}
+
+export { useIntentionalSaveMedia, saveMedia, copyMediaIOS };
diff --git a/patches/@react-native-clipboard+clipboard+1.11.1.patch b/patches/@react-native-clipboard+clipboard+1.11.1.patch
--- a/patches/@react-native-clipboard+clipboard+1.11.1.patch
+++ b/patches/@react-native-clipboard+clipboard+1.11.1.patch
@@ -97,7 +97,7 @@
       * Set content of string array type. You can use following code to set clipboard content
       * ```javascript
 diff --git a/node_modules/@react-native-clipboard/clipboard/ios/RNCClipboard.m b/node_modules/@react-native-clipboard/clipboard/ios/RNCClipboard.m
-index 04143f4..97a4359 100644
+index 04143f4..0edd268 100644
 --- a/node_modules/@react-native-clipboard/clipboard/ios/RNCClipboard.m
 +++ b/node_modules/@react-native-clipboard/clipboard/ios/RNCClipboard.m
 @@ -4,6 +4,8 @@
@@ -109,7 +109,7 @@
  
  
  @implementation RNCClipboard {
-@@ -146,6 +148,100 @@ - (void) listener:(NSNotification *) notification
+@@ -146,6 +148,123 @@ - (void) listener:(NSNotification *) notification
    resolve([NSNumber numberWithBool: imagePresent]);
  }
  
@@ -134,20 +134,43 @@
 +}
 +
 +
-+RCT_EXPORT_METHOD(setImageFromURL: (NSString *)url
++RCT_EXPORT_METHOD(setImageFromURL: (NSString *)urlString
 +                  success:(RCTResponseSenderBlock)success)
 +{
-+  [SDWebImageManager.sharedManager loadImageWithURL:[NSURL URLWithString: url] options:0 progress:NULL completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
-+    
-+    if (error || !finished) {
-+      success(@[@NO]);
++  NSURL *url = [NSURL URLWithString:urlString];
++
++  if ([url.scheme isEqualToString:@"https"] || [url.scheme isEqualToString:@"http"]) {
++    [SDWebImageManager.sharedManager loadImageWithURL:url options:0 progress:NULL completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
++
++      if (error || !finished) {
++        success(@[@NO]);
++        return;
++      }
++
++      UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
++      clipboard.image = image;
++      success(@[@YES]);
++    }];
++  } else if ([url.scheme isEqualToString:@"file"]) {
++    NSString *filePath = [url path];
++    if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
++      NSLog(@"File does not exist at path: %@", filePath);
++      return;
++    }
++
++    NSData *imageData = [NSData dataWithContentsOfFile:filePath];
++    UIImage *image = [UIImage imageWithData:imageData];
++    if (!image) {
++      NSLog(@"Failed to create UIImage from local file at path: %@", filePath);
 +      return;
 +    }
-+    
++
 +    UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
 +    clipboard.image = image;
 +    success(@[@YES]);
-+  }];
++  } else {
++    NSLog(@"Unsupported URL scheme: %@", url.scheme);
++  }
 +}
 +
 +RCT_EXPORT_METHOD(getPNGImageData : (RCTPromiseResolveBlock)resolve reject : (__unused RCTPromiseRejectBlock)reject)