diff --git a/examples/ExpoMessaging/package.json b/examples/ExpoMessaging/package.json index 98c2a59624..ed87be9264 100644 --- a/examples/ExpoMessaging/package.json +++ b/examples/ExpoMessaging/package.json @@ -23,7 +23,6 @@ "expo-image-manipulator": "~13.0.6", "expo-image-picker": "~16.0.6", "expo-linking": "~7.0.5", - "expo-media-library": "~17.0.6", "expo-router": "~4.0.17", "expo-sharing": "~13.0.1", "expo-splash-screen": "~0.29.22", diff --git a/examples/ExpoMessaging/yarn.lock b/examples/ExpoMessaging/yarn.lock index 33622cec6e..3c8557230b 100644 --- a/examples/ExpoMessaging/yarn.lock +++ b/examples/ExpoMessaging/yarn.lock @@ -4301,11 +4301,6 @@ expo-linking@~7.0.5: expo-constants "~17.0.5" invariant "^2.2.4" -expo-media-library@~17.0.6: - version "17.0.6" - resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-17.0.6.tgz#355f5f5abf0b5b35cdf009f18567cbba12d8dc82" - integrity sha512-LUnfrddmee1xLOkyG2NN1l9xQbtvMX3fbM1brEGHg0SKSndvjod3FQdhTzZEYAariqW2RSxQR8v1IsheIoLQXg== - expo-modules-autolinking@2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-2.0.8.tgz#b00c10ebb589ce2220548bbaee4865db1cf1f1f7" @@ -5540,6 +5535,11 @@ linkifyjs@^4.1.1: resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== +linkifyjs@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" + integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5943,6 +5943,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -7402,10 +7407,9 @@ stream-buffers@2.2.x, stream-buffers@~2.2.0: version "0.0.0" uid "" -stream-chat@9.0.0-rc.8: - version "9.0.0-rc.8" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0-rc.8.tgz#e188e481841493584691ae491916843d0ef5f9cd" - integrity sha512-P+Ksnu1cQQfL1t2/QTJ5rr/z2Jehvd2ap41xZgtfbJssHSD7ahe14TCF/1L7q4jjaNlZcTtLcKXCWbbOdKjDcg== +"stream-chat@https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917": + version "0.0.0-development" + resolved "https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917" dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" @@ -7414,6 +7418,7 @@ stream-chat@9.0.0-rc.8: form-data "^4.0.0" isomorphic-ws "^5.0.0" jsonwebtoken "^9.0.2" + linkifyjs "^4.2.0" ws "^8.18.1" stream-slice@^0.1.2: diff --git a/examples/SampleApp/ios/Podfile.lock b/examples/SampleApp/ios/Podfile.lock index 37c20914c9..c732d5ddd0 100644 --- a/examples/SampleApp/ios/Podfile.lock +++ b/examples/SampleApp/ios/Podfile.lock @@ -1475,6 +1475,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-image-picker (8.2.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-netinfo (11.4.1): - React-Core - react-native-safe-area-context (5.2.0): @@ -2284,6 +2305,7 @@ DEPENDENCIES: - react-native-blob-util (from `../node_modules/react-native-blob-util`) - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - "react-native-document-picker (from `../node_modules/@react-native-documents/picker`)" + - react-native-image-picker (from `../node_modules/react-native-image-picker`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-video (from `../node_modules/react-native-video`) @@ -2439,6 +2461,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-camera-roll/camera-roll" react-native-document-picker: :path: "../node_modules/@react-native-documents/picker" + react-native-image-picker: + :path: "../node_modules/react-native-image-picker" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-safe-area-context: @@ -2557,7 +2581,7 @@ SPEC CHECKSUMS: op-sqlite: c33561ea312a2ae38aae032fd3a42635dc6b57e8 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35 RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd @@ -2589,8 +2613,9 @@ SPEC CHECKSUMS: React-Mapbuffer: 0df2a235bd0182f5cbed6c5f095e66deca12e335 React-microtasksnativemodule: b31e56a980634f383221bfefd5111d04c14c110b react-native-blob-util: 875bbeee07e4ada135e4edf9fc7b22acf8d9721d - react-native-cameraroll: 36dc62b41c7943a79ac2f7cf4d3da10d4138513f + react-native-cameraroll: cdc91c4c953d1a18aa3ce88b5a25698025c8c4d2 react-native-document-picker: 19be73c0423e4bc886cef74ec282eff750698013 + react-native-image-picker: 1c620a65f900a47d6d12ec94874c6a1820ebea7d react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac react-native-safe-area-context: 0b43456abcaaa3c8323bbfafe9c5f0f9511219d2 react-native-video: a225b4d4d3286f3253dc7b00a62e7c8e59d04d51 @@ -2637,9 +2662,9 @@ SPEC CHECKSUMS: SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - stream-chat-react-native: 6b89c43ee042e7a6f00e6eaddf3228b712884846 + stream-chat-react-native: aa6626ef9dd68e28b1239c9f2ee2fc7ce1fd3c3f Yoga: be02ca501b03c79d7027a6bbbd0a8db985034f11 PODFILE CHECKSUM: 4f662370295f8f9cee909f1a4c59a614999a209d -COCOAPODS: 1.16.2 +COCOAPODS: 1.14.3 diff --git a/examples/SampleApp/package.json b/examples/SampleApp/package.json index d8adbf0d48..a3c2e6e4c9 100644 --- a/examples/SampleApp/package.json +++ b/examples/SampleApp/package.json @@ -42,6 +42,7 @@ "react-native-fast-image": "^8.6.3", "react-native-gesture-handler": "^2.24.0", "react-native-haptic-feedback": "^2.3.3", + "react-native-image-picker": "^8.2.0", "react-native-reanimated": "^3.17.1", "react-native-safe-area-context": "^5.2.0", "react-native-screens": "^4.9.1", diff --git a/examples/SampleApp/yarn.lock b/examples/SampleApp/yarn.lock index f92346e8f4..27f23dcb57 100644 --- a/examples/SampleApp/yarn.lock +++ b/examples/SampleApp/yarn.lock @@ -6273,6 +6273,11 @@ mime@^2.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -6890,6 +6895,11 @@ react-native-haptic-feedback@^2.3.3: resolved "https://registry.yarnpkg.com/react-native-haptic-feedback/-/react-native-haptic-feedback-2.3.3.tgz#88b6876e91399a69bd1b551fe1681b2f3dc1214e" integrity sha512-svS4D5PxfNv8o68m9ahWfwje5NqukM3qLS48+WTdhbDkNUkOhP9rDfDSRHzlhk4zq+ISjyw95EhLeh8NkKX5vQ== +react-native-image-picker@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-8.2.0.tgz#d8656fdd1a0f1ad262c9c129d4f75900b685e56e" + integrity sha512-jIGllQJuJIn0YKss/JEeb0Kos1HSsnIpU+i3bYxR27sOxSyDZQyP9dKR22olssQPlfH+rGNR/Jc6xKRkhm48vw== + react-native-is-edge-to-edge@1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz#69ec13f70d76e9245e275eed4140d0873a78f902" diff --git a/examples/TypeScriptMessaging/yarn.lock b/examples/TypeScriptMessaging/yarn.lock index 4725572e1f..7643827d31 100644 --- a/examples/TypeScriptMessaging/yarn.lock +++ b/examples/TypeScriptMessaging/yarn.lock @@ -3564,7 +3564,7 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-symbol@^3.1.1, es6-symbol@^3.1.3: +es6-symbol@^3.1.1, es6-symbol@^3.1.3, es6-symbol@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c" integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== @@ -5287,7 +5287,7 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkifyjs@^4.1.1: +linkifyjs@^4.1.1, linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== @@ -6929,10 +6929,9 @@ statuses@~1.5.0: version "0.0.0" uid "" -stream-chat@9.0.0-rc.8: - version "9.0.0-rc.8" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.0.0-rc.8.tgz#e188e481841493584691ae491916843d0ef5f9cd" - integrity sha512-P+Ksnu1cQQfL1t2/QTJ5rr/z2Jehvd2ap41xZgtfbJssHSD7ahe14TCF/1L7q4jjaNlZcTtLcKXCWbbOdKjDcg== +"stream-chat@https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917": + version "0.0.0-development" + resolved "https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917" dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" @@ -6941,6 +6940,7 @@ stream-chat@9.0.0-rc.8: form-data "^4.0.0" isomorphic-ws "^5.0.0" jsonwebtoken "^9.0.2" + linkifyjs "^4.2.0" ws "^8.18.1" strict-uri-encode@^2.0.0: diff --git a/package/expo-package/package.json b/package/expo-package/package.json index d98b520f8f..9c5524c0f3 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -10,12 +10,12 @@ "main": "src/index.js", "types": "types/index.d.ts", "dependencies": { + "mime": "^4.0.7", "stream-chat-react-native-core": "link:../" }, "peerDependencies": { "expo": ">=51.0.0", "expo-av": "*", - "expo-video": "*", "expo-clipboard": "*", "expo-document-picker": "*", "expo-file-system": "*", @@ -23,7 +23,8 @@ "expo-image-manipulator": "*", "expo-image-picker": "*", "expo-media-library": "*", - "expo-sharing": "*" + "expo-sharing": "*", + "expo-video": "*" }, "peerDependenciesMeta": { "expo-av": { diff --git a/package/expo-package/src/optionalDependencies/getPhotos.ts b/package/expo-package/src/optionalDependencies/getPhotos.ts index cd858364a2..c8f244a04a 100644 --- a/package/expo-package/src/optionalDependencies/getPhotos.ts +++ b/package/expo-package/src/optionalDependencies/getPhotos.ts @@ -1,4 +1,7 @@ import { Platform } from 'react-native'; +import mime from 'mime'; + +import type { File } from 'stream-chat-react-native-core'; let MediaLibrary; @@ -13,12 +16,11 @@ if (!MediaLibrary) { 'expo-media-library is not installed. Please install it or you can choose to install expo-image-picker for native image picker.', ); } -import type { Asset } from 'stream-chat-react-native-core'; import { getLocalAssetUri } from './getLocalAssetUri'; type ReturnType = { - assets: Array & { source: 'picker' }>; + assets: File[]; endCursor: string | undefined; hasNextPage: boolean; iOSLimited: boolean; @@ -52,14 +54,13 @@ export const getPhotos = MediaLibrary const assets = await Promise.all( results.assets.map(async (asset) => { const localUri = await getLocalAssetUri(asset.id); + const mimeType = mime.getType(asset.filename); return { duration: asset.duration * 1000, height: asset.height, - id: asset.id, name: asset.filename, - originalUri: asset.uri, - source: 'picker' as const, - type: asset.mediaType, + thumb_url: asset.mediaType === 'photo' ? undefined : asset.uri, + type: mimeType, uri: localUri || asset.uri, width: asset.width, }; diff --git a/package/expo-package/src/optionalDependencies/pickDocument.ts b/package/expo-package/src/optionalDependencies/pickDocument.ts index 6c12ebe74b..5071c8d729 100644 --- a/package/expo-package/src/optionalDependencies/pickDocument.ts +++ b/package/expo-package/src/optionalDependencies/pickDocument.ts @@ -38,13 +38,21 @@ export const pickDocument = DocumentPicker // Applicable to latest version of expo-document-picker if (assets) { return { - assets, + assets: assets.map((asset) => ({ + ...asset, + type: asset.mimeType, + })), cancelled: false, }; } // Applicable to older version of expo-document-picker return { - assets: [rest], + assets: [ + { + ...rest, + type: rest.mimeType, + }, + ], cancelled: false, }; } catch (err) { diff --git a/package/expo-package/src/optionalDependencies/pickImage.ts b/package/expo-package/src/optionalDependencies/pickImage.ts index 6b3df6e6af..25ef02f531 100644 --- a/package/expo-package/src/optionalDependencies/pickImage.ts +++ b/package/expo-package/src/optionalDependencies/pickImage.ts @@ -45,11 +45,10 @@ export const pickImage = ImagePicker duration: asset.duration, name: asset.fileName, size: asset.fileSize, - source: 'picker', type: asset.mimeType, uri: asset.uri, })); - return { assets, cancelled: false, source: 'picker' }; + return { assets, cancelled: false }; } else { return { cancelled: true }; } diff --git a/package/expo-package/src/optionalDependencies/takePhoto.ts b/package/expo-package/src/optionalDependencies/takePhoto.ts index 6baec01d2a..53580644ed 100644 --- a/package/expo-package/src/optionalDependencies/takePhoto.ts +++ b/package/expo-package/src/optionalDependencies/takePhoto.ts @@ -63,7 +63,6 @@ export const takePhoto = ImagePicker duration: photo.duration, // in milliseconds name: 'video_recording_' + date + '.' + photo.uri.split('.').pop(), size: photo.fileSize, - source: 'camera', type: photo.mimeType, uri: photo.uri, }; @@ -95,10 +94,8 @@ export const takePhoto = ImagePicker const date = new Date().toISOString().replace(clearFilter, '_'); return { cancelled: false, - mimeType: photo.mimeType, name: 'image_' + date + '.' + photo.uri.split('.').pop(), size: photo.fileSize, - source: 'camera', type: photo.mimeType, uri: photo.uri, ...size, diff --git a/package/expo-package/yarn.lock b/package/expo-package/yarn.lock index 89f49712c7..640fb75626 100644 --- a/package/expo-package/yarn.lock +++ b/package/expo-package/yarn.lock @@ -3764,6 +3764,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -4777,19 +4782,9 @@ stream-buffers@2.2.x, stream-buffers@~2.2.0: version "0.0.0" uid "" -"stream-chat@https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917": - version "0.0.0-development" - resolved "https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917" - dependencies: - "@types/jsonwebtoken" "^9.0.8" - "@types/ws" "^8.5.14" - axios "^1.6.0" - base64-js "^1.5.1" - form-data "^4.0.0" - isomorphic-ws "^5.0.0" - jsonwebtoken "^9.0.2" - linkifyjs "^4.2.0" - ws "^8.18.1" +"stream-chat@link:../../../stream-chat-js": + version "0.0.0" + uid "" "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" diff --git a/package/native-package/package.json b/package/native-package/package.json index 6af6e3563c..86cbca1f3b 100644 --- a/package/native-package/package.json +++ b/package/native-package/package.json @@ -20,16 +20,17 @@ "types": "types/index.d.ts", "dependencies": { "es6-symbol": "^3.1.4", + "mime": "^4.0.7", "stream-chat-react-native-core": "link:../" }, "peerDependencies": { "@react-native-camera-roll/camera-roll": ">=7.8.0", "@react-native-clipboard/clipboard": ">=1.14.1", + "@react-native-documents/picker": ">=10.1.1", "@stream-io/flat-list-mvcp": ">=0.10.3", "react-native": ">=0.71.0", "react-native-audio-recorder-player": ">=3.6.4", "react-native-blob-util": ">=0.19.9", - "@react-native-documents/picker": ">=10.1.1", "react-native-haptic-feedback": ">=2.2.0", "react-native-image-picker": ">=7.1.2", "react-native-share": ">=10.2.1", diff --git a/package/native-package/src/optionalDependencies/getPhotos.ts b/package/native-package/src/optionalDependencies/getPhotos.ts index bfbf051d37..0b1c0d22c9 100644 --- a/package/native-package/src/optionalDependencies/getPhotos.ts +++ b/package/native-package/src/optionalDependencies/getPhotos.ts @@ -1,4 +1,7 @@ import { PermissionsAndroid, Platform } from 'react-native'; +import mime from 'mime'; + +import type { File } from 'stream-chat-react-native-core'; let CameraRollDependency; @@ -11,12 +14,10 @@ try { ); } -import type { Asset } from 'stream-chat-react-native-core'; - import { getLocalAssetUri } from './getLocalAssetUri'; type ReturnType = { - assets: Array & { source: 'picker' }>; + assets: File[]; endCursor: string | undefined; hasNextPage: boolean; iOSLimited: boolean; @@ -90,16 +91,22 @@ export const getPhotos = CameraRollDependency const assets = await Promise.all( results.edges.map(async (edge) => { const originalUri = edge.node?.image?.uri; - const uri = getLocalAssetUri ? await getLocalAssetUri(originalUri) : originalUri; + const type = + Platform.OS === 'ios' + ? mime.getType(edge.node.image.filename as string) + : edge.node.type; + const isImage = type.includes('image'); + + const uri = + isImage && getLocalAssetUri ? await getLocalAssetUri(originalUri) : originalUri; + return { ...edge.node.image, - duration: edge.node.image.playableDuration * 1000, - // since we include filename, fileSize in the query, we can safely assume it will be defined name: edge.node.image.filename as string, - originalUri, + duration: edge.node.image.playableDuration * 1000, + thumb_url: isImage ? undefined : originalUri, size: edge.node.image.fileSize as number, - source: 'picker' as const, - type: edge.node.type, + type, uri, }; }), diff --git a/package/native-package/src/optionalDependencies/pickDocument.ts b/package/native-package/src/optionalDependencies/pickDocument.ts index 3ed801cec4..42aebe01bb 100644 --- a/package/native-package/src/optionalDependencies/pickDocument.ts +++ b/package/native-package/src/optionalDependencies/pickDocument.ts @@ -43,9 +43,9 @@ export const pickDocument = DocumentPicker return { assets: res.map(({ name, size, type, uri }) => ({ - mimeType: type, name, size, + type, uri, })), cancelled: false, diff --git a/package/native-package/src/optionalDependencies/pickImage.ts b/package/native-package/src/optionalDependencies/pickImage.ts index 770319b285..1d04e68753 100644 --- a/package/native-package/src/optionalDependencies/pickImage.ts +++ b/package/native-package/src/optionalDependencies/pickImage.ts @@ -26,11 +26,10 @@ export const pickImage = ImagePicker duration: asset.duration ? asset.duration * 1000 : undefined, // in milliseconds name: asset.fileName, size: asset.fileSize, - source: 'picker', type: asset.type, uri: asset.uri, })); - return { assets, cancelled: false, source: 'picker' }; + return { assets, cancelled: false }; } else { return { cancelled: true }; } diff --git a/package/native-package/src/optionalDependencies/takePhoto.ts b/package/native-package/src/optionalDependencies/takePhoto.ts index 477841ddc1..914dec735b 100644 --- a/package/native-package/src/optionalDependencies/takePhoto.ts +++ b/package/native-package/src/optionalDependencies/takePhoto.ts @@ -53,10 +53,8 @@ export const takePhoto = ImagePicker ...asset, cancelled: false, duration: asset.duration * 1000, - mimeType: asset.type, name: 'video_recording_' + date + '.' + asset.fileName.split('.').pop(), size: asset.fileSize, - source: 'camera', type: asset.type, uri: asset.uri, }; @@ -90,10 +88,8 @@ export const takePhoto = ImagePicker const date = new Date().toISOString().replace(clearFilter, '_'); return { cancelled: false, - mimeType: asset.type, name: 'video_recording_' + date + '.' + asset.fileName.split('.').pop(), size: asset.size, - source: 'camera', type: asset.type, uri: asset.uri, ...size, diff --git a/package/native-package/yarn.lock b/package/native-package/yarn.lock index 38eb6e1a30..b6bab71d41 100644 --- a/package/native-package/yarn.lock +++ b/package/native-package/yarn.lock @@ -2864,6 +2864,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.7.tgz#0b7a98b08c63bd3c10251e797d67840c9bde9f13" + integrity sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ== + minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -3493,19 +3498,9 @@ statuses@~1.5.0: version "0.0.0" uid "" -"stream-chat@https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917": - version "0.0.0-development" - resolved "https://github.com/GetStream/stream-chat-js.git#9bc79358e9d0a23e0ff7e2a056711289e5bdf917" - dependencies: - "@types/jsonwebtoken" "^9.0.8" - "@types/ws" "^8.5.14" - axios "^1.6.0" - base64-js "^1.5.1" - form-data "^4.0.0" - isomorphic-ws "^5.0.0" - jsonwebtoken "^9.0.2" - linkifyjs "^4.2.0" - ws "^8.18.1" +"stream-chat@link:../../../stream-chat-js": + version "0.0.0" + uid "" string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" diff --git a/package/src/components/Attachment/AudioAttachment.tsx b/package/src/components/Attachment/AudioAttachment.tsx index f2b5333c38..7e0f0873fb 100644 --- a/package/src/components/Attachment/AudioAttachment.tsx +++ b/package/src/components/Attachment/AudioAttachment.tsx @@ -15,7 +15,7 @@ import { VideoProgressData, VideoSeekResponse, } from '../../native'; -import { FileTypes, type FileUpload } from '../../types/types'; +import { AudioUpload, FileTypes } from '../../types/types'; import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; import { ProgressControl } from '../ProgressControl/ProgressControl'; import { WaveProgressBar } from '../ProgressControl/WaveProgressBar'; @@ -23,7 +23,7 @@ import { WaveProgressBar } from '../ProgressControl/WaveProgressBar'; dayjs.extend(duration); export type AudioAttachmentProps = { - item: Omit; + item: Omit; onLoad: (index: string, duration: number) => void; onPlayPause: (index: string, pausedStatus?: boolean) => void; onProgress: (index: string, progress: number) => void; diff --git a/package/src/components/Attachment/FileAttachmentGroup.tsx b/package/src/components/Attachment/FileAttachmentGroup.tsx index ab61c1e06b..dc9247b02a 100644 --- a/package/src/components/Attachment/FileAttachmentGroup.tsx +++ b/package/src/components/Attachment/FileAttachmentGroup.tsx @@ -114,7 +114,9 @@ const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithConte duration: file.duration, file: { name: file.title as string, - uri: file.asset_url, + size: file.file_size || 0, + type: file.mime_type || '', + uri: file.asset_url || '', waveform_data: file.waveform_data, }, id: index.toString(), diff --git a/package/src/components/AttachmentPicker/AttachmentPicker.tsx b/package/src/components/AttachmentPicker/AttachmentPicker.tsx index 46f5284a03..2aeb3d6bb9 100644 --- a/package/src/components/AttachmentPicker/AttachmentPicker.tsx +++ b/package/src/components/AttachmentPicker/AttachmentPicker.tsx @@ -18,7 +18,7 @@ import { import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useScreenDimensions } from '../../hooks/useScreenDimensions'; import { NativeHandlers } from '../../native'; -import type { Asset } from '../../types/types'; +import type { File } from '../../types/types'; import { BottomSheet } from '../BottomSheetCompatibility/BottomSheet'; import { BottomSheetFlatList } from '../BottomSheetCompatibility/BottomSheetFlatList'; @@ -108,7 +108,7 @@ export const AttachmentPicker = React.forwardRef( const [iOSLimited, setIosLimited] = useState(false); const hasNextPageRef = useRef(true); const [loadingPhotos, setLoadingPhotos] = useState(false); - const [photos, setPhotos] = useState([]); + const [photos, setPhotos] = useState([]); const attemptedToLoadPhotosOnOpenRef = useRef(false); const getMorePhotos = useCallback(async () => { @@ -245,14 +245,8 @@ export const AttachmentPicker = React.forwardRef( numberOfUploads: selectedFiles.length + selectedImages.length, // `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri` selected: - selectedImages.some((image) => - image.id - ? image.id === asset.id - : image.uri === asset.uri || image.originalUri === asset.uri, - ) || - selectedFiles.some((file) => - file.id ? file.id === asset.id : file.uri === asset.uri || file.originalUri === asset.uri, - ), + selectedImages.some((image) => image.uri === asset.uri) || + selectedFiles.some((file) => file.uri === asset.uri), selectedFiles, selectedImages, setSelectedFiles, diff --git a/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx b/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx index 1d86d8bb94..0eccbfd1a3 100644 --- a/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx +++ b/package/src/components/AttachmentPicker/components/AttachmentPickerItem.tsx @@ -2,22 +2,19 @@ import React from 'react'; import { Alert, ImageBackground, StyleSheet, Text, View } from 'react-native'; -import { lookup } from 'mime-types'; - import { AttachmentPickerContextValue } from '../../../contexts/attachmentPickerContext/AttachmentPickerContext'; import { useTheme } from '../../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../../contexts/translationContext/TranslationContext'; import { useViewport } from '../../../hooks/useViewport'; import { Recorder } from '../../../icons'; -import type { Asset, File } from '../../../types/types'; +import type { File } from '../../../types/types'; import { getDurationLabelFromDuration } from '../../../utils/utils'; import { BottomSheetTouchableOpacity } from '../../BottomSheetCompatibility/BottomSheetTouchableOpacity'; - type AttachmentPickerItemType = Pick< AttachmentPickerContextValue, 'selectedFiles' | 'setSelectedFiles' | 'setSelectedImages' | 'selectedImages' | 'maxNumberOfFiles' > & { - asset: Asset; + asset: File; ImageOverlaySelectedComponent: React.ComponentType; numberOfUploads: number; selected: boolean; @@ -48,46 +45,25 @@ const AttachmentVideo = (props: AttachmentVideoProps) => { }, } = useTheme(); - const { duration: videoDuration, id: assetId, originalUri, uri } = asset; + const { duration: videoDuration, thumb_url, uri } = asset; - const durationLabel = getDurationLabelFromDuration(videoDuration); + const durationLabel = videoDuration ? getDurationLabelFromDuration(videoDuration) : '00:00'; const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2; - /* Patches video files with uri and mimetype */ - const patchVideoFile = (files: File[]) => { - // We need a mime-type to upload a video file. - const mimeType = lookup(asset.name) || 'multipart/form-data'; - return [ - ...files, - { - duration: asset.duration, - id: asset.id, - mimeType, - name: asset.name, - originalUri, - size: asset.size, - uri, - }, - ]; - }; - const updateSelectedFiles = () => { if (numberOfUploads >= maxNumberOfFiles) { Alert.alert(t('Maximum number of files reached')); return; } - const files = patchVideoFile(selectedFiles); - setSelectedFiles(files); + setSelectedFiles([...selectedFiles, asset]); }; const onPressVideo = () => { if (selected) { setSelectedFiles((files) => // `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri` - files.filter((file) => - file.id ? file.id !== assetId : file.uri !== uri && file.originalUri !== uri, - ), + files.filter((file) => file.uri !== uri), ); } else { updateSelectedFiles(); @@ -97,7 +73,7 @@ const AttachmentVideo = (props: AttachmentVideoProps) => { return ( { const size = vw(100) / (numberOfAttachmentPickerImageColumns || 3) - 2; - const { id: assetId, originalUri, uri } = asset; + const { uri } = asset; const updateSelectedImages = () => { if (numberOfUploads >= maxNumberOfFiles) { @@ -160,11 +136,7 @@ const AttachmentImage = (props: AttachmentImageProps) => { const onPressImage = () => { if (selected) { // `id` is available for Expo MediaLibrary while Cameraroll doesn't share id therefore we use `uri` - setSelectedImages((images) => - images.filter((image) => - assetId ? image.id !== assetId : image.uri !== uri && originalUri !== uri, - ), - ); + setSelectedImages((images) => images.filter((image) => image.uri !== uri)); } else { updateSelectedImages(); } diff --git a/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx b/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx index db0bd739c4..5dc3b9ae47 100644 --- a/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx +++ b/package/src/components/AutoCompleteInput/AutoCompleteInput.tsx @@ -81,14 +81,11 @@ export type AutoCompleteInputProps = Partial; const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext) => { const { additionalTextInputProps, - autoCompleteSuggestionsLimit, closeSuggestions, cooldownActive = false, giphyActive, giphyEnabled, maxMessageLength, - mentionAllAppUsersEnabled, - mentionAllAppUsersQuery, numberOfLines, onChange, openSuggestions, @@ -96,10 +93,16 @@ const AutoCompleteInputWithContext = (props: AutoCompleteInputPropsWithContext) setInputBoxRef, t, text, - triggerSettings, updateSuggestions: updateSuggestionsContext, } = props; + const { + autoCompleteSuggestionsLimit, + mentionAllAppUsersEnabled, + mentionAllAppUsersQuery, + triggerSettings, + } = useMessageInputContext(); + const isTrackingStarted = useRef(false); const selectionEnd = useRef(0); const [textHeight, setTextHeight] = useState(0); diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 3ef716900e..03f7af3491 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -1285,7 +1285,7 @@ const ChannelWithContext = (props: PropsWithChildren) = } const response = doDocUploadRequest ? await doDocUploadRequest(file, channel) - : await channel.sendFile(file.uri, file.name, file.mimeType); + : await channel.sendFile(file.uri, file.name, file.type); attachment.asset_url = response.file; if (response.thumb_url) { attachment.thumb_url = response.thumb_url; diff --git a/package/src/components/MessageInput/FileUploadPreview.tsx b/package/src/components/MessageInput/FileUploadPreview.tsx index 0ec0fa78f9..accf612708 100644 --- a/package/src/components/MessageInput/FileUploadPreview.tsx +++ b/package/src/components/MessageInput/FileUploadPreview.tsx @@ -17,7 +17,7 @@ import { useTranslationContext } from '../../contexts/translationContext/Transla import { Close } from '../../icons/Close'; import { Warning } from '../../icons/Warning'; import { isSoundPackageAvailable } from '../../native'; -import type { FileUpload } from '../../types/types'; +import type { AudioUpload, FileUpload } from '../../types/types'; import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle'; import { getDurationLabelFromDuration, @@ -123,26 +123,49 @@ const UnsupportedFileTypeOrFileSizeIndicator = ({ ); }; -type FileUploadPreviewPropsWithContext = Pick< - MessageInputContextValue, - 'fileUploads' | 'removeFile' | 'uploadFile' | 'setFileUploads' | 'AudioAttachmentUploadPreview' +export type FileUploadPreviewProps = Partial< + Pick< + MessageInputContextValue, + 'fileUploads' | 'removeFile' | 'uploadFile' | 'setFileUploads' | 'AudioAttachmentUploadPreview' + > > & - Pick & - Pick; + Partial> & + Partial>; -const FileUploadPreviewWithContext = (props: FileUploadPreviewPropsWithContext) => { +/** + * FileUploadPreview + * UI Component to preview the files set for upload + */ +export const FileUploadPreview = (props: FileUploadPreviewProps) => { const { - AudioAttachmentUploadPreview, - enableOfflineSupport, - FileAttachmentIcon, - fileUploads, - removeFile, - uploadFile, + AudioAttachmentUploadPreview: propAudioAttachmentUploadPreview, + enableOfflineSupport: propEnableOfflineSupport, + FileAttachmentIcon: propFileAttachmentIcon, + fileUploads: propFileUploads, + removeFile: propRemoveFile, + uploadFile: propUploadFile, } = props; - const [filesToDisplay, setFilesToDisplay] = useState([]); + const { enableOfflineSupport: contextEnableOfflineSupport } = useChatContext(); + const { + AudioAttachmentUploadPreview: contextAudioAttachmentUploadPreview, + fileUploads: contextFileUploads, + removeFile: contextRemoveFile, + uploadFile: contextUploadFile, + } = useMessageInputContext(); + const { FileAttachmentIcon: contextFileAttachmentIcon } = useMessagesContext(); - const flatListRef = useRef | null>(null); + const enableOfflineSupport = propEnableOfflineSupport ?? contextEnableOfflineSupport; + const AudioAttachmentUploadPreview = + propAudioAttachmentUploadPreview ?? contextAudioAttachmentUploadPreview; + const fileUploads = propFileUploads ?? contextFileUploads; + const removeFile = propRemoveFile ?? contextRemoveFile; + const uploadFile = propUploadFile ?? contextUploadFile; + const FileAttachmentIcon = propFileAttachmentIcon ?? contextFileAttachmentIcon; + + const [filesToDisplay, setFilesToDisplay] = useState([]); + + const flatListRef = useRef | null>(null); const [flatListWidth, setFlatListWidth] = useState(0); useEffect(() => { @@ -207,9 +230,9 @@ const FileUploadPreviewWithContext = (props: FileUploadPreviewPropsWithContext) }, } = useTheme(); - const renderItem = ({ item }: { item: FileUpload }) => { + const renderItem = ({ item }: { item: AudioUpload }) => { const indicatorType = getIndicatorTypeForFileState(item.state, enableOfflineSupport); - const isAudio = item.file.mimeType?.startsWith('audio/'); + const isAudio = item.file.type?.startsWith('audio/'); return ( <> @@ -241,7 +264,7 @@ const FileUploadPreviewWithContext = (props: FileUploadPreviewPropsWithContext) ]} > - + { - const { fileUploads: prevFileUploads } = prevProps; - const { fileUploads: nextFileUploads } = nextProps; - - return ( - prevFileUploads.length === nextFileUploads.length && - prevFileUploads.every( - (prevFileUpload, index) => - prevFileUpload.state === nextFileUploads[index].state && - prevFileUpload.paused === nextFileUploads[index].paused && - prevFileUpload.progress === nextFileUploads[index].progress && - prevFileUpload.duration === nextFileUploads[index].duration, - ) - ); -}; - -const MemoizedFileUploadPreview = React.memo( - FileUploadPreviewWithContext, - areEqual, -) as typeof FileUploadPreviewWithContext; - -export type FileUploadPreviewProps = Partial; - -/** - * FileUploadPreview - * UI Component to preview the files set for upload - */ -export const FileUploadPreview = (props: FileUploadPreviewProps) => { - const { enableOfflineSupport } = useChatContext(); - const { AudioAttachmentUploadPreview, fileUploads, removeFile, setFileUploads, uploadFile } = - useMessageInputContext(); - const { FileAttachmentIcon } = useMessagesContext(); - - return ( - - ); -}; - FileUploadPreview.displayName = 'FileUploadPreview{messageInput{fileUploadPreview}}'; diff --git a/package/src/components/MessageInput/ImageUploadPreview.tsx b/package/src/components/MessageInput/ImageUploadPreview.tsx index 9f7417e3c8..e1046f7ba6 100644 --- a/package/src/components/MessageInput/ImageUploadPreview.tsx +++ b/package/src/components/MessageInput/ImageUploadPreview.tsx @@ -20,7 +20,7 @@ import { useTheme } from '../../contexts/themeContext/ThemeContext'; import { useTranslationContext } from '../../contexts/translationContext/TranslationContext'; import { Close } from '../../icons/Close'; import { Warning } from '../../icons/Warning'; -import type { ImageUpload } from '../../types/types'; +import type { FileUpload } from '../../types/types'; import { getIndicatorTypeForFileState, ProgressIndicatorTypes } from '../../utils/utils'; const IMAGE_PREVIEW_SIZE = 100; @@ -81,7 +81,7 @@ type ImageUploadPreviewPropsWithContext = Pick< export type ImageUploadPreviewProps = Partial; -type ImageUploadPreviewItem = { index: number; item: ImageUpload }; +type ImageUploadPreviewItem = { index: number; item: FileUpload }; export const UnsupportedImageTypeIndicator = ({ indicatorType, diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index 64d3810345..9195798dcd 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -61,7 +61,7 @@ import { isImageMediaLibraryAvailable, NativeHandlers, } from '../../native'; -import type { Asset } from '../../types/types'; +import { compressedImageURI } from '../../utils/compressImage'; import { AIStates, useAIState } from '../AITypingIndicatorView'; import { AutoCompleteInput } from '../AutoCompleteInput/AutoCompleteInput'; import { CreatePoll } from '../Poll/CreatePollContent'; @@ -129,6 +129,7 @@ type MessageInputPropsWithContext = Pick< | 'clearEditingState' | 'clearQuotedMessageState' | 'closeAttachmentPicker' + | 'compressImageQuality' | 'editing' | 'FileUploadPreview' | 'fileUploads' @@ -197,6 +198,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { AutoCompleteSuggestionList, closeAttachmentPicker, closePollCreationDialog, + compressImageQuality, cooldownEndsAt, CooldownTimer, CreatePollContent, @@ -347,7 +349,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [imagesForInput, imageUploadsLength]); - const uploadImagesHandler = () => { + const uploadImagesHandler = async () => { const imageToUpload = selectedImages.find((selectedImage) => { const uploadedImage = imageUploads.find( (imageUpload) => @@ -357,7 +359,11 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { }); if (imageToUpload) { - uploadNewImage(imageToUpload); + const compressedImage = await compressedImageURI(imageToUpload, compressImageQuality); + uploadNewImage({ + ...imageToUpload, + uri: compressedImage, + }); } }; @@ -455,16 +461,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { /** * User is editing some message which contains image attachments. **/ - setSelectedImages( - imageUploads - .map((imageUpload) => ({ - height: imageUpload.file.height, - source: imageUpload.file.source, - uri: imageUpload.url || imageUpload.file.uri, - width: imageUpload.file.width, - })) - .filter(Boolean) as Asset[], - ); + setSelectedImages(imageUploads.map((imageUpload) => imageUpload.file)); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -489,15 +486,7 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { /** * User is editing some message which contains video attachments. **/ - setSelectedFiles( - fileUploads.map((fileUpload) => ({ - duration: fileUpload.file.duration, - mimeType: fileUpload.file.mimeType, - name: fileUpload.file.name, - size: fileUpload.file.size, - uri: fileUpload.file.uri, - })), - ); + setSelectedFiles(fileUploads.map((fileUpload) => fileUpload.file)); } } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -1171,6 +1160,7 @@ export const MessageInput = (props: MessageInputProps) => { clearQuotedMessageState, closeAttachmentPicker, closePollCreationDialog, + compressImageQuality, cooldownEndsAt, CooldownTimer, CreatePollContent, @@ -1258,6 +1248,7 @@ export const MessageInput = (props: MessageInputProps) => { clearQuotedMessageState, closeAttachmentPicker, closePollCreationDialog, + compressImageQuality, cooldownEndsAt, CooldownTimer, CreatePollContent, diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx index e9c63d131d..4919a9b74e 100644 --- a/package/src/components/MessageInput/hooks/useAudioController.tsx +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -10,7 +10,8 @@ import { RecordingStatus, SoundReturnType, } from '../../../native'; -import { File, FileTypes } from '../../../types/types'; +import type { File } from '../../../types/types'; +import { FileTypes } from '../../../types/types'; import { resampleWaveformData } from '../utils/audioSampling'; import { normalizeAudioLevel } from '../utils/normalizeAudioLevel'; @@ -267,18 +268,18 @@ export const useAudioController = () => { const file: File = { duration: durationInSeconds, - mimeType: 'audio/aac', name: `audio_recording_${date}.aac`, - type: FileTypes.VoiceRecording, + size: 0, + type: 'audio/aac', uri: typeof recording !== 'string' ? (recording?.getURI() as string) : (recording as string), waveform_data: resampledWaveformData, }; if (multiSendEnabled) { - await uploadNewFile(file); + await uploadNewFile(file, FileTypes.VoiceRecording); } else { // FIXME: cannot call handleSubmit() directly as the function has stale reference to file uploads - await uploadNewFile(file); + await uploadNewFile(file, FileTypes.VoiceRecording); setIsScheduleForSubmit(true); } resetState(); diff --git a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx index a8b34e29c1..189d6a80c7 100644 --- a/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx +++ b/package/src/contexts/attachmentPickerContext/AttachmentPickerContext.tsx @@ -2,7 +2,8 @@ import React, { PropsWithChildren, useContext, useEffect, useState } from 'react import { BottomSheetHandleProps } from '@gorhom/bottom-sheet'; -import type { Asset, File } from '../../types/types'; +import type { File } from '../../types/types'; + import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; @@ -82,11 +83,11 @@ export type AttachmentPickerContextValue = { maxNumberOfFiles: number; openPicker: () => void; selectedFiles: File[]; - selectedImages: Asset[]; + selectedImages: File[]; setBottomInset: React.Dispatch>; setMaxNumberOfFiles: React.Dispatch>; setSelectedFiles: React.Dispatch>; - setSelectedImages: React.Dispatch>; + setSelectedImages: React.Dispatch>; setSelectedPicker: React.Dispatch>; setTopInset: React.Dispatch>; topInset: number; @@ -124,7 +125,7 @@ export const AttachmentPickerProvider = ({ const [bottomInset, setBottomInset] = useState(bottomInsetValue ?? 0); const [maxNumberOfFiles, setMaxNumberOfFiles] = useState(10); - const [selectedImages, setSelectedImages] = useState([]); + const [selectedImages, setSelectedImages] = useState([]); const [selectedFiles, setSelectedFiles] = useState([]); const [selectedPicker, setSelectedPicker] = useState<'images'>(); const [topInset, setTopInset] = useState(topInsetValue ?? 0); diff --git a/package/src/contexts/messageInputContext/MessageInputContext.tsx b/package/src/contexts/messageInputContext/MessageInputContext.tsx index b8d0359cdf..684b4e718a 100644 --- a/package/src/contexts/messageInputContext/MessageInputContext.tsx +++ b/package/src/contexts/messageInputContext/MessageInputContext.tsx @@ -10,7 +10,6 @@ import React, { import { Alert, Keyboard, Linking, TextInput, TextInputProps } from 'react-native'; import uniq from 'lodash/uniq'; -import { lookup } from 'mime-types'; import { Attachment, logChatPromiseExecution, @@ -60,7 +59,7 @@ import { MediaTypes, NativeHandlers, } from '../../native'; -import { Asset, File, FileTypes, FileUpload, ImageUpload } from '../../types/types'; +import { File, FileTypes, FileUpload } from '../../types/types'; import { ACITriggerSettings, ACITriggerSettingsParams, @@ -73,6 +72,7 @@ import { FileStateValue, generateRandomId, getFileNameFromPath, + getFileTypeFromMimeType, isBouncedMessage, } from '../../utils/utils'; import { useAttachmentPickerContext } from '../attachmentPickerContext/AttachmentPickerContext'; @@ -159,7 +159,7 @@ export type LocalMessageInputContext = { * ``` * */ - imageUploads: ImageUpload[]; + imageUploads: FileUpload[]; inputBoxRef: React.MutableRefObject; isValidMessage: () => boolean; mentionedUsers: string[]; @@ -204,7 +204,7 @@ export type LocalMessageInputContext = { >; setFileUploads: React.Dispatch>; setGiphyActive: React.Dispatch>; - setImageUploads: React.Dispatch>; + setImageUploads: React.Dispatch>; /** * Ref callback to set reference on input box */ @@ -229,9 +229,9 @@ export type LocalMessageInputContext = { /** Function for attempting to upload a file */ uploadFile: ({ newFile }: { newFile: FileUpload }) => Promise; /** Function for attempting to upload an image */ - uploadImage: ({ newImage }: { newImage: ImageUpload }) => Promise; - uploadNewFile: (file: File) => Promise; - uploadNewImage: (image: Partial) => Promise; + uploadImage: ({ newImage }: { newImage: FileUpload }) => Promise; + uploadNewFile: (file: File, fileType?: FileTypes) => Promise; + uploadNewImage: (image: File) => Promise; }; export type InputMessageInputContextValue = { @@ -428,10 +428,7 @@ export type InputMessageInputContextValue = { * @overrideType Function */ doImageUploadRequest?: ( - file: { - name?: string; - uri?: string; - }, + file: File, channel: ChannelContextValue['channel'], ) => Promise; @@ -667,11 +664,11 @@ export const MessageInputProvider = ({ const takeAndUploadImage = async (mediaType?: MediaTypes) => { setSelectedPicker(undefined); closePicker(); - const photo = await NativeHandlers.takePhoto({ + const file = await NativeHandlers.takePhoto({ compressImageQuality: value.compressImageQuality, mediaType, }); - if (photo.askToOpenSettings) { + if (file.askToOpenSettings) { Alert.alert( t('Allow camera access in device settings'), t('Device camera is used to take photos or videos.'), @@ -681,11 +678,12 @@ export const MessageInputProvider = ({ ], ); } - if (!photo.cancelled) { - if (photo.type.includes('image')) { - await uploadNewImage(photo); + if (!file.cancelled) { + if (file.type.includes('image')) { + // We already compressed the image in the native handler, so we can upload it directly. + await uploadNewImage(file); } else { - await uploadNewFile({ ...photo, mimeType: photo.type, type: FileTypes.Video }); + await uploadNewFile(file); } } }; @@ -720,9 +718,13 @@ export const MessageInputProvider = ({ } result.assets.forEach(async (asset) => { if (asset.type.includes('image')) { - await uploadNewImage(asset); + const compressedURI = await compressedImageURI(asset, value.compressImageQuality); + await uploadNewImage({ + ...asset, + uri: compressedURI, + }); } else { - await uploadNewFile({ ...asset, mimeType: asset.type, type: FileTypes.Video }); + await uploadNewFile(asset); } }); } @@ -779,14 +781,15 @@ export const MessageInputProvider = ({ if (!result.cancelled && result.assets) { result.assets.forEach(async (asset) => { - /** - * TODO: The current tight coupling of images to the image - * picker does not allow images picked from the file picker - * to be rendered in a preview via the uploadNewImage call. - * This should be updated alongside allowing image a file - * uploads together. - */ - await uploadNewFile(asset); + if (asset.type.includes('image')) { + const compressedURI = await compressedImageURI(asset, value.compressImageQuality); + await uploadNewImage({ + ...asset, + uri: compressedURI, + }); + } else { + await uploadNewFile(asset); + } }); } }; @@ -834,15 +837,13 @@ export const MessageInputProvider = ({ } }; - const mapImageUploadToAttachment = (image: ImageUpload): Attachment => { - const mime_type: string | boolean = lookup(image.file.name as string); - const name = image.file.name as string; + const mapImageUploadToAttachment = (image: FileUpload): Attachment => { return { - fallback: name, + fallback: image.file.name, image_url: image.url, - mime_type: mime_type ? mime_type : undefined, - original_height: image.height, - original_width: image.width, + mime_type: image.file.type, + original_height: image.file.height, + original_width: image.file.width, originalImage: image.file, type: FileTypes.Image, }; @@ -853,7 +854,9 @@ export const MessageInputProvider = ({ return { fallback: file.file.name, image_url: file.url, - mime_type: file.file.mimeType, + mime_type: file.file.type, + original_height: file.file.height, + original_width: file.file.width, originalFile: file.file, type: FileTypes.Image, }; @@ -862,7 +865,7 @@ export const MessageInputProvider = ({ asset_url: file.url || file.file.uri, duration: file.file.duration, file_size: file.file.size, - mime_type: file.file.mimeType, + mime_type: file.file.type, originalFile: file.file, title: file.file.name, type: FileTypes.Audio, @@ -872,7 +875,7 @@ export const MessageInputProvider = ({ asset_url: file.url || file.file.uri, duration: file.file.duration, file_size: file.file.size, - mime_type: file.file.mimeType, + mime_type: file.file.type, originalFile: file.file, thumb_url: file.thumb_url, title: file.file.name, @@ -883,7 +886,7 @@ export const MessageInputProvider = ({ asset_url: file.url || file.file.uri, duration: file.file.duration, file_size: file.file.size, - mime_type: file.file.mimeType, + mime_type: file.file.type, originalFile: file.file, title: file.file.name, type: FileTypes.VoiceRecording, @@ -893,7 +896,7 @@ export const MessageInputProvider = ({ return { asset_url: file.url || file.file.uri, file_size: file.file.size, - mime_type: file.file.mimeType, + mime_type: file.file.type, originalFile: file.file, title: file.file.name, type: FileTypes.File, @@ -1145,7 +1148,7 @@ export const MessageInputProvider = ({ const regexCondition = /File (extension \.\w{2,4}|type \S+) is not supported/; const getUploadSetStateAction = - ( + ( id: string, fileState: FileStateValue, extraData: Partial = {}, @@ -1202,11 +1205,11 @@ export const MessageInputProvider = ({ client.createAbortControllerForNextRequest(), ); // Compress images selected through file picker when uploading them - if (file.mimeType?.includes('image')) { + if (file.type?.includes('image')) { const compressedUri = await compressedImageURI(file, value.compressImageQuality); - response = await channel.sendFile(compressedUri, filename, file.mimeType); + response = await channel.sendFile(compressedUri, filename, file.type); } else { - response = await channel.sendFile(file.uri, filename, file.mimeType); + response = await channel.sendFile(file.uri, filename, file.type); } uploadAbortControllerRef.current.delete(filename); } @@ -1229,7 +1232,7 @@ export const MessageInputProvider = ({ } }; - const uploadImage = async ({ newImage }: { newImage: ImageUpload }) => { + const uploadImage = async ({ newImage }: { newImage: FileUpload }) => { const { file, id } = newImage || {}; if (!file) { @@ -1243,17 +1246,16 @@ export const MessageInputProvider = ({ const filename = escapeRegExp(file.name ?? getFileNameFromPath(uri)); try { - const compressedUri = await compressedImageURI(file, value.compressImageQuality); - const contentType = lookup(filename) || 'multipart/form-data'; + const contentType = file.type || 'multipart/form-data'; if (value.doImageUploadRequest) { response = await value.doImageUploadRequest(file, channel); - } else if (compressedUri && channel) { + } else if (channel) { if (value.sendImageAsync) { uploadAbortControllerRef.current.set( filename, client.createAbortControllerForNextRequest(), ); - channel.sendImage(compressedUri, filename, contentType).then( + channel.sendImage(file.uri, filename, contentType).then( (res) => { uploadAbortControllerRef.current.delete(filename); if (asyncIds.includes(id)) { @@ -1267,7 +1269,7 @@ export const MessageInputProvider = ({ return prevAsyncUploads; }); } else { - const newImageUploads = getUploadSetStateAction( + const newImageUploads = getUploadSetStateAction( id, FileState.UPLOADED, { @@ -1286,13 +1288,13 @@ export const MessageInputProvider = ({ filename, client.createAbortControllerForNextRequest(), ); - response = await channel.sendImage(compressedUri, filename, contentType); + response = await channel.sendImage(file.uri, filename, contentType); uploadAbortControllerRef.current.delete(filename); } } if (Object.keys(response).length) { - const newImageUploads = getUploadSetStateAction(id, FileState.UPLOADED, { + const newImageUploads = getUploadSetStateAction(id, FileState.UPLOADED, { height: file.height, url: response.file, width: file.width, @@ -1312,7 +1314,12 @@ export const MessageInputProvider = ({ } }; - const uploadNewFile = async (file: File) => { + /** + * The fileType is optional and is used to override the file type detection. + * This is useful for voice recordings, where the file type is not always detected correctly. + * This will change if we unify the file uploads to attachments. + */ + const uploadNewFile = async (file: File, fileType?: FileTypes) => { try { const id: string = generateRandomId(); const fileConfig = getFileUploadConfig(); @@ -1334,16 +1341,16 @@ export const MessageInputProvider = ({ } const fileState = isAllowed ? FileState.UPLOADING : FileState.NOT_SUPPORTED; - - // If file type is explicitly provided while upload we use it, else we derive the file type. - const fileType = file.type || file.mimeType?.split('/')[0]; + const derivedFileType = fileType ?? getFileTypeFromMimeType(file.type); const newFile: FileUpload = { duration: file.duration || 0, file, - id: file.id || id, + id, + mime_type: file.type, state: fileState, - type: fileType, + thumb_url: file.thumb_url, + type: derivedFileType, url: file.uri, }; @@ -1360,7 +1367,7 @@ export const MessageInputProvider = ({ } }; - const uploadNewImage = async (image: Partial) => { + const uploadNewImage = async (image: File) => { try { const id = generateRandomId(); const imageUploadConfig = getImageUploadConfig(); @@ -1386,11 +1393,13 @@ export const MessageInputProvider = ({ const imageState = isAllowed ? FileState.UPLOADING : FileState.NOT_SUPPORTED; - const newImage: ImageUpload = { + const newImage: FileUpload = { file: image, height: image.height, id, + mime_type: image.type, state: imageState, + type: FileTypes.Image, url: image.uri, width: image.width, }; diff --git a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts index 2f643289b5..c15a539ef6 100644 --- a/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts +++ b/package/src/contexts/messageInputContext/hooks/useCreateMessageInputContext.ts @@ -114,9 +114,7 @@ export const useCreateMessageInputContext = ({ UploadProgressIndicator, }: MessageInputContextValue & Pick) => { const editingdep = editing?.id; - const fileUploadsValue = fileUploads - .map(({ duration, paused, progress, state }) => `${state},${paused},${progress},${duration}`) - .join(); + const fileUploadsValue = fileUploads.map(({ state }) => state).join(); const imageUploadsValue = imageUploads.map(({ state }) => state).join(); const asyncUploadsValue = Object.keys(asyncUploads).join(); const mentionedUsersLength = mentionedUsers.length; diff --git a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts index ac1e433376..f4d1f93e65 100644 --- a/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts +++ b/package/src/contexts/messageInputContext/hooks/useMessageDetailsForState.ts @@ -2,8 +2,8 @@ import { useEffect, useState } from 'react'; import { Attachment } from 'stream-chat'; -import { FileTypes, FileUpload, ImageUpload } from '../../../types/types'; -import { generateRandomId, stringifyMessage } from '../../../utils/utils'; +import { FileTypes, FileUpload } from '../../../types/types'; +import { generateRandomId, getFileTypeFromMimeType, stringifyMessage } from '../../../utils/utils'; import type { MessageInputContextValue } from '../MessageInputContext'; @@ -12,7 +12,7 @@ export const useMessageDetailsForState = ( initialValue?: string, ) => { const [fileUploads, setFileUploads] = useState([]); - const [imageUploads, setImageUploads] = useState([]); + const [imageUploads, setImageUploads] = useState([]); const [mentionedUsers, setMentionedUsers] = useState([]); const [numberOfUploads, setNumberOfUploads] = useState(0); const [showMoreOptions, setShowMoreOptions] = useState(true); @@ -46,66 +46,59 @@ export const useMessageDetailsForState = ( if (attachment.type === FileTypes.Audio) { return { file: { - duration: attachment.duration, - mimeType: attachment.mime_type, + duration: attachment.duration || 0, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + type: attachment.mime_type || '', + uri: attachment.asset_url || '', }, id, state: 'finished', + type: FileTypes.Audio, url: attachment.asset_url, }; } else if (attachment.type === FileTypes.Video) { return { file: { - duration: attachment.duration, - mimeType: attachment.mime_type, + duration: attachment.duration || 0, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + thumb_url: attachment.thumb_url || '', + type: attachment.mime_type || '', + uri: attachment.asset_url || '', }, id, state: 'finished', thumb_url: attachment.thumb_url, + type: FileTypes.Video, url: attachment.asset_url, }; } else if (attachment.type === FileTypes.VoiceRecording) { return { file: { - duration: attachment.duration, - mimeType: attachment.mime_type, + duration: attachment.duration || 0, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + type: attachment.mime_type || '', + uri: attachment.asset_url || '', waveform_data: attachment.waveform_data, }, id, state: 'finished', - url: attachment.asset_url, - }; - } else if (attachment.type === FileTypes.File) { - return { - file: { - mimeType: attachment.mime_type, - name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, - }, - id, - state: 'finished', + type: FileTypes.VoiceRecording, url: attachment.asset_url, }; } else { return { file: { - mimeType: attachment.mime_type, name: attachment.title || '', - size: attachment.file_size, - uri: attachment.asset_url, + size: attachment.file_size || 0, + type: attachment.mime_type || '', + uri: attachment.asset_url || '', }, id, state: 'finished', + type: getFileTypeFromMimeType(attachment.mime_type || ''), url: attachment.asset_url, }; } @@ -115,7 +108,7 @@ export const useMessageDetailsForState = ( if (message) { setText(message?.text || ''); const newFileUploads: FileUpload[] = []; - const newImageUploads: ImageUpload[] = []; + const newImageUploads: FileUpload[] = []; const attachments = Array.isArray(message.attachments) ? message.attachments : []; @@ -124,14 +117,16 @@ export const useMessageDetailsForState = ( const id = generateRandomId(); newImageUploads.push({ file: { - height: attachment.original_height, - name: attachment.fallback, - size: attachment.file_size, - type: attachment.type, - width: attachment.original_width, + height: attachment.original_height || 0, + name: attachment.fallback || '', + size: attachment.file_size || 0, + type: attachment.type || '', + uri: attachment.image_url || '', + width: attachment.original_width || 0, }, id, state: 'finished', + type: FileTypes.Image, url: attachment.image_url || attachment.asset_url || attachment.thumb_url, }); } else { diff --git a/package/src/contexts/messageInputContext/utils/utils.ts b/package/src/contexts/messageInputContext/utils/utils.ts index 4999a1f1ec..9fbc3ead8a 100644 --- a/package/src/contexts/messageInputContext/utils/utils.ts +++ b/package/src/contexts/messageInputContext/utils/utils.ts @@ -1,13 +1,13 @@ import { lookup } from 'mime-types'; import type { FileUploadConfig } from 'stream-chat'; -import { Asset, File } from '../../../types/types'; +import { File } from '../../../types/types'; export const MAX_FILE_SIZE_TO_UPLOAD = 100 * 1024 * 1024; // 100 MB type CheckUploadPermissionsParams = { config: FileUploadConfig; - file: File | Partial; + file: File; }; /** @@ -44,10 +44,9 @@ export const isUploadAllowed = ({ config, file }: CheckUploadPermissionsParams) } if (allowed_mime_types?.length) { - if (file.mimeType) { + if (file.type) { const allowed = allowed_mime_types.some( - (mimeType: string) => - file.mimeType && file.mimeType.toLowerCase() === mimeType.toLowerCase(), + (mimeType: string) => file.type && file.type.toLowerCase() === mimeType.toLowerCase(), ); if (!allowed) { @@ -66,10 +65,9 @@ export const isUploadAllowed = ({ config, file }: CheckUploadPermissionsParams) } if (blocked_mime_types?.length) { - if (file.mimeType) { + if (file.type) { const blocked = blocked_mime_types.some( - (mimeType: string) => - file.mimeType && file.mimeType.toLowerCase() === mimeType.toLowerCase(), + (mimeType: string) => file.type && file.type.toLowerCase() === mimeType.toLowerCase(), ); if (blocked) { diff --git a/package/src/native.ts b/package/src/native.ts index 88ce2ac038..ca2b816aee 100644 --- a/package/src/native.ts +++ b/package/src/native.ts @@ -1,8 +1,7 @@ import type React from 'react'; import { FlatList as DefaultFlatList, StyleProp, ViewStyle } from 'react-native'; -import type { Asset, File } from './types/types'; - +import type { File } from './types/types'; const fail = () => { throw Error( 'Native handler was not registered, you should import stream-chat-expo or stream-chat-react-native', @@ -31,7 +30,7 @@ type iOS14RefreshGallerySelection = () => Promise; type GetPhotos = ({ after, first }: { first: number; after?: string }) => | Promise<{ - assets: Array & { source: 'picker' }>; + assets: File[]; endCursor: string; hasNextPage: boolean; iOSLimited: boolean; @@ -47,7 +46,7 @@ type PickDocument = ({ maxNumberOfFiles }: { maxNumberOfFiles?: number }) => type PickImageAssetType = { askToOpenSettings?: boolean; - assets?: Array & { source: 'picker' }>; + assets?: File[]; cancelled?: boolean; }; @@ -67,8 +66,7 @@ type ShareOptions = { }; type ShareImage = (options: ShareOptions) => Promise | never; -type Photo = Omit & { - source: 'camera'; +type TakePhotoFileType = File & { askToOpenSettings?: boolean; cancelled?: boolean; }; @@ -78,7 +76,7 @@ export type MediaTypes = 'image' | 'video' | 'mixed'; type TakePhoto = (options: { compressImageQuality?: number; mediaType?: MediaTypes; -}) => Promise | never; +}) => Promise | never; type HapticFeedbackMethod = | 'impactHeavy' diff --git a/package/src/types/types.ts b/package/src/types/types.ts index 4b8d7ea321..3ed8c297ac 100644 --- a/package/src/types/types.ts +++ b/package/src/types/types.ts @@ -1,4 +1,4 @@ -import type { ChannelFilters, ChannelSort, ChannelState } from 'stream-chat'; +import type { ChannelFilters, ChannelSort, ChannelState, RNFile } from 'stream-chat'; import type { FileStateValue } from '../utils/utils'; @@ -12,51 +12,42 @@ export enum FileTypes { VoiceRecording = 'voiceRecording', } -export type Asset = { - duration: number; - height: number; - name: string; - source: 'camera' | 'picker'; - type: string; - uri: string; - width: number; - id?: string; - mimeType?: string; - originalUri?: string; - size?: number; -}; - -export type File = { - name: string; - duration?: number; - id?: string; - mimeType?: string; - originalUri?: string; - size?: number; - type?: FileTypes; - // The uri should be of type `string`. But is `string|undefined` because the same type is used for the response from Stream's Attachment. This shall be fixed. - uri?: string; - waveform_data?: number[]; -}; +export type File = RNFile; +/** + * This is nothing but a substitute for the attachment type prior to sending the message. + * This will change if we unify the file uploads to attachments. + */ export type FileUpload = { file: File; id: string; state: FileStateValue; - duration?: number; - paused?: boolean; - progress?: number; - thumb_url?: string; - type?: string; + + mime_type?: string; + + type?: FileTypes; url?: string; + + thumb_url?: string; + + duration?: number; waveform_data?: number[]; + + height?: number; + width?: number; }; + +export type AudioUpload = FileUpload & { + progress?: number; + paused?: boolean; +}; + export interface DefaultAttachmentType { duration?: number; file_size?: number; mime_type?: string; originalFile?: File; - originalImage?: Partial; + originalImage?: File; waveform_data?: number[]; } @@ -90,16 +81,6 @@ export interface DefaultReactionType {} export interface DefaultThreadType {} -/* eslint-enable @typescript-eslint/no-empty-object-type */ -export type ImageUpload = { - file: Partial; - id: string; - state: FileStateValue; - height?: number; - url?: string; - width?: number; -}; - export type Reaction = { id: string; name: string; diff --git a/package/src/utils/compressImage.ts b/package/src/utils/compressImage.ts index b3efe1a4ac..ff6b7a24f4 100644 --- a/package/src/utils/compressImage.ts +++ b/package/src/utils/compressImage.ts @@ -1,5 +1,5 @@ import { NativeHandlers } from '../native'; -import type { Asset } from '../types/types'; +import type { File } from '../types/types'; /** * Function to compress and Image and return the compressed Image URI @@ -7,7 +7,7 @@ import type { Asset } from '../types/types'; * @param compressImageQuality * @returns string */ -export const compressedImageURI = async (image: Partial, compressImageQuality?: number) => { +export const compressedImageURI = async (image: File, compressImageQuality?: number) => { const uri = image.uri || ''; /** * We skip compression if: @@ -15,8 +15,7 @@ export const compressedImageURI = async (image: Partial, compressImageQua * - the file has no height/width value to maintain for compression * - the compressImageQuality number is not present or is 1 (meaning no compression) */ - const compressedUri = await (image.source === 'camera' || - !image.height || + const compressedUri = await (!image.height || !image.width || typeof compressImageQuality !== 'number' || compressImageQuality === 1 diff --git a/package/src/utils/utils.ts b/package/src/utils/utils.ts index b31b1dc35d..a856ee4c26 100644 --- a/package/src/utils/utils.ts +++ b/package/src/utils/utils.ts @@ -12,7 +12,7 @@ import { import type { EmojiSearchIndex } from '../contexts/messageInputContext/MessageInputContext'; import { compiledEmojis } from '../emoji-data'; import type { TableRowJoinedUser } from '../store/types'; -import type { ValueOf } from '../types/types'; +import { FileTypes, ValueOf } from '../types/types'; export type ReactionData = { Icon: React.ComponentType; @@ -239,12 +239,28 @@ export const getFileNameFromPath = (path: string) => { return match ? match[0] : ''; }; +export const getFileTypeFromMimeType = (mimeType: string) => { + const fileType = mimeType.split('/')[0]; + if (fileType === 'image') { + return FileTypes.Image; + } else if (fileType === 'video') { + return FileTypes.Video; + } else if (fileType === 'audio') { + return FileTypes.Audio; + } + return FileTypes.File; +}; + /** * Utility to get the duration label from the duration in seconds. * @param duration number * @returns string */ export const getDurationLabelFromDuration = (duration: number) => { + if (!duration) { + return '00:00'; + } + const ONE_HOUR_IN_SECONDS = 3600; const ONE_HOUR_IN_MILLISECONDS = ONE_HOUR_IN_SECONDS * 1000; let durationLabel = '00:00';