Files
imexmobile/components/upload-progress/upload-progress.jsx
2025-10-31 09:38:51 -07:00

164 lines
4.4 KiB
JavaScript

import { useTheme } from "@/hooks";
import { cancelUploads, clearUploadError } from "@/redux/photos/photos.actions";
import { formatBytes } from "@/util/uploadUtils";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { ScrollView, StyleSheet, View } from "react-native";
import {
Button,
Divider,
Modal,
Portal,
ProgressBar,
Text,
} from "react-native-paper";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectPhotos,
selectUploadError,
selectUploadProgress,
} from "../../redux/photos/photos.selectors";
const mapStateToProps = createStructuredSelector({
photos: selectPhotos,
photoUploadProgress: selectUploadProgress,
uploadError: selectUploadError,
});
const mapDispatchToProps = (dispatch) => ({
clearError: () => dispatch(clearUploadError()),
cancelUploads: () => dispatch(cancelUploads()),
});
export default connect(mapStateToProps, mapDispatchToProps)(UploadProgress);
export function UploadProgress({
photos,
photoUploadProgress,
uploadError,
clearError,
cancelUploads,
}) {
const { t } = useTranslation();
const theme = useTheme();
const completion = useMemo(() => {
const total = Object.keys(photoUploadProgress).length;
if (total === 0) return 0;
const completed = Object.values(photoUploadProgress).filter(
(p) => p.progress === 1
).length;
return completed / total;
}, [photoUploadProgress]);
const handleCancelUploads = () => {
cancelUploads();
};
return (
<Portal>
<Modal
visible={photos?.length > 0}
style={styles.modalOuter} // add
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: theme.colors.elevation.level1 },
]}
>
<ScrollView style={styles.modalFill}>
<Text variant="titleLarge" style={styles.title}>
{t("general.labels.upload")}
</Text>
<Text variant="labelLarge">
{`${t("general.labels.uploadprogress")} ${Math.round(
completion * 100
)}%`}
</Text>
<ProgressBar
progress={completion}
style={styles.progress}
color={completion === 1 ? "green" : "blue"}
/>
<Divider style={{ marginVertical: 12 }} />
{Object.keys(photoUploadProgress).map((key) => (
<View key={key} style={styles.progressItem}>
<Text
style={styles.progressText}
numberOfLines={1}
ellipsizeMode="tail"
>
{photoUploadProgress[key].fileName}
</Text>
<View style={styles.progressBarContainer}>
<ProgressBar
progress={photoUploadProgress[key].progress || 0}
style={styles.progress}
color={
photoUploadProgress[key].progress === 1 ? "green" : "blue"
}
/>
<View style={styles.speedRow}>
<Text>{`${formatBytes(
photoUploadProgress[key].loaded /
(((photoUploadProgress[key].endTime || new Date()) -
photoUploadProgress[key].startTime) /
1000)
)}/sec`}</Text>
</View>
</View>
</View>
))}
<Button onPress={handleCancelUploads}>
{t("general.actions.cancel")}
</Button>
</ScrollView>
</Modal>
</Portal>
);
}
const styles = StyleSheet.create({
modalOuter: {
flex: 1, // ensure outer container can grow,
paddingHorizontal: 24,
paddingVertical: 72,
},
modalContainer: {
width: "100%",
height: "50%", // force full area (important for iOS)
padding: 24,
justifyContent: "center",
borderRadius: 24,
},
modalFill: {
flex: 1,
},
speedRow: {
flexDirection: "row",
alignItems: "center",
},
title: {
alignSelf: "center",
alignItems: "center",
marginBottom: 12,
paddingLeft: 12,
paddingRight: 12,
},
progressItem: {
display: "flex",
flexDirection: "row",
alignItems: "center",
marginBottom: 12,
marginLeft: 12,
marginRight: 12,
},
progressText: {
flex: 1,
flexShrink: 1,
},
progressBarContainer: {
flex: 3,
marginLeft: 12,
marginRight: 12,
},
});