From 1185ac4053d2960cc286cbf1cf7512c8f5318330 Mon Sep 17 00:00:00 2001 From: Tanza3D Date: Tue, 26 Sep 2023 22:07:56 +0100 Subject: [PATCH 01/40] Small PFP on desktop --- 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 b19d5e8ab0..2f7a9ef822 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -42,7 +42,7 @@ import {makeProfileLink} from 'lib/routes/links' const ProfileCard = observer(function ProfileCardImpl() { const store = useStores() const {isDesktop} = useWebMediaQueries() - const size = isDesktop ? 64 : 48 + const size = isDesktop ? 32 : 48 return store.me.handle ? ( Date: Tue, 26 Sep 2023 22:12:22 +0100 Subject: [PATCH 02/40] Move sidebar by 10px for more even padding --- src/view/shell/desktop/RightNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 12ca256d21..d7dd0e6ada 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -123,7 +123,7 @@ const styles = StyleSheet.create({ position: 'absolute', top: 20, // @ts-ignore web only - left: 'calc(50vw + 310px)', + left: 'calc(50vw + 320px)', width: 304, }, From a427010629e6fa8b95113d97632abb7ba3035b3f Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 28 Sep 2023 11:03:52 +0100 Subject: [PATCH 03/40] Don't render a pressable if there's only one notification author --- src/view/com/notifications/FeedItem.tsx | 49 ++++++++++++++++++++----- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 00e56e1cc1..ff5ec9f504 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -22,7 +22,7 @@ import { import {NotificationsFeedItemModel} from 'state/models/feeds/notifications' import {PostThreadModel} from 'state/models/content/post-thread' import {s, colors} from 'lib/styles' -import {ago} from 'lib/strings/time' +import {niceDate} from 'lib/strings/time' import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeHandle} from 'lib/strings/handles' import {pluralize} from 'lib/strings/helpers' @@ -38,6 +38,7 @@ import {usePalette} from 'lib/hooks/usePalette' import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' import {formatCount} from '../util/numeric/format' import {makeProfileLink} from 'lib/routes/links' +import {TimeElapsed} from '../util/TimeElapsed' const MAX_AUTHORS = 5 @@ -88,7 +89,7 @@ export const FeedItem = observer(function FeedItemImpl({ }, [item]) const onToggleAuthorsExpanded = () => { - setAuthorsExpanded(!isAuthorsExpanded) + setAuthorsExpanded(currentlyExpanded => !currentlyExpanded) } const authors: Author[] = useMemo(() => { @@ -179,7 +180,6 @@ export const FeedItem = observer(function FeedItemImpl({ } return ( - // eslint-disable-next-line react-native-a11y/no-nested-touchables - 1 ? onToggleAuthorsExpanded : undefined} - accessible={false}> + 1} + onToggleAuthorsExpanded={onToggleAuthorsExpanded}> ) : undefined} {action} - {ago(item.indexedAt)} + + {({timeElapsed}) => ( + + {' ' + timeElapsed} + + )} + - + {item.isLike || item.isRepost || item.isQuote ? ( ) : null} @@ -250,6 +256,29 @@ export const FeedItem = observer(function FeedItemImpl({ ) }) +function ExpandListPressable({ + hasMultipleAuthors, + children, + onToggleAuthorsExpanded, +}: { + hasMultipleAuthors: boolean + children: React.ReactNode + onToggleAuthorsExpanded: () => void +}) { + if (hasMultipleAuthors) { + return ( + + {children} + + ) + } else { + return <>{children} + } +} + function CondensedAuthorsList({ visible, authors, @@ -466,7 +495,9 @@ const styles = StyleSheet.create({ paddingTop: 4, paddingLeft: 36, }, - + expandedAuthorsTrigger: { + zIndex: 1, + }, expandedAuthorsCloseBtn: { flexDirection: 'row', alignItems: 'center', From 83bb5c17102cd3c32a8538d32e9f01309b8c38fb Mon Sep 17 00:00:00 2001 From: Tanza Date: Thu, 28 Sep 2023 18:24:46 +0100 Subject: [PATCH 04/40] Move sidebar by 10px for more even padding (#1543) --- src/view/shell/desktop/RightNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 12ca256d21..d7dd0e6ada 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -123,7 +123,7 @@ const styles = StyleSheet.create({ position: 'absolute', top: 20, // @ts-ignore web only - left: 'calc(50vw + 310px)', + left: 'calc(50vw + 320px)', width: 304, }, From d57c8f7bca2afdac526fdd4ada4a297908ed02e9 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 28 Sep 2023 10:32:34 -0700 Subject: [PATCH 05/40] Fix pointer --- src/view/com/notifications/FeedItem.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index ff5ec9f504..c51335c727 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -241,7 +241,9 @@ export const FeedItem = observer(function FeedItemImpl({ {action} {({timeElapsed}) => ( - + {' ' + timeElapsed} )} @@ -448,6 +450,10 @@ const styles = StyleSheet.create({ overflowHidden: { overflow: 'hidden', }, + pointer: { + // @ts-ignore web only + cursor: 'pointer', + }, outer: { padding: 10, From 16763d1d4118292432678ef256226139c0be73c1 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 28 Sep 2023 10:33:14 -0700 Subject: [PATCH 06/40] Handle long feed listings on the right nav with scrolling (#1562) --- src/view/shell/desktop/Feeds.tsx | 5 +++-- src/view/shell/desktop/RightNav.tsx | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/view/shell/desktop/Feeds.tsx b/src/view/shell/desktop/Feeds.tsx index 4da1401c34..3f20638872 100644 --- a/src/view/shell/desktop/Feeds.tsx +++ b/src/view/shell/desktop/Feeds.tsx @@ -82,11 +82,12 @@ function FeedItem({ const styles = StyleSheet.create({ container: { - position: 'relative', + flex: 1, + overflowY: 'auto', width: 300, paddingHorizontal: 12, + paddingVertical: 18, borderTopWidth: 1, borderBottomWidth: 1, - paddingVertical: 18, }, }) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index d7dd0e6ada..84d7d78547 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -125,6 +125,8 @@ const styles = StyleSheet.create({ // @ts-ignore web only left: 'calc(50vw + 320px)', width: 304, + // @ts-ignore web only + maxHeight: '90vh', }, message: { From cd3b0e54fbefa6c38ae6ad81198c8d766baee2c5 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 28 Sep 2023 12:08:00 -0700 Subject: [PATCH 07/40] Email verification and change flows (#1560) * fix 'Reposted by' text overflow * Add email verification flow * Implement change email flow * Add verify email reminder on load * Bump @atproto/api@0.6.20 * Trim the inputs * Accessibility fixes * Fix typo * Fix: include the day in the sharding check * Update auto behaviors * Update yarn.lock * Temporary error message --------- Co-authored-by: Eric Bailey --- package.json | 2 +- src/lib/strings/helpers.ts | 17 ++ src/state/models/root-store.ts | 6 + src/state/models/session.ts | 16 ++ src/state/models/ui/reminders.ts | 65 ++++++ src/state/models/ui/shell.ts | 22 +++ src/view/com/modals/ChangeEmail.tsx | 280 ++++++++++++++++++++++++++ src/view/com/modals/Confirm.tsx | 3 +- src/view/com/modals/Modal.tsx | 8 + src/view/com/modals/Modal.web.tsx | 6 + src/view/com/modals/VerifyEmail.tsx | 296 ++++++++++++++++++++++++++++ src/view/com/util/forms/Button.tsx | 14 +- src/view/screens/Settings.tsx | 83 +++++++- yarn.lock | 52 ++++- 14 files changed, 855 insertions(+), 15 deletions(-) create mode 100644 src/state/models/ui/reminders.ts create mode 100644 src/view/com/modals/ChangeEmail.tsx create mode 100644 src/view/com/modals/VerifyEmail.tsx diff --git a/package.json b/package.json index 32ca3de7aa..28e9a69927 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "build:apk": "eas build -p android --profile dev-android-apk" }, "dependencies": { - "@atproto/api": "^0.6.16", + "@atproto/api": "^0.6.20", "@bam.tech/react-native-image-resizer": "^3.0.4", "@braintree/sanitize-url": "^6.0.2", "@emoji-mart/react": "^1.1.1", diff --git a/src/lib/strings/helpers.ts b/src/lib/strings/helpers.ts index 183d53e317..ef93a366f9 100644 --- a/src/lib/strings/helpers.ts +++ b/src/lib/strings/helpers.ts @@ -15,3 +15,20 @@ export function enforceLen(str: string, len: number, ellipsis = false): string { } return str } + +// https://stackoverflow.com/a/52171480 +export function toHashCode(str: string, seed = 0): number { + let h1 = 0xdeadbeef ^ seed, + h2 = 0x41c6ce57 ^ seed + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i) + h1 = Math.imul(h1 ^ ch, 2654435761) + h2 = Math.imul(h2 ^ ch, 1597334677) + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909) + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909) + + return 4294967296 * (2097151 & h2) + (h1 >>> 0) +} diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 1a81072a25..363a81c0f7 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -21,6 +21,7 @@ import {PreferencesModel} from './ui/preferences' import {resetToTab} from '../../Navigation' import {ImageSizesCache} from './cache/image-sizes' import {MutedThreads} from './muted-threads' +import {Reminders} from './ui/reminders' import {reset as resetNavigation} from '../../Navigation' // TEMPORARY (APP-700) @@ -53,6 +54,7 @@ export class RootStoreModel { linkMetas = new LinkMetasCache(this) imageSizes = new ImageSizesCache() mutedThreads = new MutedThreads() + reminders = new Reminders(this) constructor(agent: BskyAgent) { this.agent = agent @@ -77,6 +79,7 @@ export class RootStoreModel { preferences: this.preferences.serialize(), invitedUsers: this.invitedUsers.serialize(), mutedThreads: this.mutedThreads.serialize(), + reminders: this.reminders.serialize(), } } @@ -109,6 +112,9 @@ export class RootStoreModel { if (hasProp(v, 'mutedThreads')) { this.mutedThreads.hydrate(v.mutedThreads) } + if (hasProp(v, 'reminders')) { + this.reminders.hydrate(v.reminders) + } } } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index 1bc722c8cc..7cd3c1222f 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -30,6 +30,7 @@ export const accountData = z.object({ email: z.string().optional(), displayName: z.string().optional(), aviUrl: z.string().optional(), + emailConfirmed: z.boolean().optional(), }) export type AccountData = z.infer @@ -106,6 +107,10 @@ export class SessionModel { return this.accounts.filter(acct => acct.did !== this.data?.did) } + get emailNeedsConfirmation() { + return !this.currentSession?.emailConfirmed + } + get isSandbox() { if (!this.data) { return false @@ -217,6 +222,7 @@ export class SessionModel { ? addedInfo.displayName : existingAccount?.displayName || '', aviUrl: addedInfo ? addedInfo.aviUrl : existingAccount?.aviUrl || '', + emailConfirmed: session?.emailConfirmed, } if (!existingAccount) { this.accounts.push(newAccount) @@ -246,6 +252,8 @@ export class SessionModel { did: acct.did, displayName: acct.displayName, aviUrl: acct.aviUrl, + email: acct.email, + emailConfirmed: acct.emailConfirmed, })) } @@ -297,6 +305,8 @@ export class SessionModel { refreshJwt: account.refreshJwt || '', did: account.did, handle: account.handle, + email: account.email, + emailConfirmed: account.emailConfirmed, }), ) const addedInfo = await this.loadAccountInfo(agent, account.did) @@ -452,4 +462,10 @@ export class SessionModel { await this.rootStore.me.load() } } + + updateLocalAccountData(changes: Partial) { + this.accounts = this.accounts.map(acct => + acct.did === this.data?.did ? {...acct, ...changes} : acct, + ) + } } diff --git a/src/state/models/ui/reminders.ts b/src/state/models/ui/reminders.ts new file mode 100644 index 0000000000..f8becdec32 --- /dev/null +++ b/src/state/models/ui/reminders.ts @@ -0,0 +1,65 @@ +import {makeAutoObservable} from 'mobx' +import {isObj, hasProp} from 'lib/type-guards' +import {RootStoreModel} from '../root-store' +import {toHashCode} from 'lib/strings/helpers' + +const DAY = 60e3 * 24 * 1 // 1 day (ms) + +export class Reminders { + // NOTE + // by defaulting to the current date, we ensure that the user won't be nagged + // on first run (aka right after creating an account) + // -prf + lastEmailConfirm: Date = new Date() + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable( + this, + {serialize: false, hydrate: false}, + {autoBind: true}, + ) + } + + serialize() { + return { + lastEmailConfirm: this.lastEmailConfirm + ? this.lastEmailConfirm.toISOString() + : undefined, + } + } + + hydrate(v: unknown) { + if ( + isObj(v) && + hasProp(v, 'lastEmailConfirm') && + typeof v.lastEmailConfirm === 'string' + ) { + this.lastEmailConfirm = new Date(v.lastEmailConfirm) + } + } + + get shouldRequestEmailConfirmation() { + const sess = this.rootStore.session.currentSession + if (!sess) { + return false + } + if (sess.emailConfirmed) { + return false + } + const today = new Date() + // shard the users into 2 day of the week buckets + // (this is to avoid a sudden influx of email updates when + // this feature rolls out) + const code = toHashCode(sess.did) % 7 + if (code !== today.getDay() && code !== (today.getDay() + 1) % 7) { + return false + } + // only ask once a day at most, but because of the bucketing + // this will be more like weekly + return Number(today) - Number(this.lastEmailConfirm) > DAY + } + + setEmailConfirmationRequested() { + this.lastEmailConfirm = new Date() + } +} diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index 6475135633..15d92f9272 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -24,6 +24,7 @@ export interface ConfirmModal { onPressCancel?: () => void | Promise confirmBtnText?: string confirmBtnStyle?: StyleProp + cancelBtnText?: string } export interface EditProfileModal { @@ -140,6 +141,15 @@ export interface BirthDateSettingsModal { name: 'birth-date-settings' } +export interface VerifyEmailModal { + name: 'verify-email' + showReminder?: boolean +} + +export interface ChangeEmailModal { + name: 'change-email' +} + export type Modal = // Account | AddAppPasswordModal @@ -148,6 +158,8 @@ export type Modal = | EditProfileModal | ProfilePreviewModal | BirthDateSettingsModal + | VerifyEmailModal + | ChangeEmailModal // Curation | ContentFilteringSettingsModal @@ -250,6 +262,7 @@ export class ShellUiModel { }) this.setupClock() + this.setupLoginModals() } serialize(): unknown { @@ -375,4 +388,13 @@ export class ShellUiModel { }) }, 60_000) } + + setupLoginModals() { + this.rootStore.onSessionReady(() => { + if (this.rootStore.reminders.shouldRequestEmailConfirmation) { + this.openModal({name: 'verify-email', showReminder: true}) + this.rootStore.reminders.setEmailConfirmationRequested() + } + }) + } } diff --git a/src/view/com/modals/ChangeEmail.tsx b/src/view/com/modals/ChangeEmail.tsx new file mode 100644 index 0000000000..c92dabdcac --- /dev/null +++ b/src/view/com/modals/ChangeEmail.tsx @@ -0,0 +1,280 @@ +import React, {useState} from 'react' +import { + ActivityIndicator, + KeyboardAvoidingView, + SafeAreaView, + StyleSheet, + View, +} from 'react-native' +import {ScrollView, TextInput} from './util' +import {observer} from 'mobx-react-lite' +import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' +import {ErrorMessage} from '../util/error/ErrorMessage' +import * as Toast from '../util/Toast' +import {useStores} from 'state/index' +import {s, colors} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {isWeb} from 'platform/detection' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {cleanError} from 'lib/strings/errors' + +enum Stages { + InputEmail, + ConfirmCode, + Done, +} + +export const snapPoints = ['90%'] + +export const Component = observer(function Component({}: {}) { + const pal = usePalette('default') + const store = useStores() + const [stage, setStage] = useState(Stages.InputEmail) + const [email, setEmail] = useState( + store.session.currentSession?.email || '', + ) + const [confirmationCode, setConfirmationCode] = useState('') + const [isProcessing, setIsProcessing] = useState(false) + const [error, setError] = useState('') + const {isMobile} = useWebMediaQueries() + + const onRequestChange = async () => { + if (email === store.session.currentSession?.email) { + setError('Enter your new email above') + return + } + setError('') + setIsProcessing(true) + try { + const res = await store.agent.com.atproto.server.requestEmailUpdate() + if (res.data.tokenRequired) { + setStage(Stages.ConfirmCode) + } else { + await store.agent.com.atproto.server.updateEmail({email: email.trim()}) + store.session.updateLocalAccountData({ + email: email.trim(), + emailConfirmed: false, + }) + Toast.show('Email updated') + setStage(Stages.Done) + } + } catch (e) { + let err = cleanError(String(e)) + // TEMP + // while rollout is occuring, we're giving a temporary error message + // you can remove this any time after Oct2023 + // -prf + if (err === 'email must be confirmed (temporary)') { + err = `Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed.` + } + setError(err) + } finally { + setIsProcessing(false) + } + } + + const onConfirm = async () => { + setError('') + setIsProcessing(true) + try { + await store.agent.com.atproto.server.updateEmail({ + email: email.trim(), + token: confirmationCode.trim(), + }) + store.session.updateLocalAccountData({ + email: email.trim(), + emailConfirmed: false, + }) + Toast.show('Email updated') + setStage(Stages.Done) + } catch (e) { + setError(cleanError(String(e))) + } finally { + setIsProcessing(false) + } + } + + const onVerify = async () => { + store.shell.closeModal() + store.shell.openModal({name: 'verify-email'}) + } + + return ( + + + + + + {stage === Stages.InputEmail ? 'Change Your Email' : ''} + {stage === Stages.ConfirmCode ? 'Security Step Required' : ''} + {stage === Stages.Done ? 'Email Updated' : ''} + + + + + {stage === Stages.InputEmail ? ( + <>Enter your new email address below. + ) : stage === Stages.ConfirmCode ? ( + <> + An email has been sent to your previous address,{' '} + {store.session.currentSession?.email || ''}. It includes a + confirmation code which you can enter below. + + ) : ( + <> + Your email has been updated but not verified. As a next step, + please verify your new email. + + )} + + + {stage === Stages.InputEmail && ( + + )} + {stage === Stages.ConfirmCode && ( + + )} + + {error ? ( + + ) : undefined} + + + {isProcessing ? ( + + + + ) : ( + + {stage === Stages.InputEmail && ( + + - - You can also discover new Custom Feeds to follow. - - + + ) } const styles = StyleSheet.create({ - emptyContainer: { + container: { height: '100%', + flexDirection: 'row', + justifyContent: 'center', paddingVertical: 40, paddingHorizontal: 30, }, - emptyIconContainer: { + inner: { + maxWidth: 460, + }, + iconContainer: { marginBottom: 16, }, - emptyIcon: { + icon: { marginLeft: 'auto', marginRight: 'auto', }, @@ -94,13 +107,4 @@ const styles = StyleSheet.create({ paddingHorizontal: 24, borderRadius: 30, }, - - feedsTip: { - position: 'absolute', - left: 22, - }, - feedsTipArrow: { - marginLeft: 32, - marginTop: 8, - }, }) diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx new file mode 100644 index 0000000000..48724d8b3e --- /dev/null +++ b/src/view/com/posts/FollowingEndOfFeed.tsx @@ -0,0 +1,100 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {useNavigation} from '@react-navigation/native' +import { + FontAwesomeIcon, + FontAwesomeIconStyle, +} from '@fortawesome/react-native-fontawesome' +import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' +import {NavigationProp} from 'lib/routes/types' +import {usePalette} from 'lib/hooks/usePalette' +import {s} from 'lib/styles' +import {isWeb} from 'platform/detection' + +export function FollowingEndOfFeed() { + const pal = usePalette('default') + const palInverted = usePalette('inverted') + const navigation = useNavigation() + + const onPressFindAccounts = React.useCallback(() => { + if (isWeb) { + navigation.navigate('Search', {}) + } else { + navigation.navigate('SearchTab') + navigation.popToTop() + } + }, [navigation]) + + const onPressDiscoverFeeds = React.useCallback(() => { + if (isWeb) { + navigation.navigate('Feeds') + } else { + navigation.navigate('FeedsTab') + navigation.popToTop() + } + }, [navigation]) + + return ( + + + + You've reached the end of your feed! Find some more accounts to + follow. + + + + + You can also discover new Custom Feeds to follow. + + + + + ) +} +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'center', + paddingTop: 40, + paddingBottom: 80, + paddingHorizontal: 30, + borderTopWidth: 1, + }, + inner: { + maxWidth: 460, + }, + emptyBtn: { + marginVertical: 20, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingVertical: 18, + paddingHorizontal: 24, + borderRadius: 30, + }, +}) diff --git a/src/view/com/search/HeaderWithInput.tsx b/src/view/com/search/HeaderWithInput.tsx index f04175afda..6bd1b2f00b 100644 --- a/src/view/com/search/HeaderWithInput.tsx +++ b/src/view/com/search/HeaderWithInput.tsx @@ -93,7 +93,7 @@ export function HeaderWithInput({ onBlur={() => setIsInputFocused(false)} onChangeText={onChangeQuery} onSubmitEditing={onSubmitQuery} - autoFocus={isMobile} + autoFocus={false} accessibilityRole="search" accessibilityLabel="Search" accessibilityHint="" diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx index c7374e195c..529435cf12 100644 --- a/src/view/com/util/ErrorBoundary.tsx +++ b/src/view/com/util/ErrorBoundary.tsx @@ -28,7 +28,7 @@ export class ErrorBoundary extends Component { public render() { if (this.state.hasError) { return ( - + }, []) + const renderFollowingEndOfFeed = React.useCallback(() => { + return + }, []) + const renderCustomFeedEmptyState = React.useCallback(() => { return }, []) @@ -127,6 +132,7 @@ export const HomeScreen = withAuthRequired( isPageFocused={selectedPage === 0} feed={store.me.mainFeed} renderEmptyState={renderFollowingEmptyState} + renderEndOfFeed={renderFollowingEndOfFeed} /> {customFeeds.map((f, index) => { return ( @@ -149,11 +155,13 @@ const FeedPage = observer(function FeedPageImpl({ isPageFocused, feed, renderEmptyState, + renderEndOfFeed, }: { testID?: string feed: PostsFeedModel isPageFocused: boolean renderEmptyState?: () => JSX.Element + renderEndOfFeed?: () => JSX.Element }) { const store = useStores() const pal = usePalette('default') @@ -307,6 +315,7 @@ const FeedPage = observer(function FeedPageImpl({ onScroll={onMainScroll} scrollEventThrottle={100} renderEmptyState={renderEmptyState} + renderEndOfFeed={renderEndOfFeed} ListHeaderComponent={ListHeaderComponent} headerOffset={headerOffset} /> From c6da98c8184f6649c7dbd92c903c6869d9c77f24 Mon Sep 17 00:00:00 2001 From: Baasbase Date: Wed, 4 Oct 2023 17:58:01 +0200 Subject: [PATCH 25/40] Fixed a typo on the onboarding recommended screen (#1604) --- src/view/com/auth/onboarding/RecommendedFeeds.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.tsx index 24fc9eef1a..aaba19c802 100644 --- a/src/view/com/auth/onboarding/RecommendedFeeds.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeeds.tsx @@ -65,7 +65,7 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ tdStyles.title2, isTabletOrMobile && tdStyles.title2Small, ]}> - Recomended + Recommended Date: Wed, 4 Oct 2023 10:17:13 -0700 Subject: [PATCH 26/40] Update to react-native@0.72.5 (#1599) * Update to react-native@0.72.5 * Fix the patch --- package.json | 2 +- ...0.72.4.patch => react-native+0.72.5.patch} | 0 yarn.lock | 432 +++++++----------- 3 files changed, 175 insertions(+), 259 deletions(-) rename patches/{react-native+0.72.4.patch => react-native+0.72.5.patch} (100%) diff --git a/package.json b/package.json index 3f99aea609..cd8518738f 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "react-avatar-editor": "^13.0.0", "react-circular-progressbar": "^2.1.0", "react-dom": "^18.2.0", - "react-native": "0.72.4", + "react-native": "0.72.5", "react-native-appstate-hook": "^1.0.6", "react-native-draggable-flatlist": "^4.0.1", "react-native-drawer-layout": "^3.2.0", diff --git a/patches/react-native+0.72.4.patch b/patches/react-native+0.72.5.patch similarity index 100% rename from patches/react-native+0.72.4.patch rename to patches/react-native+0.72.5.patch diff --git a/yarn.lock b/yarn.lock index a4c1cbb612..8e92fcd2b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3223,44 +3223,44 @@ resolved "https://registry.yarnpkg.com/@react-native-community/blur/-/blur-4.3.2.tgz#185a2c7dd03ba168cc95069bc4742e9505fd6c6c" integrity sha512-0ID+pyZKdC4RdgC7HePxUQ6JmsbNrgz03u+6SgqYpmBoK/rE+7JffqIw7IEsfoKitLEcRNLGekIBsfwCqiEkew== -"@react-native-community/cli-clean@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-11.3.6.tgz#43a06cbee1a5480da804debc4f94662a197720f2" - integrity sha512-jOOaeG5ebSXTHweq1NznVJVAFKtTFWL4lWgUXl845bCGX7t1lL8xQNWHKwT8Oh1pGR2CI3cKmRjY4hBg+pEI9g== +"@react-native-community/cli-clean@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-11.3.7.tgz#cb4c2f225f78593412c2d191b55b8570f409a48f" + integrity sha512-twtsv54ohcRyWVzPXL3F9VHGb4Qhn3slqqRs3wEuRzjR7cTmV2TIO2b1VhaqF4HlCgNd+cGuirvLtK2JJyaxMg== dependencies: - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" execa "^5.0.0" prompts "^2.4.0" -"@react-native-community/cli-config@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-11.3.6.tgz#6d3636a8a3c4542ebb123eaf61bbbc0c2a1d2a6b" - integrity sha512-edy7fwllSFLan/6BG6/rznOBCLPrjmJAE10FzkEqNLHowi0bckiAPg1+1jlgQ2qqAxV5kuk+c9eajVfQvPLYDA== +"@react-native-community/cli-config@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-11.3.7.tgz#4ce95548252ecb094b576369abebf9867c95d277" + integrity sha512-FDBLku9xskS+bx0YFJFLCmUJhEZ4/MMSC9qPYOGBollWYdgE7k/TWI0IeYFmMALAnbCdKQAYP5N29N55Tad8lg== dependencies: - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" cosmiconfig "^5.1.0" deepmerge "^4.3.0" glob "^7.1.3" joi "^17.2.1" -"@react-native-community/cli-debugger-ui@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.6.tgz#1eb2276450f270a938686b49881fe232a08c01c4" - integrity sha512-jhMOSN/iOlid9jn/A2/uf7HbC3u7+lGktpeGSLnHNw21iahFBzcpuO71ekEdlmTZ4zC/WyxBXw9j2ka33T358w== +"@react-native-community/cli-debugger-ui@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-11.3.7.tgz#2147b73313af8de3c9b396406d5d344b904cf2bb" + integrity sha512-aVmKuPKHZENR8SrflkMurZqeyLwbKieHdOvaZCh1Nn/0UC5CxWcyST2DB2XQboZwsvr3/WXKJkSUO+SZ1J9qTQ== dependencies: serve-static "^1.13.1" -"@react-native-community/cli-doctor@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-11.3.6.tgz#fa33ee00fe5120af516aa0f17fe3ad50270976e7" - integrity sha512-UT/Tt6omVPi1j6JEX+CObc85eVFghSZwy4GR9JFMsO7gNg2Tvcu1RGWlUkrbmWMAMHw127LUu6TGK66Ugu1NLA== +"@react-native-community/cli-doctor@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-11.3.7.tgz#7d5f5b1aea78134bba713fa97795986345ff1344" + integrity sha512-YEHUqWISOHnsl5+NM14KHelKh68Sr5/HeEZvvNdIcvcKtZic3FU7Xd1WcbNdo3gCq5JvzGFfufx02Tabh5zmrg== dependencies: - "@react-native-community/cli-config" "11.3.6" - "@react-native-community/cli-platform-android" "11.3.6" - "@react-native-community/cli-platform-ios" "11.3.6" - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-config" "11.3.7" + "@react-native-community/cli-platform-android" "11.3.7" + "@react-native-community/cli-platform-ios" "11.3.7" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" command-exists "^1.2.8" envinfo "^7.7.2" @@ -3276,64 +3276,64 @@ wcwidth "^1.0.1" yaml "^2.2.1" -"@react-native-community/cli-hermes@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-11.3.6.tgz#b1acc7feff66ab0859488e5812b3b3e8b8e9434c" - integrity sha512-O55YAYGZ3XynpUdePPVvNuUPGPY0IJdctLAOHme73OvS80gNwfntHDXfmY70TGHWIfkK2zBhA0B+2v8s5aTyTA== +"@react-native-community/cli-hermes@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-11.3.7.tgz#091e730a1f8bace6c3729e8744bad6141002e0e8" + integrity sha512-chkKd8n/xeZkinRvtH6QcYA8rjNOKU3S3Lw/3Psxgx+hAYV0Gyk95qJHTalx7iu+PwjOOqqvCkJo5jCkYLkoqw== dependencies: - "@react-native-community/cli-platform-android" "11.3.6" - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-platform-android" "11.3.7" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" hermes-profile-transformer "^0.0.6" ip "^1.1.5" -"@react-native-community/cli-platform-android@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.6.tgz#6f3581ca4eed3deec7edba83c1bc467098c8167b" - integrity sha512-ZARrpLv5tn3rmhZc//IuDM1LSAdYnjUmjrp58RynlvjLDI4ZEjBAGCQmgysRgXAsK7ekMrfkZgemUczfn9td2A== +"@react-native-community/cli-platform-android@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-11.3.7.tgz#7845bc48258b6bb55df208a23b3690647f113995" + integrity sha512-WGtXI/Rm178UQb8bu1TAeFC/RJvYGnbHpULXvE20GkmeJ1HIrMjkagyk6kkY3Ej25JAP2R878gv+TJ/XiRhaEg== dependencies: - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" execa "^5.0.0" glob "^7.1.3" logkitty "^0.7.1" -"@react-native-community/cli-platform-ios@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.6.tgz#0fa58d01f55d85618c4218925509a4be77867dab" - integrity sha512-tZ9VbXWiRW+F+fbZzpLMZlj93g3Q96HpuMsS6DRhrTiG+vMQ3o6oPWSEEmMGOvJSYU7+y68Dc9ms2liC7VD6cw== +"@react-native-community/cli-platform-ios@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-11.3.7.tgz#87478f907634713b7236c77870446a5ca1f35ff1" + integrity sha512-Z/8rseBput49EldX7MogvN6zJlWzZ/4M97s2P+zjS09ZoBU7I0eOKLi0N9wx+95FNBvGQQ/0P62bB9UaFQH2jw== dependencies: - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" execa "^5.0.0" fast-xml-parser "^4.0.12" glob "^7.1.3" ora "^5.4.1" -"@react-native-community/cli-plugin-metro@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.6.tgz#2d632c304313435c9ea104086901fbbeba0f1882" - integrity sha512-D97racrPX3069ibyabJNKw9aJpVcaZrkYiEzsEnx50uauQtPDoQ1ELb/5c6CtMhAEGKoZ0B5MS23BbsSZcLs2g== +"@react-native-community/cli-plugin-metro@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-11.3.7.tgz#2e8a9deb30b40495c5c1347a1837a824400fa00f" + integrity sha512-0WhgoBVGF1f9jXcuagQmtxpwpfP+2LbLZH4qMyo6OtYLWLG13n2uRep+8tdGzfNzl1bIuUTeE9yZSAdnf9LfYQ== dependencies: - "@react-native-community/cli-server-api" "11.3.6" - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-server-api" "11.3.7" + "@react-native-community/cli-tools" "11.3.7" chalk "^4.1.2" execa "^5.0.0" - metro "0.76.7" - metro-config "0.76.7" - metro-core "0.76.7" - metro-react-native-babel-transformer "0.76.7" - metro-resolver "0.76.7" - metro-runtime "0.76.7" + metro "0.76.8" + metro-config "0.76.8" + metro-core "0.76.8" + metro-react-native-babel-transformer "0.76.8" + metro-resolver "0.76.8" + metro-runtime "0.76.8" readline "^1.3.0" -"@react-native-community/cli-server-api@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-11.3.6.tgz#3a16039518f7f3865f85f8f54b19174448bbcdbb" - integrity sha512-8GUKodPnURGtJ9JKg8yOHIRtWepPciI3ssXVw5jik7+dZ43yN8P5BqCoDaq8e1H1yRer27iiOfT7XVnwk8Dueg== +"@react-native-community/cli-server-api@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-11.3.7.tgz#2cce54b3331c9c51b9067129c297ab2e9a142216" + integrity sha512-yoFyGdvR3HxCnU6i9vFqKmmSqFzCbnFSnJ29a+5dppgPRetN+d//O8ard/YHqHzToFnXutAFf2neONn23qcJAg== dependencies: - "@react-native-community/cli-debugger-ui" "11.3.6" - "@react-native-community/cli-tools" "11.3.6" + "@react-native-community/cli-debugger-ui" "11.3.7" + "@react-native-community/cli-tools" "11.3.7" compression "^1.7.1" connect "^3.6.5" errorhandler "^1.5.1" @@ -3342,10 +3342,10 @@ serve-static "^1.13.1" ws "^7.5.1" -"@react-native-community/cli-tools@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-11.3.6.tgz#ec213b8409917a56e023595f148c84b9cb3ad871" - integrity sha512-JpmUTcDwAGiTzLsfMlIAYpCMSJ9w2Qlf7PU7mZIRyEu61UzEawyw83DkqfbzDPBuRwRnaeN44JX2CP/yTO3ThQ== +"@react-native-community/cli-tools@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-11.3.7.tgz#37aa7efc7b4a1b7077d541f1d7bb11a2ab7b6ff2" + integrity sha512-peyhP4TV6Ps1hk+MBHTFaIR1eI3u+OfGBvr5r0wPwo3FAJvldRinMgcB/TcCcOBXVORu7ba1XYjkubPeYcqAyA== dependencies: appdirsjs "^1.2.4" chalk "^4.1.2" @@ -3357,27 +3357,27 @@ semver "^7.5.2" shell-quote "^1.7.3" -"@react-native-community/cli-types@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-11.3.6.tgz#34012f1d0cb1c4039268828abc07c9c69f2e15be" - integrity sha512-6DxjrMKx5x68N/tCJYVYRKAtlRHbtUVBZrnAvkxbRWFD9v4vhNgsPM0RQm8i2vRugeksnao5mbnRGpS6c0awCw== +"@react-native-community/cli-types@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-types/-/cli-types-11.3.7.tgz#12fe7cff3da08bd27e11116531b2e001939854b9" + integrity sha512-OhSr/TiDQkXjL5YOs8+hvGSB+HltLn5ZI0+A3DCiMsjUgTTsYh+Z63OtyMpNjrdCEFcg0MpfdU2uxstCS6Dc5g== dependencies: joi "^17.2.1" -"@react-native-community/cli@11.3.6": - version "11.3.6" - resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-11.3.6.tgz#d92618d75229eaf6c0391a6b075684eba5d9819f" - integrity sha512-bdwOIYTBVQ9VK34dsf6t3u6vOUU5lfdhKaAxiAVArjsr7Je88Bgs4sAbsOYsNK3tkE8G77U6wLpekknXcanlww== +"@react-native-community/cli@11.3.7": + version "11.3.7" + resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-11.3.7.tgz#564c0054269d8385fa9d301750b2e56dbb5c0cc9" + integrity sha512-Ou8eDlF+yh2rzXeCTpMPYJ2fuqsusNOhmpYPYNQJQ2h6PvaF30kPomflgRILems+EBBuggRtcT+I+1YH4o/q6w== dependencies: - "@react-native-community/cli-clean" "11.3.6" - "@react-native-community/cli-config" "11.3.6" - "@react-native-community/cli-debugger-ui" "11.3.6" - "@react-native-community/cli-doctor" "11.3.6" - "@react-native-community/cli-hermes" "11.3.6" - "@react-native-community/cli-plugin-metro" "11.3.6" - "@react-native-community/cli-server-api" "11.3.6" - "@react-native-community/cli-tools" "11.3.6" - "@react-native-community/cli-types" "11.3.6" + "@react-native-community/cli-clean" "11.3.7" + "@react-native-community/cli-config" "11.3.7" + "@react-native-community/cli-debugger-ui" "11.3.7" + "@react-native-community/cli-doctor" "11.3.7" + "@react-native-community/cli-hermes" "11.3.7" + "@react-native-community/cli-plugin-metro" "11.3.7" + "@react-native-community/cli-server-api" "11.3.7" + "@react-native-community/cli-tools" "11.3.7" + "@react-native-community/cli-types" "11.3.7" chalk "^4.1.2" commander "^9.4.1" execa "^5.0.0" @@ -3438,10 +3438,10 @@ resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.72.0.tgz#c82a76a1d86ec0c3907be76f7faf97a32bbed05d" integrity sha512-Im93xRJuHHxb1wniGhBMsxLwcfzdYreSZVQGDoMJgkd6+Iky61LInGEHnQCTN0fKNYF1Dvcofb4uMmE1RQHXHQ== -"@react-native/codegen@^0.72.6": - version "0.72.6" - resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.72.6.tgz#029cf61f82f5c6872f0b2ce58f27c4239a5586c8" - integrity sha512-idTVI1es/oopN0jJT/0jB6nKdvTUKE3757zA5+NPXZTeB46CIRbmmos4XBiAec8ufu9/DigLPbHTYAaMNZJ6Ig== +"@react-native/codegen@^0.72.7": + version "0.72.7" + resolved "https://registry.yarnpkg.com/@react-native/codegen/-/codegen-0.72.7.tgz#b6832ce631ac63143024ea094a6b5480a780e589" + integrity sha512-O7xNcGeXGbY+VoqBGNlZ3O05gxfATlwE1Q1qQf5E38dK+tXn5BY4u0jaQ9DPjfE8pBba8g/BYI1N44lynidMtg== dependencies: "@babel/parser" "^7.20.0" flow-parser "^0.206.0" @@ -13003,53 +13003,53 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -metro-babel-transformer@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.76.7.tgz#ba620d64cbaf97d1aa14146d654a3e5d7477fc62" - integrity sha512-bgr2OFn0J4r0qoZcHrwEvccF7g9k3wdgTOgk6gmGHrtlZ1Jn3oCpklW/DfZ9PzHfjY2mQammKTc19g/EFGyOJw== +metro-babel-transformer@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.76.8.tgz#5efd1027353b36b73706164ef09c290dceac096a" + integrity sha512-Hh6PW34Ug/nShlBGxkwQJSgPGAzSJ9FwQXhUImkzdsDgVu6zj5bx258J8cJVSandjNoQ8nbaHK6CaHlnbZKbyA== dependencies: "@babel/core" "^7.20.0" hermes-parser "0.12.0" nullthrows "^1.1.1" -metro-cache-key@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.76.7.tgz#70913f43b92b313096673c37532edd07438cb325" - integrity sha512-0pecoIzwsD/Whn/Qfa+SDMX2YyasV0ndbcgUFx7w1Ct2sLHClujdhQ4ik6mvQmsaOcnGkIyN0zcceMDjC2+BFQ== +metro-cache-key@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-cache-key/-/metro-cache-key-0.76.8.tgz#8a0a5e991c06f56fcc584acadacb313c312bdc16" + integrity sha512-buKQ5xentPig9G6T37Ww/R/bC+/V1MA5xU/D8zjnhlelsrPG6w6LtHUS61ID3zZcMZqYaELWk5UIadIdDsaaLw== -metro-cache@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.76.7.tgz#e49e51423fa960df4eeff9760d131f03e003a9eb" - integrity sha512-nWBMztrs5RuSxZRI7hgFgob5PhYDmxICh9FF8anm9/ito0u0vpPvRxt7sRu8fyeD2AHdXqE7kX32rWY0LiXgeg== +metro-cache@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.76.8.tgz#296c1c189db2053b89735a8f33dbe82575f53661" + integrity sha512-QBJSJIVNH7Hc/Yo6br/U/qQDUpiUdRgZ2ZBJmvAbmAKp2XDzsapnMwK/3BGj8JNWJF7OLrqrYHsRsukSbUBpvQ== dependencies: - metro-core "0.76.7" + metro-core "0.76.8" rimraf "^3.0.2" -metro-config@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.76.7.tgz#f0fc171707523aa7d3a9311550872136880558c0" - integrity sha512-CFDyNb9bqxZemiChC/gNdXZ7OQkIwmXzkrEXivcXGbgzlt/b2juCv555GWJHyZSlorwnwJfY3uzAFu4A9iRVfg== +metro-config@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.76.8.tgz#20bd5397fcc6096f98d2a813a7cecb38b8af062d" + integrity sha512-SL1lfKB0qGHALcAk2zBqVgQZpazDYvYFGwCK1ikz0S6Y/CM2i2/HwuZN31kpX6z3mqjv/6KvlzaKoTb1otuSAA== dependencies: connect "^3.6.5" cosmiconfig "^5.0.5" jest-validate "^29.2.1" - metro "0.76.7" - metro-cache "0.76.7" - metro-core "0.76.7" - metro-runtime "0.76.7" + metro "0.76.8" + metro-cache "0.76.8" + metro-core "0.76.8" + metro-runtime "0.76.8" -metro-core@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.76.7.tgz#5d2b8bac2cde801dc22666ad7be1336d1f021b61" - integrity sha512-0b8KfrwPmwCMW+1V7ZQPkTy2tsEKZjYG9Pu1PTsu463Z9fxX7WaR0fcHFshv+J1CnQSUTwIGGjbNvj1teKe+pw== +metro-core@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.76.8.tgz#917c8157c63406cb223522835abb8e7c6291dcad" + integrity sha512-sl2QLFI3d1b1XUUGxwzw/KbaXXU/bvFYrSKz6Sg19AdYGWFyzsgZ1VISRIDf+HWm4R/TJXluhWMEkEtZuqi3qA== dependencies: lodash.throttle "^4.1.1" - metro-resolver "0.76.7" + metro-resolver "0.76.8" -metro-file-map@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.76.7.tgz#0f041a4f186ac672f0188180310609c8483ffe89" - integrity sha512-s+zEkTcJ4mOJTgEE2ht4jIo1DZfeWreQR3tpT3gDV/Y/0UQ8aJBTv62dE775z0GLsWZApiblAYZsj7ZE8P06nw== +metro-file-map@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-file-map/-/metro-file-map-0.76.8.tgz#a1db1185b6c316904ba6b53d628e5d1323991d79" + integrity sha512-A/xP1YNEVwO1SUV9/YYo6/Y1MmzhL4ZnVgcJC3VmHp/BYVOXVStzgVbWv2wILe56IIMkfXU+jpXrGKKYhFyHVw== dependencies: anymatch "^3.0.3" debug "^2.2.0" @@ -13066,10 +13066,10 @@ metro-file-map@0.76.7: optionalDependencies: fsevents "^2.3.2" -metro-inspector-proxy@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.76.7.tgz#c067df25056e932002a72a4b45cf7b4b749f808e" - integrity sha512-rNZ/6edTl/1qUekAhAbaFjczMphM50/UjtxiKulo6vqvgn/Mjd9hVqDvVYfAMZXqPvlusD88n38UjVYPkruLSg== +metro-inspector-proxy@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-inspector-proxy/-/metro-inspector-proxy-0.76.8.tgz#6b8678a7461b0b42f913a7881cc9319b4d3cddff" + integrity sha512-Us5o5UEd4Smgn1+TfHX4LvVPoWVo9VsVMn4Ldbk0g5CQx3Gu0ygc/ei2AKPGTwsOZmKxJeACj7yMH2kgxQP/iw== dependencies: connect "^3.6.5" debug "^2.2.0" @@ -13077,65 +13077,20 @@ metro-inspector-proxy@0.76.7: ws "^7.5.1" yargs "^17.6.2" -metro-minify-terser@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.76.7.tgz#aefac8bb8b6b3a0fcb5ea0238623cf3e100893ff" - integrity sha512-FQiZGhIxCzhDwK4LxyPMLlq0Tsmla10X7BfNGlYFK0A5IsaVKNJbETyTzhpIwc+YFRT4GkFFwgo0V2N5vxO5HA== +metro-minify-terser@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-minify-terser/-/metro-minify-terser-0.76.8.tgz#915ab4d1419257fc6a0b9fa15827b83fe69814bf" + integrity sha512-Orbvg18qXHCrSj1KbaeSDVYRy/gkro2PC7Fy2tDSH1c9RB4aH8tuMOIXnKJE+1SXxBtjWmQ5Yirwkth2DyyEZA== dependencies: terser "^5.15.0" -metro-minify-uglify@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.76.7.tgz#3e0143786718dcaea4e28a724698d4f8ac199a43" - integrity sha512-FuXIU3j2uNcSvQtPrAJjYWHruPiQ+EpE++J9Z+VznQKEHcIxMMoQZAfIF2IpZSrZYfLOjVFyGMvj41jQMxV1Vw== +metro-minify-uglify@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.76.8.tgz#74745045ea2dd29f8783db483b2fce58385ba695" + integrity sha512-6l8/bEvtVaTSuhG1FqS0+Mc8lZ3Bl4RI8SeRIifVLC21eeSDp4CEBUWSGjpFyUDfi6R5dXzYaFnSgMNyfxADiQ== dependencies: uglify-es "^3.1.9" -metro-react-native-babel-preset@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.7.tgz#dfe15c040d0918147a8b0e9f530d558287acbb54" - integrity sha512-R25wq+VOSorAK3hc07NW0SmN8z9S/IR0Us0oGAsBcMZnsgkbOxu77Mduqf+f4is/wnWHc5+9bfiqdLnaMngiVw== - dependencies: - "@babel/core" "^7.20.0" - "@babel/plugin-proposal-async-generator-functions" "^7.0.0" - "@babel/plugin-proposal-class-properties" "^7.18.0" - "@babel/plugin-proposal-export-default-from" "^7.0.0" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.0" - "@babel/plugin-proposal-numeric-separator" "^7.0.0" - "@babel/plugin-proposal-object-rest-spread" "^7.20.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" - "@babel/plugin-proposal-optional-chaining" "^7.20.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-export-default-from" "^7.0.0" - "@babel/plugin-syntax-flow" "^7.18.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.0.0" - "@babel/plugin-syntax-optional-chaining" "^7.0.0" - "@babel/plugin-transform-arrow-functions" "^7.0.0" - "@babel/plugin-transform-async-to-generator" "^7.20.0" - "@babel/plugin-transform-block-scoping" "^7.0.0" - "@babel/plugin-transform-classes" "^7.0.0" - "@babel/plugin-transform-computed-properties" "^7.0.0" - "@babel/plugin-transform-destructuring" "^7.20.0" - "@babel/plugin-transform-flow-strip-types" "^7.20.0" - "@babel/plugin-transform-function-name" "^7.0.0" - "@babel/plugin-transform-literals" "^7.0.0" - "@babel/plugin-transform-modules-commonjs" "^7.0.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.0.0" - "@babel/plugin-transform-parameters" "^7.0.0" - "@babel/plugin-transform-react-display-name" "^7.0.0" - "@babel/plugin-transform-react-jsx" "^7.0.0" - "@babel/plugin-transform-react-jsx-self" "^7.0.0" - "@babel/plugin-transform-react-jsx-source" "^7.0.0" - "@babel/plugin-transform-runtime" "^7.0.0" - "@babel/plugin-transform-shorthand-properties" "^7.0.0" - "@babel/plugin-transform-spread" "^7.0.0" - "@babel/plugin-transform-sticky-regex" "^7.0.0" - "@babel/plugin-transform-typescript" "^7.5.0" - "@babel/plugin-transform-unicode-regex" "^7.0.0" - "@babel/template" "^7.0.0" - babel-plugin-transform-flow-enums "^0.0.2" - react-refresh "^0.4.0" - metro-react-native-babel-preset@0.76.8: version "0.76.8" resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.76.8.tgz#7476efae14363cbdfeeec403b4f01d7348e6c048" @@ -13225,29 +13180,21 @@ metro-react-native-babel-preset@^0.73.7: "@babel/template" "^7.0.0" react-refresh "^0.4.0" -metro-react-native-babel-transformer@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.7.tgz#ccc7c25b49ee8a1860aafdbf48bfa5441d206f8f" - integrity sha512-W6lW3J7y/05ph3c2p3KKJNhH0IdyxdOCbQ5it7aM2MAl0SM4wgKjaV6EYv9b3rHklpV6K3qMH37UKVcjMooWiA== +metro-react-native-babel-transformer@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.76.8.tgz#c3a98e1f4cd5faf1e21eba8e004b94a90c4db69b" + integrity sha512-3h+LfS1WG1PAzhq8QF0kfXjxuXetbY/lgz8vYMQhgrMMp17WM1DNJD0gjx8tOGYbpbBC1qesJ45KMS4o5TA73A== dependencies: "@babel/core" "^7.20.0" babel-preset-fbjs "^3.4.0" hermes-parser "0.12.0" - metro-react-native-babel-preset "0.76.7" + metro-react-native-babel-preset "0.76.8" nullthrows "^1.1.1" -metro-resolver@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.76.7.tgz#f00ebead64e451c060f30926ecbf4f797588df52" - integrity sha512-pC0Wgq29HHIHrwz23xxiNgylhI8Rq1V01kQaJ9Kz11zWrIdlrH0ZdnJ7GC6qA0ErROG+cXmJ0rJb8/SW1Zp2IA== - -metro-runtime@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-runtime/-/metro-runtime-0.76.7.tgz#4d75f2dbbcd19a4f01e0d89494e140b0ba8247e4" - integrity sha512-MuWHubQHymUWBpZLwuKZQgA/qbb35WnDAKPo83rk7JRLIFPvzXSvFaC18voPuzJBt1V98lKQIonh6MiC9gd8Ug== - dependencies: - "@babel/runtime" "^7.0.0" - react-refresh "^0.4.0" +metro-resolver@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.76.8.tgz#0862755b9b84e26853978322464fb37c6fdad76d" + integrity sha512-KccOqc10vrzS7ZhG2NSnL2dh3uVydarB7nOhjreQ7C4zyWuiW9XpLC4h47KtGQv3Rnv/NDLJYeDqaJ4/+140HQ== metro-runtime@0.76.8: version "0.76.8" @@ -13257,20 +13204,6 @@ metro-runtime@0.76.8: "@babel/runtime" "^7.0.0" react-refresh "^0.4.0" -metro-source-map@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.76.7.tgz#9a4aa3a35e1e8ffde9a74cd7ab5f49d9d4a4da14" - integrity sha512-Prhx7PeRV1LuogT0Kn5VjCuFu9fVD68eefntdWabrksmNY6mXK8pRqzvNJOhTojh6nek+RxBzZeD6MIOOyXS6w== - dependencies: - "@babel/traverse" "^7.20.0" - "@babel/types" "^7.20.0" - invariant "^2.2.4" - metro-symbolicate "0.76.7" - nullthrows "^1.1.1" - ob1 "0.76.7" - source-map "^0.5.6" - vlq "^1.0.0" - metro-source-map@0.76.8: version "0.76.8" resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.76.8.tgz#f085800152a6ba0b41ca26833874d31ec36c5a53" @@ -13285,18 +13218,6 @@ metro-source-map@0.76.8: source-map "^0.5.6" vlq "^1.0.0" -metro-symbolicate@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.76.7.tgz#1720e6b4ce5676935d7a8a440f25d3f16638e87a" - integrity sha512-p0zWEME5qLSL1bJb93iq+zt5fz3sfVn9xFYzca1TJIpY5MommEaS64Va87lp56O0sfEIvh4307Oaf/ZzRjuLiQ== - dependencies: - invariant "^2.2.4" - metro-source-map "0.76.7" - nullthrows "^1.1.1" - source-map "^0.5.6" - through2 "^2.0.1" - vlq "^1.0.0" - metro-symbolicate@0.76.8: version "0.76.8" resolved "https://registry.yarnpkg.com/metro-symbolicate/-/metro-symbolicate-0.76.8.tgz#f102ac1a306d51597ecc8fdf961c0a88bddbca03" @@ -13309,10 +13230,10 @@ metro-symbolicate@0.76.8: through2 "^2.0.1" vlq "^1.0.0" -metro-transform-plugins@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.76.7.tgz#5d5f75371706fbf5166288e43ffd36b5e5bd05bc" - integrity sha512-iSmnjVApbdivjuzb88Orb0JHvcEt5veVyFAzxiS5h0QB+zV79w6JCSqZlHCrbNOkOKBED//LqtKbFVakxllnNg== +metro-transform-plugins@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-transform-plugins/-/metro-transform-plugins-0.76.8.tgz#d77c28a6547a8e3b72250f740fcfbd7f5408f8ba" + integrity sha512-PlkGTQNqS51Bx4vuufSQCdSn2R2rt7korzngo+b5GCkeX5pjinPjnO2kNhQ8l+5bO0iUD/WZ9nsM2PGGKIkWFA== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.0" @@ -13320,28 +13241,28 @@ metro-transform-plugins@0.76.7: "@babel/traverse" "^7.20.0" nullthrows "^1.1.1" -metro-transform-worker@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.76.7.tgz#b842d5a542f1806cca401633fc002559b3e3d668" - integrity sha512-cGvELqFMVk9XTC15CMVzrCzcO6sO1lURfcbgjuuPdzaWuD11eEyocvkTX0DPiRjsvgAmicz4XYxVzgYl3MykDw== +metro-transform-worker@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro-transform-worker/-/metro-transform-worker-0.76.8.tgz#b9012a196cee205170d0c899b8b175b9305acdea" + integrity sha512-mE1fxVAnJKmwwJyDtThildxxos9+DGs9+vTrx2ktSFMEVTtXS/bIv2W6hux1pqivqAfyJpTeACXHk5u2DgGvIQ== dependencies: "@babel/core" "^7.20.0" "@babel/generator" "^7.20.0" "@babel/parser" "^7.20.0" "@babel/types" "^7.20.0" babel-preset-fbjs "^3.4.0" - metro "0.76.7" - metro-babel-transformer "0.76.7" - metro-cache "0.76.7" - metro-cache-key "0.76.7" - metro-source-map "0.76.7" - metro-transform-plugins "0.76.7" + metro "0.76.8" + metro-babel-transformer "0.76.8" + metro-cache "0.76.8" + metro-cache-key "0.76.8" + metro-source-map "0.76.8" + metro-transform-plugins "0.76.8" nullthrows "^1.1.1" -metro@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/metro/-/metro-0.76.7.tgz#4885917ad28738c7d1e556630e0155f687336230" - integrity sha512-67ZGwDeumEPnrHI+pEDSKH2cx+C81Gx8Mn5qOtmGUPm/Up9Y4I1H2dJZ5n17MWzejNo0XAvPh0QL0CrlJEODVQ== +metro@0.76.8: + version "0.76.8" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.76.8.tgz#ba526808b99977ca3f9ac5a7432fd02a340d13a6" + integrity sha512-oQA3gLzrrYv3qKtuWArMgHPbHu8odZOD9AoavrqSFllkPgOtmkBvNNDLCELqv5SjBfqjISNffypg+5UGG3y0pg== dependencies: "@babel/code-frame" "^7.0.0" "@babel/core" "^7.20.0" @@ -13365,22 +13286,22 @@ metro@0.76.7: jest-worker "^27.2.0" jsc-safe-url "^0.2.2" lodash.throttle "^4.1.1" - metro-babel-transformer "0.76.7" - metro-cache "0.76.7" - metro-cache-key "0.76.7" - metro-config "0.76.7" - metro-core "0.76.7" - metro-file-map "0.76.7" - metro-inspector-proxy "0.76.7" - metro-minify-terser "0.76.7" - metro-minify-uglify "0.76.7" - metro-react-native-babel-preset "0.76.7" - metro-resolver "0.76.7" - metro-runtime "0.76.7" - metro-source-map "0.76.7" - metro-symbolicate "0.76.7" - metro-transform-plugins "0.76.7" - metro-transform-worker "0.76.7" + metro-babel-transformer "0.76.8" + metro-cache "0.76.8" + metro-cache-key "0.76.8" + metro-config "0.76.8" + metro-core "0.76.8" + metro-file-map "0.76.8" + metro-inspector-proxy "0.76.8" + metro-minify-terser "0.76.8" + metro-minify-uglify "0.76.8" + metro-react-native-babel-preset "0.76.8" + metro-resolver "0.76.8" + metro-runtime "0.76.8" + metro-source-map "0.76.8" + metro-symbolicate "0.76.8" + metro-transform-plugins "0.76.8" + metro-transform-worker "0.76.8" mime-types "^2.1.27" node-fetch "^2.2.0" nullthrows "^1.1.1" @@ -13857,11 +13778,6 @@ nwsapi@^2.2.0, nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== -ob1@0.76.7: - version "0.76.7" - resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.76.7.tgz#95b68fadafd47e7a6a0ad64cf80f3140dd6d1124" - integrity sha512-BQdRtxxoUNfSoZxqeBGOyuT9nEYSn18xZHwGMb0mMVpn2NBcYbnyKY4BK2LIHRgw33CBGlUmE+KMaNvyTpLLtQ== - ob1@0.76.8: version "0.76.8" resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.76.8.tgz#ac4c459465b1c0e2c29aaa527e09fc463d3ffec8" @@ -15931,17 +15847,17 @@ react-native-web@~0.19.6: postcss-value-parser "^4.2.0" styleq "^0.1.3" -react-native@0.72.4: - version "0.72.4" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.72.4.tgz#97b57e22e4d7657eaf4d1f62a678511fcf9bdda7" - integrity sha512-+vrObi0wZR+NeqL09KihAAdVlQ9IdplwznJWtYrjnQ4UbCW6rkzZJebRsugwUneSOKNFaHFEo1uKU89HsgtYBg== +react-native@0.72.5: + version "0.72.5" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.72.5.tgz#2c343fa6f3ead362cf07376634a33a4078864357" + integrity sha512-oIewslu5DBwOmo7x5rdzZlZXCqDIna0R4dUwVpfmVteORYLr4yaZo5wQnMeR+H7x54GaMhmgeqp0ZpULtulJFg== dependencies: "@jest/create-cache-key-function" "^29.2.1" - "@react-native-community/cli" "11.3.6" - "@react-native-community/cli-platform-android" "11.3.6" - "@react-native-community/cli-platform-ios" "11.3.6" + "@react-native-community/cli" "11.3.7" + "@react-native-community/cli-platform-android" "11.3.7" + "@react-native-community/cli-platform-ios" "11.3.7" "@react-native/assets-registry" "^0.72.0" - "@react-native/codegen" "^0.72.6" + "@react-native/codegen" "^0.72.7" "@react-native/gradle-plugin" "^0.72.11" "@react-native/js-polyfills" "^0.72.1" "@react-native/normalize-colors" "^0.72.0" From 9278822088d212c9bee6a40a6a8b773bc482242d Mon Sep 17 00:00:00 2001 From: Patroll <6214736+P4tr0ll@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:31:43 +0200 Subject: [PATCH 27/40] Fix invite codes flash on desktop, use loading placeholder (#1591) * Fix invite codes flashing untrue value before loaded * Add loading placeholder for right nav invites --- src/state/models/me.ts | 8 ++-- src/view/com/modals/InviteCodes.tsx | 27 +++++++++++ src/view/screens/Settings.tsx | 70 +++++++++++++++------------- src/view/shell/Drawer.tsx | 54 +++++++++++----------- src/view/shell/desktop/RightNav.tsx | 72 +++++++++++++++++------------ 5 files changed, 142 insertions(+), 89 deletions(-) diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 186e61cf6b..8a7a4c851e 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -25,13 +25,13 @@ export class MeModel { savedFeeds: SavedFeedsModel notifications: NotificationsFeedModel follows: MyFollowsCache - invites: ComAtprotoServerDefs.InviteCode[] = [] + invites: ComAtprotoServerDefs.InviteCode[] | null = [] appPasswords: ComAtprotoServerListAppPasswords.AppPassword[] = [] lastProfileStateUpdate = Date.now() lastNotifsUpdate = Date.now() get invitesAvailable() { - return this.invites.filter(isInviteAvailable).length + return this.invites?.filter(isInviteAvailable).length || null } constructor(public rootStore: RootStoreModel) { @@ -180,7 +180,9 @@ export class MeModel { } catch (e) { this.rootStore.log.error('Failed to fetch user invite codes', e) } - await this.rootStore.invitedUsers.fetch(this.invites) + if (this.invites) { + await this.rootStore.invitedUsers.fetch(this.invites) + } } } diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx index 09cfd4de79..0cb0c56aae 100644 --- a/src/view/com/modals/InviteCodes.tsx +++ b/src/view/com/modals/InviteCodes.tsx @@ -26,6 +26,33 @@ export function Component({}: {}) { store.shell.closeModal() }, [store]) + if (store.me.invites === null) { + return ( + + + Error + + + An error occurred while loading invite codes. + + + + - - - - ) - }, - [store.shell.activeLightbox, isAltExpanded, saveImageToAlbumWithToasts], - ) - if (!store.shell.activeLightbox) { return null } else if (store.shell.activeLightbox.name === 'profile-image') { @@ -132,6 +48,92 @@ export const Lightbox = observer(function Lightbox() { } }) +const LightboxFooter = observer(function LightboxFooter({ + imageIndex, +}: { + imageIndex: number +}) { + const store = useStores() + const [isAltExpanded, setAltExpanded] = React.useState(false) + const [permissionResponse, requestPermission] = MediaLibrary.usePermissions() + + const saveImageToAlbumWithToasts = React.useCallback( + async (uri: string) => { + if (!permissionResponse || permissionResponse.granted === false) { + Toast.show('Permission to access camera roll is required.') + if (permissionResponse?.canAskAgain) { + requestPermission() + } else { + Toast.show( + 'Permission to access camera roll was denied. Please enable it in your system settings.', + ) + } + return + } + + try { + await saveImageToMediaLibrary({uri}) + Toast.show('Saved to your camera roll.') + } catch (e: any) { + Toast.show(`Failed to save image: ${String(e)}`) + } + }, + [permissionResponse, requestPermission], + ) + + const lightbox = store.shell.activeLightbox + if (!lightbox) { + return null + } + + let altText = '' + let uri = '' + if (lightbox.name === 'images') { + const opts = lightbox as models.ImagesLightbox + uri = opts.images[imageIndex].uri + altText = opts.images[imageIndex].alt || '' + } else if (lightbox.name === 'profile-image') { + const opts = lightbox as models.ProfileImageLightbox + uri = opts.profileView.avatar || '' + } + + return ( + + {altText ? ( + setAltExpanded(!isAltExpanded)} + accessibilityRole="button"> + + {altText} + + + ) : null} + + + + + + ) +}) + const styles = StyleSheet.create({ footer: { paddingTop: 16, From 4ec5fabdd1c1b70fd8a3f5671c056bba3d38e174 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 5 Oct 2023 23:52:04 +0100 Subject: [PATCH 30/40] Fix animations and gestures getting reset on state updates in the lightbox (#1618) * Fix translation resetting on state update * Copy getImageStyles into iOS and Android forks * Fix opacity resetting on state update --- .../ImageItem/ImageItem.android.tsx | 25 +++++++++++- .../components/ImageItem/ImageItem.ios.tsx | 39 +++++++++++++------ src/view/com/lightbox/ImageViewing/index.tsx | 11 +++--- src/view/com/lightbox/ImageViewing/utils.ts | 24 ------------ 4 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx index 927657baf2..6276a1a146 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx @@ -22,7 +22,7 @@ import {Image} from 'expo-image' import useImageDimensions from '../../hooks/useImageDimensions' import usePanResponder from '../../hooks/usePanResponder' -import {getImageStyles, getImageTransform} from '../../utils' +import {getImageTransform} from '../../utils' import {ImageSource} from '../../@types' import {ImageLoading} from './ImageLoading' @@ -133,4 +133,27 @@ const styles = StyleSheet.create({ }, }) +const getImageStyles = ( + image: {width: number; height: number} | null, + translate: Animated.ValueXY, + scale?: Animated.Value, +) => { + if (!image?.width || !image?.height) { + return {width: 0, height: 0} + } + + const transform = translate.getTranslateTransform() + + if (scale) { + // @ts-ignore TODO - is scale incorrect? might need to remove -prf + transform.push({scale}, {perspective: new Animated.Value(1000)}) + } + + return { + width: image.width, + height: image.height, + transform, + } +} + export default React.memo(ImageItem) diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx index f379df22f1..b9f3ae5102 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -23,7 +23,7 @@ import {Image} from 'expo-image' import useImageDimensions from '../../hooks/useImageDimensions' -import {getImageStyles, getImageTransform} from '../../utils' +import {getImageTransform} from '../../utils' import {ImageSource} from '../../@types' import {ImageLoading} from './ImageLoading' @@ -52,23 +52,14 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => { const [scaled, setScaled] = useState(false) const imageDimensions = useImageDimensions(imageSrc) const [translate, scale] = getImageTransform(imageDimensions, SCREEN) - - // TODO: It's not valid to reinitialize Animated values during render. - // This is a bug. - const scrollValueY = new Animated.Value(0) - const scaleValue = new Animated.Value(scale || 1) - const translateValue = new Animated.ValueXY(translate) + const [scrollValueY] = useState(() => new Animated.Value(0)) const maxScrollViewZoom = MAX_SCALE / (scale || 1) const imageOpacity = scrollValueY.interpolate({ inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], outputRange: [0.5, 1, 0.5], }) - const imagesStyles = getImageStyles( - imageDimensions, - translateValue, - scaleValue, - ) + const imagesStyles = getImageStyles(imageDimensions, translate, scale || 1) const imageStylesWithOpacity = {...imagesStyles, opacity: imageOpacity} const onScrollEndDrag = useCallback( @@ -260,4 +251,28 @@ const getZoomRectAfterDoubleTap = ( } } +const getImageStyles = ( + image: {width: number; height: number} | null, + translate: {readonly x: number; readonly y: number} | undefined, + scale?: number, +) => { + if (!image?.width || !image?.height) { + return {width: 0, height: 0} + } + const transform = [] + if (translate) { + transform.push({translateX: translate.x}) + transform.push({translateY: translate.y}) + } + if (scale) { + // @ts-ignore TODO - is scale incorrect? might need to remove -prf + transform.push({scale}, {perspective: new Animated.Value(1000)}) + } + return { + width: image.width, + height: image.height, + transform, + } +} + export default React.memo(ImageItem) diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index 3b659e2db7..4b7208a222 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -69,11 +69,12 @@ function ImageViewing({ const imageList = useRef>(null) const [opacity, setOpacity] = useState(1) const [currentImageIndex, setImageIndex] = useState(imageIndex) - - // TODO: It's not valid to reinitialize Animated values during render. - // This is a bug. - const headerTranslate = new Animated.ValueXY(INITIAL_POSITION) - const footerTranslate = new Animated.ValueXY(INITIAL_POSITION) + const [headerTranslate] = useState( + () => new Animated.ValueXY(INITIAL_POSITION), + ) + const [footerTranslate] = useState( + () => new Animated.ValueXY(INITIAL_POSITION), + ) const toggleBarsVisible = (isVisible: boolean) => { if (isVisible) { diff --git a/src/view/com/lightbox/ImageViewing/utils.ts b/src/view/com/lightbox/ImageViewing/utils.ts index 03f28d61a9..6fc4116383 100644 --- a/src/view/com/lightbox/ImageViewing/utils.ts +++ b/src/view/com/lightbox/ImageViewing/utils.ts @@ -6,7 +6,6 @@ * */ -import {Animated} from 'react-native' import {Dimensions, Position} from './@types' export const getImageTransform = ( @@ -25,29 +24,6 @@ export const getImageTransform = ( return [{x, y}, scale] as const } -export const getImageStyles = ( - image: Dimensions | null, - translate: Animated.ValueXY, - scale?: Animated.Value, -) => { - if (!image?.width || !image?.height) { - return {width: 0, height: 0} - } - - const transform = translate.getTranslateTransform() - - if (scale) { - // @ts-ignore TODO - is scale incorrect? might need to remove -prf - transform.push({scale}, {perspective: new Animated.Value(1000)}) - } - - return { - width: image.width, - height: image.height, - transform, - } -} - export const getImageTranslate = ( image: Dimensions, screen: Dimensions, From 0168aada372d6a42f8ee4557f6d6dcfa1a81ed71 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 5 Oct 2023 15:55:31 -0700 Subject: [PATCH 31/40] Pull upstream bugfixes to bottom-sheet (#1606) * Bump bottom-sheet to latest for bugfixes * Small tweak to the account switcher --- package.json | 2 +- src/view/com/modals/SwitchAccount.tsx | 113 ++++++++++++-------------- yarn.lock | 8 +- 3 files changed, 59 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index cd8518738f..a886bfcd01 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-native-fontawesome": "^0.3.0", - "@gorhom/bottom-sheet": "^4.4.7", + "@gorhom/bottom-sheet": "^4.5.1", "@mattermost/react-native-paste-input": "^0.6.4", "@miblanchard/react-native-slider": "^2.3.1", "@react-native-async-storage/async-storage": "1.18.2", diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx index 51d75e3ef4..d5fa32692b 100644 --- a/src/view/com/modals/SwitchAccount.tsx +++ b/src/view/com/modals/SwitchAccount.tsx @@ -37,74 +37,69 @@ export function Component({}: {}) { }, [track, store]) return ( - + Switch Account - - {isSwitching ? ( + {isSwitching ? ( + + + + ) : ( + - - - ) : ( - - - - - - - - {store.me.displayName || store.me.handle} - - - {store.me.handle} - - - - - Sign out - - - - - )} - {store.session.switchableAccounts.map(account => ( - onPressSwitchAccount(account) - } - accessibilityRole="button" - accessibilityLabel={`Switch to ${account.handle}`} - accessibilityHint="Switches the account you are logged in to"> - + - - {account.displayName || account.handle} + + {store.me.displayName || store.me.handle} - - {account.handle} + + {store.me.handle} - - - ))} - - + + + Sign out + + + + + )} + {store.session.switchableAccounts.map(account => ( + onPressSwitchAccount(account) + } + accessibilityRole="button" + accessibilityLabel={`Switch to ${account.handle}`} + accessibilityHint="Switches the account you are logged in to"> + + + + + + {account.displayName || account.handle} + + + {account.handle} + + + + + ))} + ) } @@ -113,7 +108,7 @@ const styles = StyleSheet.create({ flex: 1, }, innerContainer: { - paddingBottom: 20, + paddingBottom: 40, }, title: { textAlign: 'center', diff --git a/yarn.lock b/yarn.lock index 8e92fcd2b3..fffbff57ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2255,10 +2255,10 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@gorhom/bottom-sheet@^4.4.7": - version "4.4.7" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.4.7.tgz#fc80b3f0b7ebab056ce226f3aa3a89b2db8660dd" - integrity sha512-ukTuTqDQi2heo68hAJsBpUQeEkdqP9REBcn47OpuvPKhdPuO1RBOOADjqXJNCnZZRcY+HqbnGPMSLFVc31zylQ== +"@gorhom/bottom-sheet@^4.5.1": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-4.5.1.tgz#1ac4b234a80e7dff263f0b7ac207f92e41562849" + integrity sha512-4Qy6hzvN32fXu2hDxDXOIS0IBGBT6huST7J7+K1V5bXemZ08KIx5ZffyLgwhCUl+CnyeG2KG6tqk6iYLkIwi7Q== dependencies: "@gorhom/portal" "1.0.14" invariant "^2.2.4" From 19f8389fc777c7ff41466748f1238f4e0a4b0619 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 5 Oct 2023 16:13:49 -0700 Subject: [PATCH 32/40] Slightly smaller highlighted post text (#1608) --- src/lib/themes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/themes.ts b/src/lib/themes.ts index 95aee0842f..b778d5b30d 100644 --- a/src/lib/themes.ts +++ b/src/lib/themes.ts @@ -264,8 +264,8 @@ export const defaultTheme: Theme = { fontWeight: '400', }, 'post-text-lg': { - fontSize: 22, - letterSpacing: 0.4, + fontSize: 20, + letterSpacing: 0.2, fontWeight: '400', }, 'button-lg': { From bd7db8af26bfbf94a80972671ca714a143bee28e Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Thu, 5 Oct 2023 16:44:05 -0700 Subject: [PATCH 33/40] Improve typeahead search with inclusion of followed users (temporary solution) (#1612) * Update follows cache to maintain some user info * Prioritize follows in composer autocomplete * Clean up logic and add new autocomplete to search * Update follow hook --- .../{useFollowDid.ts => useFollowProfile.ts} | 24 ++-- src/state/models/cache/my-follows.ts | 103 ++++++++++----- src/state/models/content/profile.ts | 4 +- src/state/models/discovery/foafs.ts | 36 +----- .../models/discovery/suggested-actors.ts | 4 +- .../models/discovery/user-autocomplete.ts | 117 ++++++++++++------ src/state/models/feeds/posts.ts | 2 +- src/state/models/invited-users.ts | 2 +- src/state/models/lists/likes.ts | 2 +- src/state/models/lists/reposted-by.ts | 2 +- src/state/models/lists/user-followers.ts | 2 +- src/state/models/lists/user-follows.ts | 2 +- src/state/models/ui/search.ts | 2 +- .../onboarding/RecommendedFollowsItem.tsx | 2 +- src/view/com/notifications/InvitedUsers.tsx | 2 +- src/view/com/profile/FollowButton.tsx | 9 +- src/view/com/profile/ProfileCard.tsx | 2 +- .../profile/ProfileHeaderSuggestedFollows.tsx | 6 +- src/view/screens/SearchMobile.tsx | 12 +- src/view/shell/desktop/Search.tsx | 4 +- 20 files changed, 197 insertions(+), 142 deletions(-) rename src/lib/hooks/{useFollowDid.ts => useFollowProfile.ts} (57%) diff --git a/src/lib/hooks/useFollowDid.ts b/src/lib/hooks/useFollowProfile.ts similarity index 57% rename from src/lib/hooks/useFollowDid.ts rename to src/lib/hooks/useFollowProfile.ts index 223adb0475..6220daba86 100644 --- a/src/lib/hooks/useFollowDid.ts +++ b/src/lib/hooks/useFollowProfile.ts @@ -1,11 +1,11 @@ import React from 'react' - +import {AppBskyActorDefs} from '@atproto/api' import {useStores} from 'state/index' import {FollowState} from 'state/models/cache/my-follows' -export function useFollowDid({did}: {did: string}) { +export function useFollowProfile(profile: AppBskyActorDefs.ProfileViewBasic) { const store = useStores() - const state = store.me.follows.getFollowState(did) + const state = store.me.follows.getFollowState(profile.did) return { state, @@ -13,8 +13,10 @@ export function useFollowDid({did}: {did: string}) { toggle: React.useCallback(async () => { if (state === FollowState.Following) { try { - await store.agent.deleteFollow(store.me.follows.getFollowUri(did)) - store.me.follows.removeFollow(did) + await store.agent.deleteFollow( + store.me.follows.getFollowUri(profile.did), + ) + store.me.follows.removeFollow(profile.did) return { state: FollowState.NotFollowing, following: false, @@ -25,8 +27,14 @@ export function useFollowDid({did}: {did: string}) { } } else if (state === FollowState.NotFollowing) { try { - const res = await store.agent.follow(did) - store.me.follows.addFollow(did, res.uri) + const res = await store.agent.follow(profile.did) + store.me.follows.addFollow(profile.did, { + followRecordUri: res.uri, + did: profile.did, + handle: profile.handle, + displayName: profile.displayName, + avatar: profile.avatar, + }) return { state: FollowState.Following, following: true, @@ -41,6 +49,6 @@ export function useFollowDid({did}: {did: string}) { state: FollowState.Unknown, following: false, } - }, [store, did, state]), + }, [store, profile, state]), } } diff --git a/src/state/models/cache/my-follows.ts b/src/state/models/cache/my-follows.ts index 10f88c4a96..14dc9895db 100644 --- a/src/state/models/cache/my-follows.ts +++ b/src/state/models/cache/my-follows.ts @@ -1,7 +1,14 @@ import {makeAutoObservable} from 'mobx' -import {AppBskyActorDefs} from '@atproto/api' +import { + AppBskyActorDefs, + AppBskyGraphGetFollows as GetFollows, + moderateProfile, +} from '@atproto/api' import {RootStoreModel} from '../root-store' +const MAX_SYNC_PAGES = 10 +const SYNC_TTL = 60e3 * 10 // 10 minutes + type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView export enum FollowState { @@ -10,6 +17,14 @@ export enum FollowState { Unknown, } +export interface FollowInfo { + did: string + followRecordUri: string | undefined + handle: string + displayName: string | undefined + avatar: string | undefined +} + /** * This model is used to maintain a synced local cache of the user's * follows. It should be periodically refreshed and updated any time @@ -17,9 +32,8 @@ export enum FollowState { */ export class MyFollowsCache { // data - followDidToRecordMap: Record = {} + byDid: Record = {} lastSync = 0 - myDid?: string constructor(public rootStore: RootStoreModel) { makeAutoObservable( @@ -35,16 +49,45 @@ export class MyFollowsCache { // = clear() { - this.followDidToRecordMap = {} - this.lastSync = 0 - this.myDid = undefined + this.byDid = {} + } + + /** + * Syncs a subset of the user's follows + * for performance reasons, caps out at 1000 follows + */ + async syncIfNeeded() { + if (this.lastSync > Date.now() - SYNC_TTL) { + return + } + + let cursor + for (let i = 0; i < MAX_SYNC_PAGES; i++) { + const res: GetFollows.Response = await this.rootStore.agent.getFollows({ + actor: this.rootStore.me.did, + cursor, + limit: 100, + }) + res.data.follows = res.data.follows.filter( + profile => + !moderateProfile(profile, this.rootStore.preferences.moderationOpts) + .account.filter, + ) + this.hydrateMany(res.data.follows) + if (!res.data.cursor) { + break + } + cursor = res.data.cursor + } + + this.lastSync = Date.now() } getFollowState(did: string): FollowState { - if (typeof this.followDidToRecordMap[did] === 'undefined') { + if (typeof this.byDid[did] === 'undefined') { return FollowState.Unknown } - if (typeof this.followDidToRecordMap[did] === 'string') { + if (typeof this.byDid[did].followRecordUri === 'string') { return FollowState.Following } return FollowState.NotFollowing @@ -53,49 +96,41 @@ export class MyFollowsCache { async fetchFollowState(did: string): Promise { // TODO: can we get a more efficient method for this? getProfile fetches more data than we need -prf const res = await this.rootStore.agent.getProfile({actor: did}) - if (res.data.viewer?.following) { - this.addFollow(did, res.data.viewer.following) - } else { - this.removeFollow(did) - } + this.hydrate(did, res.data) return this.getFollowState(did) } getFollowUri(did: string): string { - const v = this.followDidToRecordMap[did] + const v = this.byDid[did] if (typeof v === 'string') { return v } throw new Error('Not a followed user') } - addFollow(did: string, recordUri: string) { - this.followDidToRecordMap[did] = recordUri + addFollow(did: string, info: FollowInfo) { + this.byDid[did] = info } removeFollow(did: string) { - this.followDidToRecordMap[did] = false - } - - /** - * Use this to incrementally update the cache as views provide information - */ - hydrate(did: string, recordUri: string | undefined) { - if (recordUri) { - this.followDidToRecordMap[did] = recordUri - } else { - this.followDidToRecordMap[did] = false + if (this.byDid[did]) { + this.byDid[did].followRecordUri = undefined } } - /** - * Use this to incrementally update the cache as views provide information - */ - hydrateProfiles(profiles: Profile[]) { + hydrate(did: string, profile: Profile) { + this.byDid[did] = { + did, + followRecordUri: profile.viewer?.following, + handle: profile.handle, + displayName: profile.displayName, + avatar: profile.avatar, + } + } + + hydrateMany(profiles: Profile[]) { for (const profile of profiles) { - if (profile.viewer) { - this.hydrate(profile.did, profile.viewer.following) - } + this.hydrate(profile.did, profile) } } } diff --git a/src/state/models/content/profile.ts b/src/state/models/content/profile.ts index 26fa6008c8..906f84c281 100644 --- a/src/state/models/content/profile.ts +++ b/src/state/models/content/profile.ts @@ -137,7 +137,7 @@ export class ProfileModel { runInAction(() => { this.followersCount++ this.viewer.following = res.uri - this.rootStore.me.follows.addFollow(this.did, res.uri) + this.rootStore.me.follows.hydrate(this.did, this) }) track('Profile:Follow', { username: this.handle, @@ -290,8 +290,8 @@ export class ProfileModel { this.labels = res.data.labels if (res.data.viewer) { Object.assign(this.viewer, res.data.viewer) - this.rootStore.me.follows.hydrate(this.did, res.data.viewer.following) } + this.rootStore.me.follows.hydrate(this.did, res.data) } async _createRichText() { diff --git a/src/state/models/discovery/foafs.ts b/src/state/models/discovery/foafs.ts index 580145f65d..4a647dcfe3 100644 --- a/src/state/models/discovery/foafs.ts +++ b/src/state/models/discovery/foafs.ts @@ -1,8 +1,4 @@ -import { - AppBskyActorDefs, - AppBskyGraphGetFollows as GetFollows, - moderateProfile, -} from '@atproto/api' +import {AppBskyActorDefs} from '@atproto/api' import {makeAutoObservable, runInAction} from 'mobx' import sampleSize from 'lodash.samplesize' import {bundleAsync} from 'lib/async/bundle' @@ -43,35 +39,13 @@ export class FoafsModel { try { this.isLoading = true - // fetch & hydrate up to 1000 follows - { - let cursor - for (let i = 0; i < 10; i++) { - const res: GetFollows.Response = - await this.rootStore.agent.getFollows({ - actor: this.rootStore.me.did, - cursor, - limit: 100, - }) - res.data.follows = res.data.follows.filter( - profile => - !moderateProfile( - profile, - this.rootStore.preferences.moderationOpts, - ).account.filter, - ) - this.rootStore.me.follows.hydrateProfiles(res.data.follows) - if (!res.data.cursor) { - break - } - cursor = res.data.cursor - } - } + // fetch some of the user's follows + await this.rootStore.me.follows.syncIfNeeded() // grab 10 of the users followed by the user runInAction(() => { this.sources = sampleSize( - Object.keys(this.rootStore.me.follows.followDidToRecordMap), + Object.keys(this.rootStore.me.follows.byDid), 10, ) }) @@ -100,7 +74,7 @@ export class FoafsModel { for (let i = 0; i < results.length; i++) { const res = results[i] if (res.status === 'fulfilled') { - this.rootStore.me.follows.hydrateProfiles(res.value.data.follows) + this.rootStore.me.follows.hydrateMany(res.value.data.follows) } const profile = profiles.data.profiles[i] const source = this.sources[i] diff --git a/src/state/models/discovery/suggested-actors.ts b/src/state/models/discovery/suggested-actors.ts index afa5e74e3e..d270267eee 100644 --- a/src/state/models/discovery/suggested-actors.ts +++ b/src/state/models/discovery/suggested-actors.ts @@ -76,7 +76,7 @@ export class SuggestedActorsModel { !moderateProfile(actor, this.rootStore.preferences.moderationOpts) .account.filter, ) - this.rootStore.me.follows.hydrateProfiles(actors) + this.rootStore.me.follows.hydrateMany(actors) runInAction(() => { if (replace) { @@ -118,7 +118,7 @@ export class SuggestedActorsModel { actor: actor, }) const {suggestions: moreSuggestions} = res.data - this.rootStore.me.follows.hydrateProfiles(moreSuggestions) + this.rootStore.me.follows.hydrateMany(moreSuggestions) // dedupe const toInsert = moreSuggestions.filter( s => !this.suggestions.find(s2 => s2.did === s.did), diff --git a/src/state/models/discovery/user-autocomplete.ts b/src/state/models/discovery/user-autocomplete.ts index 461073e457..25ce859d2e 100644 --- a/src/state/models/discovery/user-autocomplete.ts +++ b/src/state/models/discovery/user-autocomplete.ts @@ -4,6 +4,8 @@ import AwaitLock from 'await-lock' import {RootStoreModel} from '../root-store' import {isInvalidHandle} from 'lib/strings/handles' +type ProfileViewBasic = AppBskyActorDefs.ProfileViewBasic + export class UserAutocompleteModel { // state isLoading = false @@ -12,9 +14,8 @@ export class UserAutocompleteModel { lock = new AwaitLock() // data - follows: AppBskyActorDefs.ProfileViewBasic[] = [] - searchRes: AppBskyActorDefs.ProfileViewBasic[] = [] knownHandles: Set = new Set() + _suggestions: ProfileViewBasic[] = [] constructor(public rootStore: RootStoreModel) { makeAutoObservable( @@ -27,29 +28,35 @@ export class UserAutocompleteModel { ) } - get suggestions() { + get follows(): ProfileViewBasic[] { + return Object.values(this.rootStore.me.follows.byDid).map(item => ({ + did: item.did, + handle: item.handle, + displayName: item.displayName, + avatar: item.avatar, + })) + } + + get suggestions(): ProfileViewBasic[] { if (!this.isActive) { return [] } - if (this.prefix) { - return this.searchRes.map(user => ({ - handle: user.handle, - displayName: user.displayName, - avatar: user.avatar, - })) - } - return this.follows.map(follow => ({ - handle: follow.handle, - displayName: follow.displayName, - avatar: follow.avatar, - })) + return this._suggestions } // public api // = async setup() { - await this._getFollows() + await this.rootStore.me.follows.syncIfNeeded() + runInAction(() => { + for (const did in this.rootStore.me.follows.byDid) { + const info = this.rootStore.me.follows.byDid[did] + if (!isInvalidHandle(info.handle)) { + this.knownHandles.add(info.handle) + } + } + }) } setActive(v: boolean) { @@ -57,7 +64,7 @@ export class UserAutocompleteModel { } async setPrefix(prefix: string) { - const origPrefix = prefix.trim() + const origPrefix = prefix.trim().toLocaleLowerCase() this.prefix = origPrefix await this.lock.acquireAsync() try { @@ -65,9 +72,27 @@ export class UserAutocompleteModel { if (this.prefix !== origPrefix) { return // another prefix was set before we got our chance } - await this._search() + + // reset to follow results + this._computeSuggestions([]) + + // ask backend + const res = await this.rootStore.agent.searchActorsTypeahead({ + term: this.prefix, + limit: 8, + }) + this._computeSuggestions(res.data.actors) + + // update known handles + runInAction(() => { + for (const u of res.data.actors) { + this.knownHandles.add(u.handle) + } + }) } else { - this.searchRes = [] + runInAction(() => { + this._computeSuggestions([]) + }) } } finally { this.lock.release() @@ -77,28 +102,40 @@ export class UserAutocompleteModel { // internal // = - async _getFollows() { - const res = await this.rootStore.agent.getFollows({ - actor: this.rootStore.me.did || '', - }) - runInAction(() => { - this.follows = res.data.follows.filter(f => !isInvalidHandle(f.handle)) - for (const f of this.follows) { - this.knownHandles.add(f.handle) + _computeSuggestions(searchRes: AppBskyActorDefs.ProfileViewBasic[] = []) { + if (this.prefix) { + const items: ProfileViewBasic[] = [] + for (const item of this.follows) { + if (prefixMatch(this.prefix, item)) { + items.push(item) + } + if (items.length >= 8) { + break + } } - }) - } - - async _search() { - const res = await this.rootStore.agent.searchActorsTypeahead({ - term: this.prefix, - limit: 8, - }) - runInAction(() => { - this.searchRes = res.data.actors - for (const u of this.searchRes) { - this.knownHandles.add(u.handle) + for (const item of searchRes) { + if (!items.find(item2 => item2.handle === item.handle)) { + items.push({ + did: item.did, + handle: item.handle, + displayName: item.displayName, + avatar: item.avatar, + }) + } } - }) + this._suggestions = items + } else { + this._suggestions = this.follows + } } } + +function prefixMatch(prefix: string, info: ProfileViewBasic): boolean { + if (info.handle.includes(prefix)) { + return true + } + if (info.displayName?.toLocaleLowerCase().includes(prefix)) { + return true + } + return false +} diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts index 2a71703252..2462689b14 100644 --- a/src/state/models/feeds/posts.ts +++ b/src/state/models/feeds/posts.ts @@ -316,7 +316,7 @@ export class PostsFeedModel { this.emptyFetches = 0 } - this.rootStore.me.follows.hydrateProfiles( + this.rootStore.me.follows.hydrateMany( res.feed.map(item => item.post.author), ) for (const item of res.feed) { diff --git a/src/state/models/invited-users.ts b/src/state/models/invited-users.ts index a28e0309a6..cd36670622 100644 --- a/src/state/models/invited-users.ts +++ b/src/state/models/invited-users.ts @@ -61,7 +61,7 @@ export class InvitedUsers { profile => !profile.viewer?.following, ) }) - this.rootStore.me.follows.hydrateProfiles(this.profiles) + this.rootStore.me.follows.hydrateMany(this.profiles) } catch (e) { this.rootStore.log.error( 'Failed to fetch profiles for invited users', diff --git a/src/state/models/lists/likes.ts b/src/state/models/lists/likes.ts index 39882d73af..dd3cf18a33 100644 --- a/src/state/models/lists/likes.ts +++ b/src/state/models/lists/likes.ts @@ -126,7 +126,7 @@ export class LikesModel { _appendAll(res: GetLikes.Response) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor - this.rootStore.me.follows.hydrateProfiles( + this.rootStore.me.follows.hydrateMany( res.data.likes.map(like => like.actor), ) this.likes = this.likes.concat(res.data.likes) diff --git a/src/state/models/lists/reposted-by.ts b/src/state/models/lists/reposted-by.ts index a70375bdc3..5d4fc107d7 100644 --- a/src/state/models/lists/reposted-by.ts +++ b/src/state/models/lists/reposted-by.ts @@ -130,6 +130,6 @@ export class RepostedByModel { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor this.repostedBy = this.repostedBy.concat(res.data.repostedBy) - this.rootStore.me.follows.hydrateProfiles(res.data.repostedBy) + this.rootStore.me.follows.hydrateMany(res.data.repostedBy) } } diff --git a/src/state/models/lists/user-followers.ts b/src/state/models/lists/user-followers.ts index 2962d62428..1f817c33c0 100644 --- a/src/state/models/lists/user-followers.ts +++ b/src/state/models/lists/user-followers.ts @@ -115,6 +115,6 @@ export class UserFollowersModel { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor this.followers = this.followers.concat(res.data.followers) - this.rootStore.me.follows.hydrateProfiles(res.data.followers) + this.rootStore.me.follows.hydrateMany(res.data.followers) } } diff --git a/src/state/models/lists/user-follows.ts b/src/state/models/lists/user-follows.ts index 56432a7961..c9630eba82 100644 --- a/src/state/models/lists/user-follows.ts +++ b/src/state/models/lists/user-follows.ts @@ -115,6 +115,6 @@ export class UserFollowsModel { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor this.follows = this.follows.concat(res.data.follows) - this.rootStore.me.follows.hydrateProfiles(res.data.follows) + this.rootStore.me.follows.hydrateMany(res.data.follows) } } diff --git a/src/state/models/ui/search.ts b/src/state/models/ui/search.ts index 4ab9db5135..2b2036751d 100644 --- a/src/state/models/ui/search.ts +++ b/src/state/models/ui/search.ts @@ -59,7 +59,7 @@ export class SearchUIModel { } while (profilesSearch.length) } - this.rootStore.me.follows.hydrateProfiles(profiles) + this.rootStore.me.follows.hydrateMany(profiles) runInAction(() => { this.profiles = profiles diff --git a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx index 51e3bc382a..2b26918d02 100644 --- a/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFollowsItem.tsx @@ -89,7 +89,7 @@ export const ProfileCard = observer(function ProfileCardImpl({ { if (isFollow) { diff --git a/src/view/com/notifications/InvitedUsers.tsx b/src/view/com/notifications/InvitedUsers.tsx index 89a0da47f6..aaf358b876 100644 --- a/src/view/com/notifications/InvitedUsers.tsx +++ b/src/view/com/notifications/InvitedUsers.tsx @@ -75,7 +75,7 @@ function InvitedUser({