mirror of
https://github.com/timepill/timepill-app.git
synced 2025-04-30 09:59:31 +08:00
1. 提取DiaryList组件,首页和关注页复用
2. 设备相关常量固定在api.js,其他组件直接引用
This commit is contained in:
parent
1ea2afe25c
commit
66b26e7562
8 changed files with 340 additions and 227 deletions
4
index.js
4
index.js
|
@ -15,8 +15,8 @@ import BottomNav from './src/nav/bottomNav'
|
|||
async function init() {
|
||||
await loadIcon();
|
||||
|
||||
// let token = await Token.getUserToken();
|
||||
let token;
|
||||
let token = await Token.getUserToken();
|
||||
// let token;
|
||||
if (!token) {
|
||||
Navigation.startSingleScreenApp({
|
||||
screen: {
|
||||
|
|
196
src/component/diary/diaryList.js
Normal file
196
src/component/diary/diaryList.js
Normal file
|
@ -0,0 +1,196 @@
|
|||
import React, {Component} from 'react';
|
||||
import {
|
||||
InteractionManager,
|
||||
ActivityIndicator,
|
||||
StyleSheet,
|
||||
FlatList,
|
||||
Text, View
|
||||
} from 'react-native';
|
||||
import {Divider} from "react-native-elements";
|
||||
|
||||
import Color from '../../style/color'
|
||||
import Msg from '../../util/msg'
|
||||
import Api from '../../util/api'
|
||||
|
||||
import Touchable from '../touchable'
|
||||
import DiaryBrief from './diaryBrief'
|
||||
|
||||
|
||||
export default class DiaryList extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.dataSource = props.dataSource;
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
|
||||
diaries: [],
|
||||
hasMore: false,
|
||||
|
||||
refreshing: false,
|
||||
refreshFailed: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('diaryList mounted.');
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
async refresh(loadMore = false) {
|
||||
if (this.state.refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({hasMore: false, refreshing: true, refreshFailed: false});
|
||||
this.dataSource.refresh(loadMore)
|
||||
.then(result => {
|
||||
console.log('diaryList refresh:', result);
|
||||
|
||||
if(!result) {
|
||||
throw {
|
||||
message: 'refresh no result'
|
||||
}
|
||||
|
||||
} else {
|
||||
let diaries = this.state.diaries;
|
||||
let newDiaries = result.list;
|
||||
if (!loadMore && diaries.length > 0 && newDiaries.length > 0
|
||||
&& diaries[0].id === newDiaries[0].id) {
|
||||
|
||||
Msg.showMsg('没有新内容');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
diaries: result.list ? result.list : [],
|
||||
hasMore: result.more,
|
||||
refreshFailed: false
|
||||
});
|
||||
}
|
||||
|
||||
}).catch(e => {
|
||||
if (e.code === 401) {
|
||||
this.props.navigator.showModal({
|
||||
screen: "App"
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
diaries: [],
|
||||
hasMore: false,
|
||||
refreshFailed: true
|
||||
});
|
||||
|
||||
}).done(() => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
refreshing: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async loadMore() {
|
||||
if (this.state.refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.refresh(true);
|
||||
}
|
||||
|
||||
_onDiaryPress(diary) {
|
||||
/*
|
||||
this.props.navigator.push({
|
||||
screen: 'DiaryDetail',
|
||||
title: '日记详情',
|
||||
passProps: { diary: diary }
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={localStyle.container}>
|
||||
<FlatList style={localStyle.list}
|
||||
|
||||
data={this.state.diaries}
|
||||
|
||||
keyExtractor={(item, index) => {
|
||||
return item.id.toString()
|
||||
}}
|
||||
|
||||
ListHeaderComponent={this.state.isLoading ? null : this.props.header}
|
||||
|
||||
renderItem={({item}) => {
|
||||
return (
|
||||
<Touchable onPress={() => this._onDiaryPress(item)}>
|
||||
<DiaryBrief diary={item}></DiaryBrief>
|
||||
</Touchable>
|
||||
)
|
||||
}}
|
||||
|
||||
ItemSeparatorComponent={({highlighted}) => <Divider style={{backgroundColor: '#eee'}}/>}
|
||||
|
||||
ListFooterComponent={this.renderFooter()}
|
||||
|
||||
refreshing={this.state.refreshing}
|
||||
onRefresh={this.refresh.bind(this)}
|
||||
|
||||
automaticallyAdjustContentInsets={true}
|
||||
onEndReachedThreshold={2}
|
||||
onEndReached={this.state.hasMore ? this.loadMore.bind(this) : null}
|
||||
>
|
||||
</FlatList>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter() {
|
||||
if (this.state.refreshing || this.state.diaries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.refreshFailed) {
|
||||
return (
|
||||
<View style={localStyle.footer}>
|
||||
<TouchableOpacity style={{marginTop: 15}}
|
||||
onPress={() => {this.loadMore();}}>
|
||||
<Text style={{color: Color.primary}}>加载失败,请点击重试</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.hasMore) {
|
||||
return (
|
||||
<View style={localStyle.footer}>
|
||||
<Text style={{color: Color.inactiveText, fontSize: 12}}>—— THE END ——</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={localStyle.footer}>
|
||||
<ActivityIndicator animating={true} color={Color.primary}
|
||||
size={Api.IS_ANDROID ? 'large' : 'small'}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const localStyle = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
list: {
|
||||
height: '100%'
|
||||
},
|
||||
footer: {
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 15
|
||||
}
|
||||
});
|
|
@ -1,30 +1,23 @@
|
|||
import React, { Component } from 'react';
|
||||
import { StyleSheet, Text, View, ActivityIndicator, Dimensions } from 'react-native';
|
||||
import {StyleSheet, Text, View, ActivityIndicator} from 'react-native';
|
||||
|
||||
const { width, height } = Dimensions.get('window')
|
||||
import Api from '../util/api'
|
||||
|
||||
|
||||
export default class Loading extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
color: props.color ? props.color : '#aaa'
|
||||
}
|
||||
|
||||
/*
|
||||
show() {
|
||||
this.setState({visible: true})
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.setState({visible: false})
|
||||
}
|
||||
*/
|
||||
|
||||
render() {
|
||||
if (this.props.visible) {
|
||||
return (
|
||||
<View style={styles.loading}>
|
||||
<ActivityIndicator size="large" color="#aaa" />
|
||||
<View style={localStyle.loading}>
|
||||
<ActivityIndicator size="large" color={this.state.color} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
@ -34,13 +27,13 @@ export default class Loading extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const localStyle = StyleSheet.create({
|
||||
loading: {
|
||||
position: "absolute",
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
width: width,
|
||||
width: Api.DEVICE_WINDOW.width,
|
||||
top: 200,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
});
|
30
src/dataLoader/followListData.js
Normal file
30
src/dataLoader/followListData.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Api from '../util/api'
|
||||
|
||||
|
||||
const PAGE_SIZE = 21;
|
||||
|
||||
export default class FollowListData {
|
||||
|
||||
list: [];
|
||||
last_id: 0;
|
||||
|
||||
async refresh(loadMore = false) {
|
||||
let lastId = !loadMore ? 0 : this.last_id;
|
||||
let data = await Api.getFollowDiaries(0, PAGE_SIZE, lastId);
|
||||
let more = data.diaries.length === PAGE_SIZE;
|
||||
|
||||
if(!loadMore) {
|
||||
this.list = data.diaries.slice(0, PAGE_SIZE - 1);
|
||||
|
||||
} else if(data.diaries.length > 0) {
|
||||
this.list = this.list.concat(data.diaries.slice(0, PAGE_SIZE - 1));
|
||||
}
|
||||
|
||||
this.last_id = more ? data.diaries[PAGE_SIZE - 1].id : 0;
|
||||
|
||||
return {
|
||||
list: this.list,
|
||||
more
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,33 +1,55 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Platform, StyleSheet, Text, View} from 'react-native';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
import Api from '../util/api'
|
||||
|
||||
import DiaryList from '../component/diary/diaryList'
|
||||
import FollowListData from '../dataLoader/followListData';
|
||||
|
||||
|
||||
export default class FollowPage extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.dataSource = new FollowListData();
|
||||
}
|
||||
|
||||
renderHeader() {
|
||||
return (
|
||||
<View style={localStyle.header}>
|
||||
<Text style={localStyle.title}>关注</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>Follow Page !</Text>
|
||||
<View style={localStyle.wrap}>
|
||||
<DiaryList ref={(r) => this.list = r}
|
||||
dataSource={this.dataSource}
|
||||
header={this.renderHeader.bind(this)}
|
||||
|
||||
navigator={this.props.navigator}
|
||||
|
||||
></DiaryList>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
const localStyle = StyleSheet.create({
|
||||
wrap: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
backgroundColor: '#fff',
|
||||
paddingTop: Api.IS_IPHONEX || Api.IS_ANDROID ? 44 : 20
|
||||
},
|
||||
welcome: {
|
||||
fontSize: 20,
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
instructions: {
|
||||
textAlign: 'center',
|
||||
color: '#333333',
|
||||
marginBottom: 5,
|
||||
header: {
|
||||
paddingLeft: 20,
|
||||
flexDirection: "row"
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
fontSize: 30,
|
||||
color: '#000'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,189 +1,37 @@
|
|||
import React, {Component} from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
InteractionManager, Platform, StyleSheet, Text, TouchableOpacity, View,
|
||||
Alert,
|
||||
Dimensions
|
||||
} from 'react-native';
|
||||
import {Divider} from "react-native-elements";
|
||||
import {isIphoneX} from 'react-native-iphone-x-helper'
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
import Color from '../style/color'
|
||||
import Msg from '../util/msg'
|
||||
import Api from '../util/api'
|
||||
|
||||
import Loading from '../component/loading'
|
||||
import Touchable from '../component/touchable'
|
||||
import DiaryBrief from '../component/diary/diaryBrief'
|
||||
import DiaryFull from '../component/diary/diaryFull'
|
||||
import HomeListData from '../entity/homeListData';
|
||||
import DiaryList from '../component/diary/diaryList'
|
||||
import HomeListData from '../dataLoader/homeListData';
|
||||
|
||||
|
||||
const isIpx = isIphoneX();
|
||||
const isAndroid = Platform.OS === 'android';
|
||||
const HEADER_PADDING = Platform.OS === 'android' ? 20 : (isIpx ? 10 : 25);
|
||||
const { width, height } = Dimensions.get('window')
|
||||
|
||||
export default class HomePage extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.dataSource = new HomeListData();
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
|
||||
diaries: [],
|
||||
hasMore: false,
|
||||
|
||||
refreshing: false,
|
||||
refreshFailed: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
async refresh(loadMore = false) {
|
||||
if (this.state.refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({refreshing: true, hasMore: false, refreshFailed: false});
|
||||
this.dataSource.refresh(loadMore)
|
||||
.then(data => {
|
||||
console.log('homepage data:', data);
|
||||
if(!data) {
|
||||
throw {
|
||||
message: 'empty data'
|
||||
}
|
||||
|
||||
} else {
|
||||
let diaries = this.state.diaries;
|
||||
let newDiaries = data.list;
|
||||
if (!loadMore && diaries.length > 0 && newDiaries.length > 0
|
||||
&& diaries[0].id === newDiaries[0].id) {
|
||||
|
||||
Msg.showMsg('没有新内容');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
diaries: data.list ? data.list : [],
|
||||
hasMore: data.more,
|
||||
refreshFailed: false
|
||||
});
|
||||
}
|
||||
|
||||
}).catch(e => {
|
||||
if (e.code && e.code === 401) {
|
||||
this.props.navigator.showModal({
|
||||
screen: "App"
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
diaries: [],
|
||||
hasMore: false,
|
||||
refreshFailed: true
|
||||
});
|
||||
|
||||
}).done(() => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
refreshing: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async loadMore() {
|
||||
if (this.state.refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.refresh(true);
|
||||
}
|
||||
|
||||
_checkResult(result) {
|
||||
|
||||
}
|
||||
|
||||
_onDiaryPress(diary) {
|
||||
/*
|
||||
this.props.navigator.push({
|
||||
screen: 'DiaryDetail',
|
||||
title: '日记详情',
|
||||
passProps: { diary: diary }
|
||||
});
|
||||
*/
|
||||
renderHeader() {
|
||||
return (
|
||||
<View style={localStyle.header}>
|
||||
<Text style={localStyle.title}>胶囊日记</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={localStyle.wrap}>
|
||||
<Loading visible={this.state.isLoading}></Loading>
|
||||
<DiaryList ref={(r) => this.list = r}
|
||||
dataSource={this.dataSource}
|
||||
header={this.renderHeader.bind(this)}
|
||||
|
||||
<FlatList style={localStyle.list}
|
||||
navigator={this.props.navigator}
|
||||
|
||||
data={this.state.diaries}
|
||||
|
||||
keyExtractor={(item, index) => {
|
||||
return item.id.toString()
|
||||
}}
|
||||
|
||||
renderItem={({item}) => {
|
||||
return (
|
||||
<Touchable onPress={() => this._onDiaryPress(item)}>
|
||||
<DiaryBrief diary={item}></DiaryBrief>
|
||||
</Touchable>
|
||||
)
|
||||
}}
|
||||
|
||||
ItemSeparatorComponent={({highlighted}) => <Divider style={{backgroundColor: '#eee'}}/>}
|
||||
|
||||
ListFooterComponent={this.renderFooter()}
|
||||
|
||||
refreshing={this.state.refreshing}
|
||||
onRefresh={this.refresh.bind(this)}
|
||||
|
||||
automaticallyAdjustContentInsets={true}
|
||||
onEndReachedThreshold={2}
|
||||
onEndReached={this.state.hasMore ? this.loadMore.bind(this) : null}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter() {
|
||||
if (this.state.refreshing || this.state.diaries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.refreshFailed) {
|
||||
return (
|
||||
<View style={localStyle.footer}>
|
||||
<TouchableOpacity style={{marginTop: 15}}
|
||||
onPress={() => {this.loadMore();}}>
|
||||
<Text style={{color: Color.primary}}>加载失败,请点击重试</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.state.hasMore) {
|
||||
return (
|
||||
<View style={localStyle.footer}>
|
||||
<Text style={{color: Color.inactiveText, fontSize: 12}}>—— THE END ——</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={localStyle.footer}>
|
||||
<ActivityIndicator animating={true} color={Color.primary}
|
||||
size={Platform.OS === 'android' ? 'large' : 'small'}/>
|
||||
></DiaryList>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -191,21 +39,17 @@ export default class HomePage extends Component {
|
|||
|
||||
const localStyle = StyleSheet.create({
|
||||
wrap: {
|
||||
position: 'absolute',
|
||||
backgroundColor: '#fff',
|
||||
flex: 1,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
paddingTop: isIpx || isAndroid ? 44 : 20
|
||||
backgroundColor: '#fff',
|
||||
paddingTop: Api.IS_IPHONEX || Api.IS_ANDROID ? 44 : 20
|
||||
},
|
||||
list: {
|
||||
backgroundColor: 'white',
|
||||
height: '100%'
|
||||
header: {
|
||||
paddingLeft: 20,
|
||||
flexDirection: "row"
|
||||
},
|
||||
footer: {
|
||||
height: 60,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 15
|
||||
title: {
|
||||
flex: 1,
|
||||
fontSize: 30,
|
||||
color: '#000'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import TokenManager from './token'
|
||||
import {Platform, Dimensions} from 'react-native'
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {isIphoneX} from 'react-native-iphone-x-helper'
|
||||
|
||||
import TokenManager from './token'
|
||||
|
||||
|
||||
const IS_ANDROID = Platform.OS === 'android';
|
||||
const DEVICE_WINDOW = Dimensions.get('window')
|
||||
|
||||
const OS = DeviceInfo.getSystemName();
|
||||
const OS_VERSION = DeviceInfo.getSystemVersion();
|
||||
const DEVICE_ID = DeviceInfo.getUniqueID();
|
||||
const VERSION = DeviceInfo.getVersion();
|
||||
|
||||
const IS_IPHONEX = isIphoneX();
|
||||
|
||||
|
||||
const baseUrl = 'http://open.timepill.net/api';
|
||||
|
||||
|
||||
|
@ -32,7 +41,7 @@ async function login(username, password) {
|
|||
}
|
||||
|
||||
async function getSelfInfo() {
|
||||
return call('GET', '/users/my')
|
||||
return call('GET', '/users/my');
|
||||
}
|
||||
|
||||
async function getTodayDiaries(page = 1, page_size = 20, first_id = '') {
|
||||
|
@ -45,6 +54,16 @@ async function getTodayDiaries(page = 1, page_size = 20, first_id = '') {
|
|||
});
|
||||
}
|
||||
|
||||
async function getFollowDiaries(page = 1, page_size = 20, first_id = '') {
|
||||
return call('GET', '/diaries/follow?page=' + page + '&page_size=' + page_size + `&first_id=${first_id}`)
|
||||
.then((json) => {
|
||||
json.page = Number(json.page);
|
||||
json.page_size = Number(json.page_size);
|
||||
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -125,6 +144,15 @@ function handleCatch(err) {
|
|||
|
||||
|
||||
export default {
|
||||
IS_ANDROID,
|
||||
DEVICE_WINDOW,
|
||||
OS,
|
||||
OS_VERSION,
|
||||
DEVICE_ID,
|
||||
VERSION,
|
||||
IS_IPHONEX,
|
||||
|
||||
login,
|
||||
getTodayDiaries
|
||||
getTodayDiaries,
|
||||
getFollowDiaries
|
||||
}
|
Loading…
Reference in a new issue