Better loading screens
parent
e470e3933b
commit
4ae6fbd3c8
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue