about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-09-04 19:36:23 -0500
committerGitHub <noreply@github.com>2025-09-04 19:36:23 -0500
commitc129108b786a3389181c401b0bdfe1a3de528ebb (patch)
treee075d0c41c8829d9c0b66464ba7cb86dd105be4e /src
parent0b480bdaf862b0f93ed480589f81433bd6c93126 (diff)
downloadvoidsky-c129108b786a3389181c401b0bdfe1a3de528ebb.tar.zst
108 fixes (#8977)
* Translation comment

* Fix error handling in starter pack generation

* Allow access to DM settings for age restricted users

* Leave post stat unit formatting up to translators
Diffstat (limited to 'src')
-rw-r--r--src/components/PostControls/RepostButton.tsx9
-rw-r--r--src/components/PostControls/index.tsx11
-rw-r--r--src/components/PostControls/util.ts58
-rw-r--r--src/components/StarterPack/ProfileStarterPacks.tsx2
-rw-r--r--src/components/ageAssurance/AgeRestrictedScreen.tsx6
-rw-r--r--src/components/dialogs/nuxs/BookmarksAnnouncement.tsx6
-rw-r--r--src/lib/custom-animations/CountWheel.tsx14
-rw-r--r--src/lib/custom-animations/CountWheel.web.tsx8
-rw-r--r--src/lib/notifications/notifications.ts3
-rw-r--r--src/screens/Messages/ChatList.tsx13
-rw-r--r--src/screens/Messages/Settings.tsx13
-rw-r--r--src/screens/PostThread/components/ThreadItemAnchor.tsx13
12 files changed, 89 insertions, 67 deletions
diff --git a/src/components/PostControls/RepostButton.tsx b/src/components/PostControls/RepostButton.tsx
index 522e80dd3..d4a3960a7 100644
--- a/src/components/PostControls/RepostButton.tsx
+++ b/src/components/PostControls/RepostButton.tsx
@@ -10,7 +10,7 @@ import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
 import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote'
 import {Repost_Stroke2_Corner3_Rounded as Repost} from '#/components/icons/Repost'
-import {formatPostStatCount} from '#/components/PostControls/util'
+import {useFormatPostStatCount} from '#/components/PostControls/util'
 import {Text} from '#/components/Typography'
 import {
   PostControlButton,
@@ -25,7 +25,6 @@ interface Props {
   onQuote: () => void
   big?: boolean
   embeddingDisabled: boolean
-  compactCount?: boolean
 }
 
 let RepostButton = ({
@@ -35,12 +34,12 @@ let RepostButton = ({
   onQuote,
   big,
   embeddingDisabled,
-  compactCount,
 }: Props): React.ReactNode => {
   const t = useTheme()
-  const {_, i18n} = useLingui()
+  const {_} = useLingui()
   const requireAuth = useRequireAuth()
   const dialogControl = Dialog.useDialogControl()
+  const formatPostStatCount = useFormatPostStatCount()
 
   const onPress = () => requireAuth(() => dialogControl.open())
 
@@ -88,7 +87,7 @@ let RepostButton = ({
         <PostControlButtonIcon icon={Repost} />
         {typeof repostCount !== 'undefined' && repostCount > 0 && (
           <PostControlButtonText testID="repostCount">
-            {formatPostStatCount(i18n, repostCount, {compact: compactCount})}
+            {formatPostStatCount(repostCount)}
           </PostControlButtonText>
         )}
       </PostControlButton>
diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx
index 834ad8e7d..f91bcd8a5 100644
--- a/src/components/PostControls/index.tsx
+++ b/src/components/PostControls/index.tsx
@@ -27,7 +27,7 @@ import {
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a, flatten, useBreakpoints} from '#/alf'
 import {Reply as Bubble} from '#/components/icons/Reply'
-import {formatPostStatCount} from '#/components/PostControls/util'
+import {useFormatPostStatCount} from '#/components/PostControls/util'
 import {BookmarkButton} from './BookmarkButton'
 import {
   PostControlButton,
@@ -69,7 +69,7 @@ let PostControls = ({
   viaRepost?: {uri: string; cid: string}
   variant?: 'compact' | 'normal' | 'large'
 }): React.ReactNode => {
-  const {_, i18n} = useLingui()
+  const {_} = useLingui()
   const {openComposer} = useOpenComposer()
   const {feedDescriptor} = useFeedFeedbackContext()
   const [queueLike, queueUnlike] = usePostLikeMutationQueue(
@@ -95,6 +95,7 @@ let PostControls = ({
   )
   const replyDisabled = post.viewer?.replyDisabled
   const {gtPhone} = useBreakpoints()
+  const formatPostStatCount = useFormatPostStatCount()
 
   const [hasLikeIconBeenToggled, setHasLikeIconBeenToggled] = useState(false)
 
@@ -232,9 +233,7 @@ let PostControls = ({
             <PostControlButtonIcon icon={Bubble} />
             {typeof post.replyCount !== 'undefined' && post.replyCount > 0 && (
               <PostControlButtonText>
-                {formatPostStatCount(i18n, post.replyCount, {
-                  compact: variant === 'compact',
-                })}
+                {formatPostStatCount(post.replyCount)}
               </PostControlButtonText>
             )}
           </PostControlButton>
@@ -247,7 +246,6 @@ let PostControls = ({
             onQuote={onQuote}
             big={big}
             embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)}
-            compactCount={variant === 'compact'}
           />
         </View>
         <View style={[a.flex_1, a.align_start]}>
@@ -288,7 +286,6 @@ let PostControls = ({
               big={big}
               isLiked={Boolean(post.viewer?.like)}
               hasBeenToggled={hasLikeIconBeenToggled}
-              compactCount={variant === 'compact'}
             />
           </PostControlButton>
         </View>
diff --git a/src/components/PostControls/util.ts b/src/components/PostControls/util.ts
index 5d3ea74e4..b8050a85a 100644
--- a/src/components/PostControls/util.ts
+++ b/src/components/PostControls/util.ts
@@ -1,24 +1,48 @@
-import {type I18n} from '@lingui/core'
+import {useCallback} from 'react'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
 /**
  * This matches `formatCount` from `view/com/util/numeric/format.ts`, but has
  * additional truncation logic for large numbers. `roundingMode` should always
  * match the original impl, regardless of if we add more formatting here.
  */
-export function formatPostStatCount(
-  i18n: I18n,
-  count: number,
-  {
-    compact = false,
-  }: {
-    compact?: boolean
-  } = {},
-): string {
-  const isOver10k = count >= 10_000
-  return i18n.number(count, {
-    notation: 'compact',
-    maximumFractionDigits: isOver10k || compact ? 0 : 1,
-    // @ts-expect-error - roundingMode not in the types
-    roundingMode: 'trunc',
-  })
+export function useFormatPostStatCount() {
+  const {i18n} = useLingui()
+
+  return useCallback(
+    (postStatCount: number) => {
+      const isOver1k = postStatCount >= 1_000
+      const isOver10k = postStatCount >= 10_000
+      const isOver1M = postStatCount >= 1_000_000
+      const formatted = i18n.number(postStatCount, {
+        notation: 'compact',
+        maximumFractionDigits: isOver10k ? 0 : 1,
+        // @ts-expect-error - roundingMode not in the types
+        roundingMode: 'trunc',
+      })
+      const count = formatted.replace(/\D+$/g, '')
+
+      if (isOver1M) {
+        return i18n._(
+          msg({
+            message: `${count}M`,
+            comment:
+              'For post statistics. Indicates a number in the millions. Please use the shortest format appropriate for your language.',
+          }),
+        )
+      } else if (isOver1k) {
+        return i18n._(
+          msg({
+            message: `${count}K`,
+            comment:
+              'For post statistics. Indicates a number in the thousands. Please use the shortest format appropriate for your language.',
+          }),
+        )
+      } else {
+        return count
+      }
+    },
+    [i18n],
+  )
 }
diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx
index 73aee28f4..bbe0bc52b 100644
--- a/src/components/StarterPack/ProfileStarterPacks.tsx
+++ b/src/components/StarterPack/ProfileStarterPacks.tsx
@@ -214,7 +214,7 @@ function Empty() {
     onError: e => {
       logger.error('Failed to generate starter pack', {safeMessage: e})
       setIsGenerating(false)
-      if (e.name === 'NOT_ENOUGH_FOLLOWERS') {
+      if (e.message.includes('NOT_ENOUGH_FOLLOWERS')) {
         followersDialogControl.open()
       } else {
         errorDialogControl.open()
diff --git a/src/components/ageAssurance/AgeRestrictedScreen.tsx b/src/components/ageAssurance/AgeRestrictedScreen.tsx
index b47cc5b0c..1430aaaff 100644
--- a/src/components/ageAssurance/AgeRestrictedScreen.tsx
+++ b/src/components/ageAssurance/AgeRestrictedScreen.tsx
@@ -18,10 +18,12 @@ export function AgeRestrictedScreen({
   children,
   screenTitle,
   infoText,
+  rightHeaderSlot,
 }: {
   children: React.ReactNode
   screenTitle?: string
   infoText?: string
+  rightHeaderSlot?: React.ReactNode
 }) {
   const {_} = useLingui()
   const copy = useAgeAssuranceCopy()
@@ -46,12 +48,12 @@ export function AgeRestrictedScreen({
     <Layout.Screen>
       <Layout.Header.Outer>
         <Layout.Header.BackButton />
-        <Layout.Header.Content>
+        <Layout.Header.Content align="left">
           <Layout.Header.TitleText>
             {screenTitle ?? <Trans>Unavailable</Trans>}
           </Layout.Header.TitleText>
         </Layout.Header.Content>
-        <Layout.Header.Slot />
+        {rightHeaderSlot ?? <Layout.Header.Slot />}
       </Layout.Header.Outer>
       <Layout.Content>
         <View style={[a.p_lg]}>
diff --git a/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx b/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx
index c63558334..266e226a6 100644
--- a/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx
+++ b/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx
@@ -117,7 +117,11 @@ export function BookmarksAnnouncement() {
                   },
                 ]}
                 alt={_(
-                  msg`A screenshot of a post with a new button next to the share button that allows you to save the post to your bookmarks. The post is from @jcsalterego.bsky.social and reads "inventing a saturday that immediately follows monday".`,
+                  msg({
+                    message: `A screenshot of a post with a new button next to the share button that allows you to save the post to your bookmarks. The post is from @jcsalterego.bsky.social and reads "inventing a saturday that immediately follows monday".`,
+                    comment:
+                      'Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English.',
+                  }),
                 )}
               />
             </View>
diff --git a/src/lib/custom-animations/CountWheel.tsx b/src/lib/custom-animations/CountWheel.tsx
index 6db22554e..b8763827a 100644
--- a/src/lib/custom-animations/CountWheel.tsx
+++ b/src/lib/custom-animations/CountWheel.tsx
@@ -6,13 +6,12 @@ import Animated, {
   useReducedMotion,
   withTiming,
 } from 'react-native-reanimated'
-import {i18n} from '@lingui/core'
 
 import {decideShouldRoll} from '#/lib/custom-animations/util'
 import {s} from '#/lib/styles'
 import {Text} from '#/view/com/util/text/Text'
 import {atoms as a, useTheme} from '#/alf'
-import {formatPostStatCount} from '#/components/PostControls/util'
+import {useFormatPostStatCount} from '#/components/PostControls/util'
 
 const animationConfig = {
   duration: 400,
@@ -92,13 +91,11 @@ export function CountWheel({
   big,
   isLiked,
   hasBeenToggled,
-  compactCount,
 }: {
   likeCount: number
   big?: boolean
   isLiked: boolean
   hasBeenToggled: boolean
-  compactCount?: boolean
 }) {
   const t = useTheme()
   const shouldAnimate = !useReducedMotion() && hasBeenToggled
@@ -111,12 +108,9 @@ export function CountWheel({
   const [key, setKey] = React.useState(0)
   const [prevCount, setPrevCount] = React.useState(likeCount)
   const prevIsLiked = React.useRef(isLiked)
-  const formattedCount = formatPostStatCount(i18n, likeCount, {
-    compact: compactCount,
-  })
-  const formattedPrevCount = formatPostStatCount(i18n, prevCount, {
-    compact: compactCount,
-  })
+  const formatPostStatCount = useFormatPostStatCount()
+  const formattedCount = formatPostStatCount(likeCount)
+  const formattedPrevCount = formatPostStatCount(prevCount)
 
   React.useEffect(() => {
     if (isLiked === prevIsLiked.current) {
diff --git a/src/lib/custom-animations/CountWheel.web.tsx b/src/lib/custom-animations/CountWheel.web.tsx
index fc2505ead..be48e19d7 100644
--- a/src/lib/custom-animations/CountWheel.web.tsx
+++ b/src/lib/custom-animations/CountWheel.web.tsx
@@ -1,13 +1,12 @@
 import React from 'react'
 import {View} from 'react-native'
 import {useReducedMotion} from 'react-native-reanimated'
-import {i18n} from '@lingui/core'
 
 import {decideShouldRoll} from '#/lib/custom-animations/util'
 import {s} from '#/lib/styles'
-import {formatCount} from '#/view/com/util/numeric/format'
 import {Text} from '#/view/com/util/text/Text'
 import {atoms as a, useTheme} from '#/alf'
+import {useFormatPostStatCount} from '#/components/PostControls/util'
 
 const animationConfig = {
   duration: 400,
@@ -55,8 +54,9 @@ export function CountWheel({
 
   const [prevCount, setPrevCount] = React.useState(likeCount)
   const prevIsLiked = React.useRef(isLiked)
-  const formattedCount = formatCount(i18n, likeCount)
-  const formattedPrevCount = formatCount(i18n, prevCount)
+  const formatPostStatCount = useFormatPostStatCount()
+  const formattedCount = formatPostStatCount(likeCount)
+  const formattedPrevCount = formatPostStatCount(prevCount)
 
   React.useEffect(() => {
     if (isLiked === prevIsLiked.current) {
diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts
index 67a38a52c..03dc4726f 100644
--- a/src/lib/notifications/notifications.ts
+++ b/src/lib/notifications/notifications.ts
@@ -11,6 +11,7 @@ import {isNative} from '#/platform/detection'
 import {useAgeAssuranceContext} from '#/state/ageAssurance'
 import {type SessionAccount, useAgent, useSession} from '#/state/session'
 import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
+import {IS_DEV} from '#/env'
 
 /**
  * @private
@@ -129,7 +130,7 @@ export function useGetAndRegisterPushToken() {
     }: {
       isAgeRestricted?: boolean
     } = {}) => {
-      if (!isNative) return
+      if (!isNative || IS_DEV) return
 
       /**
        * This will also fire the listener added via `addPushTokenListener`. That
diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx
index 345446464..cb8598e79 100644
--- a/src/screens/Messages/ChatList.tsx
+++ b/src/screens/Messages/ChatList.tsx
@@ -74,7 +74,18 @@ export function MessagesScreen(props: Props) {
   return (
     <AgeRestrictedScreen
       screenTitle={_(msg`Chats`)}
-      infoText={aaCopy.chatsInfoText}>
+      infoText={aaCopy.chatsInfoText}
+      rightHeaderSlot={
+        <Link
+          to="/messages/settings"
+          label={_(msg`Chat settings`)}
+          size="small"
+          color="secondary">
+          <ButtonText>
+            <Trans>Chat settings</Trans>
+          </ButtonText>
+        </Link>
+      }>
       <MessagesScreenInner {...props} />
     </AgeRestrictedScreen>
   )
diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx
index 6015c07cd..b4b84aafe 100644
--- a/src/screens/Messages/Settings.tsx
+++ b/src/screens/Messages/Settings.tsx
@@ -12,8 +12,6 @@ import {useSession} from '#/state/session'
 import * as Toast from '#/view/com/util/Toast'
 import {atoms as a} from '#/alf'
 import {Admonition} from '#/components/Admonition'
-import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen'
-import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
 import {Divider} from '#/components/Divider'
 import * as Toggle from '#/components/forms/Toggle'
 import * as Layout from '#/components/Layout'
@@ -25,16 +23,7 @@ type AllowIncoming = 'all' | 'none' | 'following'
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'>
 
 export function MessagesSettingsScreen(props: Props) {
-  const {_} = useLingui()
-  const aaCopy = useAgeAssuranceCopy()
-
-  return (
-    <AgeRestrictedScreen
-      screenTitle={_(msg`Chat Settings`)}
-      infoText={aaCopy.chatsInfoText}>
-      <MessagesSettingsScreenInner {...props} />
-    </AgeRestrictedScreen>
-  )
+  return <MessagesSettingsScreenInner {...props} />
 }
 
 export function MessagesSettingsScreenInner({}: Props) {
diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx
index 08dd272f7..66ef96200 100644
--- a/src/screens/PostThread/components/ThreadItemAnchor.tsx
+++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx
@@ -52,7 +52,7 @@ import {PostAlerts} from '#/components/moderation/PostAlerts'
 import {type AppModerationCause} from '#/components/Pills'
 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
 import {PostControls} from '#/components/PostControls'
-import {formatPostStatCount} from '#/components/PostControls/util'
+import {useFormatPostStatCount} from '#/components/PostControls/util'
 import {ProfileHoverCard} from '#/components/ProfileHoverCard'
 import * as Prompt from '#/components/Prompt'
 import {RichText} from '#/components/RichText'
@@ -176,11 +176,12 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
   postSource?: PostSource
 }) {
   const t = useTheme()
-  const {_, i18n} = useLingui()
+  const {_} = useLingui()
   const {openComposer} = useOpenComposer()
   const {currentAccount, hasSession} = useSession()
   const {gtTablet} = useBreakpoints()
   const feedFeedback = useFeedFeedback(postSource?.feedSourceInfo, hasSession)
+  const formatPostStatCount = useFormatPostStatCount()
 
   const post = postShadow
   const record = item.value.post.record
@@ -439,7 +440,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
                     testID="repostCount-expanded"
                     style={[a.text_md, t.atoms.text_contrast_medium]}>
                     <Text style={[a.text_md, a.font_bold, t.atoms.text]}>
-                      {formatPostStatCount(i18n, post.repostCount)}
+                      {formatPostStatCount(post.repostCount)}
                     </Text>{' '}
                     <Plural
                       value={post.repostCount}
@@ -457,7 +458,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
                     testID="quoteCount-expanded"
                     style={[a.text_md, t.atoms.text_contrast_medium]}>
                     <Text style={[a.text_md, a.font_bold, t.atoms.text]}>
-                      {formatPostStatCount(i18n, post.quoteCount)}
+                      {formatPostStatCount(post.quoteCount)}
                     </Text>{' '}
                     <Plural
                       value={post.quoteCount}
@@ -473,7 +474,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
                     testID="likeCount-expanded"
                     style={[a.text_md, t.atoms.text_contrast_medium]}>
                     <Text style={[a.text_md, a.font_bold, t.atoms.text]}>
-                      {formatPostStatCount(i18n, post.likeCount)}
+                      {formatPostStatCount(post.likeCount)}
                     </Text>{' '}
                     <Plural value={post.likeCount} one="like" other="likes" />
                   </Text>
@@ -485,7 +486,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
                     testID="bookmarkCount-expanded"
                     style={[a.text_md, t.atoms.text_contrast_medium]}>
                     <Text style={[a.text_md, a.font_bold, t.atoms.text]}>
-                      {formatPostStatCount(i18n, post.bookmarkCount)}
+                      {formatPostStatCount(post.bookmarkCount)}
                     </Text>{' '}
                     <Plural
                       value={post.bookmarkCount}