Merge branch 'simplify' into main

This commit is contained in:
Paul Frazee 2022-11-21 18:55:08 -06:00
commit e858bb52de
20 changed files with 180 additions and 232 deletions

2
src/build-flags.ts Normal file
View file

@ -0,0 +1,2 @@
export const LOGIN_INCLUDE_DEV_SERVERS = true
export const TABS_ENABLED = false

View file

@ -5,7 +5,6 @@ import {RootStoreModel} from './models/root-store'
import * as libapi from './lib/api' import * as libapi from './lib/api'
import * as storage from './lib/storage' import * as storage from './lib/storage'
export const IS_PROD_BUILD = true
export const LOCAL_DEV_SERVICE = 'http://localhost:2583' export const LOCAL_DEV_SERVICE = 'http://localhost:2583'
export const STAGING_SERVICE = 'https://pds.staging.bsky.dev' export const STAGING_SERVICE = 'https://pds.staging.bsky.dev'
export const PROD_SERVICE = 'https://bsky.social' export const PROD_SERVICE = 'https://bsky.social'

View file

@ -1,5 +1,5 @@
import {makeAutoObservable} from 'mobx' import {makeAutoObservable} from 'mobx'
import {isObj, hasProp} from '../lib/type-guards' import {TABS_ENABLED} from '../../build-flags'
let __id = 0 let __id = 0
function genId() { function genId() {
@ -244,6 +244,9 @@ export class NavigationModel {
// = // =
newTab(url: string, title?: string) { newTab(url: string, title?: string) {
if (!TABS_ENABLED) {
return this.navigate(url)
}
const tab = new NavigationTabModel() const tab = new NavigationTabModel()
tab.navigate(url, title) tab.navigate(url, title)
tab.isNewTab = true tab.isNewTab = true
@ -252,10 +255,16 @@ export class NavigationModel {
} }
setActiveTab(tabIndex: number) { setActiveTab(tabIndex: number) {
if (!TABS_ENABLED) {
return
}
this.tabIndex = Math.max(Math.min(tabIndex, this.tabs.length - 1), 0) this.tabIndex = Math.max(Math.min(tabIndex, this.tabs.length - 1), 0)
} }
closeTab(tabIndex: number) { closeTab(tabIndex: number) {
if (!TABS_ENABLED) {
return
}
this.tabs = [ this.tabs = [
...this.tabs.slice(0, tabIndex), ...this.tabs.slice(0, tabIndex),
...this.tabs.slice(tabIndex + 1), ...this.tabs.slice(tabIndex + 1),

View file

@ -0,0 +1,63 @@
import React from 'react'
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {colors} from '../../lib/styles'
import {useStores} from '../../../state'
import {UserAvatar} from '../util/UserAvatar'
export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
const store = useStores()
const onPressAvatar = () => {
store.nav.navigate(`/profile/${store.me.handle}`)
}
return (
<TouchableOpacity style={styles.container} onPress={onPressCompose}>
<TouchableOpacity style={styles.avatar} onPress={onPressAvatar}>
<UserAvatar
size={50}
handle={store.me.handle || ''}
displayName={store.me.displayName}
/>
</TouchableOpacity>
<View style={styles.textContainer}>
<Text style={styles.text}>What's happening?</Text>
</View>
<View style={styles.btn}>
<Text style={styles.btnText}>Post</Text>
</View>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
container: {
borderRadius: 6,
margin: 2,
marginBottom: 0,
paddingHorizontal: 10,
paddingVertical: 10,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.white,
},
avatar: {
width: 50,
},
textContainer: {
marginLeft: 10,
flex: 1,
},
text: {
color: colors.gray4,
fontSize: 17,
},
btn: {
backgroundColor: colors.gray1,
paddingVertical: 6,
paddingHorizontal: 14,
borderRadius: 30,
},
btnText: {
color: colors.gray5,
},
})

View file

@ -5,11 +5,11 @@ import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import { import {
IS_PROD_BUILD,
LOCAL_DEV_SERVICE, LOCAL_DEV_SERVICE,
STAGING_SERVICE, STAGING_SERVICE,
PROD_SERVICE, PROD_SERVICE,
} from '../../../state/index' } from '../../../state/index'
import {LOGIN_INCLUDE_DEV_SERVERS} from '../../../build-flags'
export const snapPoints = ['80%'] export const snapPoints = ['80%']
@ -36,7 +36,7 @@ export function Component({
<Text style={[s.textCenter, s.bold, s.f18]}>Choose Service</Text> <Text style={[s.textCenter, s.bold, s.f18]}>Choose Service</Text>
<BottomSheetScrollView style={styles.inner}> <BottomSheetScrollView style={styles.inner}>
<View style={styles.group}> <View style={styles.group}>
{!IS_PROD_BUILD ? ( {LOGIN_INCLUDE_DEV_SERVERS ? (
<> <>
<TouchableOpacity <TouchableOpacity
style={styles.btn} style={styles.btn}

View file

@ -15,6 +15,7 @@ import {UserGroupIcon} from '../../lib/icons'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {s} from '../../lib/styles' import {s} from '../../lib/styles'
import {SCENE_EXPLAINER, TABS_EXPLAINER} from '../../lib/assets' import {SCENE_EXPLAINER, TABS_EXPLAINER} from '../../lib/assets'
import {TABS_ENABLED} from '../../../build-flags'
const Intro = () => ( const Intro = () => (
<View style={styles.explainer}> <View style={styles.explainer}>
@ -85,8 +86,8 @@ export const FeatureExplainer = () => {
const routes = [ const routes = [
{key: 'intro', title: 'Intro'}, {key: 'intro', title: 'Intro'},
{key: 'scenes', title: 'Scenes'}, {key: 'scenes', title: 'Scenes'},
{key: 'tabs', title: 'Tabs'}, TABS_ENABLED ? {key: 'tabs', title: 'Tabs'} : undefined,
] ].filter(Boolean)
const onPressSkip = () => store.onboard.next() const onPressSkip = () => store.onboard.next()
const onPressNext = () => { const onPressNext = () => {

View file

@ -29,8 +29,7 @@ export const PostThreadItem = observer(function PostThreadItem({
const store = useStores() const store = useStores()
const [deleted, setDeleted] = useState(false) const [deleted, setDeleted] = useState(false)
const record = item.record as unknown as PostType.Record const record = item.record as unknown as PostType.Record
const hasEngagement = const hasEngagement = item.upvoteCount || item.repostCount
item.upvoteCount || item.downvoteCount || item.repostCount
const itemHref = useMemo(() => { const itemHref = useMemo(() => {
const urip = new AtUri(item.uri) const urip = new AtUri(item.uri)
@ -44,11 +43,6 @@ export const PostThreadItem = observer(function PostThreadItem({
return `/profile/${item.author.handle}/post/${urip.rkey}/upvoted-by` return `/profile/${item.author.handle}/post/${urip.rkey}/upvoted-by`
}, [item.uri, item.author.handle]) }, [item.uri, item.author.handle])
const upvotesTitle = 'Upvotes on this post' const upvotesTitle = 'Upvotes on this post'
const downvotesHref = useMemo(() => {
const urip = new AtUri(item.uri)
return `/profile/${item.author.handle}/post/${urip.rkey}/downvoted-by`
}, [item.uri, item.author.handle])
const downvotesTitle = 'Downvotes on this post'
const repostsHref = useMemo(() => { const repostsHref = useMemo(() => {
const urip = new AtUri(item.uri) const urip = new AtUri(item.uri)
return `/profile/${item.author.handle}/post/${urip.rkey}/reposted-by` return `/profile/${item.author.handle}/post/${urip.rkey}/reposted-by`
@ -71,11 +65,6 @@ export const PostThreadItem = observer(function PostThreadItem({
.toggleUpvote() .toggleUpvote()
.catch(e => console.error('Failed to toggle upvote', record, e)) .catch(e => console.error('Failed to toggle upvote', record, e))
} }
const onPressToggleDownvote = () => {
item
.toggleDownvote()
.catch(e => console.error('Failed to toggle downvote', record, e))
}
const onDeletePost = () => { const onDeletePost = () => {
item.delete().then( item.delete().then(
() => { () => {
@ -186,21 +175,6 @@ export const PostThreadItem = observer(function PostThreadItem({
) : ( ) : (
<></> <></>
)} )}
{item.downvoteCount ? (
<Link
style={styles.expandedInfoItem}
href={downvotesHref}
title={downvotesTitle}>
<Text style={[s.gray5, s.semiBold, s.f18]}>
<Text style={[s.bold, s.black, s.f18]}>
{item.downvoteCount}
</Text>{' '}
{pluralize(item.downvoteCount, 'downvote')}
</Text>
</Link>
) : (
<></>
)}
</View> </View>
) : ( ) : (
<></> <></>
@ -210,14 +184,11 @@ export const PostThreadItem = observer(function PostThreadItem({
replyCount={item.replyCount} replyCount={item.replyCount}
repostCount={item.repostCount} repostCount={item.repostCount}
upvoteCount={item.upvoteCount} upvoteCount={item.upvoteCount}
downvoteCount={item.downvoteCount}
isReposted={!!item.myState.repost} isReposted={!!item.myState.repost}
isUpvoted={!!item.myState.upvote} isUpvoted={!!item.myState.upvote}
isDownvoted={!!item.myState.downvote}
onPressReply={onPressReply} onPressReply={onPressReply}
onPressToggleRepost={onPressToggleRepost} onPressToggleRepost={onPressToggleRepost}
onPressToggleUpvote={onPressToggleUpvote} onPressToggleUpvote={onPressToggleUpvote}
onPressToggleDownvote={onPressToggleDownvote}
/> />
</View> </View>
</View> </View>
@ -299,14 +270,11 @@ export const PostThreadItem = observer(function PostThreadItem({
replyCount={item.replyCount} replyCount={item.replyCount}
repostCount={item.repostCount} repostCount={item.repostCount}
upvoteCount={item.upvoteCount} upvoteCount={item.upvoteCount}
downvoteCount={item.downvoteCount}
isReposted={!!item.myState.repost} isReposted={!!item.myState.repost}
isUpvoted={!!item.myState.upvote} isUpvoted={!!item.myState.upvote}
isDownvoted={!!item.myState.downvote}
onPressReply={onPressReply} onPressReply={onPressReply}
onPressToggleRepost={onPressToggleRepost} onPressToggleRepost={onPressToggleRepost}
onPressToggleUpvote={onPressToggleUpvote} onPressToggleUpvote={onPressToggleUpvote}
onPressToggleDownvote={onPressToggleDownvote}
/> />
</View> </View>
</View> </View>

View file

@ -85,11 +85,6 @@ export const Post = observer(function Post({uri}: {uri: string}) {
.toggleUpvote() .toggleUpvote()
.catch(e => console.error('Failed to toggle upvote', record, e)) .catch(e => console.error('Failed to toggle upvote', record, e))
} }
const onPressToggleDownvote = () => {
item
.toggleDownvote()
.catch(e => console.error('Failed to toggle downvote', record, e))
}
const onDeletePost = () => { const onDeletePost = () => {
item.delete().then( item.delete().then(
() => { () => {
@ -154,14 +149,11 @@ export const Post = observer(function Post({uri}: {uri: string}) {
replyCount={item.replyCount} replyCount={item.replyCount}
repostCount={item.repostCount} repostCount={item.repostCount}
upvoteCount={item.upvoteCount} upvoteCount={item.upvoteCount}
downvoteCount={item.downvoteCount}
isReposted={!!item.myState.repost} isReposted={!!item.myState.repost}
isUpvoted={!!item.myState.upvote} isUpvoted={!!item.myState.upvote}
isDownvoted={!!item.myState.downvote}
onPressReply={onPressReply} onPressReply={onPressReply}
onPressToggleRepost={onPressToggleRepost} onPressToggleRepost={onPressToggleRepost}
onPressToggleUpvote={onPressToggleUpvote} onPressToggleUpvote={onPressToggleUpvote}
onPressToggleDownvote={onPressToggleDownvote}
/> />
</View> </View>
</View> </View>

View file

@ -6,23 +6,34 @@ import {EmptyState} from '../util/EmptyState'
import {ErrorMessage} from '../util/ErrorMessage' import {ErrorMessage} from '../util/ErrorMessage'
import {FeedModel, FeedItemModel} from '../../../state/models/feed-view' import {FeedModel, FeedItemModel} from '../../../state/models/feed-view'
import {FeedItem} from './FeedItem' import {FeedItem} from './FeedItem'
import {ComposePrompt} from '../composer/Prompt'
const COMPOSE_PROMPT_ITEM = {_reactKey: '__prompt__'}
export const Feed = observer(function Feed({ export const Feed = observer(function Feed({
feed, feed,
style, style,
scrollElRef, scrollElRef,
onPressCompose,
onPressTryAgain, onPressTryAgain,
}: { }: {
feed: FeedModel feed: FeedModel
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
scrollElRef?: MutableRefObject<FlatList<any> | null> scrollElRef?: MutableRefObject<FlatList<any> | null>
onPressCompose?: () => void
onPressTryAgain?: () => void onPressTryAgain?: () => void
}) { }) {
// TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
// VirtualizedList: You have a large list that is slow to update - make sure your // VirtualizedList: You have a large list that is slow to update - make sure your
// renderItem function renders components that follow React performance best practices // renderItem function renders components that follow React performance best practices
// like PureComponent, shouldComponentUpdate, etc // like PureComponent, shouldComponentUpdate, etc
const renderItem = ({item}: {item: FeedItemModel}) => <FeedItem item={item} /> const renderItem = ({item}: {item: FeedItemModel}) => {
if (item === COMPOSE_PROMPT_ITEM) {
return <ComposePrompt onPressCompose={onPressCompose} />
} else {
return <FeedItem item={item} />
}
}
const onRefresh = () => { const onRefresh = () => {
feed.refresh().catch(err => console.error('Failed to refresh', err)) feed.refresh().catch(err => console.error('Failed to refresh', err))
} }
@ -45,7 +56,7 @@ export const Feed = observer(function Feed({
{feed.hasContent && ( {feed.hasContent && (
<FlatList <FlatList
ref={scrollElRef} ref={scrollElRef}
data={feed.feed.slice()} data={[COMPOSE_PROMPT_ITEM].concat(feed.feed.slice())}
keyExtractor={item => item._reactKey} keyExtractor={item => item._reactKey}
renderItem={renderItem} renderItem={renderItem}
refreshing={feed.isRefreshing} refreshing={feed.isRefreshing}

View file

@ -53,11 +53,6 @@ export const FeedItem = observer(function FeedItem({
.toggleUpvote() .toggleUpvote()
.catch(e => console.error('Failed to toggle upvote', record, e)) .catch(e => console.error('Failed to toggle upvote', record, e))
} }
const onPressToggleDownvote = () => {
item
.toggleDownvote()
.catch(e => console.error('Failed to toggle downvote', record, e))
}
const onDeletePost = () => { const onDeletePost = () => {
item.delete().then( item.delete().then(
() => { () => {
@ -150,14 +145,11 @@ export const FeedItem = observer(function FeedItem({
replyCount={item.replyCount} replyCount={item.replyCount}
repostCount={item.repostCount} repostCount={item.repostCount}
upvoteCount={item.upvoteCount} upvoteCount={item.upvoteCount}
downvoteCount={item.downvoteCount}
isReposted={!!item.myState.repost} isReposted={!!item.myState.repost}
isUpvoted={!!item.myState.upvote} isUpvoted={!!item.myState.upvote}
isDownvoted={!!item.myState.downvote}
onPressReply={onPressReply} onPressReply={onPressReply}
onPressToggleRepost={onPressToggleRepost} onPressToggleRepost={onPressToggleRepost}
onPressToggleUpvote={onPressToggleUpvote} onPressToggleUpvote={onPressToggleUpvote}
onPressToggleDownvote={onPressToggleDownvote}
/> />
</View> </View>
</View> </View>

View file

@ -20,6 +20,7 @@ import {
import {pluralize} from '../../lib/strings' import {pluralize} from '../../lib/strings'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
import {getGradient} from '../../lib/asset-gen' import {getGradient} from '../../lib/asset-gen'
import {MagnifyingGlassIcon} from '../../lib/icons'
import {DropdownBtn, DropdownItem} from '../util/DropdownBtn' import {DropdownBtn, DropdownItem} from '../util/DropdownBtn'
import Toast from '../util/Toast' import Toast from '../util/Toast'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder' import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
@ -43,10 +44,8 @@ export const ProfileHeader = observer(function ProfileHeader({
const onPressBack = () => { const onPressBack = () => {
store.nav.tab.goBack() store.nav.tab.goBack()
} }
const onPressMyAvatar = () => { const onPressSearch = () => {
if (store.me.handle) { store.nav.navigate(`/search`)
store.nav.navigate(`/profile/${store.me.handle}`)
}
} }
const onPressToggleFollow = () => { const onPressToggleFollow = () => {
view?.toggleFollowing().then( view?.toggleFollowing().then(
@ -117,15 +116,9 @@ export const ProfileHeader = observer(function ProfileHeader({
/> />
</TouchableOpacity> </TouchableOpacity>
) : undefined} ) : undefined}
{store.me.did ? ( <TouchableOpacity style={styles.searchBtn} onPress={onPressSearch}>
<TouchableOpacity style={styles.myAvatar} onPress={onPressMyAvatar}> <MagnifyingGlassIcon size={19} style={styles.searchIcon} />
<UserAvatar
size={30}
handle={store.me.handle || ''}
displayName={store.me.displayName}
/>
</TouchableOpacity> </TouchableOpacity>
) : undefined}
<View style={styles.avi}> <View style={styles.avi}>
<LoadingPlaceholder <LoadingPlaceholder
width={80} width={80}
@ -194,15 +187,9 @@ export const ProfileHeader = observer(function ProfileHeader({
/> />
</TouchableOpacity> </TouchableOpacity>
) : undefined} ) : undefined}
{store.me.did ? ( <TouchableOpacity style={styles.searchBtn} onPress={onPressSearch}>
<TouchableOpacity style={styles.myAvatar} onPress={onPressMyAvatar}> <MagnifyingGlassIcon size={19} style={styles.searchIcon} />
<UserAvatar
size={30}
handle={store.me.handle || ''}
displayName={store.me.displayName}
/>
</TouchableOpacity> </TouchableOpacity>
) : undefined}
<View style={styles.avi}> <View style={styles.avi}>
<UserAvatar <UserAvatar
size={80} size={80}
@ -375,14 +362,17 @@ const styles = StyleSheet.create({
height: 14, height: 14,
color: colors.black, color: colors.black,
}, },
myAvatar: { searchBtn: {
position: 'absolute', position: 'absolute',
top: 10, top: 10,
right: 12, right: 12,
backgroundColor: '#ffff', backgroundColor: '#ffff',
padding: 1, padding: 5,
borderRadius: 30, borderRadius: 30,
}, },
searchIcon: {
color: colors.black,
},
avi: { avi: {
position: 'absolute', position: 'absolute',
top: 80, top: 80,

View file

@ -16,6 +16,7 @@ import {colors} from '../../lib/styles'
import {toShareUrl} from '../../lib/strings' import {toShareUrl} from '../../lib/strings'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {ConfirmModel} from '../../../state/models/shell-ui' import {ConfirmModel} from '../../../state/models/shell-ui'
import {TABS_ENABLED} from '../../../build-flags'
export interface DropdownItem { export interface DropdownItem {
icon?: IconProp icon?: IconProp
@ -84,13 +85,15 @@ export function PostDropdownBtn({
const store = useStores() const store = useStores()
const dropdownItems: DropdownItem[] = [ const dropdownItems: DropdownItem[] = [
{ TABS_ENABLED
? {
icon: ['far', 'clone'], icon: ['far', 'clone'],
label: 'Open in new tab', label: 'Open in new tab',
onPress() { onPress() {
store.nav.newTab(itemHref) store.nav.newTab(itemHref)
}, },
}, }
: undefined,
{ {
icon: 'share', icon: 'share',
label: 'Share...', label: 'Share...',

View file

@ -9,7 +9,7 @@ import {
} from 'react-native' } from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {UpIcon, DownIcon} from '../../lib/icons' import {UpIcon} from '../../lib/icons'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
export function LoadingPlaceholder({ export function LoadingPlaceholder({
@ -93,18 +93,16 @@ export function PostLoadingPlaceholder({
<FontAwesomeIcon <FontAwesomeIcon
style={s.gray3} style={s.gray3}
icon={['far', 'comment']} icon={['far', 'comment']}
size={14} size={16}
/> />
</View> </View>
<View style={s.flex1}> <View style={s.flex1}>
<FontAwesomeIcon style={s.gray3} icon="retweet" size={18} /> <FontAwesomeIcon style={s.gray3} icon="retweet" size={20} />
</View> </View>
<View style={s.flex1}> <View style={s.flex1}>
<UpIcon style={s.gray3} size={18} /> <UpIcon style={s.gray3} size={19} strokeWidth={1.7} />
</View>
<View style={s.flex1}>
<DownIcon style={s.gray3} size={18} />
</View> </View>
<View style={s.flex1}></View>
</View> </View>
</View> </View>
</View> </View>

View file

@ -8,27 +8,23 @@ import Animated, {
interpolate, interpolate,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {UpIcon, UpIconSolid, DownIcon, DownIconSolid} from '../../lib/icons' import {UpIcon, UpIconSolid} from '../../lib/icons'
import {s, colors} from '../../lib/styles' import {s, colors} from '../../lib/styles'
interface PostCtrlsOpts { interface PostCtrlsOpts {
replyCount: number replyCount: number
repostCount: number repostCount: number
upvoteCount: number upvoteCount: number
downvoteCount: number
isReposted: boolean isReposted: boolean
isUpvoted: boolean isUpvoted: boolean
isDownvoted: boolean
onPressReply: () => void onPressReply: () => void
onPressToggleRepost: () => void onPressToggleRepost: () => void
onPressToggleUpvote: () => void onPressToggleUpvote: () => void
onPressToggleDownvote: () => void
} }
export function PostCtrls(opts: PostCtrlsOpts) { export function PostCtrls(opts: PostCtrlsOpts) {
const interp1 = useSharedValue<number>(0) const interp1 = useSharedValue<number>(0)
const interp2 = useSharedValue<number>(0) const interp2 = useSharedValue<number>(0)
const interp3 = useSharedValue<number>(0)
const anim1Style = useAnimatedStyle(() => ({ const anim1Style = useAnimatedStyle(() => ({
transform: [{scale: interpolate(interp1.value, [0, 1.0], [1.0, 3.0])}], transform: [{scale: interpolate(interp1.value, [0, 1.0], [1.0, 3.0])}],
@ -38,10 +34,6 @@ export function PostCtrls(opts: PostCtrlsOpts) {
transform: [{scale: interpolate(interp2.value, [0, 1.0], [1.0, 3.0])}], transform: [{scale: interpolate(interp2.value, [0, 1.0], [1.0, 3.0])}],
opacity: interpolate(interp2.value, [0, 1.0], [1.0, 0.0]), opacity: interpolate(interp2.value, [0, 1.0], [1.0, 0.0]),
})) }))
const anim3Style = useAnimatedStyle(() => ({
transform: [{scale: interpolate(interp3.value, [0, 1.0], [1.0, 3.0])}],
opacity: interpolate(interp3.value, [0, 1.0], [1.0, 0.0]),
}))
const onPressToggleRepostWrapper = () => { const onPressToggleRepostWrapper = () => {
if (!opts.isReposted) { if (!opts.isReposted) {
@ -59,14 +51,6 @@ export function PostCtrls(opts: PostCtrlsOpts) {
} }
opts.onPressToggleUpvote() opts.onPressToggleUpvote()
} }
const onPressToggleDownvoteWrapper = () => {
if (!opts.isDownvoted) {
interp3.value = withTiming(1, {duration: 300}, () => {
interp3.value = withDelay(100, withTiming(0, {duration: 20}))
})
}
opts.onPressToggleDownvote()
}
return ( return (
<View style={styles.ctrls}> <View style={styles.ctrls}>
@ -75,9 +59,9 @@ export function PostCtrls(opts: PostCtrlsOpts) {
<FontAwesomeIcon <FontAwesomeIcon
style={styles.ctrlIcon} style={styles.ctrlIcon}
icon={['far', 'comment']} icon={['far', 'comment']}
size={14} size={16}
/> />
<Text style={[s.gray5, s.ml5, s.f13]}>{opts.replyCount}</Text> <Text style={[s.gray5, s.ml5, s.f17]}>{opts.replyCount}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={s.flex1}> <View style={s.flex1}>
@ -90,14 +74,14 @@ export function PostCtrls(opts: PostCtrlsOpts) {
opts.isReposted ? styles.ctrlIconReposted : styles.ctrlIcon opts.isReposted ? styles.ctrlIconReposted : styles.ctrlIcon
} }
icon="retweet" icon="retweet"
size={18} size={20}
/> />
</Animated.View> </Animated.View>
<Text <Text
style={ style={
opts.isReposted opts.isReposted
? [s.bold, s.green3, s.f13, s.ml5] ? [s.bold, s.green3, s.f17, s.ml5]
: [s.gray5, s.f13, s.ml5] : [s.gray5, s.f17, s.ml5]
}> }>
{opts.repostCount} {opts.repostCount}
</Text> </Text>
@ -109,42 +93,22 @@ export function PostCtrls(opts: PostCtrlsOpts) {
onPress={onPressToggleUpvoteWrapper}> onPress={onPressToggleUpvoteWrapper}>
<Animated.View style={anim2Style}> <Animated.View style={anim2Style}>
{opts.isUpvoted ? ( {opts.isUpvoted ? (
<UpIconSolid style={styles.ctrlIconUpvoted} size={18} /> <UpIconSolid style={[styles.ctrlIconUpvoted]} size={19} />
) : ( ) : (
<UpIcon style={styles.ctrlIcon} size={18} /> <UpIcon style={[styles.ctrlIcon]} size={20} strokeWidth={1.5} />
)} )}
</Animated.View> </Animated.View>
<Text <Text
style={ style={
opts.isUpvoted opts.isUpvoted
? [s.bold, s.red3, s.f13, s.ml5] ? [s.bold, s.red3, s.f17, s.ml5]
: [s.gray5, s.f13, s.ml5] : [s.gray5, s.f17, s.ml5]
}> }>
{opts.upvoteCount} {opts.upvoteCount}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View style={s.flex1}> <View style={s.flex1}></View>
<TouchableOpacity
style={styles.ctrl}
onPress={onPressToggleDownvoteWrapper}>
<Animated.View style={anim3Style}>
{opts.isDownvoted ? (
<DownIconSolid style={styles.ctrlIconDownvoted} size={18} />
) : (
<DownIcon style={styles.ctrlIcon} size={18} />
)}
</Animated.View>
<Text
style={
opts.isDownvoted
? [s.bold, s.blue3, s.f13, s.ml5]
: [s.gray5, s.f13, s.ml5]
}>
{opts.downvoteCount}
</Text>
</TouchableOpacity>
</View>
</View> </View>
) )
} }
@ -152,12 +116,10 @@ export function PostCtrls(opts: PostCtrlsOpts) {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
ctrls: { ctrls: {
flexDirection: 'row', flexDirection: 'row',
paddingRight: 20,
}, },
ctrl: { ctrl: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingLeft: 4,
paddingRight: 4, paddingRight: 4,
}, },
ctrlIcon: { ctrlIcon: {
@ -169,7 +131,4 @@ const styles = StyleSheet.create({
ctrlIconUpvoted: { ctrlIconUpvoted: {
color: colors.red3, color: colors.red3,
}, },
ctrlIconDownvoted: {
color: colors.blue3,
},
}) })

View file

@ -3,6 +3,7 @@ import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {UserAvatar} from './UserAvatar' import {UserAvatar} from './UserAvatar'
import {colors} from '../../lib/styles' import {colors} from '../../lib/styles'
import {MagnifyingGlassIcon} from '../../lib/icons'
import {useStores} from '../../../state' import {useStores} from '../../../state'
export function ViewHeader({ export function ViewHeader({
@ -16,16 +17,14 @@ export function ViewHeader({
const onPressBack = () => { const onPressBack = () => {
store.nav.tab.goBack() store.nav.tab.goBack()
} }
const onPressAvatar = () => { const onPressSearch = () => {
if (store.me.handle) { store.nav.navigate(`/search`)
store.nav.navigate(`/profile/${store.me.handle}`)
}
} }
return ( return (
<View style={styles.header}> <View style={styles.header}>
{store.nav.tab.canGoBack ? ( {store.nav.tab.canGoBack ? (
<TouchableOpacity onPress={onPressBack} style={styles.backIcon}> <TouchableOpacity onPress={onPressBack} style={styles.backIcon}>
<FontAwesomeIcon size={18} icon="angle-left" style={{marginTop: 3}} /> <FontAwesomeIcon size={18} icon="angle-left" style={{marginTop: 6}} />
</TouchableOpacity> </TouchableOpacity>
) : ( ) : (
<View style={styles.cornerPlaceholder} /> <View style={styles.cornerPlaceholder} />
@ -38,17 +37,9 @@ export function ViewHeader({
</Text> </Text>
) : undefined} ) : undefined}
</View> </View>
{store.me.did ? ( <TouchableOpacity onPress={onPressSearch} style={styles.searchBtn}>
<TouchableOpacity onPress={onPressAvatar}> <MagnifyingGlassIcon size={17} style={styles.searchBtnIcon} />
<UserAvatar
size={24}
handle={store.me.handle || ''}
displayName={store.me.displayName}
/>
</TouchableOpacity> </TouchableOpacity>
) : (
<View style={styles.cornerPlaceholder} />
)}
</View> </View>
) )
} }
@ -83,8 +74,22 @@ const styles = StyleSheet.create({
}, },
cornerPlaceholder: { cornerPlaceholder: {
width: 24, width: 30,
height: 24, height: 30,
},
backIcon: {width: 30, height: 30},
searchBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.gray1,
width: 30,
height: 30,
borderRadius: 15,
},
searchBtnIcon: {
color: colors.black,
position: 'relative',
top: -1,
}, },
backIcon: {width: 24, height: 24},
}) })

View file

@ -91,7 +91,7 @@ export function HomeIconSolid({
// Copyright (c) 2020 Refactoring UI Inc. // Copyright (c) 2020 Refactoring UI Inc.
// https://github.com/tailwindlabs/heroicons/blob/master/LICENSE // https://github.com/tailwindlabs/heroicons/blob/master/LICENSE
export function MangifyingGlassIcon({ export function MagnifyingGlassIcon({
style, style,
size, size,
}: { }: {
@ -116,33 +116,6 @@ export function MangifyingGlassIcon({
) )
} }
// Copyright (c) 2020 Refactoring UI Inc.
// https://github.com/tailwindlabs/heroicons/blob/master/LICENSE
export function MangifyingGlassIconSolid({
style,
size,
}: {
style?: StyleProp<ViewStyle>
size?: string | number
}) {
return (
<Svg
fill="none"
viewBox="0 0 24 24"
strokeWidth={3}
stroke="currentColor"
width={size || 24}
height={size || 24}
style={style}>
<Path
strokeLinecap="round"
strokeLinejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</Svg>
)
}
// https://github.com/Remix-Design/RemixIcon/blob/master/License // https://github.com/Remix-Design/RemixIcon/blob/master/License
export function BellIcon({ export function BellIcon({
style, style,
@ -221,9 +194,11 @@ export function UserGroupIcon({
export function UpIcon({ export function UpIcon({
style, style,
size, size,
strokeWidth = 1.3,
}: { }: {
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
size?: string | number size?: string | number
strokeWidth: number
}) { }) {
return ( return (
<Svg <Svg
@ -232,8 +207,10 @@ export function UpIcon({
height={size || 24} height={size || 24}
style={style}> style={style}>
<Path <Path
strokeWidth={1.3} strokeWidth={strokeWidth}
stroke="currentColor" stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
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" 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"
/> />
</Svg> </Svg>
@ -257,6 +234,8 @@ export function UpIconSolid({
strokeWidth={1.3} strokeWidth={1.3}
stroke="currentColor" stroke="currentColor"
fill="currentColor" fill="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
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" 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"
/> />
</Svg> </Svg>
@ -279,6 +258,8 @@ export function DownIcon({
<Path <Path
strokeWidth={1.3} strokeWidth={1.3}
stroke="currentColor" stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
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" 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"
/> />
</Svg> </Svg>
@ -302,6 +283,8 @@ export function DownIconSolid({
strokeWidth={1.3} strokeWidth={1.3}
stroke="currentColor" stroke="currentColor"
fill="currentColor" fill="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
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" 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"
/> />
</Svg> </Svg>

View file

@ -5,7 +5,6 @@ import useAppState from 'react-native-appstate-hook'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/posts/Feed' import {Feed} from '../com/posts/Feed'
import {FAB} from '../com/util/FloatingActionButton'
import {useStores} from '../../state' import {useStores} from '../../state'
import {FeedModel} from '../../state/models/feed-view' import {FeedModel} from '../../state/models/feed-view'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
@ -65,7 +64,7 @@ export const Home = observer(function Home({
} }
}, [visible, store]) }, [visible, store])
const onComposePress = () => { const onPressCompose = () => {
store.shell.openComposer({onPost: onCreatePost}) store.shell.openComposer({onPost: onCreatePost})
} }
const onCreatePost = () => { const onCreatePost = () => {
@ -87,6 +86,7 @@ export const Home = observer(function Home({
feed={defaultFeedView} feed={defaultFeedView}
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
style={{flex: 1}} style={{flex: 1}}
onPressCompose={onPressCompose}
onPressTryAgain={onPressTryAgain} onPressTryAgain={onPressTryAgain}
/> />
{defaultFeedView.hasNewLatest ? ( {defaultFeedView.hasNewLatest ? (
@ -95,7 +95,6 @@ export const Home = observer(function Home({
<Text style={styles.loadLatestText}>Load new posts</Text> <Text style={styles.loadLatestText}>Load new posts</Text>
</TouchableOpacity> </TouchableOpacity>
) : undefined} ) : undefined}
<FAB icon="pen-nib" onPress={onComposePress} />
</View> </View>
) )
}) })

View file

@ -1,7 +1,6 @@
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {FAB} from '../com/util/FloatingActionButton'
import {Feed} from '../com/notifications/Feed' import {Feed} from '../com/notifications/Feed'
import {useStores} from '../../state' import {useStores} from '../../state'
import {NotificationsViewModel} from '../../state/models/notifications-view' import {NotificationsViewModel} from '../../state/models/notifications-view'
@ -37,9 +36,6 @@ export const Notifications = ({navIdx, visible}: ScreenParams) => {
} }
}, [visible, store]) }, [visible, store])
const onComposePress = () => {
store.shell.openComposer({})
}
const onPressTryAgain = () => { const onPressTryAgain = () => {
notesView?.refresh() notesView?.refresh()
} }
@ -48,7 +44,6 @@ export const Notifications = ({navIdx, visible}: ScreenParams) => {
<View style={{flex: 1}}> <View style={{flex: 1}}>
<ViewHeader title="Notifications" /> <ViewHeader title="Notifications" />
{notesView && <Feed view={notesView} onPressTryAgain={onPressTryAgain} />} {notesView && <Feed view={notesView} onPressTryAgain={onPressTryAgain} />}
<FAB icon="pen-nib" onPress={onComposePress} />
</View> </View>
) )
} }

View file

@ -3,7 +3,6 @@ import {StyleSheet, Text, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ViewSelector} from '../com/util/ViewSelector' import {ViewSelector} from '../com/util/ViewSelector'
import {FAB} from '../com/util/FloatingActionButton'
import {ScreenParams} from '../routes' import {ScreenParams} from '../routes'
import {ProfileUiModel, Sections} from '../../state/models/profile-ui' import {ProfileUiModel, Sections} from '../../state/models/profile-ui'
import {MembershipItem} from '../../state/models/memberships-view' import {MembershipItem} from '../../state/models/memberships-view'
@ -86,9 +85,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
), ),
) )
} }
const onComposePress = () => {
store.shell.openComposer({})
}
// rendering // rendering
// = // =
@ -241,7 +237,6 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
) : ( ) : (
renderHeader() renderHeader()
)} )}
<FAB icon="pen-nib" onPress={onComposePress} />
</View> </View>
) )
}) })

View file

@ -26,6 +26,7 @@ import Animated, {
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {IconProp} from '@fortawesome/fontawesome-svg-core' import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {TABS_ENABLED} from '../../../build-flags'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {NavigationModel} from '../../../state/models/navigation' import {NavigationModel} from '../../../state/models/navigation'
import {match, MatchResult} from '../../routes' import {match, MatchResult} from '../../routes'
@ -41,8 +42,6 @@ import {
GridIconSolid, GridIconSolid,
HomeIcon, HomeIcon,
HomeIconSolid, HomeIconSolid,
MangifyingGlassIcon,
MangifyingGlassIconSolid,
BellIcon, BellIcon,
BellIconSolid, BellIconSolid,
} from '../../lib/icons' } from '../../lib/icons'
@ -65,8 +64,6 @@ const Btn = ({
| 'home-solid' | 'home-solid'
| 'bell' | 'bell'
| 'bell-solid' | 'bell-solid'
| 'search'
| 'search-solid'
notificationCount?: number notificationCount?: number
tabCount?: number tabCount?: number
onPress?: (event: GestureResponderEvent) => void onPress?: (event: GestureResponderEvent) => void
@ -85,14 +82,6 @@ const Btn = ({
} else if (icon === 'home-solid') { } else if (icon === 'home-solid') {
IconEl = HomeIconSolid IconEl = HomeIconSolid
size = 24 size = 24
} else if (icon === 'search') {
IconEl = MangifyingGlassIcon
size = 24
addedStyles = {position: 'relative', top: -1} as ViewStyle
} else if (icon === 'search-solid') {
IconEl = MangifyingGlassIconSolid
size = 24
addedStyles = {position: 'relative', top: -1} as ViewStyle
} else if (icon === 'bell') { } else if (icon === 'bell') {
IconEl = BellIcon IconEl = BellIcon
size = 24 size = 24
@ -147,7 +136,6 @@ export const MobileShell: React.FC = observer(() => {
store.nav.navigate('/') store.nav.navigate('/')
} }
} }
const onPressSearch = () => store.nav.navigate('/search')
const onPressMenu = () => setMainMenuActive(true) const onPressMenu = () => setMainMenuActive(true)
const onPressNotifications = () => store.nav.navigate('/notifications') const onPressNotifications = () => store.nav.navigate('/notifications')
const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive) const onPressTabs = () => toggleTabsMenu(!isTabsSelectorActive)
@ -261,7 +249,6 @@ export const MobileShell: React.FC = observer(() => {
} }
const isAtHome = store.nav.tab.current.url === '/' const isAtHome = store.nav.tab.current.url === '/'
const isAtSearch = store.nav.tab.current.url === '/search'
const isAtNotifications = store.nav.tab.current.url === '/notifications' const isAtNotifications = store.nav.tab.current.url === '/notifications'
return ( return (
<View style={styles.outerContainer}> <View style={styles.outerContainer}>
@ -326,16 +313,13 @@ export const MobileShell: React.FC = observer(() => {
onPress={onPressHome} onPress={onPressHome}
onLongPress={doNewTab('/')} onLongPress={doNewTab('/')}
/> />
<Btn {TABS_ENABLED ? (
icon={isAtSearch ? 'search-solid' : 'search'}
onPress={onPressSearch}
onLongPress={doNewTab('/search')}
/>
<Btn <Btn
icon={isTabsSelectorActive ? 'clone' : ['far', 'clone']} icon={isTabsSelectorActive ? 'clone' : ['far', 'clone']}
onPress={onPressTabs} onPress={onPressTabs}
tabCount={store.nav.tabCount} tabCount={store.nav.tabCount}
/> />
) : undefined}
<Btn <Btn
icon={isAtNotifications ? 'bell-solid' : 'bell'} icon={isAtNotifications ? 'bell-solid' : 'bell'}
onPress={onPressNotifications} onPress={onPressNotifications}