Skip to content

Commit 9efb974

Browse files
authored
Merge pull request #56 from funnyzak/feature/likenode
2 parents 2cd36a5 + fd11e22 commit 9efb974

File tree

12 files changed

+163
-17
lines changed

12 files changed

+163
-17
lines changed

src/actions/MemberActions.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {
1111
APP_AUTH_ERROR,
1212
APP_AUTH_SUCCESS,
1313
V2exObject,
14-
MEMBER_TOPICS
14+
MEMBER_TOPICS,
15+
MEMBER_INSEREST_NODE,
16+
MEMBER_UNINTEREST_NODE
1517
} from '../types'
1618
import { v2exLib } from '@src/v2ex'
1719
import { logError } from '@src/helper/logger'
@@ -39,6 +41,16 @@ export const setMyTopics = (topics: V2exObject.Topic[]) => ({
3941
payload: topics
4042
})
4143

44+
export const interestNode = (node: V2exObject.Node) => ({
45+
type: MEMBER_INSEREST_NODE,
46+
payload: node
47+
})
48+
49+
export const unInterestNode = (node: V2exObject.Node) => ({
50+
type: MEMBER_UNINTEREST_NODE,
51+
payload: node
52+
})
53+
4254
export const setCurrentToken = (token?: V2exObject.MToken) => ({
4355
type: APP_AUTH,
4456
payload: token

src/actions/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const MEMBER_FOLLOW_PEOPLE = 'follow_people'
2121
export const MEMBER_LIKE_TOPICS = 'like_topics'
2222
export const MEMBER_READ_TOPIC = 'read_topic'
2323
export const MEMBER_TOPICS = 'member_topics'
24+
export const MEMBER_INSEREST_NODE = 'member_interest_node'
25+
export const MEMBER_UNINTEREST_NODE = 'member_uninterest_node'
2426

2527
export const FEEDBACKING = 'feedbacking'
2628
export const FEEDBACK_DONE = 'feedback_done'
@@ -48,6 +50,8 @@ export const APP_CACHE_RESET_NODES = 'v2ex_cache_reset_node'
4850
export const APP_CACHE_RESET = 'v2ex_cache_reset'
4951

5052
export const ActionTypes = {
53+
MEMBER_INSEREST_NODE,
54+
MEMBER_UNINTEREST_NODE,
5155
MEMBER_TOPICS,
5256
APP_CACHE_RESET,
5357
APP_CACHE_RESET_MEMBERS,

src/i18n/locales/zh.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"all": "所有",
6666
"favorite": "收藏",
6767
"cancelFavorite": "取消收藏",
68-
"unFollow": "取消关注",
68+
"unFollow": "取关",
6969
"follow": "关注",
7070
"node": "节点",
7171
"author": "作者",

src/reducers/MemberReducer.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,34 @@
1-
import { Action, IState, APP_AUTH, MEMBER_PROFILE, APP_LOGOUT, MEMBER_READ_TOPIC, MEMBER_TOPICS } from '../types'
1+
import InterestNodes from '@src/screens/node/InterestNodes'
2+
import {
3+
Action,
4+
IState,
5+
APP_AUTH,
6+
MEMBER_PROFILE,
7+
APP_LOGOUT,
8+
MEMBER_READ_TOPIC,
9+
MEMBER_TOPICS,
10+
MEMBER_INSEREST_NODE,
11+
MEMBER_UNINTEREST_NODE
12+
} from '../types'
213
const INITIAL_STATE: IState.MemberState = {
3-
refreshing: false
14+
refreshing: false,
15+
interestNodes: []
416
}
517

618
export default (state: IState.MemberState = INITIAL_STATE, action: Action): IState.MemberState => {
719
switch (action.type) {
20+
case MEMBER_INSEREST_NODE:
21+
return {
22+
...state,
23+
interestNodes: state.interestNodes.concat(
24+
state.interestNodes.findIndex((v) => v.id === action.payload.id) >= 0 ? [] : action.payload
25+
)
26+
}
27+
case MEMBER_UNINTEREST_NODE:
28+
return {
29+
...state,
30+
interestNodes: state.interestNodes.filter((v) => v.id !== action.payload.id)
31+
}
832
case MEMBER_TOPICS:
933
return { ...state, topics: action.payload }
1034
case APP_AUTH:

src/screens/common/WebLink.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const WebLink = ({ route, navigation }: ScreenProps) => {
2929
headerRight: () => (
3030
<>
3131
<HeaderButton
32-
containerStyle={[{ marginRight: theme.spacing.tiny }]}
32+
containerStyle={[{ marginRight: theme.spacing.medium }]}
3333
source={theme.assets.images.icons.header.refresh}
3434
onPress={() => {
3535
setLoading(true)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Created by leon<[email protected]> on 22/05/5.
3+
*/
4+
import { interestNode, unInterestNode } from '@src/actions'
5+
import { useAppDispatch, useAppSelector } from '@src/hooks'
6+
import { useSession } from '@src/hooks/useSession'
7+
import { translate } from '@src/i18n'
8+
import { NavigationService, ROUTES } from '@src/navigation'
9+
import { useTheme } from '@src/theme'
10+
import { V2exObject } from '@src/types'
11+
import React, { useMemo } from 'react'
12+
import { StyleProp, ViewStyle } from 'react-native'
13+
import { HeaderButton } from '../../common'
14+
15+
/**
16+
* Like Node Button
17+
* @param {
18+
* text,
19+
* buttonText,
20+
* buttonPress
21+
* }
22+
* @returns
23+
*/
24+
const LikeNodeHeaderButton = ({
25+
containerStyle,
26+
node
27+
}: {
28+
containerStyle?: StyleProp<ViewStyle>
29+
node: V2exObject.Node
30+
}) => {
31+
const { theme } = useTheme()
32+
const { logined } = useSession()
33+
const { interestNodes } = useAppSelector((RootState) => RootState.member)
34+
const dispatch = useAppDispatch()
35+
const isInterest = useMemo(
36+
() => (logined ? interestNodes.findIndex((v) => v.id === node.id) >= 0 : false),
37+
[interestNodes]
38+
)
39+
40+
const buttonPress = () => {
41+
if (!logined) {
42+
NavigationService.navigate(ROUTES.SignIn)
43+
} else {
44+
if (isInterest) {
45+
dispatch(unInterestNode(node))
46+
} else {
47+
dispatch(interestNode(node))
48+
}
49+
}
50+
}
51+
52+
return (
53+
<HeaderButton
54+
text={translate(`common.${isInterest ? 'cancel' : 'follow'}`)}
55+
textColor={isInterest ? theme.colors.captionText : theme.colors.secondary}
56+
onPress={buttonPress}
57+
containerStyle={containerStyle}
58+
/>
59+
)
60+
}
61+
62+
export default LikeNodeHeaderButton
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as LikeNodeHeaderButton } from './LikeNode'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './header'

src/screens/components/node/NodeInfoCard.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { useNode } from '@src/hooks/useNode'
66
import { translate } from '@src/i18n'
77
import { NavigationService, ROUTES } from '@src/navigation'
88
import { ITheme, SylCommon, useTheme } from '@src/theme'
9+
import { V2exObject } from '@src/types'
910
import dayjs from 'dayjs'
10-
import React from 'react'
11+
import React, { useEffect } from 'react'
1112
import { StyleProp, View, ViewStyle } from 'react-native'
1213
import { TextWithIconPress } from '../common'
1314

@@ -24,12 +25,23 @@ export interface NodeInfoCardProps {
2425
* node name or id
2526
*/
2627
nodeid: string | number
28+
29+
/**
30+
* Load completion callback
31+
*/
32+
loadedCallback?: (node: V2exObject.Node) => void
2733
}
2834

29-
const NodeInfoCard: React.FC<NodeInfoCardProps> = ({ nodeid, containerStyle }: NodeInfoCardProps) => {
35+
const NodeInfoCard: React.FC<NodeInfoCardProps> = ({ nodeid, loadedCallback, containerStyle }: NodeInfoCardProps) => {
3036
const { theme } = useTheme()
3137
const { node: info } = useNode({ nodeid: nodeid })
3238

39+
useEffect(() => {
40+
if (loadedCallback && info) {
41+
loadedCallback(info)
42+
}
43+
}, [info])
44+
3345
const renderContent = () => {
3446
return (
3547
<View style={[SylCommon.Card.container(theme), { paddingVertical: theme.spacing.small }, containerStyle]}>

src/screens/node/NodeDetail.tsx

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
1-
import { useNode } from '@src/hooks/useNode'
1+
/**
2+
* Created by leon<[email protected]> on 22/04/30.
3+
*/
4+
import { interestNode, unInterestNode } from '@src/actions'
25
import { NodeDetailScreenProps as ScreenProps } from '@src/navigation/routes'
6+
import { RootState } from '@src/store'
37
import { SylCommon, useTheme } from '@src/theme'
4-
import React, { useEffect } from 'react'
8+
import { V2exObject } from '@src/types'
9+
import React, { useEffect, useMemo, useState } from 'react'
510
import { View } from 'react-native'
611
import { connect } from 'react-redux'
712
import { NodeInfoCard, NodeTopicTabList } from '../components'
13+
import { LikeNodeHeaderButton } from '../components/button'
814

9-
const NodeDetail = ({ route, navigation }: ScreenProps) => {
15+
const NodeDetail = ({
16+
interestNodes,
17+
route,
18+
navigation
19+
}: ScreenProps & {
20+
interestNodes: V2exObject.Node[]
21+
}) => {
1022
const { theme } = useTheme()
23+
const nodeName = useMemo(() => route.params.nodeName, [route])
24+
const [info, setInfo] = useState<V2exObject.Node | undefined>(undefined)
25+
26+
const HeaderRight = () => () => {
27+
return !info ? undefined : <LikeNodeHeaderButton node={info} />
28+
}
29+
1130
useEffect(() => {
1231
navigation.setOptions({
13-
title: route.params.nodeTitle
32+
title: route.params.nodeTitle,
33+
headerRight: HeaderRight()
1434
})
15-
}, [])
35+
}, [interestNodes, info])
1636

1737
return (
1838
<View style={SylCommon.Layout.fill}>
19-
<NodeInfoCard nodeid={route.params.nodeName} />
20-
<NodeTopicTabList nodename={route.params.nodeName} containerStyle={[{ marginTop: theme.spacing.small }]} />
39+
<NodeInfoCard nodeid={nodeName} loadedCallback={setInfo} />
40+
<NodeTopicTabList nodename={nodeName} containerStyle={[{ marginTop: theme.spacing.small }]} />
2141
</View>
2242
)
2343
}
2444

25-
export default connect()(NodeDetail)
45+
const mapStateToProps = ({ member: { interestNodes } }: RootState) => {
46+
return { interestNodes }
47+
}
48+
49+
export default connect(mapStateToProps, {})(NodeDetail)

0 commit comments

Comments
 (0)