[🐴] Handle errors, improve styling (#3937)

* Handle errors, improve styling

* Remove old UI
zio/stable
Eric Bailey 2024-05-10 08:23:37 -05:00 committed by GitHub
parent 1821a992ab
commit 195c9f1045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 96 additions and 78 deletions

View File

@ -5,8 +5,9 @@ import {useLingui} from '@lingui/react'
import {ConvoItem, ConvoItemError} from '#/state/messages/convo/types' import {ConvoItem, ConvoItemError} from '#/state/messages/convo/types'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Refresh} from '#/components/icons/ArrowRotateCounterClockwise'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export function MessageListError({ export function MessageListError({
@ -21,39 +22,52 @@ export function MessageListError({
[ConvoItemError.Network]: _( [ConvoItemError.Network]: _(
msg`There was an issue connecting to the chat.`, msg`There was an issue connecting to the chat.`,
), ),
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`), [ConvoItemError.FirehoseFailed]: _(
[ConvoItemError.PollFailed]: _(
msg`This chat was disconnected due to a network error.`, msg`This chat was disconnected due to a network error.`,
), ),
[ConvoItemError.HistoryFailed]: _(msg`Failed to load past messages.`),
[ConvoItemError.PendingFailed]: _(msg`Failed to send message(s).`),
}[item.code] }[item.code]
}, [_, item.code]) }, [_, item.code])
return ( return (
<View style={[a.py_md, a.align_center]}> <View style={[a.py_lg, a.align_center]}>
<View <View
style={[ style={[
a.flex_row,
a.align_center, a.align_center,
a.pt_md, a.justify_between,
a.pb_lg, a.gap_lg,
a.px_3xl, a.py_md,
a.px_lg,
a.rounded_md, a.rounded_md,
t.atoms.bg_contrast_25, t.atoms.bg_contrast_25,
{maxWidth: 300}, {maxWidth: 400},
]}> ]}>
<CircleInfo size="lg" fill={t.palette.negative_400} /> <View style={[a.flex_row, a.align_start, a.justify_between, a.gap_sm]}>
<Text style={[a.pt_sm, a.leading_snug]}> <CircleInfo
{message}{' '} size="sm"
<InlineLinkText fill={t.palette.negative_400}
to="#" style={[{top: 3}]}
label={_(msg`Press to retry`)} />
onPress={e => { <View style={[a.flex_1, {maxWidth: 200}]}>
e.preventDefault() <Text style={[a.leading_snug]}>{message}</Text>
item.retry() </View>
return false </View>
}}>
{_(msg`Retry.`)} <Button
</InlineLinkText> label={_(msg`Press to retry`)}
</Text> size="small"
variant="ghost"
color="secondary"
onPress={e => {
e.preventDefault()
item.retry()
return false
}}>
<ButtonText>{_(msg`Retry`)}</ButtonText>
<ButtonIcon icon={Refresh} position="right" />
</Button>
</View> </View>
</View> </View>
) )

View File

@ -8,8 +8,6 @@ import {runOnJS, useSharedValue} from 'react-native-reanimated'
import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes' import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {AppBskyRichtextFacet, RichText} from '@atproto/api' import {AppBskyRichtextFacet, RichText} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {shortenLinks} from '#/lib/strings/rich-text-manip' import {shortenLinks} from '#/lib/strings/rich-text-manip'
import {isIOS} from '#/platform/detection' import {isIOS} from '#/platform/detection'
@ -22,7 +20,6 @@ import {List} from 'view/com/util/List'
import {MessageInput} from '#/screens/Messages/Conversation/MessageInput' import {MessageInput} from '#/screens/Messages/Conversation/MessageInput'
import {MessageListError} from '#/screens/Messages/Conversation/MessageListError' import {MessageListError} from '#/screens/Messages/Conversation/MessageListError'
import {atoms as a, useBreakpoints} from '#/alf' import {atoms as a, useBreakpoints} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {MessageItem} from '#/components/dms/MessageItem' import {MessageItem} from '#/components/dms/MessageItem'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@ -41,25 +38,6 @@ function MaybeLoader({isLoading}: {isLoading: boolean}) {
) )
} }
function RetryButton({onPress}: {onPress: () => unknown}) {
const {_} = useLingui()
return (
<View style={{alignItems: 'center'}}>
<Button
label={_(msg`Press to Retry`)}
onPress={onPress}
variant="ghost"
color="negative"
size="small">
<ButtonText>
<Trans>Press to Retry</Trans>
</ButtonText>
</Button>
</View>
)
}
function renderItem({item}: {item: ConvoItem}) { function renderItem({item}: {item: ConvoItem}) {
if (item.type === 'message' || item.type === 'pending-message') { if (item.type === 'message' || item.type === 'pending-message') {
return ( return (
@ -71,8 +49,6 @@ function renderItem({item}: {item: ConvoItem}) {
) )
} else if (item.type === 'deleted-message') { } else if (item.type === 'deleted-message') {
return <Text>Deleted message</Text> return <Text>Deleted message</Text>
} else if (item.type === 'pending-retry') {
return <RetryButton onPress={item.retry} />
} else if (item.type === 'error-recoverable') { } else if (item.type === 'error-recoverable') {
return <MessageListError item={item} /> return <MessageListError item={item} />
} }

View File

@ -401,7 +401,7 @@ export class Convo {
// throw new Error('UNCOMMENT TO TEST INIT FAILURE') // throw new Error('UNCOMMENT TO TEST INIT FAILURE')
this.dispatch({event: ConvoDispatchEvent.Ready}) this.dispatch({event: ConvoDispatchEvent.Ready})
} catch (e: any) { } catch (e: any) {
logger.error('Convo: setup() failed') logger.error(e, {context: 'Convo: setup failed'})
this.dispatch({ this.dispatch({
event: ConvoDispatchEvent.Error, event: ConvoDispatchEvent.Error,
@ -413,6 +413,7 @@ export class Convo {
}, },
}, },
}) })
this.commit()
} }
} }
@ -500,7 +501,7 @@ export class Convo {
this.sender = sender || this.sender this.sender = sender || this.sender
this.recipients = recipients || this.recipients this.recipients = recipients || this.recipients
} catch (e: any) { } catch (e: any) {
logger.error(`Convo: failed to refresh convo`) logger.error(e, {context: `Convo: failed to refresh convo`})
this.footerItems.set(ConvoItemError.Network, { this.footerItems.set(ConvoItemError.Network, {
type: 'error-recoverable', type: 'error-recoverable',
@ -601,17 +602,17 @@ export class Convo {
} }
onFirehoseConnect() { onFirehoseConnect() {
this.footerItems.delete(ConvoItemError.PollFailed) this.footerItems.delete(ConvoItemError.FirehoseFailed)
this.commit() this.commit()
} }
onFirehoseError(error?: MessagesEventBusError) { onFirehoseError(error?: MessagesEventBusError) {
this.footerItems.set(ConvoItemError.PollFailed, { this.footerItems.set(ConvoItemError.FirehoseFailed, {
type: 'error-recoverable', type: 'error-recoverable',
key: ConvoItemError.PollFailed, key: ConvoItemError.FirehoseFailed,
code: ConvoItemError.PollFailed, code: ConvoItemError.FirehoseFailed,
retry: () => { retry: () => {
this.footerItems.delete(ConvoItemError.PollFailed) this.footerItems.delete(ConvoItemError.FirehoseFailed)
this.commit() this.commit()
error?.retry() error?.retry()
}, },
@ -772,13 +773,21 @@ export class Convo {
await this.processPendingMessages() await this.processPendingMessages()
this.commit() this.commit()
} catch (e) { } catch (e: any) {
this.footerItems.set('pending-retry', { logger.error(e, {context: `Convo: failed to send message`})
type: 'pending-retry', this.footerItems.set(ConvoItemError.PendingFailed, {
key: 'pending-retry', type: 'error-recoverable',
retry: this.batchRetryPendingMessages.bind(this), key: ConvoItemError.PendingFailed,
code: ConvoItemError.PendingFailed,
retry: () => {
this.footerItems.delete(ConvoItemError.PendingFailed)
this.commit()
this.batchRetryPendingMessages()
},
}) })
this.commit() this.commit()
} finally {
this.isProcessingPendingMessages = false
} }
} }
@ -789,10 +798,8 @@ export class Convo {
logger.DebugContext.convo, logger.DebugContext.convo,
) )
this.footerItems.delete('pending-retry')
this.commit()
try { try {
// throw new Error('UNCOMMENT TO TEST RETRY')
const messageArray = Array.from(this.pendingMessages.values()) const messageArray = Array.from(this.pendingMessages.values())
const {data} = await networkRetry(2, () => { const {data} = await networkRetry(2, () => {
return this.agent.api.chat.bsky.convo.sendMessageBatch( return this.agent.api.chat.bsky.convo.sendMessageBatch(
@ -831,11 +838,23 @@ export class Convo {
} }
this.commit() this.commit()
} catch (e) {
this.footerItems.set('pending-retry', { logger.debug(
type: 'pending-retry', `Convo: sent ${this.pendingMessages.size} pending messages`,
key: 'pending-retry', {},
retry: this.batchRetryPendingMessages.bind(this), logger.DebugContext.convo,
)
} catch (e: any) {
logger.error(e, {context: `Convo: failed to batch retry messages`})
this.footerItems.set(ConvoItemError.PendingFailed, {
type: 'error-recoverable',
key: ConvoItemError.PendingFailed,
code: ConvoItemError.PendingFailed,
retry: () => {
this.footerItems.delete(ConvoItemError.PendingFailed)
this.commit()
this.batchRetryPendingMessages()
},
}) })
this.commit() this.commit()
} }
@ -862,7 +881,8 @@ export class Convo {
}, },
) )
}) })
} catch (e) { } catch (e: any) {
logger.error(e, {context: `Convo: failed to delete message`})
this.deletedMessages.delete(messageId) this.deletedMessages.delete(messageId)
this.commit() this.commit()
throw e throw e
@ -875,10 +895,6 @@ export class Convo {
getItems(): ConvoItem[] { getItems(): ConvoItem[] {
const items: ConvoItem[] = [] const items: ConvoItem[] = []
this.headerItems.forEach(item => {
items.push(item)
})
this.pastMessages.forEach(m => { this.pastMessages.forEach(m => {
if (ChatBskyConvoDefs.isMessageView(m)) { if (ChatBskyConvoDefs.isMessageView(m)) {
items.unshift({ items.unshift({
@ -897,6 +913,10 @@ export class Convo {
} }
}) })
this.headerItems.forEach(item => {
items.unshift(item)
})
this.newMessages.forEach(m => { this.newMessages.forEach(m => {
if (ChatBskyConvoDefs.isMessageView(m)) { if (ChatBskyConvoDefs.isMessageView(m)) {
items.push({ items.push({

View File

@ -24,9 +24,22 @@ export enum ConvoStatus {
} }
export enum ConvoItemError { export enum ConvoItemError {
HistoryFailed = 'historyFailed', /**
PollFailed = 'pollFailed', * Generic error
*/
Network = 'network', Network = 'network',
/**
* Error connecting to event firehose
*/
FirehoseFailed = 'firehoseFailed',
/**
* Error fetching past messages
*/
HistoryFailed = 'historyFailed',
/**
* Error sending new message
*/
PendingFailed = 'pendingFailed',
} }
export enum ConvoErrorCode { export enum ConvoErrorCode {
@ -88,11 +101,6 @@ export type ConvoItem =
| ChatBskyConvoDefs.DeletedMessageView | ChatBskyConvoDefs.DeletedMessageView
| null | null
} }
| {
type: 'pending-retry'
key: string
retry: () => void
}
| { | {
type: 'error-recoverable' type: 'error-recoverable'
key: string key: string