Skip to content

Commit fcc7981

Browse files
authored
Merge pull request #1611 from StoDevX/remote-webcams-list
Load the list of webcams from GitHub
2 parents 88c3ab1 + 8a0a4b8 commit fcc7981

File tree

9 files changed

+112
-93
lines changed

9 files changed

+112
-93
lines changed

data/_schemas/webcams.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
$schema: http://json-schema.org/draft-06/schema#
22

33
additionalProperties: false
4-
required: [name, pageUrl, streamUrl, thumbnail, tagline, accentColor]
4+
required: [name, pageUrl, streamUrl, thumbnail, tagline, textColor, accentColor]
55
properties:
66
name: {type: string}
77
pageUrl: {type: string, format: uri}
88
streamUrl: {type: string, format: uri}
99
thumbnail: {type: string}
10+
thumbnailUrl: {type: string}
1011
tagline: {type: string}
12+
textColor: {type: string}
1113
accentColor:
1214
type: array
1315
items: [{type: number}, {type: number}, {type: number}]

data/webcams/0-alumni-hall-west.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ streamUrl: https://cdn.stobcm.com/webcams/alumniwest.stream/master.m3u8
44
thumbnail: alumniwest
55
tagline: A view of the Ytterboe Hall and the wind turbine
66
accentColor: [48, 63, 102]
7+
textColor: '#fff'

data/webcams/1-buntrock-plaza.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ streamUrl: https://cdn.stobcm.com/webcams/bcplaza.stream/master.m3u8
44
thumbnail: bcplaza
55
tagline: Looking out to the plaza and campus green
66
accentColor: [93, 114, 72]
7+
textColor: '#fff'

data/webcams/2-east-quad.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ streamUrl: https://cdn.stobcm.com/webcams/eastquad.stream/master.m3u8
44
thumbnail: eastquad
55
tagline: Looking out to Regents, Holland Hall, and Old Main
66
accentColor: [82, 87, 54]
7+
textColor: '#fff'

data/webcams/3-hi-mom.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ streamUrl: https://cdn.stobcm.com/webcams/himom.stream/master.m3u8
44
thumbnail: himom
55
tagline: Located in the Crossroads inside Buntrock Commons
66
accentColor: [137, 141, 150]
7+
textColor: '#fff'

data/webcams/4-tomson-east-lantern.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ streamUrl: https://cdn.stobcm.com/webcams/tomsoneast.stream/master.m3u8
44
thumbnail: tomsoneast
55
tagline: A view of the campus green and Wind Chime Memorial
66
accentColor: [89, 84, 82]
7+
textColor: '#fff'

data/webcams/5-tomson-west-lantern.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ streamUrl: https://cdn.stobcm.com/webcams/tomsonwest.stream/master.m3u8
44
thumbnail: tomsonwest
55
tagline: Looking out to Mellby Hall and the Theater Building
66
accentColor: [110, 126, 91]
7+
textColor: '#fff'

images/transparent.png

740 Bytes
Loading

source/views/streaming/webcams.js

Lines changed: 103 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,175 +5,186 @@
55
*/
66

77
import React from 'react'
8-
import {
9-
StyleSheet,
10-
View,
11-
Text,
12-
ScrollView,
13-
Image,
14-
Dimensions,
15-
Platform,
16-
} from 'react-native'
17-
import {Column} from '../components/layout'
8+
import {StyleSheet, View, Text, FlatList, Image, Platform} from 'react-native'
9+
import delay from 'delay'
10+
import {reportNetworkProblem} from '../../lib/report-network-problem'
1811
import {TabBarIcon} from '../components/tabbar-icon'
1912
import {Touchable} from '../components/touchable'
2013
import * as c from '../components/colors'
2114
import * as defaultData from '../../../docs/webcams.json'
2215
import {webcamImages} from '../../../images/webcam-images'
2316
import {trackedOpenUrl} from '../components/open-url'
2417
import LinearGradient from 'react-native-linear-gradient'
25-
import {partitionByIndex} from '../../lib/partition-by-index'
18+
19+
const transparentPixel = require('../../../images/transparent.png')
20+
const GITHUB_URL = 'https://stodevx.github.io/AAO-React-Native/webcams.json'
2621

