Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deault label options #1055

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 35 additions & 23 deletions www/js/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState, createContext, useMemo } from 'react';
import { getAngularService } from './angular-react-helper';
import { BottomNavigation, Button, useTheme } from 'react-native-paper';
import { ActivityIndicator, BottomNavigation, useTheme } from 'react-native-paper';
import { useTranslation } from 'react-i18next';
import LabelTab from './diary/LabelTab';
import MetricsTab from './metrics/MetricsTab';
Expand All @@ -22,7 +22,8 @@ export const AppContext = createContext<any>({});
const App = () => {

const [index, setIndex] = useState(0);
const [pendingOnboardingState, setPendingOnboardingState] = useState<OnboardingState>(null);
// will remain null while the onboarding state is still being determined
const [onboardingState, setOnboardingState] = useState<OnboardingState|null>(null);
const [permissionsPopupVis, setPermissionsPopupVis] = useState(false);
const appConfig = useAppConfig();
const { colors } = useTheme();
Expand All @@ -41,7 +42,7 @@ const App = () => {
control: ProfileSettings,
});

const refreshOnboardingState = () => getPendingOnboardingState().then(setPendingOnboardingState);
const refreshOnboardingState = () => getPendingOnboardingState().then(setOnboardingState);
useEffect(() => { refreshOnboardingState() }, []);

