Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork

Merge with upstream https://github.com/bluesky-social/social-app/

xan.lol 020904c9 810d1a52

verified
+2 -2
Dockerfile
··· 1 - FROM golang:1.24.5-bullseye AS build-env 1 + FROM golang:1.25-bookworm AS build-env 2 2 3 3 WORKDIR /usr/src/social-app 4 4 ··· 89 89 -o /bskyweb \ 90 90 ./cmd/bskyweb 91 91 92 - FROM debian:bullseye-slim 92 + FROM debian:bookworm-slim 93 93 94 94 ENV GODEBUG=netdns=go 95 95 ENV TZ=Etc/UTC
+2 -2
Dockerfile.embedr
··· 1 - FROM golang:1.24.5-bullseye AS build-env 1 + FROM golang:1.25-bookworm AS build-env 2 2 3 3 WORKDIR /usr/src/social-app 4 4 ··· 58 58 -o /embedr \ 59 59 ./cmd/embedr 60 60 61 - FROM debian:bullseye-slim 61 + FROM debian:bookworm-slim 62 62 63 63 ENV GODEBUG=netdns=go 64 64 ENV TZ=Etc/UTC
+1
assets/icons/bulletlist_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 38"><path stroke="#405168" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M22.333 31.667H45M22.333 6.333H45m-33.333 0A5.333 5.333 0 1 1 1 6.333a5.333 5.333 0 0 1 10.667 0Zm0 25.334a5.333 5.333 0 1 1-10.667 0 5.333 5.333 0 0 1 10.667 0Z"/></svg>
+1
assets/icons/circle_and_square_stroke1_corner0_rounded_filled.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 62 53"><path fill="#405168" d="M28.173.231a5.653 5.653 0 0 1 7.018 3.83l2.66 9.046a20 20 0 0 1 3.986-.397c11.026 0 19.964 8.937 19.964 19.962l-.006.516c-.274 10.787-9.104 19.448-19.958 19.448l-.514-.007c-8.332-.21-15.394-5.528-18.178-12.938l-8.805 2.59a5.654 5.654 0 0 1-7.02-3.83L.232 14.34a5.654 5.654 0 0 1 3.83-7.018L28.172.23ZM41.838 14.71c-1.17 0-2.313.111-3.42.325l3.863 13.137a5.653 5.653 0 0 1-3.83 7.019L25.07 39.126c2.593 6.732 9.122 11.51 16.768 11.51 9.92 0 17.963-8.043 17.963-17.964S51.758 14.71 41.837 14.71ZM33.271 4.624a3.653 3.653 0 0 0-4.535-2.474L4.624 9.24a3.653 3.653 0 0 0-2.475 4.535l7.09 24.113a3.654 3.654 0 0 0 4.536 2.475l8.762-2.577a20 20 0 0 1-.662-5.114c0-8.961 5.905-16.544 14.037-19.069l-2.64-8.98Zm3.204 10.899c-7.302 2.28-12.601 9.096-12.601 17.15 0 1.571.203 3.095.582 4.548l13.431-3.948a3.654 3.654 0 0 0 2.474-4.536l-3.886-13.214Z"/></svg>
+1
assets/icons/editbig_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill="#405168" d="M19.667 4.458a1 1 0 1 0 0-2v2Zm25 23a1 1 0 0 0-2 0h2ZM3.912 45.543l.454-.891-.454.89Zm-2.33-2.33.89-.455h0l-.89.454Zm39.173 2.33-.454-.891h0l.454.89Zm2.33-2.33-.89-.455.89.454ZM1.581 6.37l-.89-.454h0l.89.454Zm2.331-2.331-.454-.891.454.89ZM14.333 32.79h-1a1 1 0 0 0 1 1v-1Zm.781-8.781.707.707-.707-.707ZM36.562 2.562l-.707-.707v0l.707.707Zm7.543 0-.707.707v0l.707-.707Zm.457.458.707-.707v0l-.707.707Zm0 7.542.707.707-.707-.707ZM23.114 32.01l.707.707-.707-.707Zm12.02 14.114v-1h-25.6v2h25.6v-1ZM1 37.591h1v-25.6H0v25.6h1ZM9.533 3.458v1h10.134v-2H9.533v1Zm34.134 24h-1V37.59h2V27.457h-1ZM9.533 46.124v-1c-1.51 0-2.582 0-3.421-.07-.828-.067-1.34-.195-1.746-.402l-.454.89-.454.892c.735.374 1.54.537 2.491.614.94.077 2.107.076 3.584.076v-1ZM1 37.591H0c0 1.477 0 2.645.076 3.584.078.951.24 1.756.614 2.491l.891-.454.891-.454c-.207-.406-.335-.918-.403-1.746C2.001 40.173 2 39.101 2 37.591H1Zm2.912 7.952.454-.891a4.33 4.33 0 0 1-1.894-1.894l-.89.454-.892.454a6.33 6.33 0 0 0 2.768 2.768l.454-.891Zm31.221.581v1c1.477 0 2.645.001 3.585-.076.95-.078 1.756-.24 2.49-.614l-.453-.891-.454-.891c-.406.207-.919.335-1.746.403-.84.068-1.912.07-3.422.07v1Zm8.534-8.533h-1c0 1.51-.001 2.582-.07 3.421-.067.828-.196 1.34-.403 1.746l.891.454.891.454c.375-.735.537-1.54.615-2.49.076-.94.076-2.108.076-3.585h-1Zm-2.912 7.952.454.89a6.33 6.33 0 0 0 2.767-2.767l-.89-.454-.892-.454a4.33 4.33 0 0 1-1.893 1.894l.454.89ZM1 11.99h1c0-1.51 0-2.582.07-3.422.067-.827.195-1.34.402-1.745l-.89-.454-.892-.454c-.374.734-.536 1.54-.614 2.49C-.001 9.345 0 10.513 0 11.99h1Zm8.533-8.533v-1c-1.477 0-2.645-.001-3.584.076-.951.077-1.756.24-2.49.614l.453.89.454.892c.406-.207.918-.336 1.746-.403.839-.069 1.911-.07 3.421-.07v-1ZM1.581 6.37l.891.454A4.33 4.33 0 0 1 4.366 4.93l-.454-.891-.454-.891A6.33 6.33 0 0 0 .69 5.916l.891.454Zm12.752 19.525h-1v6.896h2v-6.896h-1Zm0 6.896v1h6.896v-2h-6.896v1Zm.781-8.781.707.707L37.27 3.269l-.707-.707-.707-.707-21.448 21.448.707.707Zm28.99-21.448-.706.707.457.458.707-.707.707-.707-.457-.458-.707.707Zm.458 8-.707-.707-21.448 21.448.707.707.707.707L45.27 11.269l-.707-.707Zm0-7.542-.707.707a4.333 4.333 0 0 1 0 6.128l.707.707.707.707a6.333 6.333 0 0 0 0-8.956l-.707.707Zm-8-.458.707.707a4.333 4.333 0 0 1 6.129 0l.707-.707.707-.707a6.333 6.333 0 0 0-8.957 0l.707.707ZM21.23 32.791v1c.972 0 1.905-.386 2.593-1.074l-.708-.707-.707-.707a1.67 1.67 0 0 1-1.178.488v1Zm-6.896-6.896h1c0-.442.176-.866.489-1.178l-.708-.707-.707-.707a3.67 3.67 0 0 0-1.074 2.592h1Z"/></svg>
+1
assets/icons/hashtagwide_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 46"><path stroke="#405168" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.333 1 9 45M37 1l-5.333 44M1 11.667h44m0 22.666H1"/></svg>
+1
assets/icons/heart2_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 50 45"><path stroke="#405168" stroke-linejoin="round" stroke-width="2" d="M49 17c0 15.333-22 26.667-24 26.667S1 32.333 1 17C1 6.333 7.667 1 14.333 1S25 5 25 5s4-4 10.667-4S49 6.333 49 17Z"/></svg>
+1
assets/icons/image_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 46"><path stroke="#080B12" stroke-linecap="round" stroke-width="1.5" d="m1.417 28.645 7.586-5.676a5.33 5.33 0 0 1 6.867.809c3.98 4.286 8.594 8.182 14.88 8.182 5.794 0 9.633-2.147 13.333-5.847m-38 18.637h33.334a5.333 5.333 0 0 0 5.333-5.333V6.083A5.333 5.333 0 0 0 39.417.75H6.083A5.333 5.333 0 0 0 .75 6.083v33.334a5.333 5.333 0 0 0 5.333 5.333ZM36.75 14.083a5.333 5.333 0 1 1-10.667 0 5.333 5.333 0 0 1 10.667 0Z"/></svg>
+1
assets/icons/message_stroke1_corner0_rounded_filled.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 50 50"><path stroke="#405168" stroke-linecap="square" stroke-linejoin="round" stroke-width="2" d="M9 1h32a8 8 0 0 1 8 8v21.333a8 8 0 0 1-8 8H27.667L14.333 49V38.333H9a8 8 0 0 1-8-8V9a8 8 0 0 1 8-8Z"/></svg>
+1
assets/icons/peopleremove2_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 43 48"><path stroke="#405168" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.603 46.333H3.532c-1.572 0-2.816-1.358-2.472-2.891 2.033-9.046 9.421-15.775 19.543-15.775q1.367 0 2.666.16m18.667 7.84L36.603 41m0 0-5.334 5.333M36.603 41l-5.334-5.333M36.603 41l5.333 5.333m-12-36A9.333 9.333 0 1 1 20.603 1a9.333 9.333 0 0 1 9.333 9.333Z"/></svg>
+1
assets/icons/videoclip_stroke1_corner0_rounded.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 46"><path stroke="#405168" stroke-linecap="square" stroke-linejoin="round" stroke-width="2" d="M1 23h10.667M1 23V12m0 11v11m10.667-11h22.666m-22.666 0v11m0-11V12m22.666 11H45m-10.667 0v12.222m0-12.222V12M45 23V12m0 11v12.222M34.333 45h5.334A5.333 5.333 0 0 0 45 39.667v-4.445M34.333 45v-9.778m0 9.778H11.667M34.333 1h5.334A5.333 5.333 0 0 1 45 6.333V12M34.333 1v11m0-11H11.667m22.666 11H45M34.333 35.222H45M11.667 45H6.333A5.333 5.333 0 0 1 1 39.667V34m10.667 11V34m0-33H6.333A5.333 5.333 0 0 0 1 6.333V12M11.667 1v11M1 12h10.667M1 34h10.667"/></svg>
+1 -1
bskyweb/go.mod
··· 1 1 module github.com/bluesky-social/social-app/bskyweb 2 2 3 - go 1.24.5 3 + go 1.25 4 4 5 5 require ( 6 6 github.com/bluesky-social/indigo v0.0.0-20250729223159-573ae927246a
+2 -1
src/components/Button.tsx
··· 801 801 * also so that we can calculate transforms. 802 802 */ 803 803 const iconSize = { 804 - '2xs': 8, 805 804 xs: 12, 806 805 sm: 16, 807 806 md: 18, 808 807 lg: 24, 809 808 xl: 28, 809 + '2xs': 8, 810 810 '2xl': 32, 811 + '3xl': 40, 811 812 }[iconSizeShorthand] 812 813 813 814 /*
+27
src/components/Lists.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {cleanError} from '#/lib/strings/errors' 7 + import { 8 + EmptyState, 9 + type EmptyStateButtonProps, 10 + } from '#/view/com/util/EmptyState' 7 11 import {CenteredView} from '#/view/com/util/Views' 8 12 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 13 import {Button, ButtonText} from '#/components/Button' ··· 129 133 hideBackButton, 130 134 sideBorders, 131 135 topBorder = false, 136 + emptyStateIcon, 137 + emptyStateButton, 138 + useEmptyState = false, 132 139 }: { 133 140 isLoading: boolean 134 141 noEmpty?: boolean ··· 143 150 hideBackButton?: boolean 144 151 sideBorders?: boolean 145 152 topBorder?: boolean 153 + emptyStateIcon?: React.ComponentType<any> | React.ReactElement 154 + emptyStateButton?: EmptyStateButtonProps 155 + useEmptyState?: boolean 146 156 }): React.ReactNode => { 147 157 const t = useTheme() 148 158 const {_} = useLingui() ··· 177 187 sideBorders={sideBorders} 178 188 hideBackButton={hideBackButton} 179 189 /> 190 + ) 191 + } 192 + 193 + if (useEmptyState) { 194 + return ( 195 + <View style={[t.atoms.border_contrast_low]}> 196 + <EmptyState 197 + icon={emptyStateIcon} 198 + message={ 199 + emptyMessage ?? 200 + (emptyType === 'results' 201 + ? _(msg`No results found`) 202 + : _(msg`Page not found`)) 203 + } 204 + button={emptyStateButton} 205 + /> 206 + </View> 180 207 ) 181 208 } 182 209
+8 -1
src/components/StarterPack/Main/PostsList.tsx
··· 9 9 import {EmptyState} from '#/view/com/util/EmptyState' 10 10 import {type ListRef} from '#/view/com/util/List' 11 11 import {type SectionRef} from '#/screens/Profile/Sections/types' 12 + import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 12 13 13 14 interface ProfilesListProps { 14 15 listUri: string ··· 33 34 })) 34 35 35 36 const renderPostsEmpty = useCallback(() => { 36 - return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 37 + return ( 38 + <EmptyState 39 + icon={HashtagWideIcon} 40 + iconSize="2xl" 41 + message={_(msg`This feed is empty.`)} 42 + /> 43 + ) 37 44 }, [_]) 38 45 39 46 return (
+33 -1
src/components/StarterPack/ProfileStarterPacks.tsx
··· 21 21 import {logger} from '#/logger' 22 22 import {isIOS} from '#/platform/detection' 23 23 import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' 24 + import { 25 + EmptyState, 26 + type EmptyStateButtonProps, 27 + } from '#/view/com/util/EmptyState' 24 28 import {List, type ListRef} from '#/view/com/util/List' 25 29 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 26 30 import {atoms as a, ios, useTheme} from '#/alf' ··· 47 51 testID?: string 48 52 setScrollViewTag: (tag: number | null) => void 49 53 isMe: boolean 54 + emptyStateMessage?: string 55 + emptyStateButton?: EmptyStateButtonProps 56 + emptyStateIcon?: React.ComponentType<any> | React.ReactElement 50 57 } 51 58 52 59 function keyExtractor(item: AppBskyGraphDefs.StarterPackView) { ··· 63 70 testID, 64 71 setScrollViewTag, 65 72 isMe, 73 + emptyStateMessage, 74 + emptyStateButton, 75 + emptyStateIcon, 66 76 }: ProfileFeedgensProps) { 67 77 const t = useTheme() 68 78 const bottomBarOffset = useBottomBarOffset(100) ··· 79 89 const {isTabletOrDesktop} = useWebMediaQueries() 80 90 81 91 const items = data?.pages.flatMap(page => page.starterPacks) 92 + const {_} = useLingui() 93 + 94 + const EmptyComponent = useCallback(() => { 95 + if (emptyStateMessage || emptyStateButton || emptyStateIcon) { 96 + return ( 97 + <View style={[a.px_lg, a.align_center, a.justify_center]}> 98 + <EmptyState 99 + icon={emptyStateIcon} 100 + iconSize="3xl" 101 + message={ 102 + emptyStateMessage ?? 103 + _( 104 + 'Starter packs let you share your favorite feeds and people with your friends.', 105 + ) 106 + } 107 + button={emptyStateButton} 108 + /> 109 + </View> 110 + ) 111 + } 112 + return <Empty /> 113 + }, [_, emptyStateMessage, emptyStateButton, emptyStateIcon]) 82 114 83 115 useImperativeHandle(ref, () => ({ 84 116 scrollToTop: () => {}, ··· 146 178 onEndReached={onEndReached} 147 179 onRefresh={onRefresh} 148 180 ListEmptyComponent={ 149 - data ? (isMe ? Empty : undefined) : FeedLoadingPlaceholder 181 + data ? (isMe ? EmptyComponent : undefined) : FeedLoadingPlaceholder 150 182 } 151 183 ListFooterComponent={ 152 184 !!data && items?.length !== 0 && isMe ? CreateAnother : undefined
+8
src/components/icons/BulletList.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const BulletList_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '0 0 47 38', 5 + strokeLinecap: 'round', 6 + strokeLinejoin: 'round', 7 + strokeWidth: 2, 8 + path: 'M22.333 31.667H45M22.333 6.333H45m-33.333 0A5.333 5.333 0 1 1 1 6.333a5.333 5.333 0 0 1 10.667 0Zm0 25.334a5.333 5.333 0 1 1-10.667 0 5.333 5.333 0 0 1 10.667 0Z', 9 + }) 10 + 3 11 export const BulletList_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 12 path: 'M6 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2ZM3 7a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm9 0a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2h-7a1 1 0 0 1-1-1Zm-6 9a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm9 0a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2h-7a1 1 0 0 1-1-1Z', 5 13 })
+7
src/components/icons/CircleAndSquare.tsx
··· 1 + import {createSinglePathSVG} from './TEMPLATE' 2 + 3 + export const Circle_And_Square_Stroke1_Corner0_Rounded_Filled = 4 + createSinglePathSVG({ 5 + viewBox: '0 0 62 53', 6 + path: 'M28.173.231a5.653 5.653 0 0 1 7.018 3.83l2.66 9.046a20 20 0 0 1 3.986-.397c11.026 0 19.964 8.937 19.964 19.962l-.006.516c-.274 10.787-9.104 19.448-19.958 19.448l-.514-.007c-8.332-.21-15.394-5.528-18.178-12.938l-8.805 2.59a5.654 5.654 0 0 1-7.02-3.83L.232 14.34a5.654 5.654 0 0 1 3.83-7.018L28.172.23ZM41.838 14.71c-1.17 0-2.313.111-3.42.325l3.863 13.137a5.653 5.653 0 0 1-3.83 7.019L25.07 39.126c2.593 6.732 9.122 11.51 16.768 11.51 9.92 0 17.963-8.043 17.963-17.964S51.758 14.71 41.837 14.71ZM33.271 4.624a3.653 3.653 0 0 0-4.535-2.474L4.624 9.24a3.653 3.653 0 0 0-2.475 4.535l7.09 24.113a3.654 3.654 0 0 0 4.536 2.475l8.762-2.577a20 20 0 0 1-.662-5.114c0-8.961 5.905-16.544 14.037-19.069l-2.64-8.98Zm3.204 10.899c-7.302 2.28-12.601 9.096-12.601 17.15 0 1.571.203 3.095.582 4.548l13.431-3.948a3.654 3.654 0 0 0 2.474-4.536l-3.886-13.214Z', 7 + })
+5
src/components/icons/EditBig.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const EditBig_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '0 0 48 48', 5 + path: 'M19.667 4.458a1 1 0 1 0 0-2v2Zm25 23a1 1 0 0 0-2 0h2ZM3.912 45.543l.454-.891-.454.89Zm-2.33-2.33.89-.455h0l-.89.454Zm39.173 2.33-.454-.891h0l.454.89Zm2.33-2.33-.89-.455.89.454ZM1.581 6.37l-.89-.454h0l.89.454Zm2.331-2.331-.454-.891.454.89ZM14.333 32.79h-1a1 1 0 0 0 1 1v-1Zm.781-8.781.707.707-.707-.707ZM36.562 2.562l-.707-.707v0l.707.707Zm7.543 0-.707.707v0l.707-.707Zm.457.458.707-.707v0l-.707.707Zm0 7.542.707.707-.707-.707ZM23.114 32.01l.707.707-.707-.707Zm12.02 14.114v-1h-25.6v2h25.6v-1ZM1 37.591h1v-25.6H0v25.6h1ZM9.533 3.458v1h10.134v-2H9.533v1Zm34.134 24h-1V37.59h2V27.457h-1ZM9.533 46.124v-1c-1.51 0-2.582 0-3.421-.07-.828-.067-1.34-.195-1.746-.402l-.454.89-.454.892c.735.374 1.54.537 2.491.614.94.077 2.107.076 3.584.076v-1ZM1 37.591H0c0 1.477 0 2.645.076 3.584.078.951.24 1.756.614 2.491l.891-.454.891-.454c-.207-.406-.335-.918-.403-1.746C2.001 40.173 2 39.101 2 37.591H1Zm2.912 7.952.454-.891a4.33 4.33 0 0 1-1.894-1.894l-.89.454-.892.454a6.33 6.33 0 0 0 2.768 2.768l.454-.891Zm31.221.581v1c1.477 0 2.645.001 3.585-.076.95-.078 1.756-.24 2.49-.614l-.453-.891-.454-.891c-.406.207-.919.335-1.746.403-.84.068-1.912.07-3.422.07v1Zm8.534-8.533h-1c0 1.51-.001 2.582-.07 3.421-.067.828-.196 1.34-.403 1.746l.891.454.891.454c.375-.735.537-1.54.615-2.49.076-.94.076-2.108.076-3.585h-1Zm-2.912 7.952.454.89a6.33 6.33 0 0 0 2.767-2.767l-.89-.454-.892-.454a4.33 4.33 0 0 1-1.893 1.894l.454.89ZM1 11.99h1c0-1.51 0-2.582.07-3.422.067-.827.195-1.34.402-1.745l-.89-.454-.892-.454c-.374.734-.536 1.54-.614 2.49C-.001 9.345 0 10.513 0 11.99h1Zm8.533-8.533v-1c-1.477 0-2.645-.001-3.584.076-.951.077-1.756.24-2.49.614l.453.89.454.892c.406-.207.918-.336 1.746-.403.839-.069 1.911-.07 3.421-.07v-1ZM1.581 6.37l.891.454A4.33 4.33 0 0 1 4.366 4.93l-.454-.891-.454-.891A6.33 6.33 0 0 0 .69 5.916l.891.454Zm12.752 19.525h-1v6.896h2v-6.896h-1Zm0 6.896v1h6.896v-2h-6.896v1Zm.781-8.781.707.707L37.27 3.269l-.707-.707-.707-.707-21.448 21.448.707.707Zm28.99-21.448-.706.707.457.458.707-.707.707-.707-.457-.458-.707.707Zm.458 8-.707-.707-21.448 21.448.707.707.707.707L45.27 11.269l-.707-.707Zm0-7.542-.707.707a4.333 4.333 0 0 1 0 6.128l.707.707.707.707a6.333 6.333 0 0 0 0-8.956l-.707.707Zm-8-.458.707.707a4.333 4.333 0 0 1 6.129 0l.707-.707.707-.707a6.333 6.333 0 0 0-8.957 0l.707.707ZM21.23 32.791v1c.972 0 1.905-.386 2.593-1.074l-.708-.707-.707-.707a1.67 1.67 0 0 1-1.178.488v1Zm-6.896-6.896h1c0-.442.176-.866.489-1.178l-.708-.707-.707-.707a3.67 3.67 0 0 0-1.074 2.592h1Z', 6 + }) 7 + 3 8 export const EditBig_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 9 path: 'M17.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-9 9A1 1 0 0 1 12 16H9a1 1 0 0 1-1-1v-3a1 1 0 0 1 .293-.707l9-9ZM10 12.414V14h1.586l8-8L18 4.414l-8 8ZM3 4a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2H5v14h14v-6a1 1 0 1 1 2 0v7a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Z', 5 10 })
+8
src/components/icons/Hashtag.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const HashtagWide_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '0 0 46 46', 5 + strokeLinecap: 'round', 6 + strokeLinejoin: 'round', 7 + strokeWidth: 2, 8 + path: 'M14.333 1 9 45M37 1l-5.333 44M1 11.667h44m0 22.666H1', 9 + }) 10 + 3 11 export const Hashtag_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 12 path: 'M9.124 3.008a1 1 0 0 1 .868 1.116L9.632 7h5.985l.39-3.124a1 1 0 0 1 1.985.248L17.632 7H20a1 1 0 1 1 0 2h-2.617l-.75 6H20a1 1 0 1 1 0 2h-3.617l-.39 3.124a1 1 0 1 1-1.985-.248l.36-2.876H8.382l-.39 3.124a1 1 0 1 1-1.985-.248L6.368 17H4a1 1 0 1 1 0-2h2.617l.75-6H4a1 1 0 1 1 0-2h3.617l.39-3.124a1 1 0 0 1 1.117-.868ZM9.383 9l-.75 6h5.984l.75-6H9.383Z', 5 13 })
+7
src/components/icons/Heart2.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const Heart2_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '0 0 51 46', 5 + strokeLinejoin: 'round', 6 + strokeWidth: 2, 7 + path: 'M49 17c0 15.333-22 26.667-24 26.667S1 32.333 1 17C1 6.333 7.667 1 14.333 1S25 5 25 5s4-4 10.667-4S49 6.333 49 17Z', 8 + }) 9 + 3 10 export const Heart2_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 11 path: 'M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z', 5 12 })
+7
src/components/icons/Image.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const Image_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '0 0 46 46', 5 + strokeLinecap: 'round', 6 + strokeWidth: 1.5, 7 + path: 'm1.417 28.645 7.586-5.676a5.33 5.33 0 0 1 6.867.809c3.98 4.286 8.594 8.182 14.88 8.182 5.794 0 9.633-2.147 13.333-5.847m-38 18.637h33.334a5.333 5.333 0 0 0 5.333-5.333V6.083A5.333 5.333 0 0 0 39.417.75H6.083A5.333 5.333 0 0 0 .75 6.083v33.334a5.333 5.333 0 0 0 5.333 5.333ZM36.75 14.083a5.333 5.333 0 1 1-10.667 0 5.333 5.333 0 0 1 10.667 0Z', 8 + }) 9 + 3 10 export const Image_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 11 path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z', 5 12 })
+8
src/components/icons/Message.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const Message_Stroke1_Corner0_Rounded_Filled = createSinglePathSVG({ 4 + viewBox: '0 0 51 51', 5 + strokeWidth: 2, 6 + strokeLinecap: 'square', 7 + strokeLinejoin: 'round', 8 + path: 'M9 1h32a8 8 0 0 1 8 8v21.333a8 8 0 0 1-8 8H27.667L14.333 49V38.333H9a8 8 0 0 1-8-8V9a8 8 0 0 1 8-8Z', 9 + }) 10 + 3 11 export const Message_Stroke2_Corner0_Rounded_Filled = createSinglePathSVG({ 4 12 path: 'M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10a9.968 9.968 0 0 1-4.136-.893l-4.68.876a1 1 0 0 1-1.164-1.184l.931-4.537A9.965 9.965 0 0 1 2 12Zm4.25 0a1.25 1.25 0 1 0 2.5 0 1.25 1.25 0 0 0-2.5 0Zm4.5 0a1.25 1.25 0 1 0 2.5 0 1.25 1.25 0 0 0-2.5 0Zm5.75 1.25a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5Z', 5 13 })
+8
src/components/icons/PeopleRemove2.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const PeopleRemove2_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '-15 0 65 64', 5 + strokeWidth: 2, 6 + strokeLinecap: 'round', 7 + strokeLinejoin: 'round', 8 + path: 'M20.603 46.333H3.532c-1.572 0-2.816-1.358-2.472-2.891 2.033-9.046 9.421-15.775 19.543-15.775q1.367 0 2.666.16m18.667 7.84L36.603 41m0 0-5.334 5.333M36.603 41l-5.334-5.333M36.603 41l5.333 5.333m-12-36A9.333 9.333 0 1 1 20.603 1a9.333 9.333 0 0 1 9.333 9.333Z', 9 + }) 10 + 3 11 export const PeopleRemove2_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 12 path: 'M10 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM5.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM16 11a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1ZM3.678 19h12.644c-.71-2.909-3.092-5-6.322-5s-5.613 2.091-6.322 5Zm-2.174.906C1.917 15.521 5.242 12 10 12c4.758 0 8.083 3.521 8.496 7.906A1 1 0 0 1 17.5 21h-15a1 1 0 0 1-.996-1.094Z', 5 13 })
+44 -1
src/components/icons/TEMPLATE.tsx
··· 28 28 }, 29 29 ) 30 30 31 - export function createSinglePathSVG({path}: {path: string}) { 31 + export function createSinglePathSVG({ 32 + path, 33 + viewBox, 34 + strokeWidth = 0, 35 + strokeLinecap = 'butt', 36 + strokeLinejoin = 'miter', 37 + }: { 38 + path: string 39 + viewBox?: string 40 + strokeWidth?: number 41 + strokeLinecap?: 'butt' | 'round' | 'square' 42 + strokeLinejoin?: 'miter' | 'round' | 'bevel' 43 + }) { 44 + return React.forwardRef<Svg, Props>(function LogoImpl(props, ref) { 45 + const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) 46 + 47 + const hasStroke = strokeWidth > 0 48 + 49 + return ( 50 + <Svg 51 + fill="none" 52 + {...rest} 53 + ref={ref} 54 + viewBox={viewBox || '0 0 24 24'} 55 + width={size} 56 + height={size} 57 + style={[style]}> 58 + {gradient} 59 + <Path 60 + fill={hasStroke ? 'none' : fill} 61 + stroke={hasStroke ? fill : 'none'} 62 + strokeWidth={strokeWidth} 63 + strokeLinecap={strokeLinecap} 64 + strokeLinejoin={strokeLinejoin} 65 + fillRule="evenodd" 66 + clipRule="evenodd" 67 + d={path} 68 + /> 69 + </Svg> 70 + ) 71 + }) 72 + } 73 + 74 + export function createSinglePathSVG2({path}: {path: string}) { 32 75 return React.forwardRef<Svg, Props>(function LogoImpl(props, ref) { 33 76 const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) 34 77
+8
src/components/icons/VideoClip.tsx
··· 1 1 import {createSinglePathSVG} from './TEMPLATE' 2 2 3 + export const VideoClip_Stroke1_Corner0_Rounded = createSinglePathSVG({ 4 + viewBox: '0 0 46 46', 5 + strokeLinecap: 'square', 6 + strokeLinejoin: 'round', 7 + strokeWidth: 2, 8 + path: 'M1 23h10.667M1 23V12m0 11v11m10.667-11h22.666m-22.666 0v11m0-11V12m22.666 11H45m-10.667 0v12.222m0-12.222V12M45 23V12m0 11v12.222M34.333 45h5.334A5.333 5.333 0 0 0 45 39.667v-4.445M34.333 45v-9.778m0 9.778H11.667M34.333 1h5.334A5.333 5.333 0 0 1 45 6.333V12M34.333 1v11m0-11H11.667m22.666 11H45M34.333 35.222H45M11.667 45H6.333A5.333 5.333 0 0 1 1 39.667V34m10.667 11V34m0-33H6.333A5.333 5.333 0 0 0 1 6.333V12M11.667 1v11M1 12h10.667M1 34h10.667', 9 + }) 10 + 3 11 export const VideoClip_Stroke2_Corner0_Rounded = createSinglePathSVG({ 4 12 path: 'M3 4a1 1 0 011-1h16a1 1 0 011 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V4Zm2 1v2h2V5H5Zm4 0v6h6V5H9Zm8 0v2h2V5h-2Zm2 4h-2v2h2V9Zm0 4h-2v2h2V13Zm0 4h-2V19h2ZM15 19v-6H9v6h6Zm-8 0v-2H5v2h2Zm-2-4h2v-2H5v2Zm0-4h2V9H5v2Z', 5 13 })
+1
src/components/icons/common.tsx
··· 20 20 lg: 24, 21 21 xl: 28, 22 22 '2xl': 32, 23 + '3xl': 48, 23 24 } as const 24 25 25 26 export function useCommonSVGProps(props: Props) {
+12
src/lib/strings/errors.ts
··· 52 52 const str = String(e) 53 53 return str.includes('Bad token scope') || str.includes('Bad token method') 54 54 } 55 + 56 + /** 57 + * Intended to capture "User cancelled" or "Crop cancelled" errors 58 + * that we often get from expo modules such expo-image-crop-tool 59 + * 60 + * The exact name has changed in the past so let's just see if the string 61 + * contains "cancel" 62 + */ 63 + export function isCancelledError(e: unknown) { 64 + const str = String(e).toLowerCase() 65 + return str.includes('cancel') 66 + }
+11 -1
src/lib/strings/time.ts
··· 1 1 import {type I18n} from '@lingui/core' 2 + import {msg} from '@lingui/macro' 2 3 3 4 export function niceDate( 4 5 i18n: I18n, 5 6 date: number | string | Date, 6 - dateStyle: 'short' | 'medium' | 'long' | 'full' = 'long', 7 + dateStyle: 'short' | 'medium' | 'long' | 'full' | 'dot separated' = 'long', 7 8 ) { 8 9 const d = new Date(date) 10 + 11 + if (dateStyle === 'dot separated') { 12 + return i18n._( 13 + msg({ 14 + context: 'date and time formatted like this: [time] · [date]', 15 + message: `${i18n.date(d, {timeStyle: 'short'})} · ${i18n.date(d, {day: 'numeric', month: 'numeric', year: '2-digit'})}`, 16 + }), 17 + ) 18 + } 9 19 10 20 return i18n.date(d, { 11 21 dateStyle,
+199 -126
src/locale/locales/en/messages.po
··· 128 128 msgid "{0, plural, other {+# more}}" 129 129 msgstr "" 130 130 131 + #: src/lib/strings/time.ts:13 132 + msgctxt "date and time formatted like this: [time] · [date]" 133 + msgid "{0} · {1}" 134 + msgstr "" 135 + 131 136 #: src/components/moderation/ContentHider.tsx:89 132 137 msgid "{0} (Account)" 133 138 msgstr "" ··· 695 700 msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event." 696 701 msgstr "" 697 702 698 - #: src/screens/ProfileList/AboutSection.tsx:62 699 - #: src/screens/ProfileList/AboutSection.tsx:80 703 + #: src/screens/ProfileList/AboutSection.tsx:63 704 + #: src/screens/ProfileList/AboutSection.tsx:81 700 705 msgid "Add a user to this list" 701 706 msgstr "" 702 707 ··· 738 743 msgid "Add app password" 739 744 msgstr "" 740 745 741 - #: src/screens/Settings/AppPasswords.tsx:73 742 - #: src/screens/Settings/AppPasswords.tsx:81 746 + #: src/screens/Settings/AppPasswords.tsx:74 747 + #: src/screens/Settings/AppPasswords.tsx:82 743 748 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:111 744 749 msgid "Add App Password" 745 750 msgstr "" ··· 766 771 msgid "Add muted words and tags" 767 772 msgstr "" 768 773 769 - #: src/screens/ProfileList/AboutSection.tsx:70 770 - #: src/screens/ProfileList/AboutSection.tsx:88 774 + #: src/screens/ProfileList/AboutSection.tsx:71 775 + #: src/screens/ProfileList/AboutSection.tsx:89 771 776 msgid "Add people" 772 777 msgstr "" 773 778 ··· 958 963 msgid "Allow your followers to reply" 959 964 msgstr "" 960 965 961 - #: src/screens/Settings/AppPasswords.tsx:199 966 + #: src/screens/Settings/AppPasswords.tsx:200 962 967 msgid "Allows access to direct messages" 963 968 msgstr "" 964 969 ··· 996 1001 msgid "Alt Text" 997 1002 msgstr "" 998 1003 999 - #: src/view/com/composer/photos/Gallery.tsx:260 1004 + #: src/view/com/composer/photos/Gallery.tsx:261 1000 1005 msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." 1001 1006 msgstr "" 1002 1007 ··· 1031 1036 msgid "An error occurred while fetching the feed." 1032 1037 msgstr "" 1033 1038 1034 - #: src/components/StarterPack/ProfileStarterPacks.tsx:339 1039 + #: src/components/StarterPack/ProfileStarterPacks.tsx:371 1035 1040 msgid "An error occurred while generating your starter pack. Want to try again?" 1036 1041 msgstr "" 1037 1042 ··· 1160 1165 msgid "App Password" 1161 1166 msgstr "" 1162 1167 1163 - #: src/screens/Settings/AppPasswords.tsx:145 1168 + #: src/screens/Settings/AppPasswords.tsx:146 1164 1169 msgctxt "toast" 1165 1170 msgid "App password deleted" 1166 1171 msgstr "" ··· 1183 1188 msgstr "" 1184 1189 1185 1190 #: src/Navigation.tsx:351 1186 - #: src/screens/Settings/AppPasswords.tsx:49 1191 + #: src/screens/Settings/AppPasswords.tsx:50 1187 1192 msgid "App Passwords" 1188 1193 msgstr "" 1189 1194 ··· 1244 1249 msgid "Archived post" 1245 1250 msgstr "" 1246 1251 1247 - #: src/screens/Settings/AppPasswords.tsx:208 1252 + #: src/screens/Settings/AppPasswords.tsx:209 1248 1253 msgid "Are you sure you want to delete the app password \"{0}\"?" 1249 1254 msgstr "" 1250 1255 ··· 1368 1373 msgstr "" 1369 1374 1370 1375 #: src/components/dialogs/StarterPackDialog.tsx:71 1371 - #: src/components/StarterPack/ProfileStarterPacks.tsx:231 1372 - #: src/components/StarterPack/ProfileStarterPacks.tsx:241 1376 + #: src/components/StarterPack/ProfileStarterPacks.tsx:263 1377 + #: src/components/StarterPack/ProfileStarterPacks.tsx:273 1378 + #: src/view/screens/Profile.tsx:351 1373 1379 msgid "Before creating a starter pack, you must first verify your email." 1374 1380 msgstr "" 1375 1381 ··· 1530 1536 msgid "Bluesky Social Terms of Service" 1531 1537 msgstr "" 1532 1538 1533 - #: src/components/StarterPack/ProfileStarterPacks.tsx:306 1539 + #: src/components/StarterPack/ProfileStarterPacks.tsx:338 1534 1540 msgid "Bluesky will choose a set of recommended accounts from people in your network." 1535 1541 msgstr "" 1536 1542 ··· 1565 1571 1566 1572 #: src/components/moderation/ReportDialog/utils/useReportOptions.ts:215 1567 1573 msgid "Breaking site rules" 1574 + msgstr "" 1575 + 1576 + #: src/view/com/feeds/ProfileFeedgens.tsx:158 1577 + #: src/view/com/feeds/ProfileFeedgens.tsx:159 1578 + msgid "Browse custom feeds" 1568 1579 msgstr "" 1569 1580 1570 1581 #: src/components/FeedInterstitials.tsx:436 ··· 1613 1624 msgid "Business" 1614 1625 msgstr "" 1615 1626 1627 + #: src/screens/Bookmarks/index.tsx:277 1628 + msgid "Button to go back to the home timeline" 1629 + msgstr "" 1630 + 1616 1631 #: src/components/LabelingServiceCard/index.tsx:62 1617 1632 #: src/components/moderation/ReportDialog/index.tsx:834 1618 1633 #: src/screens/Search/components/StarterPackCard.tsx:106 ··· 1880 1895 msgid "Choose Feeds" 1881 1896 msgstr "" 1882 1897 1883 - #: src/components/StarterPack/ProfileStarterPacks.tsx:314 1898 + #: src/components/StarterPack/ProfileStarterPacks.tsx:346 1884 1899 msgid "Choose for me" 1885 1900 msgstr "" 1886 1901 ··· 2447 2462 msgid "Could not leave chat" 2448 2463 msgstr "" 2449 2464 2450 - #: src/screens/Profile/ProfileFeed/index.tsx:83 2465 + #: src/screens/Profile/ProfileFeed/index.tsx:84 2451 2466 msgid "Could not load feed" 2452 2467 msgstr "" 2453 2468 ··· 2477 2492 #. Text on button to create a new starter pack 2478 2493 #: src/components/dialogs/StarterPackDialog.tsx:112 2479 2494 #: src/components/dialogs/StarterPackDialog.tsx:201 2480 - #: src/components/StarterPack/ProfileStarterPacks.tsx:296 2495 + #: src/components/StarterPack/ProfileStarterPacks.tsx:328 2481 2496 msgid "Create" 2497 + msgstr "" 2498 + 2499 + #: src/view/com/lists/ProfileLists.tsx:159 2500 + #: src/view/com/lists/ProfileLists.tsx:160 2501 + msgid "Create a list" 2482 2502 msgstr "" 2483 2503 2484 2504 #: src/components/StarterPack/QrCodeDialog.tsx:163 2485 2505 msgid "Create a QR code for a starter pack" 2486 2506 msgstr "" 2487 2507 2488 - #: src/components/StarterPack/ProfileStarterPacks.tsx:174 2489 - #: src/components/StarterPack/ProfileStarterPacks.tsx:283 2508 + #: src/components/StarterPack/ProfileStarterPacks.tsx:206 2509 + #: src/components/StarterPack/ProfileStarterPacks.tsx:315 2490 2510 #: src/Navigation.tsx:589 2491 2511 msgid "Create a starter pack" 2492 2512 msgstr "" 2493 2513 2494 - #: src/components/StarterPack/ProfileStarterPacks.tsx:270 2514 + #: src/view/screens/Profile.tsx:542 2515 + #: src/view/screens/Profile.tsx:543 2516 + msgid "Create a Starter Pack" 2517 + msgstr "" 2518 + 2519 + #: src/components/StarterPack/ProfileStarterPacks.tsx:302 2495 2520 msgid "Create a starter pack for me" 2496 2521 msgstr "" 2497 2522 ··· 2528 2553 msgid "Create an avatar instead" 2529 2554 msgstr "" 2530 2555 2531 - #: src/components/StarterPack/ProfileStarterPacks.tsx:181 2556 + #: src/components/StarterPack/ProfileStarterPacks.tsx:213 2532 2557 msgid "Create another" 2533 2558 msgstr "" 2534 2559 ··· 2556 2581 msgid "Create user list" 2557 2582 msgstr "" 2558 2583 2559 - #: src/screens/Settings/AppPasswords.tsx:172 2584 + #: src/screens/Settings/AppPasswords.tsx:173 2560 2585 msgid "Created {0}" 2561 2586 msgstr "" 2562 2587 ··· 2602 2627 msgid "Dark" 2603 2628 msgstr "" 2604 2629 2605 - #: src/view/screens/Debug.tsx:68 2630 + #: src/view/screens/Debug.tsx:69 2606 2631 msgid "Dark mode" 2607 2632 msgstr "" 2608 2633 ··· 2624 2649 msgid "Debug Moderation" 2625 2650 msgstr "" 2626 2651 2627 - #: src/view/screens/Debug.tsx:88 2652 + #: src/view/screens/Debug.tsx:89 2628 2653 msgid "Debug panel" 2629 2654 msgstr "" 2630 2655 ··· 2644 2669 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:736 2645 2670 #: src/screens/Messages/components/ChatStatusInfo.tsx:55 2646 2671 #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:280 2647 - #: src/screens/Settings/AppPasswords.tsx:211 2672 + #: src/screens/Settings/AppPasswords.tsx:212 2648 2673 #: src/screens/StarterPack/StarterPackScreen.tsx:601 2649 2674 #: src/screens/StarterPack/StarterPackScreen.tsx:690 2650 2675 #: src/screens/StarterPack/StarterPackScreen.tsx:762 ··· 2660 2685 msgid "Delete Account <0>\"</0><1>{0}</1><2>\"</2>" 2661 2686 msgstr "" 2662 2687 2663 - #: src/screens/Settings/AppPasswords.tsx:185 2688 + #: src/screens/Settings/AppPasswords.tsx:186 2664 2689 msgid "Delete app password" 2665 2690 msgstr "" 2666 2691 2667 - #: src/screens/Settings/AppPasswords.tsx:206 2692 + #: src/screens/Settings/AppPasswords.tsx:207 2668 2693 msgid "Delete app password?" 2669 2694 msgstr "" 2670 2695 ··· 3260 3285 msgid "Enabled" 3261 3286 msgstr "" 3262 3287 3263 - #: src/screens/Profile/Sections/Feed.tsx:109 3288 + #: src/screens/Profile/Sections/Feed.tsx:131 3264 3289 msgid "End of feed" 3265 3290 msgstr "" 3266 3291 ··· 3746 3771 #: src/screens/Search/SearchResults.tsx:77 3747 3772 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3748 3773 #: src/view/screens/Feeds.tsx:511 3749 - #: src/view/screens/Profile.tsx:230 3774 + #: src/view/screens/Profile.tsx:239 3750 3775 #: src/view/shell/desktop/LeftNav.tsx:728 3751 3776 #: src/view/shell/Drawer.tsx:530 3752 3777 msgid "Feeds" ··· 4056 4081 msgid "From <0/>" 4057 4082 msgstr "" 4058 4083 4059 - #: src/components/StarterPack/ProfileStarterPacks.tsx:303 4084 + #: src/components/StarterPack/ProfileStarterPacks.tsx:335 4060 4085 msgid "Generate a starter pack" 4061 4086 msgstr "" 4062 4087 ··· 4147 4172 #: src/components/moderation/ScreenHider.tsx:160 4148 4173 #: src/components/moderation/ScreenHider.tsx:169 4149 4174 #: src/screens/Messages/Inbox.tsx:251 4150 - #: src/screens/Profile/ProfileFeed/index.tsx:92 4175 + #: src/screens/Profile/ProfileFeed/index.tsx:93 4151 4176 #: src/screens/ProfileList/components/ErrorScreen.tsx:34 4152 4177 #: src/screens/ProfileList/components/ErrorScreen.tsx:40 4153 4178 #: src/screens/VideoFeed/components/Header.tsx:163 4154 4179 #: src/screens/VideoFeed/index.tsx:1162 4155 4180 #: src/screens/VideoFeed/index.tsx:1166 4156 4181 #: src/view/com/auth/LoggedOut.tsx:72 4182 + #: src/view/com/profile/ProfileFollowers.tsx:144 4183 + #: src/view/com/profile/ProfileFollowers.tsx:145 4157 4184 #: src/view/screens/NotFound.tsx:57 4158 4185 msgid "Go back" 4159 4186 msgstr "" ··· 4162 4189 #: src/screens/List/ListHiddenScreen.tsx:224 4163 4190 #: src/screens/Profile/ErrorState.tsx:62 4164 4191 #: src/screens/Profile/ErrorState.tsx:66 4165 - #: src/screens/Profile/ProfileFeed/index.tsx:97 4192 + #: src/screens/Profile/ProfileFeed/index.tsx:98 4166 4193 #: src/screens/StarterPack/StarterPackScreen.tsx:775 4167 4194 #: src/view/screens/NotFound.tsx:56 4168 4195 msgid "Go Back" ··· 4180 4207 msgid "Go home" 4181 4208 msgstr "" 4182 4209 4210 + #: src/screens/Bookmarks/index.tsx:278 4183 4211 #: src/view/screens/NotFound.tsx:57 4184 4212 msgid "Go home" 4185 4213 msgstr "" ··· 4433 4461 msgid "Hides the content" 4434 4462 msgstr "" 4435 4463 4436 - #: src/view/com/posts/PostFeedErrorMessage.tsx:121 4464 + #: src/view/com/posts/PostFeedErrorMessage.tsx:124 4437 4465 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." 4438 4466 msgstr "" 4439 4467 4440 - #: src/view/com/posts/PostFeedErrorMessage.tsx:109 4468 + #: src/view/com/posts/PostFeedErrorMessage.tsx:112 4441 4469 msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." 4442 4470 msgstr "" 4443 4471 4444 - #: src/view/com/posts/PostFeedErrorMessage.tsx:115 4472 + #: src/view/com/posts/PostFeedErrorMessage.tsx:118 4445 4473 msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." 4446 4474 msgstr "" 4447 4475 4448 - #: src/view/com/posts/PostFeedErrorMessage.tsx:112 4476 + #: src/view/com/posts/PostFeedErrorMessage.tsx:115 4449 4477 msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." 4450 4478 msgstr "" 4451 4479 4452 - #: src/view/com/posts/PostFeedErrorMessage.tsx:106 4480 + #: src/view/com/posts/PostFeedErrorMessage.tsx:109 4453 4481 msgid "Hmm, we're having trouble finding this feed. It may have been deleted." 4454 4482 msgstr "" 4455 4483 ··· 4763 4791 msgstr "" 4764 4792 4765 4793 #: src/view/com/composer/labels/LabelsBtn.tsx:69 4766 - #: src/view/screens/Profile.tsx:223 4794 + #: src/view/screens/Profile.tsx:232 4767 4795 msgid "Labels" 4768 4796 msgstr "" 4769 4797 ··· 4905 4933 msgid "left to go." 4906 4934 msgstr "" 4907 4935 4908 - #: src/components/StarterPack/ProfileStarterPacks.tsx:319 4936 + #: src/components/StarterPack/ProfileStarterPacks.tsx:351 4909 4937 msgid "Let me choose" 4910 4938 msgstr "" 4911 4939 ··· 4986 5014 #: src/lib/hooks/useNotificationHandler.ts:126 4987 5015 #: src/screens/Settings/NotificationSettings/index.tsx:126 4988 5016 #: src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx:41 4989 - #: src/view/screens/Profile.tsx:229 5017 + #: src/view/screens/Profile.tsx:238 4990 5018 msgid "Likes" 4991 5019 msgstr "" 4992 5020 ··· 5091 5119 5092 5120 #: src/Navigation.tsx:172 5093 5121 #: src/view/screens/Lists.tsx:67 5094 - #: src/view/screens/Profile.tsx:224 5095 - #: src/view/screens/Profile.tsx:232 5122 + #: src/view/screens/Profile.tsx:233 5123 + #: src/view/screens/Profile.tsx:241 5096 5124 #: src/view/shell/desktop/LeftNav.tsx:746 5097 5125 #: src/view/shell/Drawer.tsx:545 5098 5126 msgid "Lists" 5127 + msgstr "" 5128 + 5129 + #: src/view/com/lists/MyLists.tsx:72 5130 + #: src/view/com/lists/ProfileLists.tsx:155 5131 + msgid "Lists allow you to see content from your favorite people." 5099 5132 msgstr "" 5100 5133 5101 5134 #: src/components/dms/BlockedByListDialog.tsx:39 ··· 5131 5164 msgid "Load new notifications" 5132 5165 msgstr "" 5133 5166 5134 - #: src/screens/Profile/ProfileFeed/index.tsx:224 5135 - #: src/screens/Profile/Sections/Feed.tsx:94 5136 - #: src/screens/ProfileList/FeedSection.tsx:105 5167 + #: src/screens/Profile/ProfileFeed/index.tsx:231 5168 + #: src/screens/Profile/Sections/Feed.tsx:116 5169 + #: src/screens/ProfileList/FeedSection.tsx:112 5137 5170 #: src/view/com/feeds/FeedPage.tsx:169 5138 5171 msgid "Load new posts" 5139 5172 msgstr "" ··· 5195 5228 msgid "Make adjustments to email settings for your account" 5196 5229 msgstr "" 5197 5230 5198 - #: src/components/StarterPack/ProfileStarterPacks.tsx:278 5231 + #: src/components/StarterPack/ProfileStarterPacks.tsx:310 5199 5232 msgid "Make one for me" 5200 5233 msgstr "" 5201 5234 ··· 5237 5270 msgid "Maybe later" 5238 5271 msgstr "" 5239 5272 5240 - #: src/view/screens/Profile.tsx:227 5273 + #: src/view/screens/Profile.tsx:236 5241 5274 msgid "Media" 5242 5275 msgstr "" 5243 5276 ··· 5281 5314 msgid "Message from @{0}: {1}" 5282 5315 msgstr "" 5283 5316 5284 - #: src/view/com/posts/PostFeedErrorMessage.tsx:205 5317 + #: src/view/com/posts/PostFeedErrorMessage.tsx:208 5285 5318 msgid "Message from server: {0}" 5286 5319 msgstr "" 5287 5320 ··· 5625 5658 msgid "New password" 5626 5659 msgstr "" 5627 5660 5628 - #: src/screens/Profile/ProfileFeed/index.tsx:241 5661 + #: src/screens/Profile/ProfileFeed/index.tsx:248 5629 5662 #: src/screens/ProfileList/index.tsx:246 5630 5663 #: src/screens/ProfileList/index.tsx:284 5631 5664 #: src/view/screens/Feeds.tsx:552 5632 5665 #: src/view/screens/Notifications.tsx:167 5633 - #: src/view/screens/Profile.tsx:510 5666 + #: src/view/screens/Profile.tsx:571 5634 5667 msgid "New post" 5635 5668 msgstr "" 5636 5669 ··· 5699 5732 msgid "No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention." 5700 5733 msgstr "" 5701 5734 5702 - #: src/screens/Settings/AppPasswords.tsx:106 5735 + #: src/screens/Settings/AppPasswords.tsx:107 5703 5736 msgid "No app passwords yet" 5704 5737 msgstr "" 5705 5738 ··· 5720 5753 msgid "No feeds found. Try searching for something else." 5721 5754 msgstr "" 5722 5755 5756 + #: src/view/com/profile/ProfileFollowers.tsx:135 5757 + msgid "No followers yet" 5758 + msgstr "" 5759 + 5723 5760 #: src/components/live/LinkPreview.tsx:63 5724 5761 msgid "No image" 5725 5762 msgstr "" 5726 5763 5727 5764 #: src/components/LikedByList.tsx:84 5728 5765 #: src/view/com/post-thread/PostLikedBy.tsx:84 5766 + #: src/view/screens/Profile.tsx:511 5729 5767 msgid "No likes yet" 5730 5768 msgstr "" 5731 5769 ··· 5735 5773 msgid "No longer following {0}" 5736 5774 msgstr "" 5737 5775 5776 + #: src/view/screens/Profile.tsx:467 5777 + msgid "No media yet" 5778 + msgstr "" 5779 + 5738 5780 #: src/screens/Messages/components/ChatListItem.tsx:142 5739 5781 msgid "No messages yet" 5740 5782 msgstr "" ··· 5743 5785 msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you." 5744 5786 msgstr "" 5745 5787 5746 - #: src/view/com/notifications/NotificationFeed.tsx:122 5788 + #: src/view/com/notifications/NotificationFeed.tsx:123 5747 5789 msgid "No notifications yet!" 5748 5790 msgstr "" 5749 5791 ··· 5759 5801 msgid "No one but the author can quote this post." 5760 5802 msgstr "" 5761 5803 5762 - #: src/screens/Notifications/ActivityList.tsx:38 5804 + #: src/screens/Notifications/ActivityList.tsx:42 5763 5805 msgid "No posts here" 5764 5806 msgstr "" 5765 5807 5766 - #: src/screens/Profile/Sections/Feed.tsx:62 5767 - msgid "No posts yet." 5808 + #: src/screens/Profile/Sections/Feed.tsx:80 5809 + #: src/view/screens/Profile.tsx:431 5810 + msgid "No posts yet" 5768 5811 msgstr "" 5769 5812 5770 5813 #: src/view/com/post-thread/PostQuotes.tsx:105 5771 5814 msgid "No quotes yet" 5772 5815 msgstr "" 5773 5816 5817 + #: src/view/screens/Profile.tsx:452 5818 + msgid "No replies yet" 5819 + msgstr "" 5820 + 5774 5821 #: src/view/com/post-thread/PostRepostedBy.tsx:90 5775 5822 msgid "No reposts yet" 5776 5823 msgstr "" ··· 5789 5836 msgid "No results for \"{0}\"." 5790 5837 msgstr "" 5791 5838 5792 - #: src/components/Lists.tsx:189 5839 + #: src/components/Lists.tsx:201 5840 + #: src/components/Lists.tsx:216 5793 5841 msgid "No results found" 5794 5842 msgstr "" 5795 5843 ··· 5814 5862 msgid "No thanks" 5815 5863 msgstr "" 5816 5864 5865 + #: src/view/screens/Profile.tsx:489 5866 + msgid "No video posts yet" 5867 + msgstr "" 5868 + 5817 5869 #: src/components/dialogs/PostInteractionSettingsDialog.tsx:465 5818 5870 msgid "Nobody" 5819 5871 msgstr "" ··· 5852 5904 msgstr "" 5853 5905 5854 5906 #: src/Navigation.tsx:167 5855 - #: src/view/screens/Profile.tsx:125 5907 + #: src/view/screens/Profile.tsx:132 5856 5908 msgid "Not Found" 5857 5909 msgstr "" 5858 5910 ··· 5877 5929 msgstr "" 5878 5930 5879 5931 #: src/screens/Bookmarks/components/EmptyState.tsx:35 5932 + #: src/screens/Bookmarks/index.tsx:274 5880 5933 msgid "Nothing saved yet" 5881 5934 msgstr "" 5882 5935 ··· 5896 5949 5897 5950 #: src/Navigation.tsx:564 5898 5951 #: src/Navigation.tsx:764 5899 - #: src/screens/Notifications/ActivityList.tsx:29 5952 + #: src/screens/Notifications/ActivityList.tsx:30 5900 5953 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:90 5901 5954 #: src/screens/Settings/NotificationSettings/index.tsx:92 5902 5955 #: src/screens/Settings/NotificationSettings/LikeNotificationSettings.tsx:30 ··· 6015 6068 msgid "Only WebVTT (.vtt) files are supported" 6016 6069 msgstr "" 6017 6070 6018 - #: src/components/Lists.tsx:94 6071 + #: src/components/Lists.tsx:98 6019 6072 msgid "Oops, something went wrong!" 6020 6073 msgstr "" 6021 6074 6022 - #: src/components/Lists.tsx:173 6023 - #: src/components/StarterPack/ProfileStarterPacks.tsx:328 6024 - #: src/components/StarterPack/ProfileStarterPacks.tsx:337 6025 - #: src/screens/Settings/AppPasswords.tsx:57 6075 + #: src/components/Lists.tsx:183 6076 + #: src/components/StarterPack/ProfileStarterPacks.tsx:360 6077 + #: src/components/StarterPack/ProfileStarterPacks.tsx:369 6078 + #: src/screens/Settings/AppPasswords.tsx:58 6026 6079 #: src/screens/Settings/components/ChangeHandleDialog.tsx:106 6027 - #: src/view/screens/Profile.tsx:125 6080 + #: src/view/screens/Profile.tsx:132 6028 6081 msgid "Oops!" 6029 6082 msgstr "" 6030 6083 ··· 6268 6321 msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky." 6269 6322 msgstr "" 6270 6323 6271 - #: src/components/Lists.tsx:190 6324 + #: src/components/Lists.tsx:202 6325 + #: src/components/Lists.tsx:217 6272 6326 #: src/view/screens/NotFound.tsx:47 6273 6327 msgid "Page not found" 6274 6328 msgstr "" ··· 6596 6650 msgid "Post" 6597 6651 msgstr "" 6598 6652 6653 + #: src/view/screens/Profile.tsx:469 6654 + #: src/view/screens/Profile.tsx:470 6655 + msgid "Post a photo" 6656 + msgstr "" 6657 + 6658 + #: src/view/screens/Profile.tsx:491 6659 + #: src/view/screens/Profile.tsx:492 6660 + msgid "Post a video" 6661 + msgstr "" 6662 + 6599 6663 #: src/view/com/composer/Composer.tsx:1117 6600 6664 msgctxt "action" 6601 6665 msgid "Post All" ··· 6676 6740 #: src/screens/ProfileList/index.tsx:166 6677 6741 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:216 6678 6742 #: src/screens/StarterPack/StarterPackScreen.tsx:191 6679 - #: src/view/screens/Profile.tsx:225 6743 + #: src/view/screens/Profile.tsx:234 6680 6744 msgid "Posts" 6681 6745 msgstr "" 6682 6746 ··· 6684 6748 msgid "Posts can be muted based on their text, their tags, or both. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." 6685 6749 msgstr "" 6686 6750 6687 - #: src/view/com/posts/PostFeedErrorMessage.tsx:72 6751 + #: src/view/com/posts/PostFeedErrorMessage.tsx:75 6688 6752 msgid "Posts hidden" 6689 6753 msgstr "" 6690 6754 ··· 6705 6769 msgstr "" 6706 6770 6707 6771 #: src/components/Error.tsx:60 6708 - #: src/components/Lists.tsx:99 6772 + #: src/components/Lists.tsx:103 6709 6773 #: src/screens/Messages/components/MessageListError.tsx:24 6710 6774 #: src/screens/Signup/BackNextButtons.tsx:47 6711 6775 msgid "Press to retry" ··· 6770 6834 msgstr "" 6771 6835 6772 6836 #: src/view/screens/DebugMod.tsx:936 6773 - #: src/view/screens/Profile.tsx:364 6837 + #: src/view/screens/Profile.tsx:384 6774 6838 msgid "profile" 6775 6839 msgstr "" 6776 6840 ··· 6802 6866 msgid "Public, sharable lists of users to mute or block in bulk." 6803 6867 msgstr "" 6804 6868 6805 - #: src/view/com/lists/MyLists.tsx:72 6806 - msgid "Public, sharable lists which can be used to drive feeds." 6807 - msgstr "" 6808 - 6809 6869 #. Accessibility label for button to publish a single post 6810 6870 #: src/view/com/composer/Composer.tsx:1099 6811 6871 msgid "Publish post" ··· 7011 7071 #: src/components/FeedCard.tsx:343 7012 7072 #: src/components/StarterPack/Wizard/WizardListCard.tsx:105 7013 7073 #: src/components/StarterPack/Wizard/WizardListCard.tsx:112 7014 - #: src/screens/Bookmarks/index.tsx:255 7074 + #: src/screens/Bookmarks/index.tsx:259 7015 7075 #: src/screens/Settings/Settings.tsx:664 7016 7076 #: src/view/com/modals/UserAddRemoveLists.tsx:235 7017 - #: src/view/com/posts/PostFeedErrorMessage.tsx:217 7077 + #: src/view/com/posts/PostFeedErrorMessage.tsx:220 7018 7078 msgid "Remove" 7019 7079 msgstr "" 7020 7080 ··· 7051 7111 7052 7112 #: src/view/com/posts/FeedShutdownMsg.tsx:116 7053 7113 #: src/view/com/posts/FeedShutdownMsg.tsx:120 7054 - #: src/view/com/posts/PostFeedErrorMessage.tsx:173 7114 + #: src/view/com/posts/PostFeedErrorMessage.tsx:176 7055 7115 msgid "Remove feed" 7056 7116 msgstr "" 7057 7117 7058 - #: src/view/com/posts/PostFeedErrorMessage.tsx:214 7118 + #: src/view/com/posts/PostFeedErrorMessage.tsx:217 7059 7119 msgid "Remove feed?" 7060 7120 msgstr "" 7061 7121 ··· 7077 7137 msgstr "" 7078 7138 7079 7139 #: src/components/PostControls/BookmarkButton.tsx:128 7080 - #: src/screens/Bookmarks/index.tsx:249 7140 + #: src/screens/Bookmarks/index.tsx:253 7081 7141 msgid "Remove from saved posts" 7082 7142 msgstr "" 7083 7143 ··· 7111 7171 msgid "Remove subtitle file" 7112 7172 msgstr "" 7113 7173 7114 - #: src/view/com/posts/PostFeedErrorMessage.tsx:215 7174 + #: src/view/com/posts/PostFeedErrorMessage.tsx:218 7115 7175 msgid "Remove this feed from your saved feeds" 7116 7176 msgstr "" 7117 7177 ··· 7148 7208 msgstr "" 7149 7209 7150 7210 #: src/components/PostControls/BookmarkButton.tsx:94 7151 - #: src/screens/Bookmarks/index.tsx:207 7211 + #: src/screens/Bookmarks/index.tsx:211 7152 7212 msgid "Removed from saved posts" 7153 7213 msgstr "" 7154 7214 ··· 7197 7257 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:218 7198 7258 #: src/screens/Settings/NotificationSettings/index.tsx:148 7199 7259 #: src/screens/Settings/NotificationSettings/ReplyNotificationSettings.tsx:41 7200 - #: src/view/screens/Profile.tsx:226 7260 + #: src/view/screens/Profile.tsx:235 7201 7261 msgid "Replies" 7202 7262 msgstr "" 7203 7263 ··· 7460 7520 7461 7521 #: src/components/dms/MessageItem.tsx:322 7462 7522 #: src/components/Error.tsx:65 7463 - #: src/components/Lists.tsx:110 7523 + #: src/components/Lists.tsx:114 7464 7524 #: src/components/moderation/ReportDialog/index.tsx:274 7465 7525 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:55 7466 7526 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:58 7467 - #: src/components/StarterPack/ProfileStarterPacks.tsx:342 7527 + #: src/components/StarterPack/ProfileStarterPacks.tsx:374 7468 7528 #: src/screens/Login/LoginForm.tsx:326 7469 7529 #: src/screens/Login/LoginForm.tsx:333 7470 7530 #: src/screens/Messages/ChatList.tsx:292 ··· 7498 7558 msgid "Returns to home page" 7499 7559 msgstr "" 7500 7560 7501 - #: src/screens/Profile/ProfileFeed/index.tsx:93 7561 + #: src/screens/Profile/ProfileFeed/index.tsx:94 7502 7562 #: src/screens/ProfileList/components/ErrorScreen.tsx:35 7503 7563 #: src/screens/Settings/components/ChangeHandleDialog.tsx:575 7504 7564 #: src/screens/VideoFeed/index.tsx:1163 ··· 7582 7642 7583 7643 #: src/components/dialogs/nuxs/BookmarksAnnouncement.tsx:143 7584 7644 #: src/Navigation.tsx:608 7585 - #: src/screens/Bookmarks/index.tsx:55 7645 + #: src/screens/Bookmarks/index.tsx:59 7586 7646 msgid "Saved Posts" 7587 7647 msgstr "" 7588 7648 ··· 7615 7675 msgid "Scroll right" 7616 7676 msgstr "" 7617 7677 7618 - #: src/screens/ProfileList/AboutSection.tsx:130 7678 + #: src/screens/ProfileList/AboutSection.tsx:131 7619 7679 msgid "Scroll to top" 7620 7680 msgstr "" 7621 7681 ··· 7747 7807 7748 7808 #: src/components/FeedInterstitials.tsx:392 7749 7809 msgid "See more suggested profiles on the Explore page" 7810 + msgstr "" 7811 + 7812 + #: src/view/com/profile/ProfileFollows.tsx:155 7813 + #: src/view/com/profile/ProfileFollows.tsx:156 7814 + msgid "See suggested accounts" 7750 7815 msgstr "" 7751 7816 7752 7817 #: src/screens/SavedFeeds.tsx:220 ··· 8416 8481 msgid "Something went wrong, please try again." 8417 8482 msgstr "" 8418 8483 8419 - #: src/components/Lists.tsx:174 8484 + #: src/components/Lists.tsx:184 8420 8485 msgid "Something went wrong!" 8421 8486 msgstr "" 8422 8487 ··· 8480 8545 msgid "Start a new chat" 8481 8546 msgstr "" 8482 8547 8483 - #: src/screens/ProfileList/AboutSection.tsx:102 8484 - #: src/screens/ProfileList/FeedSection.tsx:74 8548 + #: src/screens/ProfileList/AboutSection.tsx:103 8549 + #: src/screens/ProfileList/FeedSection.tsx:81 8485 8550 msgid "Start adding people" 8486 8551 msgstr "" 8487 8552 8488 - #: src/screens/ProfileList/AboutSection.tsx:108 8489 - #: src/screens/ProfileList/FeedSection.tsx:80 8553 + #: src/screens/ProfileList/AboutSection.tsx:109 8554 + #: src/screens/ProfileList/FeedSection.tsx:87 8490 8555 msgid "Start adding people!" 8491 8556 msgstr "" 8492 8557 ··· 8518 8583 msgstr "" 8519 8584 8520 8585 #: src/screens/Search/Explore.tsx:625 8521 - #: src/view/screens/Profile.tsx:231 8586 + #: src/view/screens/Profile.tsx:240 8522 8587 msgid "Starter Packs" 8523 8588 msgstr "" 8524 8589 8525 - #: src/components/StarterPack/ProfileStarterPacks.tsx:262 8590 + #: src/components/StarterPack/ProfileStarterPacks.tsx:294 8526 8591 msgid "Starter packs let you easily share your favorite feeds and people with your friends." 8592 + msgstr "" 8593 + 8594 + #: src/view/screens/Profile.tsx:539 8595 + msgid "Starter packs let you share your favorite feeds and people with your friends." 8527 8596 msgstr "" 8528 8597 8529 8598 #: src/screens/Settings/AboutSettings.tsx:100 ··· 8928 8997 msgid "There was an issue contacting the server, please check your internet connection and try again." 8929 8998 msgstr "" 8930 8999 8931 - #: src/view/com/notifications/NotificationFeed.tsx:130 9000 + #: src/view/com/notifications/NotificationFeed.tsx:131 8932 9001 msgid "There was an issue fetching notifications. Tap here to try again." 8933 9002 msgstr "" 8934 9003 ··· 8941 9010 msgid "There was an issue fetching the list. Tap here to try again." 8942 9011 msgstr "" 8943 9012 8944 - #: src/screens/Settings/AppPasswords.tsx:58 9013 + #: src/screens/Settings/AppPasswords.tsx:59 8945 9014 msgid "There was an issue fetching your app passwords" 8946 9015 msgstr "" 8947 9016 8948 - #: src/view/com/feeds/ProfileFeedgens.tsx:163 8949 - #: src/view/com/lists/ProfileLists.tsx:161 9017 + #: src/view/com/feeds/ProfileFeedgens.tsx:174 9018 + #: src/view/com/lists/ProfileLists.tsx:175 8950 9019 msgid "There was an issue fetching your lists. Tap here to try again." 8951 9020 msgstr "" 8952 9021 ··· 8954 9023 msgid "There was an issue fetching your service info" 8955 9024 msgstr "" 8956 9025 8957 - #: src/view/com/posts/PostFeedErrorMessage.tsx:149 9026 + #: src/view/com/posts/PostFeedErrorMessage.tsx:152 8958 9027 msgid "There was an issue removing this feed. Please check your internet connection and try again." 8959 9028 msgstr "" 8960 9029 ··· 9070 9139 msgid "This content is not available because one of the users involved has blocked the other." 9071 9140 msgstr "" 9072 9141 9073 - #: src/view/com/posts/PostFeedErrorMessage.tsx:118 9142 + #: src/view/com/posts/PostFeedErrorMessage.tsx:121 9074 9143 msgid "This content is not viewable without a Bluesky account." 9075 9144 msgstr "" 9076 9145 ··· 9098 9167 msgid "This feature is not available while using an App Password. Please sign in with your main password." 9099 9168 msgstr "" 9100 9169 9101 - #: src/view/com/posts/PostFeedErrorMessage.tsx:124 9170 + #: src/view/com/posts/PostFeedErrorMessage.tsx:127 9102 9171 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." 9103 9172 msgstr "" 9104 9173 ··· 9106 9175 msgid "This feed is empty! You may need to follow more users or tune your language settings." 9107 9176 msgstr "" 9108 9177 9109 - #: src/components/StarterPack/Main/PostsList.tsx:36 9110 - #: src/screens/Profile/ProfileFeed/index.tsx:192 9111 - #: src/screens/ProfileList/FeedSection.tsx:71 9178 + #: src/components/StarterPack/Main/PostsList.tsx:41 9179 + #: src/screens/Profile/ProfileFeed/index.tsx:197 9180 + #: src/screens/ProfileList/FeedSection.tsx:77 9112 9181 msgid "This feed is empty." 9113 9182 msgstr "" 9114 9183 ··· 9124 9193 msgid "This information is private and not shared with other users." 9125 9194 msgstr "" 9126 9195 9196 + #: src/view/screens/Debug.tsx:343 9197 + msgid "This is an empty state" 9198 + msgstr "" 9199 + 9127 9200 #: src/components/live/EditLiveDialog.tsx:189 9128 9201 #: src/components/live/GoLiveDialog.tsx:157 9129 9202 msgid "This is not a valid link" ··· 9153 9226 msgid "This list – created by you – contains possible violations of Bluesky's community guidelines in its name or description." 9154 9227 msgstr "" 9155 9228 9156 - #: src/screens/ProfileList/AboutSection.tsx:98 9229 + #: src/screens/ProfileList/AboutSection.tsx:99 9157 9230 msgid "This list is empty." 9158 9231 msgstr "" 9159 9232 ··· 9173 9246 msgid "This post is only visible to logged-in users." 9174 9247 msgstr "" 9175 9248 9176 - #: src/screens/Bookmarks/index.tsx:245 9249 + #: src/screens/Bookmarks/index.tsx:249 9177 9250 msgid "This post was deleted by its author" 9178 9251 msgstr "" 9179 9252 ··· 9209 9282 msgid "This user does not have a display name, and therefore cannot be verified." 9210 9283 msgstr "" 9211 9284 9212 - #: src/view/com/profile/ProfileFollowers.tsx:133 9285 + #: src/view/com/profile/ProfileFollowers.tsx:136 9213 9286 msgid "This user doesn't have any followers." 9214 9287 msgstr "" 9215 9288 ··· 9238 9311 msgid "This user is new here. Press for more info about when they joined." 9239 9312 msgstr "" 9240 9313 9241 - #: src/view/com/profile/ProfileFollows.tsx:133 9314 + #: src/view/com/profile/ProfileFollows.tsx:147 9242 9315 msgid "This user isn't following anyone." 9243 9316 msgstr "" 9244 9317 ··· 9706 9779 msgid "Uploading video..." 9707 9780 msgstr "" 9708 9781 9709 - #: src/screens/Settings/AppPasswords.tsx:65 9782 + #: src/screens/Settings/AppPasswords.tsx:66 9710 9783 msgid "Use app passwords to sign in to other Bluesky clients without giving full access to your account or password." 9711 9784 msgstr "" 9712 9785 ··· 9960 10033 msgid "Video: {0}" 9961 10034 msgstr "" 9962 10035 9963 - #: src/view/screens/Profile.tsx:228 10036 + #: src/view/screens/Profile.tsx:237 9964 10037 msgid "Videos" 9965 10038 msgstr "" 9966 10039 ··· 10032 10105 #: src/components/ProfileHoverCard/index.web.tsx:466 10033 10106 #: src/components/ProfileHoverCard/index.web.tsx:486 10034 10107 #: src/components/ProfileHoverCard/index.web.tsx:513 10035 - #: src/view/com/posts/PostFeedErrorMessage.tsx:179 10108 + #: src/view/com/posts/PostFeedErrorMessage.tsx:182 10036 10109 #: src/view/com/util/PostMeta.tsx:90 10037 10110 #: src/view/com/util/PostMeta.tsx:127 10038 10111 msgid "View profile" ··· 10262 10335 msgid "We're sorry! The post you are replying to has been deleted." 10263 10336 msgstr "" 10264 10337 10265 - #: src/components/Lists.tsx:194 10338 + #: src/components/Lists.tsx:221 10266 10339 #: src/view/screens/NotFound.tsx:50 10267 10340 msgid "We're sorry! We can't find the page you were looking for." 10268 10341 msgstr "" ··· 10386 10459 msgid "Write a message" 10387 10460 msgstr "" 10388 10461 10462 + #: src/view/screens/Profile.tsx:433 10463 + #: src/view/screens/Profile.tsx:434 10464 + msgid "Write a post" 10465 + msgstr "" 10466 + 10389 10467 #: src/view/com/composer/Composer.tsx:955 10390 10468 msgid "Write post" 10391 10469 msgstr "" ··· 10471 10549 msgid "You are not allowed to upload videos." 10472 10550 msgstr "" 10473 10551 10474 - #: src/view/com/profile/ProfileFollows.tsx:132 10475 - msgid "You are not following anyone." 10552 + #: src/view/com/profile/ProfileFollows.tsx:146 10553 + msgid "You are not following anyone yet" 10476 10554 msgstr "" 10477 10555 10478 10556 #: src/components/live/queries.ts:156 ··· 10549 10627 msgid "You can update this later from your settings." 10550 10628 msgstr "" 10551 10629 10552 - #: src/view/com/profile/ProfileFollowers.tsx:132 10553 - msgid "You do not have any followers." 10554 - msgstr "" 10555 - 10556 10630 #: src/screens/Profile/KnownFollowers.tsx:112 10557 10631 msgid "You don't follow any users who follow @{name}." 10558 10632 msgstr "" ··· 10614 10688 msgid "You have no conversations yet. Start one!" 10615 10689 msgstr "" 10616 10690 10617 - #: src/view/com/feeds/ProfileFeedgens.tsx:151 10618 - msgid "You have no feeds." 10619 - msgstr "" 10620 - 10621 10691 #: src/view/com/lists/MyLists.tsx:81 10622 - #: src/view/com/lists/ProfileLists.tsx:149 10623 10692 msgid "You have no lists." 10624 10693 msgstr "" 10625 10694 ··· 10635 10704 msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account." 10636 10705 msgstr "" 10637 10706 10638 - #: src/components/Lists.tsx:57 10707 + #: src/components/Lists.tsx:61 10639 10708 msgid "You have reached the end" 10640 10709 msgstr "" 10641 10710 ··· 10647 10716 msgid "You have temporarily reached the limit for video uploads. Please try again later." 10648 10717 msgstr "" 10649 10718 10650 - #: src/components/StarterPack/ProfileStarterPacks.tsx:259 10719 + #: src/components/StarterPack/ProfileStarterPacks.tsx:291 10651 10720 msgid "You haven't created a starter pack yet!" 10721 + msgstr "" 10722 + 10723 + #: src/view/com/feeds/ProfileFeedgens.tsx:155 10724 + msgid "You haven't made any custom feeds yet." 10652 10725 msgstr "" 10653 10726 10654 10727 #: src/components/dialogs/MutedWords.tsx:403 ··· 10696 10769 msgid "You must be at least 13 years old to use Bluesky. Read our <0>Terms of Service</0> for more information." 10697 10770 msgstr "" 10698 10771 10699 - #: src/components/StarterPack/ProfileStarterPacks.tsx:330 10772 + #: src/components/StarterPack/ProfileStarterPacks.tsx:362 10700 10773 msgid "You must be following at least seven other people to generate a starter pack." 10701 10774 msgstr "" 10702 10775
+30 -4
src/screens/Bookmarks/index.tsx
··· 7 7 } from '@atproto/api' 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 - import {useFocusEffect} from '@react-navigation/native' 10 + import { 11 + type NavigationProp, 12 + useFocusEffect, 13 + useNavigation, 14 + } from '@react-navigation/native' 11 15 12 16 import {useCleanError} from '#/lib/hooks/useCleanError' 13 17 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' ··· 21 25 import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery' 22 26 import {useSetMinimalShellMode} from '#/state/shell' 23 27 import {Post} from '#/view/com/post/Post' 28 + import {EmptyState} from '#/view/com/util/EmptyState' 24 29 import {List} from '#/view/com/util/List' 25 30 import {PostFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 26 - import {EmptyState} from '#/screens/Bookmarks/components/EmptyState' 27 31 import {atoms as a, useTheme} from '#/alf' 28 32 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 29 - import {BookmarkFilled} from '#/components/icons/Bookmark' 33 + import {BookmarkDeleteLarge, BookmarkFilled} from '#/components/icons/Bookmark' 30 34 import {CircleQuestion_Stroke2_Corner2_Rounded as QuestionIcon} from '#/components/icons/CircleQuestion' 31 35 import * as Layout from '#/components/Layout' 32 36 import {ListFooter} from '#/components/Lists' ··· 259 263 ) 260 264 } 261 265 266 + function BookmarksEmpty() { 267 + const t = useTheme() 268 + const {_} = useLingui() 269 + const navigation = useNavigation<NavigationProp<CommonNavigatorParams>>() 270 + 271 + return ( 272 + <EmptyState 273 + icon={BookmarkDeleteLarge} 274 + message={_(msg`Nothing saved yet`)} 275 + textStyle={[t.atoms.text_contrast_medium, a.font_medium]} 276 + button={{ 277 + label: _(msg`Button to go back to the home timeline`), 278 + text: _(msg`Go home`), 279 + onPress: () => navigation.navigate('Home' as never), 280 + size: 'small', 281 + color: 'secondary', 282 + }} 283 + style={[a.pt_3xl]} 284 + /> 285 + ) 286 + } 287 + 262 288 function renderItem({item, index}: {item: ListItem; index: number}) { 263 289 switch (item.type) { 264 290 case 'loading': { 265 291 return <PostFeedLoadingPlaceholder /> 266 292 } 267 293 case 'empty': { 268 - return <EmptyState /> 294 + return <BookmarksEmpty /> 269 295 } 270 296 case 'bookmark': { 271 297 return (
+6 -1
src/screens/Notifications/ActivityList.tsx
··· 5 5 import {type AllNavigatorParams} from '#/lib/routes/types' 6 6 import {PostFeed} from '#/view/com/posts/PostFeed' 7 7 import {EmptyState} from '#/view/com/util/EmptyState' 8 + import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' 8 9 import * as Layout from '#/components/Layout' 9 10 import {ListFooter} from '#/components/Lists' 10 11 ··· 35 36 feed={`posts|${uris}`} 36 37 disablePoll 37 38 renderEmptyState={() => ( 38 - <EmptyState icon="growth" message={_(msg`No skeets here`)} /> 39 + <EmptyState 40 + icon={EditIcon} 41 + iconSize="2xl" 42 + message={_(msg`No posts here`)} 43 + /> 39 44 )} 40 45 renderEndOfFeed={() => <ListFooter />} 41 46 />
+13 -5
src/screens/Onboarding/StepProfile/index.tsx
··· 15 15 import {getDataUriSize} from '#/lib/media/util' 16 16 import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' 17 17 import {logEvent, useGate} from '#/lib/statsig/statsig' 18 + import {isCancelledError} from '#/lib/strings/errors' 19 + import {logger} from '#/logger' 18 20 import {isNative, isWeb} from '#/platform/detection' 19 21 import { 20 22 DescriptionText, ··· 184 186 if (!image) return 185 187 186 188 if (!isWeb) { 187 - image = await openCropper({ 188 - imageUri: image.path, 189 - shape: 'circle', 190 - aspectRatio: 1 / 1, 191 - }) 189 + try { 190 + image = await openCropper({ 191 + imageUri: image.path, 192 + shape: 'circle', 193 + aspectRatio: 1 / 1, 194 + }) 195 + } catch (e) { 196 + if (!isCancelledError(e)) { 197 + logger.error('Failed to crop avatar in onboarding', {error: e}) 198 + } 199 + } 192 200 } 193 201 image = await compressIfNeeded(image, 1000000) 194 202
+2 -2
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 588 588 <BackdatedPostIndicator post={post} /> 589 589 <View style={[a.flex_row, a.align_center, a.flex_wrap, a.gap_sm]}> 590 590 <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> 591 - {niceDate(i18n, post.indexedAt, 'medium')} 591 + {niceDate(i18n, post.indexedAt, 'dot separated')} 592 592 </Text> 593 593 {isRootPost && ( 594 594 <WhoCanReply post={post} isThreadAuthor={isThreadAuthor} /> ··· 674 674 a.leading_tight, 675 675 t.atoms.text_contrast_medium, 676 676 ]}> 677 - <Trans>Archived from {niceDate(i18n, createdAt)}</Trans> 677 + <Trans>Archived from {niceDate(i18n, createdAt, 'medium')}</Trans> 678 678 </Text> 679 679 </View> 680 680 )}
+8 -1
src/screens/Profile/ProfileFeed/index.tsx
··· 45 45 ProfileFeedHeader, 46 46 ProfileFeedHeaderSkeleton, 47 47 } from '#/screens/Profile/components/ProfileFeedHeader' 48 + import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 48 49 import * as Layout from '#/components/Layout' 49 50 50 51 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'> ··· 189 190 }, [onScrollToTop, isScreenFocused]) 190 191 191 192 const renderPostsEmpty = useCallback(() => { 192 - return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 193 + return ( 194 + <EmptyState 195 + icon={HashtagWideIcon} 196 + iconSize="2xl" 197 + message={_(msg`This feed is empty.`)} 198 + /> 199 + ) 193 200 }, [_]) 194 201 195 202 const isVideoFeed = React.useMemo(() => {
+28 -6
src/screens/Profile/Sections/Feed.tsx
··· 6 6 7 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 8 import {isIOS, isNative} from '#/platform/detection' 9 - import {type FeedDescriptor} from '#/state/queries/post-feed' 10 - import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 9 + import { 10 + type FeedDescriptor, 11 + RQKEY as FEED_RQKEY, 12 + } from '#/state/queries/post-feed' 11 13 import {truncateAndInvalidate} from '#/state/queries/util' 12 14 import {PostFeed} from '#/view/com/posts/PostFeed' 13 - import {EmptyState} from '#/view/com/util/EmptyState' 15 + import { 16 + EmptyState, 17 + type EmptyStateButtonProps, 18 + } from '#/view/com/util/EmptyState' 14 19 import {type ListRef} from '#/view/com/util/List' 15 20 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 16 21 import {atoms as a, ios, useTheme} from '#/alf' 22 + import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' 17 23 import {Text} from '#/components/Typography' 18 24 import {type SectionRef} from './types' 19 25 ··· 25 31 scrollElRef: ListRef 26 32 ignoreFilterFor?: string 27 33 setScrollViewTag: (tag: number | null) => void 34 + emptyStateMessage?: string 35 + emptyStateButton?: EmptyStateButtonProps 36 + emptyStateIcon?: React.ComponentType<any> | React.ReactElement 28 37 } 38 + 29 39 export function ProfileFeedSection({ 30 40 ref, 31 41 feed, ··· 34 44 scrollElRef, 35 45 ignoreFilterFor, 36 46 setScrollViewTag, 47 + emptyStateMessage, 48 + emptyStateButton, 49 + emptyStateIcon, 37 50 }: FeedSectionProps) { 38 51 const {_} = useLingui() 39 52 const queryClient = useQueryClient() ··· 44 57 const adjustedInitialNumToRender = useInitialNumToRender({ 45 58 screenHeightOffset: headerHeight, 46 59 }) 47 - 48 60 const onScrollToTop = useCallback(() => { 49 61 scrollElRef.current?.scrollToOffset({ 50 62 animated: isNative, ··· 59 71 })) 60 72 61 73 const renderPostsEmpty = useCallback(() => { 62 - return <EmptyState icon="growth" message={_(msg`No skeets yet.`)} /> 63 - }, [_]) 74 + return ( 75 + <View style={[a.flex_1, a.justify_center, a.align_center]}> 76 + <EmptyState 77 + style={{width: '100%'}} 78 + icon={emptyStateIcon || EditIcon} 79 + iconSize="3xl" 80 + message={emptyStateMessage || _(msg`No posts yet`)} 81 + button={emptyStateButton} 82 + /> 83 + </View> 84 + ) 85 + }, [_, emptyStateButton, emptyStateIcon, emptyStateMessage]) 64 86 65 87 useEffect(() => { 66 88 if (isIOS && isFocused && scrollElRef.current) {
+3 -2
src/screens/ProfileList/AboutSection.tsx
··· 12 12 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 13 13 import {atoms as a, useBreakpoints} from '#/alf' 14 14 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 + import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 15 16 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 16 17 17 18 interface SectionRef { ··· 95 96 const renderEmptyState = useCallback(() => { 96 97 return ( 97 98 <View style={[a.gap_xl, a.align_center]}> 98 - <EmptyState icon="users-slash" message={_(msg`This list is empty.`)} /> 99 + <EmptyState icon={ListIcon} message={_(msg`This list is empty.`)} /> 99 100 {isOwner && ( 100 101 <Button 101 102 testID="emptyStateAddUserBtn" ··· 111 112 )} 112 113 </View> 113 114 ) 114 - }, [_, onPressAddUser, isOwner]) 115 + }, [_, isOwner, onPressAddUser]) 115 116 116 117 return ( 117 118 <View>
+10 -3
src/screens/ProfileList/FeedSection.tsx
··· 7 7 8 8 import {isNative} from '#/platform/detection' 9 9 import {listenSoftReset} from '#/state/events' 10 - import {type FeedDescriptor} from '#/state/queries/post-feed' 11 - import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 10 + import { 11 + type FeedDescriptor, 12 + RQKEY as FEED_RQKEY, 13 + } from '#/state/queries/post-feed' 12 14 import {PostFeed} from '#/view/com/posts/PostFeed' 13 15 import {EmptyState} from '#/view/com/util/EmptyState' 14 16 import {type ListRef} from '#/view/com/util/List' 15 17 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 16 18 import {atoms as a} from '#/alf' 17 19 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 20 + import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 18 21 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 19 22 20 23 interface SectionRef { ··· 68 71 const renderPostsEmpty = useCallback(() => { 69 72 return ( 70 73 <View style={[a.gap_xl, a.align_center]}> 71 - <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 74 + <EmptyState 75 + icon={HashtagWideIcon} 76 + iconSize="2xl" 77 + message={_(msg`This feed is empty.`)} 78 + /> 72 79 {isOwner && ( 73 80 <Button 74 81 label={_(msg`Start adding people`)}
+2 -1
src/screens/Settings/AppPasswords.tsx
··· 24 24 import {Admonition, colors} from '#/components/Admonition' 25 25 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 26 26 import {useDialogControl} from '#/components/Dialog' 27 + import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 27 28 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 28 29 import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 29 30 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' ··· 102 103 </View> 103 104 ) : ( 104 105 <EmptyState 105 - icon="growth" 106 + icon={Growth} 106 107 message={_(msg`No app passwords yet`)} 107 108 /> 108 109 )
+1
src/screens/Settings/components/SettingsList.tsx
··· 201 201 lg: 24, 202 202 xl: 28, 203 203 '2xl': 32, 204 + '3xl': 40, 204 205 }[size] 205 206 206 207 const color =
+2 -1
src/state/gallery.ts
··· 18 18 import {openCropper} from '#/lib/media/picker' 19 19 import {type PickerImage} from '#/lib/media/picker.shared' 20 20 import {getDataUriSize} from '#/lib/media/util' 21 + import {isCancelledError} from '#/lib/strings/errors' 21 22 import {isNative} from '#/platform/detection' 22 23 23 24 export type ImageTransformation = { ··· 147 148 }, 148 149 } 149 150 } catch (e) { 150 - if (e instanceof Error && e.message.includes('User cancelled')) { 151 + if (!isCancelledError(e)) { 151 152 return img 152 153 } 153 154
+1 -1
src/view/com/composer/photos/Gallery.tsx
··· 241 241 accessibilityIgnoresInvertColors 242 242 cachePolicy="none" 243 243 autoplay={false} 244 + contentFit="cover" 244 245 /> 245 246 246 247 <MediaInsetBorder /> ··· 285 286 marginTop: 16, 286 287 }, 287 288 image: { 288 - resizeMode: 'cover', 289 289 borderRadius: tokens.borderRadius.md, 290 290 }, 291 291 imageControl: {
+15 -4
src/view/com/feeds/ProfileFeedgens.tsx
··· 15 15 } from 'react-native' 16 16 import {msg} from '@lingui/macro' 17 17 import {useLingui} from '@lingui/react' 18 + import {useNavigation} from '@react-navigation/native' 18 19 import {useQueryClient} from '@tanstack/react-query' 19 20 20 21 import {cleanError} from '#/lib/strings/errors' ··· 29 30 import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' 30 31 import {atoms as a, ios, useTheme} from '#/alf' 31 32 import * as FeedCard from '#/components/FeedCard' 33 + import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 32 34 import {ListFooter} from '#/components/Lists' 33 35 34 36 const LOADING = {_reactKey: '__loading__'} ··· 78 80 } = useProfileFeedgensQuery(did, opts) 79 81 const isEmpty = !isPending && !data?.pages[0]?.feeds.length 80 82 const {data: preferences} = usePreferencesQuery() 83 + const navigation = useNavigation() 81 84 82 85 const items = useMemo(() => { 83 86 let items: any[] = [] ··· 147 150 if (item === EMPTY) { 148 151 return ( 149 152 <EmptyState 150 - icon="hashtag" 151 - message={_(msg`You have no feeds.`)} 152 - testID="listsEmpty" 153 + style={{width: '100%'}} 154 + icon={HashtagWideIcon} 155 + message={_(msg`You haven't made any custom feeds yet.`)} 156 + textStyle={[t.atoms.text_contrast_medium, a.font_medium]} 157 + button={{ 158 + label: _(msg`Browse custom feeds`), 159 + text: _(msg`Browse custom feeds`), 160 + onPress: () => navigation.navigate('Feeds' as never), 161 + size: 'small', 162 + color: 'secondary', 163 + }} 153 164 /> 154 165 ) 155 166 } else if (item === ERROR_ITEM) { ··· 183 194 } 184 195 return null 185 196 }, 186 - [_, t, error, refetch, onPressRetryLoadMore, preferences], 197 + [_, t, error, refetch, onPressRetryLoadMore, preferences, navigation], 187 198 ) 188 199 189 200 useEffect(() => {
+6 -7
src/view/com/lists/MyLists.tsx
··· 19 19 import {useModerationOpts} from '#/state/preferences/moderation-opts' 20 20 import {type MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists' 21 21 import {atoms as a, useTheme} from '#/alf' 22 - import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 22 + import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 23 23 import * as ListCard from '#/components/ListCard' 24 24 import {Text} from '#/components/Typography' 25 25 import {ErrorMessage} from '../util/error/ErrorMessage' ··· 71 71 switch (filter) { 72 72 case 'curate': 73 73 emptyText = _( 74 - msg`Public, sharable lists which can be used to drive feeds.`, 74 + msg`Lists allow you to see content from your favorite people.`, 75 75 ) 76 76 break 77 77 case 'mod': ··· 104 104 ({item, index}: {item: any; index: number}) => { 105 105 if (item === EMPTY) { 106 106 return ( 107 - <View style={[a.flex_1, a.align_center, a.gap_sm, a.px_xl, a.pt_xl]}> 107 + <View style={[a.flex_1, a.align_center, a.gap_sm, a.px_xl, a.pt_3xl]}> 108 108 <View 109 109 style={[ 110 110 a.align_center, 111 111 a.justify_center, 112 112 enableSquareButtons ? a.rounded_sm : a.rounded_full, 113 - t.atoms.bg_contrast_25, 114 113 { 115 - width: 32, 116 - height: 32, 114 + width: 64, 115 + height: 64, 117 116 }, 118 117 ]}> 119 - <ListIcon size="md" fill={t.atoms.text_contrast_low.color} /> 118 + <ListIcon size="2xl" fill={t.atoms.text_contrast_medium.color} /> 120 119 </View> 121 120 <Text 122 121 style={[
+50 -33
src/view/com/lists/ProfileLists.tsx
··· 15 15 } from 'react-native' 16 16 import {msg} from '@lingui/macro' 17 17 import {useLingui} from '@lingui/react' 18 + import {useNavigation} from '@react-navigation/native' 18 19 import {useQueryClient} from '@tanstack/react-query' 19 20 20 21 import {cleanError} from '#/lib/strings/errors' 21 22 import {logger} from '#/logger' 22 23 import {isIOS, isNative, isWeb} from '#/platform/detection' 23 - import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' 24 + import {usePreferencesQuery} from '#/state/queries/preferences' 25 + import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' 24 26 import {EmptyState} from '#/view/com/util/EmptyState' 25 27 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 26 28 import {List, type ListRef} from '#/view/com/util/List' 27 29 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 28 30 import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' 29 31 import {atoms as a, ios, useTheme} from '#/alf' 30 - import * as ListCard from '#/components/ListCard' 32 + import * as FeedCard from '#/components/FeedCard' 33 + import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 31 34 import {ListFooter} from '#/components/Lists' 32 35 33 36 const LOADING = {_reactKey: '__loading__'} ··· 39 42 scrollToTop: () => void 40 43 } 41 44 42 - interface ProfileListsProps { 45 + interface ProfileFeedgensProps { 43 46 ref?: React.Ref<SectionRef> 44 47 did: string 45 48 scrollElRef: ListRef ··· 59 62 style, 60 63 testID, 61 64 setScrollViewTag, 62 - }: ProfileListsProps) { 65 + }: ProfileFeedgensProps) { 66 + const {_} = useLingui() 63 67 const t = useTheme() 64 - const {_} = useLingui() 68 + const [isPTRing, setIsPTRing] = useState(false) 65 69 const {height} = useWindowDimensions() 66 - const [isPTRing, setIsPTRing] = useState(false) 67 70 const opts = useMemo(() => ({enabled}), [enabled]) 68 71 const { 69 72 data, 70 73 isPending, 74 + isFetchingNextPage, 71 75 hasNextPage, 72 76 fetchNextPage, 73 - isFetchingNextPage, 74 77 isError, 75 78 error, 76 79 refetch, 77 - } = useProfileListsQuery(did, opts) 78 - const isEmpty = !isPending && !data?.pages[0]?.lists.length 80 + } = useProfileFeedgensQuery(did, opts) 81 + const isEmpty = !isPending && !data?.pages[0]?.feeds.length 82 + const {data: preferences} = usePreferencesQuery() 83 + const navigation = useNavigation() 79 84 80 85 const items = useMemo(() => { 81 86 let items: any[] = [] ··· 88 93 items = items.concat([EMPTY]) 89 94 } else if (data?.pages) { 90 95 for (const page of data?.pages) { 91 - items = items.concat(page.lists) 96 + items = items.concat(page.feeds) 92 97 } 93 - } 94 - if (isError && !isEmpty) { 98 + } else if (isError && !isEmpty) { 95 99 items = items.concat([LOAD_MORE_ERROR_ITEM]) 96 100 } 97 101 return items ··· 119 123 try { 120 124 await refetch() 121 125 } catch (err) { 122 - logger.error('Failed to refresh lists', {message: err}) 126 + logger.error('Failed to refresh feeds', {message: err}) 123 127 } 124 128 setIsPTRing(false) 125 129 }, [refetch, setIsPTRing]) 126 130 127 131 const onEndReached = useCallback(async () => { 128 132 if (isFetchingNextPage || !hasNextPage || isError) return 133 + 129 134 try { 130 135 await fetchNextPage() 131 136 } catch (err) { 132 - logger.error('Failed to load more lists', {message: err}) 137 + logger.error('Failed to load more feeds', {message: err}) 133 138 } 134 139 }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 135 140 ··· 140 145 // rendering 141 146 // = 142 147 143 - const renderItemInner = useCallback( 148 + const renderItem = useCallback( 144 149 ({item, index}: ListRenderItemInfo<any>) => { 145 150 if (item === EMPTY) { 146 151 return ( 147 152 <EmptyState 148 - icon="list-ul" 149 - message={_(msg`You have no lists.`)} 150 - testID="listsEmpty" 153 + icon={ListIcon} 154 + message={_( 155 + msg`Lists allow you to see content from your favorite people.`, 156 + )} 157 + textStyle={[t.atoms.text_contrast_medium, a.font_medium]} 158 + button={{ 159 + label: _(msg`Create a list`), 160 + text: _(msg`Create a list`), 161 + onPress: () => navigation.navigate('Lists' as never), 162 + size: 'small', 163 + color: 'primary', 164 + }} 151 165 /> 152 166 ) 153 167 } else if (item === ERROR_ITEM) { ··· 166 180 } else if (item === LOADING) { 167 181 return <FeedLoadingPlaceholder /> 168 182 } 169 - return ( 170 - <View 171 - style={[ 172 - (index !== 0 || isWeb) && a.border_t, 173 - t.atoms.border_contrast_low, 174 - a.px_lg, 175 - a.py_lg, 176 - ]}> 177 - <ListCard.Default view={item} /> 178 - </View> 179 - ) 183 + if (preferences) { 184 + return ( 185 + <View 186 + style={[ 187 + (index !== 0 || isWeb) && a.border_t, 188 + t.atoms.border_contrast_low, 189 + a.px_lg, 190 + a.py_lg, 191 + ]}> 192 + <FeedCard.Default view={item} /> 193 + </View> 194 + ) 195 + } 196 + return null 180 197 }, 181 - [error, refetch, onPressRetryLoadMore, _, t.atoms.border_contrast_low], 198 + [_, t, error, refetch, onPressRetryLoadMore, preferences, navigation], 182 199 ) 183 200 184 201 useEffect(() => { ··· 188 205 } 189 206 }, [enabled, scrollElRef, setScrollViewTag]) 190 207 191 - const ProfileListsFooter = useCallback(() => { 208 + const ProfileFeedgensFooter = useCallback(() => { 192 209 if (isEmpty) return null 193 210 return ( 194 211 <ListFooter ··· 215 232 ref={scrollElRef} 216 233 data={items} 217 234 keyExtractor={keyExtractor} 218 - renderItem={renderItemInner} 219 - ListFooterComponent={ProfileListsFooter} 235 + renderItem={renderItem} 236 + ListFooterComponent={ProfileFeedgensFooter} 220 237 refreshing={isPTRing} 221 238 onRefresh={onRefresh} 222 239 headerOffset={headerOffset}
+2 -1
src/view/com/notifications/NotificationFeed.tsx
··· 20 20 import {List, type ListProps, type ListRef} from '#/view/com/util/List' 21 21 import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 22 22 import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' 23 + import {Bell_Stroke2_Corner0_Rounded as BellIcon} from '#/components/icons/Bell' 23 24 import {NotificationFeedItem} from './NotificationFeedItem' 24 25 25 26 const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} ··· 120 121 if (item === EMPTY_FEED_ITEM) { 121 122 return ( 122 123 <EmptyState 123 - icon="bell" 124 + icon={BellIcon} 124 125 message={_(msg`No notifications yet!`)} 125 126 style={styles.emptyState} 126 127 />
+5 -2
src/view/com/posts/PostFeedErrorMessage.tsx
··· 15 15 import {logger} from '#/logger' 16 16 import {type FeedDescriptor} from '#/state/queries/post-feed' 17 17 import {useRemoveFeedMutation} from '#/state/queries/preferences' 18 + import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 18 19 import * as Prompt from '#/components/Prompt' 19 20 import {EmptyState} from '../util/EmptyState' 20 21 import {ErrorMessage} from '../util/error/ErrorMessage' ··· 50 51 () => detectKnownError(feedDesc, error), 51 52 [feedDesc, error], 52 53 ) 54 + 53 55 if ( 54 56 typeof knownError !== 'undefined' && 55 57 knownError !== KnownError.Unknown && ··· 68 70 if (knownError === KnownError.Block) { 69 71 return ( 70 72 <EmptyState 71 - icon="ban" 72 - message={_l(msgLingui`Skeets hidden`)} 73 + icon={WarningIcon} 74 + iconSize="2xl" 75 + message={_l(msgLingui`Posts hidden`)} 73 76 style={{paddingVertical: 40}} 74 77 /> 75 78 )
+13 -1
src/view/com/profile/ProfileFollowers.tsx
··· 2 2 import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import {useNavigation} from '@react-navigation/native' 5 6 6 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 7 8 import {cleanError} from '#/lib/strings/errors' ··· 9 10 import {useProfileFollowersQuery} from '#/state/queries/profile-followers' 10 11 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 11 12 import {useSession} from '#/state/session' 13 + import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' 12 14 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 13 15 import {List} from '../util/List' 14 16 import {ProfileCardWithFollowBtn} from './ProfileCard' ··· 39 41 40 42 export function ProfileFollowers({name}: {name: string}) { 41 43 const {_} = useLingui() 44 + const navigation = useNavigation() 42 45 const initialNumToRender = useInitialNumToRender() 43 46 const {currentAccount} = useSession() 44 47 ··· 129 132 emptyType="results" 130 133 emptyMessage={ 131 134 isMe 132 - ? _(msg`You do not have any followers.`) 135 + ? _(msg`No followers yet`) 133 136 : _(msg`This user doesn't have any followers.`) 134 137 } 135 138 errorMessage={cleanError(resolveError || error)} 136 139 onRetry={isError ? refetch : undefined} 137 140 sideBorders={false} 141 + useEmptyState={true} 142 + emptyStateIcon={PeopleRemoveIcon} 143 + emptyStateButton={{ 144 + label: _(msg`Go back`), 145 + text: _(msg`Go back`), 146 + color: 'secondary', 147 + size: 'small', 148 + onPress: () => navigation.goBack(), 149 + }} 138 150 /> 139 151 ) 140 152 }
+24 -1
src/view/com/profile/ProfileFollows.tsx
··· 2 2 import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import {useNavigation} from '@react-navigation/native' 5 6 6 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 + import {type NavigationProp} from '#/lib/routes/types' 7 9 import {cleanError} from '#/lib/strings/errors' 8 10 import {logger} from '#/logger' 11 + import {isWeb} from '#/platform/detection' 9 12 import {useProfileFollowsQuery} from '#/state/queries/profile-follows' 10 13 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 11 14 import {useSession} from '#/state/session' 15 + import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' 12 16 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 13 17 import {List} from '../util/List' 14 18 import {ProfileCardWithFollowBtn} from './ProfileCard' ··· 41 45 const {_} = useLingui() 42 46 const initialNumToRender = useInitialNumToRender() 43 47 const {currentAccount} = useSession() 48 + const navigation = useNavigation<NavigationProp>() 49 + 50 + const onPressFindAccounts = React.useCallback(() => { 51 + if (isWeb) { 52 + navigation.navigate('Search', {}) 53 + } else { 54 + navigation.navigate('SearchTab') 55 + navigation.popToTop() 56 + } 57 + }, [navigation]) 44 58 45 59 const [isPTRing, setIsPTRing] = React.useState(false) 46 60 const { ··· 129 143 emptyType="results" 130 144 emptyMessage={ 131 145 isMe 132 - ? _(msg`You are not following anyone.`) 146 + ? _(msg`You are not following anyone yet`) 133 147 : _(msg`This user isn't following anyone.`) 134 148 } 135 149 errorMessage={cleanError(resolveError || error)} 136 150 onRetry={isError ? refetch : undefined} 137 151 sideBorders={false} 152 + useEmptyState={true} 153 + emptyStateIcon={PeopleRemoveIcon} 154 + emptyStateButton={{ 155 + label: _(msg`See suggested accounts`), 156 + text: _(msg`See suggested accounts`), 157 + onPress: onPressFindAccounts, 158 + size: 'tiny', 159 + color: 'primary', 160 + }} 138 161 /> 139 162 ) 140 163 }
+84 -49
src/view/com/util/EmptyState.tsx
··· 1 - import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' 2 - import {type IconProp} from '@fortawesome/fontawesome-svg-core' 3 - import { 4 - FontAwesomeIcon, 5 - type FontAwesomeIconStyle, 6 - } from '@fortawesome/react-native-fontawesome' 1 + import React from 'react' 2 + import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native' 3 + import {View} from 'react-native' 7 4 8 5 import {usePalette} from '#/lib/hooks/usePalette' 9 6 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 10 - import {UserGroupIcon} from '#/lib/icons' 11 - import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 12 - import {Text} from './text/Text' 7 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 8 + import {Button, type ButtonProps, ButtonText} from '#/components/Button' 9 + import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' 10 + import {Text} from '#/components/Typography' 11 + 12 + export type EmptyStateButtonProps = Omit<ButtonProps, 'children' | 'label'> & { 13 + label: string 14 + text: string 15 + } 13 16 14 17 export function EmptyState({ 15 18 testID, 16 19 icon, 20 + iconSize = '3xl', 17 21 message, 18 22 style, 23 + textStyle, 24 + button, 19 25 }: { 20 26 testID?: string 21 - icon: IconProp | 'user-group' | 'growth' 27 + icon?: React.ComponentType<any> | React.ReactElement 28 + iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' 22 29 message: string 23 30 style?: StyleProp<ViewStyle> 31 + textStyle?: StyleProp<TextStyle> 32 + button?: EmptyStateButtonProps 24 33 }) { 25 34 const pal = usePalette('default') 26 35 const {isTabletOrDesktop} = useWebMediaQueries() 27 - const iconSize = isTabletOrDesktop ? 64 : 48 36 + const t = useTheme() 37 + const {gtMobile} = useBreakpoints() 38 + 39 + const placeholderIcon = ( 40 + <EditIcon size="2xl" fill={t.atoms.text_contrast_medium.color} /> 41 + ) 42 + 43 + const renderIcon = () => { 44 + if (!icon) { 45 + return placeholderIcon 46 + } 47 + 48 + if (React.isValidElement(icon)) { 49 + return icon 50 + } 51 + 52 + if ( 53 + typeof icon === 'function' || 54 + (typeof icon === 'object' && icon && 'render' in icon) 55 + ) { 56 + const IconComponent = icon 57 + return ( 58 + <IconComponent 59 + size={iconSize} 60 + fill={t.atoms.text_contrast_medium.color} 61 + style={{color: t.atoms.text_contrast_low.color}} 62 + /> 63 + ) 64 + } 65 + 66 + return placeholderIcon 67 + } 68 + 28 69 return ( 29 70 <View testID={testID} style={style}> 30 71 <View 31 72 style={[ 32 - styles.iconContainer, 33 - isTabletOrDesktop && styles.iconContainerBig, 34 - pal.viewLight, 73 + a.flex_row, 74 + a.align_center, 75 + a.justify_center, 76 + a.self_center, 77 + a.rounded_full, 78 + a.mt_5xl, 79 + {height: 64, width: 64}, 80 + React.isValidElement(icon) 81 + ? a.bg_transparent 82 + : [isTabletOrDesktop && {marginTop: 50}], 35 83 ]}> 36 - {icon === 'user-group' ? ( 37 - <UserGroupIcon size={iconSize} /> 38 - ) : icon === 'growth' ? ( 39 - <Growth width={iconSize} fill={pal.colors.emptyStateIcon} /> 40 - ) : ( 41 - <FontAwesomeIcon 42 - icon={icon} 43 - size={iconSize} 44 - style={[{color: pal.colors.emptyStateIcon} as FontAwesomeIconStyle]} 45 - /> 46 - )} 84 + {renderIcon()} 47 85 </View> 48 - <Text type="xl" style={[{color: pal.colors.textLight}, styles.text]}> 86 + <Text 87 + style={[ 88 + { 89 + color: pal.colors.textLight, 90 + maxWidth: gtMobile ? '40%' : '60%', 91 + }, 92 + a.pt_xs, 93 + a.font_medium, 94 + a.text_md, 95 + a.leading_snug, 96 + a.text_center, 97 + a.self_center, 98 + textStyle, 99 + ]}> 49 100 {message} 50 101 </Text> 102 + {button && ( 103 + <View style={[a.flex_shrink, a.mt_xl, a.self_center]}> 104 + <Button {...button}> 105 + <ButtonText>{button.text}</ButtonText> 106 + </Button> 107 + </View> 108 + )} 51 109 </View> 52 110 ) 53 111 } 54 - 55 - const styles = StyleSheet.create({ 56 - iconContainer: { 57 - flexDirection: 'row', 58 - alignItems: 'center', 59 - justifyContent: 'center', 60 - height: 80, 61 - width: 80, 62 - marginLeft: 'auto', 63 - marginRight: 'auto', 64 - borderRadius: 80, 65 - marginTop: 30, 66 - }, 67 - iconContainerBig: { 68 - width: 100, 69 - height: 100, 70 - marginTop: 50, 71 - }, 72 - text: { 73 - textAlign: 'center', 74 - paddingTop: 20, 75 - }, 76 - })
+4 -3
src/view/com/util/UserAvatar.tsx
··· 26 26 import {type PickerImage} from '#/lib/media/picker.shared' 27 27 import {makeProfileLink} from '#/lib/routes/links' 28 28 import {sanitizeDisplayName} from '#/lib/strings/display-names' 29 + import {isCancelledError} from '#/lib/strings/errors' 29 30 import {sanitizeHandle} from '#/lib/strings/handles' 30 31 import {logger} from '#/logger' 31 32 import {isAndroid, isNative, isWeb} from '#/platform/detection' ··· 441 442 setRawImage(await createComposerImage(item)) 442 443 editImageDialogControl.open() 443 444 } 444 - } catch (e: any) { 445 + } catch (e) { 445 446 // Don't log errors for cancelling selection to sentry on ios or android 446 - if (!String(e).toLowerCase().includes('cancel')) { 447 - logger.error('Failed to crop banner', {error: e}) 447 + if (!isCancelledError(e)) { 448 + logger.error('Failed to crop avatar', {error: e}) 448 449 } 449 450 } 450 451 }, [
+4 -2
src/view/com/util/UserBanner.tsx
··· 12 12 import {compressIfNeeded} from '#/lib/media/manip' 13 13 import {openCamera, openCropper, openPicker} from '#/lib/media/picker' 14 14 import {type PickerImage} from '#/lib/media/picker.shared' 15 + import {isCancelledError} from '#/lib/strings/errors' 15 16 import {logger} from '#/logger' 16 17 import {isAndroid, isNative} from '#/platform/detection' 17 18 import { ··· 92 93 setRawImage(await createComposerImage(items[0])) 93 94 editImageDialogControl.open() 94 95 } 95 - } catch (e: any) { 96 - if (!String(e).includes('Canceled')) { 96 + } catch (e) { 97 + // Don't log errors for cancelling selection to sentry on ios or android 98 + if (!isCancelledError(e)) { 97 99 logger.error('Failed to crop banner', {error: e}) 98 100 } 99 101 }
+10 -1
src/view/screens/Debug.tsx
··· 20 20 import * as Toast from '#/view/com/util/Toast' 21 21 import {ViewHeader} from '#/view/com/util/ViewHeader' 22 22 import {ViewSelector} from '#/view/com/util/ViewSelector' 23 + import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 23 24 import * as Layout from '#/components/Layout' 24 25 25 26 const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs'] ··· 333 334 } 334 335 335 336 function EmptyStateView() { 336 - return <EmptyState icon="bars" message="This is an empty state" /> 337 + const {_} = useLingui() 338 + 339 + return ( 340 + <EmptyState 341 + icon={HashtagWideIcon} 342 + iconSize="2xl" 343 + message={_(msg`This is an empty state`)} 344 + /> 345 + ) 337 346 } 338 347 339 348 function LoadingPlaceholderView() {
+63 -2
src/view/screens/Profile.tsx
··· 7 7 type ModerationOpts, 8 8 RichText as RichTextAPI, 9 9 } from '@atproto/api' 10 - import {msg} from '@lingui/macro' 10 + import {msg, Trans} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 - import {useFocusEffect} from '@react-navigation/native' 12 + import {useFocusEffect, useNavigation} from '@react-navigation/native' 13 13 import {useQueryClient} from '@tanstack/react-query' 14 14 15 15 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 16 + import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' 16 17 import {useSetTitle} from '#/lib/hooks/useSetTitle' 17 18 import {ComposeIcon2} from '#/lib/icons' 18 19 import { 19 20 type CommonNavigatorParams, 20 21 type NativeStackScreenProps, 22 + type NavigationProp, 21 23 } from '#/lib/routes/types' 22 24 import {combinedDisplayName} from '#/lib/strings/display-names' 23 25 import {cleanError} from '#/lib/strings/errors' ··· 42 44 import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed' 43 45 import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels' 44 46 import {atoms as a} from '#/alf' 47 + import {Circle_And_Square_Stroke1_Corner0_Rounded_Filled as CircleAndSquareIcon} from '#/components/icons/CircleAndSquare' 48 + import {Heart2_Stroke1_Corner0_Rounded as HeartIcon} from '#/components/icons/Heart2' 49 + import {Image_Stroke1_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 50 + import {Message_Stroke1_Corner0_Rounded_Filled as MessageIcon} from '#/components/icons/Message' 51 + import {VideoClip_Stroke1_Corner0_Rounded as VideoIcon} from '#/components/icons/VideoClip' 45 52 import * as Layout from '#/components/Layout' 46 53 import {ScreenHider} from '#/components/moderation/ScreenHider' 47 54 import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks' ··· 169 176 const {hasSession, currentAccount} = useSession() 170 177 const setMinimalShellMode = useSetMinimalShellMode() 171 178 const {openComposer} = useOpenComposer() 179 + const navigation = useNavigation<NavigationProp>() 180 + const requireEmailVerification = useRequireEmailVerification() 172 181 const { 173 182 data: labelerInfo, 174 183 error: labelerError, ··· 334 343 scrollSectionToTop(index) 335 344 } 336 345 346 + const navToWizard = useCallback(() => { 347 + navigation.navigate('StarterPackWizard', {}) 348 + }, [navigation]) 349 + const wrappedNavToWizard = requireEmailVerification(navToWizard, { 350 + instructions: [ 351 + <Trans key="nav"> 352 + Before creating a starter pack, you must first verify your email. 353 + </Trans>, 354 + ], 355 + }) 356 + 337 357 // rendering 338 358 // = 339 359 ··· 408 428 scrollElRef={scrollElRef as ListRef} 409 429 ignoreFilterFor={profile.did} 410 430 setScrollViewTag={setScrollViewTag} 431 + emptyStateMessage={_(msg`No posts yet`)} 432 + emptyStateButton={{ 433 + label: _(msg`Write a post`), 434 + text: _(msg`Write a post`), 435 + onPress: () => openComposer({}), 436 + size: 'small', 437 + color: 'primary', 438 + }} 411 439 /> 412 440 ) 413 441 : null} ··· 421 449 scrollElRef={scrollElRef as ListRef} 422 450 ignoreFilterFor={profile.did} 423 451 setScrollViewTag={setScrollViewTag} 452 + emptyStateMessage={_(msg`No replies yet`)} 453 + emptyStateIcon={MessageIcon} 424 454 /> 425 455 ) 426 456 : null} ··· 434 464 scrollElRef={scrollElRef as ListRef} 435 465 ignoreFilterFor={profile.did} 436 466 setScrollViewTag={setScrollViewTag} 467 + emptyStateMessage={_(msg`No media yet`)} 468 + emptyStateButton={{ 469 + label: _(msg`Post a photo`), 470 + text: _(msg`Post a photo`), 471 + onPress: () => openComposer({}), 472 + size: 'small', 473 + color: 'primary', 474 + }} 475 + emptyStateIcon={ImageIcon} 437 476 /> 438 477 ) 439 478 : null} ··· 447 486 scrollElRef={scrollElRef as ListRef} 448 487 ignoreFilterFor={profile.did} 449 488 setScrollViewTag={setScrollViewTag} 489 + emptyStateMessage={_(msg`No video posts yet`)} 490 + emptyStateButton={{ 491 + label: _(msg`Post a video`), 492 + text: _(msg`Post a video`), 493 + onPress: () => openComposer({}), 494 + size: 'small', 495 + color: 'primary', 496 + }} 497 + emptyStateIcon={VideoIcon} 450 498 /> 451 499 ) 452 500 : null} ··· 460 508 scrollElRef={scrollElRef as ListRef} 461 509 ignoreFilterFor={profile.did} 462 510 setScrollViewTag={setScrollViewTag} 511 + emptyStateMessage={_(msg`No likes yet`)} 512 + emptyStateIcon={HeartIcon} 463 513 /> 464 514 ) 465 515 : null} ··· 485 535 headerOffset={headerHeight} 486 536 enabled={isFocused} 487 537 setScrollViewTag={setScrollViewTag} 538 + emptyStateMessage={_( 539 + msg`Starter packs let you share your favorite feeds and people with your friends.`, 540 + )} 541 + emptyStateButton={{ 542 + label: _(msg`Create a Starter Pack`), 543 + text: _(msg`Create a Starter Pack`), 544 + onPress: wrappedNavToWizard, 545 + color: 'primary', 546 + size: 'small', 547 + }} 548 + emptyStateIcon={CircleAndSquareIcon} 488 549 /> 489 550 ) 490 551 : null}