Implement working screen-state management and remove extraneous loads
parent
346385ce43
commit
bb51af5ae9
|
@ -27,7 +27,6 @@ export async function setupState() {
|
|||
// track changes & save to storage
|
||||
autorun(() => {
|
||||
const snapshot = rootStore.serialize()
|
||||
console.log('saving', snapshot)
|
||||
storage.save(ROOT_STATE_STORAGE_KEY, snapshot)
|
||||
})
|
||||
|
||||
|
|
|
@ -9,15 +9,11 @@ import {isObj, hasProp} from '../lib/type-guards'
|
|||
import {SessionModel} from './session'
|
||||
import {NavigationModel} from './navigation'
|
||||
import {MeModel} from './me'
|
||||
import {FeedViewModel} from './feed-view'
|
||||
import {NotificationsViewModel} from './notifications-view'
|
||||
|
||||
export class RootStoreModel {
|
||||
session = new SessionModel()
|
||||
nav = new NavigationModel()
|
||||
me = new MeModel(this)
|
||||
homeFeed = new FeedViewModel(this, {})
|
||||
notesFeed = new NotificationsViewModel(this, {})
|
||||
|
||||
constructor(public api: AdxClient) {
|
||||
makeAutoObservable(this, {
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import {useEffect} from 'react'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
type CB = () => void
|
||||
/**
|
||||
* This custom effect hook will trigger on every "navigation"
|
||||
* Use this in screens to handle any loading behaviors needed
|
||||
*/
|
||||
export function useLoadEffect(cb: CB, deps: any[] = []) {
|
||||
const store = useStores()
|
||||
useEffect(cb, [store.nav.tab, ...deps])
|
||||
}
|
|
@ -16,6 +16,7 @@ import {ProfileFollows} from './screens/ProfileFollows'
|
|||
|
||||
export type ScreenParams = {
|
||||
params: Record<string, any>
|
||||
visible: boolean
|
||||
}
|
||||
export type Route = [React.FC<ScreenParams>, IconProp, RegExp]
|
||||
export type MatchResult = {
|
||||
|
|
|
@ -1,53 +1,31 @@
|
|||
import React, {useState, useEffect, useLayoutEffect} from 'react'
|
||||
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Feed} from '../com/feed/Feed'
|
||||
import {FAB} from '../com/util/FloatingActionButton'
|
||||
import {useStores} from '../../state'
|
||||
import {useLoadEffect} from '../lib/navigation'
|
||||
import {AVIS} from '../lib/assets'
|
||||
import {FeedViewModel} from '../../state/models/feed-view'
|
||||
import {ScreenParams} from '../routes'
|
||||
|
||||
export function Home({params}: ScreenParams) {
|
||||
export function Home({visible}: ScreenParams) {
|
||||
const [hasSetup, setHasSetup] = useState<boolean>(false)
|
||||
const [feedView, setFeedView] = useState<FeedViewModel | undefined>()
|
||||
const store = useStores()
|
||||
useLoadEffect(() => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
if (hasSetup) {
|
||||
console.log('Updating home feed')
|
||||
feedView?.update()
|
||||
} else {
|
||||
store.nav.setTitle('Home')
|
||||
console.log('Fetching home feed')
|
||||
store.homeFeed.setup().then(() => setHasSetup(true))
|
||||
}, [store.nav, store.homeFeed])
|
||||
|
||||
// TODO
|
||||
// useEffect(() => {
|
||||
// return navigation.addListener('focus', () => {
|
||||
// if (hasSetup) {
|
||||
// console.log('Updating home feed')
|
||||
// store.homeFeed.update()
|
||||
// }
|
||||
// })
|
||||
// }, [navigation, store.homeFeed, hasSetup])
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'V I B E',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity
|
||||
// onPress={() => navigation.push('Profile', {name: 'alice.com'})}>
|
||||
// <Image source={AVIS['alice.com']} style={styles.avi} />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// headerRight: () => (
|
||||
// <TouchableOpacity
|
||||
// onPress={() => {
|
||||
// navigation.push('Composer', {})
|
||||
// }}>
|
||||
// <FontAwesomeIcon icon="plus" style={{color: '#006bf7'}} />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
const newFeedView = new FeedViewModel(store, {})
|
||||
setFeedView(newFeedView)
|
||||
newFeedView.setup().then(() => setHasSetup(true))
|
||||
}
|
||||
}, [visible, store])
|
||||
|
||||
const onComposePress = () => {
|
||||
store.nav.navigate('/compose')
|
||||
|
@ -55,17 +33,8 @@ export function Home({params}: ScreenParams) {
|
|||
|
||||
return (
|
||||
<View>
|
||||
<Feed feed={store.homeFeed} />
|
||||
{feedView && <Feed feed={feedView} />}
|
||||
<FAB icon="pen-nib" onPress={onComposePress} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
avi: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,65 +1,32 @@
|
|||
import React, {useState, useEffect, useLayoutEffect} from 'react'
|
||||
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Feed} from '../com/notifications/Feed'
|
||||
import {useStores} from '../../state'
|
||||
import {AVIS} from '../lib/assets'
|
||||
import {NotificationsViewModel} from '../../state/models/notifications-view'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useLoadEffect} from '../lib/navigation'
|
||||
|
||||
export const Notifications = ({params}: ScreenParams) => {
|
||||
export const Notifications = ({visible}: ScreenParams) => {
|
||||
const [hasSetup, setHasSetup] = useState<boolean>(false)
|
||||
const [notesView, setNotesView] = useState<
|
||||
NotificationsViewModel | undefined
|
||||
>()
|
||||
const store = useStores()
|
||||
useLoadEffect(() => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
if (hasSetup) {
|
||||
console.log('Updating notifications feed')
|
||||
notesView?.update()
|
||||
} else {
|
||||
store.nav.setTitle('Notifications')
|
||||
console.log('Fetching notifications feed')
|
||||
store.notesFeed.setup().then(() => setHasSetup(true))
|
||||
}, [store.notesFeed])
|
||||
const newNotesView = new NotificationsViewModel(store, {})
|
||||
setNotesView(newNotesView)
|
||||
newNotesView.setup().then(() => setHasSetup(true))
|
||||
}
|
||||
}, [visible, store])
|
||||
|
||||
// TODO
|
||||
// useEffect(() => {
|
||||
// return navigation.addListener('focus', () => {
|
||||
// if (hasSetup) {
|
||||
// console.log('Updating notifications feed')
|
||||
// store.notesFeed.update()
|
||||
// }
|
||||
// })
|
||||
// }, [navigation, store.notesFeed, hasSetup])
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'Notifications',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity
|
||||
// onPress={() => navigation.push('Profile', {name: 'alice.com'})}>
|
||||
// <Image source={AVIS['alice.com']} style={styles.avi} />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// headerRight: () => (
|
||||
// <TouchableOpacity
|
||||
// onPress={() => {
|
||||
// navigation.push('Composer', {})
|
||||
// }}>
|
||||
// <FontAwesomeIcon icon="plus" style={{color: '#006bf7'}} />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Feed view={store.notesFeed} />
|
||||
</View>
|
||||
)
|
||||
return <View>{notesView && <Feed view={notesView} />}</View>
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
avi: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
resizeMode: 'cover',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,26 +1,19 @@
|
|||
import React, {useLayoutEffect} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useEffect} from 'react'
|
||||
import {makeRecordUri} from '../lib/strings'
|
||||
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
export const PostLikedBy = ({params}: ScreenParams) => {
|
||||
export const PostLikedBy = ({visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
const {name, recordKey} = params
|
||||
const uri = makeRecordUri(name, 'blueskyweb.xyz:Posts', recordKey)
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'Liked By',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity onPress={() => navigation.goBack()}>
|
||||
// <FontAwesomeIcon icon="arrow-left" />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
store.nav.setTitle('Liked by')
|
||||
}
|
||||
}, [store, visible])
|
||||
|
||||
return <PostLikedByComponent uri={uri} />
|
||||
}
|
||||
|
|
|
@ -1,26 +1,19 @@
|
|||
import React, {useLayoutEffect} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useEffect} from 'react'
|
||||
import {makeRecordUri} from '../lib/strings'
|
||||
import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
export const PostRepostedBy = ({params}: ScreenParams) => {
|
||||
export const PostRepostedBy = ({visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
const {name, recordKey} = params
|
||||
const uri = makeRecordUri(name, 'blueskyweb.xyz:Posts', recordKey)
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'Reposted By',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity onPress={() => navigation.goBack()}>
|
||||
// <FontAwesomeIcon icon="arrow-left" />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
store.nav.setTitle('Reposted by')
|
||||
}
|
||||
}, [store, visible])
|
||||
|
||||
return <PostRepostedByComponent uri={uri} />
|
||||
}
|
||||
|
|
|
@ -1,32 +1,19 @@
|
|||
import React, {useEffect, useLayoutEffect} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useEffect} from 'react'
|
||||
import {makeRecordUri} from '../lib/strings'
|
||||
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
import {useLoadEffect} from '../lib/navigation'
|
||||
|
||||
export const PostThread = ({params}: ScreenParams) => {
|
||||
export const PostThread = ({visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
const {name, recordKey} = params
|
||||
const uri = makeRecordUri(name, 'blueskyweb.xyz:Posts', recordKey)
|
||||
useLoadEffect(() => {
|
||||
store.nav.setTitle(`Post by ${name}`)
|
||||
}, [store.nav, name])
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'Thread',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity onPress={() => navigation.goBack()}>
|
||||
// <FontAwesomeIcon icon="arrow-left" />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
store.nav.setTitle(`Post by ${name}`)
|
||||
}
|
||||
}, [visible, store.nav, name])
|
||||
|
||||
return <PostThreadComponent uri={uri} />
|
||||
}
|
||||
|
|
|
@ -5,39 +5,33 @@ import {useStores} from '../../state'
|
|||
import {ProfileHeader} from '../com/profile/ProfileHeader'
|
||||
import {Feed} from '../com/feed/Feed'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useLoadEffect} from '../lib/navigation'
|
||||
|
||||
export const Profile = ({params}: ScreenParams) => {
|
||||
export const Profile = ({visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
const [hasSetup, setHasSetup] = useState<string>('')
|
||||
const [hasSetup, setHasSetup] = useState<boolean>(false)
|
||||
const [feedView, setFeedView] = useState<FeedViewModel | undefined>()
|
||||
|
||||
useLoadEffect(() => {
|
||||
const author = params.name
|
||||
if (feedView?.params.author === author) {
|
||||
return // no change needed? or trigger refresh?
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
console.log('Fetching profile feed', author)
|
||||
const author = params.name
|
||||
if (hasSetup) {
|
||||
console.log('Updating profile feed for', author)
|
||||
feedView?.update()
|
||||
} else {
|
||||
console.log('Fetching profile feed for', author)
|
||||
const newFeedView = new FeedViewModel(store, {author})
|
||||
setFeedView(newFeedView)
|
||||
newFeedView
|
||||
.setup()
|
||||
.catch(err => console.error('Failed to fetch feed', err))
|
||||
.then(() => {
|
||||
setHasSetup(author)
|
||||
setHasSetup(true)
|
||||
store.nav.setTitle(author)
|
||||
})
|
||||
}, [params.name, feedView?.params.author, store])
|
||||
|
||||
// TODO
|
||||
// useEffect(() => {
|
||||
// return navigation.addListener('focus', () => {
|
||||
// if (hasSetup === feedView?.params.author) {
|
||||
// console.log('Updating profile feed', hasSetup)
|
||||
// feedView?.update()
|
||||
// }
|
||||
// })
|
||||
// }, [navigation, feedView, hasSetup])
|
||||
}
|
||||
}, [visible, params.name, store])
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import React, {useLayoutEffect} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useEffect} from 'react'
|
||||
import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
export const ProfileFollowers = ({params}: ScreenParams) => {
|
||||
export const ProfileFollowers = ({visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
const {name} = params
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'Followers',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity onPress={() => navigation.goBack()}>
|
||||
// <FontAwesomeIcon icon="arrow-left" />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
store.nav.setTitle('Followers of')
|
||||
}
|
||||
}, [store, visible])
|
||||
|
||||
return <ProfileFollowersComponent name={name} />
|
||||
}
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import React, {useLayoutEffect} from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import React, {useEffect} from 'react'
|
||||
import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
|
||||
import {ScreenParams} from '../routes'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
export const ProfileFollows = ({params}: ScreenParams) => {
|
||||
export const ProfileFollows = ({visible, params}: ScreenParams) => {
|
||||
const store = useStores()
|
||||
const {name} = params
|
||||
|
||||
// TODO
|
||||
// useLayoutEffect(() => {
|
||||
// navigation.setOptions({
|
||||
// headerShown: true,
|
||||
// headerTitle: 'Following',
|
||||
// headerLeft: () => (
|
||||
// <TouchableOpacity onPress={() => navigation.goBack()}>
|
||||
// <FontAwesomeIcon icon="arrow-left" />
|
||||
// </TouchableOpacity>
|
||||
// ),
|
||||
// })
|
||||
// }, [navigation])
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
store.nav.setTitle('Followers of')
|
||||
}
|
||||
}, [store, visible])
|
||||
|
||||
return <ProfileFollowsComponent name={name} />
|
||||
}
|
||||
|
|
|
@ -113,12 +113,12 @@ export const MobileShell: React.FC = observer(() => {
|
|||
</View>
|
||||
<SafeAreaView style={styles.innerContainer}>
|
||||
<ScreenContainer style={styles.screenContainer}>
|
||||
{screenRenderDesc.screens.map(({Com, params, key, activityState}) => (
|
||||
{screenRenderDesc.screens.map(({Com, params, key, visible}) => (
|
||||
<Screen
|
||||
key={key}
|
||||
style={[StyleSheet.absoluteFill, styles.screen]}
|
||||
activityState={activityState}>
|
||||
<Com params={params} />
|
||||
activityState={visible ? 2 : 0}>
|
||||
<Com params={params} visible={visible} />
|
||||
</Screen>
|
||||
))}
|
||||
</ScreenContainer>
|
||||
|
@ -156,7 +156,7 @@ export const MobileShell: React.FC = observer(() => {
|
|||
* This method produces the information needed by the shell to
|
||||
* render the current screens with screen-caching behaviors.
|
||||
*/
|
||||
type ScreenRenderDesc = MatchResult & {key: string; activityState: 0 | 1 | 2}
|
||||
type ScreenRenderDesc = MatchResult & {key: string; visible: boolean}
|
||||
function constructScreenRenderDesc(nav: NavigationModel): {
|
||||
icon: IconProp
|
||||
screens: ScreenRenderDesc[]
|
||||
|
@ -176,7 +176,7 @@ function constructScreenRenderDesc(nav: NavigationModel): {
|
|||
}
|
||||
return Object.assign(matchRes, {
|
||||
key: `t${tab.id}-s${screen.index}`,
|
||||
activityState: isCurrent ? 2 : 0,
|
||||
visible: isCurrent,
|
||||
}) as ScreenRenderDesc
|
||||
})
|
||||
screens = screens.concat(parsedTabScreens)
|
||||
|
|
|
@ -12,6 +12,6 @@ Paul's todo list
|
|||
- Reposted by
|
||||
- Followers list
|
||||
- Follows list
|
||||
- Navigation
|
||||
- Restore all functionality that was disabled during the refactor
|
||||
- Reduce extraneous triggers of useLoadEffect
|
||||
- Bugs
|
||||
- Check that sub components arent reloading too much
|
||||
- Check that caching is choosing the right views
|
Loading…
Reference in New Issue