eventlist: Section events by day
- Add pull to refresh - Add infinite scroll
This commit is contained in:
1 parent
77b173b7a6
commit
02c38ae6f2
3 files changed
+126
-46
No files matched your search
@@ -27,3 +27,7 @@
|
||||
|
||||
# Output is in android/app/build/outputs/apk
|
||||
|
||||
|
||||
## Libraries
|
||||
|
||||
We use https://nativebase.io/ for generic UI components
|
||||
@@ -1,41 +1,76 @@
|
||||
import {Platform, StyleSheet, FlatList, Text, View, Linking, TouchableOpacity, ActivityIndicator} from "react-native";
|
||||
import {StyleSheet, Linking, ActivityIndicator, View, SectionList} from "react-native";
|
||||
import {Card, ListItem, CardItem, Body, Text as Text, Left, Right, Icon, Button, Content} from 'native-base';
|
||||
import React, { Component } from 'react';
|
||||
import moment from "moment";
|
||||
import 'moment/locale/nb';
|
||||
|
||||
export default class EventList extends Component {
|
||||
_keyExtractor = (item, index) => item.id;
|
||||
|
||||
_renderItem = ({item}) => (
|
||||
<View>
|
||||
<TouchableOpacity onPress={() => { this._onPressItem(item); }} style={[styles.listItem, styles.card]}>
|
||||
<View>
|
||||
<Text style={styles.listItemTitle}>{item.title.rendered}</Text>
|
||||
<Text style={styles.listItemTime}>{this._formatTime(item.start_time)}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ListItem button onPress={() => { this._onPressItem(item); }} style={styles.listItem}>
|
||||
<Body style={{flex: 5}}>
|
||||
<Text style={styles.listItemTitle} numberOfLines={1}>{item.title.decoded}</Text>
|
||||
<Text style={styles.listItemTime}>{this._formatTime(item.start_time)}</Text>
|
||||
</Body>
|
||||
<Right style={{flex: 1}}>
|
||||
<Icon name="arrow-forward" />
|
||||
</Right>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
_renderSectionHeader = ({section}) => {
|
||||
return (<ListItem style={[styles.listItem, {paddingBottom: 8}]}>
|
||||
<Body>
|
||||
<Text style={styles.listItemSectionTitle}>{section.title}</Text>
|
||||
</Body>
|
||||
</ListItem>);
|
||||
};
|
||||
|
||||
_renderFooter = () => {
|
||||
if(!this.props.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (<View style={{paddingVertical: 20}}>
|
||||
<ActivityIndicator animating size="large" />
|
||||
</View>);
|
||||
};
|
||||
|
||||
_onPressItem = (item) => {
|
||||
Linking.openURL(item.link);
|
||||
};
|
||||
|
||||
|
||||
_formatTime(time) {
|
||||
moment.locale('nb');
|
||||
return moment(time).format('llll');
|
||||
}
|
||||
|
||||
render() {
|
||||
if(this.props.loading) {
|
||||
return (<View style={styles.card}><Text style={styles.loadingText}>Loading...</Text><ActivityIndicator/></View>)
|
||||
if (this.props.error) {
|
||||
return (<Content>
|
||||
<Card style={{margin: 4}}>
|
||||
<CardItem onPress={() => { this._onPressItem(item); }}>
|
||||
<Text style={styles.loadingText}>Kunne ikke hente programmet...</Text>
|
||||
</CardItem>
|
||||
<CardItem>
|
||||
<Button onPress={this.props.handleRefresh}><Text>Prøv igjen</Text></Button>
|
||||
</CardItem>
|
||||
</Card>
|
||||
</Content>)
|
||||
}
|
||||
return (<FlatList
|
||||
data={this.props.events}
|
||||
|
||||
return (<SectionList
|
||||
sections={this.props.eventsSectioned}
|
||||
renderItem={this._renderItem}
|
||||
keyExtractor={this._keyExtractor}
|
||||
style={styles.container}
|
||||
renderSectionHeader={this._renderSectionHeader}
|
||||
keyExtractor={(item) => item.id}
|
||||
refreshing={this.props.refreshing}
|
||||
onRefresh={this.props.handleRefresh}
|
||||
onEndReached={this.props.handleLoadMore}
|
||||
onEndReachedThreshould={10}
|
||||
initialNumToRender={10}
|
||||
ListFooterComponent={this._renderFooter}
|
||||
style={styles.list}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
@@ -43,40 +78,24 @@ export default class EventList extends Component {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listItem: {
|
||||
height: 86,
|
||||
marginLeft: 0
|
||||
},
|
||||
listItemTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: 'black'
|
||||
fontSize: 14,
|
||||
},
|
||||
listItemTime: {
|
||||
fontSize: 16,
|
||||
color: 'black'
|
||||
fontSize: 14,
|
||||
color: '#666'
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
card: {
|
||||
borderColor: '#e1e8ee',
|
||||
borderWidth: 1,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
shadowColor: 'rgba(0,0,0, .2)',
|
||||
shadowOffset: { height: 0, width: 0 },
|
||||
shadowOpacity: 1,
|
||||
shadowRadius: 1,
|
||||
},
|
||||
android: {
|
||||
elevation: 1,
|
||||
},
|
||||
}),
|
||||
padding: 8,
|
||||
margin: 8,
|
||||
backgroundColor: '#fff',
|
||||
listItemSectionTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
loadingText: {
|
||||
textAlign: 'center',
|
||||
paddingBottom: 8
|
||||
},
|
||||
list: {
|
||||
backgroundColor: 'white'
|
||||
}
|
||||
});
|
||||
@@ -2,6 +2,8 @@ import {connect} from "react-redux";
|
||||
import EventList from "./EventList";
|
||||
|
||||
import React from 'react';
|
||||
import moment from "moment";
|
||||
import 'moment/locale/nb';
|
||||
|
||||
/* Ref: https://medium.com/react-native-development/how-to-use-the-flatlist-component-react-native-basics-92c482816fe6 */
|
||||
|
||||
@@ -12,26 +14,81 @@ class EventListContainer extends React.Component {
|
||||
this.state = {
|
||||
loading: false,
|
||||
events: [],
|
||||
eventsSectioned: [],
|
||||
page: 1,
|
||||
noMorePages: false,
|
||||
error: null,
|
||||
refreshing: false,
|
||||
};
|
||||
}
|
||||
|
||||
_formatDate(time) {
|
||||
moment.locale('nb');
|
||||
return moment(time).format('ll');
|
||||
}
|
||||
|
||||
_toSectionFormat(events) {
|
||||
let sectioned = {};
|
||||
events.map((event) => {
|
||||
const title = this._formatDate(event.start_time);
|
||||
if( title in sectioned ) {
|
||||
sectioned[title].push(event);
|
||||
} else {
|
||||
sectioned[title] = [event];
|
||||
}
|
||||
});
|
||||
|
||||
let sectionedList = [];
|
||||
Object.entries(sectioned).forEach(([title, section]) => {
|
||||
sectionedList.push({data: section, title: title});
|
||||
});
|
||||
|
||||
return sectionedList;
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
this.setState({
|
||||
page: 1,
|
||||
refreshing: true,
|
||||
noMorePages: false,
|
||||
}, () => {
|
||||
this.fetchEvents()
|
||||
});
|
||||
};
|
||||
|
||||
handleLoadMore = () => {
|
||||
if(this.state.noMorePages || this.state.loading) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
page: this.state.page + 1,
|
||||
}, () => {
|
||||
this.fetchEvents();
|
||||
})
|
||||
};
|
||||
|
||||
fetchEvents() {
|
||||
const { page } = this.state;
|
||||
const url = `https://studentersamfundet.no/wp-json/wp/v2/events?future=1`; //&page=${page}&results=20`;
|
||||
const url = `https://studentersamfundet.no/wp-json/wp/v2/events?page=${page}&future=1`;
|
||||
this.setState({ loading: true });
|
||||
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
console.log(res);
|
||||
if(res.code === 'rest_post_invalid_page_number') {
|
||||
this.setState({
|
||||
noMorePages: true
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
events: page === 1 ? res : [...this.state.events, ...res],
|
||||
eventsSectioned: page === 1 ? this._toSectionFormat(res) : this._toSectionFormat([...this.state.events, ...res]),
|
||||
error: res.error || null,
|
||||
loading: false,
|
||||
refreshing: false
|
||||
});
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({ error, loading: false });
|
||||
@@ -43,7 +100,7 @@ class EventListContainer extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<EventList {...this.state} />)
|
||||
return (<EventList {...{...this.state, handleRefresh: this.handleRefresh, handleLoadMore: this.handleLoadMore}} />)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user