Fix infinite loading spinner when changing search terms (#9950)
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
import {memo, useCallback, useMemo, useState} from 'react'
|
import {memo, useCallback, useMemo, useState} from 'react'
|
||||||
import {ActivityIndicator, View} from 'react-native'
|
import {ActivityIndicator, View} from 'react-native'
|
||||||
import {type AppBskyFeedDefs} from '@atproto/api'
|
import {type AppBskyFeedDefs} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import {urls} from '#/lib/constants'
|
import {urls} from '#/lib/constants'
|
||||||
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
||||||
@@ -27,6 +25,7 @@ import {InlineLinkText} from '#/components/Link'
|
|||||||
import {ListFooter} from '#/components/Lists'
|
import {ListFooter} from '#/components/Lists'
|
||||||
import {SearchError} from '#/components/SearchError'
|
import {SearchError} from '#/components/SearchError'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
let SearchResults = ({
|
let SearchResults = ({
|
||||||
query,
|
query,
|
||||||
@@ -43,14 +42,14 @@ let SearchResults = ({
|
|||||||
headerHeight: number
|
headerHeight: number
|
||||||
initialPage?: number
|
initialPage?: number
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
const sections = useMemo(() => {
|
const sections = useMemo(() => {
|
||||||
if (!queryWithParams) return []
|
if (!queryWithParams) return []
|
||||||
const noParams = queryWithParams === query
|
const noParams = queryWithParams === query
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: _(msg`Top`),
|
title: l`Top`,
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenPostResults
|
<SearchScreenPostResults
|
||||||
query={queryWithParams}
|
query={queryWithParams}
|
||||||
@@ -60,7 +59,7 @@ let SearchResults = ({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: _(msg`Latest`),
|
title: l`Latest`,
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenPostResults
|
<SearchScreenPostResults
|
||||||
query={queryWithParams}
|
query={queryWithParams}
|
||||||
@@ -70,13 +69,13 @@ let SearchResults = ({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
noParams && {
|
noParams && {
|
||||||
title: _(msg`People`),
|
title: l`People`,
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
noParams && {
|
noParams && {
|
||||||
title: _(msg`Feeds`),
|
title: l`Feeds`,
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenFeedsResults query={query} active={activeTab === 3} />
|
<SearchScreenFeedsResults query={query} active={activeTab === 3} />
|
||||||
),
|
),
|
||||||
@@ -85,7 +84,10 @@ let SearchResults = ({
|
|||||||
title: string
|
title: string
|
||||||
component: React.ReactNode
|
component: React.ReactNode
|
||||||
}[]
|
}[]
|
||||||
}, [_, query, queryWithParams, activeTab])
|
}, [l, query, queryWithParams, activeTab])
|
||||||
|
|
||||||
|
// There may be fewer tabs after changing the search options.
|
||||||
|
const selectedPage = initialPage > sections.length - 1 ? 0 : initialPage
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pager
|
<Pager
|
||||||
@@ -95,7 +97,7 @@ let SearchResults = ({
|
|||||||
<TabBar items={sections.map(section => section.title)} {...props} />
|
<TabBar items={sections.map(section => section.title)} {...props} />
|
||||||
</Layout.Center>
|
</Layout.Center>
|
||||||
)}
|
)}
|
||||||
initialPage={initialPage}>
|
initialPage={selectedPage}>
|
||||||
{sections.map((section, i) => (
|
{sections.map((section, i) => (
|
||||||
<View key={i}>{section.component}</View>
|
<View key={i}>{section.component}</View>
|
||||||
))}
|
))}
|
||||||
@@ -161,15 +163,15 @@ function EmptyState({
|
|||||||
|
|
||||||
function NoResultsText({query}: {query: string}) {
|
function NoResultsText({query}: {query: string}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={[a.text_lg, t.atoms.text_contrast_high]}>
|
<Text style={[a.text_lg, t.atoms.text_contrast_high]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
No results found for "
|
No results found for “
|
||||||
<Text style={[a.text_lg, t.atoms.text, a.font_medium]}>{query}</Text>
|
<Text style={[a.text_lg, t.atoms.text, a.font_medium]}>{query}</Text>
|
||||||
".
|
”.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
{'\n\n'}
|
{'\n\n'}
|
||||||
@@ -177,12 +179,10 @@ function NoResultsText({query}: {query: string}) {
|
|||||||
<Trans context="english-only-resource">
|
<Trans context="english-only-resource">
|
||||||
Try a different search term, or{' '}
|
Try a different search term, or{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
label={_(
|
label={l({
|
||||||
msg({
|
message: 'read about how to use search filters',
|
||||||
message: 'read about how to use search filters',
|
context: 'english-only-resource',
|
||||||
context: 'english-only-resource',
|
})}
|
||||||
}),
|
|
||||||
)}
|
|
||||||
to={urls.website.blog.searchTipsAndTricks}
|
to={urls.website.blog.searchTipsAndTricks}
|
||||||
style={[a.text_md, a.leading_snug]}>
|
style={[a.text_md, a.leading_snug]}>
|
||||||
read about how to use search filters
|
read about how to use search filters
|
||||||
@@ -214,7 +214,7 @@ let SearchScreenPostResults = ({
|
|||||||
sort?: 'top' | 'latest'
|
sort?: 'top' | 'latest'
|
||||||
active: boolean
|
active: boolean
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {currentAccount, hasSession} = useSession()
|
const {currentAccount, hasSession} = useSession()
|
||||||
const [isPTR, setIsPTR] = useState(false)
|
const [isPTR, setIsPTR] = useState(false)
|
||||||
const trackPostView = usePostViewTracking('SearchResults')
|
const trackPostView = usePostViewTracking('SearchResults')
|
||||||
@@ -242,7 +242,7 @@ let SearchScreenPostResults = ({
|
|||||||
}, [setIsPTR, refetch])
|
}, [setIsPTR, refetch])
|
||||||
const onEndReached = useCallback(() => {
|
const onEndReached = useCallback(() => {
|
||||||
if (isFetching || !hasNextPage || error) return
|
if (isFetching || !hasNextPage || error) return
|
||||||
fetchNextPage()
|
void fetchNextPage()
|
||||||
}, [isFetching, error, hasNextPage, fetchNextPage])
|
}, [isFetching, error, hasNextPage, fetchNextPage])
|
||||||
|
|
||||||
const posts = useMemo(() => {
|
const posts = useMemo(() => {
|
||||||
@@ -289,19 +289,15 @@ let SearchScreenPostResults = ({
|
|||||||
|
|
||||||
if (!hasSession) {
|
if (!hasSession) {
|
||||||
return (
|
return (
|
||||||
<SearchError
|
<SearchError title={l`Search is currently unavailable when logged out`}>
|
||||||
title={_(msg`Search is currently unavailable when logged out`)}>
|
|
||||||
<Text style={[a.text_md, a.text_center, a.leading_snug]}>
|
<Text style={[a.text_md, a.text_center, a.leading_snug]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
<InlineLinkText
|
<InlineLinkText label={l`Sign in`} to={'#'} onPress={showSignIn}>
|
||||||
label={_(msg`Sign in`)}
|
|
||||||
to={'#'}
|
|
||||||
onPress={showSignIn}>
|
|
||||||
Sign in
|
Sign in
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<Text style={t.atoms.text_contrast_medium}> or </Text>
|
<Text style={t.atoms.text_contrast_medium}> or </Text>
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
label={_(msg`Create an account`)}
|
label={l`Create an account`}
|
||||||
to={'#'}
|
to={'#'}
|
||||||
onPress={showCreateAccount}>
|
onPress={showCreateAccount}>
|
||||||
create an account
|
create an account
|
||||||
@@ -319,9 +315,7 @@ let SearchScreenPostResults = ({
|
|||||||
|
|
||||||
return error ? (
|
return error ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
messageText={_(
|
messageText={l`We're sorry, but your search could not be completed. Please try again in a few minutes.`}
|
||||||
msg`We're sorry, but your search could not be completed. Please try again in a few minutes.`,
|
|
||||||
)}
|
|
||||||
error={cleanError(error)}
|
error={cleanError(error)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -331,18 +325,20 @@ let SearchScreenPostResults = ({
|
|||||||
{posts.length ? (
|
{posts.length ? (
|
||||||
<List
|
<List
|
||||||
data={items}
|
data={items}
|
||||||
renderItem={({item}) => {
|
renderItem={({item}: {item: SearchResultSlice}) => {
|
||||||
if (item.type === 'post') {
|
if (item.type === 'post') {
|
||||||
return <Post post={item.post} />
|
return <Post post={item.post} />
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
keyExtractor={item => item.key}
|
keyExtractor={(item: SearchResultSlice) => item.key}
|
||||||
refreshing={isPTR}
|
refreshing={isPTR}
|
||||||
onRefresh={onPullToRefresh}
|
onRefresh={() => {
|
||||||
|
void onPullToRefresh()
|
||||||
|
}}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onItemSeen={item => {
|
onItemSeen={(item: SearchResultSlice) => {
|
||||||
if (item.type === 'post') {
|
if (item.type === 'post') {
|
||||||
trackPostView(item.post)
|
trackPostView(item.post)
|
||||||
}
|
}
|
||||||
@@ -374,7 +370,7 @@ let SearchScreenUserResults = ({
|
|||||||
query: string
|
query: string
|
||||||
active: boolean
|
active: boolean
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const [isPTR, setIsPTR] = useState(false)
|
const [isPTR, setIsPTR] = useState(false)
|
||||||
|
|
||||||
@@ -400,7 +396,7 @@ let SearchScreenUserResults = ({
|
|||||||
const onEndReached = useCallback(() => {
|
const onEndReached = useCallback(() => {
|
||||||
if (!hasSession) return
|
if (!hasSession) return
|
||||||
if (isFetching || !hasNextPage || error) return
|
if (isFetching || !hasNextPage || error) return
|
||||||
fetchNextPage()
|
void fetchNextPage()
|
||||||
}, [isFetching, error, hasNextPage, fetchNextPage, hasSession])
|
}, [isFetching, error, hasNextPage, fetchNextPage, hasSession])
|
||||||
|
|
||||||
const profiles = useMemo(() => {
|
const profiles = useMemo(() => {
|
||||||
@@ -410,9 +406,7 @@ let SearchScreenUserResults = ({
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
messageText={_(
|
messageText={l`We’re sorry, but your search could not be completed. Please try again in a few minutes.`}
|
||||||
msg`We're sorry, but your search could not be completed. Please try again in a few minutes.`,
|
|
||||||
)}
|
|
||||||
error={error.toString()}
|
error={error.toString()}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -423,10 +417,12 @@ let SearchScreenUserResults = ({
|
|||||||
{profiles.length ? (
|
{profiles.length ? (
|
||||||
<List
|
<List
|
||||||
data={profiles}
|
data={profiles}
|
||||||
renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} />}
|
renderItem={({item}: {item: bsky.profile.AnyProfileView}) => (
|
||||||
keyExtractor={item => item.did}
|
<ProfileCardWithFollowBtn profile={item} />
|
||||||
|
)}
|
||||||
|
keyExtractor={(item: bsky.profile.AnyProfileView) => item.did}
|
||||||
refreshing={isPTR}
|
refreshing={isPTR}
|
||||||
onRefresh={onPullToRefresh}
|
onRefresh={() => void onPullToRefresh()}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
@@ -465,7 +461,7 @@ let SearchScreenFeedsResults = ({
|
|||||||
{results.length ? (
|
{results.length ? (
|
||||||
<List
|
<List
|
||||||
data={results}
|
data={results}
|
||||||
renderItem={({item}) => (
|
renderItem={({item}: {item: AppBskyFeedDefs.GeneratorView}) => (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.border_t,
|
a.border_t,
|
||||||
@@ -476,7 +472,7 @@ let SearchScreenFeedsResults = ({
|
|||||||
<FeedCard.Default view={item} />
|
<FeedCard.Default view={item} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
keyExtractor={item => item.uri}
|
keyExtractor={(item: AppBskyFeedDefs.GeneratorView) => item.uri}
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
ListFooterComponent={<ListFooter />}
|
ListFooterComponent={<ListFooter />}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
type ViewStyle,
|
type ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation, useRoute} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
@@ -49,6 +47,23 @@ import {SearchLanguageDropdown} from './components/SearchLanguageDropdown'
|
|||||||
import {Explore} from './Explore'
|
import {Explore} from './Explore'
|
||||||
import {SearchResults} from './SearchResults'
|
import {SearchResults} from './SearchResults'
|
||||||
|
|
||||||
|
type TabParam = 'user' | 'profile' | 'feed' | 'latest'
|
||||||
|
|
||||||
|
// Map tab parameter to tab index
|
||||||
|
function getTabIndex(tabParam?: TabParam) {
|
||||||
|
switch (tabParam) {
|
||||||
|
case 'feed':
|
||||||
|
return 3 // Feeds tab
|
||||||
|
case 'user':
|
||||||
|
case 'profile':
|
||||||
|
return 2 // People tab
|
||||||
|
case 'latest':
|
||||||
|
return 1 // Latest tab
|
||||||
|
default:
|
||||||
|
return 0 // Top tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function SearchScreenShell({
|
export function SearchScreenShell({
|
||||||
queryParam,
|
queryParam,
|
||||||
testID,
|
testID,
|
||||||
@@ -69,11 +84,15 @@ export function SearchScreenShell({
|
|||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const textInput = useRef<TextInput>(null)
|
const textInput = useRef<TextInput>(null)
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
// Get tab parameter from route params
|
||||||
|
const tabParam = (route.params as {q?: string; tab?: TabParam})?.tab
|
||||||
|
const [activeTab, setActiveTab] = useState(() => getTabIndex(tabParam))
|
||||||
|
|
||||||
// Query terms
|
// Query terms
|
||||||
const [searchText, setSearchText] = useState<string>(queryParam)
|
const [searchText, setSearchText] = useState<string>(queryParam)
|
||||||
const {data: autocompleteData, isFetching: isAutocompleteFetching} =
|
const {data: autocompleteData, isFetching: isAutocompleteFetching} =
|
||||||
@@ -96,7 +115,7 @@ export function SearchScreenShell({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updateSearchHistory = useCallback(
|
const updateSearchHistory = useCallback(
|
||||||
async (item: string) => {
|
(item: string) => {
|
||||||
if (!item) return
|
if (!item) return
|
||||||
const newSearchHistory = [
|
const newSearchHistory = [
|
||||||
item,
|
item,
|
||||||
@@ -108,7 +127,7 @@ export function SearchScreenShell({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const updateProfileHistory = useCallback(
|
const updateProfileHistory = useCallback(
|
||||||
async (item: bsky.profile.AnyProfileView) => {
|
(item: bsky.profile.AnyProfileView) => {
|
||||||
const newAccountHistory = [
|
const newAccountHistory = [
|
||||||
item.did,
|
item.did,
|
||||||
...accountHistory.filter(p => p !== item.did),
|
...accountHistory.filter(p => p !== item.did),
|
||||||
@@ -119,13 +138,13 @@ export function SearchScreenShell({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const deleteSearchHistoryItem = useCallback(
|
const deleteSearchHistoryItem = useCallback(
|
||||||
async (item: string) => {
|
(item: string) => {
|
||||||
setTermHistory(termHistory.filter(search => search !== item))
|
setTermHistory(termHistory.filter(search => search !== item))
|
||||||
},
|
},
|
||||||
[termHistory, setTermHistory],
|
[termHistory, setTermHistory],
|
||||||
)
|
)
|
||||||
const deleteProfileHistoryItem = useCallback(
|
const deleteProfileHistoryItem = useCallback(
|
||||||
async (item: bsky.profile.AnyProfileView) => {
|
(item: bsky.profile.AnyProfileView) => {
|
||||||
setAccountHistory(accountHistory.filter(p => p !== item.did))
|
setAccountHistory(accountHistory.filter(p => p !== item.did))
|
||||||
},
|
},
|
||||||
[accountHistory, setAccountHistory],
|
[accountHistory, setAccountHistory],
|
||||||
@@ -162,7 +181,7 @@ export function SearchScreenShell({
|
|||||||
textInput.current?.focus()
|
textInput.current?.focus()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onChangeText = useCallback(async (text: string) => {
|
const onChangeText = useCallback((text: string) => {
|
||||||
scrollToTopWeb()
|
scrollToTopWeb()
|
||||||
setSearchText(text)
|
setSearchText(text)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -277,7 +296,7 @@ export function SearchScreenShell({
|
|||||||
}, [setShowAutocomplete])
|
}, [setShowAutocomplete])
|
||||||
|
|
||||||
const focusSearchInput = useCallback(
|
const focusSearchInput = useCallback(
|
||||||
(tab?: 'user' | 'profile' | 'feed') => {
|
(tab?: TabParam) => {
|
||||||
textInput.current?.focus()
|
textInput.current?.focus()
|
||||||
|
|
||||||
// If a tab is specified, set the tab parameter
|
// If a tab is specified, set the tab parameter
|
||||||
@@ -350,15 +369,14 @@ export function SearchScreenShell({
|
|||||||
onClearText={onPressClearQuery}
|
onClearText={onPressClearQuery}
|
||||||
onSubmitEditing={onSubmit}
|
onSubmitEditing={onSubmit}
|
||||||
placeholder={
|
placeholder={
|
||||||
inputPlaceholder ??
|
inputPlaceholder ?? l`Search for posts, users, or feeds`
|
||||||
_(msg`Search for posts, users, or feeds`)
|
|
||||||
}
|
}
|
||||||
hitSlop={{...HITSLOP_20, top: 0}}
|
hitSlop={{...HITSLOP_20, top: 0}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{showAutocomplete && (
|
{showAutocomplete && (
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Cancel search`)}
|
label={l`Cancel search`}
|
||||||
size="large"
|
size="large"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -423,6 +441,9 @@ export function SearchScreenShell({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
}}>
|
}}>
|
||||||
<SearchScreenInner
|
<SearchScreenInner
|
||||||
|
key={params.lang}
|
||||||
|
activeTab={activeTab}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
query={query}
|
query={query}
|
||||||
queryWithParams={queryWithParams}
|
queryWithParams={queryWithParams}
|
||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
@@ -434,57 +455,31 @@ export function SearchScreenShell({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let SearchScreenInner = ({
|
let SearchScreenInner = ({
|
||||||
|
activeTab,
|
||||||
|
setActiveTab,
|
||||||
query,
|
query,
|
||||||
queryWithParams,
|
queryWithParams,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
focusSearchInput,
|
focusSearchInput,
|
||||||
}: {
|
}: {
|
||||||
|
activeTab: number
|
||||||
|
setActiveTab: React.Dispatch<React.SetStateAction<number>>
|
||||||
query: string
|
query: string
|
||||||
queryWithParams: string
|
queryWithParams: string
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
focusSearchInput: (tab?: 'user' | 'profile' | 'feed') => void
|
focusSearchInput: (tab?: TabParam) => void
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {gtTablet} = useBreakpoints()
|
const {gtTablet} = useBreakpoints()
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
// Get tab parameter from route params
|
|
||||||
const tabParam = (
|
|
||||||
route.params as {q?: string; tab?: 'user' | 'profile' | 'feed'}
|
|
||||||
)?.tab
|
|
||||||
|
|
||||||
// Map tab parameter to tab index
|
|
||||||
const getInitialTabIndex = useCallback(() => {
|
|
||||||
if (!tabParam) return 0
|
|
||||||
switch (tabParam) {
|
|
||||||
case 'user':
|
|
||||||
case 'profile':
|
|
||||||
return 2 // People tab
|
|
||||||
case 'feed':
|
|
||||||
return 3 // Feeds tab
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}, [tabParam])
|
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(getInitialTabIndex())
|
|
||||||
|
|
||||||
// Update activeTab when tabParam changes
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const newTabIndex = getInitialTabIndex()
|
|
||||||
if (newTabIndex !== activeTab) {
|
|
||||||
setActiveTab(newTabIndex)
|
|
||||||
}
|
|
||||||
}, [tabParam, activeTab, getInitialTabIndex])
|
|
||||||
|
|
||||||
const onPageSelected = useCallback(
|
const onPageSelected = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
setMinimalShellMode(false)
|
setMinimalShellMode(false)
|
||||||
setActiveTab(index)
|
setActiveTab(index)
|
||||||
},
|
},
|
||||||
[setMinimalShellMode],
|
[setActiveTab, setMinimalShellMode],
|
||||||
)
|
)
|
||||||
|
|
||||||
return queryWithParams ? (
|
return queryWithParams ? (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type AppBskyActorDefs} from '@atproto/api'
|
|
||||||
|
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
|
import type * as bsky from '#/types/bsky'
|
||||||
|
|
||||||
export function ProfileCardWithFollowBtn({
|
export function ProfileCardWithFollowBtn({
|
||||||
profile,
|
profile,
|
||||||
@@ -12,7 +12,7 @@ export function ProfileCardWithFollowBtn({
|
|||||||
position,
|
position,
|
||||||
contextProfileDid,
|
contextProfileDid,
|
||||||
}: {
|
}: {
|
||||||
profile: AppBskyActorDefs.ProfileView
|
profile: bsky.profile.AnyProfileView
|
||||||
noBorder?: boolean
|
noBorder?: boolean
|
||||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||||
position?: number
|
position?: number
|
||||||
|
|||||||
Reference in New Issue
Block a user