diff --git a/assets/icons/pin_filled_stroke2_corner0_rounded.svg b/assets/icons/pin_filled_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..a2e71b967c
--- /dev/null
+++ b/assets/icons/pin_filled_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 0cfe09aadc..cb97a7065b 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -60,6 +60,7 @@ export function createThemes({
dim: Theme
} {
const color = {
+ like: '#ec4899',
trueBlack: '#000000',
gray_0: `hsl(${hues.primary}, 20%, ${defaultScale[14]}%)`,
@@ -124,6 +125,7 @@ export function createThemes({
const lightPalette = {
white: color.gray_0,
black: color.gray_1000,
+ like: color.like,
contrast_25: color.gray_25,
contrast_50: color.gray_50,
@@ -185,6 +187,7 @@ export function createThemes({
const darkPalette: Palette = {
white: color.gray_25,
black: color.trueBlack,
+ like: color.like,
contrast_25: color.gray_975,
contrast_50: color.gray_950,
@@ -246,6 +249,7 @@ export function createThemes({
const dimPalette: Palette = {
...darkPalette,
black: `hsl(${hues.primary}, 28%, ${dimScale[0]}%)`,
+ like: color.like,
contrast_25: `hsl(${hues.primary}, 28%, ${dimScale[1]}%)`,
contrast_50: `hsl(${hues.primary}, 28%, ${dimScale[2]}%)`,
diff --git a/src/alf/types.ts b/src/alf/types.ts
index 08ec593927..5bac690e2b 100644
--- a/src/alf/types.ts
+++ b/src/alf/types.ts
@@ -12,6 +12,7 @@ export type ThemeName = 'light' | 'dim' | 'dark'
export type Palette = {
white: string
black: string
+ like: string
contrast_25: string
contrast_50: string
diff --git a/src/components/icons/Pin.tsx b/src/components/icons/Pin.tsx
index 03dbbac90e..d1c37f39a3 100644
--- a/src/components/icons/Pin.tsx
+++ b/src/components/icons/Pin.tsx
@@ -3,3 +3,7 @@ import {createSinglePathSVG} from './TEMPLATE'
export const Pin_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M6.5 3a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v3.997a6.25 6.25 0 0 0 1.83 4.42l.377.376A1 1 0 0 1 20 12.5V15a1 1 0 0 1-1 1h-6v5a1 1 0 1 1-2 0v-5H5a1 1 0 0 1-1-1v-2.5a1 1 0 0 1 .293-.707l.376-.377A6.25 6.25 0 0 0 6.5 6.996V3.001Zm2 1v2.997a8.25 8.25 0 0 1-2.416 5.834L6 12.914V14h12v-1.086l-.084-.083A8.25 8.25 0 0 1 15.5 6.997V4h-7Z',
})
+
+export const Pin_Filled_Corner0_Rounded = createSinglePathSVG({
+ path: 'M7.5 2a1 1 0 0 0-1 1v3.997a6.25 6.25 0 0 1-1.83 4.42l-.377.376A1 1 0 0 0 4 12.5V15a1 1 0 0 0 1 1h6v5a1 1 0 1 0 2 0v-5h6a1 1 0 0 0 1-1v-2.5a1 1 0 0 0-.293-.707l-.376-.377a6.25 6.25 0 0 1-1.831-4.42V3.001a1 1 0 0 0-1-1h-9Z',
+})
diff --git a/src/screens/Profile/ProfileFeed/index.tsx b/src/screens/Profile/ProfileFeed/index.tsx
index 84e31ed2fb..4ce5af132a 100644
--- a/src/screens/Profile/ProfileFeed/index.tsx
+++ b/src/screens/Profile/ProfileFeed/index.tsx
@@ -1,46 +1,34 @@
import React, {useCallback, useMemo} from 'react'
-import {Pressable, StyleSheet, View, ScrollView} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg, Plural, Trans} from '@lingui/macro'
+import {StyleSheet, View} from 'react-native'
+import {useAnimatedRef} from 'react-native-reanimated'
+import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useIsFocused, useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query'
-import {useAnimatedRef} from 'react-native-reanimated'
-import {HITSLOP_20} from '#/lib/constants'
-import {useHaptics} from '#/lib/haptics'
import {usePalette} from '#/lib/hooks/usePalette'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {ComposeIcon2} from '#/lib/icons'
-import {makeCustomFeedLink} from '#/lib/routes/links'
import {CommonNavigatorParams} from '#/lib/routes/types'
import {NavigationProp} from '#/lib/routes/types'
-import {shareUrl} from '#/lib/sharing'
import {makeRecordUri} from '#/lib/strings/url-helpers'
-import {toShareUrl} from '#/lib/strings/url-helpers'
import {s} from '#/lib/styles'
-import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
import {FeedSourceFeedInfo, useFeedSourceInfoQuery} from '#/state/queries/feed'
-import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {
- useAddSavedFeedsMutation,
usePreferencesQuery,
UsePreferencesQueryResponse,
- useRemoveFeedMutation,
- useUpdateSavedFeedsMutation,
} from '#/state/queries/preferences'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {truncateAndInvalidate} from '#/state/queries/util'
import {useSession} from '#/state/session'
import {useComposerControls} from '#/state/shell/composer'
import {PostFeed} from '#/view/com/posts/PostFeed'
-import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
import {EmptyState} from '#/view/com/util/EmptyState'
import {FAB} from '#/view/com/util/fab/FAB'
import {Button} from '#/view/com/util/forms/Button'
@@ -48,29 +36,8 @@ import {ListRef} from '#/view/com/util/List'
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
import {Text} from '#/view/com/util/text/Text'
-import * as Toast from '#/view/com/util/Toast'
-import {atoms as a, useTheme} from '#/alf'
-import {Button as NewButton, ButtonText} from '#/components/Button'
-import {useRichText} from '#/components/hooks/useRichText'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
-import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
-import {
- Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
- Heart2_Stroke2_Corner0_Rounded as HeartOutline,
-} from '#/components/icons/Heart2'
-import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
-import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
-import * as Layout from '#/components/Layout'
-import {InlineLinkText} from '#/components/Link'
-import * as Menu from '#/components/Menu'
-import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
-import {RichText} from '#/components/RichText'
-
import {ProfileSubpageHeader as NewHeader} from '#/screens/Profile/components/ProfileSubpageHeader'
-
-interface SectionRef {
- scrollToTop: () => void
-}
+import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps
export function ProfileFeedScreen(props: Props) {
@@ -154,282 +121,49 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
}
export function ProfileFeedScreenInner({
- preferences,
feedInfo,
}: {
preferences: UsePreferencesQueryResponse
feedInfo: FeedSourceFeedInfo
}) {
const {_} = useLingui()
- const t = useTheme()
- const {hasSession, currentAccount} = useSession()
- const reportDialogControl = useReportDialogControl()
+ const {hasSession} = useSession()
const {openComposer} = useComposerControls()
- const playHaptic = useHaptics()
const isScreenFocused = useIsFocused()
- const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} =
- useAddSavedFeedsMutation()
- const {mutateAsync: removeFeed, isPending: isRemovePending} =
- useRemoveFeedMutation()
- const {mutateAsync: updateSavedFeeds, isPending: isUpdateFeedPending} =
- useUpdateSavedFeedsMutation()
-
- const isPending =
- isAddSavedFeedPending || isRemovePending || isUpdateFeedPending
- const savedFeedConfig = preferences.savedFeeds.find(
- f => f.value === feedInfo.uri,
- )
- const isSaved = Boolean(savedFeedConfig)
- const isPinned = Boolean(savedFeedConfig?.pinned)
-
useSetTitle(feedInfo?.displayName)
- // event handlers
- //
-
- const onToggleSaved = React.useCallback(async () => {
- try {
- playHaptic()
-
- if (savedFeedConfig) {
- await removeFeed(savedFeedConfig)
- Toast.show(_(msg`Removed from your feeds`))
- } else {
- await addSavedFeeds([
- {
- type: 'feed',
- value: feedInfo.uri,
- pinned: false,
- },
- ])
- Toast.show(_(msg`Saved to your feeds`))
- }
- } catch (err) {
- Toast.show(
- _(
- msg`There was an issue updating your feeds, please check your internet connection and try again.`,
- ),
- 'xmark',
- )
- logger.error('Failed to update feeds', {message: err})
- }
- }, [_, playHaptic, feedInfo, removeFeed, addSavedFeeds, savedFeedConfig])
-
- const onTogglePinned = React.useCallback(async () => {
- try {
- playHaptic()
-
- if (savedFeedConfig) {
- await updateSavedFeeds([
- {
- ...savedFeedConfig,
- pinned: !savedFeedConfig.pinned,
- },
- ])
- } else {
- await addSavedFeeds([
- {
- type: 'feed',
- value: feedInfo.uri,
- pinned: true,
- },
- ])
- }
- } catch (e) {
- Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
- logger.error('Failed to toggle pinned feed', {message: e})
- }
- }, [
- playHaptic,
- feedInfo,
- _,
- savedFeedConfig,
- updateSavedFeeds,
- addSavedFeeds,
- ])
-
- const onPressShare = React.useCallback(() => {
- const url = toShareUrl(feedInfo.route.href)
- shareUrl(url)
- }, [feedInfo])
-
- const onPressReport = React.useCallback(() => {
- reportDialogControl.open()
- }, [reportDialogControl])
-
- const renderHeader = useCallback(() => {
- return (
-
-
-
- {feedInfo && hasSession && (
-
-
- {isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
-
-
- )}
-
-
- {({props, state}) => {
- return (
-
-
-
- )
- }}
-
-
-
-
- {hasSession && (
- <>
-
-
- {isSaved
- ? _(msg`Remove from my feeds`)
- : _(msg`Save to my feeds`)}
-
-
-
-
-
- {_(msg`Report feed`)}
-
-
- >
- )}
-
-
- {_(msg`Share feed`)}
-
-
-
-
-
-
-
-
-
-
- )
- }, [
- _,
- hasSession,
- feedInfo,
- isPinned,
- onTogglePinned,
- onToggleSaved,
- currentAccount?.did,
- isSaved,
- onPressReport,
- onPressShare,
- t,
- isPending,
- ])
-
const feed = `feedgen|${feedInfo.uri}` as FeedDescriptor
- const [hasNew, setHasNew] = React.useState(false)
- const [isScrolledDown, setIsScrolledDown] = React.useState(false)
- const queryClient = useQueryClient()
- const feedFeedback = useFeedFeedback(feed, hasSession)
- const scrollElRef = useAnimatedRef() as ListRef
+ const [hasNew, setHasNew] = React.useState(false)
+ const [isScrolledDown, setIsScrolledDown] = React.useState(false)
+ const queryClient = useQueryClient()
+ const feedFeedback = useFeedFeedback(feed, hasSession)
+ const scrollElRef = useAnimatedRef() as ListRef
- const onScrollToTop = useCallback(() => {
- scrollElRef.current?.scrollToOffset({
- animated: isNative,
- offset: 0, // -headerHeight,
- })
- truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
- setHasNew(false)
- }, [scrollElRef, queryClient, feed, setHasNew])
+ const onScrollToTop = useCallback(() => {
+ scrollElRef.current?.scrollToOffset({
+ animated: isNative,
+ offset: 0, // -headerHeight,
+ })
+ truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
+ setHasNew(false)
+ }, [scrollElRef, queryClient, feed, setHasNew])
- React.useEffect(() => {
- if (!isScreenFocused) {
- return
- }
- return listenSoftReset(onScrollToTop)
- }, [onScrollToTop, isScreenFocused])
+ React.useEffect(() => {
+ if (!isScreenFocused) {
+ return
+ }
+ return listenSoftReset(onScrollToTop)
+ }, [onScrollToTop, isScreenFocused])
- const renderPostsEmpty = useCallback(() => {
- return
- }, [_])
+ const renderPostsEmpty = useCallback(() => {
+ return
+ }, [_])
return (
<>
-
-
-
+
@@ -472,101 +205,6 @@ export function ProfileFeedScreenInner({
)
}
-function AboutSection({
- feedOwnerDid,
- feedRkey,
- feedInfo,
-}: {
- feedOwnerDid: string
- feedRkey: string
- feedInfo: FeedSourceFeedInfo
-}) {
- const t = useTheme()
- const pal = usePalette('default')
- const {_} = useLingui()
- const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
- const {hasSession} = useSession()
- const playHaptic = useHaptics()
- const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation()
- const {mutateAsync: unlikeFeed, isPending: isUnlikePending} =
- useUnlikeMutation()
- const [resolvedRT] = useRichText(feedInfo.description.text || '')
-
- const isLiked = !!likeUri
- const likeCount =
- isLiked && likeUri ? (feedInfo.likeCount || 0) + 1 : feedInfo.likeCount
-
- const onToggleLiked = React.useCallback(async () => {
- try {
- playHaptic()
-
- if (isLiked && likeUri) {
- await unlikeFeed({uri: likeUri})
- setLikeUri('')
- } else {
- const res = await likeFeed({uri: feedInfo.uri, cid: feedInfo.cid})
- setLikeUri(res.uri)
- }
- } catch (err) {
- Toast.show(
- _(
- msg`There was an issue contacting the server, please check your internet connection and try again.`,
- ),
- 'xmark',
- )
- logger.error('Failed to toggle like', {message: err})
- }
- }, [playHaptic, isLiked, likeUri, unlikeFeed, likeFeed, feedInfo, _])
-
- return (
-
-
- {feedInfo.description ? (
-
- ) : (
-
- No description
-
- )}
-
-
-
-
- {isLiked ? (
-
- ) : (
-
- )}
-
- {typeof likeCount === 'number' && (
-
-
-
- )}
-
-
- )
-}
-
const styles = StyleSheet.create({
btn: {
flexDirection: 'row',
diff --git a/src/screens/Profile/components/ProfileSubpageHeader.tsx b/src/screens/Profile/components/ProfileSubpageHeader.tsx
index 7b56405227..14b86cfbe9 100644
--- a/src/screens/Profile/components/ProfileSubpageHeader.tsx
+++ b/src/screens/Profile/components/ProfileSubpageHeader.tsx
@@ -1,125 +1,478 @@
-import {View, Text as RNText} from 'react-native'
-import {useLingui} from '@lingui/react'
-import {msg, plural, Trans} from '@lingui/macro'
+import React from 'react'
+import {View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {AtUri} from '@atproto/api'
+import {msg, Plural, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
-import * as Layout from '#/components/Layout'
-import {atoms as a, useTheme, useBreakpoints, web} from '#/alf'
-import {UserAvatar} from '#/view/com/util/UserAvatar'
-import {Text} from '#/components/Typography'
-import {Pin_Stroke2_Corner0_Rounded as Pin} from '#/components/icons/Pin'
-import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
-import {Button, ButtonIcon} from '#/components/Button'
+import {useHaptics} from '#/lib/haptics'
+import {makeCustomFeedLink} from '#/lib/routes/links'
+import {shareUrl} from '#/lib/sharing'
import {sanitizeHandle} from '#/lib/strings/handles'
+import {toShareUrl} from '#/lib/strings/url-helpers'
+import {logger} from '#/logger'
+import {FeedSourceFeedInfo} from '#/state/queries/feed'
+import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
+import {
+ useAddSavedFeedsMutation,
+ usePreferencesQuery,
+ useRemoveFeedMutation,
+ useUpdateSavedFeedsMutation,
+} from '#/state/queries/preferences'
+import {useSession} from '#/state/session'
+import {formatCount} from '#/view/com/util/numeric/format'
+import * as Toast from '#/view/com/util/Toast'
+import {UserAvatar} from '#/view/com/util/UserAvatar'
+import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {Divider} from '#/components/Divider'
+import {useRichText} from '#/components/hooks/useRichText'
+import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
+import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+import {
+ Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
+ Heart2_Stroke2_Corner0_Rounded as Heart,
+} from '#/components/icons/Heart2'
+import {
+ Pin_Filled_Corner0_Rounded as PinFilled,
+ Pin_Stroke2_Corner0_Rounded as Pin,
+} from '#/components/icons/Pin'
+import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
+import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
+import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
+import * as Layout from '#/components/Layout'
+import {InlineLinkText} from '#/components/Link'
+import {Loader} from '#/components/Loader'
+import * as Menu from '#/components/Menu'
+import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
+import {RichText} from '#/components/RichText'
+import {Text} from '#/components/Typography'
-export function ProfileSubpageHeader({
- title,
- avatar,
- creator,
- likeCount,
-}: {
- title: string
- avatar?: string
- creator: {did: string; handle: string}
- likeCount: number
-}) {
+export function ProfileSubpageHeader({info}: {info: FeedSourceFeedInfo}) {
const t = useTheme()
+ const {_, i18n} = useLingui()
+ const {hasSession} = useSession()
const {gtPhone, gtMobile} = useBreakpoints()
- const {_} = useLingui()
const {top} = useSafeAreaInsets()
+ const infoControl = Dialog.useDialogControl()
+ const playHaptic = useHaptics()
+ const reportDialogControl = useReportDialogControl()
+
+ const {data: preferences} = usePreferencesQuery()
+
+ const [likeUri, setLikeUri] = React.useState(info.likeUri || '')
+ const isLiked = !!likeUri
+ const likeCount =
+ isLiked && likeUri ? (info.likeCount || 0) + 1 : info.likeCount || 0
+
+ const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} =
+ useAddSavedFeedsMutation()
+ const {mutateAsync: removeFeed, isPending: isRemovePending} =
+ useRemoveFeedMutation()
+ const {mutateAsync: updateSavedFeeds, isPending: isUpdateFeedPending} =
+ useUpdateSavedFeedsMutation()
+
+ const isPending =
+ isAddSavedFeedPending || isRemovePending || isUpdateFeedPending
+ const savedFeedConfig = preferences?.savedFeeds?.find(
+ f => f.value === info.uri,
+ )
+ const isSaved = Boolean(savedFeedConfig)
+ const isPinned = Boolean(savedFeedConfig?.pinned)
+
+ const onToggleSaved = React.useCallback(async () => {
+ try {
+ playHaptic()
+
+ if (savedFeedConfig) {
+ await removeFeed(savedFeedConfig)
+ Toast.show(_(msg`Removed from your feeds`))
+ } else {
+ await addSavedFeeds([
+ {
+ type: 'feed',
+ value: info.uri,
+ pinned: false,
+ },
+ ])
+ Toast.show(_(msg`Saved to your feeds`))
+ }
+ } catch (err) {
+ Toast.show(
+ _(
+ msg`There was an issue updating your feeds, please check your internet connection and try again.`,
+ ),
+ 'xmark',
+ )
+ logger.error('Failed to update feeds', {message: err})
+ }
+ }, [_, playHaptic, info, removeFeed, addSavedFeeds, savedFeedConfig])
+
+ const onTogglePinned = React.useCallback(async () => {
+ try {
+ playHaptic()
+
+ if (savedFeedConfig) {
+ const pinned = !savedFeedConfig.pinned
+ await updateSavedFeeds([
+ {
+ ...savedFeedConfig,
+ pinned,
+ },
+ ])
+
+ if (pinned) {
+ Toast.show(_(msg`Pinned to your home screen`))
+ } else {
+ Toast.show(_(msg`Un-pinned from your home screen`))
+ }
+ } else {
+ await addSavedFeeds([
+ {
+ type: 'feed',
+ value: info.uri,
+ pinned: true,
+ },
+ ])
+ Toast.show(_(msg`Pinned to your home screen`))
+ }
+ } catch (e) {
+ Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
+ logger.error('Failed to toggle pinned feed', {message: e})
+ }
+ }, [playHaptic, info, _, savedFeedConfig, updateSavedFeeds, addSavedFeeds])
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {info.avatar && (
+
+ )}
+
+
+ {/* Should roughly match Layout.Header.TitleText */}
+
+ {info.displayName}
+
+ {/* Should roughly match Layout.Header.SubtitleText */}
+
+
+ {sanitizeHandle(info.creatorHandle, '@')}
+
+
+
+
+ {formatCount(i18n, likeCount)}
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+ {hasSession && (
+
+
+
+ {({props}) => {
+ return (
+
+ {isPinned ? (
+
+ ) : (
+
+ )}
+
+ )
+ }}
+
+
+
+
+
+ {isPinned ? _(msg`Unpin from home`) : _(msg`Pin to home`)}
+
+
+
+
+
+ {isSaved
+ ? _(msg`Remove from my feeds`)
+ : _(msg`Save to my feeds`)}
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+function DialogInner({
+ info,
+ likeUri,
+ setLikeUri,
+ likeCount,
+ reportDialogControl,
+}: {
+ info: FeedSourceFeedInfo
+ likeUri: string
+ setLikeUri: (uri: string) => void
+ likeCount: number
+ reportDialogControl: Dialog.DialogOuterProps['control']
+}) {
+ const t = useTheme()
+ const {_} = useLingui()
+ const {hasSession} = useSession()
+ const playHaptic = useHaptics()
+ const [rt, loading] = useRichText(info.description.text)
+ const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation()
+ const {mutateAsync: unlikeFeed, isPending: isUnlikePending} =
+ useUnlikeMutation()
+
+ const isLiked = !!likeUri
+ const feedRkey = React.useMemo(() => new AtUri(info.uri).rkey, [info.uri])
+
+ const onToggleLiked = React.useCallback(async () => {
+ try {
+ playHaptic()
+
+ if (isLiked && likeUri) {
+ await unlikeFeed({uri: likeUri})
+ setLikeUri('')
+ } else {
+ const res = await likeFeed({uri: info.uri, cid: info.cid})
+ setLikeUri(res.uri)
+ }
+ } catch (err) {
+ Toast.show(
+ _(
+ msg`There was an issue contacting the server, please check your internet connection and try again.`,
+ ),
+ 'xmark',
+ )
+ logger.error('Failed to toggle like', {message: err})
+ }
+ }, [playHaptic, isLiked, likeUri, unlikeFeed, setLikeUri, likeFeed, info, _])
+
+ const onPressShare = React.useCallback(() => {
+ const url = toShareUrl(info.route.href)
+ shareUrl(url)
+ }, [info])
+
+ const onPressReport = React.useCallback(() => {
+ reportDialogControl.open()
+ }, [reportDialogControl])
+
+ return loading ? (
+
+ ) : (
+
+
+
+
+
+
+ {info.displayName}
+
+
+ By {sanitizeHandle(info.creatorHandle, '@')}
+
+
+
+
+
+
+
+
+
+
+
+
+ {isLiked ? (
+
+ ) : (
+
+ )}
+
+ {typeof likeCount === 'number' && (
+
+
+
+ )}
+
+
+
+
+
+
+
+ Something wrong? Let us know.
+
+
+
+
+ Report feed
+
+
+
+
+
+
)
}