From bf37cc09144d0d8c6be83ce66895a3d14db9e6a3 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 23 Sep 2024 18:06:47 -0500 Subject: [PATCH] Add lang dropdown --- src/view/screens/Search/Search.tsx | 175 +++++++++++++++++++++++++---- 1 file changed, 151 insertions(+), 24 deletions(-) diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 8cbb973943..e954fd1c14 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -11,6 +11,7 @@ import { View, } from 'react-native' import {ScrollView as RNGHScrollView} from 'react-native-gesture-handler' +import RNPickerSelect from 'react-native-picker-select' import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api' import { FontAwesomeIcon, @@ -21,6 +22,7 @@ import {useLingui} from '@lingui/react' import AsyncStorage from '@react-native-async-storage/async-storage' import {useFocusEffect, useNavigation} from '@react-navigation/native' +import {LANGUAGES} from '#/lib/../locale/languages' import {useAnalytics} from '#/lib/analytics/analytics' import {createHitslop} from '#/lib/constants' import {HITSLOP_10} from '#/lib/constants' @@ -38,6 +40,7 @@ import {augmentSearchQuery} from '#/lib/strings/helpers' import {logger} from '#/logger' import {isNative, isWeb} from '#/platform/detection' import {listenSoftReset} from '#/state/events' +import {useLanguagePrefs} from '#/state/preferences/languages' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 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 {Explore} from '#/view/screens/Search/Explore' 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 * as FeedCard from '#/components/FeedCard' 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 {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' +import {Text as NewText} from '#/components/Typography' const HEADER_HEIGHT = 56 @@ -256,7 +261,7 @@ let SearchScreenUserResults = ({ const {_} = useLingui() const {data: results, isFetched} = useActorSearch({ - query: query, + query: sanitizeLangFromQuery(query), enabled: active, }) @@ -294,7 +299,7 @@ let SearchScreenFeedsResults = ({ const {_} = useLingui() const {data: results, isFetched} = usePopularFeedsSearch({ - query, + query: sanitizeLangFromQuery(query), enabled: active, }) @@ -329,7 +334,83 @@ let 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 ( + ( + + )} + 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 => { + const t = useThemeNew() const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() @@ -337,6 +418,14 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { const {isDesktop} = useWebMediaQueries() const [activeTab, setActiveTab] = React.useState(0) 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( (index: number) => { @@ -348,13 +437,13 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { ) const sections = React.useMemo(() => { - if (!query) return [] + if (!queryWithoutLang) return [] return [ { title: _(msg`Top`), component: ( @@ -364,7 +453,7 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { title: _(msg`Latest`), component: ( @@ -373,33 +462,59 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { { title: _(msg`People`), component: ( - + ), }, { title: _(msg`Feeds`), component: ( - + ), }, ] - }, [_, query, activeTab]) + }, [_, queryWithoutLang, queryWithLang, activeTab]) - return query ? ( - ( - - section.title)} {...props} /> - - )} - initialPage={0}> - {sections.map((section, i) => ( - {section.component} - ))} - + return queryWithoutLang ? ( + <> + + + + Language: + + + + + + + + ( + + section.title)} {...props} /> + + )} + initialPage={0}> + {sections.map((section, i) => ( + {section.component} + ))} + + ) : hasSession ? ( ) : ( @@ -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({ header: { flexDirection: 'row',