Add lang dropdown
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {ScrollView as RNGHScrollView} from 'react-native-gesture-handler'
|
import {ScrollView as RNGHScrollView} from 'react-native-gesture-handler'
|
||||||
|
import RNPickerSelect from 'react-native-picker-select'
|
||||||
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
|
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
|
||||||
import {
|
import {
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
@@ -21,6 +22,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {LANGUAGES} from '#/lib/../locale/languages'
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {createHitslop} from '#/lib/constants'
|
import {createHitslop} from '#/lib/constants'
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
import {HITSLOP_10} from '#/lib/constants'
|
||||||
@@ -38,6 +40,7 @@ import {augmentSearchQuery} from '#/lib/strings/helpers'
|
|||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {listenSoftReset} from '#/state/events'
|
import {listenSoftReset} from '#/state/events'
|
||||||
|
import {useLanguagePrefs} from '#/state/preferences/languages'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||||
import {useActorSearch} from '#/state/queries/actor-search'
|
import {useActorSearch} from '#/state/queries/actor-search'
|
||||||
@@ -56,13 +59,15 @@ import {Text} from '#/view/com/util/text/Text'
|
|||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||||
import {Explore} from '#/view/screens/Search/Explore'
|
import {Explore} from '#/view/screens/Search/Explore'
|
||||||
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
||||||
import {atoms as a, useBreakpoints,useTheme as useThemeNew, web} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme as useThemeNew, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import * as FeedCard from '#/components/FeedCard'
|
import * as FeedCard from '#/components/FeedCard'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||||
|
import {Text as NewText} from '#/components/Typography'
|
||||||
|
|
||||||
const HEADER_HEIGHT = 56
|
const HEADER_HEIGHT = 56
|
||||||
|
|
||||||
@@ -256,7 +261,7 @@ let SearchScreenUserResults = ({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const {data: results, isFetched} = useActorSearch({
|
const {data: results, isFetched} = useActorSearch({
|
||||||
query: query,
|
query: sanitizeLangFromQuery(query),
|
||||||
enabled: active,
|
enabled: active,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -294,7 +299,7 @@ let SearchScreenFeedsResults = ({
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const {data: results, isFetched} = usePopularFeedsSearch({
|
const {data: results, isFetched} = usePopularFeedsSearch({
|
||||||
query,
|
query: sanitizeLangFromQuery(query),
|
||||||
enabled: active,
|
enabled: active,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -329,7 +334,83 @@ let SearchScreenFeedsResults = ({
|
|||||||
}
|
}
|
||||||
SearchScreenFeedsResults = React.memo(SearchScreenFeedsResults)
|
SearchScreenFeedsResults = React.memo(SearchScreenFeedsResults)
|
||||||
|
|
||||||
|
function SearchLanguageDropdown({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: string
|
||||||
|
onChange(value: string): void
|
||||||
|
}) {
|
||||||
|
const t = useThemeNew()
|
||||||
|
const {contentLanguages} = useLanguagePrefs()
|
||||||
|
|
||||||
|
const items = React.useMemo(() => {
|
||||||
|
return LANGUAGES.filter(l => Boolean(l.code2))
|
||||||
|
.map(l => ({
|
||||||
|
label: l.name,
|
||||||
|
inputLabel: l.name,
|
||||||
|
value: l.code2,
|
||||||
|
key: l.code2 + l.code3,
|
||||||
|
}))
|
||||||
|
.sort(a => (contentLanguages.includes(a.value) ? -1 : 1))
|
||||||
|
}, [contentLanguages])
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||||
|
color: t.atoms.text.color,
|
||||||
|
fontSize: a.text_xs.fontSize,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
fontWeight: a.font_bold.fontWeight,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingRight: 32,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: a.rounded_full.borderRadius,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RNPickerSelect
|
||||||
|
placeholder={{}}
|
||||||
|
value={value}
|
||||||
|
onValueChange={onChange}
|
||||||
|
items={items}
|
||||||
|
Icon={() => (
|
||||||
|
<ChevronDown fill={t.atoms.text_contrast_low.color} size="sm" />
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
iconContainer: {
|
||||||
|
pointerEvents: 'none',
|
||||||
|
right: a.px_sm.paddingRight,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
inputAndroid: {
|
||||||
|
...style,
|
||||||
|
},
|
||||||
|
inputIOS: {
|
||||||
|
...style,
|
||||||
|
},
|
||||||
|
inputWeb: web({
|
||||||
|
...style,
|
||||||
|
cursor: 'pointer',
|
||||||
|
// @ts-ignore web only
|
||||||
|
'-moz-appearance': 'none',
|
||||||
|
'-webkit-appearance': 'none',
|
||||||
|
appearance: 'none',
|
||||||
|
outline: 0,
|
||||||
|
borderWidth: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
||||||
|
const t = useThemeNew()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
|
const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled()
|
||||||
@@ -337,6 +418,14 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
|||||||
const {isDesktop} = useWebMediaQueries()
|
const {isDesktop} = useWebMediaQueries()
|
||||||
const [activeTab, setActiveTab] = React.useState(0)
|
const [activeTab, setActiveTab] = React.useState(0)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
|
const {contentLanguages} = useLanguagePrefs()
|
||||||
|
const [language, setLanguage] = React.useState(
|
||||||
|
parseLanguageFromQuery(query || '') || contentLanguages[0],
|
||||||
|
)
|
||||||
|
const queryWithoutLang = sanitizeLangFromQuery(query || '')
|
||||||
|
const queryWithLang = queryWithoutLang + ` lang:${language}`
|
||||||
|
|
||||||
const onPageSelected = React.useCallback(
|
const onPageSelected = React.useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
@@ -348,13 +437,13 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sections = React.useMemo(() => {
|
const sections = React.useMemo(() => {
|
||||||
if (!query) return []
|
if (!queryWithoutLang) return []
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: _(msg`Top`),
|
title: _(msg`Top`),
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenPostResults
|
<SearchScreenPostResults
|
||||||
query={query}
|
query={queryWithLang}
|
||||||
sort="top"
|
sort="top"
|
||||||
active={activeTab === 0}
|
active={activeTab === 0}
|
||||||
/>
|
/>
|
||||||
@@ -364,7 +453,7 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
|||||||
title: _(msg`Latest`),
|
title: _(msg`Latest`),
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenPostResults
|
<SearchScreenPostResults
|
||||||
query={query}
|
query={queryWithLang}
|
||||||
sort="latest"
|
sort="latest"
|
||||||
active={activeTab === 1}
|
active={activeTab === 1}
|
||||||
/>
|
/>
|
||||||
@@ -373,33 +462,59 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => {
|
|||||||
{
|
{
|
||||||
title: _(msg`People`),
|
title: _(msg`People`),
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
<SearchScreenUserResults
|
||||||
|
query={queryWithoutLang}
|
||||||
|
active={activeTab === 2}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: _(msg`Feeds`),
|
title: _(msg`Feeds`),
|
||||||
component: (
|
component: (
|
||||||
<SearchScreenFeedsResults query={query} active={activeTab === 3} />
|
<SearchScreenFeedsResults
|
||||||
|
query={queryWithoutLang}
|
||||||
|
active={activeTab === 3}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [_, query, activeTab])
|
}, [_, queryWithoutLang, queryWithLang, activeTab])
|
||||||
|
|
||||||
return query ? (
|
return queryWithoutLang ? (
|
||||||
<Pager
|
<>
|
||||||
onPageSelected={onPageSelected}
|
<CenteredView
|
||||||
renderTabBar={props => (
|
sideBorders={gtMobile}
|
||||||
<CenteredView
|
style={[
|
||||||
sideBorders
|
isWeb && a.pt_sm,
|
||||||
style={[pal.border, pal.view, styles.tabBarContainer]}>
|
isNative && a.pb_xs,
|
||||||
<TabBar items={sections.map(section => section.title)} {...props} />
|
a.px_md,
|
||||||
</CenteredView>
|
t.atoms.border_contrast_low,
|
||||||
)}
|
]}>
|
||||||
initialPage={0}>
|
<View style={[a.flex_row, a.justify_end, a.align_center, a.gap_sm]}>
|
||||||
{sections.map((section, i) => (
|
<NewText style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||||
<View key={i}>{section.component}</View>
|
<Trans>Language:</Trans>
|
||||||
))}
|
</NewText>
|
||||||
</Pager>
|
<View style={[{width: 120}]}>
|
||||||
|
<SearchLanguageDropdown value={language} onChange={setLanguage} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</CenteredView>
|
||||||
|
|
||||||
|
<Pager
|
||||||
|
onPageSelected={onPageSelected}
|
||||||
|
renderTabBar={props => (
|
||||||
|
<CenteredView
|
||||||
|
sideBorders
|
||||||
|
style={[pal.border, pal.view, styles.tabBarContainer]}>
|
||||||
|
<TabBar items={sections.map(section => section.title)} {...props} />
|
||||||
|
</CenteredView>
|
||||||
|
)}
|
||||||
|
initialPage={0}>
|
||||||
|
{sections.map((section, i) => (
|
||||||
|
<View key={i}>{section.component}</View>
|
||||||
|
))}
|
||||||
|
</Pager>
|
||||||
|
</>
|
||||||
) : hasSession ? (
|
) : hasSession ? (
|
||||||
<Explore />
|
<Explore />
|
||||||
) : (
|
) : (
|
||||||
@@ -1036,6 +1151,18 @@ function scrollToTopWeb() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeLangFromQuery(query: string) {
|
||||||
|
return query.replace(/lang:[a-z-]+/gi, '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLanguageFromQuery(query: string) {
|
||||||
|
const match = query.match(/lang:[a-z-]+/gi)
|
||||||
|
if (match && match[0]) {
|
||||||
|
return match[0].replace('lang:', '').split('-')[0]
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
header: {
|
header: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
Reference in New Issue
Block a user