[🐴] Handle errors, improve styling (#3937)
* Handle errors, improve styling * Remove old UIzio/stable
parent
1821a992ab
commit
195c9f1045
|
@ -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}]}
|
||||||
|
/>
|
||||||
|
<View style={[a.flex_1, {maxWidth: 200}]}>
|
||||||
|
<Text style={[a.leading_snug]}>{message}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button
|
||||||
label={_(msg`Press to retry`)}
|
label={_(msg`Press to retry`)}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
onPress={e => {
|
onPress={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
item.retry()
|
item.retry()
|
||||||
return false
|
return false
|
||||||
}}>
|
}}>
|
||||||
{_(msg`Retry.`)}
|
<ButtonText>{_(msg`Retry`)}</ButtonText>
|
||||||
</InlineLinkText>
|
<ButtonIcon icon={Refresh} position="right" />
|
||||||
</Text>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue