Rework profile page to include working view selector
This commit is contained in:
parent
2ec09ba545
commit
bb06ef4f6e
19 changed files with 569 additions and 94 deletions
|
@ -1,12 +1,5 @@
|
|||
import React, {useState} from 'react'
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
||||
import {BottomSheetTextInput} from '@gorhom/bottom-sheet'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
|
|
|
@ -238,7 +238,7 @@ export const PostThreadItem = observer(function PostThreadItem({
|
|||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 10,
|
||||
borderRadius: 6,
|
||||
margin: 2,
|
||||
marginBottom: 0,
|
||||
},
|
||||
|
|
|
@ -154,7 +154,7 @@ export const Post = observer(function Post({uri}: {uri: string}) {
|
|||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
marginTop: 1,
|
||||
borderRadius: 4,
|
||||
borderRadius: 6,
|
||||
backgroundColor: colors.white,
|
||||
padding: 10,
|
||||
},
|
||||
|
|
|
@ -3,21 +3,17 @@ import {observer} from 'mobx-react-lite'
|
|||
import {Text, View, FlatList} from 'react-native'
|
||||
import {FeedViewModel, FeedViewItemModel} from '../../../state/models/feed-view'
|
||||
import {FeedItem} from './FeedItem'
|
||||
import {SharePostModel} from '../../../state/models/shell'
|
||||
import {useStores} from '../../../state'
|
||||
|
||||
export const Feed = observer(function Feed({feed}: {feed: FeedViewModel}) {
|
||||
const store = useStores()
|
||||
|
||||
const onPressShare = (uri: string) => {
|
||||
store.shell.openModal(new SharePostModel(uri))
|
||||
}
|
||||
// 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
|
||||
// renderItem function renders components that follow React performance best practices
|
||||
// like PureComponent, shouldComponentUpdate, etc
|
||||
const renderItem = ({item}: {item: FeedViewItemModel}) => (
|
||||
<FeedItem item={item} onPressShare={onPressShare} />
|
||||
<FeedItem item={item} />
|
||||
)
|
||||
const onRefresh = () => {
|
||||
feed.refresh().catch(err => console.error('Failed to refresh', err))
|
||||
|
@ -33,7 +29,7 @@ export const Feed = observer(function Feed({feed}: {feed: FeedViewModel}) {
|
|||
{feed.hasError && <Text>{feed.error}</Text>}
|
||||
{feed.hasContent && (
|
||||
<FlatList
|
||||
data={feed.feed}
|
||||
data={feed.feed.slice()}
|
||||
keyExtractor={item => item._reactKey}
|
||||
renderItem={renderItem}
|
||||
refreshing={feed.isRefreshing}
|
|
@ -4,7 +4,7 @@ import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
|||
import {bsky, AdxUri} from '@adxp/mock-api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {FeedViewItemModel} from '../../../state/models/feed-view'
|
||||
import {ComposePostModel} from '../../../state/models/shell'
|
||||
import {ComposePostModel, SharePostModel} from '../../../state/models/shell'
|
||||
import {Link} from '../util/Link'
|
||||
import {PostDropdownBtn} from '../util/DropdownBtn'
|
||||
import {s, colors} from '../../lib/styles'
|
||||
|
@ -14,10 +14,8 @@ import {useStores} from '../../../state'
|
|||
|
||||
export const FeedItem = observer(function FeedItem({
|
||||
item,
|
||||
onPressShare,
|
||||
}: {
|
||||
item: FeedViewItemModel
|
||||
onPressShare: (_uri: string) => void
|
||||
}) {
|
||||
const store = useStores()
|
||||
const record = item.record as unknown as bsky.Post.Record
|
||||
|
@ -41,6 +39,9 @@ export const FeedItem = observer(function FeedItem({
|
|||
.toggleLike()
|
||||
.catch(e => console.error('Failed to toggle like', record, e))
|
||||
}
|
||||
const onPressShare = (uri: string) => {
|
||||
store.shell.openModal(new SharePostModel(uri))
|
||||
}
|
||||
|
||||
return (
|
||||
<Link style={styles.outer} href={itemHref} title={itemTitle}>
|
||||
|
@ -151,7 +152,7 @@ export const FeedItem = observer(function FeedItem({
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
borderRadius: 10,
|
||||
borderRadius: 6,
|
||||
margin: 2,
|
||||
marginBottom: 0,
|
||||
backgroundColor: colors.white,
|
|
@ -17,31 +17,13 @@ import {s, gradients, colors} from '../../lib/styles'
|
|||
import {AVIS, BANNER} from '../../lib/assets'
|
||||
import Toast from '../util/Toast'
|
||||
import {Link} from '../util/Link'
|
||||
import {Selector, SelectorItem} from '../util/Selector'
|
||||
|
||||
export const ProfileHeader = observer(function ProfileHeader({
|
||||
user,
|
||||
view,
|
||||
}: {
|
||||
user: string
|
||||
view: ProfileViewModel
|
||||
}) {
|
||||
const store = useStores()
|
||||
const [view, setView] = useState<ProfileViewModel | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (view?.params.user === user) {
|
||||
console.log('Profile header doing nothing')
|
||||
return // no change needed? or trigger refresh?
|
||||
}
|
||||
console.log('Fetching profile', user)
|
||||
const newView = new ProfileViewModel(store, {user: user})
|
||||
setView(newView)
|
||||
newView.setup().catch(err => console.error('Failed to fetch profile', err))
|
||||
}, [user, view?.params.user, store])
|
||||
|
||||
const selectorItems: SelectorItem[] = [
|
||||
{label: 'Posts', onSelect() {}},
|
||||
{label: 'Badges', onSelect() {}},
|
||||
]
|
||||
|
||||
const onPressToggleFollow = () => {
|
||||
view?.toggleFollowing().then(
|
||||
|
@ -66,19 +48,15 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
// TODO
|
||||
}
|
||||
const onPressFollowers = () => {
|
||||
store.nav.navigate(`/profile/${user}/followers`)
|
||||
store.nav.navigate(`/profile/${view.name}/followers`)
|
||||
}
|
||||
const onPressFollows = () => {
|
||||
store.nav.navigate(`/profile/${user}/follows`)
|
||||
store.nav.navigate(`/profile/${view.name}/follows`)
|
||||
}
|
||||
|
||||
// loading
|
||||
// =
|
||||
if (
|
||||
!view ||
|
||||
(view.isLoading && !view.isRefreshing) ||
|
||||
view.params.user !== user
|
||||
) {
|
||||
if (!view || (view.isLoading && !view.isRefreshing)) {
|
||||
return (
|
||||
<View>
|
||||
<ActivityIndicator />
|
||||
|
@ -120,13 +98,13 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
<TouchableOpacity
|
||||
onPress={onPressEditProfile}
|
||||
style={[styles.mainBtn, styles.btn]}>
|
||||
<Text style={[s.fw600, s.f16]}>Edit Profile</Text>
|
||||
<Text style={[s.fw400, s.f14]}>Edit Profile</Text>
|
||||
</TouchableOpacity>
|
||||
) : view.myState.hasFollowed ? (
|
||||
<TouchableOpacity
|
||||
onPress={onPressToggleFollow}
|
||||
style={[styles.mainBtn, styles.btn]}>
|
||||
<Text style={[s.fw600, s.f16]}>Following</Text>
|
||||
<Text style={[s.fw400, s.f14]}>Following</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity onPress={onPressToggleFollow}>
|
||||
|
@ -146,7 +124,7 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
<FontAwesomeIcon icon="ellipsis" style={[s.gray5]} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={[s.flexRow, s.mb10]}>
|
||||
<View style={[s.flexRow]}>
|
||||
<TouchableOpacity
|
||||
style={[s.flexRow, s.mr10]}
|
||||
onPress={onPressFollowers}>
|
||||
|
@ -167,10 +145,9 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
</View>
|
||||
</View>
|
||||
{view.description && (
|
||||
<Text style={[s.mb10, s.f15, s['lh15-1.3']]}>{view.description}</Text>
|
||||
<Text style={[s.mt10, s.f15, s['lh15-1.3']]}>{view.description}</Text>
|
||||
)}
|
||||
</View>
|
||||
<Selector items={selectorItems} />
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
@ -178,8 +155,6 @@ export const ProfileHeader = observer(function ProfileHeader({
|
|||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
backgroundColor: colors.white,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: colors.gray2,
|
||||
},
|
||||
banner: {
|
||||
width: '100%',
|
||||
|
@ -222,14 +197,17 @@ const styles = StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 60,
|
||||
paddingVertical: 6,
|
||||
paddingLeft: 55,
|
||||
paddingRight: 60,
|
||||
borderRadius: 30,
|
||||
borderWidth: 1,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
btn: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 8,
|
||||
paddingVertical: 7,
|
||||
borderRadius: 30,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.gray2,
|
||||
|
|
66
src/view/com/util/ErrorMessage.tsx
Normal file
66
src/view/com/util/ErrorMessage.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
export function ErrorMessage({
|
||||
message,
|
||||
onPressTryAgain,
|
||||
}: {
|
||||
message: string
|
||||
onPressTryAgain?: () => void
|
||||
}) {
|
||||
return (
|
||||
<View style={styles.outer}>
|
||||
<View style={styles.errorIcon}>
|
||||
<FontAwesomeIcon
|
||||
icon="exclamation"
|
||||
style={{color: colors.white}}
|
||||
size={16}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.message}>{message}</Text>
|
||||
{onPressTryAgain && (
|
||||
<TouchableOpacity style={styles.btn} onPress={onPressTryAgain}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrows-rotate"
|
||||
style={{color: colors.red4}}
|
||||
size={16}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.red1,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.red3,
|
||||
borderRadius: 6,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
errorIcon: {
|
||||
backgroundColor: colors.red4,
|
||||
borderRadius: 12,
|
||||
width: 24,
|
||||
height: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 8,
|
||||
},
|
||||
message: {
|
||||
flex: 1,
|
||||
color: colors.red4,
|
||||
paddingRight: 10,
|
||||
},
|
||||
btn: {
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
})
|
111
src/view/com/util/ErrorScreen.tsx
Normal file
111
src/view/com/util/ErrorScreen.tsx
Normal file
|
@ -0,0 +1,111 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
export function ErrorScreen({
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
onPressTryAgain,
|
||||
}: {
|
||||
title: string
|
||||
message: string
|
||||
details?: string
|
||||
onPressTryAgain?: () => void
|
||||
}) {
|
||||
return (
|
||||
<View style={styles.outer}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View style={styles.errorIcon}>
|
||||
<FontAwesomeIcon
|
||||
icon="exclamation"
|
||||
style={{color: colors.white}}
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<Text style={styles.message}>{message}</Text>
|
||||
{details && <Text style={styles.details}>{details}</Text>}
|
||||
{onPressTryAgain && (
|
||||
<View style={styles.btnContainer}>
|
||||
<TouchableOpacity style={styles.btn} onPress={onPressTryAgain}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrows-rotate"
|
||||
style={{color: colors.white}}
|
||||
size={16}
|
||||
/>
|
||||
<Text style={styles.btnText}>Try again</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
outer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.red1,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.red3,
|
||||
borderRadius: 6,
|
||||
paddingVertical: 30,
|
||||
paddingHorizontal: 14,
|
||||
margin: 10,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
color: colors.red4,
|
||||
fontSize: 24,
|
||||
marginBottom: 10,
|
||||
},
|
||||
message: {
|
||||
textAlign: 'center',
|
||||
color: colors.red4,
|
||||
marginBottom: 20,
|
||||
},
|
||||
details: {
|
||||
textAlign: 'center',
|
||||
color: colors.black,
|
||||
backgroundColor: colors.white,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.gray5,
|
||||
borderRadius: 6,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 14,
|
||||
overflow: 'hidden',
|
||||
marginBottom: 20,
|
||||
},
|
||||
btnContainer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.red4,
|
||||
borderRadius: 6,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
btnText: {
|
||||
marginLeft: 5,
|
||||
color: colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
errorIconContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
errorIcon: {
|
||||
backgroundColor: colors.red4,
|
||||
borderRadius: 30,
|
||||
width: 50,
|
||||
height: 50,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 5,
|
||||
},
|
||||
})
|
|
@ -9,17 +9,13 @@ import {
|
|||
} from 'react-native'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
export interface SelectorItem {
|
||||
label: string
|
||||
}
|
||||
|
||||
export function Selector({
|
||||
style,
|
||||
items,
|
||||
onSelect,
|
||||
}: {
|
||||
style?: StyleProp<ViewStyle>
|
||||
items: SelectorItem[]
|
||||
items: string[]
|
||||
onSelect?: (index: number) => void
|
||||
}) {
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0)
|
||||
|
@ -36,7 +32,7 @@ export function Selector({
|
|||
<TouchableWithoutFeedback key={i} onPress={() => onPressItem(i)}>
|
||||
<View style={selected ? styles.itemSelected : styles.item}>
|
||||
<Text style={selected ? styles.labelSelected : styles.label}>
|
||||
{item.label}
|
||||
{item}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue