diff --git a/app/client/mobile/src/LocalStore.ts b/app/client/mobile/src/LocalStore.ts new file mode 100644 index 00000000..7893c2cf --- /dev/null +++ b/app/client/mobile/src/LocalStore.ts @@ -0,0 +1,58 @@ +import {SqlStore} from 'databag-client-sdk'; +import SQLite from 'react-native-sqlite-storage'; + +export class LocalStore implements SqlStore { + private db: any = null; + + constructor() { + SQLite.DEBUG(false); + SQLite.enablePromise(true); + } + + public async open(path: string) { + this.db = await SQLite.openDatabase({name: path, location: 'default'}); + await this.localStoreSet("CREATE TABLE IF NOT EXISTS local_store (key text, value text, unique(key));"); + } + + public async get(key: string, value: string, unset: string): Promise { + try { + const rows = await this.localStoreGet(`SELECT * FROM local_store WHERE key='${key}';`); + if (rows.length == 1 && rows[0].value != null) { + return rows[0].value; + } + } + catch(err) { + console.log(err); + } + return unset; + } + + public async set(key: string, value: string): Promise { + await this.localStoreSet('INSERT OR REPLACE INTO local_store (key, value) values (?, ?)', [key, value]); + } + + public async clear(key: string): Promise { + await this.localStoreSet('INSERT OR REPLACE INTO local_store (key, value) values (?, null)', [key]); + } + + private async localStoreSet( + stmt: string, + params: (string | number | null)[], + ): Promise { + await this.db.executeSql(stmt, params); + } + + private async localStoreGet( + stmt: string, + params: (string | number | null)[], + ): Promise { + const res = await this.db.executeSql(stmt, params); + const rows = []; + if (res[0] && res[0].rows && res[0].rows.length > 0) { + for (let i = 0; i < res[0].rows.length; i++) { + rows.push(res[0].rows.item(i)); + } + } + return rows; + } +} diff --git a/app/client/mobile/src/constants/Colors.ts b/app/client/mobile/src/constants/Colors.ts index 3f737c8c..7e9c4a4d 100644 --- a/app/client/mobile/src/constants/Colors.ts +++ b/app/client/mobile/src/constants/Colors.ts @@ -1,4 +1,5 @@ export const Colors = { primary: '#66aa88', + danger: '#ff8888', } diff --git a/app/client/mobile/src/constants/Strings.ts b/app/client/mobile/src/constants/Strings.ts index 1315866a..77c1834a 100644 --- a/app/client/mobile/src/constants/Strings.ts +++ b/app/client/mobile/src/constants/Strings.ts @@ -25,6 +25,8 @@ const Strings = [ messaging: 'Messaging', timeFull: '24h', timeHalf: '12h', + timeFormat: 'Time Format', + dateFormat: 'Date Format', monthStart: 'mm/dd', monthEnd: 'dd/mm', error: 'Error', @@ -238,6 +240,8 @@ const Strings = [ messaging: 'Messagerie', timeFull: '24h', timeHalf: '12h', + timeFormat: "Format de l'heure", + dateFormat: 'Format de la date', monthStart: 'mm/jj', monthEnd: 'jj/mm', error: 'Erreur', @@ -451,6 +455,8 @@ const Strings = [ messaging: 'Mensajería', timeFull: '24h', timeHalf: '12h', + timeFormat: 'Formato de Hora', + dateFormat: 'Formato de Fecha', monthStart: 'mm/dd', monthEnd: 'dd/mm', error: 'Error', @@ -661,6 +667,8 @@ const Strings = [ display: 'Format', timeFull: '24h', timeHalf: '12h', + timeFormat: 'Zeitformat', + dateFormat: 'Datumsformat', monthStart: 'mm/dd', monthEnd: 'dd/mm', error: 'Fehler', @@ -874,6 +882,8 @@ const Strings = [ messaging: 'Mensagens', timeFull: '24h', timeHalf: '12h', + timeFormat: 'Formato de hora', + dateFormat: 'Formato de data', monthStart: 'mm/dd', monthEnd: 'dd/mm', error: 'Erro', @@ -1071,6 +1081,8 @@ const Strings = [ messaging: 'Обмен сообщениями', timeFull: '24 часа', timeHalf: '12 часов', + timeFormat: 'Формат времени', + dateFormat: 'Формат даты', monthStart: 'мм/дд', monthEnd: 'дд/мм', error: 'Ошибка', diff --git a/app/client/mobile/src/context/useAppContext.hook.ts b/app/client/mobile/src/context/useAppContext.hook.ts index 8183a8a6..337d2344 100644 --- a/app/client/mobile/src/context/useAppContext.hook.ts +++ b/app/client/mobile/src/context/useAppContext.hook.ts @@ -1,12 +1,17 @@ import {useState, useEffect, useRef} from 'react'; import {DatabagSDK, Session} from 'databag-client-sdk'; import {SessionStore} from '../SessionStore'; +import {LocalStore} from '../LocalStore'; const DATABAG_DB = 'db_v202.db'; +const SETTINGS_DB = 'ls_v001.db'; export function useAppContext() { + const local = useRef(new LocalStore()); const sdk = useRef(new DatabagSDK(null)); const [state, setState] = useState({ session: null as null | Session, + fullDayTime: false, + monthFirstDate: true, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -15,11 +20,15 @@ export function useAppContext() { }; const setup = async () => { + await local.current.open(SETTINGS_DB); + const fullDayTime = await local.current.get('time_format', '12h') === '24h'; + const monthFirstDate = await local.current.get('date_format', 'month_first') === 'month_first'; + const store = new SessionStore(); await store.open(DATABAG_DB); const session: Session | null = await sdk.current.initOfflineStore(store); if (session) { - updateState({session}); + updateState({session, fullDayTime, monthFirstDate}); } }; @@ -28,6 +37,14 @@ export function useAppContext() { }, []); const actions = { + setMonthFirstDate: async (monthFirstDate: boolean) => { + updateState({ monthFirstDate }); + await local.current.set('date_format', monthFirstDate ? 'month_first' : 'day_first'); + }, + setFullDayTime: async (fullDayTime: boolean) => { + updateState({ fullDayTime }); + await local.current.set('time_format', fullDayTime ? '24h' : '12h'); + }, accountLogin: async ( username: string, password: string, diff --git a/app/client/mobile/src/settings/Settings.styled.ts b/app/client/mobile/src/settings/Settings.styled.ts index 4881a5cc..a227e92b 100644 --- a/app/client/mobile/src/settings/Settings.styled.ts +++ b/app/client/mobile/src/settings/Settings.styled.ts @@ -98,18 +98,19 @@ export const styles = StyleSheet.create({ borderColor: Colors.primary, }, editDetails: { + borderBottomLeftRadius: 8, + overflow: 'hidden', position: 'absolute', - bottom: -11, - right: 12, - zIndex: 3, + top: 0, + right: 16, }, editDetailsLabel: { fontSize: 16, color: Colors.primary, paddingLeft: 8, paddingRight: 8, - paddingTop: 8, - paddingBottom: 8, + paddingTop: 4, + paddingBottom: 4, }, editBar: { position: 'absolute', @@ -140,15 +141,15 @@ export const styles = StyleSheet.create({ paddingLeft: 16, paddingRight: 16, position: 'relative', - height: 32, display: 'flex', justifyContent: 'center', + paddingTop: 12, }, nameSet: { fontSize: 24, width: '100%', paddingLeft: 32, - paddingRight: 32, + paddingRight: 72, }, nameUnset: { fontSize: 24, @@ -162,19 +163,25 @@ export const styles = StyleSheet.create({ flexDirection: 'column', gap: 8, width: '100%', + paddingTop: 12, }, attribute: { display: 'flex', flexDirection: 'row', - alignItem: 'center', + alignItems: 'center', justifyContent: 'center', paddingLeft: 32, - paddingRight: 32, + paddingRight: 8, }, icon: { + flexShrink: 0, width: 32, display: 'flex', - justifyContent: 'center', + justifyContent: 'flex-begin', + height: '100%', + }, + label: { + fontSize: 16, }, labelUnset: { fontSize: 16, @@ -185,8 +192,43 @@ export const styles = StyleSheet.create({ fontSize: 16, flexGrow: 1, }, + control: { + flexGrow: 1, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + }, + radioControl: { + flexGrow: 1, + }, + radioButtons: { + display: 'flex', + flexDirection: 'row', + }, + controlIcon: { + flexShrink: 0, + width: 32, + }, + controlLabel: { + fontSize: 16, + color: Colors.primary, + }, + dangerLabel: { + fontSize: 16, + color: Colors.danger, + }, + controlSwitch: { + transform: [ + { scaleX: 0.7 }, + {scaleY: 0.7 }, + ] + }, input: { width: '100%', marginTop: 16, }, + option: { + fontSize: 16, + paddingLeft: 24, + }, }) diff --git a/app/client/mobile/src/settings/Settings.tsx b/app/client/mobile/src/settings/Settings.tsx index 3165c112..bce16f34 100644 --- a/app/client/mobile/src/settings/Settings.tsx +++ b/app/client/mobile/src/settings/Settings.tsx @@ -1,5 +1,5 @@ import React, {useState, useContext} from 'react'; -import {Surface, Button, Text, IconButton, Divider, Icon, TextInput} from 'react-native-paper'; +import {Surface, Button, Text, IconButton, Divider, Icon, TextInput, RadioButton, Switch} from 'react-native-paper'; import {SafeAreaView, TouchableOpacity, Modal, View, Image, ScrollView} from 'react-native'; import {styles} from './Settings.styled'; import {useSettings} from './useSettings.hook'; @@ -12,13 +12,16 @@ export function Settings() { const [alert, setAlert] = useState(false); const [details, setDetails] = useState(false); const [savingDetails, setSavingDetails] = useState(false); + const [savingRegistry, setSavingRegistry] = useState(false); + const [savingNotifications, setSavingNotifications] = useState(false); + +console.log("PREF: ", state.fullDayTime, state.monthFirstDate); const selectImage = async () => { try { - const full = await ImagePicker.openPicker({ mediaType: 'photo', width: 256, height: 256 }); - const crop = await ImagePicker.openCropper({ path: full.path, width: 256, height: 256, cropperCircleOverlay: true, includeBase64: true }); + const img = await ImagePicker.openPicker({ mediaType: 'photo', width: 256, height: 256, cropping: true, cropperCircleOverlay: true, includeBase64: true }); try { - await actions.setProfileImage(crop.data); + await actions.setProfileImage(img.data); } catch (err) { console.log(err); @@ -46,6 +49,42 @@ export function Settings() { } } + const setRegistry = async (flag: boolean) => { + if (!savingRegistry) { + setSavingRegistry(true); + try { + if (flag) { + await actions.enableRegistry(); + } else { + await actions.disableRegistry(); + } + } + catch (err) { + console.log(err); + setAlert(true); + } + setSavingRegistry(false); + } + } + + const setNotifications = async (flag: boolean) => { + if (!savingNotifications) { + setSavingNotifications(true); + try { + if (flag) { + await actions.enableNotifications(); + } else { + await actions.disableNotifications(); + } + } + catch (err) { + console.log(err); + setAlert(true); + } + setSavingNotifications(false); + } + } + return ( <> @@ -69,9 +108,6 @@ export function Settings() { - setDetails(true)}> - {state.strings.edit} - @@ -103,15 +139,127 @@ export function Settings() { {state.profile.description} )} + setDetails(true)}> + + {state.strings.edit} + + + + + + + + + setRegistry(!state.config.searchable)}> + {state.strings.visibleRegistry} + + + + + + + + + + manageSeal}> + {state.strings.manageTopics} + + + + + + + + + setRegistry(!state.config.pushEnabled)}> + {state.strings.enableNotifications} + + + + + - + + + + + + + + + + setRegistry(!state.config.searchable)}> + {state.strings.mfaTitle} + + + + + + + + + + manageSeal}> + {state.strings.changeLogin} + + + + + + + + + manageSeal}> + {state.strings.logout} + + + + + + + + + manageSeal}> + {state.strings.deleteAccount} + + + + + + + + + + + + + + + {state.strings.timeFormat}: + + {actions.setFullDayTime(false)}} /> + {actions.setFullDayTime(true)}} /> + + + + + + + + + {state.strings.dateFormat}: + + {actions.setMonthFirstDate(true)}} /> + {actions.setMonthFirstDate(false)}} /> + + + + { + const { fullDayTime, monthFirstDate } = app.state; + updateState({ fullDayTime, monthFirstDate }); + }, [app.state.fullDayTime, app.state.monthFirstDate]); + useEffect(() => { const { strings, @@ -229,6 +234,17 @@ export function useSettings() { setSealConfirm: (sealConfirm: string) => { updateState({ sealConfirm }); }, + setFullDayTime: async (flag: boolean) => { + try { + await app.actions.setFullDayTime(flag); + } + catch (err) { + console.log(err); + } + }, + setMonthFirstDate: async (flag: boolean) => { + await app.actions.setMonthFirstDate(flag); + }, } return { state, actions }