New navigation model (#1)

* Flatten all routing into a single stack

* Replace router with custom implementation

* Add shell header and titles

* Add tab selector

* Add back/forward history menus on longpress

* Fix: don't modify state during render

* Add refresh() to navigation and reroute navigations to the current location to refresh instead of add to history

* Cache screens during navigation to maintain scroll position and improve load-time for renders
This commit is contained in:
Paul Frazee 2022-08-31 14:36:50 -05:00 committed by GitHub
parent d1470bad66
commit 97f52b6a03
57 changed files with 1382 additions and 1159 deletions

View file

@ -0,0 +1,43 @@
import React, {useLayoutEffect, useRef} from 'react'
// import {Text, TouchableOpacity} from 'react-native'
// import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Composer as ComposerComponent} from '../com/composer/Composer'
import {ScreenParams} from '../routes'
export const Composer = ({params}: ScreenParams) => {
const {replyTo} = params
const ref = useRef<{publish: () => Promise<boolean>}>()
// TODO
// useLayoutEffect(() => {
// navigation.setOptions({
// headerShown: true,
// headerTitle: replyTo ? 'Reply' : 'New Post',
// headerLeft: () => (
// <TouchableOpacity onPress={() => navigation.goBack()}>
// <FontAwesomeIcon icon="x" />
// </TouchableOpacity>
// ),
// headerRight: () => (
// <TouchableOpacity
// onPress={() => {
// if (!ref.current) {
// return
// }
// ref.current.publish().then(
// posted => {
// if (posted) {
// navigation.goBack()
// }
// },
// err => console.error('Failed to create post', err),
// )
// }}>
// <Text>Post</Text>
// </TouchableOpacity>
// ),
// })
// }, [navigation, replyTo, ref])
return <ComposerComponent ref={ref} replyTo={replyTo} />
}

65
src/view/screens/Home.tsx Normal file
View file

@ -0,0 +1,65 @@
import React, {useState, useEffect, useLayoutEffect} from 'react'
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Feed} from '../com/feed/Feed'
import {useStores} from '../../state'
import {useLoadEffect} from '../lib/navigation'
import {AVIS} from '../lib/assets'
import {ScreenParams} from '../routes'
export function Home({params}: ScreenParams) {
const [hasSetup, setHasSetup] = useState<boolean>(false)
const store = useStores()
useLoadEffect(() => {
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])
return (
<View>
<Feed feed={store.homeFeed} />
</View>
)
}
const styles = StyleSheet.create({
avi: {
width: 20,
height: 20,
borderRadius: 10,
resizeMode: 'cover',
},
})

View file

@ -1,18 +1,15 @@
import React from 'react'
import {Text, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Shell} from '../../shell'
// import type {RootTabsScreenProps} from '../routes/types'
// import {useStores} from '../../state'
export const Login = observer(
(/*{navigation}: RootTabsScreenProps<'Login'>*/) => {
// const store = useStores()
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Sign In</Text>
{/*store.session.uiError && <Text>{store.session.uiError}</Text>}
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Sign In</Text>
{/*store.session.uiError && <Text>{store.session.uiError}</Text>}
{!store.session.uiIsProcessing ? (
<>
<Button title="Login" onPress={() => store.session.login()} />
@ -24,8 +21,7 @@ export const Login = observer(
) : (
<ActivityIndicator />
)*/}
</View>
</Shell>
</View>
)
},
)

View file

@ -0,0 +1,13 @@
import React from 'react'
import {Text, Button, View} from 'react-native'
import {useStores} from '../../state'
export const NotFound = () => {
const stores = useStores()
return (
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Page not found</Text>
<Button title="Home" onPress={() => stores.nav.navigate('/')} />
</View>
)
}

View file

@ -0,0 +1,65 @@
import React, {useState, useEffect, useLayoutEffect} from 'react'
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Feed} from '../com/notifications/Feed'
import {useStores} from '../../state'
import {AVIS} from '../lib/assets'
import {ScreenParams} from '../routes'
import {useLoadEffect} from '../lib/navigation'
export const Notifications = ({params}: ScreenParams) => {
const [hasSetup, setHasSetup] = useState<boolean>(false)
const store = useStores()
useLoadEffect(() => {
store.nav.setTitle('Notifications')
console.log('Fetching notifications feed')
store.notesFeed.setup().then(() => setHasSetup(true))
}, [store.notesFeed])
// 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>
)
}
const styles = StyleSheet.create({
avi: {
width: 20,
height: 20,
borderRadius: 10,
resizeMode: 'cover',
},
})

View file