2722
type WebcamType = {
2823
streamUrl: string,
2924
pageUrl: string,
3025
name: string,
3126
thumbnail: string,
27+
thumbnailUrl?: string,
28+
textColor: string,
3229
accentColor: [number, number, number],
3330
}
3431

35-
type DProps = {
36-
webcams: Array<WebcamType>,
37-
}
38-
39-
type Props = {
40-
webcams: Array<WebcamType>,
41-
}
32+
type Props = {}
4233

4334
type State = {
44-
width: number,
35+
webcams: Array<WebcamType>,
36+
loading: boolean,
37+
refreshing: boolean,
4538
}
4639

47-
export class WebcamsView extends React.PureComponent<DProps, Props, State> {
40+
export class WebcamsView extends React.PureComponent<void, Props, State> {
4841
static navigationOptions = {
4942
tabBarLabel: 'Webcams',
5043
tabBarIcon: TabBarIcon('videocam'),
5144
}
5245

53-
static defaultProps = {
54-
webcams: defaultData.data,
55-
}
56-
5746
state = {
58-
width: Dimensions.get('window').width,
47+
webcams: defaultData.data,
48+
loading: false,
49+
refreshing: false,
5950
}
6051

6152
componentWillMount() {
62-
Dimensions.addEventListener('change', this.handleResizeEvent)
53+
this.fetchData()
6354
}
6455

65-
componentWillUnmount() {
66-
Dimensions.removeEventListener('change', this.handleResizeEvent)
56+
refresh = async () => {
57+
const start = Date.now()
58+
this.setState(() => ({refreshing: true}))
59+
60+
await this.fetchData()
61+
62+
// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
63+
const elapsed = Date.now() - start
64+
if (elapsed < 500) {
65+
await delay(500 - elapsed)
66+
}
67+
68+
this.setState(() => ({refreshing: false}))
6769
}
6870

69-
handleResizeEvent = (event: {window: {width: number}}) => {
70-
this.setState(() => ({width: event.window.width}))
71+
fetchData = async () => {
72+
this.setState(() => ({loading: true}))
73+
74+
let {data: webcams} = await fetchJson(GITHUB_URL).catch(err => {
75+
reportNetworkProblem(err)
76+
return defaultData
77+
})
78+
79+
if (process.env.NODE_ENV === 'development') {
80+
webcams = defaultData.data
81+
}
82+
83+
this.setState(() => ({webcams, loading: false}))
7184
}
7285

73-
render() {
74-
const columns = partitionByIndex(this.props.webcams)
86+
renderItem = ({item}: {item: WebcamType}) =>
87+
<StreamThumbnail key={item.name} webcam={item} />
88+
89+
keyExtractor = (item: WebcamType) => item.name
7590

91+
render() {
7692
return (
77-
<ScrollView contentContainerStyle={styles.gridWrapper}>
78-
{columns.map((contents, i) =>
79-
<Column key={i} style={styles.column}>
80-
{contents.map(webcam =>
81-
<StreamThumbnail
82-
key={webcam.name}
83-
webcam={webcam}
84-
textColor="white"
85-
viewportWidth={this.state.width}
86-
/>,
87-
)}
88-
</Column>,
89-
)}
90-
</ScrollView>
93+
<FlatList
94+
keyExtractor={this.keyExtractor}
95+
renderItem={this.renderItem}
96+
refreshing={this.state.refreshing}
97+
onRefresh={this.refresh}
98+
data={this.state.webcams}
99+
numColumns={2}
100+
columnWrapperStyle={styles.row}
101+
contentContainerStyle={styles.container}
102+
/>
91103
)
92104
}
93105
}
94106

95107
class StreamThumbnail extends React.PureComponent {
96108
props: {
97109
webcam: WebcamType,
98-
textColor: 'white' | 'black',
99-
viewportWidth: number,
100110
}
101111

102112
handlePress = () => {
103113
const {streamUrl, name, pageUrl} = this.props.webcam
104-
if (Platform.OS === 'android') {
114+
if (Platform.OS === 'ios') {
115+
trackedOpenUrl({url: streamUrl, id: `${name}WebcamView`})
116+
} else if (Platform.OS === 'android') {
105117
trackedOpenUrl({url: pageUrl, id: `${name}WebcamView`})
106118
} else {
107-
trackedOpenUrl({url: streamUrl, id: `${name}WebcamView`})
119+
trackedOpenUrl({url: pageUrl, id: `${name}WebcamView`})
108120
}
109121
}
110122

111123
render() {
112-
const {textColor, viewportWidth} = this.props
113-
const {name, thumbnail, accentColor} = this.props.webcam
124+
const {
125+
name,
126+
thumbnail,
127+
accentColor,
128+
textColor,
129+
thumbnailUrl,
130+
} = this.props.webcam
114131

115132
const [r, g, b] = accentColor
116133
const baseColor = `rgba(${r}, ${g}, ${b}, 1)`
117134
const startColor = `rgba(${r}, ${g}, ${b}, 0.1)`
118-
const actualTextColor = c[textColor]
119135

120-
const width = viewportWidth / 2 - CELL_MARGIN * 1.5
121-
const cellRatio = 2.15625
122-
const height = width / cellRatio
136+
const img = thumbnailUrl
137+
? {uri: thumbnailUrl}
138+
: webcamImages.hasOwnProperty(thumbnail)
139+
? webcamImages[thumbnail]
140+
: transparentPixel
123141

124142
return (
125-
<View style={[styles.cell, styles.rounded, {width, height}]}>
126-
<Touchable
127-
highlight={true}
128-
underlayColor={baseColor}
129-
activeOpacity={0.7}
130-
onPress={this.handlePress}
131-
>
132-
<Image source={webcamImages[thumbnail]} style={[styles.image]}>
133-
<View style={styles.titleWrapper}>
134-
<LinearGradient
135-
colors={[startColor, baseColor]}
136-
locations={[0, 0.8]}
137-
>
138-
<Text style={[styles.titleText, {color: actualTextColor}]}>
139-
{name}
140-
</Text>
141-
</LinearGradient>
142-
</View>
143-
</Image>
144-
</Touchable>
145-
</View>
143+
<Touchable
144+
highlight={true}
145+
underlayColor={baseColor}
146+
activeOpacity={0.7}
147+
onPress={this.handlePress}
148+
containerStyle={styles.cell}
149+
>
150+
<Image source={img} style={styles.image}>
151+
<View style={styles.titleWrapper}>
152+
<LinearGradient
153+
colors={[startColor, baseColor]}
154+
locations={[0, 0.8]}
155+
>
156+
<Text style={[styles.titleText, {color: textColor}]}>
157+
{name}
158+
</Text>
159+
</LinearGradient>
160+
</View>
161+
</Image>
162+
</Touchable>
146163
)
147164
}
148165
}
149166

150167
const CELL_MARGIN = 10
151168

152169
const styles = StyleSheet.create({
153-
column: {
154-
flex: 1,
170+
container: {
171+
marginVertical: CELL_MARGIN / 2,
155172
},
156-
gridWrapper: {
157-
marginHorizontal: CELL_MARGIN / 2,
158-
marginTop: CELL_MARGIN / 2,
159-
paddingBottom: CELL_MARGIN / 2,
160-
161-
alignItems: 'center',
173+
row: {
162174
justifyContent: 'center',
163-
flexDirection: 'row',
164-
flexWrap: 'wrap',
165-
},
166-
rounded: {
167-
// TODO: Android doesn't currently (0.42) respect both
168-
// overflow:hidden and border-radius.
169-
borderRadius: Platform.OS === 'android' ? 0 : 6,
175+
marginHorizontal: CELL_MARGIN / 2,
170176
},
171177
cell: {
178+
flex: 1,
172179
overflow: 'hidden',
173180
margin: CELL_MARGIN / 2,
174181
justifyContent: 'center',
175182

176183
elevation: 2,
184+
185+
// TODO: Android doesn't currently (0.42) respect both
186+
// overflow:hidden and border-radius.
187+
borderRadius: Platform.OS === 'android' ? 0 : 6,
177188
},
178189
image: {
179190
width: '100%',

0 commit comments

Comments
 (0)