1. 提出用户头像组件

2. 提出单条日记组件,分为摘要模式和完整模式(未完成)
3. 首页列表初步成形
This commit is contained in:
xuwenyang 2019-05-08 21:27:37 +08:00
parent 4076fa7333
commit 2951ea054c
11 changed files with 380 additions and 49 deletions

10
package-lock.json generated
View file

@ -9830,6 +9830,11 @@
} }
} }
}, },
"moment": {
"version": "2.24.0",
"resolved": "http://registry.npm.taobao.org/moment/download/moment-2.24.0.tgz",
"integrity": "sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s="
},
"morgan": { "morgan": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "http://registry.npm.taobao.org/morgan/download/morgan-1.9.1.tgz", "resolved": "http://registry.npm.taobao.org/morgan/download/morgan-1.9.1.tgz",
@ -10814,6 +10819,11 @@
"prop-types": "^15.5.8" "prop-types": "^15.5.8"
} }
}, },
"react-native-iphone-x-helper": {
"version": "1.0.2",
"resolved": "http://registry.npm.taobao.org/react-native-iphone-x-helper/download/react-native-iphone-x-helper-1.0.2.tgz",
"integrity": "sha1-fbylMJMPfBzoYzzI/RO6lBApkuE="
},
"react-native-navigation": { "react-native-navigation": {
"version": "1.1.376", "version": "1.1.376",
"resolved": "https://registry.npm.taobao.org/react-native-navigation/download/react-native-navigation-1.1.376.tgz", "resolved": "https://registry.npm.taobao.org/react-native-navigation/download/react-native-navigation-1.1.376.tgz",

View file

@ -9,10 +9,12 @@
"dependencies": { "dependencies": {
"@react-native-community/async-storage": "^1.3.4", "@react-native-community/async-storage": "^1.3.4",
"base-64": "^0.1.0", "base-64": "^0.1.0",
"moment": "^2.24.0",
"react": "16.8.3", "react": "16.8.3",
"react-native": "0.59.5", "react-native": "0.59.5",
"react-native-device-info": "^1.6.1", "react-native-device-info": "^1.6.1",
"react-native-elements": "^0.19.0", "react-native-elements": "^0.19.0",
"react-native-iphone-x-helper": "^1.0.2",
"react-native-navigation": "^1.1.376", "react-native-navigation": "^1.1.376",
"react-native-vector-icons": "^4.5.0" "react-native-vector-icons": "^4.5.0"
}, },

View file

@ -0,0 +1,75 @@
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import moment from 'moment'
import Color from '../../style/color'
import UserIcon from './userIcon'
export default class DiaryBrief extends Component {
render() {
let diary = this.props.diary;
let user = diary.user;
return (
<View style={localStyle.box}>
<UserIcon iconUrl={user.iconUrl}></UserIcon>
<View style={localStyle.body}>
<View style={localStyle.title}>
<Text style={localStyle.titleName} numberOfLines={1}>
{user.name}
</Text>
<Text style={[localStyle.titleText, {flex: 1}]} numberOfLines={1}>
{diary.notebook_subject}
</Text>
<Text style={localStyle.titleText}>
{moment(diary.created).format('H:mm')}
</Text>
</View>
<Text style={localStyle.content} numberOfLines={5}>
{diary.content.trim()}
</Text>
</View>
</View>
);
}
}
const localStyle = StyleSheet.create({
box: {
flexDirection: "row",
overflow: "hidden",
paddingHorizontal: 15,
paddingTop: 15
},
body: {
flexDirection: "column",
flexGrow: 1,
flexShrink: 1,
paddingTop: 2
},
title: {
flexDirection: "row",
alignItems: "flex-end",
paddingBottom: 5
},
titleName: {
fontWeight: 'bold',
color: Color.text,
fontSize: 14
},
titleText: {
fontSize: 12,
color: Color.inactiveText
},
content: {
flexGrow: 1,
lineHeight: 24,
color: Color.text,
fontSize: 15,
textAlignVertical: 'bottom',
paddingBottom: 15
}
});

View file

@ -0,0 +1,75 @@
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import moment from 'moment'
import Color from '../../style/color'
import UserIcon from './userIcon'
export default class DiaryFull extends Component {
render() {
let diary = this.props.diary;
let user = diary.user;
return (
<View style={localStyle.box}>
<UserIcon iconUrl={user.iconUrl}></UserIcon>
<View style={localStyle.body}>
<View style={localStyle.title}>
<Text style={localStyle.titleName} numberOfLines={1}>
{user.name}
</Text>
<Text style={[localStyle.titleText, {flex: 1}]} numberOfLines={1}>
{diary.notebook_subject}
</Text>
<Text style={localStyle.titleText}>
{moment(diary.created).format('H:mm')}
</Text>
</View>
<Text style={localStyle.content}>
{diary.content}
</Text>
</View>
</View>
);
}
}
const localStyle = StyleSheet.create({
box: {
flexDirection: "row",
overflow: "hidden",
paddingHorizontal: 15,
paddingTop: 15
},
body: {
flexDirection: "column",
flexGrow: 1,
flexShrink: 1,
paddingTop: 2
},
title: {
flexDirection: "row",
alignItems: "flex-end",
paddingBottom: 5
},
titleName: {
fontWeight: 'bold',
color: Color.text,
fontSize: 14
},
titleText: {
fontSize: 12,
color: Color.inactiveText
},
content: {
flexGrow: 1,
lineHeight: 24,
color: Color.text,
fontSize: 15,
textAlignVertical: 'bottom',
paddingBottom: 15
}
});

View file

@ -0,0 +1,29 @@
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import {Avatar} from "react-native-elements";
export default class UserIcon extends Component {
_defaultOnPress() {
// empty
}
render() {
return (
<Avatar small rounded
containerStyle={localStyle.container}
source={{uri: this.props.iconUrl}}
onPress={this.props.onPress ? this.props.onPress : this._defaultOnPress.bind(this)}
activeOpacity={0.7}
/>
);
}
}
const localStyle = StyleSheet.create({
container: {
marginTop: 3,
marginRight: 8,
}
});

View file

@ -38,9 +38,8 @@ const styles = StyleSheet.create({
loading: { loading: {
position: "absolute", position: "absolute",
left: 0, left: 0,
top: 0,
width: width, width: width,
height: 400, top: 200,
justifyContent: "center", justifyContent: "center",
alignItems: "center" alignItems: "center"
} }

View file

@ -71,8 +71,6 @@ export default class LoginForm extends Component {
render() {return ( render() {return (
<View> <View>
<Text style={localStyle.title}>{'欢迎来到胶囊日记'}</Text> <Text style={localStyle.title}>{'欢迎来到胶囊日记'}</Text>
<View style={localStyle.form}> <View style={localStyle.form}>

View file

@ -0,0 +1,22 @@
import React from "react";
import {Platform, TouchableNativeFeedback, TouchableOpacity, View} from "react-native";
let TouchableIOS = (props) => {
return <TouchableOpacity activeOpacity={0.7} {...props}/>
};
let TouchableAndroid = (props) => {
return (
<TouchableNativeFeedback background={TouchableNativeFeedback.SelectableBackground()} useForeground={true} {...props}>
<View>
{props.children}
</View>
</TouchableNativeFeedback>
)
};
const Touchable = Platform.OS === 'android' ? TouchableAndroid : TouchableIOS;
Touchable.propTypes = (Platform.OS === 'android' ? null : TouchableOpacity.propTypes);
export default Touchable

View file

@ -0,0 +1,39 @@
import Api from '../util/api'
const PAGE_SIZE = 21;
export default class HomeListData {
list: [];
last_id: 0;
async refresh() {
this.last_id = 0;
let data = await Api.getTodayDiaries(0, PAGE_SIZE, this.last_id);
let more = data.diaries.length === PAGE_SIZE;
this.list = data.diaries.slice(0, PAGE_SIZE - 1);
this.last_id = more ? data.diaries[PAGE_SIZE - 1].id : 0;
return {
list: this.list,
more
};
}
async load_more() {
let data = await Api.getTodayDiaries(0, PAGE_SIZE, this.last_id);
let more = data.diaries.length === PAGE_SIZE;
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
};
}
}

View file

@ -1,50 +1,119 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native'; 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 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';
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 { export default class HomePage extends Component {
constructor(props) {
onPress() { super(props);
this.dataSource = new HomeListData();
this.state = {
isLoading: true,
diaries: []
};
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.refresh()
.then(data => {
console.log('homepage data:', data);
this.setState({
isLoading: false,
diaries: data && data.list ? data.list : []
});
}).catch(e => {
if (e.code && e.code === 401) {
this.props.navigator.showModal({
screen: "App"
});
this.setState({
diaries: []
});
}
});
});
}
async refresh() {
return await this.dataSource.refresh();
}
_onDiaryPress(diary) {
/* /*
this.props.navigator.push({ this.props.navigator.push({
screen: 'Home' screen: 'DiaryDetail',
}); title: '日记详情',
passProps: { diary: diary }
Navigation.startSingleScreenApp({
screen: {
screen: 'Home',
title: 'Home Title',
}
}); });
*/ */
} }
render() { render() {
return ( return (
<View style={styles.container}> <View style={localStyle.wrap}>
<Text style={styles.welcome}>Home Page !</Text> <Loading visible={this.state.isLoading}></Loading>
</View>
); <FlatList style={localStyle.list}
data={this.state.diaries}
keyExtractor={(item, index) => {
return item.id.toString()
}}
renderItem={({item}) => {
return (
<Touchable onPress={() => this._onDiaryPress(item)}>
<DiaryFull diary={item}></DiaryFull>
</Touchable>
)
}}
ItemSeparatorComponent={({highlighted}) => <Divider style={{backgroundColor: '#eee'}}/>}
automaticallyAdjustContentInsets={true}
onEndReachedThreshold={2}
/>
</View>
);
} }
} }
const styles = StyleSheet.create({ const localStyle = StyleSheet.create({
container: { wrap: {
flex: 1, position: 'absolute',
justifyContent: 'center', backgroundColor: '#fff',
alignItems: 'center', flex: 1,
backgroundColor: '#F5FCFF', top: 0,
}, bottom: 0,
welcome: { paddingTop: isIpx || isAndroid ? 44 : 20
fontSize: 20, },
textAlign: 'center', list: {
margin: 10, backgroundColor: 'white',
}, height: '100%'
instructions: { }
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
}); });

View file

@ -35,6 +35,19 @@ async function getSelfInfo() {
return call('GET', '/users/my') return call('GET', '/users/my')
} }
async function getTodayDiaries(page = 1, page_size = 20, first_id = '') {
return call('GET', '/diaries/today?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;
});
}
async function call(method, api, body, _timeout = 10000) { async function call(method, api, body, _timeout = 10000) {
let token = await TokenManager.getUserToken(); let token = await TokenManager.getUserToken();
@ -58,16 +71,6 @@ async function call(method, api, body, _timeout = 10000) {
, _timeout); , _timeout);
} }
function timeout(promise, time) {
return Promise.race([
promise,
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), time)
})
]);
}
async function checkStatus(response) { async function checkStatus(response) {
if (response.status >= 200 && response.status < 300) { if (response.status >= 200 && response.status < 300) {
return response; return response;
@ -94,6 +97,15 @@ async function checkStatus(response) {
} }
} }
function timeout(promise, time) {
return Promise.race([
promise,
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), time)
})
]);
}
function parseJSON(response) { function parseJSON(response) {
if (response.headers.get('content-type') === 'application/json') { if (response.headers.get('content-type') === 'application/json') {
return response.json(); return response.json();
@ -113,5 +125,6 @@ function handleCatch(err) {
export default { export default {
login login,
getTodayDiaries
} }