diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index 243d52977..c39baf367 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -11,6 +11,7 @@ import {sanitizeHandle} from '#/lib/strings/handles' import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' import {useProfilesQuery} from '#/state/queries/profile' import {type SessionAccount, useSession} from '#/state/session' +import {useSortedAccountItems} from '#/state/session/sorting' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {Button} from '#/components/Button' @@ -20,6 +21,7 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/ import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useActorStatus} from '#/features/liveNow' +import {useHiddenAccountsElsewhere} from '#/storage/hooks/hidden-accounts-elsewhere' export function AccountList({ onSelectAccount, @@ -36,9 +38,13 @@ export function AccountList({ const t = useTheme() const {_} = useLingui() const enableSquareButtons = useEnableSquareButtons() + const [, , hiddenDidsSet] = useHiddenAccountsElsewhere() const {data: profiles} = useProfilesQuery({ handles: accounts.map(acc => acc.did), }) + const sortedAccounts = useSortedAccountItems(accounts).filter( + account => !hiddenDidsSet.has(account.did), + ) const onPressAddAccount = useCallback(() => { onSelectOther() @@ -53,7 +59,7 @@ export function AccountList({ a.border, t.atoms.border_contrast_low, ]}> - {accounts.map(account => ( + {sortedAccounts.map(account => ( p.did === account.did)} diff --git a/src/components/AvatarStack.tsx b/src/components/AvatarStack.tsx index ae42a7470..513998141 100644 --- a/src/components/AvatarStack.tsx +++ b/src/components/AvatarStack.tsx @@ -95,10 +95,14 @@ export function AvatarStackWithFetch({ return null } + const orderedProfiles = profiles + .map(did => data?.profiles?.find(profile => profile.did === did)) + .filter(Boolean) + return ( diff --git a/src/screens/Settings/RunesSettings/ExtraSettings.tsx b/src/screens/Settings/RunesSettings/ExtraSettings.tsx index 47ee29e54..ddbf39183 100644 --- a/src/screens/Settings/RunesSettings/ExtraSettings.tsx +++ b/src/screens/Settings/RunesSettings/ExtraSettings.tsx @@ -25,10 +25,12 @@ import {atoms as a} from '#/alf' import {Admonition} from '#/components/Admonition' import * as Toggle from '#/components/forms/Toggle' import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging' +import {CodeBrackets_Stroke2_Corner2_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {Explosion_Stroke2_Corner0_Rounded as ExplosionIcon} from '#/components/icons/Explosion' import {Eye_Stroke2_Corner0_Rounded as VisibilityIcon} from '#/components/icons/Eye' import {LikeRepost_Stroke2_Corner2_Rounded as LikeRepostIcon} from '#/components/icons/Heart2' import {Lab_Stroke2_Corner0_Rounded as BeakerIcon} from '#/components/icons/Lab' +import {useDevMode} from '#/storage/hooks/dev-mode' import {RunesScreenLayout} from './components/RunesScreenLayout' export function RunesExtraSettingsScreen() { @@ -48,6 +50,7 @@ export function RunesExtraSettingsScreen() { const omitViaField = useOmitViaField() const setOmitViaField = useSetOmitViaField() + const [devMode, setDevMode] = useDevMode() return ( @@ -127,6 +130,19 @@ export function RunesExtraSettingsScreen() { + setDevMode(value)}> + + + + Developer mode + + + + ) } diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 2ca6f9de8..939689695 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,6 +1,11 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Pressable, View} from 'react-native' -import {useReducedMotion} from 'react-native-reanimated' +import Animated, { + useAnimatedRef, + useReducedMotion, + useScrollViewOffset, +} from 'react-native-reanimated' +import {setStringAsync} from 'expo-clipboard' import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -24,6 +29,11 @@ import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' import {useAgent} from '#/state/session' import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {pdsAgent} from '#/state/session/agent' +import { + type AccountSortOption, + sortAccountItems, + useAccountSwitcherSortSettings, +} from '#/state/session/sorting' import {useOnboardingDispatch} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' @@ -32,18 +42,23 @@ import * as SettingsList from '#/screens/Settings/components/SettingsList' import {atoms as a, platform, tokens, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceDismissibleNotice} from '#/components/ageAssurance/AgeAssuranceDismissibleNotice' import {AvatarStackWithFetch} from '#/components/AvatarStack' -import {Button, ButtonText} from '#/components/Button' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from '#/components/contacts/country-allowlist' import {useDialogControl} from '#/components/Dialog' import {SwitchAccountDialog} from '#/components/dialogs/SwitchAccount' +import {SortableList} from '#/components/DraggableList' import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' +import {ArrowRotateClockwise_Stroke2_Corner0_Rounded as ReverseIcon} from '#/components/icons/ArrowRotate' import {Bell_Stroke2_Corner0_Rounded as NotificationIcon} from '#/components/icons/Bell' import {BubbleInfo_Stroke2_Corner2_Rounded as BubbleInfoIcon} from '#/components/icons/BubbleInfo' import {ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon} from '#/components/icons/Chevron' +import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {CodeBrackets_Stroke2_Corner2_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {Contacts_Stroke2_Corner2_Rounded as ContactsIcon} from '#/components/icons/Contacts' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {Eclipse_Stroke2_Corner0_Rounded as EclipseIcon} from '#/components/icons/Eclipse' +import {Filter_Stroke2_Corner0_Rounded as SortIcon} from '#/components/icons/Filter' +import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe' import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock' import {PaintRoller_Stroke2_Corner2_Rounded as PaintRollerIcon} from '#/components/icons/PaintRoller' @@ -68,13 +83,23 @@ import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env' import {useActorStatus} from '#/features/liveNow' import {device, useStorage} from '#/storage' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' +import {useDevMode} from '#/storage/hooks/dev-mode' +import {useHiddenAccountsElsewhere} from '#/storage/hooks/hidden-accounts-elsewhere' type Props = NativeStackScreenProps +type AccountListItem = { + account: SessionAccount + profile?: AppBskyActorDefs.ProfileViewDetailed +} + export function SettingsScreen({}: Props) { const ax = useAnalytics() const {t: l} = useLingui() + const t = useTheme() const reducedMotion = useReducedMotion() - const {logoutEveryAccount} = useSessionApi() + const scrollRef = useAnimatedRef() + const scrollOffset = useScrollViewOffset(scrollRef) + const {logoutEveryAccount, reorderAccounts} = useSessionApi() const {accounts, currentAccount} = useSession() const switchAccountControl = useDialogControl() const signOutPromptControl = Prompt.usePromptControl() @@ -82,13 +107,68 @@ export function SettingsScreen({}: Props) { const {data: otherProfiles} = useProfilesQuery({ handles: accounts .filter(acc => acc.did !== currentAccount?.did) - .map(acc => acc.handle), + .map(acc => acc.did), }) const {pendingDid, onPressSwitchAccount} = useAccountSwitcher() + const enableSquareButtons = useEnableSquareButtons() const [showAccounts, setShowAccounts] = useState(false) + const [isDraggingAccounts, setIsDraggingAccounts] = useState(false) + const [isCustomSortEditing, setIsCustomSortEditing] = useState(false) + const [customAccountsDraft, setCustomAccountsDraft] = useState< + AccountListItem[] + >([]) + const {sortBy, setSortBy, reverse, setReverse} = + useAccountSwitcherSortSettings() + const [, , hiddenDidsSet] = useHiddenAccountsElsewhere() const [showDevOptions, setShowDevOptions] = useState(false) const findContactsEnabled = useIsFindContactsFeatureEnabledBasedOnGeolocation() + const allAccounts = accounts.map(account => ({ + account, + profile: + account.did === currentAccount?.did + ? profile + : otherProfiles?.profiles?.find(p => p.did === account.did), + })) + const otherAccounts = allAccounts.filter( + item => item.account.did !== currentAccount?.did, + ) + const displayedAccounts = isCustomSortEditing + ? customAccountsDraft + : sortAccountItems(otherAccounts, sortBy, reverse) + + const onSelectAccountsSort = (nextSortBy: AccountSortOption) => { + if (nextSortBy === 'custom') { + setCustomAccountsDraft(sortAccountItems(allAccounts, sortBy, reverse)) + setIsCustomSortEditing(true) + return + } + setSortBy(nextSortBy) + setIsCustomSortEditing(false) + setCustomAccountsDraft([]) + } + + const onToggleReverseAccounts = () => { + setReverse(!reverse) + if (isCustomSortEditing) { + setCustomAccountsDraft(prev => [...prev].reverse()) + } + } + + const onCancelCustomSort = () => { + setIsCustomSortEditing(false) + setCustomAccountsDraft([]) + } + + const onSaveCustomSort = () => { + const orderedAccounts = reverse + ? [...customAccountsDraft].reverse() + : customAccountsDraft + reorderAccounts(orderedAccounts.map(item => item.account)) + setSortBy('custom') + setIsCustomSortEditing(false) + setCustomAccountsDraft([]) + } return ( @@ -101,7 +181,7 @@ export function SettingsScreen({}: Props) { - + @@ -119,50 +199,194 @@ export function SettingsScreen({}: Props) { {accounts.length > 1 ? ( <> - { - if (!reducedMotion) { - LayoutAnimation.configureNext( - LayoutAnimation.Presets.easeInEaseOut, - ) - } - setShowAccounts(s => !s) - }}> - - - Switch account - - {showAccounts ? ( - - ) : ( - acc.did) - .filter(did => did !== currentAccount?.did) - .slice(0, 5)} - /> + + { + if (!reducedMotion) { + LayoutAnimation.configureNext( + LayoutAnimation.Presets.easeInEaseOut, + ) + } + if (showAccounts) { + setIsCustomSortEditing(false) + setCustomAccountsDraft([]) + } + setShowAccounts(s => !s) + }}> + + + Switch account + + {showAccounts ? ( + + ) : ( + !hiddenDidsSet.has(item.account.did)) + .map(item => item.account.did) + .slice(0, 5)} + /> + )} + + {showAccounts && ( + + + {({props, state}) => ( + + + + )} + + + + + Sort accounts + + onSelectAccountsSort('alphabetical')}> + + + Alphabetical + + + onSelectAccountsSort('dateModified')}> + + + By date modified + + + onSelectAccountsSort('dateAdded')}> + + + By date added + + + onSelectAccountsSort('custom')}> + + + Custom + + + + + + + + Reverse order + + + + + )} - + {showAccounts && ( <> - {accounts - .filter(acc => acc.did !== currentAccount?.did) - .map(account => ( + {isCustomSortEditing ? ( + item.account.did} + itemHeight={48} + scrollRef={scrollRef} + scrollOffset={scrollOffset} + onDragStart={() => setIsDraggingAccounts(true)} + onDragEnd={() => setIsDraggingAccounts(false)} + onReorder={setCustomAccountsDraft} + renderItem={(item, dragHandle) => ( + + void onPressSwitchAccount(account, logContext) + } + /> + )} + /> + ) : ( + displayedAccounts.map(item => ( p.did === account.did, - )} + key={item.account.did} + account={item.account} + profile={item.profile} pendingDid={pendingDid} onPressSwitchAccount={(account, logContext) => void onPressSwitchAccount(account, logContext) } /> - ))} + )) + )} + {isCustomSortEditing && ( + + + + + )} )} @@ -580,11 +804,15 @@ function AccountRow({ profile, account, pendingDid, + disableSwitching, + dragHandle, onPressSwitchAccount, }: { profile?: AppBskyActorDefs.ProfileViewDetailed account: SessionAccount pendingDid: string | null + disableSwitching?: boolean + dragHandle?: React.ReactNode onPressSwitchAccount: ( account: SessionAccount, logContext: 'Settings', @@ -597,19 +825,47 @@ function AccountRow({ const removePromptControl = Prompt.usePromptControl() const {removeAccount} = useSessionApi() const {isActive: live} = useActorStatus(profile) + const [devModeEnabled] = useDevMode() + const [hiddenAccountsElsewhere, setHiddenAccountsElsewhere, hiddenDidsSet] = + useHiddenAccountsElsewhere() const enableSquareButtons = useEnableSquareButtons() + const isHiddenElsewhere = hiddenDidsSet.has(account.did) const onSwitchAccount = () => { - if (pendingDid) return + if (pendingDid || disableSwitching) return onPressSwitchAccount(account, 'Settings') } + const onToggleHideElsewhere = () => { + setHiddenAccountsElsewhere( + isHiddenElsewhere + ? hiddenAccountsElsewhere.filter(did => did !== account.did) + : [...hiddenAccountsElsewhere, account.did], + ) + Toast.show( + isHiddenElsewhere + ? l`Account will show in other switchers again` + : l`Account hidden from other switchers`, + ) + } + + const onCopyDid = () => { + void setStringAsync(account.did) + Toast.show(l`DID copied to clipboard`) + } + return ( - + + label={l`Switch account`} + disabled={Boolean(disableSwitching)} + contentContainerStyle={[ + { + minHeight: 48, + }, + ]}> {moderationOpts && profile ? ( + style={[ + a.leading_snug, + a.self_center, + !disableSwitching && a.pr_2xl, + ]}> {sanitizeHandle(account.handle, '@')} {pendingDid === account.did && } + {disableSwitching ? ( + {dragHandle} + ) : null} - {!pendingDid && ( + {!pendingDid && !disableSwitching && ( {({props, state}) => ( @@ -637,7 +900,7 @@ function AccountRow({ {...props} style={[ a.absolute, - {top: 10, right: tokens.space.lg}, + {top: 12, right: tokens.space.lg}, a.p_xs, enableSquareButtons ? a.rounded_sm : a.rounded_full, (state.hovered || state.pressed) && t.atoms.bg_contrast_25, @@ -647,6 +910,29 @@ function AccountRow({ )} + + + {isHiddenElsewhere ? ( + Hidden elsewhere + ) : ( + Hide elsewhere + )} + + + + {devModeEnabled ? ( + + + Copy DID + + + + ) : null} + removePromptControl.open()}> diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index bc5ef2f8d..29c407045 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -16,6 +16,8 @@ const accountSchema = z.object({ service: z.string(), did: z.string(), handle: z.string(), + addedAt: z.string().optional(), + lastActiveAt: z.string().optional(), email: z.string().optional(), emailConfirmed: z.boolean().optional(), emailAuthFactor: z.boolean().optional(), diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx index 772e46953..7f0e9a8aa 100644 --- a/src/state/session/index.tsx +++ b/src/state/session/index.tsx @@ -64,6 +64,7 @@ const ApiContext = createContext({ logoutEveryAccount: () => {}, resumeSession: async () => {}, removeAccount: () => {}, + reorderAccounts: () => {}, partialRefreshSession: async () => {}, createEphemeralAgent: async () => { throw new Error('Not implemented') @@ -324,7 +325,7 @@ export function Provider({children}: PropsWithChildren<{}>) { async storedAccount => { if (storedAccount.isOauthSession) { const {agent} = await oauthResumeSession(storedAccount) - return agent + return agent as unknown as import('@atproto/api').BskyAgent } const {agent} = await createAgentAndResume( storedAccount, @@ -340,7 +341,7 @@ export function Provider({children}: PropsWithChildren<{}>) { }) }, ) - return agent + return agent as import('@atproto/api').BskyAgent }, [store], ) @@ -362,6 +363,15 @@ export function Provider({children}: PropsWithChildren<{}>) { }, [store, cancelPendingTask], ) + const reorderAccounts = useCallback( + accounts => { + store.dispatch({ + type: 'reordered-accounts', + accounts, + }) + }, + [store], + ) useEffect(() => { return persisted.onUpdate('session', nextSession => { const synced = nextSession @@ -418,6 +428,7 @@ export function Provider({children}: PropsWithChildren<{}>) { logoutEveryAccount, resumeSession, removeAccount, + reorderAccounts, partialRefreshSession, createEphemeralAgent, }), @@ -428,6 +439,7 @@ export function Provider({children}: PropsWithChildren<{}>) { logoutEveryAccount, resumeSession, removeAccount, + reorderAccounts, partialRefreshSession, createEphemeralAgent, ], diff --git a/src/state/session/reducer.ts b/src/state/session/reducer.ts index 9e2415be8..30b23a0e3 100644 --- a/src/state/session/reducer.ts +++ b/src/state/session/reducer.ts @@ -44,6 +44,10 @@ export type Action = type: 'removed-account' accountDid: string } + | { + type: 'reordered-accounts' + accounts: SessionAccount[] + } | { type: 'logged-out-current-account' } @@ -105,7 +109,11 @@ let reducer = (state: State, action: Action): State => { accounts: state.accounts.map(a => { if (a.did === accountDid) { if (refreshedAccount) { - return refreshedAccount + return { + ...refreshedAccount, + addedAt: a.addedAt, + lastActiveAt: a.lastActiveAt, + } } else { return { ...a, @@ -126,13 +134,22 @@ let reducer = (state: State, action: Action): State => { } case 'switched-to-account': { const {newAccount, newAgent} = action + const existingAccount = state.accounts.find(a => a.did === newAccount.did) + const now = new Date().toISOString() + const mergedAccount = { + ...existingAccount, + ...newAccount, + addedAt: existingAccount?.addedAt ?? now, + lastActiveAt: now, + } return { - accounts: [ - newAccount, - ...state.accounts.filter(a => a.did !== newAccount.did), - ], + accounts: existingAccount + ? state.accounts.map(a => + a.did === mergedAccount.did ? mergedAccount : a, + ) + : [mergedAccount, ...state.accounts], currentAgentState: { - did: newAccount.did, + did: mergedAccount.did, agent: newAgent, }, needsPersist: true, @@ -166,6 +183,13 @@ let reducer = (state: State, action: Action): State => { needsPersist: true, } } + case 'reordered-accounts': { + return { + ...state, + accounts: action.accounts, + needsPersist: true, + } + } case 'logged-out-current-account': { const {currentAgentState} = state const accountDid = currentAgentState.did diff --git a/src/state/session/sorting.ts b/src/state/session/sorting.ts new file mode 100644 index 000000000..4524f1d12 --- /dev/null +++ b/src/state/session/sorting.ts @@ -0,0 +1,80 @@ +import {useMemo} from 'react' + +import {device, useStorage} from '#/storage' +import {type SessionAccount} from './types' + +export type AccountSortOption = + | 'alphabetical' + | 'dateModified' + | 'dateAdded' + | 'custom' + +type SortableAccountItem = { + account: Pick +} + +function getSortableAccount( + item: SessionAccount | SortableAccountItem, +): Pick { + return 'account' in item ? item.account : item +} + +export function sortAccountItems< + T extends SessionAccount | SortableAccountItem, +>(accounts: T[], sortBy: AccountSortOption, reverse: boolean) { + const next = [...accounts] + if (sortBy === 'alphabetical') { + next.sort((a, b) => + getSortableAccount(a).handle.localeCompare( + getSortableAccount(b).handle, + undefined, + { + sensitivity: 'base', + }, + ), + ) + } else if (sortBy === 'dateModified') { + next.sort((a, b) => { + const left = Date.parse(getSortableAccount(a).lastActiveAt ?? '') || 0 + const right = Date.parse(getSortableAccount(b).lastActiveAt ?? '') || 0 + return right - left + }) + } else if (sortBy === 'dateAdded') { + next.sort((a, b) => { + const left = Date.parse(getSortableAccount(a).addedAt ?? '') || 0 + const right = Date.parse(getSortableAccount(b).addedAt ?? '') || 0 + return right - left + }) + } + if (reverse) { + next.reverse() + } + return next +} + +export function useAccountSwitcherSortSettings() { + const [storedSortBy, setStoredSortBy] = useStorage(device, [ + 'settingsAccountSwitcherSortBy', + ]) + const [reverseAccounts = false, setReverseAccounts] = useStorage(device, [ + 'settingsAccountSwitcherReverse', + ]) + + return { + sortBy: storedSortBy ?? ('dateModified' as AccountSortOption), + setSortBy: setStoredSortBy, + reverse: reverseAccounts, + setReverse: setReverseAccounts, + } +} + +export function useSortedAccountItems< + T extends SessionAccount | SortableAccountItem, +>(accounts: T[]) { + const {sortBy, reverse} = useAccountSwitcherSortSettings() + + return useMemo( + () => sortAccountItems(accounts, sortBy, reverse), + [accounts, sortBy, reverse], + ) +} diff --git a/src/state/session/types.ts b/src/state/session/types.ts index ca5850f64..328c8ac3c 100644 --- a/src/state/session/types.ts +++ b/src/state/session/types.ts @@ -46,6 +46,7 @@ export type SessionApiContext = { isSwitchingAccounts?: boolean, ) => Promise removeAccount: (account: SessionAccount) => void + reorderAccounts: (accounts: SessionAccount[]) => void /** * Calls `getSession` and updates select fields on the current account and * `BskyAgent`. This is an alternative to `resumeSession`, which updates diff --git a/src/storage/hooks/hidden-accounts-elsewhere.ts b/src/storage/hooks/hidden-accounts-elsewhere.ts new file mode 100644 index 000000000..71e6908f8 --- /dev/null +++ b/src/storage/hooks/hidden-accounts-elsewhere.ts @@ -0,0 +1,21 @@ +import {useMemo} from 'react' + +import {device, useStorage} from '#/storage' + +export function useHiddenAccountsElsewhere() { + const [hiddenAccountsElsewhere = [], setHiddenAccountsElsewhere] = useStorage( + device, + ['hiddenAccountsElsewhere'], + ) + + const hiddenDidsSet = useMemo( + () => new Set(hiddenAccountsElsewhere), + [hiddenAccountsElsewhere], + ) + + return [ + hiddenAccountsElsewhere, + setHiddenAccountsElsewhere, + hiddenDidsSet, + ] as const +} diff --git a/src/storage/schema.ts b/src/storage/schema.ts index 8d0ea2882..e31ea18e8 100644 --- a/src/storage/schema.ts +++ b/src/storage/schema.ts @@ -64,6 +64,13 @@ export type Device = { activitySubscriptionsNudged?: boolean threadgateNudged?: boolean customAppViewDid: string | undefined + hiddenAccountsElsewhere?: string[] + settingsAccountSwitcherSortBy?: + | 'alphabetical' + | 'dateModified' + | 'dateAdded' + | 'custom' + settingsAccountSwitcherReverse?: boolean /** * Policy update overlays. New IDs are required for each new announcement. diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index baa79e8d8..8901421a4 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -112,8 +112,8 @@ import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' import {type Gif} from '#/state/queries/tenor' import {useAgent, useSession, useSessionApi} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' -import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {type ComposerOpts, type OnPostSuccessData} from '#/state/shell/composer' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {ComposerReplyTo} from '#/view/com/composer/ComposerReplyTo' import {DraftsButton} from '#/view/com/composer/drafts/DraftsButton' @@ -212,7 +212,7 @@ export const ComposePost = ({ }: Props & { cancelRef?: RefObject }) => { - const {currentAccount, accounts} = useSession() + const {accounts} = useSession() const t = useTheme() const ax = useAnalytics() const agent = useAgent() @@ -920,17 +920,24 @@ export const ComposePost = ({ const activeAccount = accounts.find(a => a.did === activeAccountDid) if (activeAccount) { try { - ephemeralAgent = await sessionApi.createEphemeralAgent(activeAccount) + ephemeralAgent = + await sessionApi.createEphemeralAgent(activeAccount) currentAgent = ephemeralAgent } catch (e) { - logger.error('Composer: failed to create ephemeral agent for account switch', { - message: e instanceof Error ? e.message : String(e), - }) + logger.error( + 'Composer: failed to create ephemeral agent for account switch', + { + message: e instanceof Error ? e.message : String(e), + }, + ) setIsPublishing(false) requestSwitchToAccount({requestedAccount: activeAccount.did}) - Toast.show(l`Please sign in as @${activeAccount.handle} to post as them`, { - type: 'warning', - }) + Toast.show( + l`Please sign in as @${activeAccount.handle} to post as them`, + { + type: 'warning', + }, + ) return } } @@ -971,12 +978,14 @@ export const ComposePost = ({ 5, _e => true, async () => { - const res = await currentAgent.app.bsky.unspecced.getPostThreadV2({ - anchor: postUri!, - above: false, - below: filteredThread.posts.length - 1, - branchingFactor: 1, - }) + const res = await currentAgent.app.bsky.unspecced.getPostThreadV2( + { + anchor: postUri!, + above: false, + below: filteredThread.posts.length - 1, + branchingFactor: 1, + }, + ) if (res.data.thread.length !== filteredThread.posts.length) { throw new Error(`composer: app view is not ready`) } @@ -1467,7 +1476,7 @@ let ComposerPost = memo(function ComposerPost({ activeAccountDid: string setActiveAccountDid: (did: string) => void }) { - const {currentAccount, accounts} = useSession() + const {accounts} = useSession() const {t: l} = useLingui() const {data: currentProfile} = useProfileQuery({did: activeAccountDid}) const richtext = post.richtext @@ -1544,6 +1553,7 @@ let ComposerPost = memo(function ComposerPost({ const profiles = data?.profiles const allAccounts = accounts + .filter(account => account.did !== activeAccountDid) .map(account => ({ account, profile: profiles?.find(p => p.did === account.did), diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 066eee683..3482c3758 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -25,6 +25,7 @@ import {useUnreadMessageCount} from '#/state/queries/messages/list-conversations import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useProfilesQuery} from '#/state/queries/profile' import {type SessionAccount, useSession, useSessionApi} from '#/state/session' +import {useSortedAccountItems} from '#/state/session/sorting' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useCloseAllActiveElements} from '#/state/util' import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' @@ -77,6 +78,7 @@ import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {useActorStatus} from '#/features/liveNow' +import {useHiddenAccountsElsewhere} from '#/storage/hooks/hidden-accounts-elsewhere' import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army' import {router} from '../../../routes' @@ -241,6 +243,10 @@ export function SwitchMenuItems({ const {_} = useLingui() const {setShowLoggedOut} = useLoggedOutViewControls() const closeEverything = useCloseAllActiveElements() + const [, , hiddenDidsSet] = useHiddenAccountsElsewhere() + const sortedAccounts = useSortedAccountItems(accounts ?? []).filter( + item => !hiddenDidsSet.has(item.account.did), + ) showExtraButtons = showExtraButtons ?? true @@ -251,13 +257,13 @@ export function SwitchMenuItems({ return ( - {accounts && accounts.length > 0 && ( + {sortedAccounts.length > 0 && ( <> Switch account - {accounts.map(other => ( + {sortedAccounts.map(other => (