···11-import React from 'react'
11+import React, {useState} from 'react'
22import {View} from 'react-native'
33import {type ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api'
44+import {XRPCError} from '@atproto/xrpc'
45import {msg, Trans} from '@lingui/macro'
56import {useLingui} from '@lingui/react'
67import {useMutation} from '@tanstack/react-query'
···1920import * as Dialog from '#/components/Dialog'
2021import {InlineLinkText} from '#/components/Link'
2122import {Text} from '#/components/Typography'
2323+import {Admonition} from '../Admonition'
2224import {Divider} from '../Divider'
2325import {Loader} from '../Loader'
2426···228230 const sourceName = labeler
229231 ? sanitizeHandle(labeler.creator.handle, '@')
230232 : label.src
233233+ const [error, setError] = useState<string | null>(null)
231234232235 const {mutate, isPending} = useMutation({
233236 mutationFn: async () => {
···252255 )
253256 },
254257 onError: err => {
258258+ if (err instanceof XRPCError && err.error === 'AlreadyAppealed') {
259259+ setError(
260260+ _(
261261+ msg`You've already appealed this label and it's being reviewed by our moderation team.`,
262262+ ),
263263+ )
264264+ } else {
265265+ setError(_(msg`Failed to submit appeal, please try again.`))
266266+ }
255267 logger.error('Failed to submit label appeal', {message: err})
256256- Toast.show(_(msg`Failed to submit appeal, please try again.`), 'xmark')
257268 },
258269 onSuccess: () => {
259270 control.close()
···285296 </Trans>
286297 </Text>
287298 </View>
299299+ {error && (
300300+ <Admonition type="error" style={[a.mt_sm]}>
301301+ {error}
302302+ </Admonition>
303303+ )}
288304 <View style={[a.my_md]}>
289305 <Dialog.Input
290306 label={_(msg`Text input field`)}
+34-2
src/lib/media/manip.ts
···115115 // as the starting image, or put it directly into the album
116116 const album = await MediaLibrary.getAlbumAsync(ALBUM_NAME)
117117 if (album) {
118118- // if album exists, put the image straight in there
119119- await MediaLibrary.createAssetAsync(imagePath, album)
118118+ // try and migrate if needed
119119+ try {
120120+ if (await MediaLibrary.albumNeedsMigrationAsync(album)) {
121121+ await MediaLibrary.migrateAlbumIfNeededAsync(album)
122122+ }
123123+ } catch (err) {
124124+ logger.info('Attempted and failed to migrate album', {
125125+ safeMessage: err,
126126+ })
127127+ }
128128+129129+ try {
130130+ // if album exists, put the image straight in there
131131+ await MediaLibrary.createAssetAsync(imagePath, album)
132132+ } catch (err) {
133133+ logger.info('Failed to create asset', {safeMessage: err})
134134+ // however, it's possible that we don't have write permission to the album
135135+ // try making a new one!
136136+ try {
137137+ await MediaLibrary.createAlbumAsync(
138138+ ALBUM_NAME,
139139+ undefined,
140140+ undefined,
141141+ imagePath,
142142+ )
143143+ } catch (err2) {
144144+ logger.info('Failed to create asset in a fresh album', {
145145+ safeMessage: err2,
146146+ })
147147+ // ... and if all else fails, just put it in DCIM
148148+ await MediaLibrary.createAssetAsync(imagePath)
149149+ }
150150+ }
120151 } else {
121152 // otherwise, create album with asset (albums must always have at least one asset)
122153 await MediaLibrary.createAlbumAsync(
···133164 logger.error(err instanceof Error ? err : String(err), {
134165 message: 'Failed to save image to media library',
135166 })
167167+ throw err
136168 } finally {
137169 safeDeleteAsync(imagePath)
138170 }
···4646import {atoms as a, useTheme} from '#/alf'
4747import {colors} from '#/components/Admonition'
4848import {Button} from '#/components/Button'
4949+import {DebugFieldDisplay} from '#/components/DebugFieldDisplay'
4950import {CalendarClock_Stroke2_Corner0_Rounded as CalendarClockIcon} from '#/components/icons/CalendarClock'
5051import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
5152import {InlineLinkText, Link} from '#/components/Link'
···529530 />
530531 </FeedFeedbackProvider>
531532 </View>
533533+ <DebugFieldDisplay subject={post} />
532534 </View>
533535 </View>
534536 </>