adding settings options

This commit is contained in:
balzack 2024-09-22 16:57:38 -07:00
parent cfa20fda43
commit d4b8fbc51c
7 changed files with 317 additions and 23 deletions

View File

@ -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<string> {
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<void> {
await this.localStoreSet('INSERT OR REPLACE INTO local_store (key, value) values (?, ?)', [key, value]);
}
public async clear(key: string): Promise<void> {
await this.localStoreSet('INSERT OR REPLACE INTO local_store (key, value) values (?, null)', [key]);
}
private async localStoreSet(
stmt: string,
params: (string | number | null)[],
): Promise<void> {
await this.db.executeSql(stmt, params);
}
private async localStoreGet(
stmt: string,
params: (string | number | null)[],
): Promise<any[]> {
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;
}
}

View File

@ -1,4 +1,5 @@
export const Colors = {
primary: '#66aa88',
danger: '#ff8888',
}

View File

@ -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: 'Ошибка',

View File

@ -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,

View File

@ -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,
},
})

View File

@ -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 (
<>
<ScrollView bounces={false}>
@ -69,9 +108,6 @@ export function Settings() {
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
<TouchableOpacity style={styles.editDetails} onPress={() => setDetails(true)}>
<Text style={styles.editDetailsLabel}>{state.strings.edit}</Text>
</TouchableOpacity>
</View>
<View style={styles.attributes}>
@ -103,15 +139,127 @@ export function Settings() {
<Text style={styles.labelSet}>{state.profile.description}</Text>
)}
</View>
<TouchableOpacity style={styles.editDetails} onPress={() => setDetails(true)}>
<Surface elevation={4} mode="flat">
<Text style={styles.editDetailsLabel}>{state.strings.edit}</Text>
</Surface>
</TouchableOpacity>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="eye-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.searchable)}>
<Text style={styles.controlLabel}>{state.strings.visibleRegistry}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.searchable} onValueChange={setRegistry} />
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="cloud-lock-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.manageTopics}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="bell-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.pushEnabled)}>
<Text style={styles.controlLabel}>{state.strings.enableNotifications}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.pushEnabled} onValueChange={setRegistry} />
</View>
</View>
</View>
<Button mode="contained" onPress={actions.logout}>
Logout
</Button>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="ticket-confirmation-outline" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => setRegistry(!state.config.searchable)}>
<Text style={styles.controlLabel}>{state.strings.mfaTitle}</Text>
</TouchableOpacity>
<Switch style={styles.controlSwitch} value={state.config.searchable} onValueChange={setRegistry} />
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="login" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.changeLogin}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="logout" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.controlLabel}>{state.strings.logout}</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.controlIcon}>
<Icon size={24} source="account-remove" />
</View>
<View style={styles.control}>
<TouchableOpacity activeOpacity={1} onPress={() => manageSeal}>
<Text style={styles.dangerLabel}>{state.strings.deleteAccount}</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.divider}>
<Divider style={styles.line} bold={true} />
</View>
<View style={styles.attributes}>
<View style={styles.attribute}>
<View style={styles.icon}>
<Icon size={24} source="clock-outline" />
</View>
<View style={styles.radioControl}>
<Text style={styles.label}>{state.strings.timeFormat}:</Text>
<View style={styles.radioButtons}>
<RadioButton.Item label={state.strings.timeHalf} mode="android" status={state.fullDayTime ? 'unchecked' : 'checked'} onPress={() => {actions.setFullDayTime(false)}} />
<RadioButton.Item label={state.strings.timeFull} mode="android" status={state.fullDayTime ? 'checked' : 'unchecked'} onPress={() => {actions.setFullDayTime(true)}} />
</View>
</View>
</View>
<View style={styles.attribute}>
<View style={styles.icon}>
<Icon size={24} source="calendar-text-outline" />
</View>
<View style={styles.radioControl}>
<Text style={styles.label}>{state.strings.dateFormat}:</Text>
<View style={styles.radioButtons}>
<RadioButton.Item label={state.strings.monthStart} mode="android" status={state.monthFirstDate ? 'checked' : 'unchecked'} onPress={() => {actions.setMonthFirstDate(true)}} />
<RadioButton.Item label={state.strings.monthEnd} mode="android" status={state.monthFirstDate ? 'unchecked' : 'checked'} onPress={() => {actions.setMonthFirstDate(false)}} />
</View>
</View>
</View>
</View>
</SafeAreaView>
</ScrollView>
<Modal

View File

@ -16,8 +16,6 @@ export function useSettings() {
profileSet: false,
imageUrl: null,
strings: display.state.strings,
timeFormat: '12h',
dateFormat: 'mm/dd',
all: false,
password: '',
confirm: '',
@ -35,6 +33,8 @@ export function useSettings() {
sealConfirm: '',
sealDelete: '',
secretCopied: false,
monthFirstDate: true,
fullDayTime: false,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -78,6 +78,11 @@ export function useSettings() {
}
}, [])
useEffect(() => {
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 }