Bluesky app fork with some witchin' additions 💫

Merge branch 'simplify' into main

+180 -232
+2
src/build-flags.ts
··· 1 + export const LOGIN_INCLUDE_DEV_SERVERS = true 2 + export const TABS_ENABLED = false
-1
src/state/index.ts
··· 5 5 import * as libapi from './lib/api' 6 6 import * as storage from './lib/storage' 7 7 8 - export const IS_PROD_BUILD = true 9 8 export const LOCAL_DEV_SERVICE = 'http://localhost:2583' 10 9 export const STAGING_SERVICE = 'https://pds.staging.bsky.dev' 11 10 export const PROD_SERVICE = 'https://bsky.social'
+10 -1
src/state/models/navigation.ts
··· 1 1 import {makeAutoObservable} from 'mobx' 2 - import {isObj, hasProp} from '../lib/type-guards' 2 + import {TABS_ENABLED} from '../../build-flags' 3 3 4 4 let __id = 0 5 5 function genId() { ··· 244 244 // = 245 245 246 246 newTab(url: string, title?: string) { 247 + if (!TABS_ENABLED) { 248 + return this.navigate(url) 249 + } 247 250 const tab = new NavigationTabModel() 248 251 tab.navigate(url, title) 249 252 tab.isNewTab = true ··· 252 255 } 253 256 254 257 setActiveTab(tabIndex: number) { 258 + if (!TABS_ENABLED) { 259 + return 260 + } 255 261 this.tabIndex = Math.max(Math.min(tabIndex, this.tabs.length - 1), 0) 256 262 } 257 263 258 264 closeTab(tabIndex: number) { 265 + if (!TABS_ENABLED) { 266 + return 267 + } 259 268 this.tabs = [ 260 269 ...this.tabs.slice(0, tabIndex), 261 270 ...this.tabs.slice(tabIndex + 1),
+63
src/view/com/composer/Prompt.tsx
··· 1 + import React from 'react' 2 + import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' 3 + import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 + import {colors} from '../../lib/styles' 5 + import {useStores} from '../../../state' 6 + import {UserAvatar} from '../util/UserAvatar' 7 + 8 + export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) { 9 + const store = useStores() 10 + const onPressAvatar = () => { 11 + store.nav.navigate(`/profile/${store.me.handle}`) 12 + } 13 + return ( 14 + <TouchableOpacity style={styles.container} onPress={onPressCompose}> 15 + <TouchableOpacity style={styles.avatar} onPress={onPressAvatar}> 16 + <UserAvatar 17 + size={50} 18 + handle={store.me.handle || ''} 19 + displayName={store.me.displayName} 20 + /> 21 + </TouchableOpacity> 22 + <View style={styles.textContainer}> 23 + <Text style={styles.text}>What's happening?</Text> 24 + </View> 25 + <View style={styles.btn}> 26 + <Text style={styles.btnText}>Post</Text> 27 + </View> 28 + </TouchableOpacity> 29 + ) 30 + } 31 + 32 + const styles = StyleSheet.create({ 33 + container: { 34 + borderRadius: 6, 35 + margin: 2, 36 + marginBottom: 0, 37 + paddingHorizontal: 10, 38 + paddingVertical: 10, 39 + flexDirection: 'row', 40 + alignItems: 'center', 41 + backgroundColor: colors.white, 42 + }, 43 + avatar: { 44 + width: 50, 45 + }, 46 + textContainer: { 47 + marginLeft: 10, 48 + flex: 1, 49 + }, 50 + text: { 51 + color: colors.gray4, 52 + fontSize: 17, 53 + }, 54 + btn: { 55 + backgroundColor: colors.gray1, 56 + paddingVertical: 6, 57 + paddingHorizontal: 14, 58 + borderRadius: 30, 59 + }, 60 + btnText: { 61 + color: colors.gray5, 62 + }, 63 + })
+2 -2
src/view/com/modals/ServerInput.tsx
··· 5 5 import {useStores} from '../../../state' 6 6 import {s, colors} from '../../lib/styles' 7 7 import { 8 - IS_PROD_BUILD, 9 8 LOCAL_DEV_SERVICE, 10 9 STAGING_SERVICE, 11 10 PROD_SERVICE, 12 11 } from '../../../state/index' 12 + import {LOGIN_INCLUDE_DEV_SERVERS} from '../../../build-flags' 13 13 14 14 export const snapPoints = ['80%'] 15 15 ··· 36 36 <Text style={[s.textCenter, s.bold, s.f18]}>Choose Service</Text> 37 37 <BottomSheetScrollView style={styles.inner}> 38 38 <View style={styles.group}> 39 - {!IS_PROD_BUILD ? ( 39 + {LOGIN_INCLUDE_DEV_SERVERS ? ( 40 40 <> 41 41 <TouchableOpacity 42 42 style={styles.btn}
+3 -2
src/view/com/onboard/FeatureExplainer.tsx
··· 15 15 import {useStores} from '../../../state' 16 16 import {s} from '../../lib/styles' 17 17 import {SCENE_EXPLAINER, TABS_EXPLAINER} from '../../lib/assets' 18 + import {TABS_ENABLED} from '../../../build-flags' 18 19 19 20 const Intro = () => ( 20 21 <View style={styles.explainer}> ··· 85 86 const routes = [ 86 87 {key: 'intro', title: 'Intro'}, 87 88 {key: 'scenes', title: 'Scenes'}, 88 - {key: 'tabs', title: 'Tabs'}, 89 - ] 89 + TABS_ENABLED ? {key: 'tabs', title: 'Tabs'} : undefined, 90 + ].filter(Boolean) 90 91 91 92 const onPressSkip = () => store.onboard.next() 92 93 const onPressNext = () => {
+1 -33
src/view/com/post-thread/PostThreadItem.tsx
··· 29 29 const store = useStores() 30 30 const [deleted, setDeleted] = useState(false) 31 31 const record = item.record as unknown as PostType.Record 32 - const hasEngagement = 33 - item.upvoteCount || item.downvoteCount || item.repostCount 32 + const hasEngagement = item.upvoteCount || item.repostCount 34 33 35 34 const itemHref = useMemo(() => { 36 35 const urip = new AtUri(item.uri) ··· 44 43 return `/profile/${item.author.handle}/post/${urip.rkey}/upvoted-by` 45 44 }, [item.uri, item.author.handle]) 46 45 const upvotesTitle = 'Upvotes on this post' 47 - const downvotesHref = useMemo(() => { 48 - const urip = new AtUri(item.uri) 49 - return `/profile/${item.author.handle}/post/${urip.rkey}/downvoted-by` 50 - }, [item.uri, item.author.handle]) 51 - const downvotesTitle = 'Downvotes on this post' 52 46 const repostsHref = useMemo(() => { 53 47 const urip = new AtUri(item.uri) 54 48 return `/profile/${item.author.handle}/post/${urip.rkey}/reposted-by` ··· 70 64 item 71 65 .toggleUpvote() 72 66 .catch(e => console.error('Failed to toggle upvote', record, e)) 73 - } 74 - const onPressToggleDownvote = () => { 75 - item 76 - .toggleDownvote() 77 - .catch(e => console.error('Failed to toggle downvote', record, e)) 78 67 } 79 68 const onDeletePost = () => { 80 69 item.delete().then( ··· 186 175 ) : ( 187 176 <></> 188 177 )} 189 - {item.downvoteCount ? ( 190 - <Link 191 - style={styles.expandedInfoItem} 192 - href={downvotesHref} 193 - title={downvotesTitle}> 194 - <Text style={[s.gray5, s.semiBold, s.f18]}> 195 - <Text style={[s.bold, s.black, s.f18]}> 196 - {item.downvoteCount} 197 - </Text>{' '} 198 - {pluralize(item.downvoteCount, 'downvote')} 199 - </Text> 200 - </Link> 201 - ) : ( 202 - <></> 203 - )} 204 178 </View> 205 179 ) : ( 206 180 <></> ··· 210 184 replyCount={item.replyCount} 211 185 repostCount={item.repostCount} 212 186 upvoteCount={item.upvoteCount} 213 - downvoteCount={item.downvoteCount} 214 187 isReposted={!!item.myState.repost} 215 188 isUpvoted={!!item.myState.upvote} 216 - isDownvoted={!!item.myState.downvote} 217 189 onPressReply={onPressReply} 218 190 onPressToggleRepost={onPressToggleRepost} 219 191 onPressToggleUpvote={onPressToggleUpvote} 220 - onPressToggleDownvote={onPressToggleDownvote} 221 192 /> 222 193 </View> 223 194 </View> ··· 299 270 replyCount={item.replyCount} 300 271 repostCount={item.repostCount} 301 272 upvoteCount={item.upvoteCount} 302 - downvoteCount={item.downvoteCount} 303 273 isReposted={!!item.myState.repost} 304 274 isUpvoted={!!item.myState.upvote} 305 - isDownvoted={!!item.myState.downvote} 306 275 onPressReply={onPressReply} 307 276 onPressToggleRepost={onPressToggleRepost} 308 277 onPressToggleUpvote={onPressToggleUpvote} 309 - onPressToggleDownvote={onPressToggleDownvote} 310 278 /> 311 279 </View> 312 280 </View>
-8
src/view/com/post/Post.tsx
··· 85 85 .toggleUpvote() 86 86 .catch(e => console.error('Failed to toggle upvote', record, e)) 87 87 } 88 - const onPressToggleDownvote = () => { 89 - item 90 - .toggleDownvote() 91 - .catch(e => console.error('Failed to toggle downvote', record, e)) 92 - } 93 88 const onDeletePost = () => { 94 89 item.delete().then( 95 90 () => { ··· 154 149 replyCount={item.replyCount} 155 150 repostCount={item.repostCount} 156 151 upvoteCount={item.upvoteCount} 157 - downvoteCount={item.downvoteCount} 158 152 isReposted={!!item.myState.repost} 159 153 isUpvoted={!!item.myState.upvote} 160 - isDownvoted={!!item.myState.downvote} 161 154 onPressReply={onPressReply} 162 155 onPressToggleRepost={onPressToggleRepost} 163 156 onPressToggleUpvote={onPressToggleUpvote} 164 - onPressToggleDownvote={onPressToggleDownvote} 165 157 /> 166 158 </View> 167 159 </View>
+13 -2
src/view/com/posts/Feed.tsx
··· 6 6 import {ErrorMessage} from '../util/ErrorMessage' 7 7 import {FeedModel, FeedItemModel} from '../../../state/models/feed-view' 8 8 import {FeedItem} from './FeedItem' 9 + import {ComposePrompt} from '../composer/Prompt' 10 + 11 + const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'} 9 12 10 13 export const Feed = observer(function Feed({ 11 14 feed, 12 15 style, 13 16 scrollElRef, 17 + onPressCompose, 14 18 onPressTryAgain, 15 19 }: { 16 20 feed: FeedModel 17 21 style?: StyleProp<ViewStyle> 18 22 scrollElRef?: MutableRefObject<FlatList<any> | null> 23 + onPressCompose?: () => void 19 24 onPressTryAgain?: () => void 20 25 }) { 21 26 // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf 22 27 // VirtualizedList: You have a large list that is slow to update - make sure your 23 28 // renderItem function renders components that follow React performance best practices 24 29 // like PureComponent, shouldComponentUpdate, etc 25 - const renderItem = ({item}: {item: FeedItemModel}) => <FeedItem item={item} /> 30 + const renderItem = ({item}: {item: FeedItemModel}) => { 31 + if (item === COMPOSE_PROMPT_ITEM) { 32 + return <ComposePrompt onPressCompose={onPressCompose} /> 33 + } else { 34 + return <FeedItem item={item} /> 35 + } 36 + } 26 37 const onRefresh = () => { 27 38 feed.refresh().catch(err => console.error('Failed to refresh', err)) 28 39 } ··· 45 56 {feed.hasContent && ( 46 57 <FlatList 47 58 ref={scrollElRef} 48 - data={feed.feed.slice()} 59 + data={[COMPOSE_PROMPT_ITEM].concat(feed.feed.slice())} 49 60 keyExtractor={item => item._reactKey} 50 61 renderItem={renderItem} 51 62 refreshing={feed.isRefreshing}
-8
src/view/com/posts/FeedItem.tsx
··· 53 53 .toggleUpvote() 54 54 .catch(e => console.error('Failed to toggle upvote', record, e)) 55 55 } 56 - const onPressToggleDownvote = () => { 57 - item 58 - .toggleDownvote() 59 - .catch(e => console.error('Failed to toggle downvote', record, e)) 60 - } 61 56 const onDeletePost = () => { 62 57 item.delete().then( 63 58 () => { ··· 150 145 replyCount={item.replyCount} 151 146 repostCount={item.repostCount} 152 147 upvoteCount={item.upvoteCount} 153 - downvoteCount={item.downvoteCount} 154 148 isReposted={!!item.myState.repost} 155 149 isUpvoted={!!item.myState.upvote} 156 - isDownvoted={!!item.myState.downvote} 157 150 onPressReply={onPressReply} 158 151 onPressToggleRepost={onPressToggleRepost} 159 152 onPressToggleUpvote={onPressToggleUpvote} 160 - onPressToggleDownvote={onPressToggleDownvote} 161 153 /> 162 154 </View> 163 155 </View>
+14 -24
src/view/com/profile/ProfileHeader.tsx
··· 20 20 import {pluralize} from '../../lib/strings' 21 21 import {s, colors} from '../../lib/styles' 22 22 import {getGradient} from '../../lib/asset-gen' 23 + import {MagnifyingGlassIcon} from '../../lib/icons' 23 24 import {DropdownBtn, DropdownItem} from '../util/DropdownBtn' 24 25 import Toast from '../util/Toast' 25 26 import {LoadingPlaceholder} from '../util/LoadingPlaceholder' ··· 43 44 const onPressBack = () => { 44 45 store.nav.tab.goBack() 45 46 } 46 - const onPressMyAvatar = () => { 47 - if (store.me.handle) { 48 - store.nav.navigate(`/profile/${store.me.handle}`) 49 - } 47 + const onPressSearch = () => { 48 + store.nav.navigate(`/search`) 50 49 } 51 50 const onPressToggleFollow = () => { 52 51 view?.toggleFollowing().then( ··· 117 116 /> 118 117 </TouchableOpacity> 119 118 ) : undefined} 120 - {store.me.did ? ( 121 - <TouchableOpacity style={styles.myAvatar} onPress={onPressMyAvatar}> 122 - <UserAvatar 123 - size={30} 124 - handle={store.me.handle || ''} 125 - displayName={store.me.displayName} 126 - /> 127 - </TouchableOpacity> 128 - ) : undefined} 119 + <TouchableOpacity style={styles.searchBtn} onPress={onPressSearch}> 120 + <MagnifyingGlassIcon size={19} style={styles.searchIcon} /> 121 + </TouchableOpacity> 129 122 <View style={styles.avi}> 130 123 <LoadingPlaceholder 131 124 width={80} ··· 194 187 /> 195 188 </TouchableOpacity> 196 189 ) : undefined} 197 - {store.me.did ? ( 198 - <TouchableOpacity style={styles.myAvatar} onPress={onPressMyAvatar}> 199 - <UserAvatar 200 - size={30} 201 - handle={store.me.handle || ''} 202 - displayName={store.me.displayName} 203 - /> 204 - </TouchableOpacity> 205 - ) : undefined} 190 + <TouchableOpacity style={styles.searchBtn} onPress={onPressSearch}> 191 + <MagnifyingGlassIcon size={19} style={styles.searchIcon} /> 192 + </TouchableOpacity> 206 193 <View style={styles.avi}> 207 194 <UserAvatar 208 195 size={80} ··· 375 362 height: 14, 376 363 color: colors.black, 377 364 }, 378 - myAvatar: { 365 + searchBtn: { 379 366 position: 'absolute', 380 367 top: 10, 381 368 right: 12, 382 369 backgroundColor: '#ffff', 383 - padding: 1, 370 + padding: 5, 384 371 borderRadius: 30, 372 + }, 373 + searchIcon: { 374 + color: colors.black, 385 375 }, 386 376 avi: { 387 377 position: 'absolute',
+10 -7
src/view/com/util/DropdownBtn.tsx
··· 16 16 import {toShareUrl} from '../../lib/strings' 17 17 import {useStores} from '../../../state' 18 18 import {ConfirmModel} from '../../../state/models/shell-ui' 19 + import {TABS_ENABLED} from '../../../build-flags' 19 20 20 21 export interface DropdownItem { 21 22 icon?: IconProp ··· 84 85 const store = useStores() 85 86 86 87 const dropdownItems: DropdownItem[] = [ 87 - { 88 - icon: ['far', 'clone'], 89 - label: 'Open in new tab', 90 - onPress() { 91 - store.nav.newTab(itemHref) 92 - }, 93 - }, 88 + TABS_ENABLED 89 + ? { 90 + icon: ['far', 'clone'], 91 + label: 'Open in new tab', 92 + onPress() { 93 + store.nav.newTab(itemHref) 94 + }, 95 + } 96 + : undefined, 94 97 { 95 98 icon: 'share', 96 99 label: 'Share...',
+5 -7
src/view/com/util/LoadingPlaceholder.tsx
··· 9 9 } from 'react-native' 10 10 import LinearGradient from 'react-native-linear-gradient' 11 11 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12 - import {UpIcon, DownIcon} from '../../lib/icons' 12 + import {UpIcon} from '../../lib/icons' 13 13 import {s, colors} from '../../lib/styles' 14 14 15 15 export function LoadingPlaceholder({ ··· 93 93 <FontAwesomeIcon 94 94 style={s.gray3} 95 95 icon={['far', 'comment']} 96 - size={14} 96 + size={16} 97 97 /> 98 98 </View> 99 99 <View style={s.flex1}> 100 - <FontAwesomeIcon style={s.gray3} icon="retweet" size={18} /> 101 - </View> 102 - <View style={s.flex1}> 103 - <UpIcon style={s.gray3} size={18} /> 100 + <FontAwesomeIcon style={s.gray3} icon="retweet" size={20} /> 104 101 </View> 105 102 <View style={s.flex1}> 106 - <DownIcon style={s.gray3} size={18} /> 103 + <UpIcon style={s.gray3} size={19} strokeWidth={1.7} /> 107 104 </View> 105 + <View style={s.flex1}></View> 108 106 </View> 109 107 </View> 110 108 </View>
+11 -52
src/view/com/util/PostCtrls.tsx
··· 8 8 interpolate, 9 9 } from 'react-native-reanimated' 10 10 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 11 - import {UpIcon, UpIconSolid, DownIcon, DownIconSolid} from '../../lib/icons' 11 + import {UpIcon, UpIconSolid} from '../../lib/icons' 12 12 import {s, colors} from '../../lib/styles' 13 13 14 14 interface PostCtrlsOpts { 15 15 replyCount: number 16 16 repostCount: number 17 17 upvoteCount: number 18 - downvoteCount: number 19 18 isReposted: boolean 20 19 isUpvoted: boolean 21 - isDownvoted: boolean 22 20 onPressReply: () => void 23 21 onPressToggleRepost: () => void 24 22 onPressToggleUpvote: () => void 25 - onPressToggleDownvote: () => void 26 23 } 27 24 28 25 export function PostCtrls(opts: PostCtrlsOpts) { 29 26 const interp1 = useSharedValue<number>(0) 30 27 const interp2 = useSharedValue<number>(0) 31 - const interp3 = useSharedValue<number>(0) 32 28 33 29 const anim1Style = useAnimatedStyle(() => ({ 34 30 transform: [{scale: interpolate(interp1.value, [0, 1.0], [1.0, 3.0])}], ··· 38 34 transform: [{scale: interpolate(interp2.value, [0, 1.0], [1.0, 3.0])}], 39 35 opacity: interpolate(interp2.value, [0, 1.0], [1.0, 0.0]), 40 36 })) 41 - const anim3Style = useAnimatedStyle(() => ({ 42 - transform: [{scale: interpolate(interp3.value, [0, 1.0], [1.0, 3.0])}], 43 - opacity: interpolate(interp3.value, [0, 1.0], [1.0, 0.0]), 44 - })) 45 37 46 38 const onPressToggleRepostWrapper = () => { 47 39 if (!opts.isReposted) { ··· 59 51 } 60 52 opts.onPressToggleUpvote() 61 53 } 62 - const onPressToggleDownvoteWrapper = () => { 63 - if (!opts.isDownvoted) { 64 - interp3.value = withTiming(1, {duration: 300}, () => { 65 - interp3.value = withDelay(100, withTiming(0, {duration: 20})) 66 - }) 67 - } 68 - opts.onPressToggleDownvote() 69 - } 70 54 71 55 return ( 72 56 <View style={styles.ctrls}> ··· 75 59 <FontAwesomeIcon 76 60 style={styles.ctrlIcon} 77 61 icon={['far', 'comment']} 78 - size={14} 62 + size={16} 79 63 /> 80 - <Text style={[s.gray5, s.ml5, s.f13]}>{opts.replyCount}</Text> 64 + <Text style={[s.gray5, s.ml5, s.f17]}>{opts.replyCount}</Text> 81 65 </TouchableOpacity> 82 66 </View> 83 67 <View style={s.flex1}> ··· 90 74 opts.isReposted ? styles.ctrlIconReposted : styles.ctrlIcon 91 75 } 92 76 icon="retweet" 93 - size={18} 77 + size={20} 94 78 /> 95 79 </Animated.View> 96 80 <Text 97 81 style={ 98 82 opts.isReposted 99 - ? [s.bold, s.green3, s.f13, s.ml5] 100 - : [s.gray5, s.f13, s.ml5] 83 + ? [s.bold, s.green3, s.f17, s.ml5] 84 + : [s.gray5, s.f17, s.ml5] 101 85 }> 102 86 {opts.repostCount} 103 87 </Text> ··· 109 93 onPress={onPressToggleUpvoteWrapper}> 110 94 <Animated.View style={anim2Style}> 111 95 {opts.isUpvoted ? ( 112 - <UpIconSolid style={styles.ctrlIconUpvoted} size={18} /> 96 + <UpIconSolid style={[styles.ctrlIconUpvoted]} size={19} /> 113 97 ) : ( 114 - <UpIcon style={styles.ctrlIcon} size={18} /> 98 + <UpIcon style={[styles.ctrlIcon]} size={20} strokeWidth={1.5} /> 115 99 )} 116 100 </Animated.View> 117 101 <Text 118 102 style={ 119 103 opts.isUpvoted 120 - ? [s.bold, s.red3, s.f13, s.ml5] 121 - : [s.gray5, s.f13, s.ml5] 104 + ? [s.bold, s.red3, s.f17, s.ml5] 105 + : [s.gray5, s.f17, s.ml5] 122 106 }> 123 107 {opts.upvoteCount} 124 108 </Text> 125 109 </TouchableOpacity> 126 110 </View> 127 - <View style={s.flex1}> 128 - <TouchableOpacity 129 - style={styles.ctrl} 130 - onPress={onPressToggleDownvoteWrapper}> 131 - <Animated.View style={anim3Style}> 132 - {opts.isDownvoted ? ( 133 - <DownIconSolid style={styles.ctrlIconDownvoted} size={18} /> 134 - ) : ( 135 - <DownIcon style={styles.ctrlIcon} size={18} /> 136 - )} 137 - </Animated.View> 138 - <Text 139 - style={ 140 - opts.isDownvoted 141 - ? [s.bold, s.blue3, s.f13, s.ml5] 142 - : [s.gray5, s.f13, s.ml5] 143 - }> 144 - {opts.downvoteCount} 145 - </Text> 146 - </TouchableOpacity> 147 - </View> 111 + <View style={s.flex1}></View> 148 112 </View> 149 113 ) 150 114 } ··· 152 116 const styles = StyleSheet.create({ 153 117 ctrls: { 154 118 flexDirection: 'row', 155 - paddingRight: 20, 156 119 }, 157 120 ctrl: { 158 121 flexDirection: 'row', 159 122 alignItems: 'center', 160 - paddingLeft: 4, 161 123 paddingRight: 4, 162 124 }, 163 125 ctrlIcon: { ··· 168 130 }, 169 131 ctrlIconUpvoted: { 170 132 color: colors.red3, 171 - }, 172 - ctrlIconDownvoted: { 173 - color: colors.blue3, 174 133 }, 175 134 })
+24 -19
src/view/com/util/ViewHeader.tsx
··· 3 3 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 4 import {UserAvatar} from './UserAvatar' 5 5 import {colors} from '../../lib/styles' 6 + import {MagnifyingGlassIcon} from '../../lib/icons' 6 7 import {useStores} from '../../../state' 7 8 8 9 export function ViewHeader({ ··· 16 17 const onPressBack = () => { 17 18 store.nav.tab.goBack() 18 19 } 19 - const onPressAvatar = () => { 20 - if (store.me.handle) { 21 - store.nav.navigate(`/profile/${store.me.handle}`) 22 - } 20 + const onPressSearch = () => { 21 + store.nav.navigate(`/search`) 23 22 } 24 23 return ( 25 24 <View style={styles.header}> 26 25 {store.nav.tab.canGoBack ? ( 27 26 <TouchableOpacity onPress={onPressBack} style={styles.backIcon}> 28 - <FontAwesomeIcon size={18} icon="angle-left" style={{marginTop: 3}} /> 27 + <FontAwesomeIcon size={18} icon="angle-left" style={{marginTop: 6}} /> 29 28 </TouchableOpacity> 30 29 ) : ( 31 30 <View style={styles.cornerPlaceholder} /> ··· 38 37 </Text> 39 38 ) : undefined} 40 39 </View> 41 - {store.me.did ? ( 42 - <TouchableOpacity onPress={onPressAvatar}> 43 - <UserAvatar 44 - size={24} 45 - handle={store.me.handle || ''} 46 - displayName={store.me.displayName} 47 - /> 48 - </TouchableOpacity> 49 - ) : ( 50 - <View style={styles.cornerPlaceholder} /> 51 - )} 40 + <TouchableOpacity onPress={onPressSearch} style={styles.searchBtn}> 41 + <MagnifyingGlassIcon size={17} style={styles.searchBtnIcon} /> 42 + </TouchableOpacity> 52 43 </View> 53 44 ) 54 45 } ··· 83 74 }, 84 75 85 76 cornerPlaceholder: { 86 - width: 24, 87 - height: 24, 77 + width: 30, 78 + height: 30, 88 79 }, 89 - backIcon: {width: 24, height: 24}, 80 + backIcon: {width: 30, height: 30}, 81 + searchBtn: { 82 + flexDirection: 'row', 83 + alignItems: 'center', 84 + justifyContent: 'center', 85 + backgroundColor: colors.gray1, 86 + width: 30, 87 + height: 30, 88 + borderRadius: 15, 89 + }, 90 + searchBtnIcon: { 91 + color: colors.black, 92 + position: 'relative', 93 + top: -1, 94 + }, 90 95 })
+12 -29
src/view/lib/icons.tsx
··· 91 91 92 92 // Copyright (c) 2020 Refactoring UI Inc. 93 93 // https://github.com/tailwindlabs/heroicons/blob/master/LICENSE 94 - export function MangifyingGlassIcon({ 94 + export function MagnifyingGlassIcon({ 95 95 style, 96 96 size, 97 97 }: { ··· 116 116 ) 117 117 } 118 118 119 - // Copyright (c) 2020 Refactoring UI Inc. 120 - // https://github.com/tailwindlabs/heroicons/blob/master/LICENSE 121 - export function MangifyingGlassIconSolid({ 122 - style, 123 - size, 124 - }: { 125 - style?: StyleProp<ViewStyle> 126 - size?: string | number 127 - }) { 128 - return ( 129 - <Svg 130 - fill="none" 131 - viewBox="0 0 24 24" 132 - strokeWidth={3} 133 - stroke="currentColor" 134 - width={size || 24} 135 - height={size || 24} 136 - style={style}> 137 - <Path 138 - strokeLinecap="round" 139 - strokeLinejoin="round" 140 - d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" 141 - /> 142 - </Svg> 143 - ) 144 - } 145 - 146 119 // https://github.com/Remix-Design/RemixIcon/blob/master/License 147 120 export function BellIcon({ 148 121 style, ··· 221 194 export function UpIcon({ 222 195 style, 223 196 size, 197 + strokeWidth = 1.3, 224 198 }: { 225 199 style?: StyleProp<ViewStyle> 226 200 size?: string | number 201 + strokeWidth: number 227 202 }) { 228 203 return ( 229 204 <Svg ··· 232 207 height={size || 24} 233 208 style={style}> 234 209 <Path 235 - strokeWidth={1.3} 210 + strokeWidth={strokeWidth} 236 211 stroke="currentColor" 212 + strokeLinecap="round" 213 + strokeLinejoin="round" 237 214 d="M 7 3 L 2 8 L 4.5 8 L 4.5 11.5 L 9.5 11.5 L 9.5 8 L 12 8 L 7 3 Z" 238 215 /> 239 216 </Svg> ··· 257 234 strokeWidth={1.3} 258 235 stroke="currentColor" 259 236 fill="currentColor" 237 + strokeLinecap="round" 238 + strokeLinejoin="round" 260 239 d="M 7 3 L 2 8 L 4.5 8 L 4.5 11.5 L 9.5 11.5 L 9.5 8 L 12 8 L 7 3 Z" 261 240 /> 262 241 </Svg> ··· 279 258 <Path 280 259 strokeWidth={1.3} 281 260 stroke="currentColor" 261 + strokeLinecap="round" 262 + strokeLinejoin="round" 282 263 d="M 7 11.5 L 2 6.5 L 4.5 6.5 L 4.5 3 L 9.5 3 L 9.5 6.5 L 12 6.5 L 7 11.5 Z" 283 264 /> 284 265 </Svg> ··· 302 283 strokeWidth={1.3} 303 284 stroke="currentColor" 304 285 fill="currentColor" 286 + strokeLinecap="round" 287 + strokeLinejoin="round" 305 288 d="M 7 11.5 L 2 6.5 L 4.5 6.5 L 4.5 3 L 9.5 3 L 9.5 6.5 L 12 6.5 L 7 11.5 Z" 306 289 /> 307 290 </Svg>
+2 -3
src/view/screens/Home.tsx
··· 5 5 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 6 6 import {ViewHeader} from '../com/util/ViewHeader' 7 7 import {Feed} from '../com/posts/Feed' 8 - import {FAB} from '../com/util/FloatingActionButton' 9 8 import {useStores} from '../../state' 10 9 import {FeedModel} from '../../state/models/feed-view' 11 10 import {ScreenParams} from '../routes' ··· 65 64 } 66 65 }, [visible, store]) 67 66 68 - const onComposePress = () => { 67 + const onPressCompose = () => { 69 68 store.shell.openComposer({onPost: onCreatePost}) 70 69 } 71 70 const onCreatePost = () => { ··· 87 86 feed={defaultFeedView} 88 87 scrollElRef={scrollElRef} 89 88 style={{flex: 1}} 89 + onPressCompose={onPressCompose} 90 90 onPressTryAgain={onPressTryAgain} 91 91 /> 92 92 {defaultFeedView.hasNewLatest ? ( ··· 95 95 <Text style={styles.loadLatestText}>Load new posts</Text> 96 96 </TouchableOpacity> 97 97 ) : undefined} 98 - <FAB icon="pen-nib" onPress={onComposePress} /> 99 98 </View> 100 99 ) 101 100 })
-5
src/view/screens/Notifications.tsx
··· 1 1 import React, {useState, useEffect} from 'react' 2 2 import {View} from 'react-native' 3 3 import {ViewHeader} from '../com/util/ViewHeader' 4 - import {FAB} from '../com/util/FloatingActionButton' 5 4 import {Feed} from '../com/notifications/Feed' 6 5 import {useStores} from '../../state' 7 6 import {NotificationsViewModel} from '../../state/models/notifications-view' ··· 37 36 } 38 37 }, [visible, store]) 39 38 40 - const onComposePress = () => { 41 - store.shell.openComposer({}) 42 - } 43 39 const onPressTryAgain = () => { 44 40 notesView?.refresh() 45 41 } ··· 48 44 <View style={{flex: 1}}> 49 45 <ViewHeader title="Notifications" /> 50 46 {notesView && <Feed view={notesView} onPressTryAgain={onPressTryAgain} />} 51 - <FAB icon="pen-nib" onPress={onComposePress} /> 52 47 </View> 53 48 ) 54 49 }
-5
src/view/screens/Profile.tsx
··· 3 3 import {observer} from 'mobx-react-lite' 4 4 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 5 5 import {ViewSelector} from '../com/util/ViewSelector' 6 - import {FAB} from '../com/util/FloatingActionButton' 7 6 import {ScreenParams} from '../routes' 8 7 import {ProfileUiModel, Sections} from '../../state/models/profile-ui' 9 8 import {MembershipItem} from '../../state/models/memberships-view' ··· 85 84 }, 86 85 ), 87 86 ) 88 - } 89 - const onComposePress = () => { 90 - store.shell.openComposer({}) 91 87 } 92 88 93 89 // rendering ··· 241 237 ) : ( 242 238 renderHeader() 243 239 )} 244 - <FAB icon="pen-nib" onPress={onComposePress} /> 245 240 </View> 246 241 ) 247 242 })
+8 -24
src/view/shell/mobile/index.tsx
··· 26 26 } from 'react-native-reanimated' 27 27 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 28 28 import {IconProp} from '@fortawesome/fontawesome-svg-core' 29 + import {TABS_ENABLED} from '../../../build-flags' 29 30 import {useStores} from '../../../state' 30 31 import {NavigationModel} from '../../../state/models/navigation' 31 32 import {match, MatchResult} from '../../routes' ··· 41 42 GridIconSolid, 42 43 HomeIcon, 43 44 HomeIconSolid, 44 - MangifyingGlassIcon, 45 - MangifyingGlassIconSolid, 46 45 BellIcon, 47 46 BellIconSolid, 48 47 } from '../../lib/icons' ··· 65 64 | 'home-solid' 66 65 | 'bell' 67 66 | 'bell-solid' 68 - | 'search' 69 - | 'search-solid' 70 67 notificationCount?: number 71 68 tabCount?: number 72 69 onPress?: (event: GestureResponderEvent) => void ··· 85 82 } else if (icon === 'home-solid') { 86 83 IconEl = HomeIconSolid 87 84 size = 24 88 - } else if (icon === 'search') { 89 - IconEl = MangifyingGlassIcon 90 - size = 24 91 - addedStyles = {position: 'relative', top: -1} as ViewStyle 92 - } else if (icon === 'search-solid') { 93 - IconEl = MangifyingGlassIconSolid 94 - size = 24 95 - addedStyles = {position: 'relative', top: -1} as ViewStyle 96 85 } else if (icon === 'bell') { 97 86 IconEl = BellIcon 98 87 size = 24 ··· 147 136 store.nav.navigate('/') 148 137 } 149 138 } 150 - const onPressSearch = () => store.nav.navigate('/search') 151 139 const onPressMenu = () => setMainMenuActive(true) 152 140 const onPressNotifications = () => store.nav.navigate('/notifications') 153 141 const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive) ··· 261 249 } 262 250 263 251 const isAtHome = store.nav.tab.current.url === '/' 264 - const isAtSearch = store.nav.tab.current.url === '/search' 265 252 const isAtNotifications = store.nav.tab.current.url === '/notifications' 266 253 return ( 267 254 <View style={styles.outerContainer}> ··· 326 313 onPress={onPressHome} 327 314 onLongPress={doNewTab('/')} 328 315 /> 329 - <Btn 330 - icon={isAtSearch ? 'search-solid' : 'search'} 331 - onPress={onPressSearch} 332 - onLongPress={doNewTab('/search')} 333 - /> 334 - <Btn 335 - icon={isTabsSelectorActive ? 'clone' : ['far', 'clone']} 336 - onPress={onPressTabs} 337 - tabCount={store.nav.tabCount} 338 - /> 316 + {TABS_ENABLED ? ( 317 + <Btn 318 + icon={isTabsSelectorActive ? 'clone' : ['far', 'clone']} 319 + onPress={onPressTabs} 320 + tabCount={store.nav.tabCount} 321 + /> 322 + ) : undefined} 339 323 <Btn 340 324 icon={isAtNotifications ? 'bell-solid' : 'bell'} 341 325 onPress={onPressNotifications}