From b1ca2503de55c41431aac38db4d164da7d506d4f Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 25 Sep 2024 15:54:43 -0500 Subject: [PATCH 1/9] Add language filtering UI to search (#5459) * Use new TextField for search bar * Add lang dropdown * Dialog * Revert "Dialog" This reverts commit 257573cd9c2a70d29df4ef5bdd503eea4ae411fe. * Extract util, test, cleanup * Fix formatting * Pass through other params * Fix sticky header * Fix stale data, hide/show * Improve query parsing * Replace memo * Couple tweaks * Revert cancel change * Remove unused placeholder --- src/alf/themes.ts | 3 + src/alf/types.ts | 1 + src/components/forms/TextField.tsx | 23 +- src/screens/Search/__tests__/utils.test.ts | 43 ++ src/screens/Search/utils.ts | 43 ++ src/view/screens/Search/Search.tsx | 461 ++++++++++++++------- src/view/screens/Storybook/Forms.tsx | 2 +- 7 files changed, 427 insertions(+), 149 deletions(-) create mode 100644 src/screens/Search/__tests__/utils.test.ts create mode 100644 src/screens/Search/utils.ts diff --git a/src/alf/themes.ts b/src/alf/themes.ts index 9f7ec5c673..0cfe09aadc 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -305,6 +305,7 @@ export function createThemes({ } as const const light: Theme = { + scheme: 'light', name: 'light', palette: lightPalette, atoms: { @@ -390,6 +391,7 @@ export function createThemes({ } const dark: Theme = { + scheme: 'dark', name: 'dark', palette: darkPalette, atoms: { @@ -479,6 +481,7 @@ export function createThemes({ const dim: Theme = { ...dark, + scheme: 'dark', name: 'dim', palette: dimPalette, atoms: { diff --git a/src/alf/types.ts b/src/alf/types.ts index 41822b8dd5..08ec593927 100644 --- a/src/alf/types.ts +++ b/src/alf/types.ts @@ -156,6 +156,7 @@ export type ThemedAtoms = { } } export type Theme = { + scheme: 'light' | 'dark' // for library support name: ThemeName palette: Palette atoms: ThemedAtoms diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx index 94ee261e38..21928d3df3 100644 --- a/src/components/forms/TextField.tsx +++ b/src/components/forms/TextField.tsx @@ -135,6 +135,8 @@ export function createInput(Component: typeof TextInput) { placeholder, value, onChangeText, + onFocus, + onBlur, isInvalid, inputRef, style, @@ -173,8 +175,14 @@ export function createInput(Component: typeof TextInput) { ref={refs} value={value} onChangeText={onChangeText} - onFocus={ctx.onFocus} - onBlur={ctx.onBlur} + onFocus={e => { + ctx.onFocus() + onFocus?.(e) + }} + onBlur={e => { + ctx.onBlur() + onBlur?.(e) + }} placeholder={placeholder || label} placeholderTextColor={t.palette.contrast_500} keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} @@ -188,8 +196,8 @@ export function createInput(Component: typeof TextInput) { a.px_xs, { // paddingVertical doesn't work w/multiline - esb - paddingTop: 14, - paddingBottom: 14, + paddingTop: 12, + paddingBottom: 13, lineHeight: a.text_md.fontSize * 1.1875, textAlignVertical: rest.multiline ? 'top' : undefined, minHeight: rest.multiline ? 80 : undefined, @@ -197,13 +205,14 @@ export function createInput(Component: typeof TextInput) { }, // fix for autofill styles covering border web({ - paddingTop: 12, - paddingBottom: 12, + paddingTop: 10, + paddingBottom: 11, marginTop: 2, marginBottom: 2, }), android({ - paddingBottom: 16, + paddingTop: 8, + paddingBottom: 8, }), style, ]} diff --git a/src/screens/Search/__tests__/utils.test.ts b/src/screens/Search/__tests__/utils.test.ts new file mode 100644 index 0000000000..81610cc59a --- /dev/null +++ b/src/screens/Search/__tests__/utils.test.ts @@ -0,0 +1,43 @@ +import {describe, expect, it} from '@jest/globals' + +import {parseSearchQuery} from '#/screens/Search/utils' + +describe(`parseSearchQuery`, () => { + const tests = [ + { + input: `bluesky`, + output: {query: `bluesky`, params: {}}, + }, + { + input: `bluesky from:esb.lol`, + output: {query: `bluesky`, params: {from: `esb.lol`}}, + }, + { + input: `bluesky "from:esb.lol"`, + output: {query: `bluesky "from:esb.lol"`, params: {}}, + }, + { + input: `bluesky mentions:@esb.lol`, + output: {query: `bluesky`, params: {mentions: `@esb.lol`}}, + }, + { + input: `bluesky since:2021-01-01:00:00:00`, + output: {query: `bluesky`, params: {since: `2021-01-01:00:00:00`}}, + }, + { + input: `bluesky lang:"en"`, + output: {query: `bluesky`, params: {lang: `en`}}, + }, + { + input: `bluesky "literal" lang:en "from:invalid"`, + output: {query: `bluesky "literal" "from:invalid"`, params: {lang: `en`}}, + }, + ] + + it.each(tests)( + `$input -> $output.query $output.params`, + ({input, output}) => { + expect(parseSearchQuery(input)).toEqual(output) + }, + ) +}) diff --git a/src/screens/Search/utils.ts b/src/screens/Search/utils.ts new file mode 100644 index 0000000000..dcf92c0926 --- /dev/null +++ b/src/screens/Search/utils.ts @@ -0,0 +1,43 @@ +export type Params = Record + +export function parseSearchQuery(rawQuery: string) { + let base = rawQuery + const rawLiterals = rawQuery.match(/[^:\w\d]".+?"/gi) || [] + + // remove literals from base + for (const literal of rawLiterals) { + base = base.replace(literal.trim(), '') + } + + // find remaining params in base + const rawParams = base.match(/[a-z]+:[a-z-\.@\d:"]+/gi) || [] + + for (const param of rawParams) { + base = base.replace(param, '') + } + + base = base.trim() + + const params = rawParams.reduce((params, param) => { + const [name, ...value] = param.split(/:/) + params[name] = value.join(':').replace(/"/g, '') // dates can contain additional colons + return params + }, {} as Params) + const literals = rawLiterals.map(l => String(l).trim()) + + return { + query: [base, literals.join(' ')].filter(Boolean).join(' '), + params, + } +} + +export function makeSearchQuery(query: string, params: Params) { + return [ + query, + Object.entries(params) + .map(([name, value]) => `${name}:${value}`) + .join(' '), + ] + .filter(Boolean) + .join(' ') +} diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 07d762c0fe..cfd77f7ef2 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' @@ -35,10 +37,10 @@ import { SearchTabNavigatorParams, } from '#/lib/routes/types' import {augmentSearchQuery} from '#/lib/strings/helpers' -import {useTheme} from '#/lib/ThemeContext' 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' @@ -57,9 +59,16 @@ 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, useTheme as useThemeNew} from '#/alf' +import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils' +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 {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2' +import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' function Loader() { const pal = usePalette('default') @@ -251,7 +260,7 @@ let SearchScreenUserResults = ({ const {_} = useLingui() const {data: results, isFetched} = useActorSearch({ - query: query, + query, enabled: active, }) @@ -324,7 +333,137 @@ let SearchScreenFeedsResults = ({ } SearchScreenFeedsResults = React.memo(SearchScreenFeedsResults) -let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { +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: 14, + paddingRight: 32, + paddingVertical: 8, + borderRadius: a.rounded_full.borderRadius, + borderWidth: a.border.borderWidth, + borderColor: t.atoms.border_contrast_low.borderColor, + } + + return ( + ( + + )} + useNativeAndroidPickerStyle={false} + style={{ + iconContainer: { + pointerEvents: 'none', + right: a.px_sm.paddingRight, + top: 0, + bottom: 0, + display: 'flex', + justifyContent: 'center', + }, + inputAndroid: { + ...style, + paddingVertical: 2, + }, + 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', + }), + }} + /> + ) +} + +function useQueryManager({initialQuery}: {initialQuery: string}) { + const {contentLanguages} = useLanguagePrefs() + const {query, params: initialParams} = React.useMemo(() => { + return parseSearchQuery(initialQuery || '') + }, [initialQuery]) + const prevInitialQuery = React.useRef(initialQuery) + const [lang, setLang] = React.useState( + initialParams.lang || contentLanguages[0], + ) + + if (initialQuery !== prevInitialQuery.current) { + // handle new queryParam change (from manual search entry) + prevInitialQuery.current = initialQuery + setLang(initialParams.lang || contentLanguages[0]) + } + + const params = React.useMemo( + () => ({ + // default stuff + ...initialParams, + // managed stuff + lang, + }), + [lang, initialParams], + ) + const handlers = React.useMemo( + () => ({ + setLang, + }), + [setLang], + ) + + return React.useMemo(() => { + return { + query, + queryWithParams: makeSearchQuery(query, params), + params: { + ...params, + ...handlers, + }, + } + }, [query, params, handlers]) +} + +let SearchScreenInner = ({ + query, + queryWithParams, + headerHeight, +}: { + query: string + queryWithParams: string + headerHeight: number +}): React.ReactNode => { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() @@ -349,7 +488,7 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { title: _(msg`Top`), component: ( @@ -359,7 +498,7 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { title: _(msg`Latest`), component: ( @@ -378,7 +517,7 @@ let SearchScreenInner = ({query}: {query?: string}): React.ReactNode => { ), }, ] - }, [_, query, activeTab]) + }, [_, query, queryWithParams, activeTab]) return query ? ( { renderTabBar={props => ( + style={[ + pal.border, + pal.view, + web({ + position: isWeb ? 'sticky' : '', + zIndex: 1, + }), + {top: isWeb ? headerHeight : undefined}, + ]}> section.title)} {...props} /> )} @@ -448,14 +595,14 @@ SearchScreenInner = React.memo(SearchScreenInner) export function SearchScreen( props: NativeStackScreenProps, ) { + const t = useThemeNew() + const {gtMobile} = useBreakpoints() const navigation = useNavigation() const textInput = React.useRef(null) const {_} = useLingui() - const pal = usePalette('default') const {track} = useAnalytics() const setDrawerOpen = useSetDrawerOpen() const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop, isTabletOrMobile} = useWebMediaQueries() // Query terms const queryParam = props.route?.params?.q ?? '' @@ -469,6 +616,17 @@ export function SearchScreen( AppBskyActorDefs.ProfileViewBasic[] >([]) + const {params, query, queryWithParams} = useQueryManager({ + initialQuery: queryParam, + }) + const showFiltersButton = Boolean(query && !showAutocomplete) + const [showFilters, setShowFilters] = React.useState(false) + /* + * Arbitrary sizing, so guess and check, used for sticky header alignment and + * sizing. + */ + const headerHeight = 56 + (showFilters ? 40 : 0) + useFocusEffect( useNonReactiveCallback(() => { if (isWeb) { @@ -507,13 +665,6 @@ export function SearchScreen( textInput.current?.focus() }, []) - const onPressCancelSearch = React.useCallback(() => { - scrollToTopWeb() - textInput.current?.blur() - setShowAutocomplete(false) - setSearchText(queryParam) - }, [queryParam]) - const onChangeText = React.useCallback(async (text: string) => { scrollToTopWeb() setSearchText(text) @@ -586,6 +737,13 @@ export function SearchScreen( [updateSearchHistory, navigation], ) + const onPressCancelSearch = React.useCallback(() => { + scrollToTopWeb() + textInput.current?.blur() + setShowAutocomplete(false) + setSearchText(queryParam) + }, [setShowAutocomplete, setSearchText, queryParam]) + const onSubmit = React.useCallback(() => { navigateToItem(searchText) }, [navigateToItem, searchText]) @@ -624,6 +782,7 @@ export function SearchScreen( setSearchText('') navigation.setParams({q: ''}) } + setShowFilters(false) }, [navigation]) useFocusEffect( @@ -663,50 +822,107 @@ export function SearchScreen( [selectedProfiles], ) + const onSearchInputFocus = React.useCallback(() => { + if (isWeb) { + // Prevent a jump on iPad by ensuring that + // the initial focused render has no result list. + requestAnimationFrame(() => { + setShowAutocomplete(true) + }) + } else { + setShowAutocomplete(true) + } + setShowFilters(false) + }, [setShowAutocomplete]) + return ( - {isTabletOrMobile && ( - - - - )} - - {showAutocomplete && ( - - + + {!gtMobile && ( + + )} + + {showFiltersButton && ( + + )} + {showAutocomplete && ( + + )} + + + {showFilters && ( + + + + )} + - + ) @@ -747,7 +967,7 @@ let SearchInputBox = ({ textInput, searchText, showAutocomplete, - setShowAutocomplete, + onFocus, onChangeText, onSubmit, onPressClearQuery, @@ -755,83 +975,62 @@ let SearchInputBox = ({ textInput: React.RefObject searchText: string showAutocomplete: boolean - setShowAutocomplete: (show: boolean) => void + onFocus: () => void onChangeText: (text: string) => void onSubmit: () => void onPressClearQuery: () => void }): React.ReactNode => { - const pal = usePalette('default') const {_} = useLingui() - const theme = useTheme() + const t = useThemeNew() + return ( - { - textInput.current?.focus() - }}> - - { - if (isWeb) { - // Prevent a jump on iPad by ensuring that - // the initial focused render has no result list. - requestAnimationFrame(() => { - setShowAutocomplete(true) - }) - } else { - setShowAutocomplete(true) - } - }} - onChangeText={onChangeText} - onSubmitEditing={onSubmit} - autoFocus={false} - accessibilityRole="search" - accessibilityLabel={_(msg`Search`)} - accessibilityHint="" - autoCorrect={false} - autoComplete="off" - autoCapitalize="none" - /> + + + + + + {showAutocomplete && searchText.length > 0 && ( - - - + + + )} - + ) } SearchInputBox = React.memo(SearchInputBox) @@ -1029,21 +1228,7 @@ function scrollToTopWeb() { } } -const HEADER_HEIGHT = 46 - const styles = StyleSheet.create({ - header: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - paddingLeft: 13, - paddingVertical: 4, - height: HEADER_HEIGHT, - // @ts-ignore web only - position: isWeb ? 'sticky' : '', - top: 0, - zIndex: 1, - }, headerMenuBtn: { width: 30, height: 30, @@ -1075,12 +1260,6 @@ const styles = StyleSheet.create({ zIndex: -1, elevation: -1, // For Android }, - tabBarContainer: { - // @ts-ignore web only - position: isWeb ? 'sticky' : '', - top: isWeb ? HEADER_HEIGHT : 0, - zIndex: 1, - }, searchHistoryContainer: { width: '100%', paddingHorizontal: 12, diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx index fc414d31f3..8ec118ae3e 100644 --- a/src/view/screens/Storybook/Forms.tsx +++ b/src/view/screens/Storybook/Forms.tsx @@ -32,7 +32,7 @@ export function Forms() { label="Text field" /> - + Date: Wed, 25 Sep 2024 15:05:33 -0700 Subject: [PATCH 2/9] Filter errors that get sent to Sentry (#5247) --- __tests__/lib/errors.test.ts | 14 +++++++------- src/lib/strings/errors.ts | 18 +++++++++++++----- src/logger/index.ts | 10 ++++++++-- src/view/com/util/UserAvatar.tsx | 3 ++- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/__tests__/lib/errors.test.ts b/__tests__/lib/errors.test.ts index 39e8d189e0..e721396845 100644 --- a/__tests__/lib/errors.test.ts +++ b/__tests__/lib/errors.test.ts @@ -9,11 +9,11 @@ describe('isNetworkError', () => { ] const outputs = [true, false, false, true] - it('correctly distinguishes network errors', () => { - for (let i = 0; i < inputs.length; i++) { - const input = inputs[i] - const result = isNetworkError(input) - expect(result).toEqual(outputs[i]) - } - }) + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i] + const output = outputs[i] + it(`correctly distinguishes network errors for ${input}`, () => { + expect(isNetworkError(input)).toEqual(output) + }) + } }) diff --git a/src/lib/strings/errors.ts b/src/lib/strings/errors.ts index 899d8ebce4..7d00c5e7f5 100644 --- a/src/lib/strings/errors.ts +++ b/src/lib/strings/errors.ts @@ -20,11 +20,19 @@ export function cleanError(str: any): string { return str } +const NETWORK_ERRORS = [ + 'Abort', + 'Network request failed', + 'Failed to fetch', + 'Load failed', +] + export function isNetworkError(e: unknown) { const str = String(e) - return ( - str.includes('Abort') || - str.includes('Network request failed') || - str.includes('Failed to fetch') - ) + for (const err of NETWORK_ERRORS) { + if (str.includes(err)) { + return true + } + } + return false } diff --git a/src/logger/index.ts b/src/logger/index.ts index d6d8d9fc1d..98635c6a97 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,10 +1,11 @@ import format from 'date-fns/format' import {nanoid} from 'nanoid/non-secure' -import {Sentry} from '#/logger/sentry' -import * as env from '#/env' import {DebugContext} from '#/logger/debugContext' import {add} from '#/logger/logDump' +import {Sentry} from '#/logger/sentry' +import {isNetworkError} from 'lib/strings/errors' +import * as env from '#/env' export enum LogLevel { Debug = 'debug', @@ -160,6 +161,11 @@ export const sentryTransport: Transport = ( timestamp: timestamp / 1000, // Sentry expects seconds }) + // We don't want to send any network errors to sentry + if (isNetworkError(message)) { + return + } + /** * Send all higher levels with `captureMessage`, with appropriate severity * level diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 76d9d1503e..2b4376b698 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -327,7 +327,8 @@ let EditableUserAvatar = ({ onSelectNewAvatar(croppedImage) } catch (e: any) { - if (!String(e).includes('Canceled')) { + // Don't log errors for cancelling selection to sentry on ios or android + if (!String(e).toLowerCase().includes('cancel')) { logger.error('Failed to crop banner', {error: e}) } } From 2e5f95c8dd92c0be665414be41fb690e2434a941 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 25 Sep 2024 17:41:03 -0500 Subject: [PATCH 3/9] Add back empty placeholder (#5489) --- src/view/screens/Search/Search.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index cfd77f7ef2..36639e7ed7 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -370,6 +370,7 @@ function SearchLanguageDropdown({ return ( Date: Thu, 26 Sep 2024 01:32:54 +0100 Subject: [PATCH 4/9] Messages list - make avatars link to profile (#5484) --- src/screens/Messages/List/ChatListItem.tsx | 25 ++++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/screens/Messages/List/ChatListItem.tsx b/src/screens/Messages/List/ChatListItem.tsx index e9668b4e11..11c071082b 100644 --- a/src/screens/Messages/List/ChatListItem.tsx +++ b/src/screens/Messages/List/ChatListItem.tsx @@ -24,8 +24,9 @@ import {useProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useSession} from '#/state/session' import {TimeElapsed} from '#/view/com/util/TimeElapsed' -import {UserAvatar} from '#/view/com/util/UserAvatar' +import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' +import * as tokens from '#/alf/tokens' import {ConvoMenu} from '#/components/dms/ConvoMenu' import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2' import {Link} from '#/components/Link' @@ -203,6 +204,19 @@ function ChatListItemReady({ onFocus={onFocus} onBlur={onMouseLeave} style={[a.relative]}> + + + + - + {/* Avatar goes here */} + @@ -357,7 +368,7 @@ function ChatListItemReady({ a.self_end, a.justify_center, { - right: a.px_lg.paddingRight, + right: tokens.space.lg, opacity: !gtMobile || showActions || menuControl.isOpen ? 1 : 0, }, ]} From a9765fd23f59d31d60f5aaedd95e751f65adc969 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 26 Sep 2024 14:09:02 +0100 Subject: [PATCH 5/9] Fix banner height in edit profile modal (#5494) * fix banner height * fix user banner, it's not edit profile's fault --- src/view/com/modals/EditProfile.tsx | 8 ++++---- src/view/com/util/UserBanner.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index beea3ca1a8..b4cb8e013c 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -27,11 +27,11 @@ import {logger} from '#/logger' import {isWeb} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {useProfileUpdateMutation} from '#/state/queries/profile' +import {Text} from '#/view/com/util/text/Text' +import * as Toast from '#/view/com/util/Toast' +import {EditableUserAvatar} from '#/view/com/util/UserAvatar' +import {UserBanner} from '#/view/com/util/UserBanner' import {ErrorMessage} from '../util/error/ErrorMessage' -import {Text} from '../util/text/Text' -import * as Toast from '../util/Toast' -import {EditableUserAvatar} from '../util/UserAvatar' -import {UserBanner} from '../util/UserBanner' const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity) diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 0e07a57454..13f4081fce 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -202,7 +202,7 @@ const styles = StyleSheet.create({ }, bannerImage: { width: '100%', - height: '100%', + height: 150, }, defaultBanner: { backgroundColor: '#0070ff', From f2a69c4528d318bfff9fff084ef75719fd5f41fb Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 26 Sep 2024 14:18:48 +0100 Subject: [PATCH 6/9] add emoji prop to composer reply to text (#5495) --- src/view/com/composer/ComposerReplyTo.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 74ca615070..7f4bb85f23 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -10,12 +10,12 @@ import { import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {sanitizeHandle} from 'lib/strings/handles' -import {ComposerOptsPostRef} from 'state/shell/composer' -import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed' -import {Text} from 'view/com/util/text/Text' -import {PreviewableUserAvatar} from 'view/com/util/UserAvatar' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {sanitizeHandle} from '#/lib/strings/handles' +import {ComposerOptsPostRef} from '#/state/shell/composer' +import {QuoteEmbed} from '#/view/com/util/post-embeds/QuoteEmbed' +import {Text} from '#/view/com/util/text/Text' +import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { @@ -91,7 +91,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { type={replyTo.author.associated?.labeler ? 'labeler' : 'user'} /> - + {sanitizeDisplayName( replyTo.author.displayName || sanitizeHandle(replyTo.author.handle), )} @@ -101,7 +101,8 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { + numberOfLines={!showFull ? 6 : undefined} + emoji> {replyTo.text} From 625d7460d8af868ef0ea755cb3ac931dac4aad77 Mon Sep 17 00:00:00 2001 From: Igor Adrov Date: Thu, 26 Sep 2024 16:48:12 +0200 Subject: [PATCH 7/9] Make the counter more rounded (#5083) --- src/view/shell/desktop/LeftNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 6cceaccd92..c36c4ae758 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -470,7 +470,7 @@ const styles = StyleSheet.create({ fontSize: 12, fontWeight: '600', paddingHorizontal: 4, - borderRadius: 6, + borderRadius: 8, }, navItemCountTablet: { left: 18, From 179a913f20b9b77edbf9d167ebf32ad455f78eac Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 26 Sep 2024 09:59:32 -0500 Subject: [PATCH 8/9] Emoji in account list (#5497) --- src/components/AccountList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index 883c06c144..68bb482af2 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -126,10 +126,12 @@ function AccountItem({ - + {profile?.displayName || account.handle}{' '} - {account.handle} + + {account.handle} + {isCurrentAccount ? ( Date: Thu, 26 Sep 2024 09:59:47 -0500 Subject: [PATCH 9/9] Adjust line height to not cut off emoji (#5496) --- src/view/com/util/PostMeta.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 3f647f9784..adf9c5eb1b 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -77,7 +77,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { disableMismatchWarning onPress={onBeforePressAuthor} style={[t.atoms.text]}> - + {forceLTR( sanitizeDisplayName( displayName, @@ -92,14 +92,10 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { disableMismatchWarning disableUnderline onPress={onBeforePressAuthor} - style={[a.text_md, t.atoms.text_contrast_medium, a.leading_tight]}> + style={[a.text_md, t.atoms.text_contrast_medium, a.leading_snug]}> + style={[a.text_md, t.atoms.text_contrast_medium, a.leading_snug]}> {NON_BREAKING_SPACE + sanitizeHandle(handle, '@')} @@ -124,7 +120,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { style={[ a.text_md, t.atoms.text_contrast_medium, - a.leading_tight, + a.leading_snug, web({ whiteSpace: 'nowrap', }),