diff --git a/src/App.native.tsx b/src/App.native.tsx index 6402b4a898..c993157049 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -13,6 +13,7 @@ import { import 'view/icons' +import {ThemeProvider as Alf} from '#/alf' import {init as initPersistedState} from '#/state/persisted' import {listenSessionDropped} from './state/events' import {useColorMode} from 'state/shell' @@ -39,6 +40,7 @@ import { import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import * as persisted from '#/state/persisted' import {Splash} from '#/Splash' +import {useColorModeTheme} from '#/alf/util/useColorModeTheme' SplashScreen.preventAutoHideAsync() @@ -46,6 +48,7 @@ function InnerApp() { const colorMode = useColorMode() const {isInitialLoad, currentAccount} = useSession() const {resumeSession} = useSessionApi() + const theme = useColorModeTheme(colorMode) // init useEffect(() => { @@ -60,25 +63,27 @@ function InnerApp() { return ( - - - - - - {/* All components should be within this provider */} - - - - - - - - - - - + + + + + + + {/* All components should be within this provider */} + + + + + + + + + + + + ) } diff --git a/src/alf/components/Button.tsx b/src/alf/components/Button.tsx index 9179f85b92..253bcc9515 100644 --- a/src/alf/components/Button.tsx +++ b/src/alf/components/Button.tsx @@ -96,9 +96,9 @@ const sizeVariants: { } = { small: { pressable: { - py: 'm', - px: 'l', - radius: 'm', + py: 's', + px: 'm', + radius: 'round', }, text: { fontSize: 'm', diff --git a/src/alf/components/Pressable.tsx b/src/alf/components/Pressable.tsx new file mode 100644 index 0000000000..f81a58e7e4 --- /dev/null +++ b/src/alf/components/Pressable.tsx @@ -0,0 +1,3 @@ +import {Pressable as RNPressable} from 'react-native' +import {styled} from '#/alf/system' +export const Pressable = styled(RNPressable, {}) diff --git a/src/alf/index.tsx b/src/alf/index.tsx index fc4e308a9c..a389055231 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -1,4 +1,6 @@ export * from '#/alf/system' +export * from '#/alf/util/platform' export * from '#/alf/components/Box' export * from '#/alf/components/Typography' export * from '#/alf/components/Button' +export * from '#/alf/components/Pressable' diff --git a/src/alf/themes.ts b/src/alf/themes.ts index 55f1904c6c..9749c85104 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -147,6 +147,8 @@ export const light = createTheme({ jcc: (_: boolean) => ({justifyContent: 'center'}), /** Shorthand for `justifyContent: 'space-between'` */ jcb: (_: boolean) => ({justifyContent: 'space-between'}), + /** Shorthand for `justifyContent: 'flex-end'` */ + jce: (_: boolean) => ({justifyContent: 'flex-end'}), /** Shorthand for `position: 'absolute'` */ abs: (_: boolean) => ({position: 'absolute'}), /** Shorthand for `StyleSheet.absoluteFillObject` */ diff --git a/src/alf/util/platform.ts b/src/alf/util/platform.ts index 544f5480b6..5120961527 100644 --- a/src/alf/util/platform.ts +++ b/src/alf/util/platform.ts @@ -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) { return Platform.select({ native: value, diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 8d5c11bdad..7af0171d3a 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -30,6 +30,7 @@ import {isWeb} from '#/platform/detection' import {listenPostCreated} from '#/state/events' import {useSession} from '#/state/session' import {STALE} from '#/state/queries' +import {Box} from '#/alf' const LOADING_ITEM = {_reactKey: '__loading__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -273,14 +274,14 @@ let Feed = ({ const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2) return isFetchingNextPage ? ( - + - - + + ) : shouldRenderEndOfFeed ? ( - {renderEndOfFeed()} + {renderEndOfFeed()} ) : ( - + ) }, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset]) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 20d1997456..5d7a86b7ac 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -13,7 +13,6 @@ import { } from '@fortawesome/react-native-fontawesome' import {ReasonFeedSource, isReasonFeedSource} from 'lib/api/feed/types' import {Link, TextLinkOnWebOnly, TextLink} from '../util/Link' -import {Text} from '../util/text/Text' import {UserInfoText} from '../util/UserInfoText' import {PostMeta} from '../util/PostMeta' import {PostCtrls} from '../util/post-ctrls/PostCtrls' @@ -34,6 +33,7 @@ import {countLines} from 'lib/strings/helpers' import {useComposerControls} from '#/state/shell/composer' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {FeedNameText} from '../util/FeedInfoText' +import {Box, Text, useStyle} from '#/alf' export function FeedItem({ post, @@ -130,18 +130,19 @@ let FeedItemInner = ({ }) }, [post, record, openComposer]) - const outerStyles = [ - styles.outer, - pal.view, - { - borderColor: pal.colors.border, - paddingBottom: - isThreadLastChild || (!isThreadChild && !isThreadParent) - ? 6 - : undefined, - }, - isThreadChild ? styles.outerSmallTop : undefined, - ] + const outerStyles = useStyle(React.useMemo(() => ({ + bg: 'l0', + borderTopWidth: isThreadChild ? 0 : 1, + paddingLeft: 'm', + pr: 'l', + cursor: 'pointer', + overflow: 'hidden', + borderColor: 'l2', + paddingBottom: + isThreadLastChild || (!isThreadChild && !isThreadParent) + ? 's' + : undefined, + }), [])) return ( - - + + {isThreadChild && ( )} - + - + {isReasonFeedSource(reason) ? ( From{' '} Reposted by{' '} ) : null} - - + + - - + + )} - - + + {!isThreadChild && replyAuthorDid !== '' && ( - + Reply to{' '} - + )} - - + + ) } @@ -320,7 +321,7 @@ let PostContent = ({ childContainerStyle={styles.contentHiderChild}> {richText.text ? ( - + - + ) : undefined} {limitLines ? ( { @@ -72,25 +70,25 @@ export function ProfileSubpageHeader({ }, [openLightbox, avatar]) return ( - + {isMobile && ( - + @@ -98,28 +96,28 @@ export function ProfileSubpageHeader({ ) : ( )} - + {children} - + )} - - + {isLoading ? ( ) : ( - + by{' '} {!creator ? ( '—' @@ -162,37 +160,18 @@ export function ProfileSubpageHeader({ )} )} - + {!isMobile && ( - + {children} - + )} - + ) } - -const styles = StyleSheet.create({ - backBtn: { - width: 20, - height: 30, - }, - backBtnWide: { - width: 20, - height: 30, - paddingHorizontal: 6, - }, - backIcon: { - marginTop: 6, - }, -}) diff --git a/src/view/com/util/LoadingPlaceholder.tsx b/src/view/com/util/LoadingPlaceholder.tsx index b7f133774a..a6a3266f75 100644 --- a/src/view/com/util/LoadingPlaceholder.tsx +++ b/src/view/com/util/LoadingPlaceholder.tsx @@ -11,6 +11,7 @@ import {HeartIcon, HeartIconSolid} from 'lib/icons' import {s} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' import {usePalette} from 'lib/hooks/usePalette' +import {Box, useTokens} from '#/alf' export function LoadingPlaceholder({ width, @@ -42,58 +43,57 @@ export function PostLoadingPlaceholder({ }: { style?: StyleProp }) { - const theme = useTheme() - const pal = usePalette('default') + const tokens = useTokens() return ( - - + - - - - - - - + + + + + + + + + - - + + - - + + - - - - - + + + + + ) } export function PostFeedLoadingPlaceholder() { return ( - + @@ -102,7 +102,7 @@ export function PostFeedLoadingPlaceholder() { - + ) } diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index eef7094cd7..840e8e9cd6 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,6 +1,5 @@ import React, {memo} from 'react' -import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -import {Text} from './text/Text' +import {StyleProp, TextStyle, ViewStyle} from 'react-native' import {TextLinkOnWebOnly} from './Link' import {niceDate} from 'lib/strings/time' import {usePalette} from 'lib/hooks/usePalette' @@ -11,6 +10,7 @@ import {sanitizeHandle} from 'lib/strings/handles' import {isAndroid} from 'platform/detection' import {TimeElapsed} from './TimeElapsed' import {makeProfileLink} from 'lib/routes/links' +import {Box, Text, android, notAndroid} from '#/alf' interface PostMetaOpts { author: { @@ -35,17 +35,17 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { const handle = opts.author.handle return ( - + {opts.showAvatar && ( - + - + )} - + { <> {sanitizeDisplayName(displayName)}  + c='l4' + fontSize='m' + fontWeight='normal' + numberOfLines={1}> {sanitizeHandle(handle, '@')} } href={makeProfileLink(opts.author)} /> - + {!isAndroid && ( · @@ -89,26 +88,8 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { /> )} - + ) } PostMeta = memo(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, - }, -}) diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx index 9787d92fb6..90181eadce 100644 --- a/src/view/com/util/fab/FABInner.tsx +++ b/src/view/com/util/fab/FABInner.tsx @@ -1,5 +1,5 @@ import React, {ComponentProps} from 'react' -import {StyleSheet, TouchableWithoutFeedback} from 'react-native' +import {TouchableWithoutFeedback} from 'react-native' import LinearGradient from 'react-native-linear-gradient' import {gradients} from 'lib/styles' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' @@ -8,6 +8,8 @@ import {clamp} from 'lib/numbers' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import Animated from 'react-native-reanimated' +import {useStyles} from '#/alf' + export interface FABProps extends ComponentProps { testID?: string @@ -18,6 +20,26 @@ export function FABInner({testID, icon, ...props}: FABProps) { const insets = useSafeAreaInsets() const {isMobile, isTablet} = useWebMediaQueries() 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(() => { return isTablet ? styles.sizeLarge : styles.sizeRegular @@ -51,24 +73,3 @@ export function FABInner({testID, icon, ...props}: FABProps) { ) } - -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', - }, -}) diff --git a/src/view/com/util/load-latest/LoadLatestBtn.tsx b/src/view/com/util/load-latest/LoadLatestBtn.tsx index 970d3a73af..5c65d1bce0 100644 --- a/src/view/com/util/load-latest/LoadLatestBtn.tsx +++ b/src/view/com/util/load-latest/LoadLatestBtn.tsx @@ -1,7 +1,6 @@ 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 {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {colors} from 'lib/styles' import {HITSLOP_20} from 'lib/constants' @@ -11,6 +10,8 @@ const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity) import {isWeb} from 'platform/detection' +import { useStyles, useTokens } from '#/alf' + export function LoadLatestBtn({ onPress, label, @@ -20,18 +21,53 @@ export function LoadLatestBtn({ label: string showIndicator: boolean }) { - const pal = usePalette('default') const {isDesktop, isTablet, isMobile} = useWebMediaQueries() const {fabMinimalShellTransform} = useMinimalShellMode() + const tokens = useTokens() + const styles = useStyles({ + loadLatest: { + // @ts-ignore 'fixed' is web only -prf + position: isWeb ? 'fixed' : 'absolute', + left: 18, + bottom: 44, + borderWidth: 1, + width: 52, + height: 52, + borderRadius: 26, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderColor: 'l3', + bg: 'l0', + }, + loadLatestTablet: { + // @ts-ignore web only + left: 'calc(50vw - 282px)', + }, + loadLatestDesktop: { + // @ts-ignore web only + left: 'calc(50vw - 382px)', + }, + indicator: { + position: 'absolute', + top: 3, + right: 3, + backgroundColor: colors.blue3, + width: 12, + height: 12, + borderRadius: 6, + borderWidth: 1, + borderColor: 'l3', + }, + }) + return ( - - {showIndicator && } + + {showIndicator && } ) } - -const styles = StyleSheet.create({ - loadLatest: { - // @ts-ignore 'fixed' is web only -prf - position: isWeb ? 'fixed' : 'absolute', - left: 18, - bottom: 44, - borderWidth: 1, - width: 52, - height: 52, - borderRadius: 26, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - }, - loadLatestTablet: { - // @ts-ignore web only - left: 'calc(50vw - 282px)', - }, - loadLatestDesktop: { - // @ts-ignore web only - left: 'calc(50vw - 382px)', - }, - indicator: { - position: 'absolute', - top: 3, - right: 3, - backgroundColor: colors.blue3, - width: 12, - height: 12, - borderRadius: 6, - borderWidth: 1, - }, -}) diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 414fb1e094..ae37347866 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -7,7 +7,6 @@ import { ViewStyle, } from 'react-native' import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' -import {Text} from '../text/Text' import {PostDropdownBtn} from '../forms/PostDropdownBtn' import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons' import {s} from 'lib/styles' @@ -26,6 +25,7 @@ import { import {useComposerControls} from '#/state/shell/composer' import {Shadow} from '#/state/cache/types' import {useRequireAuth} from '#/state/session' +import {Box, Text, Pressable} from '#/alf' let PostCtrls = ({ big, @@ -129,12 +129,12 @@ let PostCtrls = ({ ]) return ( - - + {typeof post.replyCount !== 'undefined' ? ( - + {post.replyCount} ) : undefined} - + - { requireAuth(() => onPressToggleLike()) }} @@ -190,16 +191,15 @@ let PostCtrls = ({ )} {typeof post.likeCount !== 'undefined' ? ( + fontSize='s' + ml='xs' + c={post.viewer?.like ? 'red' : 'l4'} + fontWeight={post.viewer?.like ? 'semi' : 'normal'} + testID="likeCount"> {post.likeCount} ) : undefined} - + {big ? undefined : ( - + ) } PostCtrls = memo(PostCtrls) diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 620852d8e4..4c99773d94 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -3,11 +3,11 @@ import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' import {RepostIcon} from 'lib/icons' import {s, colors} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' -import {Text} from '../text/Text' import {pluralize} from 'lib/strings/helpers' import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {useModalControls} from '#/state/modals' import {useRequireAuth} from '#/state/session' +import {Pressable, Text} from '#/alf' interface Props { isReposted: boolean @@ -45,12 +45,13 @@ let RepostButton = ({ }, [onRepost, onQuote, isReposted, openModal]) return ( - { requireAuth(() => onPressToggleRepostWrapper()) }} - style={[styles.control, !big && styles.controlPad]} accessibilityRole="button" accessibilityLabel={`${ isReposted ? 'Undo repost' : 'Repost' @@ -77,7 +78,7 @@ let RepostButton = ({ {repostCount} ) : undefined} - + ) } RepostButton = memo(RepostButton) diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index cde39a33f4..b077e869e7 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -1,9 +1,8 @@ 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 {useIsFocused, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' -import {usePalette} from 'lib/hooks/usePalette' import {HeartIcon, HeartIconSolid} from 'lib/icons' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 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 {TextLink} from 'view/com/util/Link' 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 {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' 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 {isNative} from '#/platform/detection' +import {Box, Text, Button, Pressable, web, native, useTokens} from '#/alf' + const SECTION_TITLES = ['Posts', 'About'] interface SectionRef { @@ -68,7 +67,6 @@ type Props = NativeStackScreenProps export function ProfileFeedScreen(props: Props) { const {rkey, name: handleOrDid} = props.route.params - const pal = usePalette('default') const {_} = useLingui() const navigation = useNavigation() @@ -89,27 +87,26 @@ export function ProfileFeedScreen(props: Props) { if (error) { return ( - - + + Could not load feed - + {error.toString()} - + - - + + ) } @@ -118,9 +115,9 @@ export function ProfileFeedScreen(props: Props) { ) : ( - + - + ) } @@ -132,9 +129,9 @@ function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) { if (!preferences || !info) { return ( - + - + ) } @@ -155,7 +152,7 @@ export function ProfileFeedScreenInner({ feedInfo: FeedSourceFeedInfo }) { const {_} = useLingui() - const pal = usePalette('default') + const tokens = useTokens() const {hasSession, currentAccount} = useSession() const {openModal} = useModalControls() const {openComposer} = useComposerControls() @@ -335,44 +332,47 @@ export function ProfileFeedScreenInner({ : undefined } avatarType="algo"> - {feedInfo && hasSession && ( - <> - + + + )} + + + + + + + ) }, [ _, hasSession, - pal, feedInfo, isPinned, onTogglePinned, @@ -387,7 +387,7 @@ export function ProfileFeedScreenInner({ ]) return ( - + )} - + ) } @@ -503,7 +503,7 @@ function AboutSection({ scrollElRef: React.MutableRefObject isOwner: boolean }) { - const pal = usePalette('default') + const tokens = useTokens() const {_} = useLingui() const scrollHandlers = useScrollHandlers() const onScroll = useAnimatedScrollHandler(scrollHandlers) @@ -548,52 +548,50 @@ function AboutSection({ paddingTop: headerHeight, minHeight: Dimensions.get('window').height * 1.5, }}> - + {feedInfo.description ? ( ) : ( - + No description )} - - + {typeof likeCount === 'number' && ( )} - - + + Created by{' '} {isOwner ? ( 'you' @@ -604,29 +602,11 @@ function AboutSection({ did: feedInfo.creatorDid, handle: feedInfo.creatorHandle, })} - style={pal.textLight} + style={{ color: tokens.color.l4 }} /> )} - + ) } - -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, - }, -})