diff --git a/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx b/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx
index 8a12f0374d..0c8f30d2b9 100644
--- a/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx
+++ b/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx
@@ -1,11 +1,15 @@
import React from 'react'
-import {ActivityIndicator, GestureResponderEvent, Pressable} from 'react-native'
+import {
+ ActivityIndicator,
+ type GestureResponderEvent,
+ Pressable,
+} from 'react-native'
import {Image} from 'expo-image'
-import {AppBskyEmbedExternal} from '@atproto/api'
+import {type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-import {EmbedPlayerParams} from '#/lib/strings/embed-player'
+import {type EmbedPlayerParams} from '#/lib/strings/embed-player'
import {isIOS, isNative, isWeb} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {atoms as a, useTheme} from '#/alf'
diff --git a/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx b/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx
index 7f6d533408..392cdd8a10 100644
--- a/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx
+++ b/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import {
ActivityIndicator,
- GestureResponderEvent,
+ type GestureResponderEvent,
Pressable,
StyleSheet,
useWindowDimensions,
@@ -16,13 +16,16 @@ import Animated, {
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {WebView} from 'react-native-webview'
import {Image} from 'expo-image'
-import {AppBskyEmbedExternal} from '@atproto/api'
+import {type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
-import {NavigationProp} from '#/lib/routes/types'
-import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
+import {type NavigationProp} from '#/lib/routes/types'
+import {
+ type EmbedPlayerParams,
+ getPlayerAspect,
+} from '#/lib/strings/embed-player'
import {isNative} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {EventStopper} from '#/view/com/util/EventStopper'
diff --git a/src/components/Post/Embed/ExternalEmbed/Gif.tsx b/src/components/Post/Embed/ExternalEmbed/Gif.tsx
index a839294f1c..8e84997314 100644
--- a/src/components/Post/Embed/ExternalEmbed/Gif.tsx
+++ b/src/components/Post/Embed/ExternalEmbed/Gif.tsx
@@ -1,17 +1,17 @@
import React from 'react'
import {
Pressable,
- StyleProp,
+ type StyleProp,
StyleSheet,
TouchableOpacity,
View,
- ViewStyle,
+ type ViewStyle,
} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {HITSLOP_20} from '#/lib/constants'
-import {EmbedPlayerParams} from '#/lib/strings/embed-player'
+import {type EmbedPlayerParams} from '#/lib/strings/embed-player'
import {isWeb} from '#/platform/detection'
import {useAutoplayDisabled} from '#/state/preferences'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
@@ -22,7 +22,7 @@ import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
import {GifView} from '../../../../../modules/expo-bluesky-gif-view'
-import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
+import {type GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
function PlaybackControls({
onPress,
diff --git a/src/components/Post/Embed/ListEmbed.tsx b/src/components/Post/Embed/ListEmbed.tsx
index dc79a75798..82685d2715 100644
--- a/src/components/Post/Embed/ListEmbed.tsx
+++ b/src/components/Post/Embed/ListEmbed.tsx
@@ -6,8 +6,8 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {atoms as a, useTheme} from '#/alf'
import * as ListCard from '#/components/ListCard'
import {ContentHider} from '#/components/moderation/ContentHider'
-import {EmbedType} from '#/types/bsky/post'
-import {CommonProps} from './types'
+import {type EmbedType} from '#/types/bsky/post'
+import {type CommonProps} from './types'
export function ListEmbed({
embed,
diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx
index 95401309f4..67af7618c3 100644
--- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx
+++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/TimeIndicator.tsx
@@ -1,4 +1,4 @@
-import {StyleProp, ViewStyle} from 'react-native'
+import {type StyleProp, type ViewStyle} from 'react-native'
import {View} from 'react-native'
import {msg, plural} from '@lingui/macro'
import {useLingui} from '@lingui/react'
diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx
index 88879d45a7..351e9f3056 100644
--- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx
+++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx
@@ -1,6 +1,6 @@
import React, {useRef} from 'react'
-import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
-import {AppBskyEmbedVideo} from '@atproto/api'
+import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'
+import {type AppBskyEmbedVideo} from '@atproto/api'
import {BlueskyVideoView} from '@haileyok/bluesky-video'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
index 1b46163cce..37b44751d1 100644
--- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
+++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
@@ -1,7 +1,7 @@
-import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import type React from 'react'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
index 1b69a3e253..9b0c963eaf 100644
--- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
+++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
@@ -1,5 +1,5 @@
-import React from 'react'
-import {SvgProps} from 'react-native-svg'
+import {type SvgProps} from 'react-native-svg'
+import type React from 'react'
import {PressableWithHover} from '#/view/com/util/PressableWithHover'
import {atoms as a, useTheme, web} from '#/alf'
diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
index 96960bad47..d84a90fa62 100644
--- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
+++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
@@ -1,7 +1,8 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react'
+import {useCallback, useEffect, useRef, useState} from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import type React from 'react'
import {isFirefox, isTouchDevice} from '#/lib/browser'
import {clamp} from '#/lib/numbers'
diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
index e0b6880757..ec5f23fc07 100644
--- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
+++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
@@ -1,8 +1,9 @@
-import React, {useCallback} from 'react'
+import {useCallback} from 'react'
import {View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import type React from 'react'
import {isSafari, isTouchDevice} from '#/lib/browser'
import {atoms as a} from '#/alf'
diff --git a/src/components/Post/Embed/VideoEmbed/index.tsx b/src/components/Post/Embed/VideoEmbed/index.tsx
index fe29ecad63..8cb78ff70b 100644
--- a/src/components/Post/Embed/VideoEmbed/index.tsx
+++ b/src/components/Post/Embed/VideoEmbed/index.tsx
@@ -1,7 +1,7 @@
import React, {useCallback, useState} from 'react'
import {ActivityIndicator, View} from 'react-native'
import {ImageBackground} from 'expo-image'
-import {AppBskyEmbedVideo} from '@atproto/api'
+import {type AppBskyEmbedVideo} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
diff --git a/src/components/Post/Embed/VideoEmbed/index.web.tsx b/src/components/Post/Embed/VideoEmbed/index.web.tsx
index 53adc3b6aa..7f601af47b 100644
--- a/src/components/Post/Embed/VideoEmbed/index.web.tsx
+++ b/src/components/Post/Embed/VideoEmbed/index.web.tsx
@@ -1,8 +1,9 @@
-import React, {useCallback, useEffect, useRef, useState} from 'react'
+import {useCallback, useEffect, useRef, useState} from 'react'
import {View} from 'react-native'
-import {AppBskyEmbedVideo} from '@atproto/api'
+import {type AppBskyEmbedVideo} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import type React from 'react'
import {isFirefox} from '#/lib/browser'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
diff --git a/src/components/Post/ShowMoreTextButton.tsx b/src/components/Post/ShowMoreTextButton.tsx
new file mode 100644
index 0000000000..bc6db55b9e
--- /dev/null
+++ b/src/components/Post/ShowMoreTextButton.tsx
@@ -0,0 +1,56 @@
+import {useCallback, useMemo} from 'react'
+import {LayoutAnimation, type TextStyle} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {HITSLOP_10} from '#/lib/constants'
+import {atoms as a, flatten, type TextStyleProp, useTheme} from '#/alf'
+import {Button} from '#/components/Button'
+import {Text} from '#/components/Typography'
+
+export function ShowMoreTextButton({
+ onPress: onPressProp,
+ style,
+}: TextStyleProp & {onPress: () => void}) {
+ const t = useTheme()
+ const {_} = useLingui()
+
+ const onPress = useCallback(() => {
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
+ onPressProp()
+ }, [onPressProp])
+
+ const textStyle = useMemo(() => {
+ return flatten([a.leading_snug, a.text_sm, style]) as TextStyle & {
+ fontSize: number
+ lineHeight: number
+ }
+ }, [style])
+
+ return (
+
+ )
+}
diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx
index 9393a6d1bb..4337397f84 100644
--- a/src/screens/PostThread/components/ThreadItemPost.tsx
+++ b/src/screens/PostThread/components/ThreadItemPost.tsx
@@ -6,13 +6,11 @@ import {
AtUri,
RichText as RichTextAPI,
} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
+import {Trans} from '@lingui/macro'
import {useActorStatus} from '#/lib/actor-status'
import {MAX_POST_LINES} from '#/lib/constants'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
-import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links'
import {countLines} from '#/lib/strings/helpers'
import {
@@ -24,7 +22,6 @@ import {type ThreadItem} from '#/state/queries/usePostThread/types'
import {useSession} from '#/state/session'
import {type OnPostSuccessData} from '#/state/shell/composer'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
-import {TextLink} from '#/view/com/util/Link'
import {PostMeta} from '#/view/com/util/PostMeta'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {
@@ -40,6 +37,7 @@ import {PostAlerts} from '#/components/moderation/PostAlerts'
import {PostHider} from '#/components/moderation/PostHider'
import {type AppModerationCause} from '#/components/Pills'
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
+import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton'
import {PostControls} from '#/components/PostControls'
import {RichText} from '#/components/RichText'
import * as Skele from '#/components/Skeleton'
@@ -187,8 +185,6 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
postShadow: Shadow
}) {
const t = useTheme()
- const pal = usePalette('default')
- const {_} = useLingui()
const {openComposer} = useOpenComposer()
const {currentAccount} = useSession()
@@ -304,22 +300,22 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
-
- ) : undefined}
- {limitLines ? (
-
+ <>
+
+ {limitLines && (
+
+ )}
+ >
) : undefined}
{post.embed && (
diff --git a/src/screens/PostThread/components/ThreadItemTreePost.tsx b/src/screens/PostThread/components/ThreadItemTreePost.tsx
index ac659a6e06..a8ffb76f46 100644
--- a/src/screens/PostThread/components/ThreadItemTreePost.tsx
+++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx
@@ -1,4 +1,4 @@
-import React, {memo, useMemo} from 'react'
+import {memo, useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
type AppBskyFeedDefs,
@@ -6,12 +6,10 @@ import {
AtUri,
RichText as RichTextAPI,
} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
+import {Trans} from '@lingui/macro'
import {MAX_POST_LINES} from '#/lib/constants'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
-import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links'
import {countLines} from '#/lib/strings/helpers'
import {
@@ -23,7 +21,6 @@ import {type ThreadItem} from '#/state/queries/usePostThread/types'
import {useSession} from '#/state/session'
import {type OnPostSuccessData} from '#/state/shell/composer'
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
-import {TextLink} from '#/view/com/util/Link'
import {PostMeta} from '#/view/com/util/PostMeta'
import {
OUTER_SPACE,
@@ -39,6 +36,7 @@ import {PostAlerts} from '#/components/moderation/PostAlerts'
import {PostHider} from '#/components/moderation/PostHider'
import {type AppModerationCause} from '#/components/Pills'
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
+import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton'
import {PostControls} from '#/components/PostControls'
import {RichText} from '#/components/RichText'
import * as Skele from '#/components/Skeleton'
@@ -255,8 +253,6 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
onPostSuccess?: (data: OnPostSuccessData) => void
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode {
- const pal = usePalette('default')
- const {_} = useLingui()
const {openComposer} = useOpenComposer()
const {currentAccount} = useSession()
@@ -271,18 +267,18 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
}),
[record],
)
- const [limitLines, setLimitLines] = React.useState(
+ const [limitLines, setLimitLines] = useState(
() => countLines(richText?.text) >= MAX_POST_LINES,
)
const threadRootUri = record.reply?.root?.uri || post.uri
- const postHref = React.useMemo(() => {
+ const postHref = useMemo(() => {
const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey)
}, [post.uri, post.author])
const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({
threadgateRecord,
})
- const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => {
+ const additionalPostAlerts: AppModerationCause[] = useMemo(() => {
const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri)
const isControlledByViewer =
new AtUri(threadRootUri).host === currentAccount?.did
@@ -297,7 +293,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
: []
}, [post, currentAccount?.did, threadgateHiddenReplies, threadRootUri])
- const onPressReply = React.useCallback(() => {
+ const onPressReply = useCallback(() => {
openComposer({
replyTo: {
uri: post.uri,
@@ -311,7 +307,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
})
}, [openComposer, post, record, onPostSuccess, moderation])
- const onPressShowMore = React.useCallback(() => {
+ const onPressShowMore = useCallback(() => {
setLimitLines(false)
}, [setLimitLines])
@@ -348,7 +344,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
-
+ <>
-
- ) : undefined}
- {limitLines ? (
-
+ {limitLines && (
+
+ )}
+ >
) : undefined}
{post.embed && (
diff --git a/src/screens/VideoFeed/components/Scrubber.tsx b/src/screens/VideoFeed/components/Scrubber.tsx
index 29cc4b278a..69e68ec9e3 100644
--- a/src/screens/VideoFeed/components/Scrubber.tsx
+++ b/src/screens/VideoFeed/components/Scrubber.tsx
@@ -3,13 +3,13 @@ import {View} from 'react-native'
import {
Gesture,
GestureDetector,
- NativeGesture,
+ type NativeGesture,
} from 'react-native-gesture-handler'
import Animated, {
interpolate,
runOnJS,
runOnUI,
- SharedValue,
+ type SharedValue,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
@@ -20,7 +20,7 @@ import {
useSafeAreaInsets,
} from 'react-native-safe-area-context'
import {useEventListener} from 'expo'
-import {VideoPlayer} from 'expo-video'
+import {type VideoPlayer} from 'expo-video'
import {tokens} from '#/alf'
import {atoms as a} from '#/alf'
diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx
index 0a460e77b6..85f67919ac 100644
--- a/src/view/com/notifications/NotificationFeedItem.tsx
+++ b/src/view/com/notifications/NotificationFeedItem.tsx
@@ -30,6 +30,7 @@ import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
+import {MAX_POST_LINES} from '#/lib/constants'
import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue'
import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links'
@@ -918,7 +919,8 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
{text?.length > 0 && (
+ style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}
+ numberOfLines={MAX_POST_LINES}>
{text}
)}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 15f5539c9d..592224ff54 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -44,7 +44,7 @@ import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replie
import {type PostSource} from '#/state/unstable-post-source'
import {PostThreadFollowBtn} from '#/view/com/post-thread/PostThreadFollowBtn'
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
-import {Link, TextLink} from '#/view/com/util/Link'
+import {Link} from '#/view/com/util/Link'
import {formatCount} from '#/view/com/util/numeric/format'
import {PostMeta} from '#/view/com/util/PostMeta'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
@@ -62,6 +62,7 @@ import {PostAlerts} from '#/components/moderation/PostAlerts'
import {PostHider} from '#/components/moderation/PostHider'
import {type AppModerationCause} from '#/components/Pills'
import {Embed, PostEmbedViewContext} from '#/components/Post/Embed'
+import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton'
import {PostControls} from '#/components/PostControls'
import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
@@ -685,16 +686,14 @@ let PostThreadItemLoaded = ({
authorHandle={post.author.handle}
shouldProxyLinks={true}
/>
+ {limitLines && (
+
+ )}
) : undefined}
- {limitLines ? (
-
- ) : undefined}
{post.embed && (