@ -0,0 +1,26 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {makeRecordUri} from '../lib/strings'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {ScreenParams} from '../routes'
export const PostLikedBy = ({params}: ScreenParams) => {
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])
return <PostLikedByComponent uri={uri} />
}

View file

@ -0,0 +1,26 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {makeRecordUri} from '../lib/strings'
import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy'
import {ScreenParams} from '../routes'
export const PostRepostedBy = ({params}: ScreenParams) => {
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])
return <PostRepostedByComponent uri={uri} />
}

View file

@ -0,0 +1,32 @@
import React, {useEffect, useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
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) => {
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])
return <PostThreadComponent uri={uri} />
}

View file

@ -0,0 +1,58 @@
import React, {useState, useEffect} from 'react'
import {View, StyleSheet} from 'react-native'
import {FeedViewModel} from '../../state/models/feed-view'
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) => {
const store = useStores()
const [hasSetup, setHasSetup] = useState<string>('')
const [feedView, setFeedView] = useState<FeedViewModel | undefined>()
useLoadEffect(() => {
const author = params.name
if (feedView?.params.author === author) {
return // no change needed? or trigger refresh?
}
console.log('Fetching profile feed', author)
const newFeedView = new FeedViewModel(store, {author})
setFeedView(newFeedView)
newFeedView
.setup()
.catch(err => console.error('Failed to fetch feed', err))
.then(() => {
setHasSetup(author)
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])
return (
<View style={styles.container}>
<ProfileHeader user={params.name} />
<View style={styles.feed}>{feedView && <Feed feed={feedView} />}</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
height: '100%',
},
feed: {
flex: 1,
},
})

View file

@ -0,0 +1,24 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
import {ScreenParams} from '../routes'
export const ProfileFollowers = ({params}: ScreenParams) => {
const {name} = params
// TODO
// useLayoutEffect(() => {
// navigation.setOptions({
// headerShown: true,
// headerTitle: 'Followers',
// headerLeft: () => (
// <TouchableOpacity onPress={() => navigation.goBack()}>
// <FontAwesomeIcon icon="arrow-left" />
// </TouchableOpacity>
// ),
// })
// }, [navigation])
return <ProfileFollowersComponent name={name} />
}

View file

@ -0,0 +1,24 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
import {ScreenParams} from '../routes'
export const ProfileFollows = ({params}: ScreenParams) => {
const {name} = params
// TODO
// useLayoutEffect(() => {
// navigation.setOptions({
// headerShown: true,
// headerTitle: 'Following',
// headerLeft: () => (
// <TouchableOpacity onPress={() => navigation.goBack()}>
// <FontAwesomeIcon icon="arrow-left" />
// </TouchableOpacity>
// ),
// })
// }, [navigation])
return <ProfileFollowsComponent name={name} />
}

View file

@ -0,0 +1,11 @@
import React from 'react'
import {Text, View} from 'react-native'
import {ScreenParams} from '../routes'
export const Search = ({params}: ScreenParams) => {
return (
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Search</Text>
</View>
)
}

View file

@ -1,18 +1,15 @@
import React from 'react'
import {Text, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {Shell} from '../../shell'
// import type {RootTabsScreenProps} from '../routes/types'
// import {useStores} from '../../state'
export const Signup = observer(
(/*{navigation}: RootTabsScreenProps<'Signup'>*/) => {
// const store = useStores()
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Create Account</Text>
{/*store.session.uiError ?? <Text>{store.session.uiError}</Text>}
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Create Account</Text>
{/*store.session.uiError ?? <Text>{store.session.uiError}</Text>}
{!store.session.uiIsProcessing ? (
<>
<Button
@ -27,8 +24,7 @@ export const Signup = observer(
) : (
<ActivityIndicator />
)*/}
</View>
</Shell>
</View>
)
},
)

View file

@ -1,50 +0,0 @@
import React, {useLayoutEffect, useRef} from 'react'
import {Text, TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {Composer as ComposerComponent} from '../../com/composer/Composer'
export const Composer = ({
navigation,
route,
}: RootTabsScreenProps<'Composer'>) => {
const {replyTo} = route.params
const ref = useRef<{publish: () => Promise<boolean>}>()
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: replyTo ? 'Reply' : 'New Post',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<FontAwesomeIcon icon="x" />
</TouchableOpacity>
),
headerRight: () => (
<TouchableOpacity
onPress={() => {
if (!ref.current) {
return
}
ref.current.publish().then(
posted => {
if (posted) {
navigation.goBack()
}
},
err => console.error('Failed to create post', err),
)
}}>
<Text>Post</Text>
</TouchableOpacity>
),
})
}, [navigation, replyTo, ref])
return (
<Shell>
<ComposerComponent ref={ref} replyTo={replyTo} />
</Shell>
)
}

