Authored by 王钱钧

Merge branch 'develop' of http://git.yoho.cn/mobile/YH_RNComponent into develop

Conflicts:
	js/community/containers/HomeContainer.js
'use strict';
import React, {Component} from 'react'
import {
StyleSheet,
View,
Text,
Dimensions,
ActivityIndicator,
Platform,
} from 'react-native';
export default class LoadingIndicator extends Component {
static propTypes = {
isVisible: React.PropTypes.bool.isRequired,
size: React.PropTypes.string,
color: React.PropTypes.string,
overlayWidth: React.PropTypes.number,
overlayHeight: React.PropTypes.number,
overlayColor: React.PropTypes.string,
text: React.PropTypes.string,
textColor: React.PropTypes.string,
textFontSize: React.PropTypes.number,
};
static defaultProps = {
isDismissible: false,
overlayWidth: 100,
overlayHeight: 100,
overlayColor: 'rgba(0,0,0,0.6)',
size: (Platform.OS === 'ios') ? 'large' : 'Normal',
color: (Platform.OS === 'ios') ? 'white' : 'gray',
text: '加载中...',
textColor: '#eeeeee',
textFontSize: 14,
};
render() {
if (!this.props.isVisible) {
return null;
}
let customStyles = StyleSheet.create({
overlay: {
alignItems: 'center',
justifyContent: 'center',
borderRadius: 10,
backgroundColor: this.props.overlayColor,
width: this.props.overlayWidth,
height: this.props.overlayHeight,
},
text: {
color: this.props.textColor,
fontSize: this.props.textFontSize,
marginTop: 8,
},
});
return (
<View style={styles.container}>
<View style={customStyles.overlay}>
<ActivityIndicator color={this.props.color} size={this.props.size}/>
<Text style={customStyles.text}>{this.props.text}</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
backgroundColor: 'transparent',
justifyContent: 'center',
alignItems: 'center',
top: 0,
bottom: 0,
left: 0,
right: 0,
},
});
... ...
'use strict';
import React from 'react';
import {AppRegistry, Platform, StyleSheet, Dimensions, Text, Alert} from 'react-native';
import ReactNative, {
AppRegistry,
Platform,
StyleSheet,
Dimensions,
TouchableOpacity,
View,
Text,
Image,
Alert,
Animated,
} from 'react-native';
import {
Provider,
... ... @@ -35,9 +46,13 @@ import NavBar from './components/NavBar';
import {
setPlatform,
setContianer,
setDot
setChannel,
} from './reducers/app/appActions';
import {
syncUser,
} from './reducers/home/homeActions';
/**
*
... ... @@ -70,6 +85,8 @@ export default function community(platform) {
store.dispatch(setPlatform(platform));
store.dispatch(setContianer(this.props.container));
store.dispatch(setChannel(this.props.channel));
// setup the router table with App selected as the initial component
return (
<Provider store={store}>
... ... @@ -80,9 +97,12 @@ export default function community(platform) {
hideTabBar={true}
navBar={NavBar}
titleStyle={styles.navTitle}
leftButtonStyle={styles.button}
rightButtonStyle={styles.button}
getSceneStyle={(props) => {
return this.navPushStyle(props);
}}
getChannel={() => store.getState().app.channel}
>
<Scene
key="Home"
... ... @@ -92,10 +112,13 @@ export default function community(platform) {
initial={true}
leftTitle={null}
leftButtonImage={require('./images/home/menu_burger.png')}
onLeft={() => {}}
onLeft={() => {
ReactNative.NativeModules.YH_CommunityHelper.toggleDrawer();
}}
rightTitle={null}
rightButtonImage={require('./images/home/menu_write.png')}
onRight={() => {Actions.Posting();}}
onRight={this.homeOnRight}
renderTitle={this.renderHomeTitle}
/>
<Scene
... ... @@ -164,6 +187,51 @@ export default function community(platform) {
);
},
renderHomeTitle(navProps) {
let {width, height} = Dimensions.get('window');
let distance = Math.ceil(width * 90 / 375);
let distance2 = Math.ceil(width * 60 / 375);
let distance3 = Math.ceil(width * 35 / 375);
let line = 375 - (distance + distance2);
return (
<Animated.View style={[styles.title,]}>
<TouchableOpacity onPress={() => {
ReactNative.NativeModules.YH_CommunityHelper.hideRNNavBar(0);
}}>
<Text style={[styles.text, {flex: 0, marginLeft: distance,}]}></Text>
</TouchableOpacity>
<Text style={[styles.text, {flex: 0, marginLeft: distance2,}]}>社区</Text>
<TouchableOpacity onPress={() => {
ReactNative.NativeModules.YH_CommunityHelper.hideRNNavBar(2);
}}>
<Image style={{marginLeft: distance3,}} source={require('./images/home/sd_show_ic.png')}/>
</TouchableOpacity>
<View style={{
backgroundColor: 'white',
position: 'absolute',
width: 45,
height: 2,
left: 165,
top: 30,
}}/>
</Animated.View>
);
},
homeOnRight(state) {
ReactNative.NativeModules.YH_CommunityHelper.uid()
.then(uid => {
state.dispatch(syncUser(uid));
})
.catch(error => {
ReactNative.NativeModules.YH_CommunityHelper.login()
.then(uid => {
state.dispatch(syncUser(uid));
});
});
},
/*
* 自定义导航push动画
*
... ... @@ -214,13 +282,16 @@ export default function community(platform) {
let styles = StyleSheet.create({
scene: {
backgroundColor:'#F0F0F0',
backgroundColor: '#F0F0F0',
},
navTitle: {
color: 'white',
marginLeft: 60,
marginRight: 60,
},
button: {
width: 60,
},
rightButton: {
width: 100,
height: 37,
... ... @@ -242,4 +313,30 @@ let styles = StyleSheet.create({
textAlign: 'right',
fontSize: 17,
},
title: {
flexDirection: 'row',
// justifyContent: 'space-around',
alignItems: 'center',
marginTop: 12,
// width: Dimensions.get('window').width - 2 * 50,
position: 'absolute',
...Platform.select({
ios: {
top: 20,
},
android: {
top: 5,
},
}),
left: 0,
right: 0,
backgroundColor: 'transparent',
},
text: {
textAlign: 'center',
fontSize: 18,
fontWeight: 'bold',
color: '#ffffff',
},
});
... ...
... ... @@ -454,6 +454,28 @@ class NavBar extends React.Component {
);
}
getNavBarBackgroundImage(channel) {
let img = require('../images/nav/boy/navbar_bg.png');
switch (parseInt(channel)) {
case 1:
img = require('../images/nav/boy/navbar_bg.png');
break;
case 2:
img = require('../images/nav/girl/navbar_bg.png');
break;
case 3:
img = require('../images/nav/kid/navbar_bg.png');
break;
case 4:
img = require('../images/nav/lifestyle/navbar_bg.png');
break;
case 5:
img = require('../images/nav/yoho/navbar_bg.png');
break;
}
return img;
}
render() {
let state = this.props.navigationState;
let selected = state.children[state.index];
... ... @@ -474,6 +496,7 @@ class NavBar extends React.Component {
const renderTitle = selected.renderTitle ||
selected.component.renderTitle ||
this.props.renderTitle;
return (
<Animated.View
style={[
... ... @@ -484,7 +507,7 @@ class NavBar extends React.Component {
]}
>
<Image
source={require('../images/navbar.png')}
source={this.getNavBarBackgroundImage(this.props.getChannel())}
style={styles.image}
>
{renderTitle ? renderTitle(navProps) : state.children.map(this.renderTitle, this)}
... ...
... ... @@ -8,6 +8,7 @@ import Notice from './Notice';
import YH_SectionView from './YH_SectionView';
import ListCell from './ListCell';
import UploadProgress from './UploadProgress';
import LoadingIndicator from '../../../common/components/LoadingIndicator';
import LoadMoreIndicator from '../../../common/components/LoadMoreIndicator';
... ... @@ -172,11 +173,10 @@ export default class Home extends React.Component {
/>
);
case 'section':
console.log(rowData.toJS());
return (
<YH_SectionView
style={styles.carouselSection}
items={rowData}
items={rowData.toJS()}
onClick={(info) => {
let item = rowData.get(info.index);
let section = {
... ... @@ -241,7 +241,7 @@ export default class Home extends React.Component {
}
render() {
let {banner, notice, section, recommendation, isRefreshing, isLoadingMore, isFetching} = this.props;
let {banner, notice, section, recommendation, isRefreshing, isLoadingMore, isFetching, isSyncFetching} = this.props;
let dataSource = {
progressing: this.props.progressing.toArray(),
banner: banner.toArray(),
... ... @@ -251,6 +251,7 @@ export default class Home extends React.Component {
}
return (
<View>
<ListView
dataSource={this.dataSource.cloneWithRowsAndSections(dataSource)}
renderRow={this._renderRow}
... ... @@ -275,6 +276,10 @@ export default class Home extends React.Component {
/>
}}
/>
<LoadingIndicator
isVisible={isSyncFetching}
/>
</View>
);
}
... ...
... ... @@ -26,15 +26,14 @@ class SectionView extends React.Component {
}
render() {
return <YH_SectionView {...this.props} items={this.props.items.toJS()} onClick={this._onClick} />;
return <YH_SectionView {...this.props} items={this.props.items} onClick={this._onClick} />;
}
}
SectionView.propTypes = {
items: ImmutablePropTypes.listOf(
ImmutablePropTypes.contains({
header: ImmutablePropTypes.contains({
items: React.PropTypes.arrayOf(
React.PropTypes.shape({
header: React.PropTypes.shape({
id: React.PropTypes.number.isRequired,
logo: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
... ... @@ -42,15 +41,15 @@ SectionView.propTypes = {
commentNum: React.PropTypes.number.isRequired,
laudCount: React.PropTypes.number.isRequired,
}),
hot: ImmutablePropTypes.contains({
hot: React.PropTypes.shape({
avatar: React.PropTypes.string,
content: React.PropTypes.string.isRequired,
}),
new: ImmutablePropTypes.contains({
new: React.PropTypes.shape({
avatar: React.PropTypes.string,
content: React.PropTypes.string.isRequired,
}),
num: ImmutablePropTypes.contains({
num: React.PropTypes.shape({
onedayAddNum: React.PropTypes.number.isRequired,
}),
})
... ...
... ... @@ -4,6 +4,11 @@ export default keyMirror({
SET_PLATFORM: null,
SET_CONTAINER: null,
SET_CHANNEL: null,
SYNC_USER_REQUEST: null,
SYNC_USER_SUCCESS: null,
SYNC_USER_FAILURE: null,
GO_TO_SECTION: null,
... ...
... ... @@ -11,6 +11,7 @@ import Immutable, {Map, List} from 'immutable';
import Home from '../components/home/Home';
import * as homeActions from '../reducers/home/homeActions';
import * as appActions from '../reducers/app/appActions';
import {Actions} from 'react-native-router-flux';
... ... @@ -22,6 +23,7 @@ const {
Platform,
NativeModules,
InteractionManager,
NativeAppEventEmitter,
} = ReactNative;
/**
... ... @@ -30,6 +32,7 @@ const {
*/
const actions = [
homeActions,
appActions,
];
/**
... ... @@ -74,16 +77,35 @@ class HomeContainer extends React.Component {
this._onRefresh = this._onRefresh.bind(this);
this._onEndReached = this._onEndReached.bind(this);
if (parseInt(this.props.app.container) === 1) {
this.subscription = NativeAppEventEmitter.addListener(
'ChannelReminder',
(reminder) => {
this.props.actions.setChannel(reminder.channel);
Actions.refresh({channel: reminder.channel});
}
);
}
}
componentDidMount() {
let RNNativeConfig = NativeModules.RNNativeConfig;
RNNativeConfig.hideNavBar();
InteractionManager.runAfterInteractions(() => {
this._onRefresh();
});
}
componentWillReceiveProps(nextProps) {
if (nextProps.showNativeTabBar) {
ReactNative.NativeModules.YH_CommunityHelper.showTabBar();
}
}
componentWillUnmount() {
if (parseInt(this.props.app.container) === 1) {
this.subscription && this.subscription.remove();
}
}
_onPressBanner(url) {
console.log('banner');
... ... @@ -94,7 +116,7 @@ class HomeContainer extends React.Component {
}
_onPressSection(section) {
this.props.actions.goToSection(section);
this.props.actions.goToSection(section, this.props.navigationState.name);
}
_onPressPost(url) {
... ... @@ -155,10 +177,9 @@ class HomeContainer extends React.Component {
image:'',
}];
progressing = Immutable.fromJS(progressing);
let {banner, notice, section, recommendation, isFetching, ptr} = this.props.home;
let {banner, notice, section, recommendation, isFetching, ptr, sync} = this.props.home;
let bannerData = Immutable.fromJS([banner.list]);
let noticeData = notice.open === 'Y' ? Immutable.fromJS([notice.list]) : List();
let sectionData = Immutable.fromJS([section]);
... ... @@ -196,6 +217,8 @@ class HomeContainer extends React.Component {
onUploadSuccess={this._onUploadSuccess}
onUploadRetry={this._onUploadRetry}
onUploadDelete={this._onUploadDelete}
isSyncFetching={sync.isFetching}
/>
</View>
);
... ...
... ... @@ -73,17 +73,23 @@ class SectionContainer extends React.Component {
this._onPressLike = this._onPressLike.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this._onEndReached = this._onEndReached.bind(this);
this.page = 0;
}
componentDidMount() {
if (this.props.section.previousScene === 'Home') {
ReactNative.NativeModules.YH_CommunityHelper.hideTabBar();
}
InteractionManager.runAfterInteractions(() => {
this._onRefresh();
});
}
componentWillUnmount() {
if (this.props.section.previousScene === 'Home') {
Actions.refresh({key: 'Home', showNativeTabBar: true});
}
this.props.actions.sectionClean();
}
... ... @@ -189,7 +195,7 @@ let navbarHeight = (Platform.OS === 'android') ? 50 : 64;
let styles = StyleSheet.create({
container: {
top: navbarHeight,
height: height - navbarHeight - 49,
height: height - navbarHeight,
flex: 1,
},
... ...
... ... @@ -11,7 +11,7 @@ import {Actions} from 'react-native-router-flux';
const {
SET_PLATFORM,
SET_CONTAINER,
SET_CHANNEL,
} = require('../../constants/actionTypes').default;
export function setPlatform(platform) {
... ... @@ -27,3 +27,10 @@ export function setContianer(container) {
payload: container
};
}
export function setChannel(channel) {
return {
type: SET_CHANNEL,
payload: channel
};
}
... ...
... ... @@ -16,7 +16,8 @@ import {Record, List, Map} from 'immutable';
*/
let InitialState = Record({
platform: 'ios', // ios, android
container: 1, // 1:有货APP,2:YOHO!
container: 1, // 1:有货APP, 2:YOHO!
channel: 1, // 1 - boy, 2 - girl, 3 - kid, 4 - lifestyle, 5 - yoho
});
export default InitialState;
... ...
... ... @@ -14,7 +14,7 @@ import InitialState from './appInitialState';
const {
SET_PLATFORM,
SET_CONTAINER,
SET_CHANNEL,
} = require('../../constants/actionTypes').default;
const initialState = new InitialState;
... ... @@ -32,6 +32,8 @@ export default function appReducer(state = initialState, action) {
return state.set('platform', action.payload);
case SET_CONTAINER:
return state.set('container', action.payload);
case SET_CHANNEL:
return state.set('channel', action.payload);
}
return state;
... ...
... ... @@ -21,9 +21,46 @@ const {
HOME_RECOMMENDATION_FAILURE,
GO_TO_SECTION,
SYNC_USER_REQUEST,
SYNC_USER_SUCCESS,
SYNC_USER_FAILURE,
} = require('../../constants/actionTypes').default;
export function syncUserRequest() {
return {
type: SYNC_USER_REQUEST,
};
}
export function syncUserSuccess(json) {
return {
type: SYNC_USER_SUCCESS,
payload: json
};
}
export function syncUserFailure(error) {
return {
type: SYNC_USER_FAILURE,
payload: error
};
}
export function syncUser(uid) {
return (dispatch, getState) => {
dispatch(syncUserRequest());
let {app} = getState();
return new HomeService().syncUser(uid, app.container)
.then(json => {
dispatch(syncUserSuccess(json.ssouid));
})
.catch(error => {
dispatch(syncUserFailure(error));
});
};
}
export function bnsRequest() {
return {
type: HOME_BNS_REQUEST,
... ... @@ -83,7 +120,7 @@ export function bannerNoticeSection() {
export function recommendation(ptr = false) {
return (dispatch, getState) => {
let {home} = getState();
if (home.isFetching) {
if (home.isFetching || home.recommendation.endReached) {
return;
}
dispatch(recommendationRequest(ptr));
... ... @@ -110,11 +147,11 @@ export function recommendation(ptr = false) {
};
}
export function goToSection(section) {
export function goToSection(section, previousScene) {
Actions.Section();
return {
type: GO_TO_SECTION,
payload: section,
payload: {...section, previousScene},
};
}
... ... @@ -223,8 +260,10 @@ function parseRecommendation(json) {
posts.push(post);
});
let endReached = posts.length == 0;
return {
lastedTime,
list: posts,
endReached,
}
}
... ...
... ... @@ -31,6 +31,12 @@ let InitialState = Record({
recommendation: new (Record({
lastedTime: 0,
list: List(),
endReached: false,
})),
sync: new (Record({
isFetching: false,
error: null,
ssouid: 0,
})),
});
... ...
... ... @@ -22,6 +22,9 @@ const {
HOME_RECOMMENDATION_SUCCESS,
HOME_RECOMMENDATION_FAILURE,
SYNC_USER_REQUEST,
SYNC_USER_SUCCESS,
SYNC_USER_FAILURE,
} = require('../../constants/actionTypes').default;
... ... @@ -55,10 +58,11 @@ export default function homeReducer(state = initialState, action) {
return nextState;
}
case HOME_RECOMMENDATION_SUCCESS: {
let {lastedTime, list} = action.payload;
let {lastedTime, list, endReached} = action.payload;
let nextState = state.set('isFetching', false)
.set('error', null)
.setIn(['recommendation', 'lastedTime'], lastedTime)
.setIn(['recommendation', 'endReached'], endReached)
.setIn(['recommendation', 'list'], Immutable.fromJS(list));
return nextState;
}
... ... @@ -67,6 +71,23 @@ export default function homeReducer(state = initialState, action) {
return state.set('isFetching', false)
.set('error', action.payload);
case SYNC_USER_REQUEST: {
let nextState = state.setIn(['sync', 'isFetching'], true)
.setIn(['sync', 'error'], null);
return nextState;
}
case SYNC_USER_SUCCESS: {
let nextState = state.setIn(['sync', 'isFetching'], false)
.setIn(['sync', 'error'], null)
.setIn(['sync', 'ssouid'], action.payload);
return nextState;
}
case SYNC_USER_FAILURE:
return state.setIn(['sync', 'isFetching'], false)
.setIn(['sync', 'error'], action.payload);
}
return state;
... ...
... ... @@ -105,7 +105,7 @@ export function header() {
export function newPost(ptr = false) {
return (dispatch, getState) => {
let {section} = getState();
if (section.new.isFetching) {
if (section.new.isFetching || section.new.endReached) {
return;
}
dispatch(newPostRequest(ptr));
... ... @@ -135,7 +135,7 @@ export function newPost(ptr = false) {
export function hotPost(ptr = false) {
return (dispatch, getState) => {
let {section} = getState();
if (section.hot.isFetching) {
if (section.hot.isFetching || section.hot.endReached) {
return;
}
dispatch(hotPostRequest(ptr));
... ... @@ -252,8 +252,11 @@ function parseNewPost(json) {
posts.push(post);
});
let endReached = posts.length == 0;
return {
lastedTime,
list: posts,
endReached,
}
}
... ...
... ... @@ -38,13 +38,16 @@ let InitialState = Record({
error: null,
lastedTime: 0,
list: List(),
endReached: false,
})),
new: new (Record({
isFetching: false,
error: null,
lastedTime: 0,
list: List(),
endReached: false,
})),
previousScene: '',
});
export default InitialState;
... ...
... ... @@ -70,19 +70,21 @@ export default function sectionReducer(state = initialState, action) {
}
case SECTION_HOT_POST_SUCCESS: {
let {lastedTime, list} = action.payload;
let {lastedTime, list, endReached} = action.payload;
let nextState = state.setIn(['hot', 'isFetching'], false)
.setIn(['hot', 'error'], null)
.setIn(['hot', 'lastedTime'], lastedTime)
.setIn(['hot', 'endReached'], endReached)
.setIn(['hot', 'list'], Immutable.fromJS(list));
return nextState;
}
case SECTION_NEW_POST_SUCCESS: {
let {lastedTime, list} = action.payload;
let {lastedTime, list, endReached} = action.payload;
let nextState = state.setIn(['new', 'isFetching'], false)
.setIn(['new', 'error'], null)
.setIn(['new', 'lastedTime'], lastedTime)
.setIn(['new', 'endReached'], endReached)
.setIn(['new', 'list'], Immutable.fromJS(list));
return nextState;
}
... ... @@ -104,7 +106,8 @@ export default function sectionReducer(state = initialState, action) {
case GO_TO_SECTION:
return state.set('id', action.payload.id)
.set('name', action.payload.name);
.set('name', action.payload.name)
.set('previousScene', action.payload.previousScene);
case SECTION_CLEAN:
return initialState;
... ...
... ... @@ -42,5 +42,21 @@ export default class HomeService {
});
}
async syncUser(uid, appChannel) {
return await this.api.get({
url: '',
body: {
method: 'app.social.synCommunityUser',
uid,
appChannel,
}
})
.then((json) => {
return json;
})
.catch((error) => {
throw(error);
});
}
}
... ...