Skip to content

Commit

Permalink
[Gateway] add flatlist screen (#119)
Browse files Browse the repository at this point in the history
* [Gateway] add flatlist screen

* add upColor/downColor to StatusDot

* add gateway connected status

* prepend ttn to gateway

* add catch to gateway actions

* define alive gateway as seen in last minute

* [eslint] remove import plugin

* fix prop type

* lint
  • Loading branch information
cjcaj authored and christopherdro committed May 4, 2017
1 parent 1422e87 commit f05a08b
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 159 deletions.
2 changes: 0 additions & 2 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ globals:
jest: true

extends:
- plugin:eslint-plugin-import/warnings
- plugin:eslint-plugin-import/errors
- standard-react

plugins:
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"eslint": "^3.18.0",
"eslint-config-standard-react": "^4.3.0",
"eslint-plugin-flowtype": "^2.30.4",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-react": "git+ssh://git@github.com/async-la/eslint-plugin-react#ttn-patch",
"flow-bin": "0.42",
"haul-cli": "^0.1.2",
Expand All @@ -68,4 +67,4 @@
"jest": {
"preset": "react-native"
}
}
}
101 changes: 101 additions & 0 deletions src/components/GatewayListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// @flow

import React, { Component } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'

import StatusDot from '../components/StatusDot'
import TagLabel from '../components/TagLabel'

import { GATEWAY_DETAIL } from '../scopes/navigation/constants'
import { BLUE, MID_GREY, WHITE } from '../constants/colors'
import { LATO_REGULAR } from '../constants/fonts'

const LAST_SEEN_LIMIT_MSECS = 1000 * 60 // 1 minute

import type { TTNGateway } from '../scopes/content/gateways/types'

type Props = {
gateway: TTNGateway,
navigation: Object,
}

export default class GatewayListItem extends Component {
props: Props
_navigateToSingleGateway = gateway => {
this.props.navigation.navigate(GATEWAY_DETAIL, {
gateway: gateway,
gatwayName: gateway.attributes.description,
gatwayId: gateway.id,
})
}
render() {
const { gateway } = this.props
const isAlive =
Date.now() - gateway.status.time / (1000 * 1000) < LAST_SEEN_LIMIT_MSECS

return (
<TouchableOpacity
onPress={() => this._navigateToSingleGateway(gateway)}
style={styles.gatewayItem}
>
<View style={styles.idHandlerRow}>
<TagLabel center orange style={{ width: '50%' }}>
{gateway.id}
</TagLabel>
<StatusDot downColor={MID_GREY} up={isAlive} upColor={BLUE} />
</View>
<View style={styles.descriptionRow}>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={styles.descriptionText}
>
{gateway.attributes.description}
</Text>
<Text style={styles.frequencyText}>
{gateway.frequency_plan}
</Text>
</View>
</TouchableOpacity>
)
}
}

const styles = StyleSheet.create({
gatewayItem: {
backgroundColor: WHITE,
borderRadius: 3,
padding: 10,
minHeight: 100,
justifyContent: 'center',
},
idHandlerRow: {
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 5,
flexDirection: 'row',
},
descriptionRow: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 5,
marginTop: 10,
marginLeft: 5,
},
descriptionText: {
color: MID_GREY,
fontFamily: LATO_REGULAR,
},
nameContainer: {
flex: 1,
alignItems: 'flex-start',
},
handlerContainer: {
width: 70,
},
frequencyText: {
fontStyle: 'italic',
fontSize: 10,
},
})
15 changes: 9 additions & 6 deletions src/components/StatusDot.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ import { StyleSheet, View } from 'react-native'
import { GREY, ORANGE, BRIGHT_GREEN } from '../constants/colors'

type Props = {
downColor?: string,
up: boolean,
upColor?: string,
}

const StatusDot = ({ up }: Props) => {
return <View style={[styles.dot, up && styles.up]} />
const StatusDot = ({
downColor = ORANGE,
up,
upColor = BRIGHT_GREEN,
}: Props) => {
const backgroundColor = up ? upColor : downColor
return <View style={[styles.dot, { backgroundColor }]} />
}

export default StatusDot
Expand All @@ -21,10 +28,6 @@ const styles = StyleSheet.create({
width: 15,
borderRadius: 10,
borderWidth: 1,
backgroundColor: ORANGE,
borderColor: GREY,
},
up: {
backgroundColor: BRIGHT_GREEN,
},
})
3 changes: 3 additions & 0 deletions src/constants/apiEndpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ export const AUTHORIZATION_URI =

// Applications
export const APPLICATIONS = `${HOST}applications/`

// Gateways
export const GATEWAYS = `${HOST}gateways/`
40 changes: 40 additions & 0 deletions src/scopes/content/gateways/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// @flow

import apiClient from '../../../utils/apiClient'

import { GATEWAYS } from '../../../constants/apiEndpoints'
import type { TTNGateway } from './types'
import type { Dispatch, GetState } from '../../../types/redux'

/**
* Fetch ALL Gateways
*/

