Skip to content

Commit c3a29ba

Browse files
authored
Merge pull request #64 from funnyzak/feature/followpeople
2 parents 7c7a923 + b0a5516 commit c3a29ba

File tree

10 files changed

+329
-95
lines changed

10 files changed

+329
-95
lines changed

src/i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
"profileLastModified": "Last Modified $。",
9191
"activeLatest": "Last actived at $ ",
9292
"favorited": "Favorited",
93-
"joinSinceTime": "Joined from",
93+
"v2exNumber": "V2EXer #$.",
94+
"joinSinceTime": "Joined from $.",
9495
"joinV2exSinceTime": "V2EXer #$, joined on $.",
9596
"createNodeSinceTime": "Created from $ .",
9697
"noLogin": "Not logged in",

src/i18n/locales/zh.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
"profileLastModified": "最近修改于 $。",
9191
"activeLatest": "最近活跃于 $。",
9292
"favorited": "收藏了",
93-
"joinSinceTime": "加入于",
93+
"v2exNumber": "第 $ 号会员。",
94+
"joinSinceTime": "加入于 $。",
9495
"joinV2exSinceTime": "第 $ 号会员,加入于 $。",
9596
"createNodeSinceTime": "节点从 $ 创建至今。",
9697
"noLogin": "未登陆",
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Created by leon<[email protected]> on 22/05/27.
3+
*/
4+
import { Placeholder, Spinner } from '@src/components'
5+
import { translate } from '@src/i18n'
6+
import { SylCommon, useTheme } from '@src/theme'
7+
import { ITheme, V2exObject } from '@src/types'
8+
import React from 'react'
9+
import { FlatList, StyleProp, View, ViewStyle } from 'react-native'
10+
import { BorderLine } from '../common'
11+
import SimpleProfileInfoCard from './SimpleProfileInfoCard'
12+
13+
export interface ProfileCardListProps {
14+
/**
15+
* container style
16+
*/
17+
containerStyle?: StyleProp<ViewStyle>
18+
19+
itemContainerStyle?: StyleProp<ViewStyle>
20+
21+
canLoadMoreContent?: boolean
22+
members?: Array<V2exObject.Member>
23+
onEndReached?: () => void
24+
refreshControl?: React.ReactElement
25+
searchIndicator?: boolean
26+
refreshCallback?: () => void
27+
28+
useFlatList?: boolean
29+
}
30+
31+
const ProfileCardList: React.FC<ProfileCardListProps> = ({
32+
useFlatList = true,
33+
containerStyle,
34+
itemContainerStyle,
35+
canLoadMoreContent,
36+
members,
37+
onEndReached,
38+
refreshControl,
39+
searchIndicator,
40+
refreshCallback
41+
}: ProfileCardListProps) => {
42+
const { theme } = useTheme()
43+
44+
const renderItemRow = ({ item }: { item: V2exObject.Member }) =>
45+
!item || item === null ? null : (
46+
<SimpleProfileInfoCard
47+
key={item.id}
48+
containerStyle={[styles.infoItemContainer(theme), itemContainerStyle]}
49+
info={item}
50+
/>
51+
)
52+
53+
const renderFooter = () => {
54+
if (canLoadMoreContent) {
55+
return <Spinner style={{ padding: theme.spacing.large }} />
56+
} else if (members && members.length > 0) {
57+
return (
58+
<Placeholder
59+
containerStyle={[{ backgroundColor: theme.colors.background }]}
60+
placeholderText={translate('tips.noMore')}
61+
/>
62+
)
63+
}
64+
return null
65+
}
66+
67+
const renderItemSeparator = () => <BorderLine />
68+
69+
const renderContent = () => {
70+
if (!members) {
71+
return <Spinner style={{ marginTop: 50 }} />
72+
}
73+
74+
if (members.length > 0) {
75+
return useFlatList ? (
76+
<FlatList
77+
refreshControl={refreshControl}
78+
style={styles.container(theme)}
79+
data={members}
80+
renderItem={renderItemRow}
81+
keyExtractor={(item, index) => index.toString()}
82+
onEndReached={onEndReached}
83+
onEndReachedThreshold={0.1}
84+
ListFooterComponent={renderFooter}
85+
numColumns={1}
86+
horizontal={false}
87+
key={'ONE COLUMN'}
88+
maxToRenderPerBatch={10}
89+
initialNumToRender={10}
90+
ItemSeparatorComponent={renderItemSeparator}
91+
/>
92+
) : (
93+
<View style={[styles.container(theme), containerStyle]}>{members.map((v) => renderItemRow({ item: v }))}</View>
94+
)
95+
}
96+
if (!searchIndicator) {
97+
return <Placeholder buttonText={translate('button.tryAgain')} buttonPress={refreshCallback} />
98+
}
99+
}
100+
101+
return <View style={[styles.container(theme), containerStyle]}>{renderContent()}</View>
102+
}
103+
104+
/**
105+
* @description styles settings
106+
*/
107+
const styles = {
108+
container: (theme: ITheme): ViewStyle => ({
109+
flex: 1
110+
}),
111+
infoItemContainer: (theme: ITheme): ViewStyle => ({
112+
...SylCommon.Card.container(theme),
113+
paddingTop: theme.spacing.medium
114+
})
115+
}
116+
117+
export default ProfileCardList

