This commit is contained in:
Eric Bailey
2023-12-19 17:09:02 -06:00
parent e84d035e86
commit e333a3bc38
16 changed files with 331 additions and 366 deletions
+5
View File
@@ -13,6 +13,7 @@ import {
import 'view/icons' import 'view/icons'
import {ThemeProvider as Alf} from '#/alf'
import {init as initPersistedState} from '#/state/persisted' import {init as initPersistedState} from '#/state/persisted'
import {listenSessionDropped} from './state/events' import {listenSessionDropped} from './state/events'
import {useColorMode} from 'state/shell' import {useColorMode} from 'state/shell'
@@ -39,6 +40,7 @@ import {
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
import {Splash} from '#/Splash' import {Splash} from '#/Splash'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
SplashScreen.preventAutoHideAsync() SplashScreen.preventAutoHideAsync()
@@ -46,6 +48,7 @@ function InnerApp() {
const colorMode = useColorMode() const colorMode = useColorMode()
const {isInitialLoad, currentAccount} = useSession() const {isInitialLoad, currentAccount} = useSession()
const {resumeSession} = useSessionApi() const {resumeSession} = useSessionApi()
const theme = useColorModeTheme(colorMode)
// init // init
useEffect(() => { useEffect(() => {
@@ -60,6 +63,7 @@ function InnerApp() {
return ( return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}> <SafeAreaProvider initialMetrics={initialWindowMetrics}>
<Alf theme={theme}>
<Splash isReady={!isInitialLoad}> <Splash isReady={!isInitialLoad}>
<React.Fragment <React.Fragment
// Resets the entire tree below when it changes: // Resets the entire tree below when it changes:
@@ -79,6 +83,7 @@ function InnerApp() {
</LoggedOutViewProvider> </LoggedOutViewProvider>
</React.Fragment> </React.Fragment>
</Splash> </Splash>
</Alf>
</SafeAreaProvider> </SafeAreaProvider>
) )
} }
+3 -3
View File
@@ -96,9 +96,9 @@ const sizeVariants: {
} = { } = {
small: { small: {
pressable: { pressable: {
py: 'm', py: 's',
px: 'l', px: 'm',
radius: 'm', radius: 'round',
}, },
text: { text: {
fontSize: 'm', fontSize: 'm',
+3
View File
@@ -0,0 +1,3 @@
import {Pressable as RNPressable} from 'react-native'
import {styled} from '#/alf/system'
export const Pressable = styled(RNPressable, {})
+2
View File
@@ -1,4 +1,6 @@
export * from '#/alf/system' export * from '#/alf/system'
export * from '#/alf/util/platform'
export * from '#/alf/components/Box' export * from '#/alf/components/Box'
export * from '#/alf/components/Typography' export * from '#/alf/components/Typography'
export * from '#/alf/components/Button' export * from '#/alf/components/Button'
export * from '#/alf/components/Pressable'
+2
View File
@@ -147,6 +147,8 @@ export const light = createTheme({
jcc: (_: boolean) => ({justifyContent: 'center'}), jcc: (_: boolean) => ({justifyContent: 'center'}),
/** Shorthand for `justifyContent: 'space-between'` */ /** Shorthand for `justifyContent: 'space-between'` */
jcb: (_: boolean) => ({justifyContent: 'space-between'}), jcb: (_: boolean) => ({justifyContent: 'space-between'}),
/** Shorthand for `justifyContent: 'flex-end'` */
jce: (_: boolean) => ({justifyContent: 'flex-end'}),
/** Shorthand for `position: 'absolute'` */ /** Shorthand for `position: 'absolute'` */
abs: (_: boolean) => ({position: 'absolute'}), abs: (_: boolean) => ({position: 'absolute'}),
/** Shorthand for `StyleSheet.absoluteFillObject` */ /** Shorthand for `StyleSheet.absoluteFillObject` */
+7
View File
@@ -18,6 +18,13 @@ export function android(value: any) {
}) })
} }
export function notAndroid(value: any) {
return Platform.select({
ios: value,
web: value,
})
}
export function native(value: any) { export function native(value: any) {
return Platform.select({ return Platform.select({
native: value, native: value,
+6 -5
View File
@@ -30,6 +30,7 @@ import {isWeb} from '#/platform/detection'
import {listenPostCreated} from '#/state/events' import {listenPostCreated} from '#/state/events'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {Box} from '#/alf'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@@ -273,14 +274,14 @@ let Feed = ({
const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2) const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2)
return isFetchingNextPage ? ( return isFetchingNextPage ? (
<View style={[styles.feedFooter]}> <Box pt='xl'>
<ActivityIndicator /> <ActivityIndicator />
<View style={{height: offset}} /> <Box h={offset} />
</View> </Box>
) : shouldRenderEndOfFeed ? ( ) : shouldRenderEndOfFeed ? (
<View style={{minHeight: offset}}>{renderEndOfFeed()}</View> <Box minHeight={offset}>{renderEndOfFeed()}</Box>
) : ( ) : (
<View style={{height: offset}} /> <Box h={offset} />
) )
}, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset]) }, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset])
+36 -35
View File
@@ -13,7 +13,6 @@ import {
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {ReasonFeedSource, isReasonFeedSource} from 'lib/api/feed/types' import {ReasonFeedSource, isReasonFeedSource} from 'lib/api/feed/types'
import {Link, TextLinkOnWebOnly, TextLink} from '../util/Link' import {Link, TextLinkOnWebOnly, TextLink} from '../util/Link'
import {Text} from '../util/text/Text'
import {UserInfoText} from '../util/UserInfoText' import {UserInfoText} from '../util/UserInfoText'
import {PostMeta} from '../util/PostMeta' import {PostMeta} from '../util/PostMeta'
import {PostCtrls} from '../util/post-ctrls/PostCtrls' import {PostCtrls} from '../util/post-ctrls/PostCtrls'
@@ -34,6 +33,7 @@ import {countLines} from 'lib/strings/helpers'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {FeedNameText} from '../util/FeedInfoText' import {FeedNameText} from '../util/FeedInfoText'
import {Box, Text, useStyle} from '#/alf'
export function FeedItem({ export function FeedItem({
post, post,
@@ -130,18 +130,19 @@ let FeedItemInner = ({
}) })
}, [post, record, openComposer]) }, [post, record, openComposer])
const outerStyles = [ const outerStyles = useStyle(React.useMemo(() => ({
styles.outer, bg: 'l0',
pal.view, borderTopWidth: isThreadChild ? 0 : 1,
{ paddingLeft: 'm',
borderColor: pal.colors.border, pr: 'l',
cursor: 'pointer',
overflow: 'hidden',
borderColor: 'l2',
paddingBottom: paddingBottom:
isThreadLastChild || (!isThreadChild && !isThreadParent) isThreadLastChild || (!isThreadChild && !isThreadParent)
? 6 ? 's'
: undefined, : undefined,
}, }), []))
isThreadChild ? styles.outerSmallTop : undefined,
]
return ( return (
<Link <Link
@@ -152,8 +153,8 @@ let FeedItemInner = ({
accessible={false}> accessible={false}>
<PostSandboxWarning /> <PostSandboxWarning />
<View style={{flexDirection: 'row', gap: 10, paddingLeft: 8}}> <Box row gap='s' pl='s'>
<View style={{width: 52}}> <Box width={52}>
{isThreadChild && ( {isThreadChild && (
<View <View
style={[ style={[
@@ -166,15 +167,15 @@ let FeedItemInner = ({
]} ]}
/> />
)} )}
</View> </Box>
<View style={{paddingTop: 12, flexShrink: 1}}> <Box pt='m' flexShrink={1}>
{isReasonFeedSource(reason) ? ( {isReasonFeedSource(reason) ? (
<Link href={reason.href}> <Link href={reason.href}>
<Text <Text
type="sm-bold" fontSize='s'
style={pal.textLight} fontWeight='bold'
lineHeight={1.2} c='l5'
numberOfLines={1}> numberOfLines={1}>
From{' '} From{' '}
<FeedNameText <FeedNameText
@@ -203,9 +204,9 @@ let FeedItemInner = ({
}} }}
/> />
<Text <Text
type="sm-bold" fontSize='s'
style={pal.textLight} fontWeight='bold'
lineHeight={1.2} c='l5'
numberOfLines={1}> numberOfLines={1}>
Reposted by{' '} Reposted by{' '}
<TextLinkOnWebOnly <TextLinkOnWebOnly
@@ -221,11 +222,11 @@ let FeedItemInner = ({
</Text> </Text>
</Link> </Link>
) : null} ) : null}
</View> </Box>
</View> </Box>
<View style={styles.layout}> <Box row gap='m' mt={1}>
<View style={styles.layoutAvi}> <Box pl='s'>
<PreviewableUserAvatar <PreviewableUserAvatar
size={52} size={52}
did={post.author.did} did={post.author.did}
@@ -245,8 +246,8 @@ let FeedItemInner = ({
]} ]}
/> />
)} )}
</View> </Box>
<View style={styles.layoutContent}> <Box flex={1}>
<PostMeta <PostMeta
author={post.author} author={post.author}
authorHasWarning={!!post.author.labels?.length} authorHasWarning={!!post.author.labels?.length}
@@ -254,7 +255,7 @@ let FeedItemInner = ({
postHref={href} postHref={href}
/> />
{!isThreadChild && replyAuthorDid !== '' && ( {!isThreadChild && replyAuthorDid !== '' && (
<View style={[s.flexRow, s.mb2, s.alignCenter]}> <Box row aic mb={2}>
<FontAwesomeIcon <FontAwesomeIcon
icon="reply" icon="reply"
size={9} size={9}
@@ -264,9 +265,9 @@ let FeedItemInner = ({
]} ]}
/> />
<Text <Text
type="md" fontSize='m'
style={[pal.textLight, s.mr2]} c='l4'
lineHeight={1.2} mr={2}
numberOfLines={1}> numberOfLines={1}>
Reply to{' '} Reply to{' '}
<UserInfoText <UserInfoText
@@ -276,7 +277,7 @@ let FeedItemInner = ({
style={[pal.textLight, s.ml2]} style={[pal.textLight, s.ml2]}
/> />
</Text> </Text>
</View> </Box>
)} )}
<PostContent <PostContent
moderation={moderation} moderation={moderation}
@@ -285,8 +286,8 @@ let FeedItemInner = ({
postAuthor={post.author} postAuthor={post.author}
/> />
<PostCtrls post={post} record={record} onPressReply={onPressReply} /> <PostCtrls post={post} record={record} onPressReply={onPressReply} />
</View> </Box>
</View> </Box>
</Link> </Link>
) )
} }
@@ -320,7 +321,7 @@ let PostContent = ({
childContainerStyle={styles.contentHiderChild}> childContainerStyle={styles.contentHiderChild}>
<PostAlerts moderation={moderation.content} style={styles.alert} /> <PostAlerts moderation={moderation.content} style={styles.alert} />
{richText.text ? ( {richText.text ? (
<View style={styles.postTextContainer}> <Box row aic flexWrap='wrap' pb='xs'>
<RichText <RichText
testID="postText" testID="postText"
type="post-text" type="post-text"
@@ -329,7 +330,7 @@ let PostContent = ({
numberOfLines={limitLines ? MAX_POST_LINES : undefined} numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={s.flex1} style={s.flex1}
/> />
</View> </Box>
) : undefined} ) : undefined}
{limitLines ? ( {limitLines ? (
<TextLink <TextLink
+37 -58
View File
@@ -1,10 +1,7 @@
import React from 'react' import React from 'react'
import {Pressable, StyleSheet, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Text} from '../util/text/Text'
import {TextLink} from '../util/Link' import {TextLink} from '../util/Link'
import {UserAvatar, UserAvatarType} from '../util/UserAvatar' import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
@@ -13,13 +10,14 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {BACK_HITSLOP} from 'lib/constants' import {BACK_HITSLOP} from 'lib/constants'
import {isNative} from 'platform/detection'
import {useLightboxControls, ImagesLightbox} from '#/state/lightbox' import {useLightboxControls, ImagesLightbox} from '#/state/lightbox'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerOpen} from '#/state/shell'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import { Box, useTokens, web, Pressable, Text } from '#/alf'
export function ProfileSubpageHeader({ export function ProfileSubpageHeader({
isLoading, isLoading,
href, href,
@@ -48,7 +46,7 @@ export function ProfileSubpageHeader({
const {_} = useLingui() const {_} = useLingui()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openLightbox} = useLightboxControls() const {openLightbox} = useLightboxControls()
const pal = usePalette('default') const tokens = useTokens()
const canGoBack = navigation.canGoBack() const canGoBack = navigation.canGoBack()
const onPressBack = React.useCallback(() => { const onPressBack = React.useCallback(() => {
@@ -72,25 +70,25 @@ export function ProfileSubpageHeader({
}, [openLightbox, avatar]) }, [openLightbox, avatar])
return ( return (
<CenteredView style={pal.view}> <CenteredView style={{ backgroundColor: tokens.color.l0 }}>
{isMobile && ( {isMobile && (
<View <Box
style={[ row aic
{ pt={web('s')}
flexDirection: 'row', pb='s'
alignItems: 'center', px='m'
borderBottomWidth: 1, borderColor='l3'
paddingTop: isNative ? 0 : 8, borderBottomWidth={1}
paddingBottom: 8, gtMobile={{
paddingHorizontal: isMobile ? 12 : 14, px: 14
}, }}>
pal.border,
]}>
<Pressable <Pressable
testID="headerDrawerBtn" testID="headerDrawerBtn"
onPress={canGoBack ? onPressBack : onPressMenu} onPress={canGoBack ? onPressBack : onPressMenu}
hitSlop={BACK_HITSLOP} hitSlop={BACK_HITSLOP}
style={canGoBack ? styles.backBtn : styles.backBtnWide} w={canGoBack ? 20 : 40}
h={30}
px={canGoBack ? 0 : 6}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={canGoBack ? 'Back' : 'Menu'} accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
accessibilityHint=""> accessibilityHint="">
@@ -98,28 +96,28 @@ export function ProfileSubpageHeader({
<FontAwesomeIcon <FontAwesomeIcon
size={18} size={18}
icon="angle-left" icon="angle-left"
style={[styles.backIcon, pal.text]} style={{ marginTop: tokens.space.s, color: tokens.color.l5 }}
/> />
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
size={18} size={18}
icon="bars" icon="bars"
style={[styles.backIcon, pal.textLight]} style={{ marginTop: tokens.space.s, color: tokens.color.l5 }}
/> />
)} )}
</Pressable> </Pressable>
<View style={{flex: 1}} /> <Box style={{flex: 1}} />
{children} {children}
</View> </Box>
)} )}
<View <Box
style={{ row
flexDirection: 'row', gap='m'
alignItems: 'flex-start', pt='m'
gap: 10, pb='s'
paddingTop: 14, px='m'
paddingBottom: 6, gtMobile={{
paddingHorizontal: isMobile ? 12 : 14, px: 14
}}> }}>
<Pressable <Pressable
testID="headerAviButton" testID="headerAviButton"
@@ -130,7 +128,7 @@ export function ProfileSubpageHeader({
style={{width: 58}}> style={{width: 58}}>
<UserAvatar type={avatarType} size={58} avatar={avatar} /> <UserAvatar type={avatarType} size={58} avatar={avatar} />
</Pressable> </Pressable>
<View style={{flex: 1}}> <Box flex={1}>
{isLoading ? ( {isLoading ? (
<LoadingPlaceholder <LoadingPlaceholder
width={200} width={200}
@@ -142,7 +140,7 @@ export function ProfileSubpageHeader({
testID="headerTitle" testID="headerTitle"
type="title-xl" type="title-xl"
href={href} href={href}
style={[pal.text, {fontWeight: 'bold'}]} style={{fontWeight: 'bold', color: tokens.color.l7}}
text={title || ''} text={title || ''}
onPress={emitSoftReset} onPress={emitSoftReset}
numberOfLines={4} numberOfLines={4}
@@ -152,7 +150,7 @@ export function ProfileSubpageHeader({
{isLoading ? ( {isLoading ? (
<LoadingPlaceholder width={50} height={8} /> <LoadingPlaceholder width={50} height={8} />
) : ( ) : (
<Text type="xl" style={[pal.textLight]} numberOfLines={1}> <Text fontSize='l' c='l5' numberOfLines={1}>
by{' '} by{' '}
{!creator ? ( {!creator ? (
'—' '—'
@@ -162,37 +160,18 @@ export function ProfileSubpageHeader({
<TextLink <TextLink
text={sanitizeHandle(creator.handle, '@')} text={sanitizeHandle(creator.handle, '@')}
href={makeProfileLink(creator)} href={makeProfileLink(creator)}
style={pal.textLight} style={{ color: tokens.color.l5 }}
/> />
)} )}
</Text> </Text>
)} )}
</View> </Box>
{!isMobile && ( {!isMobile && (
<View <Box row aic>
style={{
flexDirection: 'row',
alignItems: 'center',
}}>
{children} {children}
</View> </Box>
)} )}
</View> </Box>
</CenteredView> </CenteredView>
) )
} }
const styles = StyleSheet.create({
backBtn: {
width: 20,
height: 30,
},
backBtnWide: {
width: 20,
height: 30,
paddingHorizontal: 6,
},
backIcon: {
marginTop: 6,
},
})
+32 -32
View File
@@ -11,6 +11,7 @@ import {HeartIcon, HeartIconSolid} from 'lib/icons'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {Box, useTokens} from '#/alf'
export function LoadingPlaceholder({ export function LoadingPlaceholder({
width, width,
@@ -42,58 +43,57 @@ export function PostLoadingPlaceholder({
}: { }: {
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const theme = useTheme() const tokens = useTokens()
const pal = usePalette('default')
return ( return (
<View style={[styles.post, pal.view, style]}> <Box bg='l0' row px='m' pt='xl' pb='xs'>
<LoadingPlaceholder <Box
bg='l1'
width={52} width={52}
height={52} height={52}
style={[ radius='round'
styles.avatar, mr='m'
{ ml='s'
position: 'relative', top={-6}
top: -6,
},
]}
/> />
<View style={[s.flex1]}> <Box flex={1}>
<LoadingPlaceholder width={100} height={6} style={{marginBottom: 10}} /> <Box gap='s'>
<LoadingPlaceholder width="95%" height={6} style={{marginBottom: 8}} /> <Box bg='l1' width={100} height={6} radius='s' />
<LoadingPlaceholder width="95%" height={6} style={{marginBottom: 8}} /> <Box bg='l1' width="95%" height={6} radius='s' />
<LoadingPlaceholder width="80%" height={6} style={{marginBottom: 15}} /> <Box bg='l1' width="95%" height={6} radius='s' />
<View style={s.flexRow}> <Box bg='l1' width="80%" height={6} radius='s' mb='s' />
<View style={s.flex1}> </Box>
<Box row>
<Box column>
<FontAwesomeIcon <FontAwesomeIcon
style={{color: theme.palette.default.icon}} style={{color: tokens.color.l2}}
icon={['far', 'comment']} icon={['far', 'comment']}
size={14} size={14}
/> />
</View> </Box>
<View style={s.flex1}> <Box column>
<FontAwesomeIcon <FontAwesomeIcon
style={{color: theme.palette.default.icon}} style={{color: tokens.color.l2}}
icon="retweet" icon="retweet"
size={18} size={18}
/> />
</View> </Box>
<View style={s.flex1}> <Box column>
<HeartIcon <HeartIcon
style={{color: theme.palette.default.icon} as ViewStyle} style={{color: tokens.color.l2}}
size={17} size={17}
strokeWidth={1.7} strokeWidth={1.7}
/> />
</View> </Box>
<View style={s.flex1} /> <Box column />
</View> </Box>
</View> </Box>
</View> </Box>
) )
} }
export function PostFeedLoadingPlaceholder() { export function PostFeedLoadingPlaceholder() {
return ( return (
<View> <Box>
<PostLoadingPlaceholder /> <PostLoadingPlaceholder />
<PostLoadingPlaceholder /> <PostLoadingPlaceholder />
<PostLoadingPlaceholder /> <PostLoadingPlaceholder />
@@ -102,7 +102,7 @@ export function PostFeedLoadingPlaceholder() {
<PostLoadingPlaceholder /> <PostLoadingPlaceholder />
<PostLoadingPlaceholder /> <PostLoadingPlaceholder />
<PostLoadingPlaceholder /> <PostLoadingPlaceholder />
</View> </Box>
) )
} }
+13 -32
View File
@@ -1,6 +1,5 @@
import React, {memo} from 'react' import React, {memo} from 'react'
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' import {StyleProp, TextStyle, ViewStyle} from 'react-native'
import {Text} from './text/Text'
import {TextLinkOnWebOnly} from './Link' import {TextLinkOnWebOnly} from './Link'
import {niceDate} from 'lib/strings/time' import {niceDate} from 'lib/strings/time'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@@ -11,6 +10,7 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {isAndroid} from 'platform/detection' import {isAndroid} from 'platform/detection'
import {TimeElapsed} from './TimeElapsed' import {TimeElapsed} from './TimeElapsed'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {Box, Text, android, notAndroid} from '#/alf'
interface PostMetaOpts { interface PostMetaOpts {
author: { author: {
@@ -35,17 +35,17 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
const handle = opts.author.handle const handle = opts.author.handle
return ( return (
<View style={[styles.container, opts.style]}> <Box row aic pb={2} gap='xs' zIndex={1} style={[opts.style]}>
{opts.showAvatar && ( {opts.showAvatar && (
<View style={styles.avatar}> <Box alignSelf='center'>
<UserAvatar <UserAvatar
avatar={opts.author.avatar} avatar={opts.author.avatar}
size={opts.avatarSize || 16} size={opts.avatarSize || 16}
// TODO moderation // TODO moderation
/> />
</View> </Box>
)} )}
<View style={styles.maxWidth}> <Box flex={android(1)} maxWidth={notAndroid('80%')}>
<TextLinkOnWebOnly <TextLinkOnWebOnly
type={opts.displayNameType || 'lg-bold'} type={opts.displayNameType || 'lg-bold'}
style={[pal.text, opts.displayNameStyle]} style={[pal.text, opts.displayNameStyle]}
@@ -55,22 +55,21 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
<> <>
{sanitizeDisplayName(displayName)}&nbsp; {sanitizeDisplayName(displayName)}&nbsp;
<Text <Text
type="md" c='l4'
numberOfLines={1} fontSize='m'
lineHeight={1.2} fontWeight='normal'
style={pal.textLight}> numberOfLines={1}>
{sanitizeHandle(handle, '@')} {sanitizeHandle(handle, '@')}
</Text> </Text>
</> </>
} }
href={makeProfileLink(opts.author)} href={makeProfileLink(opts.author)}
/> />
</View> </Box>
{!isAndroid && ( {!isAndroid && (
<Text <Text
type="md" fontSize='m'
style={pal.textLight} style={pal.textLight}
lineHeight={1.2}
accessible={false}> accessible={false}>
&middot; &middot;
</Text> </Text>
@@ -89,26 +88,8 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
/> />
)} )}
</TimeElapsed> </TimeElapsed>
</View> </Box>
) )
} }
PostMeta = memo(PostMeta) PostMeta = memo(PostMeta)
export {PostMeta} export {PostMeta}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 2,
gap: 4,
zIndex: 1,
flex: 1,
},
avatar: {
alignSelf: 'center',
},
maxWidth: {
flex: isAndroid ? 1 : undefined,
maxWidth: !isAndroid ? '80%' : undefined,
},
})
+23 -22
View File
@@ -1,5 +1,5 @@
import React, {ComponentProps} from 'react' import React, {ComponentProps} from 'react'
import {StyleSheet, TouchableWithoutFeedback} from 'react-native' import {TouchableWithoutFeedback} from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {gradients} from 'lib/styles' import {gradients} from 'lib/styles'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -8,6 +8,8 @@ import {clamp} from 'lib/numbers'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import {useStyles} from '#/alf'
export interface FABProps export interface FABProps
extends ComponentProps<typeof TouchableWithoutFeedback> { extends ComponentProps<typeof TouchableWithoutFeedback> {
testID?: string testID?: string
@@ -18,6 +20,26 @@ export function FABInner({testID, icon, ...props}: FABProps) {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const {isMobile, isTablet} = useWebMediaQueries() const {isMobile, isTablet} = useWebMediaQueries()
const {fabMinimalShellTransform} = useMinimalShellMode() const {fabMinimalShellTransform} = useMinimalShellMode()
const styles = useStyles(React.useMemo(() => ({
sizeRegular: {
width: 60,
height: 60,
radius: 'round',
},
sizeLarge: {
width: 70,
height: 70,
radius: 'round',
},
outer: {
position: 'absolute',
zIndex: 1,
},
inner: {
justifyContent: 'center',
alignItems: 'center',
},
}), []))
const size = React.useMemo(() => { const size = React.useMemo(() => {
return isTablet ? styles.sizeLarge : styles.sizeRegular return isTablet ? styles.sizeLarge : styles.sizeRegular
@@ -51,24 +73,3 @@ export function FABInner({testID, icon, ...props}: FABProps) {
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
) )
} }
const styles = StyleSheet.create({
sizeRegular: {
width: 60,
height: 60,
borderRadius: 30,
},
sizeLarge: {
width: 70,
height: 70,
borderRadius: 35,
},
outer: {
position: 'absolute',
zIndex: 1,
},
inner: {
justifyContent: 'center',
alignItems: 'center',
},
})
+27 -25
View File
@@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {colors} from 'lib/styles' import {colors} from 'lib/styles'
import {HITSLOP_20} from 'lib/constants' import {HITSLOP_20} from 'lib/constants'
@@ -11,6 +10,8 @@ const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity) Animated.createAnimatedComponent(TouchableOpacity)
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import { useStyles, useTokens } from '#/alf'
export function LoadLatestBtn({ export function LoadLatestBtn({
onPress, onPress,
label, label,
@@ -20,32 +21,11 @@ export function LoadLatestBtn({
label: string label: string
showIndicator: boolean showIndicator: boolean
}) { }) {
const pal = usePalette('default')
const {isDesktop, isTablet, isMobile} = useWebMediaQueries() const {isDesktop, isTablet, isMobile} = useWebMediaQueries()
const {fabMinimalShellTransform} = useMinimalShellMode() const {fabMinimalShellTransform} = useMinimalShellMode()
return ( const tokens = useTokens()
<AnimatedTouchableOpacity const styles = useStyles({
style={[
styles.loadLatest,
isDesktop && styles.loadLatestDesktop,
isTablet && styles.loadLatestTablet,
pal.borderDark,
pal.view,
isMobile && fabMinimalShellTransform,
]}
onPress={onPress}
hitSlop={HITSLOP_20}
accessibilityRole="button"
accessibilityLabel={label}
accessibilityHint="">
<FontAwesomeIcon icon="angle-up" color={pal.colors.text} size={19} />
{showIndicator && <View style={[styles.indicator, pal.borderDark]} />}
</AnimatedTouchableOpacity>
)
}
const styles = StyleSheet.create({
loadLatest: { loadLatest: {
// @ts-ignore 'fixed' is web only -prf // @ts-ignore 'fixed' is web only -prf
position: isWeb ? 'fixed' : 'absolute', position: isWeb ? 'fixed' : 'absolute',
@@ -58,6 +38,8 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
borderColor: 'l3',
bg: 'l0',
}, },
loadLatestTablet: { loadLatestTablet: {
// @ts-ignore web only // @ts-ignore web only
@@ -76,5 +58,25 @@ const styles = StyleSheet.create({
height: 12, height: 12,
borderRadius: 6, borderRadius: 6,
borderWidth: 1, borderWidth: 1,
borderColor: 'l3',
}, },
}) })
return (
<AnimatedTouchableOpacity
style={[
styles.loadLatest,
isDesktop && styles.loadLatestDesktop,
isTablet && styles.loadLatestTablet,
isMobile && fabMinimalShellTransform,
]}
onPress={onPress}
hitSlop={HITSLOP_20}
accessibilityRole="button"
accessibilityLabel={label}
accessibilityHint="">
<FontAwesomeIcon icon="angle-up" color={tokens.color.l7} size={19} />
{showIndicator && <View style={[styles.indicator]} />}
</AnimatedTouchableOpacity>
)
}
+17 -17
View File
@@ -7,7 +7,6 @@ import {
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
import {Text} from '../text/Text'
import {PostDropdownBtn} from '../forms/PostDropdownBtn' import {PostDropdownBtn} from '../forms/PostDropdownBtn'
import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons' import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
import {s} from 'lib/styles' import {s} from 'lib/styles'
@@ -26,6 +25,7 @@ import {
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session' import {useRequireAuth} from '#/state/session'
import {Box, Text, Pressable} from '#/alf'
let PostCtrls = ({ let PostCtrls = ({
big, big,
@@ -129,12 +129,12 @@ let PostCtrls = ({
]) ])
return ( return (
<View style={[styles.ctrls, style]}> <Box row jcb style={[style]}>
<TouchableOpacity <Pressable
testID="replyBtn" testID="replyBtn"
row aic
pa={!big ? 'xs' : 0}
style={[ style={[
styles.ctrl,
!big && styles.ctrlPad,
{paddingLeft: 0}, {paddingLeft: 0},
post.viewer?.replyDisabled ? {opacity: 0.5} : undefined, post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
]} ]}
@@ -155,11 +155,11 @@ let PostCtrls = ({
size={big ? 20 : 15} size={big ? 20 : 15}
/> />
{typeof post.replyCount !== 'undefined' ? ( {typeof post.replyCount !== 'undefined' ? (
<Text style={[defaultCtrlColor, s.ml5, s.f15]}> <Text fontSize='s' ml='xs' c='l4'>
{post.replyCount} {post.replyCount}
</Text> </Text>
) : undefined} ) : undefined}
</TouchableOpacity> </Pressable>
<RepostButton <RepostButton
big={big} big={big}
isReposted={!!post.viewer?.repost} isReposted={!!post.viewer?.repost}
@@ -167,9 +167,10 @@ let PostCtrls = ({
onRepost={onRepost} onRepost={onRepost}
onQuote={onQuote} onQuote={onQuote}
/> />
<TouchableOpacity <Pressable
testID="likeBtn" testID="likeBtn"
style={[styles.ctrl, !big && styles.ctrlPad]} row aic
pa={!big ? 'xs' : 0}
onPress={() => { onPress={() => {
requireAuth(() => onPressToggleLike()) requireAuth(() => onPressToggleLike())
}} }}
@@ -190,16 +191,15 @@ let PostCtrls = ({
)} )}
{typeof post.likeCount !== 'undefined' ? ( {typeof post.likeCount !== 'undefined' ? (
<Text <Text
testID="likeCount" fontSize='s'
style={ ml='xs'
post.viewer?.like c={post.viewer?.like ? 'red' : 'l4'}
? [s.bold, s.likeColor, s.f15, s.ml5] fontWeight={post.viewer?.like ? 'semi' : 'normal'}
: [defaultCtrlColor, s.f15, s.ml5] testID="likeCount">
}>
{post.likeCount} {post.likeCount}
</Text> </Text>
) : undefined} ) : undefined}
</TouchableOpacity> </Pressable>
{big ? undefined : ( {big ? undefined : (
<PostDropdownBtn <PostDropdownBtn
testID="postDropdownBtn" testID="postDropdownBtn"
@@ -212,7 +212,7 @@ let PostCtrls = ({
)} )}
{/* used for adding pad to the right side */} {/* used for adding pad to the right side */}
<View /> <View />
</View> </Box>
) )
} }
PostCtrls = memo(PostCtrls) PostCtrls = memo(PostCtrls)
@@ -3,11 +3,11 @@ import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native'
import {RepostIcon} from 'lib/icons' import {RepostIcon} from 'lib/icons'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {Text} from '../text/Text'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useRequireAuth} from '#/state/session' import {useRequireAuth} from '#/state/session'
import {Pressable, Text} from '#/alf'
interface Props { interface Props {
isReposted: boolean isReposted: boolean
@@ -45,12 +45,13 @@ let RepostButton = ({
}, [onRepost, onQuote, isReposted, openModal]) }, [onRepost, onQuote, isReposted, openModal])
return ( return (
<TouchableOpacity <Pressable
row aic
pa={!big ? 'xs' : 0}
testID="repostBtn" testID="repostBtn"
onPress={() => { onPress={() => {
requireAuth(() => onPressToggleRepostWrapper()) requireAuth(() => onPressToggleRepostWrapper())
}} }}
style={[styles.control, !big && styles.controlPad]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`${ accessibilityLabel={`${
isReposted ? 'Undo repost' : 'Repost' isReposted ? 'Undo repost' : 'Repost'
@@ -77,7 +78,7 @@ let RepostButton = ({
{repostCount} {repostCount}
</Text> </Text>
) : undefined} ) : undefined}
</TouchableOpacity> </Pressable>
) )
} }
RepostButton = memo(RepostButton) RepostButton = memo(RepostButton)
+54 -74
View File
@@ -1,9 +1,8 @@
import React, {useMemo, useCallback} from 'react' import React, {useMemo, useCallback} from 'react'
import {Dimensions, StyleSheet, View, ActivityIndicator} from 'react-native' import {Dimensions, View, ActivityIndicator} from 'react-native'
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useIsFocused, useNavigation} from '@react-navigation/native' import {useIsFocused, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {usePalette} from 'lib/hooks/usePalette'
import {HeartIcon, HeartIconSolid} from 'lib/icons' import {HeartIcon, HeartIconSolid} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {CommonNavigatorParams} from 'lib/routes/types' import {CommonNavigatorParams} from 'lib/routes/types'
@@ -15,8 +14,6 @@ import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
import {Feed} from 'view/com/posts/Feed' import {Feed} from 'view/com/posts/Feed'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List' import {ListRef} from 'view/com/util/List'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
import {RichText} from 'view/com/util/text/RichText' import {RichText} from 'view/com/util/text/RichText'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {FAB} from 'view/com/util/fab/FAB' import {FAB} from 'view/com/util/fab/FAB'
@@ -58,6 +55,8 @@ import {useComposerControls} from '#/state/shell/composer'
import {truncateAndInvalidate} from '#/state/queries/util' import {truncateAndInvalidate} from '#/state/queries/util'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {Box, Text, Button, Pressable, web, native, useTokens} from '#/alf'
const SECTION_TITLES = ['Posts', 'About'] const SECTION_TITLES = ['Posts', 'About']
interface SectionRef { interface SectionRef {
@@ -68,7 +67,6 @@ type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'>
export function ProfileFeedScreen(props: Props) { export function ProfileFeedScreen(props: Props) {
const {rkey, name: handleOrDid} = props.route.params const {rkey, name: handleOrDid} = props.route.params
const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@@ -89,27 +87,26 @@ export function ProfileFeedScreen(props: Props) {
if (error) { if (error) {
return ( return (
<CenteredView> <CenteredView>
<View style={[pal.view, pal.border, styles.notFoundContainer]}> <Box bg='l0' borderColor='l4' ma='m' px='l' py='m' radius='s'>
<Text type="title-lg" style={[pal.text, s.mb10]}> <Text fontSize='l' mb='m'>
<Trans>Could not load feed</Trans> <Trans>Could not load feed</Trans>
</Text> </Text>
<Text type="md" style={[pal.text, s.mb20]}> <Text fontSize='m' mb='xl'>
{error.toString()} {error.toString()}
</Text> </Text>
<View style={{flexDirection: 'row'}}> <Box row>
<Button <Button
type="default"
accessibilityLabel={_(msg`Go Back`)} accessibilityLabel={_(msg`Go Back`)}
accessibilityHint="Return to previous page" accessibilityHint="Return to previous page"
onPress={onPressBack} onPress={onPressBack}
style={{flexShrink: 1}}> style={{flexShrink: 1}}>
<Text type="button" style={pal.text}> <Text c='l0' fontWeight='bold'>
<Trans>Go Back</Trans> <Trans>Go Back</Trans>
</Text> </Text>
</Button> </Button>
</View> </Box>
</View> </Box>
</CenteredView> </CenteredView>
) )
} }
@@ -118,9 +115,9 @@ export function ProfileFeedScreen(props: Props) {
<ProfileFeedScreenIntermediate feedUri={resolvedUri.uri} /> <ProfileFeedScreenIntermediate feedUri={resolvedUri.uri} />
) : ( ) : (
<CenteredView> <CenteredView>
<View style={s.p20}> <Box pa='xl'>
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
</View> </Box>
</CenteredView> </CenteredView>
) )
} }
@@ -132,9 +129,9 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
if (!preferences || !info) { if (!preferences || !info) {
return ( return (
<CenteredView> <CenteredView>
<View style={s.p20}> <Box pa='xl'>
<ActivityIndicator size="large" /> <ActivityIndicator size="large" />
</View> </Box>
</CenteredView> </CenteredView>
) )
} }
@@ -155,7 +152,7 @@ export function ProfileFeedScreenInner({
feedInfo: FeedSourceFeedInfo feedInfo: FeedSourceFeedInfo
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const pal = usePalette('default') const tokens = useTokens()
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
const {openModal} = useModalControls() const {openModal} = useModalControls()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
@@ -335,44 +332,47 @@ export function ProfileFeedScreenInner({
: undefined : undefined
} }
avatarType="algo"> avatarType="algo">
<Box row gap='s' jce>
{feedInfo && hasSession && ( {feedInfo && hasSession && (
<> <>
<Button <Button
size='small'
disabled={isSavePending || isRemovePending} disabled={isSavePending || isRemovePending}
type="default" type="primary"
label={isSaved ? 'Unsave' : 'Save'}
onPress={onToggleSaved} onPress={onToggleSaved}
style={styles.btn} >{isSaved ? 'Unsave' : 'Save'}
/> </Button>
<Button <Button
size='small'
testID={isPinned ? 'unpinBtn' : 'pinBtn'} testID={isPinned ? 'unpinBtn' : 'pinBtn'}
disabled={isPinPending || isUnpinPending} disabled={isPinPending || isUnpinPending}
type={isPinned ? 'default' : 'inverted'} type={isPinned ? 'secondary' : 'primary'}
label={isPinned ? 'Unpin' : 'Pin to home'}
onPress={onTogglePinned} onPress={onTogglePinned}
style={styles.btn} >
/> {isPinned ? 'Unpin' : 'Pin to home'}
</Button>
</> </>
)} )}
<NativeDropdown <NativeDropdown
testID="headerDropdownBtn" testID="headerDropdownBtn"
items={dropdownItems} items={dropdownItems}
accessibilityLabel={_(msg`More options`)} accessibilityLabel={_(msg`More options`)}
accessibilityHint=""> accessibilityHint="">
<View style={[pal.viewLight, styles.btn]}> <Box bg='l1' py='s' px='m' radius='round'>
<FontAwesomeIcon <FontAwesomeIcon
icon="ellipsis" icon="ellipsis"
size={20} size={20}
color={pal.colors.text} color={tokens.color.l4}
/> />
</View> </Box>
</NativeDropdown> </NativeDropdown>
</Box>
</ProfileSubpageHeader> </ProfileSubpageHeader>
) )
}, [ }, [
_, _,
hasSession, hasSession,
pal,
feedInfo, feedInfo,
isPinned, isPinned,
onTogglePinned, onTogglePinned,
@@ -387,7 +387,7 @@ export function ProfileFeedScreenInner({
]) ])
return ( return (
<View style={s.hContentRegion}> <Box h={web('100%')} flex={native(1)}>
<PagerWithHeader <PagerWithHeader
items={SECTION_TITLES} items={SECTION_TITLES}
isHeaderReady={true} isHeaderReady={true}
@@ -431,7 +431,7 @@ export function ProfileFeedScreenInner({
accessibilityHint="" accessibilityHint=""
/> />
)} )}
</View> </Box>
) )
} }
@@ -503,7 +503,7 @@ function AboutSection({
scrollElRef: React.MutableRefObject<ScrollView | null> scrollElRef: React.MutableRefObject<ScrollView | null>
isOwner: boolean isOwner: boolean
}) { }) {
const pal = usePalette('default') const tokens = useTokens()
const {_} = useLingui() const {_} = useLingui()
const scrollHandlers = useScrollHandlers() const scrollHandlers = useScrollHandlers()
const onScroll = useAnimatedScrollHandler(scrollHandlers) const onScroll = useAnimatedScrollHandler(scrollHandlers)
@@ -548,52 +548,50 @@ function AboutSection({
paddingTop: headerHeight, paddingTop: headerHeight,
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}}> }}>
<View <Box
style={[ borderColor='l2'
{ borderTopWidth={1}
borderTopWidth: 1, pa='xl'
paddingVertical: 20, gap='m'
paddingHorizontal: 20, >
gap: 12,
},
pal.border,
]}>
{feedInfo.description ? ( {feedInfo.description ? (
<RichText <RichText
testID="listDescription" testID="listDescription"
type="lg" type="lg"
style={pal.text} style={{ color: tokens.color.l7 }}
richText={feedInfo.description} richText={feedInfo.description}
/> />
) : ( ) : (
<Text type="lg" style={[{fontStyle: 'italic'}, pal.textLight]}> <Text fontSize='m' c='l5' fontStyle='italic'>
<Trans>No description</Trans> <Trans>No description</Trans>
</Text> </Text>
)} )}
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}> <Box row aic gap='m'>
<Button <Pressable
type="default"
testID="toggleLikeBtn" testID="toggleLikeBtn"
accessibilityLabel={_(msg`Like this feed`)} accessibilityLabel={_(msg`Like this feed`)}
accessibilityHint="" accessibilityHint=""
disabled={!hasSession || isLikePending || isUnlikePending} disabled={!hasSession || isLikePending || isUnlikePending}
onPress={onToggleLiked} onPress={onToggleLiked}
style={{paddingHorizontal: 10}}> bg='l1'
pa={10}
radius='round'
>
{isLiked ? ( {isLiked ? (
<HeartIconSolid size={19} style={s.likeColor} /> <HeartIconSolid size={19} style={s.likeColor} />
) : ( ) : (
<HeartIcon strokeWidth={3} size={19} style={pal.textLight} /> <HeartIcon strokeWidth={3} size={19} style={{ color: tokens.color.l4 }} />
)} )}
</Button> </Pressable>
{typeof likeCount === 'number' && ( {typeof likeCount === 'number' && (
<TextLink <TextLink
href={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')} href={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
text={`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`} text={`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`}
style={[pal.textLight, s.semiBold]} style={[{ color: tokens.color.l4, fontWeight: tokens.fontWeight.semi }]}
/> />
)} )}
</View> </Box>
<Text type="md" style={[pal.textLight]} numberOfLines={1}> <Text fontSize='m' c='l5' numberOfLines={1}>
Created by{' '} Created by{' '}
{isOwner ? ( {isOwner ? (
'you' 'you'
@@ -604,29 +602,11 @@ function AboutSection({
did: feedInfo.creatorDid, did: feedInfo.creatorDid,
handle: feedInfo.creatorHandle, handle: feedInfo.creatorHandle,
})} })}
style={pal.textLight} style={{ color: tokens.color.l4 }}
/> />
)} )}
</Text> </Text>
</View> </Box>
</ScrollView> </ScrollView>
) )
} }
const styles = StyleSheet.create({
btn: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
paddingVertical: 7,
paddingHorizontal: 14,
borderRadius: 50,
marginLeft: 6,
},
notFoundContainer: {
margin: 10,
paddingHorizontal: 18,
paddingVertical: 14,
borderRadius: 6,
},
})