Improve error messages

zio/stable
Paul Frazee 2022-11-15 10:46:12 -06:00
parent 6e93301542
commit fb3a43c216
15 changed files with 156 additions and 32 deletions

View File

@ -3,6 +3,7 @@ import * as GetTimeline from '../../third-party/api/src/client/types/app/bsky/fe
import * as GetAuthorFeed from '../../third-party/api/src/client/types/app/bsky/feed/getAuthorFeed'
import {RootStoreModel} from './root-store'
import * as apilib from '../lib/api'
import {cleanError} from '../../view/lib/strings'
export class FeedItemMyStateModel {
repost?: string
@ -254,7 +255,7 @@ export class FeedModel {
this.isLoading = false
this.isRefreshing = false
this.hasLoaded = true
this.error = err
this.error = cleanError(err)
}
// loader functions
@ -282,7 +283,7 @@ export class FeedModel {
this._replaceAll(res)
this._xIdle()
} catch (e: any) {
this._xIdle(`Failed to load feed: ${e.toString()}`)
this._xIdle(e.toString())
}
}
@ -293,7 +294,7 @@ export class FeedModel {
this._prependAll(res)
this._xIdle()
} catch (e: any) {
this._xIdle(`Failed to load feed: ${e.toString()}`)
this._xIdle(e.toString())
}
}

View File

@ -4,6 +4,7 @@ import {RootStoreModel} from './root-store'
import {Declaration} from './_common'
import {hasProp} from '../lib/type-guards'
import {APP_BSKY_GRAPH} from '../../third-party/api'
import {cleanError} from '../../view/lib/strings'
const UNGROUPABLE_REASONS = ['trend', 'assertion']
@ -215,7 +216,7 @@ export class NotificationsViewModel {
this.isLoading = false
this.isRefreshing = false
this.hasLoaded = true
this.error = err
this.error = cleanError(err)
}
// loader functions

View File

