Authored by 于良

Merge branch 'local' into develop

Showing 42 changed files with 583 additions and 78 deletions
@@ -44,6 +44,7 @@ const styles = StyleSheet.create({ @@ -44,6 +44,7 @@ const styles = StyleSheet.create({
44 flexDirection: 'row', 44 flexDirection: 'row',
45 justifyContent: 'center', 45 justifyContent: 'center',
46 alignItems: 'center', 46 alignItems: 'center',
  47 + width: Dimensions.get('window').width,
47 height: 44, 48 height: 44,
48 }, 49 },
49 indicator: { 50 indicator: {
@@ -23,7 +23,7 @@ export default class SlicedImage extends React.Component { @@ -23,7 +23,7 @@ export default class SlicedImage extends React.Component {
23 width = PixelRatio.getPixelSizeForLayoutSize(width); 23 width = PixelRatio.getPixelSizeForLayoutSize(width);
24 height = PixelRatio.getPixelSizeForLayoutSize(height); 24 height = PixelRatio.getPixelSizeForLayoutSize(height);
25 let newSrc = src; 25 let newSrc = src;
26 - if (src.indexOf('imageView') === -1) { 26 + if (src.indexOf('imageView') === -1 && src.indexOf('imageMogr') === -1) {
27 newSrc = src + '?imageView2/' + mode + '/w/' + width + '/h/' + height; 27 newSrc = src + '?imageView2/' + mode + '/w/' + width + '/h/' + height;
28 } else { 28 } else {
29 newSrc = src.replace('{mode}', mode) 29 newSrc = src.replace('{mode}', mode)
@@ -36,8 +36,7 @@ export default class ProductFilter extends React.Component { @@ -36,8 +36,7 @@ export default class ProductFilter extends React.Component {
36 asc: 's_n_desc', 36 asc: 's_n_desc',
37 desc: 's_n_desc', 37 desc: 's_n_desc',
38 }, 38 },
39 - isAsc: true,  
40 - selected: false, 39 + isAsc: false,
41 radio: true, 40 radio: true,
42 }, 41 },
43 { 42 {
@@ -47,8 +46,7 @@ export default class ProductFilter extends React.Component { @@ -47,8 +46,7 @@ export default class ProductFilter extends React.Component {
47 asc: 's_t_desc', 46 asc: 's_t_desc',
48 desc: 's_t_desc', 47 desc: 's_t_desc',
49 }, 48 },
50 - isAsc: true,  
51 - selected: false, 49 + isAsc: false,
52 radio: true, 50 radio: true,
53 }, 51 },
54 { 52 {
@@ -58,8 +56,7 @@ export default class ProductFilter extends React.Component { @@ -58,8 +56,7 @@ export default class ProductFilter extends React.Component {
58 asc: 's_p_asc', 56 asc: 's_p_asc',
59 desc: 's_p_desc', 57 desc: 's_p_desc',
60 }, 58 },
61 - isAsc: true,  
62 - selected: false, 59 + isAsc: false,
63 radio: false, 60 radio: false,
64 }, 61 },
65 { 62 {
@@ -69,8 +66,7 @@ export default class ProductFilter extends React.Component { @@ -69,8 +66,7 @@ export default class ProductFilter extends React.Component {
69 asc: 'p_d_asc', 66 asc: 'p_d_asc',
70 desc: 'p_d_desc', 67 desc: 'p_d_desc',
71 }, 68 },
72 - isAsc: true,  
73 - selected: false, 69 + isAsc: false,
74 radio: false, 70 radio: false,
75 }, 71 },
76 ], 72 ],
@@ -105,15 +101,22 @@ export default class ProductFilter extends React.Component { @@ -105,15 +101,22 @@ export default class ProductFilter extends React.Component {
105 let colorStyle = rowID == this.state.selectedIndex ? {color: '#444444'} : {color: '#b0b0b0'}; 101 let colorStyle = rowID == this.state.selectedIndex ? {color: '#444444'} : {color: '#b0b0b0'};
106 102
107 return ( 103 return (
108 - <TouchableOpacity onPress={()=>{ 104 + <TouchableOpacity onPress={() => {
109 let filters = this.state.filters; 105 let filters = this.state.filters;
110 let filter = this.state.filters[rowID]; 106 let filter = this.state.filters[rowID];
  107 + if (filter.radio && this.state.selectedIndex == rowID) {
  108 + return;
  109 + }
  110 +
111 filter.isAsc = !filter.isAsc; 111 filter.isAsc = !filter.isAsc;
112 filters[rowID] = filter; 112 filters[rowID] = filter;
113 this.setState({ 113 this.setState({
114 selectedIndex: rowID, 114 selectedIndex: rowID,
115 filters, 115 filters,
116 }); 116 });
  117 +
  118 + let value = filter.isAsc ? filter.value['asc'] : filter.value['desc'];
  119 + this.props.onPressFilter && this.props.onPressFilter(value);
117 }}> 120 }}>
118 <View key={'row' + rowID} style={styles.rowContainer}> 121 <View key={'row' + rowID} style={styles.rowContainer}>
119 <Text style={[styles.name, colorStyle]}>{rowData.name}</Text> 122 <Text style={[styles.name, colorStyle]}>{rowData.name}</Text>
@@ -132,17 +135,18 @@ export default class ProductFilter extends React.Component { @@ -132,17 +135,18 @@ export default class ProductFilter extends React.Component {
132 135
133 render() { 136 render() {
134 137
135 - let {} = this.props; 138 + let {style} = this.props;
136 139
137 return ( 140 return (
138 - <View style={styles.container}> 141 + <View style={[styles.container, style]}>
139 <ListView 142 <ListView
140 - contentContainerStyle={styles.contentContainer} 143 + contentContainerStyle={[styles.contentContainer, style]}
141 enableEmptySections={true} 144 enableEmptySections={true}
142 dataSource={this.dataSource.cloneWithRows(this.state.filters)} 145 dataSource={this.dataSource.cloneWithRows(this.state.filters)}
143 renderRow={this._renderRow} 146 renderRow={this._renderRow}
144 renderSeparator={this._renderSeparator} 147 renderSeparator={this._renderSeparator}
145 scrollEnabled={false} 148 scrollEnabled={false}
  149 + scrollsToTop={false}
146 /> 150 />
147 </View> 151 </View>
148 ); 152 );
@@ -159,9 +163,9 @@ let styles = StyleSheet.create({ @@ -159,9 +163,9 @@ let styles = StyleSheet.create({
159 marginLeft: -1, 163 marginLeft: -1,
160 width: width + 2, 164 width: width + 2,
161 height: 37, 165 height: 37,
  166 + borderTopColor: 'transparent',
162 borderBottomColor: '#e5e5e5', 167 borderBottomColor: '#e5e5e5',
163 borderWidth: 0.5, 168 borderWidth: 0.5,
164 -  
165 }, 169 },
166 contentContainer: { 170 contentContainer: {
167 flexDirection: 'row', 171 flexDirection: 'row',
@@ -177,7 +181,8 @@ let styles = StyleSheet.create({ @@ -177,7 +181,8 @@ let styles = StyleSheet.create({
177 color: '#b0b0b0', 181 color: '#b0b0b0',
178 }, 182 },
179 image: { 183 image: {
180 - 184 + marginTop: 2,
  185 + marginLeft: 2,
181 }, 186 },
182 separator: { 187 separator: {
183 width: 0.5, 188 width: 0.5,
@@ -12,8 +12,10 @@ import ReactNative, { @@ -12,8 +12,10 @@ import ReactNative, {
12 } from 'react-native'; 12 } from 'react-native';
13 13
14 import SlicedImage from '../../../common/components/SlicedImage'; 14 import SlicedImage from '../../../common/components/SlicedImage';
15 -import LoadingIndicator from '../../../common/components/LoadingIndicator'; 15 +import LoadMoreIndicator from '../../../common/components/LoadMoreIndicator';
16 import ProductListCell from './ProductListCell'; 16 import ProductListCell from './ProductListCell';
  17 +import ProductFilter from './ProductFilter';
  18 +import ProductShopCell from './ProductShopCell';
17 19
18 export default class Search extends Component { 20 export default class Search extends Component {
19 21
@@ -29,27 +31,76 @@ export default class Search extends Component { @@ -29,27 +31,76 @@ export default class Search extends Component {
29 } 31 }
30 32
31 _renderRow(rowData, sectionID, rowID) { 33 _renderRow(rowData, sectionID, rowID) {
  34 + switch (sectionID) {
  35 + case 'shop':
  36 + return (
  37 + <ProductShopCell
  38 + key={'row' + rowID}
  39 + data={rowData}
  40 + />
  41 + );
  42 + case 'placeholder':
  43 + return (
  44 + <View style={styles.separator}/>
  45 + );
  46 + case 'filter':
  47 + return (
  48 + <ProductFilter
  49 + style={styles.filterContainer}
  50 + onPressFilter={this.props.onPressFilter}
  51 + />
  52 + );
  53 +
  54 + case 'list':
  55 + let paddingLeft = rowID % 2 == 1 ? rowMarginHorizontal / 2 : rowMarginHorizontal;
  56 + let customStyle = rowID == 0 || rowID == 1 ? {paddingLeft, marginTop: 0} : {paddingLeft};
  57 + return (
  58 + <ProductListCell
  59 + style={[styles.listContainer, customStyle]}
  60 + key={'row' + rowID}
  61 + rowID={rowID}
  62 + data={rowData}
  63 + />
  64 + );
  65 + default:
  66 + return null;
  67 +
  68 + }
32 69
33 - return (  
34 - <ProductListCell  
35 - key={'row' + rowID}  
36 - rowID={rowID}  
37 - data={rowData}  
38 - />  
39 - );  
40 } 70 }
41 71
42 render() { 72 render() {
43 - let {list} = this.props; 73 + let {shop, list, isFetching, isLoadingMore, endReached} = this.props;
  74 + let dataSource = {
  75 + shop: shop.toArray(),
  76 + filter: ['filter'],
  77 + list: list.toArray(),
  78 + }
44 79
45 return ( 80 return (
46 <View style={styles.container}> 81 <View style={styles.container}>
47 <ListView 82 <ListView
48 contentContainerStyle={styles.contentContainer} 83 contentContainerStyle={styles.contentContainer}
49 enableEmptySections={true} 84 enableEmptySections={true}
50 - dataSource={this.dataSource.cloneWithRows(list.toArray())} 85 + dataSource={this.dataSource.cloneWithRowsAndSections(dataSource)}
51 renderRow={this._renderRow} 86 renderRow={this._renderRow}
52 keyboardDismissMode={'on-drag'} 87 keyboardDismissMode={'on-drag'}
  88 + onEndReached={() => {
  89 + this.props.onEndReached && this.props.onEndReached();
  90 + }}
  91 + renderFooter={()=>{
  92 + if (endReached) {
  93 + return <LoadMoreIndicator
  94 + isVisible={true}
  95 + text={'暂无更多'}
  96 + />
  97 + } else {
  98 + return <LoadMoreIndicator
  99 + isVisible={isLoadingMore}
  100 + animating={isFetching}
  101 + />
  102 + }
  103 + }}
53 /> 104 />
54 105
55 </View> 106 </View>
@@ -66,13 +117,21 @@ let rowMarginHorizontal = (width - rowWidth * 2) / 3; @@ -66,13 +117,21 @@ let rowMarginHorizontal = (width - rowWidth * 2) / 3;
66 let styles = StyleSheet.create({ 117 let styles = StyleSheet.create({
67 container: { 118 container: {
68 flex: 1, 119 flex: 1,
69 -  
70 backgroundColor: 'white', 120 backgroundColor: 'white',
71 }, 121 },
72 contentContainer: { 122 contentContainer: {
73 flexDirection: 'row', 123 flexDirection: 'row',
74 flexWrap: 'wrap', 124 flexWrap: 'wrap',
75 - justifyContent: 'space-between',  
76 - marginHorizontal: rowMarginHorizontal, 125 + },
  126 + filterContainer: {
  127 +
  128 + },
  129 + listContainer: {
  130 + width: width / 2,
  131 + },
  132 + separator: {
  133 + width,
  134 + height: 16,
  135 + backgroundColor: '#f0f0f0',
77 }, 136 },
78 }); 137 });
@@ -12,7 +12,8 @@ import ReactNative, { @@ -12,7 +12,8 @@ import ReactNative, {
12 } from 'react-native'; 12 } from 'react-native';
13 13
14 import SlicedImage from '../../../common/components/SlicedImage'; 14 import SlicedImage from '../../../common/components/SlicedImage';
15 -import LoadingIndicator from '../../../common/components/LoadingIndicator'; 15 +import Tags from './Tags';
  16 +
16 17
17 export default class Search extends Component { 18 export default class Search extends Component {
18 19
@@ -23,12 +24,29 @@ export default class Search extends Component { @@ -23,12 +24,29 @@ export default class Search extends Component {
23 24
24 25
25 render() { 26 render() {
26 - let {rowID, data} = this.props;  
27 - let url = data.get('default_images').replace('{width}', rowWidth).replace('{height}', imageHeight);; 27 + let {rowID, data, style} = this.props;
  28 + let url = data.get('default_images').replace('{width}', rowWidth).replace('{height}', imageHeight);
  29 + url = SlicedImage.getSlicedUrl(data.get('default_images'), 290, 386, 2);
28 return ( 30 return (
29 - <View style={[styles.container]}>  
30 - <Image style={styles.image} source={{uri: url}}/>  
31 - <Text>{data.get('product_name')}</Text> 31 + <View style={[styles.container, style]}>
  32 + <Tags/>
  33 + <View style={styles.imageContainer}>
  34 + <Image style={styles.image} source={{uri: url}}>
  35 + <Image style={styles.almostSoldOutImage} source={require('../../images/tag/tip_jjsq.png')}/>
  36 + <Image style={styles.soldOutImage} source={require('../../images/tag/outlet_sellout_bg.png')}/>
  37 + </Image>
  38 +
  39 + </View>
  40 + <View style={styles.nameContainer}>
  41 + <Text style={styles.name} numberOfLines={2}>{data.get('product_name')}</Text>
  42 + </View>
  43 + <View style={styles.priceContainer}>
  44 + <Text style={styles.nowPrice} numberOfLines={1}>{'¥' + data.get('sales_price')}</Text>
  45 + <View style={styles.oldPriceContainer}>
  46 + <Text style={styles.oldPrice} numberOfLines={1}>{'¥' + data.get('market_price')}</Text>
  47 + <View style={styles.deleteLine}/>
  48 + </View>
  49 + </View>
32 </View> 50 </View>
33 ); 51 );
34 } 52 }
@@ -36,20 +54,26 @@ export default class Search extends Component { @@ -36,20 +54,26 @@ export default class Search extends Component {
36 54
37 let {width, height} = Dimensions.get('window'); 55 let {width, height} = Dimensions.get('window');
38 56
39 -const WIDTH_RATIO = width / 320;  
40 -let rowWidth = Math.ceil(137.5 * WIDTH_RATIO);  
41 -let rowHeight = Math.ceil(254 * WIDTH_RATIO);  
42 -let rowMarginTop = Math.ceil(10 * WIDTH_RATIO); 57 +const DEVICE_WIDTH_RATIO = width / 320;
  58 +let rowWidth = Math.ceil(137.5 * DEVICE_WIDTH_RATIO);
  59 +let rowHeight = Math.ceil(254 * DEVICE_WIDTH_RATIO);
  60 +let rowMarginTop = Math.ceil(10 * DEVICE_WIDTH_RATIO);
43 61
44 const IMAGE_WIDTH = 145; 62 const IMAGE_WIDTH = 145;
45 const IMAGE_HEIGHT = 193; 63 const IMAGE_HEIGHT = 193;
46 const IMAGE_RATIO = IMAGE_HEIGHT / IMAGE_WIDTH; 64 const IMAGE_RATIO = IMAGE_HEIGHT / IMAGE_WIDTH;
47 -let imageTop = 14 * WIDTH_RATIO; 65 +let imageTop = 14 * DEVICE_WIDTH_RATIO;
48 let imageHeight = rowWidth * IMAGE_RATIO; 66 let imageHeight = rowWidth * IMAGE_RATIO;
49 67
  68 +let almostSoldOutImageHeight = Math.ceil(14 * DEVICE_WIDTH_RATIO);
  69 +let almostSoldOutImageTop = imageHeight - almostSoldOutImageHeight;
  70 +
  71 +let nameMarginTop = Math.ceil(7 * DEVICE_WIDTH_RATIO);
  72 +let nameHeight = Math.ceil(36 * DEVICE_WIDTH_RATIO);
  73 +
50 let styles = StyleSheet.create({ 74 let styles = StyleSheet.create({
51 container: { 75 container: {
52 - backgroundColor: 'gray', 76 + // backgroundColor: 'gray',
53 width: rowWidth, 77 width: rowWidth,
54 height: rowHeight, 78 height: rowHeight,
55 marginTop: rowMarginTop, 79 marginTop: rowMarginTop,
@@ -59,18 +83,70 @@ let styles = StyleSheet.create({ @@ -59,18 +83,70 @@ let styles = StyleSheet.create({
59 height: rowHeight, 83 height: rowHeight,
60 84
61 }, 85 },
  86 + imageContainer: {
  87 + width: rowWidth,
  88 + height: imageHeight,
  89 + backgroundColor: '#f0f0f0',
  90 + },
62 image: { 91 image: {
63 - top: imageTop, 92 + // top: imageTop,
  93 + width: rowWidth,
  94 + height: imageHeight,
  95 + backgroundColor: '#f0f0f0',
  96 + },
  97 + soldOutImage: {
  98 + position: 'absolute',
  99 + top: 0,
  100 + left: 0,
64 width: rowWidth, 101 width: rowWidth,
65 height: imageHeight, 102 height: imageHeight,
66 }, 103 },
67 - separator: {  
68 - height: 15, 104 + almostSoldOutImage: {
  105 + top: almostSoldOutImageTop,
  106 + width: rowWidth,
  107 + height: almostSoldOutImageHeight,
  108 + backgroundColor: '#ff9e0d',
  109 + },
  110 + nameContainer: {
  111 + justifyContent: 'center',
  112 + marginTop: nameMarginTop,
  113 + width: rowWidth,
  114 + height: nameHeight,
  115 + // backgroundColor: 'red',
  116 + },
  117 + name: {
  118 + fontFamily: 'STHeitiSC-Light',
  119 + fontSize: 12,
  120 + color: '#444444',
  121 + },
  122 + priceContainer: {
  123 + flexDirection: 'row',
  124 + },
  125 + nowPrice: {
  126 + fontSize: 12,
  127 + color: '#d0021b',
  128 + },
  129 + oldPriceContainer: {
  130 + // flex: 1,
  131 + flexDirection: 'row',
  132 + marginLeft: 5,
  133 + // backgroundColor: 'red',
  134 + },
  135 + oldPrice: {
  136 + fontSize: 12,
  137 + color: '#b0b0b0',
  138 + height: 16,
69 }, 139 },
70 - line: {  
71 - marginHorizontal: 30,  
72 - top: 10 ,  
73 - height: 1,  
74 - backgroundColor: '#e5e5e5', 140 + deleteLine: {
  141 + position: 'absolute',
  142 + // flex: 1,
  143 + top: (16 / 2) - 0.5,
  144 + left: 0,
  145 + right: 0,
  146 + // marginLeft: 0,
  147 + // marginRight: 0,
  148 + // width: 50,
  149 + height: 1,
  150 + backgroundColor: '#b0b0b0',
75 }, 151 },
76 }); 152 });
  1 +'use strict';
  2 +
  3 +import React, {Component} from 'react';
  4 +import ReactNative, {
  5 + View,
  6 + Text,
  7 + Image,
  8 + StyleSheet,
  9 + Dimensions,
  10 + TouchableOpacity,
  11 +} from 'react-native';
  12 +
  13 +import SlicedImage from '../../../common/components/SlicedImage';
  14 +
  15 +export default class ProductShopCell extends Component {
  16 +
  17 + constructor(props) {
  18 + super(props);
  19 +
  20 + }
  21 +
  22 +
  23 + render() {
  24 + let {data} = this.props;
  25 + // 品牌
  26 + let icon = data.get('brand_ico');
  27 + let name = data.get('brand_name');
  28 + // 店铺
  29 + if (data.get('shops_type') == 2) {
  30 + icon = data.get('shop_logo');
  31 + name = data.get('shop_name');
  32 + }
  33 +
  34 + return (
  35 + <View style={styles.container}>
  36 + <TouchableOpacity style={styles.content} onPress={() => {
  37 + this.props.onPressShop && this.props.onPressShop(data);
  38 + }}>
  39 + <View style={styles.content}>
  40 + <View style={styles.leftContainer}>
  41 + <SlicedImage style={styles.icon} source={{uri: icon}}/>
  42 + <Text style={styles.title}>{name}</Text>
  43 + </View>
  44 + <View style={styles.rightContainer}>
  45 + <Text style={styles.desc}>进入店铺</Text>
  46 + <Image style={styles.arrow} source={require('../../images/shared_next_icon.png')}/>
  47 + </View>
  48 + </View>
  49 + </TouchableOpacity>
  50 + <View style={styles.separator}/>
  51 + </View>
  52 + );
  53 + }
  54 +}
  55 +
  56 +let {width, height} = Dimensions.get('window');
  57 +
  58 +let styles = StyleSheet.create({
  59 + container: {
  60 + width: width,
  61 + height: 65,
  62 + },
  63 + content: {
  64 + flexDirection: 'row',
  65 + alignItems: 'center',
  66 + alignSelf: 'flex-start',
  67 + justifyContent: 'space-between',
  68 + width: width,
  69 + height: 49,
  70 + },
  71 + leftContainer: {
  72 + flexDirection: 'row',
  73 + alignItems: 'center',
  74 + height: 49,
  75 + },
  76 + icon: {
  77 + marginLeft: 5,
  78 + width: 50,
  79 + height: 49,
  80 + resizeMode: 'contain',
  81 + },
  82 + title: {
  83 + marginLeft: 5,
  84 + width: 150,
  85 + fontSize: 15,
  86 + },
  87 + rightContainer: {
  88 + flexDirection: 'row',
  89 + alignItems: 'center',
  90 + alignSelf: 'flex-end',
  91 + height: 49,
  92 + },
  93 + desc: {
  94 + marginRight: 10 ,
  95 + width: 50,
  96 + fontSize: 12,
  97 + color: '#b0b0b0',
  98 + },
  99 + arrow: {
  100 + right: 5,
  101 + width: 20,
  102 + height: 49,
  103 + resizeMode: 'contain',
  104 + },
  105 + separator: {
  106 + position: 'absolute',
  107 + left: 0,
  108 + top: 49,
  109 + width,
  110 + height: 16,
  111 + backgroundColor: '#f0f0f0',
  112 + },
  113 +});
@@ -25,7 +25,6 @@ export default class SearchResult extends React.Component { @@ -25,7 +25,6 @@ export default class SearchResult extends React.Component {
25 25
26 return ( 26 return (
27 <View style={styles.container}> 27 <View style={styles.container}>
28 - <ProductFilter/>  
29 <ProductList 28 <ProductList
30 list={list} 29 list={list}
31 /> 30 />
  1 +'use strict';
  2 +
  3 +import React from 'react';
  4 +import ReactNative from 'react-native';
  5 +
  6 +const {
  7 + View,
  8 + Image,
  9 + Text,
  10 + ListView,
  11 + Dimensions,
  12 + StyleSheet,
  13 +} = ReactNative;
  14 +
  15 +export default class Tags extends React.Component {
  16 +
  17 + constructor(props) {
  18 + super (props);
  19 +
  20 + this.config = {
  21 + is_discount: {
  22 + image: require('../../images/tag/tip_sale.png'),
  23 + width: 30,
  24 + }, // YH_ProductTagTypeSale
  25 + resale: {
  26 + image: require('../../images/tag/tip_zdz.png'),
  27 + width: 45,
  28 + }, // YH_ProductTagTypeReSale
  29 + 'mid-year': {
  30 + image: require('../../images/tag/tip_nzrc.png'),
  31 + width: 45,
  32 + }, // YH_ProductTagTypeSaleMiddle
  33 + 'year-end': {
  34 + image: require('../../images/tag/tip_nzdc.png'),
  35 + width: 45,
  36 + }, // YH_ProductTagTypeSaleHot
  37 + is_new: {
  38 + image: require('../../images/tag/tip_new.png'),
  39 + width: 30,
  40 + }, // YH_ProductTagTypeNew
  41 + is_yohood: {
  42 + image: require('../../images/tag/tip_xpj.png'),
  43 + width: 45,
  44 + }, // YH_ProductTagTypeYohood
  45 + is_limited: {
  46 + image: require('../../images/tag/tip_xl_product.png'),
  47 + width: 30,
  48 + }, // YH_ProductTagTypeLimited
  49 + is_in_stock: {
  50 + image: require('../../images/tag/tip_gnzf.png'),
  51 + width: 45,
  52 + }, // YH_ProductTagTypeInland
  53 + is_deposit_advance: {
  54 + image: require('../../images/tag/tip_advance.png'),
  55 + width: 30,
  56 + }, // YH_ProductTagTypeDeposit
  57 + default: {
  58 + image: '',
  59 + width: 45,
  60 + },
  61 + };
  62 +
  63 + this._renderRow = this._renderRow.bind(this);
  64 +
  65 + this.dataSource = new ListView.DataSource({
  66 + rowHasChanged: (r1, r2) => r1 != r2,
  67 + });
  68 + }
  69 +
  70 + _renderRow(rowData, sectionID, rowID) {
  71 + let item = this.config[rowData];
  72 + if (!item) {
  73 + return null;
  74 + }
  75 +
  76 + let width = Math.ceil(item.width * DEVICE_WIDTH_RATIO);
  77 + let marginLeft = rowID == 0 ? 0 : 2;
  78 + let iconStyle = {width, height: tagHeight, marginLeft};
  79 +
  80 + return (
  81 + <Image style={[styles.icon, iconStyle]} source={item.image}/>
  82 + );
  83 + }
  84 +
  85 + render() {
  86 +
  87 + let {style} = this.props;
  88 + let tags = ['is_discount', 'resale', ];
  89 +
  90 + return (
  91 + <View style={[styles.container]}>
  92 + <ListView
  93 + style={[styles.container]}
  94 + contentContainerStyle={[styles.contentContainer]}
  95 + enableEmptySections={true}
  96 + dataSource={this.dataSource.cloneWithRows(tags)}
  97 + renderRow={this._renderRow}
  98 + scrollEnabled={false}
  99 + scrollsToTop={false}
  100 + horizontal={true}
  101 + showsHorizontalScrollIndicator={false}
  102 + />
  103 + </View>
  104 + );
  105 + }
  106 +}
  107 +
  108 +let {width, height} = Dimensions.get('window');
  109 +
  110 +const DEVICE_WIDTH_RATIO = width / 320;
  111 +let tagHeight = Math.ceil(14 * DEVICE_WIDTH_RATIO);
  112 +
  113 +let styles = StyleSheet.create({
  114 + container: {
  115 +
  116 + },
  117 + contentContainer: {
  118 + height: tagHeight,
  119 + },
  120 + icon: {
  121 + resizeMode: 'contain',
  122 + },
  123 +});
@@ -7,11 +7,14 @@ export default keyMirror({ @@ -7,11 +7,14 @@ export default keyMirror({
7 7
8 SET_KEYWORD: null, 8 SET_KEYWORD: null,
9 SET_SEARCH_STATUS: null, 9 SET_SEARCH_STATUS: null,
  10 + SET_FILTER: null,
10 11
11 SEARCH_REQUEST: null, 12 SEARCH_REQUEST: null,
12 SEARCH_SUCCESS: null, 13 SEARCH_SUCCESS: null,
13 SEARCH_FAILURE: null, 14 SEARCH_FAILURE: null,
14 15
  16 + RESET_LIST_PAGE_INFO: null,
  17 +
15 JUMP_URL_REQUEST: null, 18 JUMP_URL_REQUEST: null,
16 JUMP_URL_SUCCESS: null, 19 JUMP_URL_SUCCESS: null,
17 JUMP_URL_FAILURE: null, 20 JUMP_URL_FAILURE: null,
@@ -18,6 +18,8 @@ import * as searchActions from '../reducers/search/searchActions'; @@ -18,6 +18,8 @@ import * as searchActions from '../reducers/search/searchActions';
18 import SearchKeyword from '../components/search/SearchKeyword'; 18 import SearchKeyword from '../components/search/SearchKeyword';
19 import FuzzySearch from '../components/search/FuzzySearch'; 19 import FuzzySearch from '../components/search/FuzzySearch';
20 import SearchResult from '../components/search/SearchResult'; 20 import SearchResult from '../components/search/SearchResult';
  21 +import ProductList from '../components/search/ProductList';
  22 +import LoadingIndicator from '../../common/components/LoadingIndicator';
21 23
22 const actions = [ 24 const actions = [
23 searchActions, 25 searchActions,
@@ -48,6 +50,8 @@ class SearchContainer extends Component { @@ -48,6 +50,8 @@ class SearchContainer extends Component {
48 50
49 this._onPressKeyword = this._onPressKeyword.bind(this); 51 this._onPressKeyword = this._onPressKeyword.bind(this);
50 this._onPressClearHistory = this._onPressClearHistory.bind(this); 52 this._onPressClearHistory = this._onPressClearHistory.bind(this);
  53 + this._onPressFilter = this._onPressFilter.bind(this);
  54 + this._onEndReached = this._onEndReached.bind(this);
51 55
52 this.subscription = NativeAppEventEmitter.addListener( 56 this.subscription = NativeAppEventEmitter.addListener(
53 "SearchKeywordDidChangeEvent", 57 "SearchKeywordDidChangeEvent",
@@ -59,6 +63,7 @@ class SearchContainer extends Component { @@ -59,6 +63,7 @@ class SearchContainer extends Component {
59 this.subscription2 = NativeAppEventEmitter.addListener( 63 this.subscription2 = NativeAppEventEmitter.addListener(
60 "SearchButtonDidClickEvent", 64 "SearchButtonDidClickEvent",
61 (event) => { 65 (event) => {
  66 + this.props.actions.resetListPageInfo();
62 this.props.actions.searchButtonPressed(event.keyword); 67 this.props.actions.searchButtonPressed(event.keyword);
63 } 68 }
64 ); 69 );
@@ -76,6 +81,7 @@ class SearchContainer extends Component { @@ -76,6 +81,7 @@ class SearchContainer extends Component {
76 } 81 }
77 82
78 _onPressKeyword(keyword) { 83 _onPressKeyword(keyword) {
  84 + this.props.actions.resetListPageInfo();
79 this.props.actions.searchButtonPressed(keyword); 85 this.props.actions.searchButtonPressed(keyword);
80 } 86 }
81 87
@@ -83,29 +89,47 @@ class SearchContainer extends Component { @@ -83,29 +89,47 @@ class SearchContainer extends Component {
83 this.props.actions.clearSearchHistory(); 89 this.props.actions.clearSearchHistory();
84 } 90 }
85 91
  92 + _onPressFilter(value) {
  93 + this.props.actions.resetListPageInfo();
  94 + this.props.actions.setFilter(value);
  95 + this.props.actions.searchProductList(this.props.search.keyword, true);
  96 + }
  97 +
  98 + _onEndReached() {
  99 + this.props.actions.searchProductList(this.props.search.keyword);
  100 + }
86 101
87 _renderSearch() { 102 _renderSearch() {
88 - let {status, keyword, placeholder, searchHistory, hotKeyword, fuzzySearch, productList} = this.props.search; 103 + let {status, keyword, placeholder, searchHistory, hotKeyword, fuzzySearch, jumpUrl, productList} = this.props.search;
89 if (status == 0) { 104 if (status == 0) {
90 return ( 105 return (
91 <SearchKeyword 106 <SearchKeyword
92 - history={searchHistory.list}  
93 - hot={hotKeyword.list}  
94 - onPressKeyword={this._onPressKeyword}  
95 - onPressClearHistory={this._onPressClearHistory} 107 + history={searchHistory.list}
  108 + hot={hotKeyword.list}
  109 + onPressKeyword={this._onPressKeyword}
  110 + onPressClearHistory={this._onPressClearHistory}
96 /> 111 />
97 ); 112 );
98 } else if (status == 1) { 113 } else if (status == 1) {
99 return ( 114 return (
100 <FuzzySearch 115 <FuzzySearch
101 - list={fuzzySearch.list}  
102 - onPressKeyword={this._onPressKeyword} 116 + list={fuzzySearch.list}
  117 + onPressKeyword={this._onPressKeyword}
103 /> 118 />
104 ); 119 );
105 } else if (status == 2) { 120 } else if (status == 2) {
  121 + console.log('currentPage: ' + productList.currentPage)
  122 + console.log('isFetching: ' + productList.isFetching)
  123 + let isLoadingMore = productList.isFetching && productList.currentPage > 0;
106 return ( 124 return (
107 - <SearchResult  
108 - list={productList.list} 125 + <ProductList
  126 + shop={productList.shopOrBrand}
  127 + list={productList.list}
  128 + isFetching={productList.isFetching}
  129 + isLoadingMore={isLoadingMore}
  130 + endReached={productList.endReached}
  131 + onPressFilter={this._onPressFilter}
  132 + onEndReached={this._onEndReached}
109 /> 133 />
110 ); 134 );
111 } 135 }
@@ -114,9 +138,15 @@ class SearchContainer extends Component { @@ -114,9 +138,15 @@ class SearchContainer extends Component {
114 } 138 }
115 139
116 render() { 140 render() {
  141 + let {jumpUrl, productList} = this.props.search;
  142 + let showLoading = jumpUrl.isFetching || (productList.isFetching && productList.currentPage == 0);
117 return ( 143 return (
118 <View style={styles.container}> 144 <View style={styles.container}>
119 {this._renderSearch()} 145 {this._renderSearch()}
  146 +
  147 + <LoadingIndicator
  148 + isVisible={showLoading}
  149 + />
120 </View> 150 </View>
121 ); 151 );
122 } 152 }
@@ -6,9 +6,11 @@ import SearchService from '../../services/SearchService'; @@ -6,9 +6,11 @@ import SearchService from '../../services/SearchService';
6 const { 6 const {
7 SET_KEYWORD, 7 SET_KEYWORD,
8 SET_SEARCH_STATUS, 8 SET_SEARCH_STATUS,
  9 + SET_FILTER,
9 SEARCH_REQUEST, 10 SEARCH_REQUEST,
10 SEARCH_SUCCESS, 11 SEARCH_SUCCESS,
11 SEARCH_FAILURE, 12 SEARCH_FAILURE,
  13 + RESET_LIST_PAGE_INFO,
12 JUMP_URL_REQUEST, 14 JUMP_URL_REQUEST,
13 JUMP_URL_SUCCESS, 15 JUMP_URL_SUCCESS,
14 JUMP_URL_FAILURE, 16 JUMP_URL_FAILURE,
@@ -31,6 +33,7 @@ const { @@ -31,6 +33,7 @@ const {
31 33
32 export function searchKeywordChanged(keyword) { 34 export function searchKeywordChanged(keyword) {
33 return (dispatch, getState) => { 35 return (dispatch, getState) => {
  36 + console.log(keyword)
34 let status = 0; 37 let status = 0;
35 if (keyword) { 38 if (keyword) {
36 status = 1; 39 status = 1;
@@ -49,16 +52,13 @@ export function searchButtonPressed(keyword) { @@ -49,16 +52,13 @@ export function searchButtonPressed(keyword) {
49 if (!keyword) { 52 if (!keyword) {
50 let {app, search} = getState(); 53 let {app, search} = getState();
51 keyword = search.placeholder 54 keyword = search.placeholder
52 - dispatch(setKeyword(keyword));  
53 } 55 }
54 - 56 + dispatch(setKeyword(keyword));
55 dispatch(insertSearchHistory(keyword)); 57 dispatch(insertSearchHistory(keyword));
56 58
57 ReactNative.NativeModules.YH_SearchHelper.setSearchKeyword(keyword); 59 ReactNative.NativeModules.YH_SearchHelper.setSearchKeyword(keyword);
58 ReactNative.NativeModules.YH_SearchHelper.resignSearchBar(); 60 ReactNative.NativeModules.YH_SearchHelper.resignSearchBar();
59 61
60 - // let status = 2;  
61 - // dispatch(setSearchStatus(status));  
62 dispatch(jumpUrl(keyword)); 62 dispatch(jumpUrl(keyword));
63 }; 63 };
64 } 64 }
@@ -77,6 +77,19 @@ export function setSearchStatus(status) { @@ -77,6 +77,19 @@ export function setSearchStatus(status) {
77 }; 77 };
78 } 78 }
79 79
  80 +export function setFilter(value) {
  81 + return {
  82 + type: SET_FILTER,
  83 + payload: value
  84 + };
  85 +}
  86 +
  87 +export function resetListPageInfo() {
  88 + return {
  89 + type: RESET_LIST_PAGE_INFO,
  90 + }
  91 +}
  92 +
80 export function searchRequest() { 93 export function searchRequest() {
81 return { 94 return {
82 type: SEARCH_REQUEST, 95 type: SEARCH_REQUEST,
@@ -97,29 +110,45 @@ export function searchFailure(error) { @@ -97,29 +110,45 @@ export function searchFailure(error) {
97 }; 110 };
98 } 111 }
99 112
100 -export function searchProductList(keyword, uid, sourcePage) { 113 +export function searchProductList(keyword, reload=false) {
101 return (dispatch, getState) => { 114 return (dispatch, getState) => {
102 let {app, search} = getState(); 115 let {app, search} = getState();
103 - // if (search.productList.isFetching) {  
104 - // return;  
105 - // } 116 + let {productList} = search;
  117 + if (reload) {
  118 +
  119 + } else {
  120 + if (productList.isFetching || productList.endReached || productList.error) {
  121 + return;
  122 + }
  123 + }
106 124
107 - let fetchList = (keyword, order, uid, page, sourcePage) => { 125 + let fetchList = (keyword, order, uid, page, pageSize, sourcePage) => {
108 dispatch(searchRequest()); 126 dispatch(searchRequest());
109 - return new SearchService(app.host).searchProductList(keyword, order, uid, page, sourcePage) 127 + return new SearchService(app.host).searchProductList(keyword, order, uid, page, pageSize, sourcePage)
110 .then(json => { 128 .then(json => {
  129 + let payload = parseProductList(json);
  130 + payload.endReached = payload.currentPage == payload.pageCount;
  131 +
  132 + if (productList.currentPage > 1) {
  133 + let oldList = productList.list.toJS();
  134 + let list = [...oldList, ...payload.list];
  135 + payload.list = list;
  136 + }
  137 +
111 dispatch(setSearchStatus(2)); 138 dispatch(setSearchStatus(2));
112 - dispatch(searchSuccess(json.product_list)); 139 + dispatch(searchSuccess(payload));
113 }) 140 })
114 .catch(error => { 141 .catch(error => {
115 dispatch(searchFailure(error)); 142 dispatch(searchFailure(error));
116 }); 143 });
117 } 144 }
118 145
  146 +
119 let uid = 0; 147 let uid = 0;
120 let sourcePage = ''; 148 let sourcePage = '';
121 - let order = 's_n_desc';  
122 - let page = 1; 149 + let order = productList.filter;
  150 + let page = productList.currentPage + 1;
  151 + let pageSize = productList.pageSize;
123 ReactNative.NativeModules.YH_CommonHelper.sourcePage('YH_SearchProListVC') 152 ReactNative.NativeModules.YH_CommonHelper.sourcePage('YH_SearchProListVC')
124 .then(data => { 153 .then(data => {
125 sourcePage = data; 154 sourcePage = data;
@@ -127,14 +156,43 @@ export function searchProductList(keyword, uid, sourcePage) { @@ -127,14 +156,43 @@ export function searchProductList(keyword, uid, sourcePage) {
127 }) 156 })
128 .then(data => { 157 .then(data => {
129 uid = data; 158 uid = data;
130 - fetchList(keyword, order, uid, page, sourcePage); 159 + fetchList(keyword, order, uid, page, pageSize, sourcePage);
131 }) 160 })
132 .catch(error => { 161 .catch(error => {
133 - fetchList(keyword, order, uid, page, sourcePage); 162 + fetchList(keyword, order, uid, page, pageSize, sourcePage);
134 }); 163 });
135 } 164 }
136 } 165 }
137 166
  167 +function parseProductList(json) {
  168 + let currentPage = json && json.page ? json.page : 1;
  169 + let pageCount = json && json.page_total ? json.page_total : 0;
  170 + let total = json && json.total ? json.total : 0;
  171 +
  172 + let shopOrBrand = [];
  173 + if (currentPage == 1) {
  174 + shopOrBrand = json && json.shop ? json.shop : [];
  175 + if (!shopOrBrand || shopOrBrand.length == 0) {
  176 + let brand = json && json.brand;
  177 + if (brand) {
  178 + shopOrBrand.push(brand);
  179 + }
  180 + }
  181 + }
  182 +
  183 + shopOrBrand = shopOrBrand ? shopOrBrand : [];
  184 +
  185 + let list = json && json.product_list ? json.product_list : [];
  186 +
  187 + return {
  188 + list,
  189 + shopOrBrand,
  190 + currentPage,
  191 + pageCount,
  192 + total,
  193 + };
  194 +}
  195 +
138 export function jumpUrlRequest() { 196 export function jumpUrlRequest() {
139 return { 197 return {
140 type: JUMP_URL_REQUEST, 198 type: JUMP_URL_REQUEST,
@@ -172,12 +230,12 @@ export function jumpUrl(keyword) { @@ -172,12 +230,12 @@ export function jumpUrl(keyword) {
172 if (jumpUrl) { 230 if (jumpUrl) {
173 ReactNative.NativeModules.YH_CommonHelper.jumpWithUrl(jumpUrl); 231 ReactNative.NativeModules.YH_CommonHelper.jumpWithUrl(jumpUrl);
174 } else { 232 } else {
175 - dispatch(searchProductList(keyword, uid, sourcePage)); 233 + dispatch(searchProductList(keyword, true));
176 } 234 }
177 }) 235 })
178 .catch(error => { 236 .catch(error => {
179 dispatch(jumpUrlFailure(error)); 237 dispatch(jumpUrlFailure(error));
180 - dispatch(searchProductList(keyword, uid, sourcePage)); 238 + dispatch(searchProductList(keyword, true));
181 }); 239 });
182 } 240 }
183 241
@@ -28,6 +28,12 @@ let InitialState = Record({ @@ -28,6 +28,12 @@ let InitialState = Record({
28 isFetching: false, 28 isFetching: false,
29 error: null, 29 error: null,
30 list: List(), 30 list: List(),
  31 + shopOrBrand: List(),
  32 + filter: 's_n_desc',
  33 + currentPage: 0,
  34 + pageCount: 0,
  35 + pageSize: 6,//60,
  36 + total: 0,
31 endReached: false, 37 endReached: false,
32 })), 38 })),
33 }); 39 });
@@ -6,9 +6,11 @@ import Immutable, {Map} from 'immutable'; @@ -6,9 +6,11 @@ import Immutable, {Map} from 'immutable';
6 const { 6 const {
7 SET_KEYWORD, 7 SET_KEYWORD,
8 SET_SEARCH_STATUS, 8 SET_SEARCH_STATUS,
  9 + SET_FILTER,
9 SEARCH_REQUEST, 10 SEARCH_REQUEST,
10 SEARCH_SUCCESS, 11 SEARCH_SUCCESS,
11 SEARCH_FAILURE, 12 SEARCH_FAILURE,
  13 + RESET_LIST_PAGE_INFO,
12 JUMP_URL_REQUEST, 14 JUMP_URL_REQUEST,
13 JUMP_URL_SUCCESS, 15 JUMP_URL_SUCCESS,
14 JUMP_URL_FAILURE, 16 JUMP_URL_FAILURE,
@@ -41,15 +43,44 @@ export default function searchReducer(state=initialState, action) { @@ -41,15 +43,44 @@ export default function searchReducer(state=initialState, action) {
41 return state.set('status', action.payload); 43 return state.set('status', action.payload);
42 } 44 }
43 45
  46 + case SET_FILTER: {
  47 + return state.setIn(['productList', 'filter'], action.payload);
  48 + }
  49 +
  50 + case RESET_LIST_PAGE_INFO: {
  51 + return state.setIn(['productList', 'currentPage'], 0)
  52 + .setIn(['productList', 'pageCount'], 0)
  53 + .setIn(['productList', 'total'], 0)
  54 + .setIn(['productList', 'endReached'], false);
  55 + }
  56 +
44 case SEARCH_REQUEST: { 57 case SEARCH_REQUEST: {
45 return state.setIn(['productList', 'isFetching'], true) 58 return state.setIn(['productList', 'isFetching'], true)
46 .setIn(['productList', 'error'], null); 59 .setIn(['productList', 'error'], null);
47 } 60 }
48 61
49 case SEARCH_SUCCESS: { 62 case SEARCH_SUCCESS: {
50 - return state.setIn(['productList', 'isFetching'], false) 63 + let {
  64 + list,
  65 + shopOrBrand,
  66 + currentPage,
  67 + pageCount,
  68 + total,
  69 + endReached,
  70 + } = action.payload;
  71 +
  72 + let newState = state.setIn(['productList', 'isFetching'], false)
51 .setIn(['productList', 'error'], null) 73 .setIn(['productList', 'error'], null)
52 - .setIn(['productList', 'list'], Immutable.fromJS(action.payload)); 74 + .setIn(['productList', 'list'], Immutable.fromJS(list))
  75 + .setIn(['productList', 'currentPage'], currentPage)
  76 + .setIn(['productList', 'pageCount'], pageCount)
  77 + .setIn(['productList', 'total'], total)
  78 + .setIn(['productList', 'endReached'], endReached);
  79 + if (currentPage == 1) {
  80 + newState = newState.setIn(['productList', 'shopOrBrand'], Immutable.fromJS(shopOrBrand));
  81 + }
  82 +
  83 + return newState;
53 } 84 }
54 85
55 case SEARCH_FAILURE: { 86 case SEARCH_FAILURE: {
@@ -63,12 +63,13 @@ export default class SearchService { @@ -63,12 +63,13 @@ export default class SearchService {
63 }); 63 });
64 } 64 }
65 65
66 - async searchProductList(query='', order='s_n_desc', uid=0, page=1, fromPage='', limit=60, v=7) { 66 + async searchProductList(query='', order='s_n_desc', uid=0, page=1, limit=60, fromPage='', v=7) {
67 return await this.api.get({ 67 return await this.api.get({
68 url: '', 68 url: '',
69 body: { 69 body: {
70 method: 'app.search.li', 70 method: 'app.search.li',
71 query, 71 query,
  72 + order,
72 uid, 73 uid,
73 page, 74 page,
74 limit, 75 limit,