Skip to content

Commit

Permalink
Merge pull request #1188 from JGreenlee/fixes-oct-2024
Browse files Browse the repository at this point in the history
🐛 Fixes for onboarding initiation if already logged in, UI loading in the background
  • Loading branch information
shankari authored Oct 26, 2024
2 parents ccc7aef + 8971f05 commit cfa709f
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 28 deletions.
3 changes: 2 additions & 1 deletion www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@
"dont-force-kill": "Do not force kill the app",
"background-restrictions": "On Samsung and Huwaei phones, make sure that background restrictions are turned off",
"close": "Close",
"proceeding-with-token": "Proceeding with OPcode: {{token}}"
"proceeding-with-token": "Proceeding with OPcode: {{token}}",
"already-logged-in": "You are already logged in with OPcode {{token}}. If you wish to use a different OPcode, please log out first."
},
"config": {
"unable-read-saved-config": "Unable to read saved config",
Expand Down
44 changes: 37 additions & 7 deletions www/js/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import AlertBar from './components/AlertBar';
import Main from './Main';
import { joinWithTokenOrUrl } from './config/dynamicConfig';
import { addStatReading } from './plugin/clientStats';
import useAppState from './useAppState';
import { displayErrorMsg, logDebug } from './plugin/logger';
import i18next from 'i18next';

export const AppContext = createContext<any>({});
const CUSTOM_LABEL_KEYS_IN_DATABASE = ['mode', 'purpose'];
Expand All @@ -34,21 +37,33 @@ const App = () => {
const appConfig = useAppConfig();
const permissionStatus = usePermissionStatus();

const refreshOnboardingState = () => getPendingOnboardingState().then(setOnboardingState);
const refreshOnboardingState = () =>
getPendingOnboardingState().then((state) => {
setOnboardingState(state);
return state;
});

useEffect(() => {
refreshOnboardingState();
}, []);

// handleOpenURL function must be provided globally for cordova-plugin-customurlscheme
// https://www.npmjs.com/package/cordova-plugin-customurlscheme
window['handleOpenURL'] = async (url: string, joinMethod: OnboardingJoinMethod = 'external') => {
const configUpdated = await joinWithTokenOrUrl(url);
async function handleTokenOrUrl(tokenOrUrl: string, joinMethod: OnboardingJoinMethod) {
const onboardingState = await refreshOnboardingState();
logDebug(`handleTokenOrUrl: onboardingState = ${JSON.stringify(onboardingState)}`);
if (onboardingState.route > OnboardingRoute.WELCOME) {
displayErrorMsg(i18next.t('join.already-logged-in', { token: onboardingState.opcode }));
return;
}
const configUpdated = await joinWithTokenOrUrl(tokenOrUrl);
addStatReading('onboard', { configUpdated, joinMethod });
if (configUpdated) {
refreshOnboardingState();
}
return configUpdated;
};
}
// handleOpenURL function must be provided globally for cordova-plugin-customurlscheme
// https://www.npmjs.com/package/cordova-plugin-customurlscheme
window['handleOpenURL'] = (url: string) => handleTokenOrUrl(url, 'external');

useEffect(() => {
if (!appConfig) return;
Expand All @@ -61,9 +76,24 @@ const App = () => {
// getUserCustomLabels(CUSTOM_LABEL_KEYS_IN_DATABASE).then((res) => setCustomLabelMap(res));
}, [appConfig]);

const appState = useAppState({});
if (appState != 'active') {
// Render nothing if the app state is not 'active'.
// On iOS, the UI can run if the app is launched by the OS in response to a notification,
// in which case the appState will be 'background'. In this case, we definitely do not want
// to load the UI because it is not visible.
// On Android, the UI can only be initiated by the user - but even so, the user can send it to
// the background and we don't need the UI to stay active.
// In the future, we may want to persist some UI states when the app is sent to the background;
// i.e. the user opens the app, navigates away, and back again.
// But currently, we're relying on a 'fresh' UI every time the app goes to 'active' state.
logDebug(`App: appState = ${appState}; returning null`);
return null;
}

const appContextValue = {
appConfig,
handleOpenURL: window['handleOpenURL'],
handleTokenOrUrl,
onboardingState,
setOnboardingState,
refreshOnboardingState,
Expand Down
2 changes: 0 additions & 2 deletions www/js/TimelineContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { getPipelineRangeTs } from './services/commHelper';
import { getNotDeletedCandidates, mapInputsToTimelineEntries } from './survey/inputMatcher';
import { EnketoUserInputEntry } from './survey/enketo/enketoHelper';
import { primarySectionForTrip } from './diary/diaryHelper';
import useAppStateChange from './useAppStateChange';
import { isoDateRangeToTsRange, isoDateWithOffset } from './datetimeUtil';
import { base_modes } from 'e-mission-common';

Expand Down Expand Up @@ -55,7 +54,6 @@ type ContextProps = {
export const useTimelineContext = (): ContextProps => {
const { t } = useTranslation();
const appConfig = useAppConfig();
useAppStateChange(() => refreshTimeline());

const [labelOptions, setLabelOptions] = useState<LabelOptions | null>(null);
// timestamp range that has been processed by the pipeline on the server
Expand Down
8 changes: 4 additions & 4 deletions www/js/onboarding/WelcomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const WelcomePage = () => {
const { t } = useTranslation();
const { colors } = useTheme();
const { width: windowWidth } = useWindowDimensions();
const { handleOpenURL } = useContext(AppContext);
const { handleTokenOrUrl } = useContext(AppContext);
const [pasteModalVis, setPasteModalVis] = useState(false);
const [infoPopupVis, setInfoPopupVis] = useState(false);
const [existingToken, setExistingToken] = useState('');
Expand All @@ -52,7 +52,7 @@ const WelcomePage = () => {
AlertManager.addMessage({ text: 'No QR code found in scan. Please try again.' });
return;
}
handleOpenURL(result.text, 'scan');
handleTokenOrUrl(result.text, 'scan');
},
(error) => {
barcodeScannerIsOpen = false;
Expand All @@ -68,7 +68,7 @@ const WelcomePage = () => {
if (!clipboardContent?.startsWith('nrelop_') && !clipboardContent?.includes('://')) {
throw new Error('Clipboard content is not a valid token or URL');
}
handleOpenURL(clipboardContent, 'paste');
handleTokenOrUrl(clipboardContent, 'paste');
} catch (e) {
logWarn(`Tried using clipboard content ${clipboardContent}: ${e}`);
setPasteModalVis(true);
Expand Down Expand Up @@ -141,7 +141,7 @@ const WelcomePage = () => {
<Button onPress={() => setPasteModalVis(false)}>{t('login.button-decline')}</Button>
<Button
onPress={() =>
handleOpenURL(existingToken, 'textbox').catch((e) =>
handleTokenOrUrl(existingToken, 'textbox').catch((e) =>
displayError(e, `Tried using token ${existingToken}`),
)
}>
Expand Down
24 changes: 14 additions & 10 deletions www/js/useAppStateChange.ts → www/js/useAppState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@
//the executes "onResume" function that is passed in
//https://reactnative.dev/docs/appstate based on react's example of detecting becoming active

import { useEffect, useRef } from 'react';
import { useEffect, useState } from 'react';
import { AppState } from 'react-native';
import { addStatReading } from './plugin/clientStats';

const useAppStateChange = (onResume) => {
const appState = useRef(AppState.currentState);
type Props = {
onResume?: () => void;
onChange?: (nextAppState: string) => void;
};
const useAppState = ({ onResume, onChange }: Props) => {
const [appState, setAppState] = useState(AppState.currentState);

useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (appState.current != 'active' && nextAppState === 'active') {
onResume();
addStatReading('app_state_change', nextAppState);
onChange?.(nextAppState);
if (nextAppState == 'active' && appState != 'active') {
onResume?.();
}

appState.current = nextAppState;
addStatReading('app_state_change', appState.current);
setAppState(nextAppState);
});
return () => subscription.remove();
}, []);

return {};
return appState;
};

export default useAppStateChange;
export default useAppState;
10 changes: 6 additions & 4 deletions www/js/usePermissionStatus.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState, useMemo } from 'react';
import useAppStateChange from './useAppStateChange';
import useAppState from './useAppState';
import useAppConfig from './useAppConfig';
import { useTranslation } from 'react-i18next';
import { useAppTheme } from './appTheme';
Expand Down Expand Up @@ -358,9 +358,11 @@ const usePermissionStatus = () => {
refreshAllChecks(checkList);
}

useAppStateChange(() => {
logDebug('PERMISSION CHECK: app has resumed, should refresh');
refreshAllChecks(checkList);
useAppState({
onResume: () => {
logDebug('PERMISSION CHECK: app has resumed, should refresh');
refreshAllChecks(checkList);
},
});

//load when ready
Expand Down

0 comments on commit cfa709f

Please sign in to comment.