@ -6,11 +6,14 @@ import {
NotificationsViewItemModel,
} from '../../../state/models/notifications-view'
import {FeedItem} from './FeedItem'
import {ErrorMessage} from '../util/ErrorMessage'
export const Feed = observer(function Feed({
view,
onPressTryAgain,
}: {
view: NotificationsViewModel
onPressTryAgain?: () => void
}) {
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
// VirtualizedList: You have a large list that is slow to update - make sure your
@ -30,7 +33,14 @@ export const Feed = observer(function Feed({
{view.isLoading && !view.isRefreshing && !view.hasContent && (
<Text>Loading...</Text>
)}
{view.hasError && <Text>{view.error}</Text>}
{view.hasError && (
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onPressTryAgain}
/>
)}
{view.hasContent && (
<FlatList
data={view.notifications}

View File

@ -1,18 +1,12 @@
import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import {
ActivityIndicator,
FlatList,
Image,
StyleSheet,
Text,
View,
} from 'react-native'
import {ActivityIndicator, FlatList, StyleSheet, Text, View} from 'react-native'
import {
RepostedByViewModel,
RepostedByViewItemModel,
} from '../../../state/models/reposted-by-view'
import {UserAvatar} from '../util/UserAvatar'
import {ErrorMessage} from '../util/ErrorMessage'
import {Link} from '../util/Link'
import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles'
@ -38,6 +32,10 @@ export const PostRepostedBy = observer(function PostRepostedBy({
.catch(err => console.error('Failed to fetch reposted by', err))
}, [uri, view?.params.uri, store])
const onRefresh = () => {
view?.refresh()
}
// loading
// =
if (
@ -57,7 +55,12 @@ export const PostRepostedBy = observer(function PostRepostedBy({
if (view.hasError) {
return (
<View>
<Text>{view.error}</Text>
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onRefresh}
/>
</View>
)
}

View File

@ -8,6 +8,7 @@ import {
import {useStores} from '../../../state'
import {SharePostModel} from '../../../state/models/shell-ui'
import {PostThreadItem} from './PostThreadItem'
import {ErrorMessage} from '../util/ErrorMessage'
export const PostThread = observer(function PostThread({uri}: {uri: string}) {
const store = useStores()
@ -50,7 +51,12 @@ export const PostThread = observer(function PostThread({uri}: {uri: string}) {
if (view.hasError) {
return (
<View>
<Text>{view.error}</Text>
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onRefresh}
/>
</View>
)
}

View File

@ -6,6 +6,7 @@ import {
VotesViewItemModel,
} from '../../../state/models/votes-view'
import {Link} from '../util/Link'
import {ErrorMessage} from '../util/ErrorMessage'
import {UserAvatar} from '../util/UserAvatar'
import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles'
@ -31,6 +32,10 @@ export const PostVotedBy = observer(function PostVotedBy({
newView.setup().catch(err => console.error('Failed to fetch voted by', err))
}, [uri, view?.params.uri, store])
const onRefresh = () => {
view?.refresh()
}
// loading
// =
if (
@ -50,7 +55,12 @@ export const PostVotedBy = observer(function PostVotedBy({
if (view.hasError) {
return (
<View>
<Text>{view.error}</Text>
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onRefresh}
/>
</View>
)
}

View File

@ -1,6 +1,7 @@
import React, {MutableRefObject} from 'react'
import {observer} from 'mobx-react-lite'
import {Text, View, FlatList, StyleProp, ViewStyle} from 'react-native'
import {ErrorMessage} from '../util/ErrorMessage'
import {FeedModel, FeedItemModel} from '../../../state/models/feed-view'
import {FeedItem} from './FeedItem'
@ -8,10 +9,12 @@ export const Feed = observer(function Feed({
feed,
style,
scrollElRef,
onPressTryAgain,
}: {
feed: FeedModel
style?: StyleProp<ViewStyle>
scrollElRef?: MutableRefObject<FlatList<any> | null>
onPressTryAgain?: () => void
}) {
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
// VirtualizedList: You have a large list that is slow to update - make sure your
@ -29,7 +32,14 @@ export const Feed = observer(function Feed({
{feed.isLoading && !feed.isRefreshing && !feed.hasContent && (
<Text>Loading...</Text>
)}
{feed.hasError && <Text>{feed.error}</Text>}
{feed.hasError && (
<ErrorMessage
dark
message={feed.error}
style={{margin: 6}}
onPressTryAgain={onPressTryAgain}
/>
)}
{feed.hasContent && (
<FlatList
ref={scrollElRef}
@ -41,7 +51,11 @@ export const Feed = observer(function Feed({
onEndReached={onEndReached}
/>
)}
{feed.isEmpty && <Text>This feed is empty!</Text>}
{feed.isEmpty && !feed.hasError && (
<View>
<Text>This feed is empty!</Text>
</View>
)}
</View>
)
})

View File

@ -6,6 +6,7 @@ import {
FollowerItem,
} from '../../../state/models/user-followers-view'
import {Link} from '../util/Link'
import {ErrorMessage} from '../util/ErrorMessage'
import {UserAvatar} from '../util/UserAvatar'
import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles'
@ -31,6 +32,10 @@ export const ProfileFollowers = observer(function ProfileFollowers({
.catch(err => console.error('Failed to fetch user followers', err))
}, [name, view?.params.user, store])
const onRefresh = () => {
view?.refresh()
}
// loading
// =
if (
@ -50,7 +55,12 @@ export const ProfileFollowers = observer(function ProfileFollowers({
if (view.hasError) {
return (
<View>
<Text>{view.error}</Text>
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onRefresh}
/>
</View>
)
}

View File

@ -7,6 +7,7 @@ import {
} from '../../../state/models/user-follows-view'
import {useStores} from '../../../state'
import {Link} from '../util/Link'
import {ErrorMessage} from '../util/ErrorMessage'
import {UserAvatar} from '../util/UserAvatar'
import {s, colors} from '../../lib/styles'
@ -31,6 +32,10 @@ export const ProfileFollows = observer(function ProfileFollows({
.catch(err => console.error('Failed to fetch user follows', err))
}, [name, view?.params.user, store])
const onRefresh = () => {
view?.refresh()
}
// loading
// =
if (
@ -50,7 +55,12 @@ export const ProfileFollows = observer(function ProfileFollows({
if (view.hasError) {
return (
<View>
<Text>{view.error}</Text>
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onRefresh}
/>
</View>
)
}

View File

@ -1,8 +1,9 @@
import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import {ActivityIndicator, FlatList, Text, View} from 'react-native'
import {ActivityIndicator, FlatList, View} from 'react-native'
import {MembersViewModel, MemberItem} from '../../../state/models/members-view'
import {ProfileCard} from './ProfileCard'
import {ErrorMessage} from '../util/ErrorMessage'
import {useStores} from '../../../state'
export const ProfileMembers = observer(function ProfileMembers({
@ -24,6 +25,10 @@ export const ProfileMembers = observer(function ProfileMembers({
newView.setup().catch(err => console.error('Failed to fetch members', err))
}, [name, view?.params.actor, store])
const onRefresh = () => {
view?.refresh()
}
// loading
// =
if (
@ -43,7 +48,12 @@ export const ProfileMembers = observer(function ProfileMembers({
if (view.hasError) {
return (
<View>
<Text>{view.error}</Text>
<ErrorMessage
dark
message={view.error}
style={{margin: 6}}
onPressTryAgain={onRefresh}
/>
</View>
)
}

View File

@ -1,40 +1,66 @@
import React from 'react'
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {
StyleSheet,
Text,
TouchableOpacity,
StyleProp,
View,
ViewStyle,
} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {colors} from '../../lib/styles'
import LinearGradient from 'react-native-linear-gradient'
import {colors, gradients} from '../../lib/styles'
export function ErrorMessage({
message,
numberOfLines,
dark,
style,
onPressTryAgain,
}: {
message: string
numberOfLines?: number
dark?: boolean
style?: StyleProp<ViewStyle>
onPressTryAgain?: () => void
}) {
return (
<View style={styles.outer}>
<View style={styles.errorIcon}>
const inner = (
<>
<View style={[styles.errorIcon, dark ? styles.darkErrorIcon : undefined]}>
<FontAwesomeIcon
icon="exclamation"
style={{color: colors.white}}
style={{color: dark ? colors.red3 : colors.white}}
size={16}
/>
</View>
<Text style={styles.message} numberOfLines={numberOfLines}>
<Text
style={[styles.message, dark ? styles.darkMessage : undefined]}
numberOfLines={numberOfLines}>
{message}
</Text>
{onPressTryAgain && (
<TouchableOpacity style={styles.btn} onPress={onPressTryAgain}>
<FontAwesomeIcon
icon="arrows-rotate"
style={{color: colors.red4}}
style={{color: dark ? colors.white : colors.red4}}
size={16}
/>
</TouchableOpacity>
)}
</View>
</>
)
if (dark) {
return (
<LinearGradient
colors={[gradients.error.start, gradients.error.end]}
start={{x: 0.5, y: 0}}
end={{x: 1, y: 1}}
style={[styles.outer, style]}>
{inner}
</LinearGradient>
)
}
return <View style={[styles.outer, style]}>{inner}</View>
}
const styles = StyleSheet.create({
@ -57,11 +83,18 @@ const styles = StyleSheet.create({
justifyContent: 'center',
marginRight: 8,
},
darkErrorIcon: {
backgroundColor: colors.white,
},
message: {
flex: 1,
color: colors.red4,
paddingRight: 10,
},
darkMessage: {
color: colors.white,
fontWeight: '600',
},
btn: {
paddingHorizontal: 4,
paddingVertical: 4,

View File

@ -96,3 +96,10 @@ export function enforceLen(str: string, len: number): string {
}
return str
}
export function cleanError(str: string): string {
if (str.startsWith('Error: ')) {
return str.slice('Error: '.length)
}
return str
}

View File

@ -45,6 +45,7 @@ export const colors = {
export const gradients = {
primary: {start: '#db00ff', end: '#ff007a'},
error: {start: '#ff007a', end: '#ed0d78'},
purple: {start: colors.pink3, end: colors.purple3},
blue: {start: colors.purple3, end: colors.blue3},
green: {start: colors.blue3, end: colors.green3},

View File

@ -51,6 +51,9 @@ export const Home = observer(function Home({
const onCreatePost = () => {
defaultFeedView.loadLatest()
}
const onPressTryAgain = () => {
defaultFeedView.refresh()
}
return (
<View style={s.flex1}>
@ -63,6 +66,7 @@ export const Home = observer(function Home({
feed={defaultFeedView}
scrollElRef={scrollElRef}
style={{flex: 1}}
onPressTryAgain={onPressTryAgain}
/>
<FAB icon="pen-nib" onPress={onComposePress} />
</View>

View File

@ -36,10 +36,14 @@ export const Notifications = ({visible}: ScreenParams) => {
}
}, [visible, store])
const onPressTryAgain = () => {
notesView?.refresh()
}
return (
<View style={{flex: 1}}>
<ViewHeader title="Notifications" />
{notesView && <Feed view={notesView} />}
{notesView && <Feed view={notesView} onPressTryAgain={onPressTryAgain} />}
</View>
)
}