@chem-po/react-native 0.0.25 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/form/UploadProgress/index.js +8 -17
- package/lib/commonjs/components/form/UploadProgress/index.js.map +1 -1
- package/lib/commonjs/components/form/input/file/index.js +74 -9
- package/lib/commonjs/components/form/input/file/index.js.map +1 -1
- package/lib/commonjs/components/image/ImageViewModal.js +52 -2
- package/lib/commonjs/components/image/ImageViewModal.js.map +1 -1
- package/lib/commonjs/components/loading/LoadingImage.js +8 -2
- package/lib/commonjs/components/loading/LoadingImage.js.map +1 -1
- package/lib/commonjs/components/loading/ProgressBar.js +78 -0
- package/lib/commonjs/components/loading/ProgressBar.js.map +1 -0
- package/lib/commonjs/components/loading/index.js +11 -0
- package/lib/commonjs/components/loading/index.js.map +1 -1
- package/lib/commonjs/utils/downloadFile.js +43 -0
- package/lib/commonjs/utils/downloadFile.js.map +1 -0
- package/lib/module/components/form/UploadProgress/index.js +6 -15
- package/lib/module/components/form/UploadProgress/index.js.map +1 -1
- package/lib/module/components/form/input/file/index.js +75 -10
- package/lib/module/components/form/input/file/index.js.map +1 -1
- package/lib/module/components/image/ImageViewModal.js +53 -3
- package/lib/module/components/image/ImageViewModal.js.map +1 -1
- package/lib/module/components/loading/LoadingImage.js +8 -2
- package/lib/module/components/loading/LoadingImage.js.map +1 -1
- package/lib/module/components/loading/ProgressBar.js +70 -0
- package/lib/module/components/loading/ProgressBar.js.map +1 -0
- package/lib/module/components/loading/index.js +1 -0
- package/lib/module/components/loading/index.js.map +1 -1
- package/lib/module/utils/downloadFile.js +35 -0
- package/lib/module/utils/downloadFile.js.map +1 -0
- package/lib/typescript/components/form/UploadProgress/index.d.ts.map +1 -1
- package/lib/typescript/components/form/input/file/index.d.ts +6 -3
- package/lib/typescript/components/form/input/file/index.d.ts.map +1 -1
- package/lib/typescript/components/image/ImageViewModal.d.ts +3 -0
- package/lib/typescript/components/image/ImageViewModal.d.ts.map +1 -1
- package/lib/typescript/components/loading/LoadingImage.d.ts +4 -1
- package/lib/typescript/components/loading/LoadingImage.d.ts.map +1 -1
- package/lib/typescript/components/loading/ProgressBar.d.ts +7 -0
- package/lib/typescript/components/loading/ProgressBar.d.ts.map +1 -0
- package/lib/typescript/components/loading/index.d.ts +1 -0
- package/lib/typescript/components/loading/index.d.ts.map +1 -1
- package/lib/typescript/utils/downloadFile.d.ts +4 -0
- package/lib/typescript/utils/downloadFile.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/components/form/UploadProgress/index.tsx +3 -17
- package/src/components/form/input/file/index.tsx +77 -8
- package/src/components/image/ImageViewModal.tsx +57 -2
- package/src/components/loading/LoadingImage.tsx +16 -1
- package/src/components/loading/ProgressBar.tsx +75 -0
- package/src/components/loading/index.ts +1 -0
- package/src/utils/downloadFile.ts +36 -0
- package/lib/commonjs/components/image/ImageViewModal.backup.js +0 -285
- package/lib/commonjs/components/image/ImageViewModal.backup.js.map +0 -1
- package/lib/commonjs/components/image/ImageViewModal.old.js +0 -285
- package/lib/commonjs/components/image/ImageViewModal.old.js.map +0 -1
- package/lib/module/components/image/ImageViewModal.backup.js +0 -277
- package/lib/module/components/image/ImageViewModal.backup.js.map +0 -1
- package/lib/module/components/image/ImageViewModal.old.js +0 -277
- package/lib/module/components/image/ImageViewModal.old.js.map +0 -1
- package/lib/typescript/components/image/ImageViewModal.backup.d.ts +0 -9
- package/lib/typescript/components/image/ImageViewModal.backup.d.ts.map +0 -1
- package/lib/typescript/components/image/ImageViewModal.old.d.ts +0 -9
- package/lib/typescript/components/image/ImageViewModal.old.d.ts.map +0 -1
- package/src/components/image/ImageViewModal.backup.tsx +0 -261
- package/src/components/image/ImageViewModal.old.tsx +0 -261
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/UploadProgress/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/UploadProgress/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAkB,MAAM,OAAO,CAAA;AAsCtC,eAAO,MAAM,cAAc,GAAI,aAAa;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,sBAUpE,CAAA"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { FileValue, InputRef } from '@chem-po/core';
|
|
1
|
+
import { FileValue, ImageViewOptions, InputRef } from '@chem-po/core';
|
|
2
2
|
import { FileField } from '@chem-po/react';
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
4
5
|
import { FieldProps } from '../../types';
|
|
5
|
-
export declare const FileView: ({ value, hasUpload, imageOptions, withFullView, }: {
|
|
6
|
+
export declare const FileView: ({ value, hasUpload, imageOptions, withFullView, nonImageContainerStyle, withDownload, }: {
|
|
6
7
|
value?: FileValue | null;
|
|
7
8
|
hasUpload?: boolean;
|
|
8
|
-
imageOptions?:
|
|
9
|
+
imageOptions?: ImageViewOptions;
|
|
9
10
|
withFullView?: boolean;
|
|
11
|
+
nonImageContainerStyle?: StyleProp<ViewStyle>;
|
|
12
|
+
withDownload?: boolean;
|
|
10
13
|
}) => React.JSX.Element;
|
|
11
14
|
export declare const FileComponent: React.ForwardRefExoticComponent<FieldProps<FileField> & React.RefAttributes<InputRef>>;
|
|
12
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/form/input/file/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/form/input/file/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAA;AACrF,OAAO,EACL,SAAS,EAQV,MAAM,gBAAgB,CAAA;AAIvB,OAAO,KAA0E,MAAM,OAAO,CAAA;AAC9F,OAAO,EAAE,SAAS,EAA4C,SAAS,EAAE,MAAM,cAAc,CAAA;AAG7F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AA+BxC,eAAO,MAAM,QAAQ,GAAI,yFAOtB;IACD,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,sBAAsB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,sBAkFA,CAAA;AAED,eAAO,MAAM,aAAa,wFA2FzB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageViewModal.d.ts","sourceRoot":"","sources":["../../../../src/components/image/ImageViewModal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoD,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ImageViewModal.d.ts","sourceRoot":"","sources":["../../../../src/components/image/ImageViewModal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoD,MAAM,OAAO,CAAA;AAOxE,UAAU,mBAAmB;IAC3B,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA4OxD,CAAA"}
|
|
@@ -4,11 +4,14 @@ export interface LoadingImageProps {
|
|
|
4
4
|
src?: string | null;
|
|
5
5
|
loadingOverride?: boolean;
|
|
6
6
|
onLoad?: (e: any) => void;
|
|
7
|
+
filename?: string;
|
|
8
|
+
fileType?: string;
|
|
7
9
|
withFullView?: boolean;
|
|
8
10
|
buttonFullView?: boolean;
|
|
9
11
|
style?: ViewStyle;
|
|
10
12
|
width?: DimensionValue;
|
|
11
13
|
height?: DimensionValue;
|
|
14
|
+
withDownload?: boolean;
|
|
12
15
|
}
|
|
13
|
-
export declare const LoadingImage: ({ src, loadingOverride, onLoad, width, height, withFullView, buttonFullView, style, }: LoadingImageProps) => React.JSX.Element;
|
|
16
|
+
export declare const LoadingImage: ({ src, loadingOverride, onLoad, filename, fileType, width, height, withFullView, buttonFullView, withDownload, style, }: LoadingImageProps) => React.JSX.Element;
|
|
14
17
|
//# sourceMappingURL=LoadingImage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingImage.d.ts","sourceRoot":"","sources":["../../../../src/components/loading/LoadingImage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAChF,OAAO,EAAE,cAAc,EAA6C,SAAS,EAAE,MAAM,cAAc,CAAA;AAInG,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,MAAM,CAAC,EAAE,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"LoadingImage.d.ts","sourceRoot":"","sources":["../../../../src/components/loading/LoadingImage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAChF,OAAO,EAAE,cAAc,EAA6C,SAAS,EAAE,MAAM,cAAc,CAAA;AAInG,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAKD,eAAO,MAAM,YAAY,GAAI,yHAY1B,iBAAiB,sBA4FnB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgressBar.d.ts","sourceRoot":"","sources":["../../../../src/components/loading/ProgressBar.tsx"],"names":[],"mappings":"AACA,OAAO,KAA4B,MAAM,OAAO,CAAA;AAGhD,eAAO,MAAM,WAAW,GAAI,8BAIzB;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,sBAiCA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/loading/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/loading/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"downloadFile.d.ts","sourceRoot":"","sources":["../../../src/utils/downloadFile.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,GAAU,KAAK,MAAM,EAAE,UAAU,MAAM,EAAE,UAAU,MAAM;;cAiCjF,CAAA"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@chem-po/react-native",
|
|
3
3
|
"author": "Elan Canfield",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.27",
|
|
6
6
|
"main": "lib/commonjs/index.js",
|
|
7
7
|
"types": "lib/typescript/index.d.ts",
|
|
8
8
|
"source": "src/index.ts",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@react-native-community/slider": "4.5.6",
|
|
37
37
|
"expo-constants": "~17.1.7",
|
|
38
38
|
"expo-document-picker": "~13.1.6",
|
|
39
|
+
"expo-file-system": "~18.1.11",
|
|
39
40
|
"expo-image-picker": "~16.1.4",
|
|
40
41
|
"lottie-react-native": "7.2.2",
|
|
41
42
|
"nested-property": "^4.0.0",
|
|
@@ -50,8 +51,8 @@
|
|
|
50
51
|
"react-native-paper-dates": "^0.22.42",
|
|
51
52
|
"react-native-svg": "15.11.2",
|
|
52
53
|
"zustand": "^4.3.3",
|
|
53
|
-
"@chem-po/core": "0.0.
|
|
54
|
-
"@chem-po/react": "0.0.
|
|
54
|
+
"@chem-po/core": "0.0.27",
|
|
55
|
+
"@chem-po/react": "0.0.27"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@babel/core": "^7.26.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UploadsState } from '@chem-po/core'
|
|
2
|
-
import { useThemeValue } from '@chem-po/react'
|
|
3
2
|
import React, { useMemo } from 'react'
|
|
4
|
-
import {
|
|
3
|
+
import { StyleSheet, View } from 'react-native'
|
|
4
|
+
import { ProgressBar } from '../../loading/ProgressBar'
|
|
5
5
|
|
|
6
6
|
const styles = StyleSheet.create({
|
|
7
7
|
container: {
|
|
@@ -40,24 +40,10 @@ const styles = StyleSheet.create({
|
|
|
40
40
|
export const UploadProgress = ({ uploads }: { uploads: UploadsState }) => {
|
|
41
41
|
const asArr = useMemo(() => Object.values(uploads), [uploads])
|
|
42
42
|
|
|
43
|
-
const accentColor = useThemeValue('colors.accent.300')
|
|
44
43
|
return (
|
|
45
44
|
<View style={styles.container}>
|
|
46
45
|
{asArr.map(upload => (
|
|
47
|
-
<
|
|
48
|
-
<View style={styles.progressBar}>
|
|
49
|
-
<Animated.View
|
|
50
|
-
style={[
|
|
51
|
-
styles.progressFill,
|
|
52
|
-
{
|
|
53
|
-
width: `${upload.percent * 100}%`,
|
|
54
|
-
backgroundColor: accentColor,
|
|
55
|
-
},
|
|
56
|
-
]}
|
|
57
|
-
/>
|
|
58
|
-
</View>
|
|
59
|
-
<Text style={styles.label}>{upload.label}</Text>
|
|
60
|
-
</Animated.View>
|
|
46
|
+
<ProgressBar key={upload.label} progress={upload.percent} label={upload.label} />
|
|
61
47
|
))}
|
|
62
48
|
</View>
|
|
63
49
|
)
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { FileValue, InputRef, LocalFileValue } from '@chem-po/core'
|
|
1
|
+
import { FileValue, ImageViewOptions, InputRef, LocalFileValue } from '@chem-po/core'
|
|
2
2
|
import {
|
|
3
3
|
FileField,
|
|
4
|
+
useBackgroundColor,
|
|
4
5
|
useBorderColor,
|
|
5
6
|
useIconColor,
|
|
6
7
|
useObjectUrl,
|
|
7
8
|
usePlaceholderColor,
|
|
8
9
|
useTextColor,
|
|
10
|
+
useToast,
|
|
9
11
|
} from '@chem-po/react'
|
|
10
12
|
import { Ionicons } from '@expo/vector-icons'
|
|
11
13
|
import * as DocumentPicker from 'expo-document-picker'
|
|
12
14
|
import * as ImagePicker from 'expo-image-picker'
|
|
13
15
|
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react'
|
|
14
|
-
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
|
|
16
|
+
import { StyleProp, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'
|
|
17
|
+
import { downloadFile } from '../../../../utils/downloadFile'
|
|
15
18
|
import { LoadingImage } from '../../../loading/LoadingImage'
|
|
16
19
|
import { FieldProps } from '../../types'
|
|
17
20
|
|
|
@@ -49,20 +52,57 @@ export const FileView = ({
|
|
|
49
52
|
hasUpload,
|
|
50
53
|
imageOptions,
|
|
51
54
|
withFullView,
|
|
55
|
+
nonImageContainerStyle,
|
|
56
|
+
withDownload,
|
|
52
57
|
}: {
|
|
53
58
|
value?: FileValue | null
|
|
54
59
|
hasUpload?: boolean
|
|
55
|
-
imageOptions?:
|
|
60
|
+
imageOptions?: ImageViewOptions
|
|
56
61
|
withFullView?: boolean
|
|
62
|
+
nonImageContainerStyle?: StyleProp<ViewStyle>
|
|
63
|
+
withDownload?: boolean
|
|
57
64
|
}) => {
|
|
58
65
|
const { storagePath, dataUrl } = value ?? {}
|
|
59
66
|
const missingFile = !dataUrl && !storagePath
|
|
60
67
|
|
|
61
68
|
const { url, loading } = useObjectUrl(value)
|
|
69
|
+
const { showError, showSuccess } = useToast()
|
|
62
70
|
|
|
63
71
|
const iconColor = useIconColor()
|
|
64
72
|
const fileNameColor = useTextColor()
|
|
65
73
|
const borderColor = useBorderColor()
|
|
74
|
+
const backgroundColor = useBackgroundColor(100)
|
|
75
|
+
|
|
76
|
+
const handleDownload = useCallback(async () => {
|
|
77
|
+
if (!url) {
|
|
78
|
+
showError('File URL is not available')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filename = value?.filename?.split('/').pop()?.replace(/\s+/g, '_')
|
|
83
|
+
if (!filename) {
|
|
84
|
+
showError('Filename is not available')
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const fileType = value?.type
|
|
89
|
+
if (!fileType) {
|
|
90
|
+
showError('File type is not available')
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
await downloadFile(url, filename, fileType)
|
|
96
|
+
showSuccess('File downloaded successfully')
|
|
97
|
+
} catch (error: unknown) {
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
console.error('Error downloading file:', error.message)
|
|
100
|
+
} else {
|
|
101
|
+
console.error('Error downloading file:', String(error))
|
|
102
|
+
}
|
|
103
|
+
showError('Failed to download file')
|
|
104
|
+
}
|
|
105
|
+
}, [url, value, showError, showSuccess])
|
|
66
106
|
if (!value || missingFile) {
|
|
67
107
|
return <NoFileView hasUpload={hasUpload} />
|
|
68
108
|
}
|
|
@@ -70,7 +110,10 @@ export const FileView = ({
|
|
|
70
110
|
if (value.type?.startsWith('image/')) {
|
|
71
111
|
return (
|
|
72
112
|
<LoadingImage
|
|
113
|
+
withDownload={withDownload}
|
|
114
|
+
filename={value.filename}
|
|
73
115
|
src={url}
|
|
116
|
+
fileType={value.type}
|
|
74
117
|
loadingOverride={loading}
|
|
75
118
|
width={imageOptions?.width ?? 120}
|
|
76
119
|
height={imageOptions?.height ?? 120}
|
|
@@ -81,9 +124,23 @@ export const FileView = ({
|
|
|
81
124
|
}
|
|
82
125
|
|
|
83
126
|
return (
|
|
84
|
-
<View style={[styles.fileContainer, { borderColor }]}>
|
|
85
|
-
<
|
|
86
|
-
|
|
127
|
+
<View style={[styles.fileContainer, { borderColor, backgroundColor }, nonImageContainerStyle]}>
|
|
128
|
+
<View style={styles.iconContainer}>
|
|
129
|
+
<Ionicons name="document" size={24} color={iconColor} />
|
|
130
|
+
</View>
|
|
131
|
+
<View style={styles.filenameContainer}>
|
|
132
|
+
<Text numberOfLines={1} style={[styles.filename, { color: fileNameColor }]}>
|
|
133
|
+
{value.filename}
|
|
134
|
+
</Text>
|
|
135
|
+
</View>
|
|
136
|
+
{withDownload && (
|
|
137
|
+
<TouchableOpacity
|
|
138
|
+
style={styles.downloadButton}
|
|
139
|
+
onPress={() => void handleDownload()}
|
|
140
|
+
disabled={loading}>
|
|
141
|
+
<Ionicons name="download" size={20} color={iconColor} />
|
|
142
|
+
</TouchableOpacity>
|
|
143
|
+
)}
|
|
87
144
|
</View>
|
|
88
145
|
)
|
|
89
146
|
}
|
|
@@ -220,13 +277,25 @@ const styles = StyleSheet.create({
|
|
|
220
277
|
fileContainer: {
|
|
221
278
|
flexDirection: 'row',
|
|
222
279
|
alignItems: 'center',
|
|
223
|
-
padding: 8,
|
|
224
280
|
borderWidth: 1,
|
|
225
281
|
borderRadius: 4,
|
|
282
|
+
maxWidth: '100%',
|
|
283
|
+
overflow: 'hidden',
|
|
284
|
+
},
|
|
285
|
+
filenameContainer: {
|
|
286
|
+
flex: 1,
|
|
287
|
+
paddingRight: 4,
|
|
288
|
+
justifyContent: 'center',
|
|
226
289
|
},
|
|
227
290
|
filename: {
|
|
228
291
|
fontSize: 14,
|
|
229
|
-
|
|
292
|
+
},
|
|
293
|
+
iconContainer: {
|
|
294
|
+
padding: 4,
|
|
295
|
+
},
|
|
296
|
+
downloadButton: {
|
|
297
|
+
padding: 8,
|
|
298
|
+
marginLeft: 4,
|
|
230
299
|
},
|
|
231
300
|
image: {
|
|
232
301
|
borderRadius: 4,
|
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
import { useScreen } from '@chem-po/react'
|
|
1
|
+
import { useScreen, useToast } from '@chem-po/react'
|
|
2
2
|
import { Ionicons } from '@expo/vector-icons'
|
|
3
3
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
4
4
|
import { Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'
|
|
5
5
|
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
6
6
|
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'
|
|
7
|
+
import { downloadFile } from '../../utils/downloadFile'
|
|
7
8
|
import { LoadingLogo } from '../loading/Loading'
|
|
8
9
|
|
|
9
10
|
interface ImageViewModalProps {
|
|
10
11
|
isOpen: boolean
|
|
11
12
|
onClose: () => void
|
|
12
13
|
src: string | null
|
|
14
|
+
filename?: string
|
|
15
|
+
fileType?: string
|
|
16
|
+
withDownload?: boolean
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
export const ImageViewModal: React.FC<ImageViewModalProps> = ({
|
|
19
|
+
export const ImageViewModal: React.FC<ImageViewModalProps> = ({
|
|
20
|
+
isOpen,
|
|
21
|
+
onClose,
|
|
22
|
+
src,
|
|
23
|
+
filename,
|
|
24
|
+
fileType,
|
|
25
|
+
withDownload,
|
|
26
|
+
}) => {
|
|
16
27
|
const [loading, setLoading] = useState(true)
|
|
17
28
|
const screenWidth = useScreen(s => s.width)
|
|
18
29
|
const screenHeight = useScreen(s => s.height)
|
|
19
30
|
const [imageSize, setImageSize] = useState({ width: screenWidth / 2, height: screenHeight / 2 })
|
|
31
|
+
const { showError, showSuccess } = useToast()
|
|
20
32
|
|
|
21
33
|
// Shared values for zoom and pan (reanimated)
|
|
22
34
|
const scale = useSharedValue(1)
|
|
@@ -64,6 +76,32 @@ export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose,
|
|
|
64
76
|
}
|
|
65
77
|
}, [isOpen, src, resetTransform])
|
|
66
78
|
|
|
79
|
+
// Download functionality
|
|
80
|
+
const handleDownload = useCallback(async () => {
|
|
81
|
+
if (!src) {
|
|
82
|
+
showError('Image URL is not available')
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
if (!fileType) {
|
|
86
|
+
showError('File type is not available')
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const usedName = filename ?? `image_${Date.now()}.jpg`
|
|
92
|
+
|
|
93
|
+
await downloadFile(src, usedName, fileType)
|
|
94
|
+
showSuccess('Image downloaded successfully')
|
|
95
|
+
} catch (error: unknown) {
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
console.error('Error downloading image:', error.message)
|
|
98
|
+
} else {
|
|
99
|
+
console.error('Error downloading image:', String(error))
|
|
100
|
+
}
|
|
101
|
+
showError('Failed to download image')
|
|
102
|
+
}
|
|
103
|
+
}, [src, showError, showSuccess, filename, fileType])
|
|
104
|
+
|
|
67
105
|
// Pan gesture with worklet
|
|
68
106
|
const panGesture = Gesture.Pan()
|
|
69
107
|
.onUpdate(event => {
|
|
@@ -194,6 +232,12 @@ export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose,
|
|
|
194
232
|
</Animated.View>
|
|
195
233
|
</GestureDetector>
|
|
196
234
|
|
|
235
|
+
{withDownload && (
|
|
236
|
+
<TouchableOpacity style={styles.downloadButton} onPress={() => void handleDownload()}>
|
|
237
|
+
<Ionicons name="download" size={24} color="white" />
|
|
238
|
+
</TouchableOpacity>
|
|
239
|
+
)}
|
|
240
|
+
|
|
197
241
|
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
|
198
242
|
<Ionicons name="close" size={24} color="white" />
|
|
199
243
|
</TouchableOpacity>
|
|
@@ -235,6 +279,17 @@ const styles = StyleSheet.create({
|
|
|
235
279
|
width: '100%',
|
|
236
280
|
height: '100%',
|
|
237
281
|
},
|
|
282
|
+
downloadButton: {
|
|
283
|
+
position: 'absolute',
|
|
284
|
+
top: 16,
|
|
285
|
+
right: 64,
|
|
286
|
+
width: 40,
|
|
287
|
+
height: 40,
|
|
288
|
+
borderRadius: 20,
|
|
289
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
290
|
+
justifyContent: 'center',
|
|
291
|
+
alignItems: 'center',
|
|
292
|
+
},
|
|
238
293
|
closeButton: {
|
|
239
294
|
position: 'absolute',
|
|
240
295
|
top: 16,
|
|
@@ -9,11 +9,14 @@ export interface LoadingImageProps {
|
|
|
9
9
|
src?: string | null
|
|
10
10
|
loadingOverride?: boolean
|
|
11
11
|
onLoad?: (e: any) => void
|
|
12
|
+
filename?: string
|
|
13
|
+
fileType?: string
|
|
12
14
|
withFullView?: boolean
|
|
13
15
|
buttonFullView?: boolean
|
|
14
16
|
style?: ViewStyle
|
|
15
17
|
width?: DimensionValue
|
|
16
18
|
height?: DimensionValue
|
|
19
|
+
withDownload?: boolean
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const emptyPng =
|
|
@@ -23,10 +26,13 @@ export const LoadingImage = ({
|
|
|
23
26
|
src,
|
|
24
27
|
loadingOverride,
|
|
25
28
|
onLoad,
|
|
29
|
+
filename,
|
|
30
|
+
fileType,
|
|
26
31
|
width,
|
|
27
32
|
height,
|
|
28
33
|
withFullView,
|
|
29
34
|
buttonFullView,
|
|
35
|
+
withDownload,
|
|
30
36
|
style,
|
|
31
37
|
}: LoadingImageProps) => {
|
|
32
38
|
const [imageLoading, setImageLoading] = useState(!!src)
|
|
@@ -108,7 +114,16 @@ export const LoadingImage = ({
|
|
|
108
114
|
]}>
|
|
109
115
|
<LoadingLogo isLoading={loading} size={10} />
|
|
110
116
|
</View>
|
|
111
|
-
{viewing
|
|
117
|
+
{viewing ? (
|
|
118
|
+
<ImageViewModal
|
|
119
|
+
withDownload={withDownload}
|
|
120
|
+
filename={filename}
|
|
121
|
+
fileType={fileType}
|
|
122
|
+
isOpen
|
|
123
|
+
src={src ?? emptyPng}
|
|
124
|
+
onClose={() => setViewing(false)}
|
|
125
|
+
/>
|
|
126
|
+
) : null}
|
|
112
127
|
</View>
|
|
113
128
|
)
|
|
114
129
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useColorModeValue, useThemeValue } from '@chem-po/react'
|
|
2
|
+
import React, { useEffect, useRef } from 'react'
|
|
3
|
+
import { Animated, StyleSheet, Text, View } from 'react-native'
|
|
4
|
+
|
|
5
|
+
export const ProgressBar = ({
|
|
6
|
+
progress,
|
|
7
|
+
label,
|
|
8
|
+
height = 26,
|
|
9
|
+
}: {
|
|
10
|
+
progress: number
|
|
11
|
+
label?: string
|
|
12
|
+
height?: number
|
|
13
|
+
}) => {
|
|
14
|
+
const accentColor = useThemeValue('colors.accent.300')
|
|
15
|
+
const animatedWidth = useRef(new Animated.Value(0)).current
|
|
16
|
+
const backgroundColor = useColorModeValue('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.2)')
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
Animated.timing(animatedWidth, {
|
|
20
|
+
toValue: progress,
|
|
21
|
+
duration: 300,
|
|
22
|
+
useNativeDriver: false, // width animations require layout driver
|
|
23
|
+
}).start()
|
|
24
|
+
}, [progress, animatedWidth])
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<View style={[styles.progressContainer, { height, backgroundColor }]}>
|
|
28
|
+
<View style={styles.progressBar}>
|
|
29
|
+
<Animated.View
|
|
30
|
+
style={[
|
|
31
|
+
styles.progressFill,
|
|
32
|
+
{
|
|
33
|
+
width: animatedWidth.interpolate({
|
|
34
|
+
inputRange: [0, 1],
|
|
35
|
+
outputRange: ['0%', '100%'],
|
|
36
|
+
extrapolate: 'clamp',
|
|
37
|
+
}),
|
|
38
|
+
backgroundColor: accentColor,
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
</View>
|
|
43
|
+
<Text style={styles.label}>{label}</Text>
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create({
|
|
49
|
+
progressContainer: {
|
|
50
|
+
width: '100%',
|
|
51
|
+
justifyContent: 'center',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
borderRadius: 20,
|
|
54
|
+
padding: 3,
|
|
55
|
+
},
|
|
56
|
+
progressBar: {
|
|
57
|
+
height: '100%',
|
|
58
|
+
width: '100%',
|
|
59
|
+
borderRadius: 20,
|
|
60
|
+
overflow: 'hidden',
|
|
61
|
+
},
|
|
62
|
+
progressFill: {
|
|
63
|
+
height: '100%',
|
|
64
|
+
},
|
|
65
|
+
label: {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
color: 'white',
|
|
68
|
+
fontSize: 12,
|
|
69
|
+
fontWeight: '500',
|
|
70
|
+
textTransform: 'uppercase',
|
|
71
|
+
textShadowColor: 'rgba(0, 0, 0, 0.67)',
|
|
72
|
+
textShadowOffset: { width: 1, height: 1 },
|
|
73
|
+
textShadowRadius: 3,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as FileSystem from 'expo-file-system'
|
|
2
|
+
|
|
3
|
+
export const downloadFile = async (uri: string, filename: string, fileType: string) => {
|
|
4
|
+
const permissions = await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync()
|
|
5
|
+
if (!permissions.granted) {
|
|
6
|
+
return
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Download to temporary location first
|
|
10
|
+
const tempFileUri = `${FileSystem.cacheDirectory}${filename}`
|
|
11
|
+
|
|
12
|
+
// Download the file to temporary location
|
|
13
|
+
await FileSystem.downloadAsync(uri, tempFileUri)
|
|
14
|
+
|
|
15
|
+
// Read the temporary file as base64
|
|
16
|
+
const base64Content = await FileSystem.readAsStringAsync(tempFileUri, {
|
|
17
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Create a file within the selected directory using SAF
|
|
21
|
+
const fileUri = await FileSystem.StorageAccessFramework.createFileAsync(
|
|
22
|
+
permissions.directoryUri,
|
|
23
|
+
filename,
|
|
24
|
+
fileType,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Write the content to the SAF file
|
|
28
|
+
await FileSystem.StorageAccessFramework.writeAsStringAsync(fileUri, base64Content, {
|
|
29
|
+
encoding: FileSystem.EncodingType.Base64,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Clean up temporary file
|
|
33
|
+
await FileSystem.deleteAsync(tempFileUri, { idempotent: true })
|
|
34
|
+
|
|
35
|
+
return { uri: fileUri }
|
|
36
|
+
}
|