View file

@ -1,38 +0,0 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {makeRecordUri} from '../../lib/strings'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {PostLikedBy as PostLikedByComponent} from '../../com/post-thread/PostLikedBy'
export const PostLikedBy = ({
navigation,
route,
}: RootTabsScreenProps<'PostLikedBy'>) => {
const {name, recordKey} = route.params
const uri = makeRecordUri(name, 'blueskyweb.xyz:Posts', recordKey)
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: 'Liked By',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<FontAwesomeIcon icon="arrow-left" />
</TouchableOpacity>
),
})
}, [navigation])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.push(screen, props)
}
return (
<Shell>
<PostLikedByComponent uri={uri} onNavigateContent={onNavigateContent} />
</Shell>
)
}

View file

@ -1,41 +0,0 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {makeRecordUri} from '../../lib/strings'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {PostRepostedBy as PostRepostedByComponent} from '../../com/post-thread/PostRepostedBy'
export const PostRepostedBy = ({
navigation,
route,
}: RootTabsScreenProps<'PostRepostedBy'>) => {
const {name, recordKey} = route.params
const uri = makeRecordUri(name, 'blueskyweb.xyz:Posts', recordKey)
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: 'Reposted By',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<FontAwesomeIcon icon="arrow-left" />
</TouchableOpacity>
),
})
}, [navigation])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.push(screen, props)
}
return (
<Shell>
<PostRepostedByComponent
uri={uri}
onNavigateContent={onNavigateContent}
/>
</Shell>
)
}

View file

@ -1,38 +0,0 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {makeRecordUri} from '../../lib/strings'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {PostThread as PostThreadComponent} from '../../com/post-thread/PostThread'
export const PostThread = ({
navigation,
route,
}: RootTabsScreenProps<'PostThread'>) => {
const {name, recordKey} = route.params
const uri = makeRecordUri(name, 'blueskyweb.xyz:Posts', recordKey)
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: 'Thread',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<FontAwesomeIcon icon="arrow-left" />
</TouchableOpacity>
),
})
}, [navigation])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.push(screen, props)
}
return (
<Shell>
<PostThreadComponent uri={uri} onNavigateContent={onNavigateContent} />
</Shell>
)
}

View file

@ -1,71 +0,0 @@
import React, {useState, useEffect} from 'react'
import {View, StyleSheet} from 'react-native'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {FeedViewModel} from '../../../state/models/feed-view'
import {useStores} from '../../../state'
import {ProfileHeader} from '../../com/profile/ProfileHeader'
import {Feed} from '../../com/feed/Feed'
export const Profile = ({
navigation,
route,
}: RootTabsScreenProps<'Profile'>) => {
const store = useStores()
const [hasSetup, setHasSetup] = useState<string>('')
const [feedView, setFeedView] = useState<FeedViewModel | undefined>()
useEffect(() => {
const author = route.params.name
if (feedView?.params.author === author) {
return // no change needed? or trigger refresh?
}
console.log('Fetching profile feed', author)
const newFeedView = new FeedViewModel(store, {author})
setFeedView(newFeedView)
newFeedView
.setup()
.catch(err => console.error('Failed to fetch feed', err))
.then(() => setHasSetup(author))
}, [route.params.name, feedView?.params.author, store])
useEffect(() => {
return navigation.addListener('focus', () => {
if (hasSetup === feedView?.params.author) {
console.log('Updating profile feed', hasSetup)
feedView?.update()
}
})
}, [navigation, feedView, hasSetup])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.push(screen, props)
}
return (
<Shell>
<View style={styles.container}>
<ProfileHeader
user={route.params.name}
onNavigateContent={onNavigateContent}
/>
<View style={styles.feed}>
{feedView && (
<Feed feed={feedView} onNavigateContent={onNavigateContent} />
)}
</View>
</View>
</Shell>
)
}
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
height: '100%',
},
feed: {
flex: 1,
},
})

View file

@ -1,39 +0,0 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {ProfileFollowers as ProfileFollowersComponent} from '../../com/profile/ProfileFollowers'
export const ProfileFollowers = ({
navigation,
route,
}: RootTabsScreenProps<'ProfileFollowers'>) => {
const {name} = route.params
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: 'Followers',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<FontAwesomeIcon icon="arrow-left" />
</TouchableOpacity>
),
})
}, [navigation])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.push(screen, props)
}
return (
<Shell>
<ProfileFollowersComponent
name={name}
onNavigateContent={onNavigateContent}
/>
</Shell>
)
}

