From f8869b4f0ed3b3702c3a338d28ba800652d0bea3 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 19 Nov 2025 11:39:32 -0800 Subject: [PATCH] Remove delete on upload for android. --- app.json | 2 +- app/jobs/[jobId]/_layout.tsx | 2 +- babel-translations.babel | 42 ++++++++ components/error/error-display.jsx | 7 +- components/settings/settings.jsx | 17 ++- components/settings/upload-delete-switch.jsx | 4 +- redux/photos/photos.sagas.js | 103 ++++++++++--------- translations/en-US/common.json | 2 + translations/es-MX/common.json | 2 + translations/fr-CA/common.json | 2 + 10 files changed, 131 insertions(+), 52 deletions(-) diff --git a/app.json b/app.json index 5786c52..6797aed 100644 --- a/app.json +++ b/app.json @@ -6,7 +6,7 @@ "scheme": "imex-mobile-scheme", "userInterfaceStyle": "automatic", "extra": { - "expover": "19", + "expover": "21", "eas": { "projectId": "ffe01f3a-d507-4698-82cd-da1f1cad450b" } diff --git a/app/jobs/[jobId]/_layout.tsx b/app/jobs/[jobId]/_layout.tsx index 201cddb..bb9486c 100644 --- a/app/jobs/[jobId]/_layout.tsx +++ b/app/jobs/[jobId]/_layout.tsx @@ -15,7 +15,7 @@ function JobTabLayout() { headerShown: false, animation: "fade", tabBarStyle: { - marginTop: -48, + marginTop: -24, }, }} > diff --git a/babel-translations.babel b/babel-translations.babel index 9993f3c..45c0f04 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -1566,6 +1566,48 @@ labels + + android_deletephotos + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + android_deletephotos_message + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + converting false diff --git a/components/error/error-display.jsx b/components/error/error-display.jsx index 18a92c1..c46c03d 100644 --- a/components/error/error-display.jsx +++ b/components/error/error-display.jsx @@ -1,11 +1,16 @@ +import { useTheme } from "@/hooks"; import { useTranslation } from "react-i18next"; import { Button, Card, Text } from "react-native-paper"; export default function ErrorDisplay({ errorMessage, error, onDismiss }) { const { t } = useTranslation(); + const theme = useTheme(); return ( - + diff --git a/components/settings/settings.jsx b/components/settings/settings.jsx index 4df792b..83c5f76 100644 --- a/components/settings/settings.jsx +++ b/components/settings/settings.jsx @@ -1,3 +1,4 @@ +import { useTheme } from "@/hooks"; import { selectDeleteAfterUpload } from "@/redux/app/app.selectors"; import { signOutStart } from "@/redux/user/user.actions"; import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors"; @@ -10,7 +11,7 @@ import * as Notifications from "expo-notifications"; import * as Updates from "expo-updates"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Alert, ScrollView, StyleSheet, View } from "react-native"; +import { Alert, Platform, ScrollView, StyleSheet, View } from "react-native"; import { Button, Card, Divider, List, Text } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; import { connect } from "react-redux"; @@ -31,7 +32,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(Tab); function Tab({ bodyshop, currentUser, signOutStart }) { const { t } = useTranslation(); const [permissionState, setPermissionState] = useState(null); - + const theme = useTheme(); useEffect(() => { (async () => { const status = await Notifications.getPermissionsAsync(); @@ -81,6 +82,18 @@ function Tab({ bodyshop, currentUser, signOutStart }) { + {Platform.OS === "android" && ( + + + {t("mediabrowser.labels.android_deletephotos_message")} + + + )} { toggleDeleteAfterUpload(); }} - value={deleteAfterUpload} + value={Platform.OS === "android" ? false : deleteAfterUpload} + disabled={Platform.OS === "android"} /> ); } diff --git a/redux/photos/photos.sagas.js b/redux/photos/photos.sagas.js index 8a0d7c3..8c4e005 100644 --- a/redux/photos/photos.sagas.js +++ b/redux/photos/photos.sagas.js @@ -1,3 +1,4 @@ +import i18n from "@/translations/i18n"; import axios from "axios"; import * as FileSystem from "expo-file-system/legacy"; import * as ImagePicker from "expo-image-picker"; @@ -11,6 +12,7 @@ import { client } from '../../graphql/client'; import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries"; import { axiosAuthInterceptorId } from "../../util/CleanAxios"; import { fetchImageFromUri, replaceAccents } from '../../util/uploadUtils'; +import { toggleDeleteAfterUpload } from "../app/app.actions"; import { selectDeleteAfterUpload } from "../app/app.selectors"; import { store } from "../store"; import { selectBodyshop, selectCurrentUser } from "../user/user.selectors"; @@ -411,60 +413,69 @@ function* mediaUploadCompletedAction({ payload: photos }) { const filesToDelete = Object.keys(progress).filter((key) => progress[key].status === 'completed').map((key) => progress[key]); if (Platform.OS === "android") { - yield MediaLibrary.getPermissionsAsync(false); - const asset = filesToDelete[0]; - let assetIdToDelete = asset.assetId; + Alert.alert( + i18n.t("mediabrowser.labels.android_deletephotos"), + i18n.t("mediabrowser.labels.android_deletephotos_message"),) - // 2. ANDROID FIX: Find the original asset ID - if (!assetIdToDelete && Platform.OS === 'android') { - // Fetch the last 50 images from the gallery - const recentAssets = yield call(MediaLibrary.getAssetsAsync, { - first: 50, - sortBy: [MediaLibrary.SortBy.creationTime], - mediaType: MediaLibrary.MediaType.photo, - }); + //Set delete on upload to false + yield put(toggleDeleteAfterUpload()) //Toggle is fine - if we got here, it's true. - // Try to match based on width, height, and proximity of creation time - // Note: The cache file timestamp might differ slightly from the original - const foundAsset = recentAssets.assets.find(libraryItem => { - console.log("Comparing library item:", moment(asset.exif.DateTime, "YYYY:MM:DD HH:mm:ss").valueOf(), libraryItem.creationTime); - return ( - libraryItem.width === asset.exif.ImageWidth && - libraryItem.height === asset.exif.ImageLength && - Math.abs(moment(asset.exif.DateTimeOriginal, "YYYY:MM:DD HH:mm:ss").valueOf() - libraryItem.creationTime) < 1000 - ); - }); + // //With new Android restrictions, we can't do this because we don't get the asset ID back from the picker. + // //The workarounds below don't work currently. Leaving code for reference. + // yield MediaLibrary.getPermissionsAsync(false); + // const asset = filesToDelete[0]; + // let assetIdToDelete = asset.assetId; - if (foundAsset) { - assetIdToDelete = foundAsset.id; - } - } + // // 2. ANDROID FIX: Find the original asset ID + // if (!assetIdToDelete && Platform.OS === 'android') { + // // Fetch the last 50 images from the gallery + // const recentAssets = yield call(MediaLibrary.getAssetsAsync, { + // first: 50, + // sortBy: [MediaLibrary.SortBy.creationTime], + // mediaType: MediaLibrary.MediaType.photo, + // }); - // 3. Upload and Delete - if (assetIdToDelete) { - // await uploadFunction(asset.uri); - yield call(MediaLibrary.deleteAssetsAsync, assetIdToDelete); - } + // // Try to match based on width, height, and proximity of creation time + // // Note: The cache file timestamp might differ slightly from the original + // const foundAsset = recentAssets.assets.find(libraryItem => { + // console.log("Comparing library item:", moment(asset.exif.DateTime, "YYYY:MM:DD HH:mm:ss").valueOf(), libraryItem.creationTime); + // return ( + // libraryItem.width === asset.exif.ImageWidth && + // libraryItem.height === asset.exif.ImageLength && + // Math.abs(moment(asset.exif.DateTimeOriginal, "YYYY:MM:DD HH:mm:ss").valueOf() - libraryItem.creationTime) < 1000 + // ); + // }); + + // if (foundAsset) { + // assetIdToDelete = foundAsset.id; + // } + // } + + // // 3. Upload and Delete + // if (assetIdToDelete) { + // // await uploadFunction(asset.uri); + // yield call(MediaLibrary.deleteAssetsAsync, assetIdToDelete); + // } - //Create a new asset with the first file to delete. - // console.log('Trying new delete.'); + // //Create a new asset with the first file to delete. + // // console.log('Trying new delete.'); - const album = yield call(MediaLibrary.createAlbumAsync, - "ImEX Mobile Deleted", - filesToDelete.pop().assetId, - false - ); - //Move the rest. - if (filesToDelete.length > 0) { - const moveResult = yield call(MediaLibrary.addAssetsToAlbumAsync, - filesToDelete.map(f => f.assetId), - album, - false - ); - } - yield call(MediaLibrary.deleteAlbumsAsync, album); + // const album = yield call(MediaLibrary.createAlbumAsync, + // "ImEX Mobile Deleted", + // filesToDelete.pop().assetId, + // false + // ); + // //Move the rest. + // if (filesToDelete.length > 0) { + // const moveResult = yield call(MediaLibrary.addAssetsToAlbumAsync, + // filesToDelete.map(f => f.assetId), + // album, + // false + // ); + // } + // yield call(MediaLibrary.deleteAlbumsAsync, album); } else { yield call(MediaLibrary.deleteAssetsAsync, filesToDelete.map(f => f.assetId)); } diff --git a/translations/en-US/common.json b/translations/en-US/common.json index 5ca1074..d9be1a6 100644 --- a/translations/en-US/common.json +++ b/translations/en-US/common.json @@ -107,6 +107,8 @@ "upload": "Upload" }, "labels": { + "android_deletephotos": "Unable to Delete Photos", + "android_deletephotos_message": "Due to updated security restrictions on Android, media cannot be automatically deleted. Please delete media manually. ", "converting": "Converting", "deleteafterupload": "Delete After Upload", "localserver": "Local Server URL: {{url}}", diff --git a/translations/es-MX/common.json b/translations/es-MX/common.json index 1803ec3..88f31af 100644 --- a/translations/es-MX/common.json +++ b/translations/es-MX/common.json @@ -107,6 +107,8 @@ "upload": "" }, "labels": { + "android_deletephotos": "", + "android_deletephotos_message": "", "converting": "", "deleteafterupload": "", "localserver": "", diff --git a/translations/fr-CA/common.json b/translations/fr-CA/common.json index ab7d174..ddd667a 100644 --- a/translations/fr-CA/common.json +++ b/translations/fr-CA/common.json @@ -107,6 +107,8 @@ "upload": "" }, "labels": { + "android_deletephotos": "", + "android_deletephotos_message": "", "converting": "", "deleteafterupload": "", "localserver": "",