Remove delete on upload for android.

This commit is contained in:
Patrick Fic
2025-11-19 11:39:32 -08:00
parent 5235519dd8
commit f8869b4f0e
10 changed files with 131 additions and 52 deletions

View File

@@ -6,7 +6,7 @@
"scheme": "imex-mobile-scheme", "scheme": "imex-mobile-scheme",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"extra": { "extra": {
"expover": "19", "expover": "21",
"eas": { "eas": {
"projectId": "ffe01f3a-d507-4698-82cd-da1f1cad450b" "projectId": "ffe01f3a-d507-4698-82cd-da1f1cad450b"
} }

View File

@@ -15,7 +15,7 @@ function JobTabLayout() {
headerShown: false, headerShown: false,
animation: "fade", animation: "fade",
tabBarStyle: { tabBarStyle: {
marginTop: -48, marginTop: -24,
}, },
}} }}
> >

View File

@@ -1566,6 +1566,48 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>android_deletephotos</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>android_deletephotos_message</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>converting</name> <name>converting</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,11 +1,16 @@
import { useTheme } from "@/hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, Card, Text } from "react-native-paper"; import { Button, Card, Text } from "react-native-paper";
export default function ErrorDisplay({ errorMessage, error, onDismiss }) { export default function ErrorDisplay({ errorMessage, error, onDismiss }) {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme();
return ( return (
<Card mode="outlined" style={{ margin: 8, backgroundColor: "#ffdddd" }}> <Card
mode="outlined"
style={{ margin: 8, backgroundColor: theme?.colors?.errorContainer }}
>
<Card.Title title={t("general.labels.error")} titleVariant="titleLarge" /> <Card.Title title={t("general.labels.error")} titleVariant="titleLarge" />
<Card.Content> <Card.Content>
<Text> <Text>

View File

@@ -1,3 +1,4 @@
import { useTheme } from "@/hooks";
import { selectDeleteAfterUpload } from "@/redux/app/app.selectors"; import { selectDeleteAfterUpload } from "@/redux/app/app.selectors";
import { signOutStart } from "@/redux/user/user.actions"; import { signOutStart } from "@/redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "@/redux/user/user.selectors"; 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 * as Updates from "expo-updates";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; 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 { Button, Card, Divider, List, Text } from "react-native-paper";
import { SafeAreaView } from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -31,7 +32,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(Tab);
function Tab({ bodyshop, currentUser, signOutStart }) { function Tab({ bodyshop, currentUser, signOutStart }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [permissionState, setPermissionState] = useState(null); const [permissionState, setPermissionState] = useState(null);
const theme = useTheme();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const status = await Notifications.getPermissionsAsync(); const status = await Notifications.getPermissionsAsync();
@@ -81,6 +82,18 @@ function Tab({ bodyshop, currentUser, signOutStart }) {
</Text> </Text>
<UploadDeleteSwitch /> <UploadDeleteSwitch />
</View> </View>
{Platform.OS === "android" && (
<Card
style={{
marginVertical: 8,
backgroundColor: theme?.colors?.errorContainer,
}}
>
<Text style={{ margin: 8, fontStyle: "italic" }}>
{t("mediabrowser.labels.android_deletephotos_message")}
</Text>
</Card>
)}
<List.Item <List.Item
title="Media Storage" title="Media Storage"
description={ description={

View File

@@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { Platform } from "react-native";
import { Switch } from "react-native-paper"; import { Switch } from "react-native-paper";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -22,7 +23,8 @@ export function UploadDeleteSwitch({
onValueChange={() => { onValueChange={() => {
toggleDeleteAfterUpload(); toggleDeleteAfterUpload();
}} }}
value={deleteAfterUpload} value={Platform.OS === "android" ? false : deleteAfterUpload}
disabled={Platform.OS === "android"}
/> />
); );
} }

View File

@@ -1,3 +1,4 @@
import i18n from "@/translations/i18n";
import axios from "axios"; import axios from "axios";
import * as FileSystem from "expo-file-system/legacy"; import * as FileSystem from "expo-file-system/legacy";
import * as ImagePicker from "expo-image-picker"; 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 { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
import { axiosAuthInterceptorId } from "../../util/CleanAxios"; import { axiosAuthInterceptorId } from "../../util/CleanAxios";
import { fetchImageFromUri, replaceAccents } from '../../util/uploadUtils'; import { fetchImageFromUri, replaceAccents } from '../../util/uploadUtils';
import { toggleDeleteAfterUpload } from "../app/app.actions";
import { selectDeleteAfterUpload } from "../app/app.selectors"; import { selectDeleteAfterUpload } from "../app/app.selectors";
import { store } from "../store"; import { store } from "../store";
import { selectBodyshop, selectCurrentUser } from "../user/user.selectors"; 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]); const filesToDelete = Object.keys(progress).filter((key) => progress[key].status === 'completed').map((key) => progress[key]);
if (Platform.OS === "android") { if (Platform.OS === "android") {
yield MediaLibrary.getPermissionsAsync(false); Alert.alert(
const asset = filesToDelete[0]; i18n.t("mediabrowser.labels.android_deletephotos"),
let assetIdToDelete = asset.assetId; i18n.t("mediabrowser.labels.android_deletephotos_message"),)
// 2. ANDROID FIX: Find the original asset ID //Set delete on upload to false
if (!assetIdToDelete && Platform.OS === 'android') { yield put(toggleDeleteAfterUpload()) //Toggle is fine - if we got here, it's true.
// Fetch the last 50 images from the gallery
const recentAssets = yield call(MediaLibrary.getAssetsAsync, {
first: 50,
sortBy: [MediaLibrary.SortBy.creationTime],
mediaType: MediaLibrary.MediaType.photo,
});
// Try to match based on width, height, and proximity of creation time // //With new Android restrictions, we can't do this because we don't get the asset ID back from the picker.
// Note: The cache file timestamp might differ slightly from the original // //The workarounds below don't work currently. Leaving code for reference.
const foundAsset = recentAssets.assets.find(libraryItem => { // yield MediaLibrary.getPermissionsAsync(false);
console.log("Comparing library item:", moment(asset.exif.DateTime, "YYYY:MM:DD HH:mm:ss").valueOf(), libraryItem.creationTime); // const asset = filesToDelete[0];
return ( // let assetIdToDelete = asset.assetId;
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) { // // 2. ANDROID FIX: Find the original asset ID
assetIdToDelete = foundAsset.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 // // Try to match based on width, height, and proximity of creation time
if (assetIdToDelete) { // // Note: The cache file timestamp might differ slightly from the original
// await uploadFunction(asset.uri); // const foundAsset = recentAssets.assets.find(libraryItem => {
yield call(MediaLibrary.deleteAssetsAsync, assetIdToDelete); // 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. // //Create a new asset with the first file to delete.
// console.log('Trying new delete.'); // // console.log('Trying new delete.');
const album = yield call(MediaLibrary.createAlbumAsync, // const album = yield call(MediaLibrary.createAlbumAsync,
"ImEX Mobile Deleted", // "ImEX Mobile Deleted",
filesToDelete.pop().assetId, // filesToDelete.pop().assetId,
false // false
); // );
//Move the rest. // //Move the rest.
if (filesToDelete.length > 0) { // if (filesToDelete.length > 0) {
const moveResult = yield call(MediaLibrary.addAssetsToAlbumAsync, // const moveResult = yield call(MediaLibrary.addAssetsToAlbumAsync,
filesToDelete.map(f => f.assetId), // filesToDelete.map(f => f.assetId),
album, // album,
false // false
); // );
} // }
yield call(MediaLibrary.deleteAlbumsAsync, album); // yield call(MediaLibrary.deleteAlbumsAsync, album);
} else { } else {
yield call(MediaLibrary.deleteAssetsAsync, filesToDelete.map(f => f.assetId)); yield call(MediaLibrary.deleteAssetsAsync, filesToDelete.map(f => f.assetId));
} }

View File

@@ -107,6 +107,8 @@
"upload": "Upload" "upload": "Upload"
}, },
"labels": { "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", "converting": "Converting",
"deleteafterupload": "Delete After Upload", "deleteafterupload": "Delete After Upload",
"localserver": "Local Server URL: {{url}}", "localserver": "Local Server URL: {{url}}",

View File

@@ -107,6 +107,8 @@
"upload": "" "upload": ""
}, },
"labels": { "labels": {
"android_deletephotos": "",
"android_deletephotos_message": "",
"converting": "", "converting": "",
"deleteafterupload": "", "deleteafterupload": "",
"localserver": "", "localserver": "",

View File

@@ -107,6 +107,8 @@
"upload": "" "upload": ""
}, },
"labels": { "labels": {
"android_deletephotos": "",
"android_deletephotos_message": "",
"converting": "", "converting": "",
"deleteafterupload": "", "deleteafterupload": "",
"localserver": "", "localserver": "",