View file

@ -1,39 +0,0 @@
import React, {useLayoutEffect} from 'react'
import {TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Shell} from '../../shell'
import type {RootTabsScreenProps} from '../../routes/types'
import {ProfileFollows as ProfileFollowsComponent} from '../../com/profile/ProfileFollows'
export const ProfileFollows = ({
navigation,
route,
}: RootTabsScreenProps<'ProfileFollows'>) => {
const {name} = route.params
useLayoutEffect(() => {
navigation.setOptions({
headerShown: true,
headerTitle: 'Following',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack()}>
<FontAwesomeIcon icon="arrow-left" />
</TouchableOpacity>
),
})
}, [navigation])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.push(screen, props)
}
return (
<Shell>
<ProfileFollowsComponent
name={name}
onNavigateContent={onNavigateContent}
/>
</Shell>
)
}

View file

@ -1,69 +0,0 @@
import React, {useState, useEffect, useLayoutEffect} from 'react'
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Shell} from '../../shell'
import {Feed} from '../../com/feed/Feed'
import type {RootTabsScreenProps} from '../../routes/types'
import {useStores} from '../../../state'
import {AVIS} from '../../lib/assets'
export function Home({navigation}: RootTabsScreenProps<'HomeTab'>) {
const [hasSetup, setHasSetup] = useState<boolean>(false)
const store = useStores()
useEffect(() => {
console.log('Fetching home feed')
store.homeFeed.setup().then(() => setHasSetup(true))
}, [store.homeFeed])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.navigate(screen, props)
}
useEffect(() => {
return navigation.addListener('focus', () => {
if (hasSetup) {
console.log('Updating home feed')
store.homeFeed.update()
}
})
}, [navigation, store.homeFeed, hasSetup])
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])
return (
<Shell>
<View>
<Feed feed={store.homeFeed} onNavigateContent={onNavigateContent} />
</View>
</Shell>
)
}
const styles = StyleSheet.create({
avi: {
width: 20,
height: 20,
borderRadius: 10,
resizeMode: 'cover',
},
})

View file

@ -1,16 +0,0 @@
import React from 'react'
import {Shell} from '../../shell'
import {ScrollView, Text, View} from 'react-native'
import type {RootTabsScreenProps} from '../../routes/types'
export const Menu = (_props: RootTabsScreenProps<'MenuTab'>) => {
return (
<Shell>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Menu</Text>
</View>
</ScrollView>
</Shell>
)
}

View file

@ -1,15 +0,0 @@
import React from 'react'
import {Shell} from '../../shell'
import {Text, Button, View} from 'react-native'
import type {RootTabsScreenProps} from '../../routes/types'
export const NotFound = ({navigation}: RootTabsScreenProps<'NotFound'>) => {
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Page not found</Text>
<Button title="Home" onPress={() => navigation.navigate('HomeTab')} />
</View>
</Shell>
)
}

View file

@ -1,71 +0,0 @@
import React, {useState, useEffect, useLayoutEffect} from 'react'
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Shell} from '../../shell'
import {Feed} from '../../com/notifications/Feed'
import type {RootTabsScreenProps} from '../../routes/types'
import {useStores} from '../../../state'
import {AVIS} from '../../lib/assets'
export const Notifications = ({
navigation,
}: RootTabsScreenProps<'NotificationsTab'>) => {
const [hasSetup, setHasSetup] = useState<boolean>(false)
const store = useStores()
useEffect(() => {
console.log('Fetching home feed')
store.notesFeed.setup().then(() => setHasSetup(true))
}, [store.notesFeed])
const onNavigateContent = (screen: string, props: Record<string, string>) => {
// @ts-ignore it's up to the callers to supply correct params -prf
navigation.navigate(screen, props)
}
useEffect(() => {
return navigation.addListener('focus', () => {
if (hasSetup) {
console.log('Updating home feed')
store.notesFeed.update()
}
})
}, [navigation, store.notesFeed, hasSetup])
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 (
<Shell>
<View>
<Feed view={store.notesFeed} onNavigateContent={onNavigateContent} />
</View>
</Shell>
)
}
const styles = StyleSheet.create({
avi: {
width: 20,
height: 20,
borderRadius: 10,
resizeMode: 'cover',
},
})

View file

@ -1,14 +0,0 @@
import React from 'react'
import {Shell} from '../../shell'
import {Text, View} from 'react-native'
import type {RootTabsScreenProps} from '../../routes/types'
export const Search = (_props: RootTabsScreenProps<'SearchTab'>) => {
return (
<Shell>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Search</Text>
</View>
</Shell>
)
}