Skip to content

Commit 17f5351

Browse files
authored
Tweak listviews (#172)
* Fix function called with loading spinners. This fixes the animations on the listviews when loading with activity spinners. * add a refresh control to the news views * check for offline in sis stuff * add pageSize to newsContainer * add a refreshControl to Calendar * fix bug about getConnected in financials data loading * make buildingHours update without needing to refresh also fixes the missing content bug * simplify refresh in calendar and news * add refresh to oleville * add refresh to balances * add time delay to refresh in courses
1 parent bc6db29 commit 17f5351

File tree

10 files changed

+219
-107
lines changed

10 files changed

+219
-107
lines changed

lib/courses.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
loadLoginCredentials,
55
sisLogin,
66
} from '../lib/login'
7-
import { AsyncStorage } from 'react-native'
7+
import { AsyncStorage, NetInfo } from 'react-native'
88

99
import buildFormData from './formdata'
1010
import {parseHtml, cssSelect, getText} from './html'
@@ -37,14 +37,17 @@ const COURSES__TERM_LIST = 'courses:term-list'
3737
const COURSES__CACHE__COURSES = 'courses:cache-time:courses'
3838
const COURSES__CACHE__TERM_LIST = 'courses:cache-time:term-list'
3939

40-
41-
export async function loadAllCourses(forceFromServer?: bool): Promise<{[key: string]: (CourseType|Error)[]}> {
40+
type CourseMappingType = {[key: string]: (CourseType|Error)[]};
41+
export async function loadAllCourses(forceFromServer?: bool): Promise<CourseMappingType> {
4242
// await AsyncStorage.removeItem(COURSES__CACHE__COURSES)
4343
let timeLastFetched = JSON.parse(await AsyncStorage.getItem(COURSES__CACHE__COURSES))
4444
let needsUpdate = moment(timeLastFetched).isBefore(moment().subtract(1, 'hour'))
45-
if (!timeLastFetched || needsUpdate || forceFromServer) {
45+
46+
let isConnected = await NetInfo.isConnected.fetch()
47+
if (isConnected && (!timeLastFetched || needsUpdate || forceFromServer)) {
4648
return await getAllCoursesFromServer()
4749
}
50+
4851
return await getAllCoursesFromStorage()
4952
}
5053

lib/financials.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
loadLoginCredentials,
55
sisLogin,
66
} from '../lib/login'
7-
import { AsyncStorage } from 'react-native'
7+
import { AsyncStorage, NetInfo } from 'react-native'
88

99
import buildFormData from './formdata'
1010
import {parseHtml, cssSelect, getText} from './html'
@@ -18,10 +18,12 @@ const FINANCIALS__PRINT = 'financials:print'
1818

1919
type FinancialDataShapeType = {flex: null|number, ole: null|number, print: null|number};
2020

21-
export async function getFinancialData(): Promise<FinancialDataShapeType> {
21+
export async function getFinancialData(forceFromServer?: bool): Promise<FinancialDataShapeType> {
2222
let timeLastFetched = JSON.parse(await AsyncStorage.getItem(FINANCIALS__CACHE__BALANCES))
2323
let needsUpdate = moment(timeLastFetched).isBefore(moment().subtract(1, 'minute'))
24-
if (!timeLastFetched || needsUpdate) {
24+
25+
let isConnected = await NetInfo.isConnected.fetch()
26+
if (isConnected && (!timeLastFetched || needsUpdate || forceFromServer)) {
2527
return await getFinancialDataFromServer()
2628
}
2729
return await getFinancialDataFromStorage()

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"dependencies": {
2727
"buffer": "4.9.1",
2828
"css-select": "1.2.0",
29+
"delay": "1.3.1",
2930
"events": "1.1.1",
3031
"frisbee": "1.1.1",
3132
"html-entities": "1.2.0",

views/building-hours/index.js

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
*/
66

77
import React from 'react'
8-
import {ListView} from 'react-native'
8+
import {View, Text, ListView, RefreshControl} from 'react-native'
99
import BuildingView from './building'
1010

11+
import delay from 'delay'
1112
import type {BuildingInfoType} from './types'
1213
import hoursData from '../../data/building-hours.json'
1314

@@ -28,15 +29,28 @@ const buildingImages = {
2829
export default class BuildingHoursView extends React.Component {
2930
state = {
3031
dataSource: new ListView.DataSource({
31-
rowHasChanged: this._rowHasChanged,
32+
rowHasChanged: (r1: BuildingInfoType, r2: BuildingInfoType) => r1.name !== r2.name,
3233
}).cloneWithRows(hoursData),
34+
intervalId: 0,
35+
now: Date.now(),
36+
refreshing: false,
3337
}
3438

35-
_rowHasChanged(r1: BuildingInfoType, r2: BuildingInfoType) {
36-
return r1.name !== r2.name
39+
componentWillMount() {
40+
// This updates the screen every ten seconds, so that the building
41+
// info statuses are updated without needing to leave and come back.
42+
this.setState({intervalId: setInterval(this.updateTime, 10000)})
3743
}
3844

39-
_renderRow(data: BuildingInfoType) {
45+
componentWillUnmount() {
46+
clearTimeout(this.state.intervalId)
47+
}
48+
49+
updateTime = () => {
50+
this.setState({now: Date.now()})
51+
}
52+
53+
_renderRow = (data: BuildingInfoType) => {
4054
return (
4155
<BuildingView
4256
name={data.name}
@@ -46,6 +60,13 @@ export default class BuildingHoursView extends React.Component {
4660
)
4761
}
4862

63+
refresh = async () => {
64+
this.setState({refreshing: true})
65+
await delay(500)
66+
this.setState({now: Date.now()})
67+
this.setState({refreshing: false})
68+
}
69+
4970
// Render a given scene
5071
render() {
5172
return (
@@ -54,6 +75,13 @@ export default class BuildingHoursView extends React.Component {
5475
renderRow={this._renderRow.bind(this)}
5576
pageSize={4}
5677
initialListSize={6}
78+
removeClippedSubviews={false} // remove after https://github.com/facebook/react-native/issues/8607#issuecomment-241715202
79+
refreshControl={
80+
<RefreshControl
81+
refreshing={this.state.refreshing}
82+
onRefresh={this.refresh}
83+
/>
84+
}
5785
/>
5886
)
5987
}

views/calendar/calendar.js

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import {
1010
Text,
1111
Platform,
1212
ListView,
13+
RefreshControl,
1314
} from 'react-native'
1415

16+
import delay from 'delay'
1517
import LoadingView from '../components/loading'
1618
import qs from 'querystring'
1719
import EventView from './event'
@@ -26,46 +28,29 @@ type GoogleCalendarEventType = {
2628
end: GoogleCalendarTimeType,
2729
location: string,
2830
};
29-
type StateType = {
30-
events: null|GoogleCalendarResponseType,
31-
loaded: boolean,
32-
error: null|string,
33-
};
34-
type GoogleCalendarResponseType = {
35-
items: GoogleCalendarEventType[],
36-
};
3731

3832
export default class CalendarView extends React.Component {
3933
static propTypes = {
40-
source: React.PropTypes.oneOf(['master', 'oleville']).isRequired,
34+
calendarId: React.PropTypes.string.isRequired,
4135
}
4236

43-
state: StateType = {
44-
events: null,
37+
state = {
38+
events: new ListView.DataSource({
39+
rowHasChanged: this._rowHasChanged,
40+
}),
4541
loaded: false,
42+
refreshing: true,
4643
error: null,
4744
}
4845

4946
componentWillMount() {
50-
if (this.props.source === 'master') {
51-
this.getMasterEvents()
52-
} else {
53-
this.getOlevilleEvents()
54-
}
47+
this.refresh()
5548
}
5649

5750
_rowHasChanged(r1: GoogleCalendarEventType, r2: GoogleCalendarEventType) {
5851
return r1.summary != r2.summary
5952
}
6053

61-
async getMasterEvents() {
62-
this.getEvents('le6tdd9i38vgb7fcmha0hu66u9gjus2e%40import.calendar.google.com')
63-
}
64-
65-
async getOlevilleEvents() {
66-
this.getEvents('[email protected]')
67-
}
68-
6954
buildCalendarUrl(calendarId: string) {
7055
let calendarUrl = `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`
7156
let params = {
@@ -79,9 +64,8 @@ export default class CalendarView extends React.Component {
7964
return `${calendarUrl}?${qs.stringify(params)}`
8065
}
8166

82-
async getEvents(calendarId: string) {
83-
let url = this.buildCalendarUrl(calendarId)
84-
console.log(url)
67+
getEvents = async () => {
68+
let url = this.buildCalendarUrl(this.props.calendarId)
8569

8670
let data = null
8771
let error = null
@@ -90,41 +74,49 @@ export default class CalendarView extends React.Component {
9074
error = result.error
9175
data = result.items
9276
} catch (error) {
93-
this.setState({error: 'Error!'})
77+
this.setState({error: error.message})
9478
console.error(error)
9579
}
9680

9781
if (data) {
98-
this.setState({events: data})
82+
this.setState({events: this.state.events.cloneWithRows(data)})
9983
}
10084
if (error) {
10185
this.setState({error: error.message})
10286
}
10387
this.setState({loaded: true})
10488
}
10589

90+
refresh = async () => {
91+
let start = Date.now()
92+
this.setState({refreshing: true})
93+
94+
await this.getEvents()
95+
96+
// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
97+
let elapsed = start - Date.now()
98+
if (elapsed < 500) {
99+
await delay(500 - elapsed)
100+
}
101+
102+
this.setState({refreshing: false})
103+
}
104+
106105
render() {
107-
if (!this.state.events) {
106+
if (!this.state.loaded) {
108107
return <LoadingView />
109108
}
110109

111110
if (this.state.error) {
112-
return (
113-
<Text>
114-
{this.state.error}
115-
</Text>
116-
)
111+
return <Text>{this.state.error}</Text>
117112
}
118113

119-
let ds = new ListView.DataSource({
120-
rowHasChanged: this._rowHasChanged,
121-
})
122-
123114
return (
124115
<ListView
125116
style={styles.container}
126117
contentInset={{bottom: Platform.OS === 'ios' ? 49 : 0}}
127-
dataSource={ds.cloneWithRows(this.state.events)}
118+
dataSource={this.state.events}
119+
pageSize={5}
128120
renderRow={data =>
129121
<EventView
130122
style={styles.row}
@@ -134,6 +126,12 @@ export default class CalendarView extends React.Component {
134126
location={data.location}
135127
/>
136128
}
129+
refreshControl={
130+
<RefreshControl
131+
refreshing={this.state.refreshing}
132+
onRefresh={this.refresh}
133+
/>
134+
}
137135
/>
138136
)
139137
}
@@ -147,7 +145,7 @@ let styles = StyleSheet.create({
147145
row: {
148146
minHeight: 88,
149147
marginLeft: 10,
150-
marginRight: 10,
148+
paddingRight: 10,
151149
borderBottomWidth: 1,
152150
borderBottomColor: '#ebebeb',
153151
},

views/calendar/tabs.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// @flow
2-
import React from 'react'
32
import CalendarView from './calendar'
43

54
export default [
@@ -8,13 +7,13 @@ export default [
87
title: 'Master Events',
98
rnVectorIcon: {iconName: 'school'},
109
component: CalendarView,
11-
props: {source: 'master'},
10+
props: {calendarId: 'le6tdd9i38vgb7fcmha0hu66u9gjus2e%40import.calendar.google.com'},
1211
},
1312
{
1413
id: 'oleville',
1514
title: 'Oleville Events',
1615
rnVectorIcon: {iconName: 'happy'},
1716
component: CalendarView,
18-
props: {source: 'oleville'},
17+
props: {calendarId: '[email protected]'},
1918
},
2019
]

0 commit comments

Comments
 (0)