Rework profile header

zio/stable
Paul Frazee 2022-09-05 16:57:20 -05:00
parent cb310ab1c1
commit 2ec09ba545
7 changed files with 224 additions and 40 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -2,19 +2,22 @@ import React, {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import { import {
ActivityIndicator, ActivityIndicator,
Button,
Image, Image,
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
View, View,
} from 'react-native' } from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {ProfileViewModel} from '../../../state/models/profile-view' import {ProfileViewModel} from '../../../state/models/profile-view'
import {useStores} from '../../../state' import {useStores} from '../../../state'
import {pluralize} from '../../lib/strings' import {pluralize} from '../../lib/strings'
import {s, colors} from '../../lib/styles' import {s, gradients, colors} from '../../lib/styles'
import {AVIS} from '../../lib/assets' import {AVIS, BANNER} from '../../lib/assets'
import Toast from '../util/Toast' import Toast from '../util/Toast'
import {Link} from '../util/Link'
import {Selector, SelectorItem} from '../util/Selector'
export const ProfileHeader = observer(function ProfileHeader({ export const ProfileHeader = observer(function ProfileHeader({
user, user,
@ -35,6 +38,11 @@ export const ProfileHeader = observer(function ProfileHeader({
newView.setup().catch(err => console.error('Failed to fetch profile', err)) newView.setup().catch(err => console.error('Failed to fetch profile', err))
}, [user, view?.params.user, store]) }, [user, view?.params.user, store])
const selectorItems: SelectorItem[] = [
{label: 'Posts', onSelect() {}},
{label: 'Badges', onSelect() {}},
]
const onPressToggleFollow = () => { const onPressToggleFollow = () => {
view?.toggleFollowing().then( view?.toggleFollowing().then(
() => { () => {
@ -51,6 +59,12 @@ export const ProfileHeader = observer(function ProfileHeader({
err => console.error('Failed to toggle follow', err), err => console.error('Failed to toggle follow', err),
) )
} }
const onPressEditProfile = () => {
// TODO
}
const onPressMenu = () => {
// TODO
}
const onPressFollowers = () => { const onPressFollowers = () => {
store.nav.navigate(`/profile/${user}/followers`) store.nav.navigate(`/profile/${user}/followers`)
} }
@ -84,40 +98,79 @@ export const ProfileHeader = observer(function ProfileHeader({
// loaded // loaded
// = // =
const isMe = store.me.did === view.did
return ( return (
<View style={styles.outer}> <View style={styles.outer}>
<Image style={styles.banner} source={BANNER} />
<Image style={styles.avi} source={AVIS[view.name] || AVIS['alice.com']} /> <Image style={styles.avi} source={AVIS[view.name] || AVIS['alice.com']} />
<View style={[styles.nameLine, s.mb2]}> <View style={styles.content}>
<Text style={[s.bold, s.f18, s.mr2]}>{view.displayName}</Text> <View style={[styles.displayNameLine]}>
<Text style={[s.gray5]}>@{view.name}</Text> <Text style={styles.displayName}>{view.displayName}</Text>
</View>
{view.description && (
<Text style={[s.mb5, s.f15, s['lh15-1.3']]}>{view.description}</Text>
)}
<View style={s.flexRow}>
<TouchableOpacity
style={[s.flexRow, s.mr10]}
onPress={onPressFollowers}>
<Text style={[s.bold, s.mr2]}>{view.followersCount}</Text>
<Text style={s.gray5}>
{pluralize(view.followersCount, 'follower')}
</Text>
</TouchableOpacity>
<TouchableOpacity style={[s.flexRow, s.mr10]} onPress={onPressFollows}>
<Text style={[s.bold, s.mr2]}>{view.followsCount}</Text>
<Text style={s.gray5}>following</Text>
</TouchableOpacity>
<View style={[s.flexRow, s.mr10]}>
<Text style={[s.bold, s.mr2]}>{view.postsCount}</Text>
<Text style={s.gray5}>{pluralize(view.postsCount, 'post')}</Text>
</View> </View>
<View style={styles.badgesLine}>
<FontAwesomeIcon icon="shield" style={s.mr5} size={12} />
<Link href="/" title="Badge TODO">
<Text style={[s.f12, s.bold]}>
Employee <Text style={[s.blue3]}>@blueskyweb.xyz</Text>
</Text>
</Link>
</View>
<View style={[styles.buttonsLine]}>
{isMe ? (
<TouchableOpacity
onPress={onPressEditProfile}
style={[styles.mainBtn, styles.btn]}>
<Text style={[s.fw600, s.f16]}>Edit Profile</Text>
</TouchableOpacity>
) : view.myState.hasFollowed ? (
<TouchableOpacity
onPress={onPressToggleFollow}
style={[styles.mainBtn, styles.btn]}>
<Text style={[s.fw600, s.f16]}>Following</Text>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={onPressToggleFollow}>
<LinearGradient
colors={[gradients.primary.start, gradients.primary.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.followBtn]}>
<FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} />
<Text style={[s.white, s.fw600, s.f16]}>Follow</Text>
</LinearGradient>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={onPressMenu}
style={[styles.btn, styles.secondaryBtn, s.ml10]}>
<FontAwesomeIcon icon="ellipsis" style={[s.gray5]} />
</TouchableOpacity>
</View>
<View style={[s.flexRow, s.mb10]}>
<TouchableOpacity
style={[s.flexRow, s.mr10]}
onPress={onPressFollowers}>
<Text style={[s.bold, s.mr2]}>{view.followersCount}</Text>
<Text style={s.gray5}>
{pluralize(view.followersCount, 'follower')}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[s.flexRow, s.mr10]}
onPress={onPressFollows}>
<Text style={[s.bold, s.mr2]}>{view.followsCount}</Text>
<Text style={s.gray5}>following</Text>
</TouchableOpacity>
<View style={[s.flexRow, s.mr10]}>
<Text style={[s.bold, s.mr2]}>{view.postsCount}</Text>
<Text style={s.gray5}>{pluralize(view.postsCount, 'post')}</Text>
</View>
</View>
{view.description && (
<Text style={[s.mb10, s.f15, s['lh15-1.3']]}>{view.description}</Text>
)}
</View> </View>
<View> <Selector items={selectorItems} />
<Button
title={view.myState.hasFollowed ? 'Unfollow' : 'Follow'}
onPress={onPressToggleFollow}
/>
</View>
</View> </View>
) )
}) })
@ -125,18 +178,66 @@ export const ProfileHeader = observer(function ProfileHeader({
const styles = StyleSheet.create({ const styles = StyleSheet.create({
outer: { outer: {
backgroundColor: colors.white, backgroundColor: colors.white,
padding: 10,
borderBottomWidth: 1, borderBottomWidth: 1,
borderColor: colors.gray2, borderColor: colors.gray2,
}, },
avi: { banner: {
width: 60, width: '100%',
height: 60, height: 120,
borderRadius: 30,
resizeMode: 'cover',
}, },
nameLine: { avi: {
position: 'absolute',
top: 80,
left: 10,
width: 80,
height: 80,
borderRadius: 40,
resizeMode: 'cover',
borderWidth: 2,
borderColor: colors.white,
},
content: {
paddingTop: 8,
paddingHorizontal: 14,
paddingBottom: 4,
},
displayNameLine: {
paddingLeft: 86,
marginBottom: 14,
},
displayName: {
fontSize: 24,
fontWeight: 'bold',
},
badgesLine: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'flex-end', alignItems: 'center',
marginBottom: 10,
},
buttonsLine: {
flexDirection: 'row',
marginBottom: 12,
},
followBtn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 8,
paddingHorizontal: 60,
borderRadius: 30,
},
btn: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 8,
borderRadius: 30,
borderWidth: 1,
borderColor: colors.gray2,
},
mainBtn: {
paddingHorizontal: 40,
},
secondaryBtn: {
paddingHorizontal: 12,
}, },
}) })

View File

@ -0,0 +1,73 @@
import React, {useState} from 'react'
import {
StyleProp,
StyleSheet,
Text,
TouchableWithoutFeedback,
View,
ViewStyle,
} from 'react-native'
import {colors} from '../../lib/styles'
export interface SelectorItem {
label: string
}
export function Selector({
style,
items,
onSelect,
}: {
style?: StyleProp<ViewStyle>
items: SelectorItem[]
onSelect?: (index: number) => void
}) {
const [selectedIndex, setSelectedIndex] = useState<number>(0)
const onPressItem = (index: number) => {
setSelectedIndex(index)
onSelect?.(index)
}
return (
<View style={[styles.outer, style]}>
{items.map((item, i) => {
const selected = i === selectedIndex
return (
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
<View style={selected ? styles.itemSelected : styles.item}>
<Text style={selected ? styles.labelSelected : styles.label}>
{item.label}
</Text>
</View>
</TouchableWithoutFeedback>
)
})}
</View>
)
}
const styles = StyleSheet.create({
outer: {
flexDirection: 'row',
paddingHorizontal: 14,
},
item: {
paddingBottom: 12,
marginRight: 20,
},
label: {
fontWeight: '600',
fontSize: 16,
color: colors.gray5,
},
itemSelected: {
paddingBottom: 8,
marginRight: 20,
borderBottomWidth: 4,
borderBottomColor: colors.purple3,
},
labelSelected: {
fontWeight: '600',
fontSize: 16,
},
})

View File

@ -26,6 +26,7 @@ import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib'
import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus' import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus'
import {faShare} from '@fortawesome/free-solid-svg-icons/faShare' import {faShare} from '@fortawesome/free-solid-svg-icons/faShare'
import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare' import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare'
import {faShield} from '@fortawesome/free-solid-svg-icons/faShield'
import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet' import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet'
import {faUser} from '@fortawesome/free-regular-svg-icons/faUser' import {faUser} from '@fortawesome/free-regular-svg-icons/faUser'
import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers' import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers'
@ -60,6 +61,7 @@ export function setup() {
faRetweet, faRetweet,
faShare, faShare,
faShareFromSquare, faShareFromSquare,
faShield,
faUser, faUser,
faUsers, faUsers,
faX, faX,

View File

@ -5,3 +5,5 @@ export const AVIS: Record<string, ImageSourcePropType> = {
'bob.com': require('../../../public/img/bob.jpg'), 'bob.com': require('../../../public/img/bob.jpg'),
'carla.com': require('../../../public/img/carla.jpg'), 'carla.com': require('../../../public/img/carla.jpg'),
} }
export const BANNER: ImageSourcePropType = require('../../../public/img/banner.jpg')

View File

@ -5,3 +5,5 @@ export const AVIS: Record<string, ImageSourcePropType> = {
'bob.com': {uri: '/img/bob.jpg'}, 'bob.com': {uri: '/img/bob.jpg'},
'carla.com': {uri: '/img/carla.jpg'}, 'carla.com': {uri: '/img/carla.jpg'},
} }
export const BANNER: ImageSourcePropType = {uri: '/img/banner.jpg'}

View File

@ -62,6 +62,10 @@ export const s = StyleSheet.create({
fw200: {fontWeight: '200'}, fw200: {fontWeight: '200'},
// font sizes // font sizes
f9: {fontSize: 9},
f10: {fontSize: 10},
f11: {fontSize: 11},
f12: {fontSize: 12},
f13: {fontSize: 13}, f13: {fontSize: 13},
f14: {fontSize: 14}, f14: {fontSize: 14},
f15: {fontSize: 15}, f15: {fontSize: 15},