diff options
Diffstat (limited to 'src/view/screens')
-rw-r--r-- | src/view/screens/Contacts.tsx | 88 | ||||
-rw-r--r-- | src/view/screens/Debug.tsx | 6 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 68 | ||||
-rw-r--r-- | src/view/screens/Log.tsx | 22 | ||||
-rw-r--r-- | src/view/screens/NotFound.tsx | 48 | ||||
-rw-r--r-- | src/view/screens/Notifications.tsx | 40 | ||||
-rw-r--r-- | src/view/screens/PostDownvotedBy.tsx | 27 | ||||
-rw-r--r-- | src/view/screens/PostRepostedBy.tsx | 19 | ||||
-rw-r--r-- | src/view/screens/PostThread.tsx | 78 | ||||
-rw-r--r-- | src/view/screens/PostUpvotedBy.tsx | 20 | ||||
-rw-r--r-- | src/view/screens/Profile.tsx | 57 | ||||
-rw-r--r-- | src/view/screens/ProfileFollowers.tsx | 19 | ||||
-rw-r--r-- | src/view/screens/ProfileFollows.tsx | 19 | ||||
-rw-r--r-- | src/view/screens/Search.tsx | 53 | ||||
-rw-r--r-- | src/view/screens/Search.web.tsx | 28 | ||||
-rw-r--r-- | src/view/screens/Settings.tsx | 54 |
16 files changed, 274 insertions, 372 deletions
diff --git a/src/view/screens/Contacts.tsx b/src/view/screens/Contacts.tsx deleted file mode 100644 index 21943a10a..000000000 --- a/src/view/screens/Contacts.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, {useEffect, useState, useRef} from 'react' -import {StyleSheet, TextInput, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' -import {Selector} from '../com/util/Selector' -import {Text} from '../com/util/text/Text' -import {colors} from 'lib/styles' -import {ScreenParams} from '../routes' -import {useStores} from 'state/index' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' - -export const Contacts = ({navIdx, visible}: ScreenParams) => { - const store = useStores() - const selectorInterp = useAnimatedValue(0) - - useEffect(() => { - if (visible) { - store.nav.setTitle(navIdx, 'Contacts') - } - }, [store, visible, navIdx]) - - const [searchText, onChangeSearchText] = useState('') - const inputRef = useRef<TextInput | null>(null) - - return ( - <View> - <View style={styles.section}> - <Text testID="contactsTitle" style={styles.title}> - Contacts - </Text> - </View> - <View style={styles.section}> - <View style={styles.searchContainer}> - <FontAwesomeIcon - icon="magnifying-glass" - size={16} - style={styles.searchIcon} - /> - <TextInput - testID="contactsTextInput" - ref={inputRef} - value={searchText} - style={styles.searchInput} - placeholder="Search" - placeholderTextColor={colors.gray4} - onChangeText={onChangeSearchText} - /> - </View> - </View> - <Selector - items={['All', 'Following', 'Scenes']} - selectedIndex={0} - panX={selectorInterp} - /> - {!!store.me.handle && <ProfileFollowsComponent name={store.me.handle} />} - </View> - ) -} - -const styles = StyleSheet.create({ - section: { - backgroundColor: colors.white, - }, - title: { - fontSize: 30, - fontWeight: 'bold', - paddingHorizontal: 12, - paddingVertical: 6, - }, - - searchContainer: { - flexDirection: 'row', - backgroundColor: colors.gray1, - paddingHorizontal: 8, - paddingVertical: 8, - marginHorizontal: 10, - marginBottom: 6, - borderRadius: 4, - }, - searchIcon: { - color: colors.gray5, - marginRight: 8, - }, - searchInput: { - flex: 1, - color: colors.black, - }, -}) diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx index eb5ffe20f..852025324 100644 --- a/src/view/screens/Debug.tsx +++ b/src/view/screens/Debug.tsx @@ -1,5 +1,6 @@ import React from 'react' import {ScrollView, View} from 'react-native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext' import {usePalette} from 'lib/hooks/usePalette' @@ -20,7 +21,10 @@ import {ErrorMessage} from '../com/util/error/ErrorMessage' const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs'] -export const Debug = () => { +export const DebugScreen = ({}: NativeStackScreenProps< + CommonNavigatorParams, + 'Debug' +>) => { const [colorScheme, setColorScheme] = React.useState<'light' | 'dark'>( 'light', ) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 42759f7ff..505b1fcfe 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,14 +1,15 @@ import React from 'react' import {FlatList, View} from 'react-native' +import {useFocusEffect, useIsFocused} from '@react-navigation/native' import {observer} from 'mobx-react-lite' import useAppState from 'react-native-appstate-hook' +import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/posts/Feed' import {LoadLatestBtn} from '../com/util/LoadLatestBtn' import {WelcomeBanner} from '../com/util/WelcomeBanner' import {FAB} from '../com/util/FAB' import {useStores} from 'state/index' -import {ScreenParams} from '../routes' import {s} from 'lib/styles' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics' @@ -16,19 +17,20 @@ import {ComposeIcon2} from 'lib/icons' const HEADER_HEIGHT = 42 -export const Home = observer(function Home({navIdx, visible}: ScreenParams) { +type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> +export const HomeScreen = observer(function Home(_opts: Props) { const store = useStores() const onMainScroll = useOnMainScroll(store) const {screen, track} = useAnalytics() const scrollElRef = React.useRef<FlatList>(null) - const [wasVisible, setWasVisible] = React.useState<boolean>(false) const {appState} = useAppState({ onForeground: () => doPoll(true), }) + const isFocused = useIsFocused() const doPoll = React.useCallback( (knownActive = false) => { - if ((!knownActive && appState !== 'active') || !visible) { + if ((!knownActive && appState !== 'active') || !isFocused) { return } if (store.me.mainFeed.isLoading) { @@ -37,7 +39,7 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { store.log.debug('HomeScreen: Polling for new posts') store.me.mainFeed.checkForLatest() }, - [appState, visible, store], + [appState, isFocused, store], ) const scrollToTop = React.useCallback(() => { @@ -46,53 +48,35 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { scrollElRef.current?.scrollToOffset({offset: -HEADER_HEIGHT}) }, [scrollElRef]) - React.useEffect(() => { - const softResetSub = store.onScreenSoftReset(scrollToTop) - const feedCleanup = store.me.mainFeed.registerListeners() - const pollInterval = setInterval(doPoll, 15e3) - const cleanup = () => { - clearInterval(pollInterval) - softResetSub.remove() - feedCleanup() - } + useFocusEffect( + React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(scrollToTop) + const feedCleanup = store.me.mainFeed.registerListeners() + const pollInterval = setInterval(doPoll, 15e3) - // guard to only continue when transitioning from !visible -> visible - // TODO is this 100% needed? depends on if useEffect() is getting refired - // for reasons other than `visible` changing -prf - if (!visible) { - setWasVisible(false) - return cleanup - } else if (wasVisible) { - return cleanup - } - setWasVisible(true) + screen('Feed') + store.log.debug('HomeScreen: Updating feed') + if (store.me.mainFeed.hasContent) { + store.me.mainFeed.update() + } - // just became visible - screen('Feed') - store.nav.setTitle(navIdx, 'Home') - store.log.debug('HomeScreen: Updating feed') - if (store.me.mainFeed.hasContent) { - store.me.mainFeed.update() - } - return cleanup - }, [ - visible, - store, - store.me.mainFeed, - navIdx, - doPoll, - wasVisible, - scrollToTop, - screen, - ]) + return () => { + clearInterval(pollInterval) + softResetSub.remove() + feedCleanup() + } + }, [store, doPoll, scrollToTop, screen]), + ) const onPressCompose = React.useCallback(() => { track('HomeScreen:PressCompose') store.shell.openComposer({}) }, [store, track]) + const onPressTryAgain = React.useCallback(() => { store.me.mainFeed.refresh() }, [store]) + const onPressLoadLatest = React.useCallback(() => { store.me.mainFeed.refresh() scrollToTop() diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx index c067d3506..8e0fe8dd3 100644 --- a/src/view/screens/Log.tsx +++ b/src/view/screens/Log.tsx @@ -1,28 +1,30 @@ -import React, {useEffect} from 'react' +import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ScrollView} from '../com/util/Views' import {useStores} from 'state/index' -import {ScreenParams} from '../routes' import {s} from 'lib/styles' import {ViewHeader} from '../com/util/ViewHeader' import {Text} from '../com/util/text/Text' import {usePalette} from 'lib/hooks/usePalette' import {ago} from 'lib/strings/time' -export const Log = observer(function Log({navIdx, visible}: ScreenParams) { +export const LogScreen = observer(function Log({}: NativeStackScreenProps< + CommonNavigatorParams, + 'Log' +>) { const pal = usePalette('default') const store = useStores() const [expanded, setExpanded] = React.useState<string[]>([]) - useEffect(() => { - if (!visible) { - return - } - store.shell.setMinimalShellMode(false) - store.nav.setTitle(navIdx, 'Log') - }, [visible, store, navIdx]) + useFocusEffect( + React.useCallback(() => { + store.shell.setMinimalShellMode(false) + }, [store]), + ) const toggler = (id: string) => () => { if (expanded.includes(id)) { diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx index 77bbdd2aa..6ab37f117 100644 --- a/src/view/screens/NotFound.tsx +++ b/src/view/screens/NotFound.tsx @@ -1,20 +1,41 @@ import React from 'react' -import {Button, StyleSheet, View} from 'react-native' +import {StyleSheet, View} from 'react-native' +import {useNavigation, StackActions} from '@react-navigation/native' import {ViewHeader} from '../com/util/ViewHeader' import {Text} from '../com/util/text/Text' -import {useStores} from 'state/index' +import {Button} from 'view/com/util/forms/Button' +import {NavigationProp} from 'lib/routes/types' +import {usePalette} from 'lib/hooks/usePalette' +import {s} from 'lib/styles' + +export const NotFoundScreen = () => { + const pal = usePalette('default') + const navigation = useNavigation<NavigationProp>() + + const canGoBack = navigation.canGoBack() + const onPressHome = React.useCallback(() => { + if (canGoBack) { + navigation.goBack() + } else { + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) + } + }, [navigation, canGoBack]) -export const NotFound = () => { - const stores = useStores() return ( - <View testID="notFoundView"> + <View testID="notFoundView" style={pal.view}> <ViewHeader title="Page not found" /> <View style={styles.container}> - <Text style={styles.title}>Page not found</Text> + <Text type="title-2xl" style={[pal.text, s.mb10]}> + Page not found + </Text> + <Text type="md" style={[pal.text, s.mb10]}> + We're sorry! We can't find the page you were looking for. + </Text> <Button - testID="navigateHomeButton" - title="Home" - onPress={() => stores.nav.navigate('/')} + type="primary" + label={canGoBack ? 'Go back' : 'Go home'} + onPress={onPressHome} /> </View> </View> @@ -23,12 +44,9 @@ export const NotFound = () => { const styles = StyleSheet.create({ container: { - justifyContent: 'center', - alignItems: 'center', paddingTop: 100, - }, - title: { - fontSize: 40, - fontWeight: 'bold', + paddingHorizontal: 20, + alignItems: 'center', + height: '100%', }, }) diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index f1a9e8bf0..492177d1f 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -1,17 +1,25 @@ import React, {useEffect} from 'react' import {FlatList, View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' import useAppState from 'react-native-appstate-hook' +import { + NativeStackScreenProps, + NotificationsTabNavigatorParams, +} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/notifications/Feed' import {useStores} from 'state/index' -import {ScreenParams} from '../routes' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {s} from 'lib/styles' import {useAnalytics} from 'lib/analytics' const NOTIFICATIONS_POLL_INTERVAL = 15e3 -export const Notifications = ({navIdx, visible}: ScreenParams) => { +type Props = NativeStackScreenProps< + NotificationsTabNavigatorParams, + 'Notifications' +> +export const NotificationsScreen = ({}: Props) => { const store = useStores() const onMainScroll = useOnMainScroll(store) const scrollElRef = React.useRef<FlatList>(null) @@ -59,21 +67,19 @@ export const Notifications = ({navIdx, visible}: ScreenParams) => { // on-visible setup // = - useEffect(() => { - if (!visible) { - // mark read when the user leaves the screen - store.me.notifications.markAllRead() - return - } - store.log.debug('NotificationsScreen: Updating feed') - const softResetSub = store.onScreenSoftReset(scrollToTop) - store.me.notifications.update() - screen('Notifications') - store.nav.setTitle(navIdx, 'Notifications') - return () => { - softResetSub.remove() - } - }, [visible, store, navIdx, screen, scrollToTop]) + useFocusEffect( + React.useCallback(() => { + store.log.debug('NotificationsScreen: Updating feed') + const softResetSub = store.onScreenSoftReset(scrollToTop) + store.me.notifications.update() + screen('Notifications') + + return () => { + softResetSub.remove() + store.me.notifications.markAllRead() + } + }, [store, screen, scrollToTop]), + ) return ( <View style={s.hContentRegion}> diff --git a/src/view/screens/PostDownvotedBy.tsx b/src/view/screens/PostDownvotedBy.tsx deleted file mode 100644 index 570482598..000000000 --- a/src/view/screens/PostDownvotedBy.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, {useEffect} from 'react' -import {View} from 'react-native' -import {ViewHeader} from '../com/util/ViewHeader' -import {PostVotedBy as PostLikedByComponent} from '../com/post-thread/PostVotedBy' -import {ScreenParams} from '../routes' -import {useStores} from 'state/index' -import {makeRecordUri} from 'lib/strings/url-helpers' - -export const PostDownvotedBy = ({navIdx, visible, params}: ScreenParams) => { - const store = useStores() - const {name, rkey} = params - const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - - useEffect(() => { - if (visible) { - store.nav.setTitle(navIdx, 'Downvoted by') - store.shell.setMinimalShellMode(false) - } - }, [store, visible, navIdx]) - - return ( - <View> - <ViewHeader title="Downvoted by" /> - <PostLikedByComponent uri={uri} direction="down" /> - </View> - ) -} diff --git a/src/view/screens/PostRepostedBy.tsx b/src/view/screens/PostRepostedBy.tsx index 4be4b4b42..1a63445e5 100644 --- a/src/view/screens/PostRepostedBy.tsx +++ b/src/view/screens/PostRepostedBy.tsx @@ -1,22 +1,23 @@ -import React, {useEffect} from 'react' +import React from 'react' import {View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy' -import {ScreenParams} from '../routes' import {useStores} from 'state/index' import {makeRecordUri} from 'lib/strings/url-helpers' -export const PostRepostedBy = ({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'> +export const PostRepostedByScreen = ({route}: Props) => { const store = useStores() - const {name, rkey} = params + const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - useEffect(() => { - if (visible) { - store.nav.setTitle(navIdx, 'Reposted by') + useFocusEffect( + React.useCallback(() => { store.shell.setMinimalShellMode(false) - } - }, [store, visible, navIdx]) + }, [store]), + ) return ( <View> diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index 0b6829735..0e9feae0b 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -1,58 +1,45 @@ -import React, {useEffect, useMemo} from 'react' +import React, {useMemo} from 'react' import {StyleSheet, View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {makeRecordUri} from 'lib/strings/url-helpers' import {ViewHeader} from '../com/util/ViewHeader' import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread' import {ComposePrompt} from 'view/com/composer/Prompt' import {PostThreadViewModel} from 'state/models/post-thread-view' -import {ScreenParams} from '../routes' import {useStores} from 'state/index' import {s} from 'lib/styles' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {clamp} from 'lodash' +import {isDesktopWeb} from 'platform/detection' const SHELL_FOOTER_HEIGHT = 44 -export const PostThread = ({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'> +export const PostThreadScreen = ({route}: Props) => { const store = useStores() const safeAreaInsets = useSafeAreaInsets() - const {name, rkey} = params + const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) const view = useMemo<PostThreadViewModel>( () => new PostThreadViewModel(store, {uri}), [store, uri], ) - useEffect(() => { - let aborted = false - const threadCleanup = view.registerListeners() - const setTitle = () => { - const author = view.thread?.post.author - const niceName = author?.handle || name - store.nav.setTitle(navIdx, `Post by ${niceName}`) - } - if (!visible) { - return threadCleanup - } - setTitle() - store.shell.setMinimalShellMode(false) - if (!view.hasLoaded && !view.isLoading) { - view.setup().then( - () => { - if (!aborted) { - setTitle() - } - }, - err => { + useFocusEffect( + React.useCallback(() => { + const threadCleanup = view.registerListeners() + store.shell.setMinimalShellMode(false) + if (!view.hasLoaded && !view.isLoading) { + view.setup().catch(err => { store.log.error('Failed to fetch thread', err) - }, - ) - } - return () => { - aborted = true - threadCleanup() - } - }, [visible, store.nav, store.log, store.shell, name, navIdx, view]) + }) + } + return () => { + threadCleanup() + } + }, [store, view]), + ) const onPressReply = React.useCallback(() => { if (!view.thread) { @@ -77,15 +64,24 @@ export const PostThread = ({navIdx, visible, params}: ScreenParams) => { <View style={s.hContentRegion}> <ViewHeader title="Post" /> <View style={s.hContentRegion}> - <PostThreadComponent uri={uri} view={view} /> - </View> - <View - style={[ - styles.prompt, - {bottom: SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30)}, - ]}> - <ComposePrompt onPressCompose={onPressReply} /> + <PostThreadComponent + uri={uri} + view={view} + onPressReply={onPressReply} + /> </View> + {!isDesktopWeb && ( + <View + style={[ + styles.prompt, + { + bottom: + SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30), + }, + ]}> + <ComposePrompt onPressCompose={onPressReply} /> + </View> + )} </View> ) } diff --git a/src/view/screens/PostUpvotedBy.tsx b/src/view/screens/PostUpvotedBy.tsx index 4d6ad4114..b1690721b 100644 --- a/src/view/screens/PostUpvotedBy.tsx +++ b/src/view/screens/PostUpvotedBy.tsx @@ -1,21 +1,23 @@ -import React, {useEffect} from 'react' +import React from 'react' import {View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {PostVotedBy as PostLikedByComponent} from '../com/post-thread/PostVotedBy' -import {ScreenParams} from '../routes' import {useStores} from 'state/index' import {makeRecordUri} from 'lib/strings/url-helpers' -export const PostUpvotedBy = ({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostUpvotedBy'> +export const PostUpvotedByScreen = ({route}: Props) => { const store = useStores() - const {name, rkey} = params + const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - useEffect(() => { - if (visible) { - store.nav.setTitle(navIdx, 'Liked by') - } - }, [store, visible, navIdx]) + useFocusEffect( + React.useCallback(() => { + store.shell.setMinimalShellMode(false) + }, [store]), + ) return ( <View> diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index fa0c04106..e0d0a5884 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -1,9 +1,10 @@ import React, {useEffect, useState} from 'react' import {ActivityIndicator, StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewSelector} from '../com/util/ViewSelector' import {CenteredView} from '../com/util/Views' -import {ScreenParams} from '../routes' import {ProfileUiModel, Sections} from 'state/models/profile-ui' import {useStores} from 'state/index' import {ProfileHeader} from '../com/profile/ProfileHeader' @@ -23,7 +24,8 @@ const LOADING_ITEM = {_reactKey: '__loading__'} const END_ITEM = {_reactKey: '__end__'} const EMPTY_ITEM = {_reactKey: '__empty__'} -export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> +export const ProfileScreen = observer(({route}: Props) => { const store = useStores() const {screen, track} = useAnalytics() @@ -34,35 +36,30 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { const onMainScroll = useOnMainScroll(store) const [hasSetup, setHasSetup] = useState<boolean>(false) const uiState = React.useMemo( - () => new ProfileUiModel(store, {user: params.name}), - [params.name, store], + () => new ProfileUiModel(store, {user: route.params.name}), + [route.params.name, store], ) - useEffect(() => { - store.nav.setTitle(navIdx, params.name) - }, [store, navIdx, params.name]) - - useEffect(() => { - let aborted = false - const feedCleanup = uiState.feed.registerListeners() - if (!visible) { - return feedCleanup - } - if (hasSetup) { - uiState.update() - } else { - uiState.setup().then(() => { - if (aborted) { - return - } - setHasSetup(true) - }) - } - return () => { - aborted = true - feedCleanup() - } - }, [visible, store, hasSetup, uiState]) + useFocusEffect( + React.useCallback(() => { + let aborted = false + const feedCleanup = uiState.feed.registerListeners() + if (hasSetup) { + uiState.update() + } else { + uiState.setup().then(() => { + if (aborted) { + return + } + setHasSetup(true) + }) + } + return () => { + aborted = true + feedCleanup() + } + }, [hasSetup, uiState]), + ) // events // = @@ -171,7 +168,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { <ErrorScreen testID="profileErrorScreen" title="Failed to load profile" - message={`There was an issue when attempting to load ${params.name}`} + message={`There was an issue when attempting to load ${route.params.name}`} details={uiState.profile.error} onPressTryAgain={onPressTryAgain} /> diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx index 9f1a9c741..b248cdc3a 100644 --- a/src/view/screens/ProfileFollowers.tsx +++ b/src/view/screens/ProfileFollowers.tsx @@ -1,20 +1,21 @@ -import React, {useEffect} from 'react' +import React from 'react' import {View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers' -import {ScreenParams} from '../routes' import {useStores} from 'state/index' -export const ProfileFollowers = ({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'> +export const ProfileFollowersScreen = ({route}: Props) => { const store = useStores() - const {name} = params + const {name} = route.params - useEffect(() => { - if (visible) { - store.nav.setTitle(navIdx, `Followers of ${name}`) + useFocusEffect( + React.useCallback(() => { store.shell.setMinimalShellMode(false) - } - }, [store, visible, name, navIdx]) + }, [store]), + ) return ( <View> diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx index 1cdb5bccf..7edf8edba 100644 --- a/src/view/screens/ProfileFollows.tsx +++ b/src/view/screens/ProfileFollows.tsx @@ -1,20 +1,21 @@ -import React, {useEffect} from 'react' +import React from 'react' import {View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {ViewHeader} from '../com/util/ViewHeader' import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows' -import {ScreenParams} from '../routes' import {useStores} from 'state/index' -export const ProfileFollows = ({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'> +export const ProfileFollowsScreen = ({route}: Props) => { const store = useStores() - const {name} = params + const {name} = route.params - useEffect(() => { - if (visible) { - store.nav.setTitle(navIdx, `Followed by ${name}`) + useFocusEffect( + React.useCallback(() => { store.shell.setMinimalShellMode(false) - } - }, [store, visible, name, navIdx]) + }, [store]), + ) return ( <View> diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index a87c41e76..a50d5c6a7 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -7,12 +7,19 @@ import { TouchableWithoutFeedback, View, } from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {useFocusEffect} from '@react-navigation/native' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' import {ScrollView} from '../com/util/Views' +import { + NativeStackScreenProps, + SearchTabNavigatorParams, +} from 'lib/routes/types' import {observer} from 'mobx-react-lite' import {UserAvatar} from '../com/util/UserAvatar' import {Text} from '../com/util/text/Text' -import {ScreenParams} from '../routes' import {useStores} from 'state/index' import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view' import {s} from 'lib/styles' @@ -21,14 +28,17 @@ import {WhoToFollow} from '../com/discover/WhoToFollow' import {SuggestedPosts} from '../com/discover/SuggestedPosts' import {ProfileCard} from '../com/profile/ProfileCard' import {usePalette} from 'lib/hooks/usePalette' +import {useTheme} from 'lib/ThemeContext' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useAnalytics} from 'lib/analytics' const MENU_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} const FIVE_MIN = 5 * 60 * 1e3 -export const Search = observer(({navIdx, visible, params}: ScreenParams) => { +type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'> +export const SearchScreen = observer<Props>(({}: Props) => { const pal = usePalette('default') + const theme = useTheme() const store = useStores() const {track} = useAnalytics() const scrollElRef = React.useRef<ScrollView>(null) @@ -41,33 +51,32 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => { () => new UserAutocompleteViewModel(store), [store], ) - const {name} = params const onSoftReset = () => { scrollElRef.current?.scrollTo({x: 0, y: 0}) } - React.useEffect(() => { - const softResetSub = store.onScreenSoftReset(onSoftReset) - const cleanup = () => { - softResetSub.remove() - } + useFocusEffect( + React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) + const cleanup = () => { + softResetSub.remove() + } - if (visible) { const now = Date.now() if (now - lastRenderTime > FIVE_MIN) { setRenderTime(Date.now()) // trigger reload of suggestions } store.shell.setMinimalShellMode(false) autocompleteView.setup() - store.nav.setTitle(navIdx, 'Search') - } - return cleanup - }, [store, visible, name, navIdx, autocompleteView, lastRenderTime]) + + return cleanup + }, [store, autocompleteView, lastRenderTime, setRenderTime]), + ) const onPressMenu = () => { track('ViewHeader:MenuButtonClicked') - store.shell.setMainMenuOpen(true) + store.shell.openDrawer() } const onChangeQuery = (text: string) => { @@ -102,12 +111,7 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => { onPress={onPressMenu} hitSlop={MENU_HITSLOP} style={styles.headerMenuBtn}> - <UserAvatar - size={30} - handle={store.me.handle} - displayName={store.me.displayName} - avatar={store.me.avatar} - /> + <UserAvatar size={30} avatar={store.me.avatar} /> </TouchableOpacity> <View style={[ @@ -127,13 +131,18 @@ export const Search = observer(({navIdx, visible, params}: ScreenParams) => { returnKeyType="search" value={query} style={[pal.text, styles.headerSearchInput]} + keyboardAppearance={theme.colorScheme} onFocus={() => setIsInputFocused(true)} onBlur={() => setIsInputFocused(false)} onChangeText={onChangeQuery} /> {query ? ( <TouchableOpacity onPress={onPressClearQuery}> - <FontAwesomeIcon icon="xmark" size={16} style={pal.textLight} /> + <FontAwesomeIcon + icon="xmark" + size={16} + style={pal.textLight as FontAwesomeIconStyle} + /> </TouchableOpacity> ) : undefined} </View> diff --git a/src/view/screens/Search.web.tsx b/src/view/screens/Search.web.tsx index 886d49af7..75b5f01ce 100644 --- a/src/view/screens/Search.web.tsx +++ b/src/view/screens/Search.web.tsx @@ -1,8 +1,12 @@ import React from 'react' import {StyleSheet, View} from 'react-native' +import {useFocusEffect} from '@react-navigation/native' import {ScrollView} from '../com/util/Views' import {observer} from 'mobx-react-lite' -import {ScreenParams} from '../routes' +import { + NativeStackScreenProps, + SearchTabNavigatorParams, +} from 'lib/routes/types' import {useStores} from 'state/index' import {s} from 'lib/styles' import {WhoToFollow} from '../com/discover/WhoToFollow' @@ -12,7 +16,8 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' const FIVE_MIN = 5 * 60 * 1e3 -export const Search = observer(({navIdx, visible}: ScreenParams) => { +type Props = NativeStackScreenProps<SearchTabNavigatorParams, 'Search'> +export const SearchScreen = observer(({}: Props) => { const pal = usePalette('default') const store = useStores() const scrollElRef = React.useRef<ScrollView>(null) @@ -23,22 +28,21 @@ export const Search = observer(({navIdx, visible}: ScreenParams) => { scrollElRef.current?.scrollTo({x: 0, y: 0}) } - React.useEffect(() => { - const softResetSub = store.onScreenSoftReset(onSoftReset) - const cleanup = () => { - softResetSub.remove() - } + useFocusEffect( + React.useCallback(() => { + const softResetSub = store.onScreenSoftReset(onSoftReset) - if (visible) { const now = Date.now() if (now - lastRenderTime > FIVE_MIN) { setRenderTime(Date.now()) // trigger reload of suggestions } store.shell.setMinimalShellMode(false) - store.nav.setTitle(navIdx, 'Search') - } - return cleanup - }, [store, visible, navIdx, lastRenderTime]) + + return () => { + softResetSub.remove() + } + }, [store, lastRenderTime, setRenderTime]), + ) return ( <ScrollView diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 47e76a124..2e5d2c001 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react' +import React from 'react' import { ActivityIndicator, StyleSheet, @@ -6,13 +6,18 @@ import { View, } from 'react-native' import { + useFocusEffect, + useNavigation, + StackActions, +} from '@react-navigation/native' +import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {observer} from 'mobx-react-lite' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import * as AppInfo from 'lib/app-info' import {useStores} from 'state/index' -import {ScreenParams} from '../routes' import {s, colors} from 'lib/styles' import {ScrollView} from '../com/util/Views' import {ViewHeader} from '../com/util/ViewHeader' @@ -25,41 +30,38 @@ import {useTheme} from 'lib/ThemeContext' import {usePalette} from 'lib/hooks/usePalette' import {AccountData} from 'state/models/session' import {useAnalytics} from 'lib/analytics' +import {NavigationProp} from 'lib/routes/types' -export const Settings = observer(function Settings({ - navIdx, - visible, -}: ScreenParams) { +type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> +export const SettingsScreen = observer(function Settings({}: Props) { const theme = useTheme() const pal = usePalette('default') const store = useStores() + const navigation = useNavigation<NavigationProp>() const {screen, track} = useAnalytics() const [isSwitching, setIsSwitching] = React.useState(false) - useEffect(() => { - screen('Settings') - }, [screen]) - - useEffect(() => { - if (!visible) { - return - } - store.shell.setMinimalShellMode(false) - store.nav.setTitle(navIdx, 'Settings') - }, [visible, store, navIdx]) + useFocusEffect( + React.useCallback(() => { + screen('Settings') + store.shell.setMinimalShellMode(false) + }, [screen, store]), + ) const onPressSwitchAccount = async (acct: AccountData) => { track('Settings:SwitchAccountButtonClicked') setIsSwitching(true) if (await store.session.resumeSession(acct)) { setIsSwitching(false) - store.nav.tab.fixedTabReset() + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) Toast.show(`Signed in as ${acct.displayName || acct.handle}`) return } setIsSwitching(false) Toast.show('Sorry! We need you to enter your password.') - store.nav.tab.fixedTabReset() + navigation.navigate('HomeTab') + navigation.dispatch(StackActions.popToTop()) store.session.clear() } const onPressAddAccount = () => { @@ -118,12 +120,7 @@ export const Settings = observer(function Settings({ noFeedback> <View style={[pal.view, styles.linkCard]}> <View style={styles.avi}> - <UserAvatar - size={40} - displayName={store.me.displayName} - handle={store.me.handle || ''} - avatar={store.me.avatar} - /> + <UserAvatar size={40} avatar={store.me.avatar} /> </View> <View style={[s.flex1]}> <Text type="md-bold" style={pal.text} numberOfLines={1}> @@ -152,12 +149,7 @@ export const Settings = observer(function Settings({ isSwitching ? undefined : () => onPressSwitchAccount(account) }> <View style={styles.avi}> - <UserAvatar - size={40} - displayName={account.displayName} - handle={account.handle || ''} - avatar={account.aviUrl} - /> + <UserAvatar size={40} avatar={account.aviUrl} /> </View> <View style={[s.flex1]}> <Text type="md-bold" style={pal.text}> |