Skip to content

Commit

Permalink
Merge pull request #1120 from jiji14/customize-modes-step2
Browse files Browse the repository at this point in the history
Custom Labels Step 2
  • Loading branch information
shankari authored Jul 20, 2024
2 parents 696f032 + 52b0fb9 commit 90595e9
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 18 deletions.
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ module.exports = {
"!www/js/**/index.{ts,tsx,js,jsx}",
"!www/js/types/**/*.{ts,tsx,js,jsx}",
],
// several functions in commHelper do not have unit tests; see note in commHelper.test.ts
coveragePathIgnorePatterns: ['www/js/services/commHelper.ts'],
};
4 changes: 4 additions & 0 deletions www/__tests__/commHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ it('fetches text from a URL and caches it so the next call is faster', async ()
* - updateUser
* - getUser
* - putOne
* - getUserCustomLabels
* - insertUserCustomLabel
* - updateUserCustomLabel
* - deleteUserCustomLabel
*/
8 changes: 6 additions & 2 deletions www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"trip-confirm": {
"services-please-fill-in": "Please fill in the {{text}} not listed.",
"services-cancel": "Cancel",
"services-save": "Save"
"services-save": "Save",
"custom-mode": "Custom Mode",
"custom-purpose": "Custom Purpose",
"custom-labels": "Custom Labels"
},

"control": {
Expand Down Expand Up @@ -52,7 +55,8 @@
"refresh-app-config": "Refresh App Configuration",
"current-version": "Current version: {{version}}",
"refreshing-app-config": "Refreshing app configuration, please wait...",
"already-up-to-date": "Already up to date!"
"already-up-to-date": "Already up to date!",
"manage-custom-labels": "Manage Custom Labels"
},

"general-settings": {
Expand Down
9 changes: 9 additions & 0 deletions www/js/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ import usePermissionStatus from './usePermissionStatus';
import { initPushNotify } from './splash/pushNotifySettings';
import { initStoreDeviceSettings } from './splash/storeDeviceSettings';
import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler';
import { getUserCustomLabels } from './services/commHelper';
import { initCustomDatasetHelper } from './metrics/customMetricsHelper';
import AlertBar from './components/AlertBar';
import Main from './Main';

export const AppContext = createContext<any>({});
const CUSTOM_LABEL_KEYS_IN_DATABASE = ['mode', 'purpose'];
type CustomLabelMap = {
[k: string]: string[];
};

const App = () => {
// will remain null while the onboarding state is still being determined
const [onboardingState, setOnboardingState] = useState<OnboardingState | null>(null);
const [permissionsPopupVis, setPermissionsPopupVis] = useState(false);
const [customLabelMap, setCustomLabelMap] = useState<CustomLabelMap>({});
const appConfig = useAppConfig();
const permissionStatus = usePermissionStatus();

Expand All @@ -39,6 +45,7 @@ const App = () => {
initPushNotify();
initStoreDeviceSettings();
initRemoteNotifyHandler();
getUserCustomLabels(CUSTOM_LABEL_KEYS_IN_DATABASE).then((res) => setCustomLabelMap(res));
initCustomDatasetHelper(appConfig);
}, [appConfig]);

Expand All @@ -50,6 +57,8 @@ const App = () => {
permissionStatus,
permissionsPopupVis,
setPermissionsPopupVis,
customLabelMap,
setCustomLabelMap,
};

let appContent;
Expand Down
166 changes: 166 additions & 0 deletions www/js/control/CustomLabelSettingRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React, { useState, useContext } from 'react';
import SettingRow from './SettingRow';
import {
Modal,
View,
Text,
TouchableOpacity,
StyleSheet,
useWindowDimensions,
ScrollView,
} from 'react-native';
import { Icon, TextInput, Dialog, Button, useTheme, SegmentedButtons } from 'react-native-paper';
import { AppContext } from '../App';
import { useTranslation } from 'react-i18next';
import { deleteUserCustomLabel, insertUserCustomLabel } from '../services/commHelper';
import { displayErrorMsg, logDebug } from '../plugin/logger';
import { labelKeyToReadable, readableLabelToKey } from '../survey/multilabel/confirmHelper';

const CustomLabelSettingRow = () => {
const [isCustomLabelModalOpen, setIsCustomLabelModalOpen] = useState(false);
const { customLabelMap, setCustomLabelMap } = useContext(AppContext);
const [isAddLabelOn, setIsAddLabelOn] = useState(false);
const [text, setText] = useState('');
const [key, setKey] = useState('mode');

const { t } = useTranslation(); //this accesses the translations
const { colors } = useTheme(); // use this to get the theme colors instead of hardcoded #hex colors
const { height } = useWindowDimensions();

const labelKeysButton = [
{
value: 'mode',
label: t('diary.mode'),
},
{
value: 'purpose',
label: t('diary.purpose'),
},
];

const onDeleteLabel = async (label) => {
const processedLabel = readableLabelToKey(label);
try {
const res = await deleteUserCustomLabel(key, processedLabel);
if (res) {
setCustomLabelMap({
...customLabelMap,
[key]: res['label'],
});
logDebug(`Successfuly deleted custom ${key}, ${JSON.stringify(res)}`);
}
} catch (e) {
displayErrorMsg(e, 'Delete Mode Error');
}
};

const onSaveLabel = async () => {
const processedLabel = readableLabelToKey(text);
if (customLabelMap[key]?.length > 0 && customLabelMap[key].indexOf(processedLabel) > -1) {
return;
}
try {
const res = await insertUserCustomLabel(key, processedLabel);
if (res) {
setText('');
setCustomLabelMap({
...customLabelMap,
[key]: res['label'],
});
setIsAddLabelOn(false);
logDebug(`Successfuly inserted custom ${key}, ${JSON.stringify(res)}`);
}
} catch (e) {
displayErrorMsg(e, 'Create Mode Error');
}
};

return (
<>
<SettingRow
textKey="control.manage-custom-labels"
iconName="label-multiple"
action={() => setIsCustomLabelModalOpen(true)}></SettingRow>
<Modal
visible={isCustomLabelModalOpen}
onDismiss={() => setIsCustomLabelModalOpen(false)}
transparent={true}>
<Dialog visible={isCustomLabelModalOpen} onDismiss={() => setIsCustomLabelModalOpen(false)}>
<Dialog.Title>
<Text>{t('trip-confirm.custom-labels')}</Text>
<TouchableOpacity style={styles.plusIconWrapper} onPress={() => setIsAddLabelOn(true)}>
<Icon source="plus-circle" size={24} />
</TouchableOpacity>
</Dialog.Title>
<Dialog.Content>
<SegmentedButtons
style={{ marginBottom: 10 }}
value={key}
onValueChange={setKey}
buttons={labelKeysButton}
/>
{isAddLabelOn && (
<>
<TextInput
label={t('trip-confirm.services-please-fill-in', {
text: key,
})}
value={text}
onChangeText={setText}
maxLength={25}
style={{ marginTop: 10 }}
/>
<View style={styles.saveButtonWrapper}>
<Button onPress={() => setIsAddLabelOn(false)}>
{t('trip-confirm.services-cancel')}
</Button>
<Button onPress={onSaveLabel}>{t('trip-confirm.services-save')}</Button>
</View>
</>
)}
<ScrollView contentContainerStyle={{ height: height / 2 }}>
{customLabelMap[key]?.length > 0 &&
customLabelMap[key].map((label, idx) => {
return (
<View
key={label + idx}
style={[styles.itemWrapper, { borderBottomColor: colors.outline }]}>
<Text>{labelKeyToReadable(label)}</Text>
<TouchableOpacity onPress={() => onDeleteLabel(label)}>
<Icon source="trash-can" size={20} />
</TouchableOpacity>
</View>
);
})}
</ScrollView>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={() => setIsCustomLabelModalOpen(false)}>
{t('trip-confirm.services-cancel')}
</Button>
</Dialog.Actions>
</Dialog>
</Modal>
</>
);
};

const styles = StyleSheet.create({
itemWrapper: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 16,
borderBottomWidth: 1,
},
saveButtonWrapper: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
plusIconWrapper: {
position: 'absolute',
right: 0,
},
});

export default CustomLabelSettingRow;
2 changes: 2 additions & 0 deletions www/js/control/ProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { storageClear } from '../plugin/storage';
import { getAppVersion } from '../plugin/clientStats';
import { getConsentDocument } from '../splash/startprefs';
import { displayError, displayErrorMsg, logDebug, logWarn } from '../plugin/logger';
import CustomLabelSettingRow from './CustomLabelSettingRow';
import { fetchOPCode, getSettings } from '../services/controlHelper';
import {
updateScheduledNotifs,
Expand Down Expand Up @@ -425,6 +426,7 @@ const ProfileSettings = () => {
desc={authSettings.opcode}
descStyle={settingStyles.monoDesc}></SettingRow>
<DemographicsSettingRow></DemographicsSettingRow>
{appConfig?.survey_info?.['trip-labels'] == 'MULTILABEL' && <CustomLabelSettingRow />}
<SettingRow
textKey="control.view-privacy"
iconName="eye"
Expand Down
74 changes: 74 additions & 0 deletions www/js/services/commHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,77 @@ export function putOne(key, data) {
throw error;
});
}

export function getUserCustomLabels(keys) {
return new Promise<any>((rs, rj) => {
window['cordova'].plugins.BEMServerComm.postUserPersonalData(
'/customlabel/get',
'keys',
keys,
rs,
rj,
);
}).catch((error) => {
error = 'While getting labels, ' + error;
throw error;
});
}

export function insertUserCustomLabel(key, newLabel) {
const insertedLabel = {
key: key,
label: newLabel,
};
return new Promise((rs, rj) => {
window['cordova'].plugins.BEMServerComm.postUserPersonalData(
'/customlabel/insert',
'inserted_label',
insertedLabel,
rs,
rj,
);
}).catch((error) => {
error = `While inserting one ${key}, ${error}`;
throw error;
});
}

export function updateUserCustomLabel(key, oldLabel, newLabel, isNewLabelMustAdded) {
const updatedLabel = {
key: key,
old_label: oldLabel,
new_label: newLabel,
is_new_label_must_added: isNewLabelMustAdded,
};
return new Promise<any>((rs, rj) => {
window['cordova'].plugins.BEMServerComm.postUserPersonalData(
'/customlabel/update',
'updated_label',
updatedLabel,
rs,
rj,
);
}).catch((error) => {
error = `While updating one ${key}, ${error}`;
throw error;
});
}

export function deleteUserCustomLabel(key, newLabel) {
const deletedLabel = {
key: key,
label: newLabel,
};
return new Promise((rs, rj) => {
window['cordova'].plugins.BEMServerComm.postUserPersonalData(
'/customlabel/delete',
'deleted_label',
deletedLabel,
rs,
rj,
);
}).catch((error) => {
error = `While deleting one ${key}, ${error}`;
throw error;
});
}
Loading

0 comments on commit 90595e9

Please sign in to comment.