export function getGatewaysAsync() {
return async (dispatch: Dispatch, getState: GetState) => {
try {
const payload: Array<TTNGateway> = await apiClient.get(GATEWAYS)
return dispatch({ type: 'content/RECEIVE_TTN_GATEWAYS', payload })
} catch (err) {
console.log('## getGatewaysAsync error', err)
throw err
}
}
}

/**
* Fetch Gateway by ID
*/

export function getGatewayAsync(gateway: TTNGateway) {
const { id } = gateway
return async (dispatch: Dispatch, getState: GetState) => {
try {
const payload: TTNGateway = await apiClient.get(GATEWAYS + id)
return dispatch({ type: 'content/RECEIVE_TTN_GATEWAY', payload })
} catch (err) {
console.log('## getGatewaysAsync error', err)
throw err
}
}
}
68 changes: 68 additions & 0 deletions src/scopes/content/gateways/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// @flow

type Collaborator = {
username: string,
rights: Array<string>,
}

export type TTNGateway = {
id: string,
activated: boolean,
frequency_plan: string,
frequency_plan_url: string,
auto_update: boolean,
location_public: boolean,
status_public: boolean,
owner_public: boolean,
antenna_location: {
longitude: number,
latitude: number,
altitude: number,
},
collaborators: ?Array<Collaborator>,
key: string,
token: {
access_token: string,
expiry: string,
},
attributes: {
brand: string,
model: string,
placement: string,
antenna_model: string,
description: string,
},
router: Router,
fallback_routers: Array<Router>,
owner: { id: string, username: string },
rights: ?Array<string>,
status: {
time: number,
gps: { altitude: number },
rx_ok: number,
tx_in: number,
},
}

type Router = {
id: string,
address: string,
mqtt_address: string,
}

export const RECEIVE_TTN_GATEWAY = 'content/RECEIVE_TTN_GATEWAY'
export const RECEIVE_TTN_GATEWAYS = 'content/RECEIVE_TTN_GATEWAYS'

export type ReceiveTTNGatewayAction = {
type: 'content/RECEIVE_TTN_GATEWAY',
payload: TTNGateway,
}

export type ReceiveTTNGatewaysAction = {
type: 'content/RECEIVE_TTN_GATEWAYS',
payload: Array<TTNGateway>,
}

export type TTNGatewayAction =
| ReceiveTTNGatewayAction
| ReceiveTTNGatewaysAction
52 changes: 51 additions & 1 deletion src/scopes/content/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,47 @@ import {
RECEIVE_TTN_APPLICATIONS,
} from './applications/types'

import { RECEIVE_TTN_GATEWAY, RECEIVE_TTN_GATEWAYS } from './gateways/types'

import { RESET_AUTH } from '../auth/types'

import type { TTNApplicationAction, TTNApplication } from './applications/types'
import type { TTNGatewayAction, TTNGateway } from './gateways/types'
import _ from 'lodash'

type ContentAction = TTNApplicationAction | TTNGatewayAction

type TTNApplicationDictionary = {
[key: string]: TTNApplication,
}

type TTNGatewayDictionary = {
[key: string]: TTNGateway,
}

export type State = {
applications: {
list: Array<string>,
dictionary: TTNApplicationDictionary,
},
gateways: {
list: Array<string>,
dictionary: TTNGatewayDictionary,
},
}

export const initialState: State = {
applications: {
list: [],
dictionary: {},
},
gateways: {
list: [],
dictionary: {},
},
}

export default (state: State = initialState, action: TTNApplicationAction) => {
export default (state: State = initialState, action: ContentAction) => {
switch (action.type) {
case RESET_AUTH:
return initialState
Expand Down Expand Up @@ -66,6 +83,39 @@ export default (state: State = initialState, action: TTNApplicationAction) => {
}
}

case RECEIVE_TTN_GATEWAY: {
const gateway = action.payload

let dictionary = {
...state.gateways.dictionary,
[gateway.id]: gateway,
}

return {
...state,
gateways: {
list: _.uniq([...state.gateways.list, ...[gateway.id]]),
dictionary,
},
}
}

case RECEIVE_TTN_GATEWAYS: {
const incomingDictionary: TTNGatewayDictionary = _.keyBy(
action.payload,
'id'
)
const incomingList: Array<string> = _.map(action.payload, 'id')

return {
...state,
gateways: {
list: incomingList,
dictionary: incomingDictionary,
},
}
}

default:
return state
}
Expand Down
6 changes: 4 additions & 2 deletions src/scopes/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { REHYDRATE as SOURCE_REHYDRATE } from 'redux-persist/constants'

import type { AuthAction } from './auth/types'
import type { TTNApplicationAction } from './content/applications/types'
import type { TTNGatewayAction } from './content/gateways/types'
import type { DeviceAction } from './device/types'
import type { State } from './rootReducer'

Expand All @@ -16,10 +17,11 @@ export { REHYDRATE }
type RehydrateAction = {
type: 'persist/REHYDRATE',
payload: State,
};
}

export type Action =
| TTNApplicationAction
| AuthAction
| DeviceAction
| RehydrateAction;
| TTNGatewayAction
| RehydrateAction
Loading

0 comments on commit f05a08b

Please sign in to comment.