src/screens/components/profile/ProfileInfo.tsx

Lines changed: 17 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
import { Avatar, Text } from '@src/components'
55
import { translate } from '@src/i18n'
66
import { NavigationService, ROUTES } from '@src/navigation'
7-
import { ITheme, SylCommon, useTheme } from '@src/theme'
7+
import { useTheme } from '@src/theme'
88
import { V2exObject } from '@src/types'
99
import dayjs from 'dayjs'
1010
import React, { useMemo } from 'react'
1111
import { Image, StyleProp, TouchableOpacity, View, ViewStyle } from 'react-native'
1212
import { TextWithIconPress } from '../common'
13+
import { ProfileInfoStyle } from './profile'
1314

1415
/**
1516
* ProfileInfo props
@@ -44,7 +45,7 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({
4445

4546
const renderContent = () => {
4647
return (
47-
<View style={[styles.container(theme), containerStyle]}>
48+
<View style={[ProfileInfoStyle.container(theme), containerStyle]}>
4849
<TouchableOpacity
4950
onPress={() =>
5051
!withArrow
@@ -53,31 +54,31 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({
5354
? NavigationService.navigate(ROUTES.Profile, { username: info?.username })
5455
: NavigationService.navigate(ROUTES.SignIn)
5556
}
56-
style={styles.infoItem(theme)}>
57-
<View style={styles.baseAvatar(theme)}>
57+
style={ProfileInfoStyle.infoItem(theme)}>
58+
<View style={ProfileInfoStyle.baseAvatar(theme)}>
5859
<Avatar
5960
size={60}
6061
source={info?.avatar_normal ? { uri: info?.avatar_normal } : undefined}
6162
username={info?.username}
6263
/>
6364
</View>
64-
<View style={styles.baseRightBox(theme)}>
65-
<View style={styles.baseRightInfo(theme)}>
65+
<View style={ProfileInfoStyle.baseRightBox(theme)}>
66+
<View style={ProfileInfoStyle.baseRightInfo(theme)}>
6667
<Text
6768
style={[
68-
styles.baseRightItem(theme),
69+
ProfileInfoStyle.baseRightItem(theme),
6970
theme.typography.subheadingText,
7071
{ color: theme.colors.secondary }
7172
]}>
7273
{info?.username ?? translate('label.goLogin')}
7374
</Text>
7475
{!isLogin || (isLogin && info?.tagline) ? (
75-
<Text style={[styles.baseRightItem(theme), theme.typography.bodyText]}>
76+
<Text style={[ProfileInfoStyle.baseRightItem(theme), theme.typography.bodyText]}>
7677
{info?.tagline ?? translate('label.loginTips')}
7778
</Text>
7879
) : null}
7980
{info?.last_modified ? (
80-
<Text style={[styles.baseRightItem(theme), theme.typography.captionText]}>
81+
<Text style={[ProfileInfoStyle.baseRightItem(theme), theme.typography.captionText]}>
8182
{translate('label.profileLastModified').replace(
8283
'$',
8384
dayjs(info?.last_modified * 1000).format('YYYY-MM-DD HH:mm:ss')
@@ -86,18 +87,20 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({
8687
) : null}
8788
</View>
8889
{withArrow && (
89-
<View style={styles.baseRightArrow(theme)}>
90+
<View style={ProfileInfoStyle.baseRightArrow(theme)}>
9091
<Image source={theme.assets.images.icons.table.rightArrow} style={{ width: 14, height: 14 }} />
9192
</View>
9293
)}
9394
</View>
9495
</TouchableOpacity>
9596
{styleType === 'full' && (
9697
<>
97-
{info?.bio ? <Text style={[styles.infoItem(theme), theme.typography.bodyText]}>{info?.bio}</Text> : null}
98+
{info?.bio ? (
99+
<Text style={[ProfileInfoStyle.infoItem(theme), theme.typography.bodyText]}>{info?.bio}</Text>
100+
) : null}
98101

99102
{info && (info.location || info.website) ? (
100-
<View style={styles.infoItem(theme)}>
103+
<View style={ProfileInfoStyle.infoItem(theme)}>
101104
{info?.location ? (
102105
<TextWithIconPress
103106
containerStyle={{ marginRight: theme.spacing.small }}
@@ -118,7 +121,7 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({
118121
</View>
119122
) : null}
120123
{info && (info.github || info.telegram || info.twitter) ? (
121-
<View style={styles.infoItem(theme)}>
124+
<View style={ProfileInfoStyle.infoItem(theme)}>
122125
{info?.github ? (
123126
<TextWithIconPress
124127
onPress={() => {
@@ -149,7 +152,7 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({
149152
</View>
150153
) : null}
151154
{info?.created ? (
152-
<Text style={[styles.infoItem(theme), theme.typography.captionText]}>
155+
<Text style={[ProfileInfoStyle.infoItem(theme), theme.typography.captionText]}>
153156
{translate('label.joinV2exSinceTime')
154157
.replace('$', info?.id.toString())
155158
.replace('$', dayjs(info?.created * 1000).format())}
@@ -164,44 +167,4 @@ const ProfileInfo: React.FC<ProfileInfoProps> = ({
164167
return renderContent()
165168
}
166169

167-
const styles = {
168-
container: (theme: ITheme): ViewStyle => ({
169-
display: 'flex',
170-
alignItems: 'flex-start',
171-
flexDirection: 'column'
172-
}),
173-
infoItem: (theme: ITheme): ViewStyle => ({
174-
paddingBottom: theme.spacing.medium,
175-
display: 'flex',
176-
flexDirection: 'row',
177-
alignItems: 'flex-start',
178-
justifyContent: 'flex-start',
179-
width: '100%'
180-
}),
181-
baseAvatar: (theme: ITheme): ViewStyle => ({
182-
width: 60,
183-
height: 60,
184-
marginRight: theme.spacing.medium
185-
}),
186-
baseRightBox: (theme: ITheme): ViewStyle => ({
187-
display: 'flex',
188-
flexDirection: 'row',
189-
flex: 1
190-
}),
191-
baseRightInfo: (theme: ITheme): ViewStyle => ({
192-
display: 'flex',
193-
flexDirection: 'column',
194-
flex: 1,
195-
alignItems: 'flex-start'
196-
}),
197-
baseRightArrow: (theme: ITheme): ViewStyle => ({
198-
width: 14,
199-
display: 'flex',
200-
justifyContent: 'center'
201-
}),
202-
baseRightItem: (theme: ITheme): ViewStyle => ({
203-
paddingBottom: theme.spacing.small
204-
})
205-
}
206-
207170
export default ProfileInfo
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* Created by leon<[email protected]> on 22/05/27.
3+
*/
4+
import { Avatar, Text } from '@src/components'
5+
import { translate } from '@src/i18n'
6+
import { NavigationService, ROUTES } from '@src/navigation'
7+
import { ITheme, useTheme } from '@src/theme'
8+
import { V2exObject } from '@src/types'
9+
import dayjs from 'dayjs'
10+
import React from 'react'
11+
import { Image, StyleProp, TouchableOpacity, View, ViewStyle } from 'react-native'
12+
import { ProfileInfoStyle } from './profile'
13+
14+
/**
15+
* SimpleProfileInfoCard props
16+
*/
17+
export interface SimpleProfileInfoCardProps {
18+
/**
19+
* container style
20+
*/
21+
containerStyle?: StyleProp<ViewStyle>
22+
/**
23+
* profile info
24+
*/
25+
info?: V2exObject.Member
26+
/**
27+
* with right arrow
28+
*/
29+
withArrow?: boolean
30+
}
31+
32+
const SimpleProfileInfoCard: React.FC<SimpleProfileInfoCardProps> = ({
33+
info,
34+
containerStyle,
35+
withArrow = true
36+
}: SimpleProfileInfoCardProps) => {
37+
const { theme } = useTheme()
38+
39+
const renderContent = () => {
40+
return (
41+
<View style={[ProfileInfoStyle.container(theme), containerStyle]}>
42+
<TouchableOpacity
43+
onPress={() => {
44+
if (withArrow) NavigationService.navigate(ROUTES.Profile, { username: info?.username })
45+
}}
46+
style={ProfileInfoStyle.infoItem(theme)}>
47+
<View style={ProfileInfoStyle.baseAvatar(theme)}>
48+
<Avatar
49+
size={60}
50+
source={info?.avatar_normal ? { uri: info?.avatar_normal } : undefined}
51+
username={info?.username}
52+
/>
53+
</View>
54+
<View style={ProfileInfoStyle.baseRightBox(theme)}>
55+
<View style={ProfileInfoStyle.baseRightInfo(theme)}>
56+
<Text
57+
style={[
58+
ProfileInfoStyle.baseRightItem(theme),
59+
styles.baseRightItem(theme),
60+
theme.typography.subheadingText,
61+
{ color: theme.colors.secondary }
62+
]}>
63+
{info?.username}
64+
</Text>
65+
{info?.tagline ? (
66+
<Text
67+
style={[
68+
ProfileInfoStyle.baseRightItem(theme),
69+
styles.baseRightItem(theme),
70+
theme.typography.bodyText
71+
]}>
72+
{info.tagline}
73+
</Text>
74+
) : null}
75+
{info ? (
76+
<Text
77+
style={[
78+
ProfileInfoStyle.baseRightItem(theme),
79+
styles.baseRightItem(theme),
80+
theme.typography.captionText
81+
]}>
82+
{translate('label.v2exNumber').replace('$', info?.id.toString())}
83+
</Text>
84+
) : null}
85+
{info?.created ? (
86+
<Text style={[ProfileInfoStyle.infoItem(theme), theme.typography.captionText]}>
87+
{translate('label.joinSinceTime').replace('$', dayjs(info?.created * 1000).format())}
88+
</Text>
89+
) : null}
90+
</View>
91+
{withArrow && (
92+
<View style={ProfileInfoStyle.baseRightArrow(theme)}>
93+
<Image source={theme.assets.images.icons.table.rightArrow} style={{ width: 14, height: 14 }} />
94+
</View>
95+
)}
96+
</View>
97+
</TouchableOpacity>
98+
</View>
99+
)
100+
}
101+
102+
return renderContent()
103+
}
104+
105+
const styles = {
106+
baseRightItem: (theme: ITheme): ViewStyle => ({
107+
paddingBottom: theme.spacing.tiny
108+
})
109+
}
110+
111+
export default SimpleProfileInfoCard

0 commit comments

Comments
 (0)