@@ -16,6 +16,7 @@ import {
1616
1717import { Ionicons } from '@expo/vector-icons' ;
1818import { useQueryClient } from '@tanstack/react-query' ;
19+ import * as FileSystem from 'expo-file-system/legacy' ;
1920import * as ImagePicker from 'expo-image-picker' ;
2021import { replaceTriggerValues } from 'react-native-controlled-mentions' ;
2122import { SafeAreaView , useSafeAreaInsets } from 'react-native-safe-area-context' ;
@@ -67,6 +68,26 @@ type TweetComposerProps = {
6768const CHARACTER_LIMIT = 280 ;
6869const MAX_CHARACTER_LIMIT = Math . ceil ( CHARACTER_LIMIT / 100 ) * 100 ;
6970
71+ const MAX_IMAGE_SIZE = 5 * 1024 * 1024 ;
72+ const MAX_VIDEO_SIZE = 10 * 1024 * 1024 ;
73+
74+ const checkFileSize = async (
75+ uri : string ,
76+ isVideo : boolean
77+ ) : Promise < { valid : boolean ; sizeMB ?: number } > => {
78+ try {
79+ const info = await FileSystem . getInfoAsync ( uri ) ;
80+ if ( ! info . exists || info . size === undefined || info . size === null ) {
81+ return { valid : true } ;
82+ }
83+ const maxSize = isVideo ? MAX_VIDEO_SIZE : MAX_IMAGE_SIZE ;
84+ const sizeMB = info . size / ( 1024 * 1024 ) ;
85+ return { valid : info . size <= maxSize , sizeMB } ;
86+ } catch {
87+ return { valid : true } ;
88+ }
89+ } ;
90+
7091export default function TweetComposer ( {
7192 visible,
7293 onClose,
@@ -138,28 +159,52 @@ export default function TweetComposer({
138159 } ) ;
139160
140161 if ( ! result . canceled ) {
141- const newAssets : Asset [ ] = result . assets . map ( ( asset ) => ( {
142- id : asset . fileName || asset . uri ,
143- uri : asset . uri ,
144- fileName : asset . fileName ?? undefined ,
145- mediaType : asset . duration ? 'video' : 'photo' ,
146- duration : asset . duration ? asset . duration / 1000 : undefined ,
147- } ) ) ;
162+ const oversizedFiles : string [ ] = [ ] ;
163+ const validAssets : Asset [ ] = [ ] ;
164+
165+ for ( const asset of result . assets ) {
166+ const isVideo = ! ! asset . duration ;
167+ const sizeCheck = await checkFileSize ( asset . uri , isVideo ) ;
168+
169+ if ( ! sizeCheck . valid ) {
170+ const maxMB = isVideo ? 10 : 5 ;
171+ oversizedFiles . push (
172+ `${ asset . fileName || 'File' } (${ sizeCheck . sizeMB ?. toFixed ( 1 ) } MB > ${ maxMB } MB)`
173+ ) ;
174+ } else {
175+ validAssets . push ( {
176+ id : asset . fileName || asset . uri ,
177+ uri : asset . uri ,
178+ fileName : asset . fileName ?? undefined ,
179+ mediaType : isVideo ? 'video' : 'photo' ,
180+ duration : asset . duration ? asset . duration / 1000 : undefined ,
181+ } ) ;
182+ }
183+ }
184+
185+ if ( oversizedFiles . length > 0 ) {
186+ Alert . alert (
187+ 'File Too Large' ,
188+ `The following files exceed size limits:\n${ oversizedFiles . join ( '\n' ) } `
189+ ) ;
190+ }
148191
149192 const availableSlots = 4 - selectedAssets . length ;
150- const assetsToAdd = newAssets
193+ const assetsToAdd = validAssets
151194 . filter (
152195 ( asset ) =>
153196 ! selectedAssets . some ( ( a ) => ( a . fileName || a . id ) === ( asset . fileName || asset . id ) )
154197 )
155198 . slice ( 0 , availableSlots ) ;
156199
157- setSelectedAssets ( [ ...assetsToAdd , ...selectedAssets ] ) ;
200+ if ( assetsToAdd . length > 0 ) {
201+ setSelectedAssets ( [ ...assetsToAdd , ...selectedAssets ] ) ;
158202
159- const newForAllPicked = assetsToAdd . filter (
160- ( asset ) => ! allPickedAssets . some ( ( a ) => a . fileName === asset . fileName )
161- ) ;
162- setAllPickedAssets ( [ ...allPickedAssets , ...newForAllPicked ] ) ;
203+ const newForAllPicked = assetsToAdd . filter (
204+ ( asset ) => ! allPickedAssets . some ( ( a ) => a . fileName === asset . fileName )
205+ ) ;
206+ setAllPickedAssets ( [ ...allPickedAssets , ...newForAllPicked ] ) ;
207+ }
163208 }
164209 } finally {
165210 setIsPickingMedia ( false ) ;
@@ -283,6 +328,16 @@ export default function TweetComposer({
283328
284329 if ( ! result . canceled ) {
285330 const asset = result . assets [ 0 ] ;
331+ const sizeCheck = await checkFileSize ( asset . uri , false ) ;
332+
333+ if ( ! sizeCheck . valid ) {
334+ Alert . alert (
335+ 'Image Too Large' ,
336+ `This image is ${ sizeCheck . sizeMB ?. toFixed ( 1 ) } MB. Maximum size is 5MB.`
337+ ) ;
338+ return ;
339+ }
340+
286341 const newAsset : Asset = {
287342 id : asset . uri ,
288343 uri : asset . uri ,
@@ -317,6 +372,16 @@ export default function TweetComposer({
317372
318373 if ( ! result . canceled ) {
319374 const asset = result . assets [ 0 ] ;
375+ const sizeCheck = await checkFileSize ( asset . uri , true ) ;
376+
377+ if ( ! sizeCheck . valid ) {
378+ Alert . alert (
379+ 'Video Too Large' ,
380+ `This video is ${ sizeCheck . sizeMB ?. toFixed ( 1 ) } MB. Maximum size is 10MB.`
381+ ) ;
382+ return ;
383+ }
384+
320385 const newAsset : Asset = {
321386 id : asset . uri ,
322387 uri : asset . uri ,
0 commit comments