useEffect(() => {
Expand All @@ -53,32 +54,43 @@ const App = () => {

const appContextValue = {
appConfig,
pendingOnboardingState, setPendingOnboardingState, refreshOnboardingState,
onboardingState, setOnboardingState, refreshOnboardingState,
permissionsPopupVis, setPermissionsPopupVis,
}

console.debug('pendingOnboardingState in App', pendingOnboardingState);
console.debug('onboardingState in App', onboardingState);

let appContent;
if (onboardingState == null) {
// if onboarding state is not yet determined, show a loading spinner
appContent = <ActivityIndicator size={'large'} style={{ flex: 1 }} />
} else if (onboardingState?.route == OnboardingRoute.DONE) {
// if onboarding route is DONE, show the main app with navigation between tabs
appContent = (
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
// Place at bottom, color of 'surface' (white) by default, and 68px tall (default was 80)
safeAreaInsets={{ bottom: 0 }}
style={{ backgroundColor: colors.surface }}
barStyle={{ height: 68, justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0)' }}
// BottomNavigation uses secondaryContainer color for the background, but we want primaryContainer
// (light blue), so we override here.
theme={{ colors: { secondaryContainer: colors.primaryContainer } }} />
);
} else {
// if there is an onboarding route that is not DONE, show the onboarding stack
appContent = <OnboardingStack />
}

return (<>
<AppContext.Provider value={appContextValue}>
{pendingOnboardingState == null ?
<BottomNavigation
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={renderScene}
// Place at bottom, color of 'surface' (white) by default, and 68px tall (default was 80)
safeAreaInsets={{ bottom: 0 }}
style={{ backgroundColor: colors.surface }}
barStyle={{ height: 68, justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0)' }}
// BottomNavigation uses secondaryContainer color for the background, but we want primaryContainer
// (light blue), so we override here.
theme={{ colors: { secondaryContainer: colors.primaryContainer } }} />
:
<OnboardingStack />
}
{ /* if onboarding is done (state == null), or if is in progress but we are past the
consent page (route > CONSENT), the permissions popup can show if needed */ }
{(pendingOnboardingState == null || pendingOnboardingState.route > OnboardingRoute.CONSENT) &&
{appContent}

{ /* If we are past the consent page (route > CONSENT), the permissions popup can show if needed.
This also includes if onboarding is DONE altogether (because "DONE" is > "CONSENT") */ }
{(onboardingState && onboardingState.route > OnboardingRoute.CONSENT) &&
<AppStatusModal permitVis={permissionsPopupVis} setPermitVis={setPermissionsPopupVis} />
}
</AppContext.Provider>
Expand Down
16 changes: 8 additions & 8 deletions www/js/onboarding/OnboardingStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import { displayErrorMsg } from "../plugin/logger";

const OnboardingStack = () => {

const { pendingOnboardingState } = useContext(AppContext);
const { onboardingState } = useContext(AppContext);

console.debug('pendingOnboardingState in OnboardingStack', pendingOnboardingState);
console.debug('onboardingState in OnboardingStack', onboardingState);

if (pendingOnboardingState.route == OnboardingRoute.WELCOME) {
if (onboardingState.route == OnboardingRoute.WELCOME) {
return <WelcomePage />;
} else if (pendingOnboardingState.route == OnboardingRoute.SUMMARY) {
} else if (onboardingState.route == OnboardingRoute.SUMMARY) {
return <SummaryPage />;
} else if (pendingOnboardingState.route == OnboardingRoute.CONSENT) {
} else if (onboardingState.route == OnboardingRoute.CONSENT) {
return <ConsentPage />;
} else if (pendingOnboardingState.route == OnboardingRoute.SAVE_QR) {
} else if (onboardingState.route == OnboardingRoute.SAVE_QR) {
return <SaveQrPage />;
} else if (pendingOnboardingState.route == OnboardingRoute.SURVEY) {
} else if (onboardingState.route == OnboardingRoute.SURVEY) {
return <SurveyPage />;
} else {
displayErrorMsg('OnboardingStack: unknown route', pendingOnboardingState.route);
displayErrorMsg('OnboardingStack: unknown route', onboardingState.route);
}
}

Expand Down
10 changes: 5 additions & 5 deletions www/js/onboarding/SaveQrPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { preloadDemoSurveyResponse } from "./SurveyPage";
const SaveQrPage = ({ }) => {

const { t } = useTranslation();
const { pendingOnboardingState, refreshOnboardingState } = useContext(AppContext);
const { onboardingState, refreshOnboardingState } = useContext(AppContext);
const { overallStatus } = usePermissionStatus();

useEffect(() => {
if (overallStatus == true && !registerUserDone) {
logDebug('permissions done, going to log in');
login(pendingOnboardingState.opcode).then((response) => {
login(onboardingState.opcode).then((response) => {
logDebug('login done, refreshing onboarding state');
setRegisterUserDone(true);
preloadDemoSurveyResponse();
Expand Down Expand Up @@ -63,13 +63,13 @@ const SaveQrPage = ({ }) => {
</Text>
</View>
<View style={[onboardingStyles.pageSection, {paddingHorizontal: 20}]}>
<QrCode value={pendingOnboardingState.opcode} style={{marginHorizontal: 8}} />
<QrCode value={onboardingState.opcode} style={{marginHorizontal: 8}} />
<Text style={s.opcodeText}>
{pendingOnboardingState.opcode}
{onboardingState.opcode}
</Text>
</View>
<View style={onboardingStyles.buttonRow}>
<Button mode='contained' icon='share' onPress={() => shareQR(pendingOnboardingState.opcode)}>
<Button mode='contained' icon='share' onPress={() => shareQR(onboardingState.opcode)}>
{t('login.save')}
</Button>
<Button mode='outlined' icon='chevron-right' onPress={onFinish}>
Expand Down
6 changes: 3 additions & 3 deletions www/js/onboarding/StudySummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ const StudySummary = () => {
const styles = StyleSheet.create({
title: {
fontWeight: "bold",
fontSize: 22,
fontSize: 24,
paddingBottom: 10,
textAlign: "center"
},
text: {
fontSize: 14,
fontSize: 15,
},
studyName: {
fontWeight: "bold",
fontSize: 16
fontSize: 17,
},
});

Expand Down
6 changes: 4 additions & 2 deletions www/js/onboarding/SummaryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ const SummaryPage = () => {
// summary of the study, followed by 'next' button
return (<>
<ScrollView contentContainerStyle={{flex: 1}}>
<Surface style={[onboardingStyles.page, {flex:1, gap: 16}]}>
<StudySummary />
<Surface style={[onboardingStyles.page, {flex: 1, padding: 32}]}>
<View style={{ gap: 40, margin: 'auto' }}>
<StudySummary />
</View>
<View style={[onboardingStyles.buttonRow, {marginTop: 'auto'}]}>
<Button mode='contained' onPress={next}> {t('intro.proceed')} </Button>
</View>
Expand Down
11 changes: 6 additions & 5 deletions www/js/onboarding/onboardingHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { getConfig } from "../config/dynamicConfig";

export const INTRO_DONE_KEY = 'intro_done';

// state = null if onboarding is done
// route = WELCOME if no config present
// route = SUMMARY if config present, but not consented and summary not done
// route = CONSENT if config present, but not consented and summary done
// route = SAVE_QR if config present, consented, but save qr not done
// route = SURVEY if config present, consented and save qr done
export enum OnboardingRoute { WELCOME, SUMMARY, CONSENT, SAVE_QR, SURVEY, NONE };
// route = DONE if onboarding is finished (intro_done marked)
export enum OnboardingRoute { WELCOME, SUMMARY, CONSENT, SAVE_QR, SURVEY, DONE };
export type OnboardingState = {
opcode: string,
route: OnboardingRoute,
Expand All @@ -27,9 +27,10 @@ export const setRegisterUserDone = (b) => registerUserDone = b;

export function getPendingOnboardingState(): Promise<OnboardingState> {
return Promise.all([getConfig(), readConsented(), readIntroDone()]).then(([config, isConsented, isIntroDone]) => {
if (isIntroDone) return null; // onboarding is done; no pending state
let route: OnboardingRoute = OnboardingRoute.NONE;
if (!config) {
let route: OnboardingRoute;
if (isIntroDone) {
route = OnboardingRoute.DONE;
} else if (!config) {
route = OnboardingRoute.WELCOME;
} else if (!isConsented && !summaryDone) {
route = OnboardingRoute.SUMMARY;
Expand Down
39 changes: 17 additions & 22 deletions www/js/survey/multilabel/confirmHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,24 @@ export async function getLabelOptions(appConfigParam?) {
if (appConfig.label_options) {
const labelOptionsJson = await fetchUrlCached(appConfig.label_options);
labelOptions = JSON.parse(labelOptionsJson) as LabelOptions;
/* fill in the translations to the 'text' fields of the labelOptions,
according to the current language */
const lang = i18next.language;
for (const opt in labelOptions) {
labelOptions[opt]?.forEach?.((o, i) => {
const translationKey = o.value;
const translation = labelOptions.translations[lang][translationKey];
labelOptions[opt][i].text = translation;
});
}
} else {
// backwards compat: if dynamic config doesn't have label_options, use the old way
const i18nUtils = getAngularService("i18nUtils");
const optionFileName = await i18nUtils.geti18nFileName("json/", "trip_confirm_options", ".json");
try {
const optionJson = await fetch(optionFileName).then(r => r.json());
labelOptions = optionJson as LabelOptions;
} catch (e) {
logDebug("error "+JSON.stringify(e)+" while reading confirm options, reverting to defaults");
const optionJson = await fetch("json/trip_confirm_options.json.sample").then(r => r.json());
labelOptions = optionJson as LabelOptions;
}
} else { // if dynamic config doesn't have label_options, use default label options
const defaultLabelOptionsURL = 'json/label-options.json.sample';
logDebug("No label_options found in config, using default label options at " + defaultLabelOptionsURL);
const defaultLabelOptionsJson = await fetchUrlCached(defaultLabelOptionsURL);
labelOptions = JSON.parse(defaultLabelOptionsJson) as LabelOptions;
}

/* fill in the translations to the 'text' fields of the labelOptions,
according to the current language */
const lang = i18next.language;
for (const opt in labelOptions) {
labelOptions[opt]?.forEach?.((o, i) => {
const translationKey = o.value;
const translation = labelOptions.translations[lang][translationKey];
labelOptions[opt][i].text = translation;
});
}

return labelOptions;
}

Expand Down
124 changes: 124 additions & 0 deletions www/json/label-options.json.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"MODE": [
{"value":"walk", "baseMode":"WALKING", "met_equivalent":"WALKING", "kgCo2PerKm": 0},
{"value":"e-bike", "baseMode":"E_BIKE", "met": {"ALL": {"range": [0, -1], "mets": 4.9}}, "kgCo2PerKm": 0.00728},
{"value":"bike", "baseMode":"BICYCLING", "met_equivalent":"BICYCLING", "kgCo2PerKm": 0},
{"value":"bikeshare", "baseMode":"BICYCLING", "met_equivalent":"BICYCLING", "kgCo2PerKm": 0},
{"value":"scootershare", "baseMode":"E_SCOOTER", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.00894},
{"value":"drove_alone", "baseMode":"CAR", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.22031},
{"value":"shared_ride", "baseMode":"CAR", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.11015},
{"value":"e_car_drove_alone", "baseMode":"E_CAR", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.08216},
{"value":"e_car_shared_ride", "baseMode":"E_CAR", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.04108},
{"value":"moped", "baseMode":"MOPED", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.05555},
{"value":"taxi", "baseMode":"TAXI", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.30741},
{"value":"bus", "baseMode":"BUS", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.20727},
{"value":"train", "baseMode":"TRAIN", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.12256},
{"value":"free_shuttle", "baseMode":"BUS", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.20727},
{"value":"air", "baseMode":"AIR", "met_equivalent":"IN_VEHICLE", "kgCo2PerKm": 0.09975},
{"value":"not_a_trip", "baseMode":"UNKNOWN", "met_equivalent":"UNKNOWN", "kgCo2PerKm": 0},
{"value":"other", "baseMode":"OTHER", "met_equivalent":"UNKNOWN", "kgCo2PerKm": 0}
],
"PURPOSE": [
{"value":"home"},
{"value":"work"},
{"value":"at_work"},
{"value":"school"},
{"value":"transit_transfer"},
{"value":"shopping"},
{"value":"meal"},
{"value":"pick_drop_person"},
{"value":"pick_drop_item"},
{"value":"personal_med"},
{"value":"access_recreation"},
{"value":"exercise"},
{"value":"entertainment"},
{"value":"religious"},
{"value":"other"}
],
"REPLACED_MODE": [
{"value":"no_travel"},
{"value":"walk"},
{"value":"bike"},
{"value":"bikeshare"},
{"value":"scootershare"},
{"value":"drove_alone"},
{"value":"shared_ride"},
{"value":"e_car_drove_alone"},
{"value":"e_car_shared_ride"},
{"value":"taxi"},
{"value":"bus"},
{"value":"train"},
{"value":"free_shuttle"},
{"value":"other"}
],
"translations": {
"en": {
"walk": "Walk",
"e-bike": "E-bike",
"bike": "Regular Bike",
"bikeshare": "Bikeshare",
"scootershare": "Scooter share",
"drove_alone": "Gas Car Drove Alone",
"shared_ride": "Gas Car Shared Ride",
"e_car_drove_alone": "E-Car Drove Alone",
"e_car_shared_ride": "E-Car Shared Ride",
"moped": "Moped",
"taxi": "Taxi/Uber/Lyft",
"bus": "Bus",
"train": "Train",
"free_shuttle": "Free Shuttle",
"air": "Air",
"not_a_trip": "Not a trip",
"no_travel": "No travel",
"home": "Home",
"work": "To Work",
"at_work": "At Work",
"school": "School",
"transit_transfer": "Transit transfer",
"shopping": "Shopping",
"meal": "Meal",
"pick_drop_person": "Pick-up/ Drop off Person",
"pick_drop_item": "Pick-up/ Drop off Item",
"personal_med": "Personal/ Medical",
"access_recreation": "Access Recreation",
"exercise": "Recreation/ Exercise",
"entertainment": "Entertainment/ Social",
"religious": "Religious",
"other": "Other"
},
"es": {
"walk": "Caminando",
"e-bike": "e-bicicleta",
"bike": "Bicicleta",
"bikeshare": "Bicicleta compartida",
"scootershare": "Motoneta compartida",
"drove_alone": "Coche de Gas, Condujo solo",
"shared_ride": "Coche de Gas, Condujo con otros",
"e_car_drove_alone": "e-coche, Condujo solo",
"e_car_shared_ride": "e-coche, Condujo con ontras",
"moped": "Ciclomotor",
"taxi": "Taxi/Uber/Lyft",
"bus": "Autobús",
"train": "Tren",
"free_shuttle": "Colectivo gratuito",
"air": "Avión",
"not_a_trip": "No es un viaje",
"no_travel": "No viajar",
"home": "Inicio",
"work": "Trabajo",
"at_work": "En el trabajo",
"school": "Escuela",
"transit_transfer": "Transbordo",
"shopping": "Compras",
"meal": "Comida",
"pick_drop_person": "Recoger/ Entregar Individuo",
"pick_drop_item": "Recoger/ Entregar Objeto",
"personal_med": "Personal/ Médico",
"access_recreation": "Acceder a Recreación",
"exercise": "Recreación/ Ejercicio",
"entertainment": "Entretenimiento/ Social",
"religious": "Religioso",
"other": "Otros"
}
}
}
Loading