diff options
Diffstat (limited to 'src/view')
53 files changed, 451 insertions, 456 deletions
diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index f0dec4b0a..02b7cae5c 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -297,7 +297,7 @@ export const ComposePost = observer(function ComposePost({ ) } }) - }, [text, pal.link]) + }, [text, pal.link, pal.text]) return ( <KeyboardAvoidingView @@ -393,7 +393,7 @@ export const ComposePost = observer(function ComposePost({ ref={textInput} multiline scrollEnabled - onChangeText={(text: string) => onChangeText(text)} + onChangeText={(str: string) => onChangeText(str)} onPaste={onPaste} placeholder={selectTextInputPlaceholder} placeholderTextColor={pal.colors.textLight} @@ -475,7 +475,7 @@ export const ComposePost = observer(function ComposePost({ ) }) -const atPrefixRegex = /@([a-z0-9\.]*)$/i +const atPrefixRegex = /@([a-z0-9.]*)$/i function extractTextAutocompletePrefix(text: string) { const match = atPrefixRegex.exec(text) if (match) { diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index 87886c652..d2afc4b33 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -39,7 +39,7 @@ export const SuggestedFollows = observer( // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment const view = React.useMemo<SuggestedActorsViewModel>( () => new SuggestedActorsViewModel(store), - [], + [store], ) useEffect(() => { @@ -54,7 +54,7 @@ export const SuggestedFollows = observer( if (!view.isLoading && !view.hasError && !view.hasContent) { onNoSuggestions?.() } - }, [view, view.isLoading, view.hasError, view.hasContent]) + }, [view, view.isLoading, view.hasError, view.hasContent, onNoSuggestions]) const onPressTryAgain = () => view @@ -128,7 +128,7 @@ export const SuggestedFollows = observer( keyExtractor={item => item._reactKey} renderItem={renderItem} style={s.flex1} - contentContainerStyle={{paddingBottom: 200}} + contentContainerStyle={s.contentContainer} /> </View> )} diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx index a39ea5e74..f0637db8d 100644 --- a/src/view/com/login/Signin.tsx +++ b/src/view/com/login/Signin.tsx @@ -207,12 +207,7 @@ const ChooseAccountForm = ({ style={[pal.borderDark, styles.group]} onPress={() => onSelectAccount(undefined)}> <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> - <View style={s.p10}> - <View - style={[pal.btn, {width: 30, height: 30, borderRadius: 15}]} - /> - </View> - <Text style={styles.accountText}> + <Text style={[styles.accountText, styles.accountTextOther]}> <Text type="lg" style={pal.text}> Other account </Text> @@ -556,7 +551,7 @@ const ForgotPasswordForm = ({ {!serviceDescription || isProcessing ? ( <ActivityIndicator /> ) : !email ? ( - <Text type="xl-bold" style={[pal.link, s.pr5, {opacity: 0.5}]}> + <Text type="xl-bold" style={[pal.link, s.pr5, styles.dimmed]}> Next </Text> ) : ( @@ -691,7 +686,7 @@ const SetNewPasswordForm = ({ {isProcessing ? ( <ActivityIndicator /> ) : !resetCode || !password ? ( - <Text type="xl-bold" style={[pal.link, s.pr5, {opacity: 0.5}]}> + <Text type="xl-bold" style={[pal.link, s.pr5, styles.dimmed]}> Next </Text> ) : ( @@ -810,6 +805,9 @@ const styles = StyleSheet.create({ alignItems: 'baseline', paddingVertical: 10, }, + accountTextOther: { + paddingLeft: 12, + }, error: { backgroundColor: colors.red4, flexDirection: 'row', @@ -832,4 +830,5 @@ const styles = StyleSheet.create({ justifyContent: 'center', marginRight: 5, }, + dimmed: {opacity: 0.5}, }) diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index f830f39e8..ba99feb32 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -121,7 +121,7 @@ export function Component({ </View> </View> {error !== '' && ( - <View style={{marginTop: 20}}> + <View style={styles.errorContainer}> <ErrorMessage message={error} /> </View> )} @@ -231,4 +231,5 @@ const styles = StyleSheet.create({ marginBottom: 36, marginHorizontal: -14, }, + errorContainer: {marginTop: 20}, }) diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx index 8792d70f1..31ef4b12d 100644 --- a/src/view/com/modals/ServerInput.tsx +++ b/src/view/com/modals/ServerInput.tsx @@ -56,7 +56,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) { </View> <View style={styles.group}> <Text style={styles.label}>Other service</Text> - <View style={{flexDirection: 'row'}}> + <View style={s.flexRow}> <BottomSheetTextInput testID="customServerTextInput" style={styles.textInput} diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx index ea7695d93..5f9cb129d 100644 --- a/src/view/com/notifications/Feed.tsx +++ b/src/view/com/notifications/Feed.tsx @@ -1,12 +1,13 @@ import React from 'react' import {observer} from 'mobx-react-lite' -import {View, FlatList} from 'react-native' +import {FlatList, StyleSheet, View} from 'react-native' import {NotificationsViewModel} from '../../../state/models/notifications-view' import {FeedItem} from './FeedItem' import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {ErrorMessage} from '../util/error/ErrorMessage' import {EmptyState} from '../util/EmptyState' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' +import {s} from '../../lib/styles' const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -29,7 +30,7 @@ export const Feed = observer(function Feed({ <EmptyState icon="bell" message="No notifications yet!" - style={{paddingVertical: 40}} + style={styles.emptyState} /> ) } @@ -58,14 +59,10 @@ export const Feed = observer(function Feed({ } } return ( - <View style={{flex: 1}}> + <View style={s.h100pct}> {view.isLoading && !data && <NotificationFeedLoadingPlaceholder />} {view.hasError && ( - <ErrorMessage - message={view.error} - style={{margin: 6}} - onPressTryAgain={onPressTryAgain} - /> + <ErrorMessage message={view.error} onPressTryAgain={onPressTryAgain} /> )} {data && ( <FlatList @@ -76,9 +73,13 @@ export const Feed = observer(function Feed({ onRefresh={onRefresh} onEndReached={onEndReached} onScroll={onScroll} - contentContainerStyle={{paddingBottom: 200}} + contentContainerStyle={s.contentContainer} /> )} </View> ) }) + +const styles = StyleSheet.create({ + emptyState: {paddingVertical: 40}, +}) diff --git a/src/view/com/onboard/FeatureExplainer.tsx b/src/view/com/onboard/FeatureExplainer.tsx index 78ace5189..03e050883 100644 --- a/src/view/com/onboard/FeatureExplainer.tsx +++ b/src/view/com/onboard/FeatureExplainer.tsx @@ -19,14 +19,13 @@ import {TABS_ENABLED} from '../../../build-flags' const Intro = () => ( <View style={styles.explainer}> <Text - style={[ - styles.explainerHeading, - s.normal, - {lineHeight: 60, paddingTop: 50, paddingBottom: 50}, - ]}> - Welcome to <Text style={[s.bold, s.blue3, {fontSize: 56}]}>Bluesky</Text> + style={[styles.explainerHeading, s.normal, styles.explainerHeadingIntro]}> + Welcome to{' '} + <Text style={[s.bold, s.blue3, styles.explainerHeadingBrand]}> + Bluesky + </Text> </Text> - <Text style={[styles.explainerDesc, {fontSize: 24}]}> + <Text style={[styles.explainerDesc, styles.explainerDescIntro]}> This is an early beta. Your feedback is appreciated! </Text> </View> @@ -161,11 +160,18 @@ const styles = StyleSheet.create({ textAlign: 'center', marginBottom: 16, }, + explainerHeadingIntro: { + lineHeight: 60, + paddingTop: 50, + paddingBottom: 50, + }, + explainerHeadingBrand: {fontSize: 56}, explainerDesc: { fontSize: 18, textAlign: 'center', marginBottom: 16, }, + explainerDescIntro: {fontSize: 24}, explainerImg: { resizeMode: 'contain', maxWidth: '100%', diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index c68ceee0b..02d61b47b 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -53,11 +53,7 @@ export const PostRepostedBy = observer(function PostRepostedBy({ if (view.hasError) { return ( <View> - <ErrorMessage - message={view.error} - style={{margin: 6}} - onPressTryAgain={onRefresh} - /> + <ErrorMessage message={view.error} onPressTryAgain={onRefresh} /> </View> ) } diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index dcdc1eb49..a52bc643c 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -7,6 +7,7 @@ import { } from '../../../state/models/post-thread-view' import {PostThreadItem} from './PostThreadItem' import {ErrorMessage} from '../util/error/ErrorMessage' +import {s} from '../../lib/styles' export const PostThread = observer(function PostThread({ uri, @@ -60,11 +61,7 @@ export const PostThread = observer(function PostThread({ if (view.hasError) { return ( <View> - <ErrorMessage - message={view.error} - style={{margin: 6}} - onPressTryAgain={onRefresh} - /> + <ErrorMessage message={view.error} onPressTryAgain={onRefresh} /> </View> ) } @@ -84,8 +81,8 @@ export const PostThread = observer(function PostThread({ onRefresh={onRefresh} onLayout={onLayout} onScrollToIndexFailed={onScrollToIndexFailed} - style={{flex: 1}} - contentContainerStyle={{paddingBottom: 200}} + style={s.h100pct} + contentContainerStyle={s.contentContainer} /> ) }) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 2c7ab716c..92f7acc03 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -80,7 +80,7 @@ export const PostThreadItem = observer(function PostThreadItem({ .catch(e => store.log.error('Failed to toggle upvote', e)) } const onCopyPostText = () => { - Clipboard.setString(record.text) + Clipboard.setString(record?.text || '') Toast.show('Copied to clipboard') } const onDeletePost = () => { @@ -131,8 +131,8 @@ export const PostThreadItem = observer(function PostThreadItem({ </Link> </View> <View style={styles.layoutContent}> - <View style={[styles.meta, {paddingTop: 5, paddingBottom: 0}]}> - <View style={{flexDirection: 'row', alignItems: 'baseline'}}> + <View style={[styles.meta, styles.metaExpandedLine1]}> + <View style={[s.flexRow, s.alignBaseline]}> <Link style={styles.metaItem} href={authorHref} @@ -305,10 +305,8 @@ export const PostThreadItem = observer(function PostThreadItem({ lineHeight={1.3} /> </View> - ) : ( - <View style={{height: 5}} /> - )} - <PostEmbeds embed={item.post.embed} style={{marginBottom: 10}} /> + ) : undefined} + <PostEmbeds embed={item.post.embed} style={s.mb10} /> <PostCtrls itemHref={itemHref} itemTitle={itemTitle} @@ -389,6 +387,10 @@ const styles = StyleSheet.create({ paddingTop: 2, paddingBottom: 2, }, + metaExpandedLine1: { + paddingTop: 5, + paddingBottom: 0, + }, metaItem: { paddingRight: 5, maxWidth: 240, diff --git a/src/view/com/post-thread/PostVotedBy.tsx b/src/view/com/post-thread/PostVotedBy.tsx index 06fe53888..011df4aa1 100644 --- a/src/view/com/post-thread/PostVotedBy.tsx +++ b/src/view/com/post-thread/PostVotedBy.tsx @@ -48,11 +48,7 @@ export const PostVotedBy = observer(function PostVotedBy({ if (view.hasError) { return ( <View> - <ErrorMessage - message={view.error} - style={{margin: 6}} - onPressTryAgain={onRefresh} - /> + <ErrorMessage message={view.error} onPressTryAgain={onRefresh} /> </View> ) } diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 08e560bda..d00cc83c2 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -156,7 +156,7 @@ export const Post = observer(function Post({ timestamp={item.post.indexedAt} /> {replyAuthorDid !== '' && ( - <View style={[s.flexRow, s.mb2, {alignItems: 'center'}]}> + <View style={[s.flexRow, s.mb2, s.alignCenter]}> <FontAwesomeIcon icon="reply" size={9} @@ -187,10 +187,8 @@ export const Post = observer(function Post({ lineHeight={1.3} /> </View> - ) : ( - <View style={{height: 5}} /> - )} - <PostEmbeds embed={item.post.embed} style={{marginBottom: 10}} /> + ) : undefined} + <PostEmbeds embed={item.post.embed} style={s.mb10} /> <PostCtrls itemHref={itemHref} itemTitle={itemTitle} diff --git a/src/view/com/post/PostText.tsx b/src/view/com/post/PostText.tsx index 44f9e4d20..0cdc875a9 100644 --- a/src/view/com/post/PostText.tsx +++ b/src/view/com/post/PostText.tsx @@ -1,6 +1,6 @@ import React, {useState, useEffect} from 'react' import {observer} from 'mobx-react-lite' -import {View} from 'react-native' +import {StyleSheet, View} from 'react-native' import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {ErrorMessage} from '../util/error/ErrorMessage' import {Text} from '../util/text/Text' @@ -31,9 +31,9 @@ export const PostText = observer(function PostText({ if (!model || model.isLoading || model.uri !== uri) { return ( <View> - <LoadingPlaceholder width="100%" height={8} style={{marginTop: 6}} /> - <LoadingPlaceholder width="100%" height={8} style={{marginTop: 6}} /> - <LoadingPlaceholder width={100} height={8} style={{marginTop: 6}} /> + <LoadingPlaceholder width="100%" height={8} style={styles.mt6} /> + <LoadingPlaceholder width="100%" height={8} style={styles.mt6} /> + <LoadingPlaceholder width={100} height={8} style={styles.mt6} /> </View> ) } @@ -56,3 +56,7 @@ export const PostText = observer(function PostText({ </View> ) }) + +const styles = StyleSheet.create({ + mt6: {marginTop: 6}, +}) diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 726338f81..db6877660 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -5,6 +5,7 @@ import { View, FlatList, StyleProp, + StyleSheet, ViewStyle, } from 'react-native' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' @@ -14,6 +15,7 @@ import {FeedModel} from '../../../state/models/feed-view' import {FeedItem} from './FeedItem' import {PromptButtons} from './PromptButtons' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' +import {s} from '../../lib/styles' const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} @@ -47,7 +49,7 @@ export const Feed = observer(function Feed({ <EmptyState icon="bars" message="This feed is empty!" - style={{paddingVertical: 40}} + style={styles.emptyState} /> ) } else { @@ -76,7 +78,7 @@ export const Feed = observer(function Feed({ } const FeedFooter = () => feed.isLoading ? ( - <View style={{paddingTop: 20}}> + <View style={styles.feedFooter}> <ActivityIndicator /> </View> ) : ( @@ -87,11 +89,7 @@ export const Feed = observer(function Feed({ {!data && <PromptButtons onPressCompose={onPressCompose} />} {feed.isLoading && !data && <PostFeedLoadingPlaceholder />} {feed.hasError && ( - <ErrorMessage - message={feed.error} - style={{margin: 6}} - onPressTryAgain={onPressTryAgain} - /> + <ErrorMessage message={feed.error} onPressTryAgain={onPressTryAgain} /> )} {feed.hasLoaded && data && ( <FlatList @@ -101,7 +99,7 @@ export const Feed = observer(function Feed({ renderItem={renderItem} ListFooterComponent={FeedFooter} refreshing={feed.isRefreshing} - contentContainerStyle={{paddingBottom: 100}} + contentContainerStyle={s.contentContainer} onScroll={onScroll} onRefresh={onRefresh} onEndReached={onEndReached} @@ -110,3 +108,8 @@ export const Feed = observer(function Feed({ </View> ) }) + +const styles = StyleSheet.create({ + feedFooter: {paddingTop: 20}, + emptyState: {paddingVertical: 40}, +}) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 4133c17d4..584fa0973 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -124,7 +124,7 @@ export const FeedItem = observer(function ({ style={[ styles.bottomReplyLine, {borderColor: pal.colors.replyLine}, - isNoTop ? {top: 64} : undefined, + isNoTop ? styles.bottomReplyLineNoTop : undefined, ]} /> )} @@ -163,7 +163,7 @@ export const FeedItem = observer(function ({ timestamp={item.post.indexedAt} /> {!isChild && replyAuthorDid !== '' && ( - <View style={[s.flexRow, s.mb2, {alignItems: 'center'}]}> + <View style={[s.flexRow, s.mb2, s.alignCenter]}> <FontAwesomeIcon icon="reply" size={9} @@ -195,9 +195,7 @@ export const FeedItem = observer(function ({ lineHeight={1.3} /> </View> - ) : ( - <View style={{height: 5}} /> - )} + ) : undefined} {item.post.embed ? ( <PostEmbeds embed={item.post.embed} style={styles.embed} /> ) : null} @@ -281,6 +279,7 @@ const styles = StyleSheet.create({ bottom: 0, borderLeftWidth: 2, }, + bottomReplyLineNoTop: {top: 64}, includeReason: { flexDirection: 'row', paddingLeft: 50, diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx index b1dfbe996..00207c4d2 100644 --- a/src/view/com/profile/ProfileFollowers.tsx +++ b/src/view/com/profile/ProfileFollowers.tsx @@ -54,11 +54,7 @@ export const ProfileFollowers = observer(function ProfileFollowers({ if (view.hasError) { return ( <View> - <ErrorMessage - message={view.error} - style={{margin: 6}} - onPressTryAgain={onRefresh} - /> + <ErrorMessage message={view.error} onPressTryAgain={onRefresh} /> </View> ) } diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index fca12d11b..2e67873c8 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -54,11 +54,7 @@ export const ProfileFollows = observer(function ProfileFollows({ if (view.hasError) { return ( <View> - <ErrorMessage - message={view.error} - style={{margin: 6}} - onPressTryAgain={onRefresh} - /> + <ErrorMessage message={view.error} onPressTryAgain={onRefresh} /> </View> ) } diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index c14a5c827..2f98fce2d 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -100,22 +100,14 @@ export const ProfileHeader = observer(function ProfileHeader({ <LoadingPlaceholder width="100%" height={120} /> <View style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> - <LoadingPlaceholder - width={80} - height={80} - style={{borderRadius: 40}} - /> + <LoadingPlaceholder width={80} height={80} style={styles.br40} /> </View> <View style={styles.content}> <View style={[styles.buttonsLine]}> - <LoadingPlaceholder - width={100} - height={31} - style={{borderRadius: 50}} - /> + <LoadingPlaceholder width={100} height={31} style={styles.br50} /> </View> <View style={styles.displayNameLine}> - <Text type="title-xl" style={[pal.text, {lineHeight: 38}]}> + <Text type="title-xl" style={[pal.text, styles.title]}> {view.displayName || view.handle} </Text> </View> @@ -208,7 +200,7 @@ export const ProfileHeader = observer(function ProfileHeader({ ) : undefined} </View> <View style={styles.displayNameLine}> - <Text type="title-xl" style={[pal.text, {lineHeight: 38}]}> + <Text type="title-xl" style={[pal.text, styles.title]}> {view.displayName || view.handle} </Text> </View> @@ -349,6 +341,7 @@ const styles = StyleSheet.create({ // paddingLeft: 86, // marginBottom: 14, }, + title: {lineHeight: 38}, handleLine: { flexDirection: 'row', @@ -369,4 +362,7 @@ const styles = StyleSheet.create({ alignItems: 'center', marginBottom: 5, }, + + br40: {borderRadius: 40}, + br50: {borderRadius: 50}, }) diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index aacdc3272..1cbb1af83 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -57,7 +57,7 @@ export const Link = observer(function Link({ ) }) -export const TextLink = observer(function Link({ +export const TextLink = observer(function TextLink({ type = 'md', style, href, diff --git a/src/view/com/util/LoadingPlaceholder.tsx b/src/view/com/util/LoadingPlaceholder.tsx index 207a3f5d2..9828058e8 100644 --- a/src/view/com/util/LoadingPlaceholder.tsx +++ b/src/view/com/util/LoadingPlaceholder.tsx @@ -19,23 +19,15 @@ export function LoadingPlaceholder({ return ( <View style={[ + styles.loadingPlaceholder, { width, height, backgroundColor: theme.palette.default.backgroundLight, - borderRadius: 6, - overflow: 'hidden', }, style, - ]}> - <View - style={{ - width, - height, - backgroundColor: theme.palette.default.backgroundLight, - }} - /> - </View> + ]} + /> ) } @@ -137,6 +129,9 @@ export function NotificationFeedLoadingPlaceholder() { } const styles = StyleSheet.create({ + loadingPlaceholder: { + borderRadius: 6, + }, post: { flexDirection: 'row', padding: 10, diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx index 0ca13b62f..bde44abab 100644 --- a/src/view/com/util/PostCtrls.tsx +++ b/src/view/com/util/PostCtrls.tsx @@ -128,10 +128,7 @@ export function PostCtrls(opts: PostCtrlsOpts) { hitSlop={HITSLOP} onPress={opts.onPressReply}> <CommentBottomArrow - style={[ - defaultCtrlColor, - opts.big ? {marginTop: 2} : {marginTop: 1}, - ]} + style={[defaultCtrlColor, opts.big ? s.mt2 : styles.mt1]} strokeWidth={3} size={opts.big ? 20 : 15} /> @@ -181,10 +178,7 @@ export function PostCtrls(opts: PostCtrlsOpts) { /> ) : ( <HeartIcon - style={[ - defaultCtrlColor, - opts.big ? {marginTop: 1} : undefined, - ]} + style={[defaultCtrlColor, opts.big ? styles.mt1 : undefined]} strokeWidth={3} size={opts.big ? 20 : 16} /> @@ -244,4 +238,7 @@ const styles = StyleSheet.create({ ctrlIconUpvoted: { color: colors.red3, }, + mt1: { + marginTop: 1, + }, }) diff --git a/src/view/com/util/PostEmbeds.tsx b/src/view/com/util/PostEmbeds.tsx index 65518470a..e3fca2538 100644 --- a/src/view/com/util/PostEmbeds.tsx +++ b/src/view/com/util/PostEmbeds.tsx @@ -67,7 +67,7 @@ export function PostEmbeds({ <AutoSizedImage uri={embed.images[0].thumb} onPress={() => openLightbox(0)} - containerStyle={{borderRadius: 8}} + containerStyle={styles.singleImage} /> </View> ) @@ -120,6 +120,9 @@ const styles = StyleSheet.create({ imagesContainer: { marginTop: 4, }, + singleImage: { + borderRadius: 8, + }, extOuter: { borderWidth: 1, borderRadius: 8, diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx index 7a8b9b530..87540cf38 100644 --- a/src/view/com/util/Selector.tsx +++ b/src/view/com/util/Selector.tsx @@ -41,7 +41,7 @@ export function Selector({ width: middle.width, } return [left, middle, right] - }, [selectedIndex, items, itemLayouts]) + }, [selectedIndex, itemLayouts]) const underlineStyle = { backgroundColor: pal.colors.text, diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index bd4897ba8..c9c255f46 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -62,8 +62,8 @@ export function UserAvatar({ ]) }, [onSelectNewAvatar]) - const renderSvg = (size: number, initials: string) => ( - <Svg width={size} height={size} viewBox="0 0 100 100"> + const renderSvg = (svgSize: number, svgInitials: string) => ( + <Svg width={svgSize} height={svgSize} viewBox="0 0 100 100"> <Defs> <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="1"> <Stop offset="0" stopColor={gradients.blue.start} stopOpacity="1" /> @@ -78,7 +78,7 @@ export function UserAvatar({ x="50" y="67" textAnchor="middle"> - {initials} + {svgInitials} </Text> </Svg> ) @@ -88,7 +88,11 @@ export function UserAvatar({ <TouchableOpacity onPress={handleEditAvatar}> {avatar ? ( <Image - style={{width: size, height: size, borderRadius: (size / 2) | 0}} + style={{ + width: size, + height: size, + borderRadius: Math.floor(size / 2), + }} source={{uri: avatar}} /> ) : ( @@ -104,7 +108,7 @@ export function UserAvatar({ </TouchableOpacity> ) : avatar ? ( <Image - style={{width: size, height: size, borderRadius: (size / 2) | 0}} + style={{width: size, height: size, borderRadius: Math.floor(size / 2)}} resizeMode="stretch" source={{uri: avatar}} /> diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 151fa54d0..a6daf18b2 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -1,6 +1,6 @@ import React, {useState, useEffect} from 'react' import {AppBskyActorGetProfile as GetProfile} from '@atproto/api' -import {StyleProp, TextStyle} from 'react-native' +import {StyleProp, StyleSheet, TextStyle} from 'react-native' import {Link} from './Link' import {Text} from './text/Text' import {LoadingPlaceholder} from './LoadingPlaceholder' @@ -53,7 +53,7 @@ export function UserInfoText({ return () => { aborted = true } - }, [did, store.api.app.bsky]) + }, [did, store.profiles]) let inner if (didFail) { @@ -73,7 +73,7 @@ export function UserInfoText({ <LoadingPlaceholder width={80} height={8} - style={{position: 'relative', top: 1, left: 2}} + style={styles.loadingPlaceholder} /> ) } @@ -91,3 +91,7 @@ export function UserInfoText({ return inner } + +const styles = StyleSheet.create({ + loadingPlaceholder: {position: 'relative', top: 1, left: 2}, +}) diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 761553cc5..c8b1b2d97 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -11,8 +11,8 @@ import {UserAvatar} from './UserAvatar' import {Text} from './text/Text' import {MagnifyingGlassIcon} from '../../lib/icons' import {useStores} from '../../../state' -import {useTheme} from '../../lib/ThemeContext' import {usePalette} from '../../lib/hooks/usePalette' +import {colors} from '../../lib/styles' const HITSLOP = {left: 10, top: 10, right: 10, bottom: 10} const BACK_HITSLOP = {left: 10, top: 10, right: 30, bottom: 10} @@ -26,7 +26,6 @@ export const ViewHeader = observer(function ViewHeader({ subtitle?: string canGoBack?: boolean }) { - const theme = useTheme() const pal = usePalette('default') const store = useStores() const onPressBack = () => { @@ -52,12 +51,12 @@ export const ViewHeader = observer(function ViewHeader({ testID="viewHeaderBackOrMenuBtn" onPress={canGoBack ? onPressBack : onPressMenu} hitSlop={BACK_HITSLOP} - style={canGoBack ? styles.backIcon : styles.backIconWide}> + style={canGoBack ? styles.backBtn : styles.backBtnWide}> {canGoBack ? ( <FontAwesomeIcon size={18} icon="angle-left" - style={[{marginTop: 6}, pal.text]} + style={[styles.backIcon, pal.text]} /> ) : ( <UserAvatar @@ -96,13 +95,10 @@ export const ViewHeader = observer(function ViewHeader({ <FontAwesomeIcon icon="signal" style={pal.text} size={16} /> <FontAwesomeIcon icon="x" - style={{ - backgroundColor: pal.colors.background, - color: theme.palette.error.background, - position: 'absolute', - right: 7, - bottom: 7, - }} + style={[ + styles.littleXIcon, + {backgroundColor: pal.colors.background}, + ]} size={8} /> </> @@ -136,15 +132,18 @@ const styles = StyleSheet.create({ fontWeight: 'normal', }, - backIcon: { + backBtn: { width: 30, height: 30, }, - backIconWide: { + backBtnWide: { width: 40, height: 30, marginLeft: 6, }, + backIcon: { + marginTop: 6, + }, btn: { flexDirection: 'row', alignItems: 'center', @@ -154,4 +153,10 @@ const styles = StyleSheet.create({ borderRadius: 20, marginLeft: 4, }, + littleXIcon: { + color: colors.red3, + position: 'absolute', + right: 7, + bottom: 7, + }, }) diff --git a/src/view/com/util/ViewSelector.tsx b/src/view/com/util/ViewSelector.tsx index 9ea7bc740..0dd93ec64 100644 --- a/src/view/com/util/ViewSelector.tsx +++ b/src/view/com/util/ViewSelector.tsx @@ -5,6 +5,7 @@ import {HorzSwipe} from './gestures/HorzSwipe' import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue' import {OnScrollCb} from '../../lib/hooks/useOnMainScroll' import {clamp} from '../../../lib/numbers' +import {s} from '../../lib/styles' const HEADER_ITEM = {_reactKey: '__header__'} const SELECTOR_ITEM = {_reactKey: '__selector__'} @@ -54,7 +55,7 @@ export function ViewSelector({ setSelectedIndex(clamp(index, 0, sections.length)) useEffect(() => { onSelectView?.(selectedIndex) - }, [selectedIndex]) + }, [selectedIndex, onSelectView]) // rendering // = @@ -98,7 +99,7 @@ export function ViewSelector({ onScroll={onScroll} onRefresh={onRefresh} onEndReached={onEndReached} - contentContainerStyle={{paddingBottom: 200}} + contentContainerStyle={s.contentContainer} /> </HorzSwipe> ) diff --git a/src/view/com/util/forms/RadioGroup.tsx b/src/view/com/util/forms/RadioGroup.tsx index 9abc2345f..b33cd9831 100644 --- a/src/view/com/util/forms/RadioGroup.tsx +++ b/src/view/com/util/forms/RadioGroup.tsx @@ -2,6 +2,7 @@ import React, {useState} from 'react' import {View} from 'react-native' import {RadioButton} from './RadioButton' import {ButtonType} from './Button' +import {s} from '../../../lib/styles' export interface RadioGroupItem { label: string @@ -29,7 +30,7 @@ export function RadioGroup({ {items.map((item, i) => ( <RadioButton key={item.key} - style={i !== 0 ? {marginTop: 2} : undefined} + style={i !== 0 ? s.mt2 : undefined} type={type} label={item.label} isSelected={item.key === selection} diff --git a/src/view/com/util/gestures/HorzSwipe.tsx b/src/view/com/util/gestures/HorzSwipe.tsx index 6dcdcf918..22b15afe7 100644 --- a/src/view/com/util/gestures/HorzSwipe.tsx +++ b/src/view/com/util/gestures/HorzSwipe.tsx @@ -9,6 +9,7 @@ import { View, } from 'react-native' import {clamp} from 'lodash' +import {s} from '../../../lib/styles' interface Props { panX: Animated.Value @@ -111,7 +112,9 @@ export function HorzSwipe({ (Math.abs(gestureState.dx) > swipeDistanceThreshold / 4 || Math.abs(gestureState.vx) > swipeVelocityThreshold) ) { - const final = ((gestureState.dx / Math.abs(gestureState.dx)) * -1) | 0 + const final = Math.floor( + (gestureState.dx / Math.abs(gestureState.dx)) * -1, + ) Animated.timing(panX, { toValue: final, duration: 100, @@ -144,7 +147,7 @@ export function HorzSwipe({ }) return ( - <View {...panResponder.panHandlers} style={{flex: 1}}> + <View {...panResponder.panHandlers} style={s.h100pct}> {children} </View> ) diff --git a/src/view/com/util/gestures/SwipeAndZoom.tsx b/src/view/com/util/gestures/SwipeAndZoom.tsx index 881eea094..ee00edab7 100644 --- a/src/view/com/util/gestures/SwipeAndZoom.tsx +++ b/src/view/com/util/gestures/SwipeAndZoom.tsx @@ -9,6 +9,7 @@ import { View, } from 'react-native' import {clamp} from 'lodash' +import {s} from '../../../lib/styles' export enum Dir { None, @@ -294,7 +295,7 @@ export function SwipeAndZoom({ }) return ( - <View {...panResponder.panHandlers} style={{flex: 1}}> + <View {...panResponder.panHandlers} style={s.h100pct}> {children} </View> ) diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index a711323a9..648bb957f 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -47,9 +47,9 @@ export function AutoSizedImage({ setImgInfo({width, height}) } }, - (error: any) => { + (err: any) => { if (!aborted) { - setError(String(error)) + setError(String(err)) } }, ) diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx index 5eb5b3c54..8acab7109 100644 --- a/src/view/com/util/images/ImageLayoutGrid.tsx +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -105,7 +105,7 @@ function ImageLayoutGridInner({ <TouchableWithoutFeedback onPress={() => onPress?.(1)}> <Image source={{uri: uris[1]}} style={size1} /> </TouchableWithoutFeedback> - <View style={{height: 5}} /> + <View style={styles.hSpace} /> <TouchableWithoutFeedback onPress={() => onPress?.(2)}> <Image source={{uri: uris[2]}} style={size1} /> </TouchableWithoutFeedback> diff --git a/src/view/lib/styles.ts b/src/view/lib/styles.ts index 0b0145ced..7129867e9 100644 --- a/src/view/lib/styles.ts +++ b/src/view/lib/styles.ts @@ -58,6 +58,8 @@ export const gradients = { export const s = StyleSheet.create({ // helpers footerSpacer: {height: 100}, + contentContainer: {paddingBottom: 200}, + border1: {borderWidth: 1}, // font weights fw600: {fontWeight: '600'}, @@ -140,6 +142,7 @@ export const s = StyleSheet.create({ flexCol: {flexDirection: 'column'}, flex1: {flex: 1}, alignCenter: {alignItems: 'center'}, + alignBaseline: {alignItems: 'baseline'}, // position absolute: {position: 'absolute'}, diff --git a/src/view/routes.ts b/src/view/routes.ts index 908036a41..b5cc014ff 100644 --- a/src/view/routes.ts +++ b/src/view/routes.ts @@ -18,7 +18,7 @@ import {Debug} from './screens/Debug' import {Log} from './screens/Log' export type ScreenParams = { - navIdx: [number, number] + navIdx: string params: Record<string, any> visible: boolean scrollElRef?: MutableRefObject<FlatList<any> | undefined> diff --git a/src/view/screens/Contacts.tsx b/src/view/screens/Contacts.tsx index a6cc7244e..cba17f285 100644 --- a/src/view/screens/Contacts.tsx +++ b/src/view/screens/Contacts.tsx @@ -17,7 +17,7 @@ export const Contacts = ({navIdx, visible}: ScreenParams) => { if (visible) { store.nav.setTitle(navIdx, 'Contacts') } - }, [store, visible]) + }, [store, visible, navIdx]) const [searchText, onChangeSearchText] = useState('') const inputRef = useRef<TextInput | null>(null) diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx index f6e2b389c..9365724a0 100644 --- a/src/view/screens/Debug.tsx +++ b/src/view/screens/Debug.tsx @@ -4,6 +4,7 @@ import {ViewHeader} from '../com/util/ViewHeader' import {ThemeProvider} from '../lib/ThemeContext' import {PaletteColorName} from '../lib/ThemeContext' import {usePalette} from '../lib/hooks/usePalette' +import {s} from '../lib/styles' import {Text} from '../com/util/text/Text' import {ViewSelector} from '../com/util/ViewSelector' @@ -48,7 +49,7 @@ function DebugInner({ const renderItem = item => { return ( <View> - <View style={{paddingTop: 10, paddingHorizontal: 10}}> + <View style={[s.pt10, s.pl10, s.pr10]}> <ToggleButton type="default-light" onPress={onToggleColorScheme} @@ -70,7 +71,7 @@ function DebugInner({ const items = [{currentView}] return ( - <View style={[{flex: 1}, pal.view]}> + <View style={[s.h100pct, pal.view]}> <ViewHeader title="Debug panel" /> <ViewSelector swipeEnabled @@ -86,7 +87,7 @@ function DebugInner({ function Heading({label}: {label: string}) { const pal = usePalette('default') return ( - <View style={{paddingTop: 10, paddingBottom: 5}}> + <View style={[s.pt10, s.pb5]}> <Text type="title-lg" style={pal.text}> {label} </Text> @@ -96,7 +97,7 @@ function Heading({label}: {label: string}) { function BaseView() { return ( - <View style={{paddingHorizontal: 10}}> + <View style={[s.pl10, s.pr10]}> <Heading label="Typography" /> <TypographyView /> <Heading label="Palettes" /> @@ -109,14 +110,14 @@ function BaseView() { <EmptyStateView /> <Heading label="Loading placeholders" /> <LoadingPlaceholderView /> - <View style={{height: 200}} /> + <View style={s.footerSpacer} /> </View> ) } function ControlsView() { return ( - <ScrollView style={{paddingHorizontal: 10}}> + <ScrollView style={[s.pl10, s.pr10]}> <Heading label="Buttons" /> <ButtonsView /> <Heading label="Dropdown Buttons" /> @@ -125,15 +126,15 @@ function ControlsView() { <ToggleButtonsView /> <Heading label="Radio Buttons" /> <RadioButtonsView /> - <View style={{height: 200}} /> + <View style={s.footerSpacer} /> </ScrollView> ) } function ErrorView() { return ( - <View style={{padding: 10}}> - <View style={{marginBottom: 5}}> + <View style={s.p10}> + <View style={s.mb5}> <ErrorScreen title="Error screen" message="A major error occurred that led the entire screen to fail" @@ -141,22 +142,22 @@ function ErrorView() { onPressTryAgain={() => {}} /> </View> - <View style={{marginBottom: 5}}> + <View style={s.mb5}> <ErrorMessage message="This is an error that occurred while things were being done" /> </View> - <View style={{marginBottom: 5}}> + <View style={s.mb5}> <ErrorMessage message="This is an error that occurred while things were being done" numberOfLines={1} /> </View> - <View style={{marginBottom: 5}}> + <View style={s.mb5}> <ErrorMessage message="This is an error that occurred while things were being done" onPressTryAgain={() => {}} /> </View> - <View style={{marginBottom: 5}}> + <View style={s.mb5}> <ErrorMessage message="This is an error that occurred while things were being done" onPressTryAgain={() => {}} @@ -171,16 +172,7 @@ function PaletteView({palette}: {palette: PaletteColorName}) { const defaultPal = usePalette('default') const pal = usePalette(palette) return ( - <View - style={[ - pal.view, - pal.border, - { - borderWidth: 1, - padding: 10, - marginBottom: 5, - }, - ]}> + <View style={[pal.view, pal.border, s.p10, s.mb5, s.border1]}> <Text style={[pal.text]}>{palette} colors</Text> <Text style={[pal.textLight]}>Light text</Text> <Text style={[pal.link]}>Link text</Text> @@ -197,21 +189,6 @@ function TypographyView() { const pal = usePalette('default') return ( <View style={[pal.view]}> - <Text type="xxl-thin" style={[pal.text]}> - 'xxl-thin' lorem ipsum dolor - </Text> - <Text type="xxl" style={[pal.text]}> - 'xxl' lorem ipsum dolor - </Text> - <Text type="xxl-medium" style={[pal.text]}> - 'xxl-medium' lorem ipsum dolor - </Text> - <Text type="xxl-bold" style={[pal.text]}> - 'xxl-bold' lorem ipsum dolor - </Text> - <Text type="xxl-heavy" style={[pal.text]}> - 'xxl-heavy' lorem ipsum dolor - </Text> <Text type="xl-thin" style={[pal.text]}> 'xl-thin' lorem ipsum dolor </Text> @@ -300,9 +277,6 @@ function TypographyView() { <Text type="button" style={[pal.text]}> Button </Text> - <Text type="overline" style={[pal.text]}> - Overline - </Text> </View> ) } @@ -325,16 +299,12 @@ function ButtonsView() { const buttonStyles = {marginRight: 5} return ( <View style={[defaultPal.view]}> - <View - style={{ - flexDirection: 'row', - marginBottom: 5, - }}> + <View style={[s.flexRow, s.mb5]}> <Button type="primary" label="Primary solid" style={buttonStyles} /> <Button type="secondary" label="Secondary solid" style={buttonStyles} /> <Button type="inverted" label="Inverted solid" style={buttonStyles} /> </View> - <View style={{flexDirection: 'row'}}> + <View style={s.flexRow}> <Button type="primary-outline" label="Primary outline" @@ -346,7 +316,7 @@ function ButtonsView() { style={buttonStyles} /> </View> - <View style={{flexDirection: 'row'}}> + <View style={s.flexRow}> <Button type="primary-light" label="Primary light" @@ -358,7 +328,7 @@ function ButtonsView() { style={buttonStyles} /> </View> - <View style={{flexDirection: 'row'}}> + <View style={s.flexRow}> <Button type="default-light" label="Default light" @@ -390,10 +360,7 @@ function DropdownButtonsView() { const defaultPal = usePalette('default') return ( <View style={[defaultPal.view]}> - <View - style={{ - marginBottom: 5, - }}> + <View style={s.mb5}> <DropdownButton type="primary" items={DROPDOWN_ITEMS} @@ -401,10 +368,7 @@ function DropdownButtonsView() { label="Primary button" /> </View> - <View - style={{ - marginBottom: 5, - }}> + <View style={s.mb5}> <DropdownButton type="bare" items={DROPDOWN_ITEMS} menuWidth={200}> <Text>Bare</Text> </DropdownButton> @@ -415,7 +379,7 @@ function DropdownButtonsView() { function ToggleButtonsView() { const defaultPal = usePalette('default') - const buttonStyles = {marginBottom: 5} + const buttonStyles = s.mb5 const [isSelected, setIsSelected] = React.useState(false) const onToggle = () => setIsSelected(!isSelected) return ( diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 8c00f4c7c..384ee15e1 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -83,14 +83,14 @@ export const Home = observer(function Home({ } return ( - <View style={s.flex1}> + <View style={s.h100pct}> <ViewHeader title="Bluesky" subtitle="Private Beta" canGoBack={false} /> <Feed testID="homeFeed" key="default" feed={store.me.mainFeed} scrollElRef={scrollElRef} - style={{flex: 1}} + style={s.h100pct} onPressCompose={onPressCompose} onPressTryAgain={onPressTryAgain} onScroll={onMainScroll} @@ -99,9 +99,9 @@ export const Home = observer(function Home({ <TouchableOpacity style={[ styles.loadLatest, - store.shell.minimalShellMode - ? {bottom: 35} - : {bottom: 60 + clamp(safeAreaInsets.bottom, 15, 30)}, + !store.shell.minimalShellMode && { + bottom: 60 + clamp(safeAreaInsets.bottom, 15, 30), + }, ]} onPress={onPressLoadLatest} hitSlop={HITSLOP}> @@ -125,6 +125,7 @@ const styles = StyleSheet.create({ loadLatest: { position: 'absolute', left: 20, + bottom: 35, shadowColor: '#000', shadowOpacity: 0.3, shadowOffset: {width: 0, height: 1}, diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx index 34eed394c..62d79f482 100644 --- a/src/view/screens/Log.tsx +++ b/src/view/screens/Log.tsx @@ -21,7 +21,7 @@ export const Log = observer(function Log({navIdx, visible}: ScreenParams) { } store.shell.setMinimalShellMode(false) store.nav.setTitle(navIdx, 'Log') - }, [visible, store]) + }, [visible, store, navIdx]) const toggler = (id: string) => () => { if (expanded.includes(id)) { @@ -52,7 +52,7 @@ export const Log = observer(function Log({navIdx, visible}: ScreenParams) { <Text type="sm" style={[styles.summary, pal.text]}> {entry.summary} </Text> - {!!entry.details ? ( + {entry.details ? ( <FontAwesomeIcon icon={ expanded.includes(entry.id) ? 'angle-up' : 'angle-down' diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx index 7d99f1444..accd0f428 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -18,9 +18,9 @@ import {s, colors} from '../lib/styles' import {usePalette} from '../lib/hooks/usePalette' enum ScreenState { - SigninOrCreateAccount, - Signin, - CreateAccount, + S_SigninOrCreateAccount, + S_Signin, + S_CreateAccount, } const SigninOrCreateAccount = ({ @@ -78,58 +78,56 @@ const SigninOrCreateAccount = ({ ) } -export const Login = observer( - (/*{navigation}: RootTabsScreenProps<'Login'>*/) => { - const pal = usePalette('default') - const [screenState, setScreenState] = useState<ScreenState>( - ScreenState.SigninOrCreateAccount, - ) - - if (screenState === ScreenState.SigninOrCreateAccount) { - return ( - <LinearGradient - colors={['#007CFF', '#00BCFF']} - start={{x: 0, y: 0.8}} - end={{x: 0, y: 1}} - style={styles.container}> - <SafeAreaView testID="noSessionView" style={styles.container}> - <ErrorBoundary> - <SigninOrCreateAccount - onPressSignin={() => setScreenState(ScreenState.Signin)} - onPressCreateAccount={() => - setScreenState(ScreenState.CreateAccount) - } - /> - </ErrorBoundary> - </SafeAreaView> - </LinearGradient> - ) - } +export const Login = observer(() => { + const pal = usePalette('default') + const [screenState, setScreenState] = useState<ScreenState>( + ScreenState.S_SigninOrCreateAccount, + ) + if (screenState === ScreenState.S_SigninOrCreateAccount) { return ( - <View style={[styles.container, pal.view]}> + <LinearGradient + colors={['#007CFF', '#00BCFF']} + start={{x: 0, y: 0.8}} + end={{x: 0, y: 1}} + style={styles.container}> <SafeAreaView testID="noSessionView" style={styles.container}> <ErrorBoundary> - {screenState === ScreenState.Signin ? ( - <Signin - onPressBack={() => - setScreenState(ScreenState.SigninOrCreateAccount) - } - /> - ) : undefined} - {screenState === ScreenState.CreateAccount ? ( - <CreateAccount - onPressBack={() => - setScreenState(ScreenState.SigninOrCreateAccount) - } - /> - ) : undefined} + <SigninOrCreateAccount + onPressSignin={() => setScreenState(ScreenState.S_Signin)} + onPressCreateAccount={() => + setScreenState(ScreenState.S_CreateAccount) + } + /> </ErrorBoundary> </SafeAreaView> - </View> + </LinearGradient> ) - }, -) + } + + return ( + <View style={[styles.container, pal.view]}> + <SafeAreaView testID="noSessionView" style={styles.container}> + <ErrorBoundary> + {screenState === ScreenState.S_Signin ? ( + <Signin + onPressBack={() => + setScreenState(ScreenState.S_SigninOrCreateAccount) + } + /> + ) : undefined} + {screenState === ScreenState.S_CreateAccount ? ( + <CreateAccount + onPressBack={() => + setScreenState(ScreenState.S_SigninOrCreateAccount) + } + /> + ) : undefined} + </ErrorBoundary> + </SafeAreaView> + </View> + ) +}) const styles = StyleSheet.create({ container: { diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/NotFound.tsx index 79477fa9b..c5c5ff002 100644 --- a/src/view/screens/NotFound.tsx +++ b/src/view/screens/NotFound.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Button, View} from 'react-native' +import {Button, StyleSheet, View} from 'react-native' import {ViewHeader} from '../com/util/ViewHeader' import {Text} from '../com/util/text/Text' import {useStores} from '../../state' @@ -9,13 +9,8 @@ export const NotFound = () => { return ( <View testID="notFoundView"> <ViewHeader title="Page not found" /> - <View - style={{ - justifyContent: 'center', - alignItems: 'center', - paddingTop: 100, - }}> - <Text style={{fontSize: 40, fontWeight: 'bold'}}>Page not found</Text> + <View style={styles.container}> + <Text style={styles.title}>Page not found</Text> <Button testID="navigateHomeButton" title="Home" @@ -25,3 +20,15 @@ export const NotFound = () => { </View> ) } + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center', + paddingTop: 100, + }, + title: { + fontSize: 40, + fontWeight: 'bold', + }, +}) diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index dd6e07611..9b5dc5970 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -5,6 +5,7 @@ import {Feed} from '../com/notifications/Feed' import {useStores} from '../../state' import {ScreenParams} from '../routes' import {useOnMainScroll} from '../lib/hooks/useOnMainScroll' +import {s} from '../lib/styles' export const Notifications = ({navIdx, visible}: ScreenParams) => { const store = useStores() @@ -24,14 +25,14 @@ export const Notifications = ({navIdx, visible}: ScreenParams) => { store.me.notifications.updateReadState() }) store.nav.setTitle(navIdx, 'Notifications') - }, [visible, store]) + }, [visible, store, navIdx]) const onPressTryAgain = () => { store.me.notifications.refresh() } return ( - <View style={{flex: 1}}> + <View style={s.h100pct}> <ViewHeader title="Notifications" canGoBack={false} /> <Feed view={store.me.notifications} diff --git a/src/view/screens/Onboard.tsx b/src/view/screens/Onboard.tsx index 4aa0e6cac..e31b42adc 100644 --- a/src/view/screens/Onboard.tsx +++ b/src/view/screens/Onboard.tsx @@ -1,5 +1,5 @@ import React, {useEffect} from 'react' -import {View} from 'react-native' +import {StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' import {FeatureExplainer} from '../com/onboard/FeatureExplainer' import {Follows} from '../com/onboard/Follows' @@ -14,7 +14,7 @@ export const Onboard = observer(() => { if (!OnboardStageOrder.includes(store.onboard.stage)) { store.onboard.stop() } - }, [store.onboard.stage]) + }, [store.onboard]) let Com if (store.onboard.stage === OnboardStage.Explainers) { @@ -26,8 +26,15 @@ export const Onboard = observer(() => { } return ( - <View style={{flex: 1, backgroundColor: '#fff'}}> + <View style={styles.container}> <Com /> </View> ) }) + +const styles = StyleSheet.create({ + container: { + height: '100%', + backgroundColor: '#fff', + }, +}) diff --git a/src/view/screens/PostDownvotedBy.tsx b/src/view/screens/PostDownvotedBy.tsx index ab110f8f9..1401868d4 100644 --- a/src/view/screens/PostDownvotedBy.tsx +++ b/src/view/screens/PostDownvotedBy.tsx @@ -16,7 +16,7 @@ export const PostDownvotedBy = ({navIdx, visible, params}: ScreenParams) => { store.nav.setTitle(navIdx, 'Downvoted by') store.shell.setMinimalShellMode(false) } - }, [store, visible]) + }, [store, visible, navIdx]) return ( <View> diff --git a/src/view/screens/PostRepostedBy.tsx b/src/view/screens/PostRepostedBy.tsx index 4e84617df..bf4d6ec91 100644 --- a/src/view/screens/PostRepostedBy.tsx +++ b/src/view/screens/PostRepostedBy.tsx @@ -16,7 +16,7 @@ export const PostRepostedBy = ({navIdx, visible, params}: ScreenParams) => { store.nav.setTitle(navIdx, 'Reposted by') store.shell.setMinimalShellMode(false) } - }, [store, visible]) + }, [store, visible, navIdx]) return ( <View> diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index c14c93af0..febaddc09 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -6,6 +6,7 @@ import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread' import {PostThreadViewModel} from '../../state/models/post-thread-view' import {ScreenParams} from '../routes' import {useStores} from '../../state' +import {s} from '../lib/styles' export const PostThread = ({navIdx, visible, params}: ScreenParams) => { const store = useStores() @@ -14,18 +15,18 @@ export const PostThread = ({navIdx, visible, params}: ScreenParams) => { const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) const view = useMemo<PostThreadViewModel>( () => new PostThreadViewModel(store, {uri}), - [uri], + [store, uri], ) - const setTitle = () => { - const author = view.thread?.author - const niceName = author?.handle || name - setViewSubtitle(`by ${niceName}`) - store.nav.setTitle(navIdx, `Post by ${niceName}`) - } useEffect(() => { let aborted = false const threadCleanup = view.registerListeners() + const setTitle = () => { + const author = view.thread?.post.author + const niceName = author?.handle || name + setViewSubtitle(`by ${niceName}`) + store.nav.setTitle(navIdx, `Post by ${niceName}`) + } if (!visible) { return threadCleanup } @@ -47,12 +48,12 @@ export const PostThread = ({navIdx, visible, params}: ScreenParams) => { aborted = true threadCleanup() } - }, [visible, store.nav, store.log, name]) + }, [visible, store.nav, store.log, store.shell, name, navIdx, view]) return ( - <View style={{flex: 1}}> + <View style={s.h100pct}> <ViewHeader title="Post" subtitle={viewSubtitle} /> - <View style={{flex: 1}}> + <View style={s.h100pct}> <PostThreadComponent uri={uri} view={view} /> </View> </View> diff --git a/src/view/screens/PostUpvotedBy.tsx b/src/view/screens/PostUpvotedBy.tsx index 7379b852f..4bba222ae 100644 --- a/src/view/screens/PostUpvotedBy.tsx +++ b/src/view/screens/PostUpvotedBy.tsx @@ -15,7 +15,7 @@ export const PostUpvotedBy = ({navIdx, visible, params}: ScreenParams) => { if (visible) { store.nav.setTitle(navIdx, 'Liked by') } - }, [store, visible]) + }, [store, visible, navIdx]) return ( <View> diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index bd60ca61c..7fd813809 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -26,10 +26,14 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { const [hasSetup, setHasSetup] = useState<boolean>(false) const uiState = React.useMemo( () => new ProfileUiModel(store, {user: params.name}), - [params.user], + [params.name, store], ) useEffect(() => { + store.nav.setTitle(navIdx, params.name) + }, [store, navIdx, params.name]) + + useEffect(() => { let aborted = false const feedCleanup = uiState.feed.registerListeners() if (!visible) { @@ -38,7 +42,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { if (hasSetup) { uiState.update() } else { - store.nav.setTitle(navIdx, params.name) uiState.setup().then(() => { if (aborted) { return @@ -50,7 +53,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { aborted = true feedCleanup() } - }, [visible, params.name, store]) + }, [visible, store, hasSetup, uiState]) // events // = @@ -139,7 +142,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { <EmptyState icon={['far', 'message']} message="No posts yet!" - style={{paddingVertical: 40}} + style={styles.emptyState} /> ) } @@ -187,7 +190,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => { function LoadingMoreFooter() { return ( - <View style={{paddingVertical: 20}}> + <View style={styles.loadingMoreFooter}> <ActivityIndicator /> </View> ) @@ -202,6 +205,12 @@ const styles = StyleSheet.create({ paddingVertical: 10, paddingHorizontal: 14, }, + emptyState: { + paddingVertical: 40, + }, + loadingMoreFooter: { + paddingVertical: 20, + }, endItem: { paddingTop: 20, paddingBottom: 30, diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx index 49b3e2e05..f7520549e 100644 --- a/src/view/screens/ProfileFollowers.tsx +++ b/src/view/screens/ProfileFollowers.tsx @@ -14,7 +14,7 @@ export const ProfileFollowers = ({navIdx, visible, params}: ScreenParams) => { store.nav.setTitle(navIdx, `Followers of ${name}`) store.shell.setMinimalShellMode(false) } - }, [store, visible, name]) + }, [store, visible, name, navIdx]) return ( <View> diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx index 58df6e76d..65e4004e9 100644 --- a/src/view/screens/ProfileFollows.tsx +++ b/src/view/screens/ProfileFollows.tsx @@ -14,7 +14,7 @@ export const ProfileFollows = ({navIdx, visible, params}: ScreenParams) => { store.nav.setTitle(navIdx, `Followed by ${name}`) store.shell.setMinimalShellMode(false) } - }, [store, visible, name]) + }, [store, visible, name, navIdx]) return ( <View> diff --git a/src/view/screens/Search.tsx b/src/view/screens/Search.tsx index 385489c4b..952972222 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/Search.tsx @@ -25,7 +25,7 @@ export const Search = ({navIdx, visible, params}: ScreenParams) => { const [query, setQuery] = useState<string>('') const autocompleteView = useMemo<UserAutocompleteViewModel>( () => new UserAutocompleteViewModel(store), - [], + [store], ) const {name} = params @@ -35,7 +35,7 @@ export const Search = ({navIdx, visible, params}: ScreenParams) => { autocompleteView.setup() store.nav.setTitle(navIdx, 'Search') } - }, [store, visible, name]) + }, [store, visible, name, navIdx, autocompleteView]) const onChangeQuery = (text: string) => { setQuery(text) diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 2c6982685..d659d25d4 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -33,7 +33,7 @@ export const Settings = observer(function Settings({ } store.shell.setMinimalShellMode(false) store.nav.setTitle(navIdx, 'Settings') - }, [visible, store]) + }, [visible, store, navIdx]) const onPressSwitchAccount = async (acct: AccountData) => { setIsSwitching(true) @@ -130,8 +130,8 @@ export const Settings = observer(function Settings({ style={[ pal.view, styles.profile, + styles.alignCenter, s.mb2, - {alignItems: 'center'}, isSwitching && styles.dimmed, ]} onPress={isSwitching ? undefined : onPressAddAccount}> @@ -142,7 +142,7 @@ export const Settings = observer(function Settings({ </Text> </View> </TouchableOpacity> - <View style={{height: 50}} /> + <View style={styles.spacer} /> <Text type="sm-medium" style={[s.mb5]}> Developer tools </Text> @@ -168,6 +168,12 @@ const styles = StyleSheet.create({ dimmed: { opacity: 0.5, }, + spacer: { + height: 50, + }, + alignCenter: { + alignItems: 'center', + }, title: { fontSize: 32, fontWeight: 'bold', diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx index 26cb5b9bd..a7d3e2142 100644 --- a/src/view/shell/mobile/Menu.tsx +++ b/src/view/shell/mobile/Menu.tsx @@ -23,163 +23,157 @@ import {Text} from '../../com/util/text/Text' import {ToggleButton} from '../../com/util/forms/ToggleButton' import {usePalette} from '../../lib/hooks/usePalette' -export const Menu = observer( - ({visible, onClose}: {visible: boolean; onClose: () => void}) => { - const pal = usePalette('default') - const store = useStores() +export const Menu = observer(({onClose}: {onClose: () => void}) => { + const pal = usePalette('default') + const store = useStores() - // events - // = + // events + // = - const onNavigate = (url: string) => { - onClose() - if (url === '/notifications') { - store.nav.switchTo(1, true) - } else { - store.nav.switchTo(0, true) - if (url !== '/') { - store.nav.navigate(url) - } + const onNavigate = (url: string) => { + onClose() + if (url === '/notifications') { + store.nav.switchTo(1, true) + } else { + store.nav.switchTo(0, true) + if (url !== '/') { + store.nav.navigate(url) } } + } - // rendering - // = + // rendering + // = - const MenuItem = ({ - icon, - label, - count, - url, - bold, - onPress, - }: { - icon: JSX.Element - label: string - count?: number - url?: string - bold?: boolean - onPress?: () => void - }) => ( - <TouchableOpacity - testID={`menuItemButton-${label}`} - style={styles.menuItem} - onPress={onPress ? onPress : () => onNavigate(url || '/')}> - <View style={[styles.menuItemIconWrapper]}> - {icon} - {count ? ( - <View style={styles.menuItemCount}> - <Text style={styles.menuItemCountLabel}>{count}</Text> - </View> - ) : undefined} - </View> - <Text - type="title" - style={[ - pal.text, - bold ? styles.menuItemLabelBold : styles.menuItemLabel, - ]} - numberOfLines={1}> - {label} - </Text> - </TouchableOpacity> - ) - - return ( - <ScrollView testID="menuView" style={[styles.view, pal.view]}> - <TouchableOpacity - testID="profileCardButton" - onPress={() => onNavigate(`/profile/${store.me.handle}`)} - style={styles.profileCard}> - <UserAvatar - size={60} - displayName={store.me.displayName} - handle={store.me.handle} - avatar={store.me.avatar} - /> - <View style={s.flex1}> - <Text - type="title-lg" - style={[pal.text, styles.profileCardDisplayName]} - numberOfLines={1}> - {store.me.displayName || store.me.handle} - </Text> - <Text - style={[pal.textLight, styles.profileCardHandle]} - numberOfLines={1}> - @{store.me.handle} - </Text> + const MenuItem = ({ + icon, + label, + count, + url, + bold, + onPress, + }: { + icon: JSX.Element + label: string + count?: number + url?: string + bold?: boolean + onPress?: () => void + }) => ( + <TouchableOpacity + testID={`menuItemButton-${label}`} + style={styles.menuItem} + onPress={onPress ? onPress : () => onNavigate(url || '/')}> + <View style={[styles.menuItemIconWrapper]}> + {icon} + {count ? ( + <View style={styles.menuItemCount}> + <Text style={styles.menuItemCountLabel}>{count}</Text> </View> - </TouchableOpacity> - <TouchableOpacity - testID="searchBtn" - style={[styles.searchBtn, pal.btn]} - onPress={() => onNavigate('/search')}> - <MagnifyingGlassIcon - style={pal.text as StyleProp<ViewStyle>} - size={25} - /> - <Text type="title" style={[pal.text, styles.searchBtnLabel]}> - Search + ) : undefined} + </View> + <Text + type="title" + style={[ + pal.text, + bold ? styles.menuItemLabelBold : styles.menuItemLabel, + ]} + numberOfLines={1}> + {label} + </Text> + </TouchableOpacity> + ) + + return ( + <ScrollView testID="menuView" style={[styles.view, pal.view]}> + <TouchableOpacity + testID="profileCardButton" + onPress={() => onNavigate(`/profile/${store.me.handle}`)} + style={styles.profileCard}> + <UserAvatar + size={60} + displayName={store.me.displayName} + handle={store.me.handle} + avatar={store.me.avatar} + /> + <View style={s.flex1}> + <Text + type="title-lg" + style={[pal.text, styles.profileCardDisplayName]} + numberOfLines={1}> + {store.me.displayName || store.me.handle} </Text> - </TouchableOpacity> - <View style={[styles.section, pal.border, {paddingTop: 5}]}> - <MenuItem - icon={ - <HomeIcon style={pal.text as StyleProp<ViewStyle>} size="26" /> - } - label="Home" - url="/" - /> - <MenuItem - icon={ - <BellIcon style={pal.text as StyleProp<ViewStyle>} size="28" /> - } - label="Notifications" - url="/notifications" - count={store.me.notificationCount} - /> - <MenuItem - icon={ - <UserIcon - style={pal.text as StyleProp<ViewStyle>} - size="30" - strokeWidth={2} - /> - } - label="Profile" - url={`/profile/${store.me.handle}`} - /> - <MenuItem - icon={ - <CogIcon - style={pal.text as StyleProp<ViewStyle>} - size="30" - strokeWidth={2} - /> - } - label="Settings" - url="/settings" - /> - </View> - <View style={[styles.section, pal.border]}> - <ToggleButton - label="Dark mode" - isSelected={store.shell.darkMode} - onPress={() => store.shell.setDarkMode(!store.shell.darkMode)} - /> - </View> - <View style={styles.footer}> - <Text style={[pal.textLight]}> - Build version {VersionNumber.appVersion} ( - {VersionNumber.buildVersion}) + <Text + style={[pal.textLight, styles.profileCardHandle]} + numberOfLines={1}> + @{store.me.handle} </Text> </View> - <View style={s.footerSpacer} /> - </ScrollView> - ) - }, -) + </TouchableOpacity> + <TouchableOpacity + testID="searchBtn" + style={[styles.searchBtn, pal.btn]} + onPress={() => onNavigate('/search')}> + <MagnifyingGlassIcon + style={pal.text as StyleProp<ViewStyle>} + size={25} + /> + <Text type="title" style={[pal.text, styles.searchBtnLabel]}> + Search + </Text> + </TouchableOpacity> + <View style={[styles.section, pal.border, s.pt5]}> + <MenuItem + icon={<HomeIcon style={pal.text as StyleProp<ViewStyle>} size="26" />} + label="Home" + url="/" + /> + <MenuItem + icon={<BellIcon style={pal.text as StyleProp<ViewStyle>} size="28" />} + label="Notifications" + url="/notifications" + count={store.me.notificationCount} + /> + <MenuItem + icon={ + <UserIcon + style={pal.text as StyleProp<ViewStyle>} + size="30" + strokeWidth={2} + /> + } + label="Profile" + url={`/profile/${store.me.handle}`} + /> + <MenuItem + icon={ + <CogIcon + style={pal.text as StyleProp<ViewStyle>} + size="30" + strokeWidth={2} + /> + } + label="Settings" + url="/settings" + /> + </View> + <View style={[styles.section, pal.border]}> + <ToggleButton + label="Dark mode" + isSelected={store.shell.darkMode} + onPress={() => store.shell.setDarkMode(!store.shell.darkMode)} + /> + </View> + <View style={styles.footer}> + <Text style={[pal.textLight]}> + Build version {VersionNumber.appVersion} ({VersionNumber.buildVersion} + ) + </Text> + </View> + <View style={s.footerSpacer} /> + </ScrollView> + ) +}) const styles = StyleSheet.create({ view: { diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index fb14211eb..62ab7a2ad 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -32,7 +32,7 @@ import {Text} from '../../com/util/text/Text' import {ErrorBoundary} from '../../com/util/ErrorBoundary' import {TabsSelector} from './TabsSelector' import {Composer} from './Composer' -import {colors} from '../../lib/styles' +import {s, colors} from '../../lib/styles' import {clamp} from '../../../lib/numbers' import { GridIcon, @@ -385,7 +385,7 @@ export const MobileShell: React.FC = observer(() => { /> <Animated.View style={[ - {height: '100%'}, + s.h100pct, screenBg, current ? [ @@ -486,7 +486,7 @@ export const MobileShell: React.FC = observer(() => { */ type ScreenRenderDesc = MatchResult & { key: string - navIdx: [number, number] + navIdx: string current: boolean previous: boolean isNewTab: boolean @@ -514,7 +514,7 @@ function constructScreenRenderDesc(nav: NavigationModel): { hasNewTab = hasNewTab || tab.isNewTab return Object.assign(matchRes, { key: `t${tab.id}-s${screen.index}`, - navIdx: [tab.id, screen.id], + navIdx: `${tab.id}-${screen.id}`, current: isCurrent, previous: isPrevious, isNewTab: tab.isNewTab, |