From c3b7752a0c5a2b23f27936b9f29c2b2a01b79b35 Mon Sep 17 00:00:00 2001 From: "xan.lol" Date: Sat, 6 Jun 2026 16:57:02 -0700 Subject: [PATCH] fix 6-7 things (DID links setting, square btns, load more,...) - search popup typeahead was always using handle-based links, now uses DID if set - settings didn't persist after restart prompt when settings sync was enabled - enableSquareButtons for chats ui (msg & send msg boxes, invite dialog, + avatars) - Load more button for search results where there should be one - Follow confirmation dialog for group chat settings screen button - don't cut off display name in native DMs headings - native notifications timestamp didn't line up with the rest of items --- src/components/AvatarBubbles.tsx | 17 +++- src/components/Button.tsx | 4 +- src/components/dms/MessageItem.tsx | 6 +- src/components/dms/MessagesListHeader.tsx | 18 +++- .../intents/GroupChatJoinDialog.tsx | 15 +++- src/features/settingsSync/index.tsx | 52 +++++++++++- .../ConversationSettings/AddMembersLink.tsx | 4 +- .../Messages/ConversationSettings/Member.tsx | 28 ++++++- .../Messages/components/ChatFooter.tsx | 4 +- .../Messages/components/EditTextButton.tsx | 6 +- .../Messages/components/InviteLinkDialog.tsx | 6 +- .../Messages/components/MessageComposer.tsx | 5 +- .../Messages/components/MessageInput.tsx | 2 +- src/screens/Search/SearchResults.tsx | 56 ++++++++++++- .../Settings/AccessibilitySettings.tsx | 51 +++++------ src/state/persisted/schema.ts | 4 + .../preferences/restart-required-prompt.tsx | 10 ++- src/state/preferences/settings-sync.tsx | 3 +- .../notifications/NotificationFeedItem.tsx | 84 ++++++++++++++----- src/view/shell/desktop/Search.tsx | 6 +- 20 files changed, 304 insertions(+), 77 deletions(-) diff --git a/src/components/AvatarBubbles.tsx b/src/components/AvatarBubbles.tsx index c49dada16..aba94d368 100644 --- a/src/components/AvatarBubbles.tsx +++ b/src/components/AvatarBubbles.tsx @@ -15,6 +15,7 @@ import { } from '@atproto/api' import {useSession} from '#/state/session' +import {useEnableSquareAvatars} from '#/state/preferences/enable-square-avatars' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme} from '#/alf' import {Person_Filled_Corner2_Rounded as PersonIcon} from '#/components/icons/Person' @@ -138,6 +139,8 @@ function AvatarBubble({ moderation?: ModerationUI }) { const t = useTheme() + const enableSquareAvatars = useEnableSquareAvatars() + const borderRadius = avatarBorderRadius(size, enableSquareAvatars) const animatedStyle = useAnimatedStyle(() => ({ transform: [{translateX: x}, {translateY: y}, {scale: scale.get()}], @@ -147,7 +150,7 @@ function AvatarBubble({ 32 ? 8 : 3 + } + return Math.floor(size / 2) +} + function getLayouts(count: number): Layout[] { if (count === 3) { return [ diff --git a/src/components/Button.tsx b/src/components/Button.tsx index e0ff9f7be..a3cafed99 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -996,6 +996,8 @@ export type StackedButtonProps = Omit< } export function StackedButton({children, ...props}: StackedButtonProps) { + const enableSquareButtons = useEnableSquareButtons() + return ( + + )} @@ -430,7 +456,20 @@ let SearchScreenPostResults = ({ } /> ) : ( - } /> + } + buttonAlign="left" + button={ + hasNextPage + ? { + label: l`Load more`, + text: l`Load more`, + onPress: onEndReached, + disabled: isFetchingNextPage, + } + : undefined + } + /> )} ) : ( @@ -550,7 +589,20 @@ let SearchScreenUserResults = ({ } /> ) : ( - } /> + } + buttonAlign="left" + button={ + hasNextPage && hasSession + ? { + label: l`Load more`, + text: l`Load more`, + onPress: onEndReached, + disabled: isFetchingNextPage, + } + : undefined + } + /> )} ) : ( diff --git a/src/screens/Settings/AccessibilitySettings.tsx b/src/screens/Settings/AccessibilitySettings.tsx index e10280008..a7d7f5d2f 100644 --- a/src/screens/Settings/AccessibilitySettings.tsx +++ b/src/screens/Settings/AccessibilitySettings.tsx @@ -103,28 +103,6 @@ export function AccessibilitySettingsScreen({}: Props) { - {IS_NATIVE && ( - <> - - - - - Haptics - - setHapticsDisabled(value)} - style={[a.w_full]}> - - Disable haptic feedback - - - - - - )} @@ -134,7 +112,7 @@ export function AccessibilitySettingsScreen({}: Props) { setOpenRouterApiKeyControl.open()} - /> + /> @@ -160,7 +138,7 @@ export function AccessibilitySettingsScreen({}: Props) { setOpenRouterModelControl.open()} - /> + /> )} @@ -188,7 +166,7 @@ export function AccessibilitySettingsScreen({}: Props) { setOpenRouterPromptControl.open()} - /> + /> )} @@ -203,6 +181,29 @@ export function AccessibilitySettingsScreen({}: Props) { )} + {IS_NATIVE && ( + <> + + + + + Haptics + + setHapticsDisabled(value)} + style={[a.w_full]}> + + Disable haptic feedback + + + + + + )} + diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index b10fc781d..13f3c5a6a 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -286,6 +286,9 @@ const schema = z.object({ settingsSyncEnabled: z.boolean().optional(), settingsSyncDraftId: z.string().optional(), syncOpenRouterApiKey: z.boolean().optional(), + // Skip the next startup pull after a restart-required setting change so + // local edits are not overwritten by stale cloud data. + settingsSyncSkipNextPull: z.boolean().optional(), }) export type Schema = z.infer @@ -418,6 +421,7 @@ export const defaults: Schema = { settingsSyncEnabled: false, settingsSyncDraftId: undefined, syncOpenRouterApiKey: false, + settingsSyncSkipNextPull: false, } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/restart-required-prompt.tsx b/src/state/preferences/restart-required-prompt.tsx index 44212a596..de2abaad3 100644 --- a/src/state/preferences/restart-required-prompt.tsx +++ b/src/state/preferences/restart-required-prompt.tsx @@ -3,6 +3,7 @@ import {useCallback} from 'react' import {useLingui} from '@lingui/react/macro' import {reloadAppAsync} from 'expo' +import {usePrepareSettingsSyncForRestart} from '#/features/settingsSync' import {IS_WEB} from '#/env' import * as Prompt from '#/components/Prompt' @@ -14,16 +15,19 @@ export function RestartRequiredPrompt({ onConfirm?: () => void }) { const {t: l} = useLingui() + const prepareSettingsSyncForRestart = usePrepareSettingsSyncForRestart() - const handleConfirm = useCallback(() => { + const handleConfirm = useCallback(async () => { onConfirm?.() + await prepareSettingsSyncForRestart() + if (IS_WEB) { window.location.reload() } else { - void reloadAppAsync() + await reloadAppAsync() } - }, [onConfirm]) + }, [onConfirm, prepareSettingsSyncForRestart]) return ( - - - {({timeElapsed}) => ( - <> - {/* make sure there's whitespace around the middot -sfn */} - - {' '} - ·{' '} - - - {timeElapsed} - - - )} - + {(item.type === 'follow' && !hasMultipleAuthors && !isFollowBack) || @@ -723,17 +711,69 @@ export {NotificationFeedItem} function NotificationContent({ content, + timestamp, + niceTimestamp, }: { content: React.ReactElement<{component?: typeof NotificationSentence}> + timestamp: string + niceTimestamp: string }) { - return cloneElement(content, {component: NotificationSentence}) + function Wrapper({children}: {children: React.ReactNode}) { + return ( + + {children} + + ) + } + return cloneElement(content, {component: Wrapper}) +} + +function NotificationTimestamp({ + timestamp, + niceTimestamp, +}: { + timestamp: string + niceTimestamp: string +}) { + const t = useTheme() + return ( + + {({timeElapsed}) => ( + + {' '}·{' '} + {timeElapsed} + + )} + + ) } -function NotificationSentence({children}: {children: React.ReactNode}) { +function NotificationSentence({ + children, + timestamp, + niceTimestamp, +}: { + children: React.ReactNode + timestamp: string + niceTimestamp: string +}) { const t = useTheme() return ( - + {renderInlineTransChildren(children, t)} + ) } diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 189a9fafc..c30081b8d 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -3,8 +3,10 @@ import {View} from 'react-native' import {useSift} from '@bsky.app/sift' import {StackActions, useNavigation} from '@react-navigation/native' +import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {atoms as a} from '#/alf' +import {router} from '#/routes' import { Autocomplete as AutocompleteBase, type AutocompleteItem, @@ -50,7 +52,9 @@ export function DesktopSearch() { if (item.type === 'profile') { onClearText() sift.elements.input.blur() - navigation.navigate('Profile', {name: item.profile.handle}) + const [screen, params] = router.matchPath(makeProfileLink(item.profile)) + // @ts-expect-error TODO: type matchPath well enough that it can be plugged into navigation.navigate directly + navigation.navigate(screen, params) } else if (item.type === 'search') { onClearText() sift.elements.input.blur() -- 2.51.2