Better loading screens

zio/stable
Paul Frazee 2022-11-15 12:07:41 -06:00
parent e470e3933b
commit 4ae6fbd3c8
4 changed files with 101 additions and 17 deletions

View File

@ -2,6 +2,7 @@ import {makeAutoObservable} from 'mobx'
import * as Post from '../../third-party/api/src/client/types/app/bsky/feed/post' import * as Post from '../../third-party/api/src/client/types/app/bsky/feed/post'
import {AtUri} from '../../third-party/uri' import {AtUri} from '../../third-party/uri'
import {RootStoreModel} from './root-store' import {RootStoreModel} from './root-store'
import {cleanError} from '../../view/lib/strings'
export type PostEntities = Post.Record['entities'] export type PostEntities = Post.Record['entities']
export type PostReply = Post.Record['reply'] export type PostReply = Post.Record['reply']
@ -67,7 +68,7 @@ export class PostModel implements RemoveIndex<Post.Record> {
private _xIdle(err: string = '') { private _xIdle(err: string = '') {
this.isLoading = false this.isLoading = false
this.hasLoaded = true this.hasLoaded = true
this.error = err this.error = cleanError(err)
} }
// loader functions // loader functions
@ -88,7 +89,7 @@ export class PostModel implements RemoveIndex<Post.Record> {
this._replaceAll(res.value) this._replaceAll(res.value)
this._xIdle() this._xIdle()
} catch (e: any) { } catch (e: any) {
this._xIdle(`Failed to load post: ${e.toString()}`) this._xIdle(e.toString())
} }
} }

View File

@ -1,6 +1,8 @@
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {ActivityIndicator, Text, View} from 'react-native' import {Text, View} from 'react-native'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/ErrorMessage'
import {PostModel} from '../../../state/models/post' import {PostModel} from '../../../state/models/post'
import {useStores} from '../../../state' import {useStores} from '../../../state'
@ -28,7 +30,9 @@ export const PostText = observer(function PostText({
if (!model || model.isLoading || model.uri !== uri) { if (!model || model.isLoading || model.uri !== uri) {
return ( return (
<View> <View>
<ActivityIndicator /> <LoadingPlaceholder width="100%" height={8} style={{marginTop: 6}} />
<LoadingPlaceholder width="100%" height={8} style={{marginTop: 6}} />
<LoadingPlaceholder width={100} height={8} style={{marginTop: 6}} />
</View> </View>
) )
} }
@ -38,7 +42,7 @@ export const PostText = observer(function PostText({
if (model.hasError) { if (model.hasError) {
return ( return (
<View> <View>
<Text style={style}>{model.error}</Text> <ErrorMessage style={style} message={model.error} />
</View> </View>
) )
} }

View File

@ -0,0 +1,73 @@
import React, {useEffect, useMemo} from 'react'
import {
Animated,
StyleProp,
useWindowDimensions,
View,
ViewStyle,
} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {colors} from '../../lib/styles'
export function LoadingPlaceholder({
width,
height,
style,
}: {
width: string | number
height: string | number
style?: StyleProp<ViewStyle>
}) {
const dim = useWindowDimensions()
const elWidth = typeof width === 'string' ? dim.width : width
const offset = useMemo(() => new Animated.Value(elWidth * -1), [])
useEffect(() => {
const anim = Animated.loop(
Animated.sequence([
Animated.timing(offset, {
toValue: elWidth,
duration: 1e3,
useNativeDriver: true,
isInteraction: false,
}),
Animated.timing(offset, {
toValue: elWidth * -1,
duration: 0,
delay: 500,
useNativeDriver: true,
isInteraction: false,
}),
]),
)
anim.start()
return () => anim.stop()
}, [])
return (
<View
style={[
{
width,
height,
backgroundColor: colors.gray2,
borderRadius: 6,
overflow: 'hidden',
},
style,
]}>
<Animated.View
style={{
width,
height,
transform: [{translateX: offset}],
}}>
<LinearGradient
colors={[colors.gray2, '#d4d2d2', colors.gray2]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}
style={{width: '100%', height: '100%'}}
/>
</Animated.View>
</View>
)
}

View File

@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'
import * as GetProfile from '../../../third-party/api/src/client/types/app/bsky/actor/getProfile' import * as GetProfile from '../../../third-party/api/src/client/types/app/bsky/actor/getProfile'
import {StyleProp, Text, TextStyle} from 'react-native' import {StyleProp, Text, TextStyle} from 'react-native'
import {Link} from './Link' import {Link} from './Link'
import {LoadingPlaceholder} from './LoadingPlaceholder'
import {useStores} from '../../../state' import {useStores} from '../../../state'
export function UserInfoText({ export function UserInfoText({
@ -48,26 +49,31 @@ export function UserInfoText({
} }
}, [did, store.api.app.bsky]) }, [did, store.api.app.bsky])
let inner
if (didFail) {
inner = <Text style={style}>{failed}</Text>
} else if (profile) {
inner = <Text style={style}>{`${prefix || ''}${profile[attr]}`}</Text>
} else {
inner = (
<LoadingPlaceholder
width={80}
height={8}
style={{position: 'relative', top: 1, left: 2}}
/>
)
}
if (asLink) { if (asLink) {
const title = profile?.displayName || profile?.handle || 'User' const title = profile?.displayName || profile?.handle || 'User'
return ( return (
<Link <Link
href={`/profile/${profile?.handle ? profile.handle : did}`} href={`/profile/${profile?.handle ? profile.handle : did}`}
title={title}> title={title}>
<Text style={style}> {inner}
{didFail
? failed
: profile
? `${prefix || ''}${profile[attr]}`
: loading}
</Text>
</Link> </Link>
) )
} }
return ( return inner
<Text style={style}>
{didFail ? failed : profile ? `${prefix || ''}${profile[attr]}` : loading}
</Text>
)
} }