@@ -17,6 +17,7 @@ import { IconImage, IconCamera, IconAttach, IconVideo } from '../common/Icons';
17
17
import { androidEnsureStoragePermission } from '../lightbox/download' ;
18
18
import { ThemeContext } from '../styles/theme' ;
19
19
import type { SpecificIconType } from '../common/Icons' ;
20
+ import { androidSdkVersion } from '../reactNativeUtils' ;
20
21
21
22
export type Attachment = { |
22
23
+ name : string | null ,
@@ -72,6 +73,21 @@ export const chooseUploadImageFilename = (uri: string, fileName: string): string
72
73
return nameWithoutPrefix ;
73
74
} ;
74
75
76
+ // From the doc:
77
+ // https://github.com/react-native-image-picker/react-native-image-picker/tree/v4.10.2#options
78
+ // > Only iOS version >= 14 & Android version >= 13 support [multi-select]
79
+ //
80
+ // Older versions of react-native-image-picker claim to support multi-select
81
+ // on older Android versions; we tried that and it gave a bad experience,
82
+ // like a generic file picker that wasn't dedicated to handling images well:
83
+ // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Android.20select.20multiple.20photos/near/1423109
84
+ //
85
+ // But multi-select on iOS and on Android 13+ seem to work well.
86
+ const kShouldOfferImageMultiselect =
87
+ Platform . OS === 'ios'
88
+ // Android 13
89
+ || androidSdkVersion ( ) >= 33 ;
90
+
75
91
type MenuButtonProps = $ReadOnly < { |
76
92
onPress : ( ) => void | Promise < void > ,
77
93
IconComponent : SpecificIconType ,
@@ -142,13 +158,13 @@ export default function ComposeMenu(props: Props): Node {
142
158
return ;
143
159
}
144
160
161
+ // This will have length one for single-select payloads, or more than
162
+ // one for multi-select. So we'll treat `assets` uniformly: expect it
163
+ // to have length >= 1, and loop over it, even if that means just one
164
+ // iteration.
145
165
const { assets } = response ;
146
166
147
- // TODO: support sending multiple files; see library's docs for how to
148
- // let `assets` have more than one item in `response`.
149
- const firstAsset = assets && assets [ 0 ] ;
150
-
151
- if ( ! firstAsset ) {
167
+ if ( ! assets || ! assets [ 0 ] ) {
152
168
// TODO: See if we these unexpected situations actually happen. …Ah,
153
169
// yep, reportedly (and we've seen in Sentry):
154
170
// https://github.com/react-native-image-picker/react-native-image-picker/issues/1945
@@ -159,22 +175,39 @@ export default function ComposeMenu(props: Props): Node {
159
175
return ;
160
176
}
161
177
162
- const { uri, fileName } = firstAsset ;
178
+ const attachments = [ ] ;
179
+ let numMalformed = 0 ;
180
+ assets . forEach ( ( asset , i ) => {
181
+ const { uri, fileName } = asset ;
163
182
164
- if ( uri == null || fileName == null ) {
165
- // TODO: See if these unexpected situations actually happen.
166
- showErrorAlert ( _ ( 'Error' ) , _ ( 'Failed to attach your file.' ) ) ;
167
- logging . error (
168
- 'First (should be only) asset returned from image picker had nullish `url` and/or `fileName`' ,
169
- {
183
+ if ( uri == null || fileName == null ) {
184
+ // TODO: See if these unexpected situations actually happen.
185
+ logging . error ( 'An asset returned from image picker had nullish `url` and/or `fileName`' , {
170
186
'uri == null' : uri == null ,
171
187
'fileName == null' : fileName == null ,
172
- } ,
173
- ) ;
174
- return ;
188
+ i,
189
+ } ) ;
190
+ numMalformed ++ ;
191
+ return ;
192
+ }
193
+
194
+ attachments . push ( { name : chooseUploadImageFilename ( uri , fileName ) , url : uri } ) ;
195
+ } ) ;
196
+
197
+ if ( numMalformed > 0 ) {
198
+ if ( assets . length === 1 && numMalformed === 1 ) {
199
+ showErrorAlert ( _ ( 'Error' ) , _ ( 'Failed to attach your file.' ) ) ;
200
+ return ;
201
+ } else if ( assets . length === numMalformed ) {
202
+ showErrorAlert ( _ ( 'Error' ) , _ ( 'Failed to attach your files.' ) ) ;
203
+ return ;
204
+ } else {
205
+ showErrorAlert ( _ ( 'Error' ) , _ ( 'Failed to attach some of your files.' ) ) ;
206
+ // no return; `attachments` will have some items that we can insert
207
+ }
175
208
}
176
209
177
- insertAttachments ( [ { name : chooseUploadImageFilename ( uri , fileName ) , url : uri } ] ) ;
210
+ insertAttachments ( attachments ) ;
178
211
} ,
179
212
[ _ , insertAttachments ] ,
180
213
) ;
@@ -187,6 +220,14 @@ export default function ComposeMenu(props: Props): Node {
187
220
188
221
quality : 1.0 ,
189
222
includeBase64 : false ,
223
+
224
+ // From the doc: "[U]se `0` to allow any number of files"
225
+ // https://github.com/react-native-image-picker/react-native-image-picker/tree/v4.10.2#options
226
+ //
227
+ // Between single- and multi-select, we expect the payload passed to
228
+ // handleImagePickerResponse to differ only in the length of the
229
+ // `assets` array (one item vs. multiple).
230
+ selectionLimit : kShouldOfferImageMultiselect ? 0 : 1 ,
190
231
} ,
191
232
handleImagePickerResponse ,
192
233
) ;
0 commit comments