Authored by 于良

收到的赞页面 review by 盖剑秋

@@ -42,6 +42,7 @@ import SectionContainer from './containers/SectionContainer'; @@ -42,6 +42,7 @@ import SectionContainer from './containers/SectionContainer';
42 import UserContainer from './containers/UserContainer'; 42 import UserContainer from './containers/UserContainer';
43 import UserThatNotMeContainer from './containers/UserThatNotMeContainer'; 43 import UserThatNotMeContainer from './containers/UserThatNotMeContainer';
44 import MessageCenterContainer from './containers/MessageCenterContainer'; 44 import MessageCenterContainer from './containers/MessageCenterContainer';
  45 +import LikeMessageContainer from './containers/LikeMessageContainer';
45 import SubjectPostContainer from './containers/SubjectPostContainer'; 46 import SubjectPostContainer from './containers/SubjectPostContainer';
46 import PostingContainer from './containers/PostingContainer'; 47 import PostingContainer from './containers/PostingContainer';
47 import SettingContainer from './containers/UserSettingContainer'; 48 import SettingContainer from './containers/UserSettingContainer';
@@ -216,6 +217,14 @@ export default function community(platform) { @@ -216,6 +217,14 @@ export default function community(platform) {
216 /> 217 />
217 218
218 <Scene 219 <Scene
  220 + key="LikeMessage"
  221 + title="收到的赞"
  222 + hideNavBar={false}
  223 + component={LikeMessageContainer}
  224 + initial={false}
  225 + />
  226 +
  227 + <Scene
219 key="Setting" 228 key="Setting"
220 title="社区资料" 229 title="社区资料"
221 hideNavBar={false} 230 hideNavBar={false}
@@ -83,7 +83,7 @@ let styles = StyleSheet.create({ @@ -83,7 +83,7 @@ let styles = StyleSheet.create({
83 fontFamily: 'Helvetica Light', 83 fontFamily: 'Helvetica Light',
84 color: 'black', 84 color: 'black',
85 fontSize: 13, 85 fontSize: 13,
86 - width: 130, 86 + maxWidth: 130,
87 }, 87 },
88 owner: { 88 owner: {
89 color: '#4a90e2', 89 color: '#4a90e2',
  1 +'use strict';
  2 +
  3 +import React from 'react';
  4 +import ReactNative from 'react-native';
  5 +import ImmutablePropTypes from 'react-immutable-proptypes';
  6 +import UserBrief from '../home/UserBrief';
  7 +import SlicedImage from '../../../common/components/SlicedImage';
  8 +
  9 +const {
  10 + View,
  11 + Text,
  12 + Image,
  13 + TouchableOpacity,
  14 + StyleSheet,
  15 + Dimensions,
  16 +} = ReactNative;
  17 +
  18 +export default class LikeCell extends React.Component {
  19 +
  20 + static propTypes = {
  21 + data: ImmutablePropTypes.contains({
  22 + user: ImmutablePropTypes.contains({
  23 + avatar: React.PropTypes.string,
  24 + uid: React.PropTypes.number,
  25 + nickName: React.PropTypes.string,
  26 + }),
  27 + timeago: React.PropTypes.string,
  28 + title: React.PropTypes.string,
  29 + post: ImmutablePropTypes.contains({
  30 + id: React.PropTypes.number,
  31 + thumb: React.PropTypes.string,
  32 + sectionId: React.PropTypes.string,
  33 + }),
  34 + }),
  35 + onPressPost: React.PropTypes.func,
  36 + onPressAvatar: React.PropTypes.func,
  37 + };
  38 +
  39 + constructor(props) {
  40 + super (props);
  41 +
  42 + }
  43 +
  44 + render() {
  45 + let data = this.props.data.toJS();
  46 + let {user, title, timeago, post} = data;
  47 +
  48 + return (
  49 + <TouchableOpacity
  50 + style={styles.row}
  51 + activeOpacity={0.8}
  52 + onPress={() => {
  53 + this.props.onPressPost && this.props.onPressPost(post.id);
  54 + }}
  55 + >
  56 + <UserBrief
  57 + avatar={user.avatar}
  58 + name={user.nickName}
  59 + timeago={timeago}
  60 + onPressAvatar={() => {
  61 + this.props.onPressAvatar && this.props.onPressAvatar(user.uid);
  62 + }}
  63 + />
  64 + <Text style={styles.title} numberOfLines={1}>{title}</Text>
  65 + <SlicedImage
  66 + style={styles.thumb}
  67 + source={{uri: post.thumb}}
  68 + defaultSource={require('../../images/message/text_default.png')}
  69 + />
  70 + </TouchableOpacity>
  71 + );
  72 + }
  73 +}
  74 +
  75 +let styles = StyleSheet.create({
  76 + row: {
  77 + backgroundColor: 'white',
  78 + paddingLeft: 17,
  79 + paddingRight: 17,
  80 + height: 65,
  81 + flexDirection: 'row',
  82 + },
  83 + title: {
  84 + fontSize: 13,
  85 + color: '#8b8b8b',
  86 + marginTop: 20,
  87 + marginLeft: 10,
  88 + },
  89 + thumb: {
  90 + width: 50,
  91 + height: 50,
  92 + position: 'absolute',
  93 + marginTop: 7.5,
  94 + right: 17,
  95 + },
  96 +});
@@ -2,12 +2,13 @@ @@ -2,12 +2,13 @@
2 2
3 import React from 'react'; 3 import React from 'react';
4 import ReactNative from 'react-native'; 4 import ReactNative from 'react-native';
  5 +import LikeCell from './LikeCell';
  6 +import LoadMoreIndicator from '../../../common/components/LoadMoreIndicator';
5 7
6 const { 8 const {
7 View, 9 View,
8 Text, 10 Text,
9 - Image,  
10 - TextInput, 11 + ListView,
11 StyleSheet, 12 StyleSheet,
12 Platform, 13 Platform,
13 Dimensions, 14 Dimensions,
@@ -15,27 +16,68 @@ const { @@ -15,27 +16,68 @@ const {
15 16
16 export default class LikeMessage extends React.Component { 17 export default class LikeMessage extends React.Component {
17 static propTypes = { 18 static propTypes = {
18 - 19 +
19 } 20 }
20 21
21 constructor(props) { 22 constructor(props) {
22 super(props); 23 super(props);
  24 +
  25 + this._renderRow = this._renderRow.bind(this);
  26 + this._renderSeparator = this._renderSeparator.bind(this);
  27 + this.dataSource = new ListView.DataSource({
  28 + rowHasChanged: (r1, r2) => !Immutable.is(r1, r2),
  29 + });
  30 + }
  31 +
  32 + _renderSeparator(sectionID, rowID, adjacentRowHighlighted) {
  33 + return (
  34 + <View key={'separator' + sectionID + rowID} style={styles.separator}/>
  35 + );
23 } 36 }
24 37
25 - _renderSeparator() { 38 + _renderRow(rowData, sectionID, rowID, highlightRow) {
26 return ( 39 return (
27 - <View style={styles.separator}/> 40 + <LikeCell
  41 + key={sectionID + rowID}
  42 + data={rowData}
  43 + onPressPost={this.props.onPressPost}
  44 + onPressAvatar={this.props.onPressAvatar}
  45 + />
28 ); 46 );
29 } 47 }
30 48
31 render() { 49 render() {
  50 + let {list, endReached, isLoadingMore, isFetching} = this.props;
32 return ( 51 return (
33 <View style={styles.container}> 52 <View style={styles.container}>
34 - <Text>收到的赞</Text> 53 + <ListView
  54 + ref={(c) => {
  55 + this.listView = c;
  56 + }}
  57 + dataSource={this.dataSource.cloneWithRows(list.toArray())}
  58 + renderRow={this._renderRow}
  59 + renderSeparator={this._renderSeparator}
  60 + enableEmptySections={true}
  61 + enablePullToRefresh={false}
  62 + onEndReached={() => {
  63 + this.props.onEndReached && this.props.onEndReached();
  64 + }}
  65 + renderFooter={()=>{
  66 + if (endReached) {
  67 + return <LoadMoreIndicator
  68 + isVisible={true}
  69 + text={'没有更多啦'}
  70 + />
  71 + } else {
  72 + return <LoadMoreIndicator
  73 + isVisible={isLoadingMore}
  74 + animating={isFetching}
  75 + />
  76 + }
  77 + }}
  78 + />
35 </View> 79 </View>
36 -  
37 -  
38 - ); 80 + )
39 } 81 }
40 } 82 }
41 83
@@ -43,11 +85,13 @@ export default class LikeMessage extends React.Component { @@ -43,11 +85,13 @@ export default class LikeMessage extends React.Component {
43 85
44 let {width, height} = Dimensions.get('window'); 86 let {width, height} = Dimensions.get('window');
45 let navbarHeight = (Platform.OS === 'android') ? 50 : 64; 87 let navbarHeight = (Platform.OS === 'android') ? 50 : 64;
  88 +
46 let styles = StyleSheet.create({ 89 let styles = StyleSheet.create({
47 container: { 90 container: {
48 - top: navbarHeight,  
49 - height: height-navbarHeight,  
50 - backgroundColor: 'white', 91 + flex: 1,
  92 + },
  93 + separator: {
  94 + height: 0.5,
  95 + backgroundColor: '#e0e0e0',
51 }, 96 },
52 -  
53 }); 97 });
@@ -150,7 +150,7 @@ export default class UserThatNotMe extends React.Component { @@ -150,7 +150,7 @@ export default class UserThatNotMe extends React.Component {
150 } 150 }
151 151
152 render() { 152 render() {
153 - let {posts, endReached, isRefreshing, isLoadingMore, isFetching, navigationState} = this.props; 153 + let {posts, endReached, isRefreshing, isLoadingMore, isFetching} = this.props;
154 let dataSource = { 154 let dataSource = {
155 posts: posts.toArray(), 155 posts: posts.toArray(),
156 } 156 }
@@ -228,9 +228,4 @@ let styles = StyleSheet.create({ @@ -228,9 +228,4 @@ let styles = StyleSheet.create({
228 height: 0.5, 228 height: 0.5,
229 backgroundColor: '#e0e0e0', 229 backgroundColor: '#e0e0e0',
230 }, 230 },
231 - fly: {  
232 - position: 'absolute',  
233 - right: 20,  
234 - bottom: 20,  
235 - },  
236 }); 231 });
@@ -7,6 +7,7 @@ import {bindActionCreators} from 'redux'; @@ -7,6 +7,7 @@ import {bindActionCreators} from 'redux';
7 import {connect} from 'react-redux'; 7 import {connect} from 'react-redux';
8 8
9 import Immutable, {Map, List} from 'immutable'; 9 import Immutable, {Map, List} from 'immutable';
  10 +import {Actions} from 'react-native-router-flux';
10 11
11 import Home from '../components/home/Home'; 12 import Home from '../components/home/Home';
12 13
@@ -14,7 +15,7 @@ import * as homeActions from '../reducers/home/homeActions'; @@ -14,7 +15,7 @@ import * as homeActions from '../reducers/home/homeActions';
14 import * as appActions from '../reducers/app/appActions'; 15 import * as appActions from '../reducers/app/appActions';
15 import * as userActions from '../reducers/user/userActions'; 16 import * as userActions from '../reducers/user/userActions';
16 import * as postingActions from '../reducers/posting/postingActions'; 17 import * as postingActions from '../reducers/posting/postingActions';
17 -import {Actions} from 'react-native-router-flux'; 18 +
18 19
19 const { 20 const {
20 View, 21 View,
@@ -4,13 +4,14 @@ import React from 'react'; @@ -4,13 +4,14 @@ import React from 'react';
4 import ReactNative from 'react-native'; 4 import ReactNative from 'react-native';
5 import Immutable, {Map} from 'immutable'; 5 import Immutable, {Map} from 'immutable';
6 6
7 -  
8 import {bindActionCreators} from 'redux'; 7 import {bindActionCreators} from 'redux';
9 import {connect} from 'react-redux'; 8 import {connect} from 'react-redux';
  9 +import {Actions} from 'react-native-router-flux';
10 10
11 import LikeMessage from '../components/message/LikeMessage'; 11 import LikeMessage from '../components/message/LikeMessage';
12 -  
13 import * as messageActions from '../reducers/message/messageActions'; 12 import * as messageActions from '../reducers/message/messageActions';
  13 +import * as homeActions from '../reducers/home/homeActions';
  14 +import {shouldShowTabBar, shouldHideTabBar} from '../utils/tabBar';
14 15
15 const { 16 const {
16 Text, 17 Text,
@@ -18,10 +19,12 @@ const { @@ -18,10 +19,12 @@ const {
18 StyleSheet, 19 StyleSheet,
19 Dimensions, 20 Dimensions,
20 Platform, 21 Platform,
  22 + InteractionManager,
21 } = ReactNative; 23 } = ReactNative;
22 24
23 const actions = [ 25 const actions = [
24 messageActions, 26 messageActions,
  27 + homeActions,
25 ]; 28 ];
26 29
27 function mapStateToProps(state) { 30 function mapStateToProps(state) {
@@ -45,20 +48,60 @@ function mapDispatchToProps(dispatch) { @@ -45,20 +48,60 @@ function mapDispatchToProps(dispatch) {
45 class LikeMessageContainer extends React.Component { 48 class LikeMessageContainer extends React.Component {
46 constructor(props) { 49 constructor(props) {
47 super(props); 50 super(props);
  51 +
  52 + this._onPressPost = this._onPressPost.bind(this);
  53 + this._onPressAvatar = this._onPressAvatar.bind(this);
  54 + this._onEndReached = this._onEndReached.bind(this);
  55 + }
  56 +
  57 + componentDidMount() {
  58 + if (shouldHideTabBar(this.props.navigationState)) {
  59 + ReactNative.NativeModules.YH_CommunityHelper.hideTabBar();
  60 + }
  61 +
  62 + this._onEndReached();
  63 + }
  64 +
  65 + componentWillUnmount() {
  66 + if (shouldShowTabBar(this.props.navigationState)) {
  67 + Actions.refresh({key: 'Home', showNativeTabBar: true});
  68 + }
  69 + }
  70 +
  71 + _onPressPost(id) {
  72 + this.props.actions.goToPost(id);
  73 + }
  74 +
  75 + _onPressAvatar(uid) {
  76 + this.props.actions.goToUserOrMe(uid);
  77 + }
  78 +
  79 + _onEndReached() {
  80 + InteractionManager.runAfterInteractions(() => {
  81 + this.props.actions.likeMessage(false);
  82 + });
48 } 83 }
49 84
50 render() { 85 render() {
  86 +
  87 + let {like} = this.props.message;
  88 +
51 return ( 89 return (
52 <View style={styles.container}> 90 <View style={styles.container}>
53 <LikeMessage 91 <LikeMessage
54 - 92 + list={like.list}
  93 + isFetching={like.isFetching}
  94 + endReached={like.endReached}
  95 + isLoadingMore={like.isFetching}
  96 + onEndReached={this._onEndReached}
  97 + onPressPost={this._onPressPost}
  98 + onPressAvatar={this._onPressAvatar}
55 /> 99 />
56 </View> 100 </View>
57 ); 101 );
58 } 102 }
59 } 103 }
60 104
61 -  
62 let {width, height} = Dimensions.get('window'); 105 let {width, height} = Dimensions.get('window');
63 let navbarHeight = (Platform.OS === 'android') ? 50 : 64; 106 let navbarHeight = (Platform.OS === 'android') ? 50 : 64;
64 107
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 import {Actions} from 'react-native-router-flux'; 3 import {Actions} from 'react-native-router-flux';
4 import MessageService from '../../services/MessageService'; 4 import MessageService from '../../services/MessageService';
5 -// import timeago from 'timeago.js'; 5 +import timeago from '../../utils/timeago';
6 6
7 const { 7 const {
8 GO_TO_MESSAGE_CENTER, 8 GO_TO_MESSAGE_CENTER,
@@ -15,30 +15,63 @@ const { @@ -15,30 +15,63 @@ const {
15 } = require('../../constants/actionTypes').default; 15 } = require('../../constants/actionTypes').default;
16 16
17 17
18 -export function likeMessageRequest() { 18 +export function likeMessageRequest(ptr) {
19 return { 19 return {
20 type: LIKE_MESSAGE_REQUEST, 20 type: LIKE_MESSAGE_REQUEST,
  21 + payload: ptr
21 }; 22 };
22 } 23 }
23 24
24 -export function likeMessageSuccess() { 25 +export function likeMessageSuccess(json) {
25 return { 26 return {
26 type: LIKE_MESSAGE_SUCCESS, 27 type: LIKE_MESSAGE_SUCCESS,
  28 + payload: json
27 }; 29 };
28 } 30 }
29 31
30 -export function likeMessageFailue() { 32 +export function likeMessageFailue(error) {
31 return { 33 return {
32 type: LIKE_MESSAGE_FAILURE, 34 type: LIKE_MESSAGE_FAILURE,
  35 + payload: error
33 }; 36 };
34 } 37 }
35 38
36 -export function likeMessage() { 39 +export function likeMessage(ptr = false) {
37 return (dispatch, getState) => { 40 return (dispatch, getState) => {
38 - dispatch(likeMessageRequest());  
39 - return new MessageService().messageList(uid) 41 + let {user, message} = getState();
  42 +
  43 + // 接口请求跳出的条件:
  44 + // 前置条件:下拉刷新优先级高于上拉加载
  45 + if (ptr) {
  46 + //下拉刷新直接执行
  47 + } else {
  48 + // 1.当次请求不是下拉刷新,同时正在进行下拉刷新的请求,跳出
  49 + // 2.当次请求不是下拉刷新,同时接口请求正在加载中, 跳出
  50 + // 3.当次请求不是下拉刷新,数据已全部加载完成,跳出
  51 + if (message.like.ptr || message.like.isFetching || message.like.endReached || message.like.error) {
  52 + return;
  53 + }
  54 + }
  55 +
  56 + dispatch(likeMessageRequest(ptr));
  57 +
  58 + let uid = user.profile.uid;
  59 + let lastedTime = 0;
  60 + if (!ptr) {
  61 + lastedTime = message.like.lastedTime;
  62 + }
  63 + let type = 2; // 点赞列表
  64 + let limit = 10;
  65 +
  66 + return new MessageService().messageList(uid, type, lastedTime, limit)
40 .then(json => { 67 .then(json => {
41 - dispatch(likeMessageSuccess(json)); 68 + let payload = parseLikeMessage(json);
  69 + if (!ptr) {
  70 + let oldList = message.like.list.toJS();
  71 + let list = [...oldList, ...payload.list];
  72 + payload.list = list;
  73 + }
  74 + dispatch(likeMessageSuccess(payload));
42 }) 75 })
43 .catch(error => { 76 .catch(error => {
44 dispatch(likeMessageFailue(error)); 77 dispatch(likeMessageFailue(error));
@@ -46,30 +79,68 @@ export function likeMessage() { @@ -46,30 +79,68 @@ export function likeMessage() {
46 }; 79 };
47 } 80 }
48 81
  82 +function parseLikeMessage(json) {
  83 + let {lastedTime, list} = json;
  84 + let messages = [];
  85 + list && list.map((item, i) => {
49 86
  87 + let id = item.id ? item.id : 0;
  88 + let title = item.title ? item.title : '';
  89 + let type = item.type ? item.type : 0;
  90 + let isRead = item.isRead ? item.isRead : 'Y';
  91 + let createTime = item.createTime;
  92 + let timeagoStr = timeago(createTime);
50 93
51 -// 页面跳转  
52 -export function goToStatsPage(type) {  
53 - switch (type) {  
54 -  
55 - case GO_TO_SYS_MESSAGE:  
56 - {  
57 - Actions.SystemMessage();  
58 - return {  
59 - type: GO_TO_SYS_MESSAGE, 94 + let user = {};
  95 + let post = {};
  96 + let {content} = item;
  97 + if (content) {
  98 + let {userInfo, postInfo} = content;
  99 + if (userInfo) {
  100 + let nickName = userInfo.nickName ? userInfo.nickName : '';
  101 + let avatar = userInfo.headIcon ? userInfo.headIcon : '';
  102 + let uid = userInfo.uid ? userInfo.uid : 0;
  103 + let sign = userInfo.signature ? userInfo.signature : '';
  104 + let backgroundImage = userInfo.bgPic ? userInfo.bgPic : '';
  105 + user = {
  106 + nickName,
  107 + avatar,
  108 + uid,
  109 + sign,
  110 + backgroundImage,
  111 + };
60 } 112 }
61 - }  
62 - break;  
63 -  
64 - case GO_TO_LIKE_MESSAGE:  
65 - {  
66 - Actions.LikeMessage();  
67 - return {  
68 - type: GO_TO_LIKE_MESSAGE, 113 + if (postInfo) {
  114 + let sectionId = postInfo.sectionId ? postInfo.sectionId : '';
  115 + let postId = postInfo.postId ? postInfo.postId : 0;
  116 + postId = parseInt(postId);
  117 + let thumb = postInfo.content ? postInfo.content : '';
  118 + post = {
  119 + id: postId,
  120 + thumb,
  121 + sectionId,
  122 + };
69 } 123 }
70 } 124 }
71 - break;  
72 - default:  
73 125
74 - } 126 + let message = {
  127 + id,
  128 + title,
  129 + type,
  130 + isRead,
  131 + createTime,
  132 + timeago: timeagoStr,
  133 + user,
  134 + post,
  135 + }
  136 +
  137 + messages.push(message);
  138 + });
  139 +
  140 + let endReached = messages.length == 0;
  141 + return {
  142 + lastedTime,
  143 + list: messages,
  144 + endReached,
  145 + }
75 } 146 }
@@ -8,6 +8,7 @@ let InitialState = Record({ @@ -8,6 +8,7 @@ let InitialState = Record({
8 name: null, 8 name: null,
9 sign:null, 9 sign:null,
10 like: new (Record({ 10 like: new (Record({
  11 + ptr: false,
11 isFetching: false, 12 isFetching: false,
12 error: null, 13 error: null,
13 lastedTime: 0, 14 lastedTime: 0,
@@ -4,9 +4,9 @@ import Immutable, {Map} from 'immutable'; @@ -4,9 +4,9 @@ import Immutable, {Map} from 'immutable';
4 4
5 const { 5 const {
6 6
7 - SETTING_SAVE_REQUEST,  
8 - SETTING_SAVE_SUCCESS,  
9 - SETTING_SAVE_FAILURE, 7 + LIKE_MESSAGE_REQUEST,
  8 + LIKE_MESSAGE_SUCCESS,
  9 + LIKE_MESSAGE_FAILURE,
10 10
11 } = require('../../constants/actionTypes').default; 11 } = require('../../constants/actionTypes').default;
12 12
@@ -14,13 +14,28 @@ const initialState = new InitialState; @@ -14,13 +14,28 @@ const initialState = new InitialState;
14 export default function message(state = initialState, action) { 14 export default function message(state = initialState, action) {
15 if (!(state instanceof InitialState)) return initialState.merge(state); 15 if (!(state instanceof InitialState)) return initialState.merge(state);
16 16
17 - // switch (action.type) {  
18 - // case SETTING_SAVE_REQUEST:  
19 - //  
20 - // break;  
21 - // default:  
22 - //  
23 - // } 17 + switch (action.type) {
  18 + case LIKE_MESSAGE_REQUEST: {
  19 + let nextState = state.setIn(['like', 'isFetching'], true)
  20 + .setIn(['like', 'error'], null);
  21 + return nextState;
  22 + }
  23 +
  24 + case LIKE_MESSAGE_SUCCESS: {
  25 + let {lastedTime, list, endReached} = action.payload;
  26 + let nextState = state.setIn(['like', 'isFetching'], false)
  27 + .setIn(['like', 'error'], null)
  28 + .setIn(['like', 'lastedTime'], lastedTime)
  29 + .setIn(['like', 'endReached'], endReached)
  30 + .setIn(['like', 'list'], Immutable.fromJS(list));
  31 + return nextState;
  32 + }
  33 +
  34 + case LIKE_MESSAGE_FAILURE:
  35 + return state.setIn(['like', 'isFetching'], false)
  36 + .setIn(['like', 'error'], action.payload);
  37 +
  38 + }
24 39
25 return state; 40 return state;
26 } 41 }