diff options
author | Kuwa Lee <kuwalee1069@gmail.com> | 2024-06-19 02:47:38 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-19 02:47:38 +0800 |
commit | a6d49062e6d50b7c9a6c0d50c38fcfeb8f63e46f (patch) | |
tree | 65ef4f28c174d1da9c8f7085635b05b754e95746 /src | |
parent | fad73fe9281baee8409a65a10923749ec24dfd68 (diff) | |
parent | 35e54e24a0b08ce0f2e3389aeb4fb0f29778170e (diff) | |
download | voidsky-a6d49062e6d50b7c9a6c0d50c38fcfeb8f63e46f.tar.zst |
Merge branch 'bluesky-social:main' into zh
Diffstat (limited to 'src')
-rw-r--r-- | src/alf/atoms.ts | 3 | ||||
-rw-r--r-- | src/alf/tokens.ts | 1 | ||||
-rw-r--r-- | src/components/NewskieDialog.tsx | 81 | ||||
-rw-r--r-- | src/components/forms/DateField/index.tsx | 7 | ||||
-rw-r--r-- | src/components/icons/Newskie.tsx | 5 | ||||
-rw-r--r-- | src/lib/hooks/__tests__/useTimeAgo.test.ts | 102 | ||||
-rw-r--r-- | src/lib/hooks/useTimeAgo.ts | 95 | ||||
-rw-r--r-- | src/lib/strings/time.ts | 42 | ||||
-rw-r--r-- | src/locale/i18n.ts | 2 | ||||
-rw-r--r-- | src/screens/Profile/Header/Handle.tsx | 7 | ||||
-rw-r--r-- | src/view/com/util/TimeElapsed.tsx | 12 | ||||
-rw-r--r-- | src/view/com/util/layouts/LoggedOutLayout.tsx | 8 | ||||
-rw-r--r-- | src/view/icons/index.tsx | 6 | ||||
-rw-r--r-- | src/view/screens/Log.tsx | 24 | ||||
-rw-r--r-- | src/view/screens/Search/Explore.tsx | 31 |
15 files changed, 347 insertions, 79 deletions
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 1ccb0460c..1dc2dfa7b 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -267,6 +267,9 @@ export const atoms = { font_bold: { fontWeight: tokens.fontWeight.bold, }, + font_heavy: { + fontWeight: tokens.fontWeight.heavy, + }, italic: { fontStyle: 'italic', }, diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts index 1bddd95d4..675844e29 100644 --- a/src/alf/tokens.ts +++ b/src/alf/tokens.ts @@ -118,6 +118,7 @@ export const fontWeight = { normal: '400', semibold: '500', bold: '600', + heavy: '700', } as const export const gradients = { diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx new file mode 100644 index 000000000..fcdae0daa --- /dev/null +++ b/src/components/NewskieDialog.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import {View} from 'react-native' +import {AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {differenceInSeconds} from 'date-fns' + +import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {HITSLOP_10} from 'lib/constants' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {atoms as a} from '#/alf' +import {Button} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useDialogControl} from '#/components/Dialog' +import {Newskie} from '#/components/icons/Newskie' +import {Text} from '#/components/Typography' + +export function NewskieDialog({ + profile, +}: { + profile: AppBskyActorDefs.ProfileViewDetailed +}) { + const {_} = useLingui() + const moderationOpts = useModerationOpts() + const control = useDialogControl() + const profileName = React.useMemo(() => { + const name = profile.displayName || profile.handle + if (!moderationOpts) return name + const moderation = moderateProfile(profile, moderationOpts) + return sanitizeDisplayName(name, moderation.ui('displayName')) + }, [moderationOpts, profile]) + const timeAgo = useGetTimeAgo() + const createdAt = profile.createdAt as string | undefined + const daysOld = React.useMemo(() => { + if (!createdAt) return Infinity + return differenceInSeconds(new Date(), new Date(createdAt)) / 86400 + }, [createdAt]) + + if (!createdAt || daysOld > 7) return null + + return ( + <View style={[a.pr_2xs]}> + <Button + label={_( + msg`This user is new here. Press for more info about when they joined.`, + )} + hitSlop={HITSLOP_10} + onPress={control.open}> + {({hovered, pressed}) => ( + <Newskie + size="lg" + fill="#FFC404" + style={{ + opacity: hovered || pressed ? 0.5 : 1, + }} + /> + )} + </Button> + + <Dialog.Outer control={control}> + <Dialog.Handle /> + <Dialog.ScrollableInner + label={_(msg`New user info dialog`)} + style={[{width: 'auto', maxWidth: 400, minWidth: 200}]}> + <View style={[a.gap_sm]}> + <Text style={[a.font_bold, a.text_xl]}> + <Trans>Say hello!</Trans> + </Text> + <Text style={[a.text_md]}> + <Trans> + {profileName} joined Bluesky{' '} + {timeAgo(createdAt, {format: 'long'})} ago + </Trans> + </Text> + </View> + </Dialog.ScrollableInner> + </Dialog.Outer> + </View> + ) +} diff --git a/src/components/forms/DateField/index.tsx b/src/components/forms/DateField/index.tsx index e231ac5ba..c916f4efc 100644 --- a/src/components/forms/DateField/index.tsx +++ b/src/components/forms/DateField/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {View} from 'react-native' +import {Keyboard, View} from 'react-native' import DatePicker from 'react-native-date-picker' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -49,7 +49,10 @@ export function DateField({ <DateFieldButton label={label} value={value} - onPress={control.open} + onPress={() => { + Keyboard.dismiss() + control.open() + }} isInvalid={isInvalid} accessibilityHint={accessibilityHint} /> diff --git a/src/components/icons/Newskie.tsx b/src/components/icons/Newskie.tsx new file mode 100644 index 000000000..ddbb33201 --- /dev/null +++ b/src/components/icons/Newskie.tsx @@ -0,0 +1,5 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Newskie = createSinglePathSVG({ + path: 'M11.183 8.561c0 .544.348.984.892.984.545 0 .893-.44.893-.985V6.985c0-.544-.348-.985-.893-.985-.543 0-.892.44-.892.985v1.576Zm5.94 7.481c0 .539-.438.942-.976.942H8.004c-.538 0-.975-.411-.975-.95 0-2.782 2.264-5.021 5.046-5.021 2.783 0 5.047 2.247 5.047 5.03Zm-.43-4.584a.983.983 0 0 1 0-1.393l1.114-1.114a.985.985 0 0 1 1.393 1.393l-1.114 1.114a.985.985 0 0 1-1.393 0Zm2.897 3.741h1.575c.544 0 .985.349.985.892 0 .544-.44.892-.985.892h-1.67a.872.872 0 0 1-.89-.887c0-.543.44-.897.985-.897Zm-14.045.893c0-.544-.44-.892-.985-.892H2.985c-.544 0-.985.349-.985.892 0 .544.44.892.985.892H4.56c.545 0 .985-.349.985-.892Zm1.913-6.027a.985.985 0 0 1-1.393 1.393L4.95 10.344A.985.985 0 0 1 6.344 8.95l1.114 1.114Z', +}) diff --git a/src/lib/hooks/__tests__/useTimeAgo.test.ts b/src/lib/hooks/__tests__/useTimeAgo.test.ts new file mode 100644 index 000000000..e74f9c62d --- /dev/null +++ b/src/lib/hooks/__tests__/useTimeAgo.test.ts @@ -0,0 +1,102 @@ +import {describe, expect, it} from '@jest/globals' +import {MessageDescriptor} from '@lingui/core' +import {addDays, subDays, subHours, subMinutes, subSeconds} from 'date-fns' + +import {dateDiff} from '../useTimeAgo' + +const lingui: any = (obj: MessageDescriptor) => obj.message + +const base = new Date('2024-06-17T00:00:00Z') + +describe('dateDiff', () => { + it(`works with numbers`, () => { + expect(dateDiff(subDays(base, 3), Number(base), {lingui})).toEqual('3d') + }) + it(`works with strings`, () => { + expect(dateDiff(subDays(base, 3), base.toString(), {lingui})).toEqual('3d') + }) + it(`works with dates`, () => { + expect(dateDiff(subDays(base, 3), base, {lingui})).toEqual('3d') + }) + + it(`equal values return now`, () => { + expect(dateDiff(base, base, {lingui})).toEqual('now') + }) + it(`future dates return now`, () => { + expect(dateDiff(addDays(base, 3), base, {lingui})).toEqual('now') + }) + + it(`values < 5 seconds ago return now`, () => { + const then = subSeconds(base, 4) + expect(dateDiff(then, base, {lingui})).toEqual('now') + }) + it(`values >= 5 seconds ago return seconds`, () => { + const then = subSeconds(base, 5) + expect(dateDiff(then, base, {lingui})).toEqual('5s') + }) + + it(`values < 1 min return seconds`, () => { + const then = subSeconds(base, 59) + expect(dateDiff(then, base, {lingui})).toEqual('59s') + }) + it(`values >= 1 min return minutes`, () => { + const then = subSeconds(base, 60) + expect(dateDiff(then, base, {lingui})).toEqual('1m') + }) + it(`minutes round down`, () => { + const then = subSeconds(base, 119) + expect(dateDiff(then, base, {lingui})).toEqual('1m') + }) + + it(`values < 1 hour return minutes`, () => { + const then = subMinutes(base, 59) + expect(dateDiff(then, base, {lingui})).toEqual('59m') + }) + it(`values >= 1 hour return hours`, () => { + const then = subMinutes(base, 60) + expect(dateDiff(then, base, {lingui})).toEqual('1h') + }) + it(`hours round down`, () => { + const then = subMinutes(base, 119) + expect(dateDiff(then, base, {lingui})).toEqual('1h') + }) + + it(`values < 1 day return hours`, () => { + const then = subHours(base, 23) + expect(dateDiff(then, base, {lingui})).toEqual('23h') + }) + it(`values >= 1 day return days`, () => { + const then = subHours(base, 24) + expect(dateDiff(then, base, {lingui})).toEqual('1d') + }) + it(`days round down`, () => { + const then = subHours(base, 47) + expect(dateDiff(then, base, {lingui})).toEqual('1d') + }) + + it(`values < 30 days return days`, () => { + const then = subDays(base, 29) + expect(dateDiff(then, base, {lingui})).toEqual('29d') + }) + it(`values >= 30 days return months`, () => { + const then = subDays(base, 30) + expect(dateDiff(then, base, {lingui})).toEqual('1mo') + }) + it(`months round down`, () => { + const then = subDays(base, 59) + expect(dateDiff(then, base, {lingui})).toEqual('1mo') + }) + it(`values are rounded by increments of 30`, () => { + const then = subDays(base, 61) + expect(dateDiff(then, base, {lingui})).toEqual('2mo') + }) + + it(`values < 360 days return months`, () => { + const then = subDays(base, 359) + expect(dateDiff(then, base, {lingui})).toEqual('11mo') + }) + it(`values >= 360 days return the earlier value`, () => { + const then = subDays(base, 360) + expect(dateDiff(then, base, {lingui})).toEqual(then.toLocaleDateString()) + }) +}) diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts new file mode 100644 index 000000000..5f0782f96 --- /dev/null +++ b/src/lib/hooks/useTimeAgo.ts @@ -0,0 +1,95 @@ +import {useCallback, useMemo} from 'react' +import {msg, plural} from '@lingui/macro' +import {I18nContext, useLingui} from '@lingui/react' +import {differenceInSeconds} from 'date-fns' + +export type TimeAgoOptions = { + lingui: I18nContext['_'] + format?: 'long' | 'short' +} + +export function useGetTimeAgo() { + const {_} = useLingui() + return useCallback( + ( + date: number | string | Date, + options?: Omit<TimeAgoOptions, 'lingui'>, + ) => { + return dateDiff(date, Date.now(), {lingui: _, format: options?.format}) + }, + [_], + ) +} + +export function useTimeAgo( + date: number | string | Date, + options?: Omit<TimeAgoOptions, 'lingui'>, +): string { + const timeAgo = useGetTimeAgo() + return useMemo(() => { + return timeAgo(date, {...options}) + }, [date, options, timeAgo]) +} + +const NOW = 5 +const MINUTE = 60 +const HOUR = MINUTE * 60 +const DAY = HOUR * 24 +const MONTH_30 = DAY * 30 + +/** + * Returns the difference between `earlier` and `later` dates, formatted as a + * natural language string. + * + * - All month are considered exactly 30 days. + * - Dates assume `earlier` <= `later`, and will otherwise return 'now'. + * - Differences >= 360 days are returned as the "M/D/YYYY" string + * - All values round down + */ +export function dateDiff( + earlier: number | string | Date, + later: number | string | Date, + options: TimeAgoOptions, +): string { + const _ = options.lingui + const format = options?.format || 'short' + const long = format === 'long' + const diffSeconds = differenceInSeconds(new Date(later), new Date(earlier)) + + if (diffSeconds < NOW) { + return _(msg`now`) + } else if (diffSeconds < MINUTE) { + return `${diffSeconds}${ + long ? ` ${plural(diffSeconds, {one: 'second', other: 'seconds'})}` : 's' + }` + } else if (diffSeconds < HOUR) { + const diff = Math.floor(diffSeconds / MINUTE) + return `${diff}${ + long ? ` ${plural(diff, {one: 'minute', other: 'minutes'})}` : 'm' + }` + } else if (diffSeconds < DAY) { + const diff = Math.floor(diffSeconds / HOUR) + return `${diff}${ + long ? ` ${plural(diff, {one: 'hour', other: 'hours'})}` : 'h' + }` + } else if (diffSeconds < MONTH_30) { + const diff = Math.floor(diffSeconds / DAY) + return `${diff}${ + long ? ` ${plural(diff, {one: 'day', other: 'days'})}` : 'd' + }` + } else { + const diff = Math.floor(diffSeconds / MONTH_30) + if (diff < 12) { + return `${diff}${ + long ? ` ${plural(diff, {one: 'month', other: 'months'})}` : 'mo' + }` + } else { + const str = new Date(earlier).toLocaleDateString() + + if (long) { + return _(msg`on ${str}`) + } + return str + } + } +} diff --git a/src/lib/strings/time.ts b/src/lib/strings/time.ts index 8de4b52ae..1194e0240 100644 --- a/src/lib/strings/time.ts +++ b/src/lib/strings/time.ts @@ -1,45 +1,3 @@ -const NOW = 5 -const MINUTE = 60 -const HOUR = MINUTE * 60 -const DAY = HOUR * 24 -const MONTH_30 = DAY * 30 -const MONTH = DAY * 30.41675 // This results in 365.001 days in a year, which is close enough for nearly all cases -export function ago(date: number | string | Date): string { - let ts: number - if (typeof date === 'string') { - ts = Number(new Date(date)) - } else if (date instanceof Date) { - ts = Number(date) - } else { - ts = date - } - const diffSeconds = Math.floor((Date.now() - ts) / 1e3) - if (diffSeconds < NOW) { - return `now` - } else if (diffSeconds < MINUTE) { - return `${diffSeconds}s` - } else if (diffSeconds < HOUR) { - return `${Math.floor(diffSeconds / MINUTE)}m` - } else if (diffSeconds < DAY) { - return `${Math.floor(diffSeconds / HOUR)}h` - } else if (diffSeconds < MONTH_30) { - return `${Math.round(diffSeconds / DAY)}d` - } else { - let months = diffSeconds / MONTH - if (months % 1 >= 0.9) { - months = Math.ceil(months) - } else { - months = Math.floor(months) - } - - if (months < 12) { - return `${months}mo` - } else { - return new Date(ts).toLocaleDateString() - } - } -} - export function niceDate(date: number | string | Date) { const d = new Date(date) return `${d.toLocaleDateString('en-us', { diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts index 9f75f83f3..baec4b8a2 100644 --- a/src/locale/i18n.ts +++ b/src/locale/i18n.ts @@ -1,5 +1,5 @@ import '@formatjs/intl-locale/polyfill' -import '@formatjs/intl-pluralrules/polyfill' +import '@formatjs/intl-pluralrules/polyfill-force' // Don't remove -force because detection is very slow import '@formatjs/intl-pluralrules/locale-data/en' import {useEffect} from 'react' diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index 9ab24fbbe..4f438a286 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -5,7 +5,9 @@ import {Trans} from '@lingui/macro' import {Shadow} from '#/state/cache/types' import {isInvalidHandle} from 'lib/strings/handles' +import {isAndroid} from 'platform/detection' import {atoms as a, useTheme, web} from '#/alf' +import {NewskieDialog} from '#/components/NewskieDialog' import {Text} from '#/components/Typography' export function ProfileHeaderHandle({ @@ -17,7 +19,10 @@ export function ProfileHeaderHandle({ const invalidHandle = isInvalidHandle(profile.handle) const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy return ( - <View style={[a.flex_row, a.gap_xs, a.align_center]} pointerEvents="none"> + <View + style={[a.flex_row, a.gap_xs, a.align_center]} + pointerEvents={isAndroid ? 'box-only' : 'auto'}> + <NewskieDialog profile={profile} /> {profile.viewer?.followedBy && !blockHide ? ( <View style={[t.atoms.bg_contrast_25, a.rounded_xs, a.px_sm, a.py_xs]}> <Text style={[t.atoms.text, a.text_sm]}> diff --git a/src/view/com/util/TimeElapsed.tsx b/src/view/com/util/TimeElapsed.tsx index a5d3a5372..d939b3163 100644 --- a/src/view/com/util/TimeElapsed.tsx +++ b/src/view/com/util/TimeElapsed.tsx @@ -1,26 +1,26 @@ import React from 'react' +import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useTickEveryMinute} from '#/state/shell' -import {ago} from 'lib/strings/time' export function TimeElapsed({ timestamp, children, - timeToString = ago, + timeToString, }: { timestamp: string children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element timeToString?: (timeElapsed: string) => string }) { + const ago = useGetTimeAgo() + const format = timeToString ?? ago const tick = useTickEveryMinute() - const [timeElapsed, setTimeAgo] = React.useState(() => - timeToString(timestamp), - ) + const [timeElapsed, setTimeAgo] = React.useState(() => format(timestamp)) const [prevTick, setPrevTick] = React.useState(tick) if (prevTick !== tick) { setPrevTick(tick) - setTimeAgo(timeToString(timestamp)) + setTimeAgo(format(timestamp)) } return children({timeElapsed}) diff --git a/src/view/com/util/layouts/LoggedOutLayout.tsx b/src/view/com/util/layouts/LoggedOutLayout.tsx index 0272a44c6..c2c080c17 100644 --- a/src/view/com/util/layouts/LoggedOutLayout.tsx +++ b/src/view/com/util/layouts/LoggedOutLayout.tsx @@ -3,6 +3,7 @@ import {ScrollView, StyleSheet, View} from 'react-native' import {isWeb} from '#/platform/detection' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' +import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {atoms as a} from '#/alf' @@ -29,13 +30,18 @@ export const LoggedOutLayout = ({ borderLeftWidth: 1, }) + const [isKeyboardVisible] = useIsKeyboardVisible() + if (isMobile) { if (scrollable) { return ( <ScrollView style={styles.scrollview} keyboardShouldPersistTaps="handled" - keyboardDismissMode="on-drag"> + keyboardDismissMode="none" + contentContainerStyle={[ + {paddingBottom: isKeyboardVisible ? 300 : 0}, + ]}> <View style={a.pt_md}>{children}</View> </ScrollView> ) diff --git a/src/view/icons/index.tsx b/src/view/icons/index.tsx index 025b903b2..b4feed990 100644 --- a/src/view/icons/index.tsx +++ b/src/view/icons/index.tsx @@ -1,5 +1,5 @@ import {library} from '@fortawesome/fontawesome-svg-core' -import {faAddressCard} from '@fortawesome/free-regular-svg-icons' +import {faAddressCard} from '@fortawesome/free-regular-svg-icons/faAddressCard' import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell' import {faBookmark as farBookmark} from '@fortawesome/free-regular-svg-icons/faBookmark' import {faCalendar as farCalendar} from '@fortawesome/free-regular-svg-icons/faCalendar' @@ -25,8 +25,6 @@ import {faSquareCheck} from '@fortawesome/free-regular-svg-icons/faSquareCheck' import {faSquarePlus} from '@fortawesome/free-regular-svg-icons/faSquarePlus' import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan' import {faUser} from '@fortawesome/free-regular-svg-icons/faUser' -import {faFlask} from '@fortawesome/free-solid-svg-icons' -import {faUniversalAccess} from '@fortawesome/free-solid-svg-icons' import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown' import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' @@ -62,6 +60,7 @@ import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation' import {faEye} from '@fortawesome/free-solid-svg-icons/faEye' import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter' import {faFire} from '@fortawesome/free-solid-svg-icons/faFire' +import {faFlask} from '@fortawesome/free-solid-svg-icons/faFlask' import {faGear} from '@fortawesome/free-solid-svg-icons/faGear' import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe' import {faHand} from '@fortawesome/free-solid-svg-icons/faHand' @@ -97,6 +96,7 @@ import {faSignal} from '@fortawesome/free-solid-svg-icons/faSignal' import {faSliders} from '@fortawesome/free-solid-svg-icons/faSliders' import {faThumbtack} from '@fortawesome/free-solid-svg-icons/faThumbtack' import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket' +import {faUniversalAccess} from '@fortawesome/free-solid-svg-icons/faUniversalAccess' import {faUserCheck} from '@fortawesome/free-solid-svg-icons/faUserCheck' import {faUserPlus} from '@fortawesome/free-solid-svg-icons/faUserPlus' import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers' diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx index e727a1fb8..e10aa83ab 100644 --- a/src/view/screens/Log.tsx +++ b/src/view/screens/Log.tsx @@ -1,18 +1,19 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {useFocusEffect} from '@react-navigation/native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {ScrollView} from '../com/util/Views' -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 {getEntries} from '#/logger/logDump' -import {ago} from 'lib/strings/time' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' + +import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' +import {getEntries} from '#/logger/logDump' import {useSetMinimalShellMode} from '#/state/shell' +import {usePalette} from 'lib/hooks/usePalette' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {s} from 'lib/styles' +import {Text} from '../com/util/text/Text' +import {ViewHeader} from '../com/util/ViewHeader' +import {ScrollView} from '../com/util/Views' export function LogScreen({}: NativeStackScreenProps< CommonNavigatorParams, @@ -22,6 +23,7 @@ export function LogScreen({}: NativeStackScreenProps< const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const [expanded, setExpanded] = React.useState<string[]>([]) + const timeAgo = useGetTimeAgo() useFocusEffect( React.useCallback(() => { @@ -70,7 +72,7 @@ export function LogScreen({}: NativeStackScreenProps< /> ) : undefined} <Text type="sm" style={[styles.ts, pal.textLight]}> - {ago(entry.timestamp)} + {timeAgo(entry.timestamp)} </Text> </TouchableOpacity> {expanded.includes(entry.id) ? ( diff --git a/src/view/screens/Search/Explore.tsx b/src/view/screens/Search/Explore.tsx index c7f5f939f..dd93bf813 100644 --- a/src/view/screens/Search/Explore.tsx +++ b/src/view/screens/Search/Explore.tsx @@ -64,7 +64,7 @@ function SuggestedItemsHeader({ fill={t.palette.primary_500} style={{marginLeft: -2}} /> - <Text style={[a.text_2xl, a.font_bold, t.atoms.text]}>{title}</Text> + <Text style={[a.text_2xl, a.font_heavy, t.atoms.text]}>{title}</Text> </View> <Text style={[t.atoms.text_contrast_high, a.leading_snug]}> {description} @@ -119,6 +119,9 @@ function LoadMore({ }) .filter(Boolean) as LoadMoreItems[] }, [item.items, moderationOpts]) + + if (items.length === 0) return null + const type = items[0].type return ( @@ -142,20 +145,20 @@ function LoadMore({ a.relative, { height: 32, - width: 32 + 15 * 3, + width: 32 + 15 * items.length, }, ]}> <View style={[ a.align_center, a.justify_center, - a.border, t.atoms.bg_contrast_25, a.absolute, { width: 30, height: 30, left: 0, + borderWidth: 1, backgroundColor: t.palette.primary_500, borderColor: t.atoms.bg.backgroundColor, borderRadius: type === 'profile' ? 999 : 4, @@ -169,13 +172,13 @@ function LoadMore({ <View key={_item.key} style={[ - a.border, t.atoms.bg_contrast_25, a.absolute, { width: 30, height: 30, left: (i + 1) * 15, + borderWidth: 1, borderColor: t.atoms.bg.backgroundColor, borderRadius: _item.type === 'profile' ? 999 : 4, zIndex: 3 - i, @@ -350,13 +353,15 @@ export function Explore() { } } - i.push({ - type: 'loadMore', - key: 'loadMoreProfiles', - isLoadingMore: isLoadingMoreProfiles, - onLoadMore: onLoadMoreProfiles, - items: i.filter(item => item.type === 'profile').slice(-3), - }) + if (hasNextProfilesPage) { + i.push({ + type: 'loadMore', + key: 'loadMoreProfiles', + isLoadingMore: isLoadingMoreProfiles, + onLoadMore: onLoadMoreProfiles, + items: i.filter(item => item.type === 'profile').slice(-3), + }) + } } else { if (profilesError) { i.push({ @@ -412,7 +417,7 @@ export function Explore() { message: _(msg`Failed to load feeds preferences`), error: cleanError(preferencesError), }) - } else { + } else if (hasNextFeedsPage) { i.push({ type: 'loadMore', key: 'loadMoreFeeds', @@ -454,6 +459,8 @@ export function Explore() { profilesError, feedsError, preferencesError, + hasNextProfilesPage, + hasNextFeedsPage, ]) const renderItem = React.useCallback( |