Skip to content

Commit 15c626a

Browse files
authored
Reduxify Settings, SIS, lib, … too much (#599)
* clarify types in buildFormData * centralize all access to AsyncStorage across lib/cache and lib/storage * update homescreen to use the new storage system * update ga to use the new storage system * rip out huge chunks of lib/login and make it use storage * remove frisbee * split lib/financials into multiple files and rework error handling and logic flows with the new helpers in lib/cache * split lib/courses into multiple files and rework the logic and error handling _completely_. I didn't change any of the parsing code, since as far as I know that still works, but the errors now propagate without throwing and the logic makes more sense? I think, anyway. * remove react-native-cookies * clean up the flux/ folder a bit * move the initialization of the flux store into flux/init * load login credentials into redux at start * extract menus reducer into flux/parts/menus * extract homescreen reducer into flux/parts/homescreen fix import for updateViewOrder test * add an "app-wide state" reducer * add basic flow to flux/ * abstract out some Balances components fix accidental flip-flop of validity in sis/balances add … to Balances' meals rows * split up Settings into multiple files add some semicolons fix capitalization typo in settings * store settings information in redux, instead of state * update views/sis to store state in redux expand parameter name in balances/mapDispatchToProps fix path indexing in views/courses * cache the meal responses in the cache * update lib/ to take isConnected as a parameter * clean up some files fix courses reducer fix double-payload bug in flux/sis fix some things in flux/settings tweak types update logOutViaToken to actually clear the stored token fix up some types around meals stuff * collapse redux actions in the console * rework settings/login view 1. make the user dismiss the view manually 2. float it from the bottom of the screen * clean up some more things fix sis-to-settings path sliding from bottom but not dismissing to bottom * check the sis login token at app start * load courses from the unofficial transcript instead of the main sis remove console.log stuff from parse-courses fix an export to export more things * load financial balances into the store at startup * extract button from contactCard * add button to NoticeView fix flow error in NoticeView * update Courses for new data source remove a typeof statement that snuck in update views/courses for bugfixes * check olaf credentials at app boot * update CourseType * check for nullability in financial responses * request SIS data as soon as login is successful * fix blank SIS courses issue * move deptnum generation into parse-courses * remove right border from Balances cell * remove unused imports from views/courses * don't log the page contents of the unofficial transcript
1 parent f5a000b commit 15c626a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1982
-1421
lines changed

analytics.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
// @flow
2-
import {AsyncStorage} from 'react-native'
32
import {
43
GoogleAnalyticsTracker,
54
GoogleAnalyticsSettings,
65
} from 'react-native-google-analytics-bridge'
76
import {stringifyFilters} from './views/components/filter'
87

8+
import * as storage from './lib/storage'
9+
910
const trackerId = __DEV__ ? 'UA-90234209-1' : 'UA-90234209-2'
1011
export const tracker = new GoogleAnalyticsTracker(trackerId)
1112

1213
async function disableIfOptedOut() {
13-
const didOptOut = JSON.parse(await AsyncStorage.getItem('optout'))
14+
const didOptOut = await storage.getAnalyticsOptOut()
1415
if (didOptOut) {
1516
GoogleAnalyticsSettings.setOptOut(true)
1617
}

android/app/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ android {
142142
dependencies {
143143
compile project(':react-native-google-analytics-bridge')
144144
compile project(':react-native-device-info')
145-
compile project(':react-native-cookies')
146145
compile project(':react-native-vector-icons')
147146
compile project(':react-native-keychain')
148147
compile fileTree(dir: "libs", include: ["*.jar"])

android/app/src/main/java/com/allaboutolaf/MainApplication.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import com.facebook.react.ReactApplication;
77
import com.idehub.GoogleAnalyticsBridge.GoogleAnalyticsBridgePackage;
88
import com.learnium.RNDeviceInfo.RNDeviceInfo;
9-
import com.psykar.cookiemanager.CookieManagerPackage;
109
import com.oblador.vectoricons.VectorIconsPackage;
1110
import com.oblador.keychain.KeychainPackage;
1211
import com.facebook.react.ReactInstanceManager;
@@ -33,7 +32,6 @@ protected List<ReactPackage> getPackages() {
3332
new MainReactPackage(),
3433
new GoogleAnalyticsBridgePackage(),
3534
new RNDeviceInfo(),
36-
new CookieManagerPackage(),
3735
new VectorIconsPackage(),
3836
new KeychainPackage(),
3937
new ReactNativeOneSignalPackage()

android/settings.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ rootProject.name = 'All About Olaf'
33
include ':app'
44
include ':react-native-device-info'
55
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
6-
include ':react-native-cookies'
7-
project(':react-native-cookies').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-cookies/android')
86
include ':react-native-vector-icons'
97
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
108
include ':react-native-keychain'

flux/README

Lines changed: 0 additions & 5 deletions
This file was deleted.

flux/__tests__/update-view-order.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-env jest */
2-
import {updateViewOrder} from '../index'
2+
import {updateViewOrder} from '../parts/homescreen'
33

44
test('it should return the current order if no screens have changed', () => {
55
let savedOrder = ['a', 'b', 'c']

flux/index.js

Lines changed: 22 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,34 @@
1+
/**
2+
* @flow
3+
* Root reducer for state storage
4+
*/
5+
16
import {createStore, applyMiddleware} from 'redux'
27
import createLogger from 'redux-logger'
38
import reduxPromise from 'redux-promise'
49
import reduxThunk from 'redux-thunk'
5-
import {AsyncStorage} from 'react-native'
6-
import {allViewNames as defaultViewOrder} from '../views/views'
7-
import difference from 'lodash/difference'
8-
import {trackMenuFilters, trackHomescreenOrder} from '../analytics'
9-
10-
export const SAVE_HOMESCREEN_ORDER = 'SAVE_HOMESCREEN_ORDER'
11-
12-
export const updateViewOrder = (currentOrder, defaultOrder=defaultViewOrder) => {
13-
currentOrder = currentOrder || []
14-
15-
// lodash/difference: Creates an array of array values _not included_ in the
16-
// other given arrays.
17-
18-
// In case new screens have been added, get a list of the new screens
19-
let addedScreens = difference(defaultOrder, currentOrder)
20-
// check for removed screens
21-
let removedScreens = difference(currentOrder, defaultOrder)
22-
23-
// add the new screens to the list
24-
currentOrder = currentOrder.concat(addedScreens)
25-
26-
// now we remove the screens that were removed
27-
currentOrder = difference(currentOrder, removedScreens)
28-
29-
return currentOrder
30-
}
31-
32-
export const loadHomescreenOrder = async () => {
33-
// get the saved list from AsyncStorage
34-
let savedOrder = JSON.parse(await AsyncStorage.getItem('homescreen:view-order'))
35-
36-
// update the order, in case new views have been added/removed
37-
let order = updateViewOrder(savedOrder, defaultViewOrder)
38-
39-
// return an action to save it to AsyncStorage
40-
return saveHomescreenOrder(order, {noTrack: true})
41-
}
10+
import {init} from './init'
4211

43-
export const saveHomescreenOrder = (order, options={}) => {
44-
options.noTrack || trackHomescreenOrder(order)
45-
AsyncStorage.setItem('homescreen:view-order', JSON.stringify(order))
46-
return {type: SAVE_HOMESCREEN_ORDER, payload: order}
47-
}
12+
import {app} from './parts/app'
13+
import {homescreen} from './parts/homescreen'
14+
import {menus} from './parts/menus'
15+
import {settings} from './parts/settings'
16+
import {sis} from './parts/sis'
4817

49-
const initialHomescreenState = {
50-
order: [],
51-
}
52-
function homescreen(state=initialHomescreenState, action) {
53-
let {type, payload} = action
54-
switch (type) {
55-
case SAVE_HOMESCREEN_ORDER: {
56-
return {...state, order: payload}
57-
}
58-
default:
59-
return state
60-
}
61-
}
6218

63-
64-
export const UPDATE_MENU_FILTERS = 'menus/UPDATE_MENU_FILTERS'
65-
66-
export const updateMenuFilters = (menuName: string, filters: any[]) => {
67-
trackMenuFilters(menuName, filters)
68-
return {type: UPDATE_MENU_FILTERS, payload: {menuName, filters}}
69-
}
70-
71-
const initialMenusState = {}
72-
function menus(state=initialMenusState, action) {
73-
let {type, payload} = action
74-
switch (type) {
75-
case UPDATE_MENU_FILTERS:
76-
return {...state, [payload.menuName]: payload.filters}
77-
default:
78-
return state
79-
}
80-
}
81-
82-
export function aao(state={}, action) {
19+
export function aao(state: Object={}, action: Object) {
8320
return {
21+
app: app(state.app, action),
8422
homescreen: homescreen(state.homescreen, action),
8523
menus: menus(state.menus, action),
24+
settings: settings(state.settings, action),
25+
sis: sis(state.sis, action),
8626
}
8727
}
8828

89-
const logger = createLogger()
90-
export const store = createStore(
29+
30+
const logger = createLogger({collapsed: () => true})
31+
const store = createStore(
9132
aao,
9233
applyMiddleware(
9334
reduxPromise,
@@ -96,4 +37,7 @@ export const store = createStore(
9637
)
9738
)
9839

99-
store.dispatch(loadHomescreenOrder())
40+
init(store)
41+
42+
export {store}
43+
export {updateMenuFilters} from './parts/menus'

flux/init.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @flow
3+
* Functions to initialize bits of the global state, as appropriate
4+
*/
5+
6+
import {NetInfo} from 'react-native'
7+
import {loadLoginCredentials} from '../lib/login'
8+
import {updateOnlineStatus} from './parts/app'
9+
import {loadHomescreenOrder} from './parts/homescreen'
10+
import {
11+
setLoginCredentials,
12+
logInViaToken,
13+
validateLoginCredentials,
14+
} from './parts/settings'
15+
import {
16+
updateFinancialData,
17+
updateMealsRemaining,
18+
updateCourses,
19+
} from './parts/sis'
20+
import {FINANCIALS_URL} from '../lib/financials/urls'
21+
22+
function homescreen(store) {
23+
store.dispatch(loadHomescreenOrder())
24+
}
25+
26+
function sisLoginCredentials(store) {
27+
loadLoginCredentials().then(({username, password}={}) => {
28+
if (!username || !password) return
29+
30+
let action = setLoginCredentials(username, password)
31+
store.dispatch(action)
32+
})
33+
}
34+
35+
function checkSisLogin(store) {
36+
// check if we can log in to the SIS
37+
fetch(FINANCIALS_URL).then(r => {
38+
if (r.url !== FINANCIALS_URL) {
39+
return
40+
}
41+
const action = logInViaToken(true)
42+
store.dispatch(action)
43+
})
44+
}
45+
46+
function validateOlafCredentials(store) {
47+
loadLoginCredentials().then(({username, password}={}) => {
48+
const action = validateLoginCredentials(username, password)
49+
store.dispatch(action)
50+
})
51+
}
52+
53+
function loadBalances(store) {
54+
store.dispatch(updateFinancialData(false, false))
55+
}
56+
function loadMeals(store) {
57+
store.dispatch(updateMealsRemaining(false, false))
58+
}
59+
function loadCourses(store) {
60+
store.dispatch(updateCourses(false, false))
61+
}
62+
63+
function netInfoIsConnected(store) {
64+
function updateConnectionStatus(isConnected) {
65+
store.dispatch(updateOnlineStatus(isConnected))
66+
}
67+
68+
NetInfo.isConnected.addEventListener('change', updateConnectionStatus)
69+
NetInfo.isConnected.fetch().then(updateConnectionStatus)
70+
}
71+
72+
export function init(store: {dispatch: any}) {
73+
homescreen(store)
74+
sisLoginCredentials(store)
75+
checkSisLogin(store)
76+
validateOlafCredentials(store)
77+
loadBalances(store)
78+
loadMeals(store)
79+
loadCourses(store)
80+
netInfoIsConnected(store)
81+
}

flux/parts/app.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @flow
3+
* Reducer for app-wide miscallaneous state
4+
*/
5+
6+
export const PUSH_VIEW = 'app/PUSH_VIEW'
7+
export const POP_VIEW = 'app/POP_VIEW'
8+
export const ONLINE_STATUS = 'app/ONLINE_STATUS'
9+
10+
11+
export function updateOnlineStatus(status: boolean) {
12+
return {type: ONLINE_STATUS, payload: status}
13+
}
14+
15+
16+
const initialAppState = {
17+
currentView: null,
18+
viewStack: [],
19+
isConnected: false,
20+
}
21+
22+
export function app(state: Object=initialAppState, action: Object) {
23+
const {type, payload} = action
24+
25+
switch (type) {
26+
case PUSH_VIEW:
27+
return {...state, viewStack: [...state.viewStack, payload]}
28+
case POP_VIEW:
29+
return {...state, viewStack: state.viewStack.slice(0, -1)}
30+
31+
case ONLINE_STATUS:
32+
return {...state, isConnected: payload}
33+
34+
default:
35+
return state
36+
}
37+
}

flux/parts/homescreen.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @flow
3+
* Reducer for state for the homescreen
4+
*/
5+
6+
import {allViewNames as defaultViewOrder} from '../../views/views'
7+
import difference from 'lodash/difference'
8+
import {trackHomescreenOrder} from '../../analytics'
9+
import * as storage from '../../lib/storage'
10+
11+
12+
export const SAVE_HOMESCREEN_ORDER = 'SAVE_HOMESCREEN_ORDER'
13+
14+
15+
export const updateViewOrder = (currentOrder: string[], defaultOrder: string[]=defaultViewOrder) => {
16+
currentOrder = currentOrder || []
17+
18+
// lodash/difference: Creates an array of array values _not included_ in the
19+
// other given arrays.
20+
21+
// In case new screens have been added, get a list of the new screens
22+
let addedScreens = difference(defaultOrder, currentOrder)
23+
// check for removed screens
24+
let removedScreens = difference(currentOrder, defaultOrder)
25+
26+
// add the new screens to the list
27+
currentOrder = currentOrder.concat(addedScreens)
28+
29+
// now we remove the screens that were removed
30+
currentOrder = difference(currentOrder, removedScreens)
31+
32+
return currentOrder
33+
}
34+
35+
export const loadHomescreenOrder = async () => {
36+
// get the saved list from persistent storage
37+
let savedOrder = await storage.getHomescreenOrder()
38+
39+
// update the order, in case new views have been added/removed
40+
let order = updateViewOrder(savedOrder, defaultViewOrder)
41+
42+
// return an action to save it to persistent storage
43+
return saveHomescreenOrder(order, {noTrack: true})
44+
}
45+
46+
export const saveHomescreenOrder = (order: string[], options: {noTrack?: boolean}={}) => {
47+
options.noTrack || trackHomescreenOrder(order)
48+
storage.setHomescreenOrder(order)
49+
return {type: SAVE_HOMESCREEN_ORDER, payload: order}
50+
}
51+
52+
53+
const initialHomescreenState = {
54+
order: [],
55+
}
56+
export function homescreen(state: Object=initialHomescreenState, action: Object) {
57+
const {type, payload} = action
58+
59+
switch (type) {
60+
case SAVE_HOMESCREEN_ORDER: {
61+
return {...state, order: payload}
62+
}
63+
default:
64+
return state
65+
}
66+
}

0 commit comments

Comments
 (0)