formatting code

This commit is contained in:
Roland Osborne 2024-09-27 09:48:36 -07:00
parent ed369424cd
commit c2642f534e
14 changed files with 1029 additions and 535 deletions

View File

@ -11,28 +11,37 @@ export class LocalStore implements SqlStore {
public async open(path: string) { public async open(path: string) {
this.db = await SQLite.openDatabase({name: path, location: 'default'}); 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));"); 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> { public async get(key: string, value: string, unset: string): Promise<string> {
try { try {
const rows = await this.localStoreGet(`SELECT * FROM local_store WHERE key='${key}';`); const rows = await this.localStoreGet(
`SELECT * FROM local_store WHERE key='${key}';`,
);
if (rows.length == 1 && rows[0].value != null) { if (rows.length == 1 && rows[0].value != null) {
return rows[0].value; return rows[0].value;
} }
} } catch (err) {
catch(err) {
console.log(err); console.log(err);
} }
return unset; return unset;
} }
public async set(key: string, value: string): Promise<void> { public async set(key: string, value: string): Promise<void> {
await this.localStoreSet('INSERT OR REPLACE INTO local_store (key, value) values (?, ?)', [key, value]); await this.localStoreSet(
'INSERT OR REPLACE INTO local_store (key, value) values (?, ?)',
[key, value],
);
} }
public async clear(key: string): Promise<void> { public async clear(key: string): Promise<void> {
await this.localStoreSet('INSERT OR REPLACE INTO local_store (key, value) values (?, null)', [key]); await this.localStoreSet(
'INSERT OR REPLACE INTO local_store (key, value) values (?, null)',
[key],
);
} }
private async localStoreSet( private async localStoreSet(

View File

@ -1,109 +1,134 @@
import { Crypto } from 'databag-client-sdk'; import {Crypto} from 'databag-client-sdk';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import { JSEncrypt } from 'jsencrypt' import {JSEncrypt} from 'jsencrypt';
import { RSA } from 'react-native-rsa-native'; import {RSA} from 'react-native-rsa-native';
import { generateSecureRandom } from 'react-native-securerandom'; import {generateSecureRandom} from 'react-native-securerandom';
export class NativeCrypto implements Crypto { export class NativeCrypto implements Crypto {
// generate salt for pbk function // generate salt for pbk function
public async pbkdfSalt(): Promise<{ saltHex: string }> { public async pbkdfSalt(): Promise<{saltHex: string}> {
const salt = await generateSecureRandom(16); const salt = await generateSecureRandom(16);
const saltHex = this.uint8ToHexStr(salt); const saltHex = this.uint8ToHexStr(salt);
return { saltHex }; return {saltHex};
} }
// generate aes key with pbkdf2 // generate aes key with pbkdf2
public async pbkdfKey(saltHex: string, password: string): Promise<{ aesKeyHex: string }> { public async pbkdfKey(
saltHex: string,
password: string,
): Promise<{aesKeyHex: string}> {
const salt = CryptoJS.enc.Hex.parse(saltHex); const salt = CryptoJS.enc.Hex.parse(saltHex);
const aes = CryptoJS.PBKDF2(password, salt, { keySize: 256 / 32, iterations: 1024, hasher: CryptoJS.algo.SHA1, }); const aes = CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations: 1024,
hasher: CryptoJS.algo.SHA1,
});
const aesKeyHex = aes.toString(); const aesKeyHex = aes.toString();
return { aesKeyHex }; return {aesKeyHex};
} }
// generate random aes key // generate random aes key
public async aesKey(): Promise<{ aesKeyHex: string }> { public async aesKey(): Promise<{aesKeyHex: string}> {
const aes = await generateSecureRandom(32); const aes = await generateSecureRandom(32);
const aesHex = this.uint8ToHexStr(aes); const aesHex = this.uint8ToHexStr(aes);
return { aesKeyHex }; return {aesKeyHex};
} }
// generate iv to use to aes function // generate iv to use to aes function
public async aesIv(): Promise<{ ivHex: string }> { public async aesIv(): Promise<{ivHex: string}> {
const iv = await generateSecureRandom(16); const iv = await generateSecureRandom(16);
const ivHex = this.uint8ToHexStr(iv); const ivHex = this.uint8ToHexStr(iv);
return { ivHex }; return {ivHex};
} }
// encrypt data with aes key and iv // encrypt data with aes key and iv
public async aesEncrypt(data: string, ivHex: string, aesKeyHex: string): Promise<{ encryptedDataB64: string }> { public async aesEncrypt(
data: string,
ivHex: string,
aesKeyHex: string,
): Promise<{encryptedDataB64: string}> {
const iv = CryptoJS.enc.Hex.parse(ivHex); const iv = CryptoJS.enc.Hex.parse(ivHex);
const key = CryptoJS.enc.Hex.parse(aesKeyHex); const key = CryptoJS.enc.Hex.parse(aesKeyHex);
const encrypted = CryptoJS.AES.encrypt(data, key, { iv }); const encrypted = CryptoJS.AES.encrypt(data, key, {iv});
const encryptedDataB64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64); const encryptedDataB64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
return { encryptedDataB64 }; return {encryptedDataB64};
} }
// decrypt data with aes key and iv // decrypt data with aes key and iv
public async aesDecrypt(encryptedDataB64: string, ivHex: string, aesKeyHex: string): Promise<{ data: string }> { public async aesDecrypt(
encryptedDataB64: string,
ivHex: string,
aesKeyHex: string,
): Promise<{data: string}> {
const iv = CryptoJS.enc.Hex.parse(ivHex); const iv = CryptoJS.enc.Hex.parse(ivHex);
const key = CryptoJS.enc.Hex.parse(aesKeyHex); const key = CryptoJS.enc.Hex.parse(aesKeyHex);
const ciphertext = CryptoJS.enc.Base64.parse(encryptedDataB64); const ciphertext = CryptoJS.enc.Base64.parse(encryptedDataB64);
const cipher = CryptoJS.lib.CipherParams.create({ ciphertext, iv }); const cipher = CryptoJS.lib.CipherParams.create({ciphertext, iv});
const decrypted = CryptoJS.AES.decrypt(cipher, key, { iv }); const decrypted = CryptoJS.AES.decrypt(cipher, key, {iv});
const data = decrypted.toString(CryptoJS.enc.Utf8); const data = decrypted.toString(CryptoJS.enc.Utf8);
return { data }; return {data};
} }
// generate rsa key // generate rsa key
public async rsaKey(): Promise<{ publicKeyB64: string, privateKeyB64: string }> { public async rsaKey(): Promise<{
const crypto = new JSEncrypt({ default_key_size: '2048' }); publicKeyB64: string;
privateKeyB64: string;
}> {
const crypto = new JSEncrypt({default_key_size: '2048'});
crypto.getKey(); crypto.getKey();
const publicKey = crypto.getPublicKey(); const publicKey = crypto.getPublicKey();
const publicKeyB64 = this.convertPem(publicKey); const publicKeyB64 = this.convertPem(publicKey);
const privateKey = crypto.getPrivateKey(); const privateKey = crypto.getPrivateKey();
const privateKeyB64 = this.convertPem(privateKey); const privateKeyB64 = this.convertPem(privateKey);
return { publicKeyB64, privateKeyB64 }; return {publicKeyB64, privateKeyB64};
} }
// encrypt data with public rsa key // encrypt data with public rsa key
public async rsaEncrypt(data: string, publicKeyB64: string): Promise<{ encryptedDataB64: string }> { public async rsaEncrypt(
data: string,
publicKeyB64: string,
): Promise<{encryptedDataB64: string}> {
const crypto = new JSEncrypt(); const crypto = new JSEncrypt();
crypto.setPublicKey(publicKeyB64); crypto.setPublicKey(publicKeyB64);
const encryptedDataB64 = crypto.encrypt(data); const encryptedDataB64 = crypto.encrypt(data);
if (!encryptedDataB64) { if (!encryptedDataB64) {
throw new Error('rsaEncrypt failed'); throw new Error('rsaEncrypt failed');
} }
return { encryptedDataB64 }; return {encryptedDataB64};
} }
// decrypt data with private rsa key // decrypt data with private rsa key
public async rsaDecrypt(encryptedDataB64: string, privateKeyB64: string): Promise<{ data: string }> { public async rsaDecrypt(
encryptedDataB64: string,
privateKeyB64: string,
): Promise<{data: string}> {
const crypto = new JSEncrypt(); const crypto = new JSEncrypt();
crypto.setPrivateKey(privateKeyB64); crypto.setPrivateKey(privateKeyB64);
const data = await RSA.decrypt(encryptedDataB64, privateKeyB64); const data = await RSA.decrypt(encryptedDataB64, privateKeyB64);
if (!data) { if (!data) {
throw new Error('rsaDecrypt failed'); throw new Error('rsaDecrypt failed');
} }
return { data }; return {data};
} }
private convertPem(pem: string): string { private convertPem(pem: string): string {
const lines = pem.split('\n'); const lines = pem.split('\n');
let encoded = ''; let encoded = '';
for(let i = 0;i < lines.length;i++){ for (let i = 0; i < lines.length; i++) {
if (lines[i].trim().length > 0 && if (
lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 && lines[i].trim().length > 0 &&
lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 && lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 &&
lines[i].indexOf('-BEGIN PUBLIC KEY-') < 0 && lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 &&
lines[i].indexOf('-END PUBLIC KEY-') < 0 && lines[i].indexOf('-BEGIN PUBLIC KEY-') < 0 &&
lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 && lines[i].indexOf('-END PUBLIC KEY-') < 0 &&
lines[i].indexOf('-END RSA PUBLIC KEY-') < 0) { lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 &&
lines[i].indexOf('-END RSA PUBLIC KEY-') < 0
) {
encoded += lines[i].trim(); encoded += lines[i].trim();
} }
} }
return encoded return encoded;
}; }
private uint8ToHexStr(bin: Uint8Array) { private uint8ToHexStr(bin: Uint8Array) {
let hex = ''; let hex = '';

View File

@ -1,10 +1,5 @@
import React, {useState} from 'react'; import React, {useState} from 'react';
import { import {Platform, KeyboardAvoidingView, View, Image} from 'react-native';
Platform,
KeyboardAvoidingView,
View,
Image,
} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {useAccess} from './useAccess.hook'; import {useAccess} from './useAccess.hook';
import {styles} from './Access.styled'; import {styles} from './Access.styled';
@ -135,12 +130,14 @@ export function Access() {
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
showPassword ? ( showPassword ? (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye-off" icon="eye-off"
onPress={() => setShowPassword(false)} onPress={() => setShowPassword(false)}
/> />
) : ( ) : (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye" icon="eye"
onPress={() => setShowPassword(true)} onPress={() => setShowPassword(true)}
/> />
@ -174,7 +171,9 @@ export function Access() {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={state.strings.token} label={state.strings.token}
left={<TextInput.Icon style={styles.icon} icon="ticket-account" />} left={
<TextInput.Icon style={styles.icon} icon="ticket-account" />
}
onChangeText={value => actions.setToken(value)} onChangeText={value => actions.setToken(value)}
/> />
<TextInput <TextInput
@ -216,7 +215,12 @@ export function Access() {
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
label={state.strings.token} label={state.strings.token}
left={<TextInput.Icon style={styles.icon} icon="ticket-account" />} left={
<TextInput.Icon
style={styles.icon}
icon="ticket-account"
/>
}
onChangeText={value => actions.setToken(value)} onChangeText={value => actions.setToken(value)}
/> />
)} )}
@ -257,12 +261,14 @@ export function Access() {
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
showPassword ? ( showPassword ? (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye-off" icon="eye-off"
onPress={() => setShowPassword(false)} onPress={() => setShowPassword(false)}
/> />
) : ( ) : (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye" icon="eye"
onPress={() => setShowPassword(true)} onPress={() => setShowPassword(true)}
/> />
@ -283,12 +289,14 @@ export function Access() {
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
showPassword ? ( showPassword ? (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye-off" icon="eye-off"
onPress={() => setShowConfirm(false)} onPress={() => setShowConfirm(false)}
/> />
) : ( ) : (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye" icon="eye"
onPress={() => setShowConfirm(true)} onPress={() => setShowConfirm(true)}
/> />
@ -343,12 +351,14 @@ export function Access() {
left={<TextInput.Icon style={styles.icon} icon="lock" />} left={<TextInput.Icon style={styles.icon} icon="lock" />}
right={ right={
showPassword ? ( showPassword ? (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye-off" icon="eye-off"
onPress={() => setShowPassword(false)} onPress={() => setShowPassword(false)}
/> />
) : ( ) : (
<TextInput.Icon style={styles.icon} <TextInput.Icon
style={styles.icon}
icon="eye" icon="eye"
onPress={() => setShowPassword(true)} onPress={() => setShowPassword(true)}
/> />

View File

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

View File

@ -8,7 +8,7 @@ const SETTINGS_DB = 'ls_v001.db';
export function useAppContext() { export function useAppContext() {
const local = useRef(new LocalStore()); const local = useRef(new LocalStore());
const sdk = useRef(new DatabagSDK(new NativeCrypto())) const sdk = useRef(new DatabagSDK(new NativeCrypto()));
const [state, setState] = useState({ const [state, setState] = useState({
session: null as null | Session, session: null as null | Session,
fullDayTime: false, fullDayTime: false,
@ -22,8 +22,10 @@ export function useAppContext() {
const setup = async () => { const setup = async () => {
await local.current.open(SETTINGS_DB); await local.current.open(SETTINGS_DB);
const fullDayTime = await local.current.get('time_format', '12h') === '24h'; const fullDayTime =
const monthFirstDate = await local.current.get('date_format', 'month_first') === 'month_first'; (await local.current.get('time_format', '12h')) === '24h';
const monthFirstDate =
(await local.current.get('date_format', 'month_first')) === 'month_first';
const store = new SessionStore(); const store = new SessionStore();
await store.open(DATABAG_DB); await store.open(DATABAG_DB);
@ -39,11 +41,14 @@ export function useAppContext() {
const actions = { const actions = {
setMonthFirstDate: async (monthFirstDate: boolean) => { setMonthFirstDate: async (monthFirstDate: boolean) => {
updateState({ monthFirstDate }); updateState({monthFirstDate});
await local.current.set('date_format', monthFirstDate ? 'month_first' : 'day_first'); await local.current.set(
'date_format',
monthFirstDate ? 'month_first' : 'day_first',
);
}, },
setFullDayTime: async (fullDayTime: boolean) => { setFullDayTime: async (fullDayTime: boolean) => {
updateState({ fullDayTime }); updateState({fullDayTime});
await local.current.set('time_format', fullDayTime ? '24h' : '12h'); await local.current.set('time_format', fullDayTime ? '24h' : '12h');
}, },
accountLogin: async ( accountLogin: async (
@ -86,7 +91,7 @@ export function useAppContext() {
accountRemove: async () => { accountRemove: async () => {
if (state.session) { if (state.session) {
await sdk.current.remove(state.session); await sdk.current.remove(state.session);
updateState({ session: null }); updateState({session: null});
} }
}, },
accountCreate: async ( accountCreate: async (

View File

@ -123,9 +123,6 @@ export const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
}, },
controlSwitch: { controlSwitch: {
transform: [ transform: [{scaleX: 0.7}, {scaleY: 0.7}],
{ scaleX: 0.7 },
{scaleY: 0.7 },
]
}, },
}); });

View File

@ -1,21 +1,29 @@
import { useState } from 'react'; import {useState} from 'react';
import { Modal, TouchableOpacity, SafeAreaView, View, Image } from 'react-native'; import {Modal, TouchableOpacity, SafeAreaView, View, Image} from 'react-native';
import { Surface, IconButton, Button, Switch, Icon, Text, Menu } from 'react-native-paper'; import {
import { styles } from './Identity.styled'; Surface,
import { useIdentity } from './useIdentity.hook'; IconButton,
Button,
Switch,
Icon,
Text,
Menu,
} from 'react-native-paper';
import {styles} from './Identity.styled';
import {useIdentity} from './useIdentity.hook';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {BlurView} from '@react-native-community/blur'; import {BlurView} from '@react-native-community/blur';
export function Identity({ openSettings }) { export function Identity({openSettings}) {
const [menu, setMenu] = useState(false); const [menu, setMenu] = useState(false);
const { state, actions } = useIdentity(); const {state, actions} = useIdentity();
const [logout, setLogout] = useState(false); const [logout, setLogout] = useState(false);
const [applyingLogout, setApplyingLogout] = useState(false); const [applyingLogout, setApplyingLogout] = useState(false);
const showLogout = () => { const showLogout = () => {
setMenu(false); setMenu(false);
setLogout(true); setLogout(true);
} };
const applyLogout = async () => { const applyLogout = async () => {
if (!applyingLogout) { if (!applyingLogout) {
@ -24,25 +32,46 @@ export function Identity({ openSettings }) {
setLogout(false); setLogout(false);
setApplyingLogout(false); setApplyingLogout(false);
} }
} };
return ( return (
<> <>
<SafeAreaView style={styles.identity}> <SafeAreaView style={styles.identity}>
<TouchableOpacity style={styles.identityData} activeOpacity={1} onPress={() => setMenu(true)}> <TouchableOpacity
style={styles.identityData}
activeOpacity={1}
onPress={() => setMenu(true)}>
<View style={styles.image}> <View style={styles.image}>
{state.profile.imageSet && ( {state.profile.imageSet && (
<Image style={styles.logoSet} resizeMode={'contain'} source={state.imageUrl} /> <Image
style={styles.logoSet}
resizeMode={'contain'}
source={state.imageUrl}
/>
)} )}
{!state.profile.imageSet && ( {!state.profile.imageSet && (
<Image style={styles.logoUnset} resizeMode={'contain'} source={state.imageUrl} /> <Image
style={styles.logoUnset}
resizeMode={'contain'}
source={state.imageUrl}
/>
)} )}
</View> </View>
<View style={styles.details}> <View style={styles.details}>
{state.profile.name && ( {state.profile.name && (
<Text style={styles.name} adjustsFontSizeToFit={true} numberOfLines={1}>{state.profile.name}</Text> <Text
style={styles.name}
adjustsFontSizeToFit={true}
numberOfLines={1}>
{state.profile.name}
</Text>
)} )}
<Text style={styles.username} adjustsFontSizeToFit={true} numberOfLines={1}>{`${state.profile.handle}${state.profile.node ? '/' + state.profile.node : ''}`}</Text> <Text
style={styles.username}
adjustsFontSizeToFit={true}
numberOfLines={1}>{`${state.profile.handle}${
state.profile.node ? '/' + state.profile.node : ''
}`}</Text>
</View> </View>
<Icon size={18} source="chevron-right" /> <Icon size={18} source="chevron-right" />
</TouchableOpacity> </TouchableOpacity>
@ -50,10 +79,29 @@ export function Identity({ openSettings }) {
visible={menu} visible={menu}
onDismiss={() => setMenu(false)} onDismiss={() => setMenu(false)}
anchorPosition="top" anchorPosition="top"
anchor={<View style={styles.anchor}><Text> </Text></View>}> anchor={
<Menu.Item leadingIcon="cog-outline" onPress={() => {setMenu(false); openSettings()}} title={state.strings.settings} /> <View style={styles.anchor}>
<Menu.Item leadingIcon="contacts-outline" onPress={() => {}} title={state.strings.contacts} /> <Text> </Text>
<Menu.Item leadingIcon="logout" onPress={showLogout} title={state.strings.logout} /> </View>
}>
<Menu.Item
leadingIcon="cog-outline"
onPress={() => {
setMenu(false);
openSettings();
}}
title={state.strings.settings}
/>
<Menu.Item
leadingIcon="contacts-outline"
onPress={() => {}}
title={state.strings.contacts}
/>
<Menu.Item
leadingIcon="logout"
onPress={showLogout}
title={state.strings.logout}
/>
</Menu> </Menu>
</SafeAreaView> </SafeAreaView>
<Modal <Modal
@ -69,24 +117,44 @@ export function Identity({ openSettings }) {
blurAmount={2} blurAmount={2}
reducedTransparencyFallbackColor="dark" reducedTransparencyFallbackColor="dark"
/> />
<KeyboardAwareScrollView style={styles.container} contentContainerStyle={styles.content}> <KeyboardAwareScrollView
style={styles.container}
contentContainerStyle={styles.content}>
<Surface elevation={5} mode="flat" style={styles.surface}> <Surface elevation={5} mode="flat" style={styles.surface}>
<Text style={styles.modalLabel}>{ state.strings.loggingOut }</Text> <Text style={styles.modalLabel}>{state.strings.loggingOut}</Text>
<IconButton style={styles.modalClose} icon="close" size={24} onPress={() => setLogout(false)} /> <IconButton
style={styles.modalClose}
icon="close"
size={24}
onPress={() => setLogout(false)}
/>
<View style={styles.allControl}> <View style={styles.allControl}>
<Text style={styles.controlLabel}>{state.strings.allDevices}</Text> <Text style={styles.controlLabel}>
<Switch style={styles.controlSwitch} value={state.all} onValueChange={actions.setAll} /> {state.strings.allDevices}
</Text>
<Switch
style={styles.controlSwitch}
value={state.all}
onValueChange={actions.setAll}
/>
</View> </View>
<View style={styles.modalControls}> <View style={styles.modalControls}>
<Button mode="outlined" onPress={() => setLogout(false)}>{ state.strings.cancel }</Button> <Button mode="outlined" onPress={() => setLogout(false)}>
<Button mode="contained" loading={applyingLogout} onPress={applyLogout}>{ state.strings.logout }</Button> {state.strings.cancel}
</Button>
<Button
mode="contained"
loading={applyingLogout}
onPress={applyLogout}>
{state.strings.logout}
</Button>
</View> </View>
</Surface> </Surface>
</KeyboardAwareScrollView> </KeyboardAwareScrollView>
</View> </View>
</Modal> </Modal>
</> </>
) );
} }

View File

@ -1,11 +1,11 @@
import { useEffect, useState, useContext, useRef } from 'react' import {useEffect, useState, useContext, useRef} from 'react';
import { AppContext } from '../context/AppContext' import {AppContext} from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext'; import {DisplayContext} from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import {ContextType} from '../context/ContextType';
export function useIdentity() { export function useIdentity() {
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
all: false, all: false,
@ -14,41 +14,40 @@ export function useIdentity() {
profileSet: false, profileSet: false,
imageUrl: null, imageUrl: null,
strings: display.state.strings, strings: display.state.strings,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState(s => ({...s, ...value}));
} };
useEffect(() => { useEffect(() => {
const identity = app.state.session?.getIdentity() const identity = app.state.session?.getIdentity();
if (!identity) { if (!identity) {
console.log('session not set in identity hook') console.log('session not set in identity hook');
} else { } else {
const setProfile = (profile: Profile) => { const setProfile = (profile: Profile) => {
updateState({ updateState({
profile, profile,
profileSet: true, profileSet: true,
imageUrl: { uri: identity.getProfileImageUrl() }, imageUrl: {uri: identity.getProfileImageUrl()},
}) });
};
} identity.addProfileListener(setProfile);
identity.addProfileListener(setProfile)
return () => { return () => {
identity.removeProfileListener(setProfile) identity.removeProfileListener(setProfile);
} };
} }
}, []) }, []);
const actions = { const actions = {
setAll: (all) => { setAll: all => {
updateState({ all }); updateState({all});
}, },
logout: async () => { logout: async () => {
await app.actions.accountLogout(state.all); await app.actions.accountLogout(state.all);
} },
} };
return { state, actions } return {state, actions};
} }

View File

@ -1,7 +1,13 @@
import React, {useState, useContext} from 'react'; import React, {useState, useContext} from 'react';
import {View, useColorScheme} from 'react-native'; import {View, useColorScheme} from 'react-native';
import {styles} from './Session.styled'; import {styles} from './Session.styled';
import {BottomNavigation, Surface, Menu, Button, Text} from 'react-native-paper'; import {
BottomNavigation,
Surface,
Menu,
Button,
Text,
} from 'react-native-paper';
import {Settings} from '../settings/Settings'; import {Settings} from '../settings/Settings';
import {Channels} from '../channels/Channels'; import {Channels} from '../channels/Channels';
import {Contacts} from '../contacts/Contacts'; import {Contacts} from '../contacts/Contacts';
@ -11,7 +17,11 @@ import {Details} from '../details/Details';
import {Identity} from '../identity/Identity'; import {Identity} from '../identity/Identity';
import {useSession} from './useSession.hook'; import {useSession} from './useSession.hook';
import {NavigationContainer, DefaultTheme, DarkTheme} from '@react-navigation/native'; import {
NavigationContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
import {createDrawerNavigator} from '@react-navigation/drawer'; import {createDrawerNavigator} from '@react-navigation/drawer';
const ChannelsRoute = () => <Channels />; const ChannelsRoute = () => <Channels />;
@ -67,7 +77,8 @@ export function Session() {
/> />
)} )}
{state.layout === 'large' && ( {state.layout === 'large' && (
<NavigationContainer theme={scheme === 'dark' ? DarkTheme : DefaultTheme}> <NavigationContainer
theme={scheme === 'dark' ? DarkTheme : DefaultTheme}>
<DetailsScreen nav={sessionNav} /> <DetailsScreen nav={sessionNav} />
</NavigationContainer> </NavigationContainer>
)} )}
@ -157,7 +168,7 @@ function SettingsScreen({nav}) {
id="SettingsDrawer" id="SettingsDrawer"
drawerContent={Settings} drawerContent={Settings}
screenOptions={{ screenOptions={{
drawerStyle: { width: '40%' }, drawerStyle: {width: '40%'},
drawerPosition: 'right', drawerPosition: 'right',
drawerType: 'front', drawerType: 'front',
headerShown: false, headerShown: false,
@ -178,8 +189,7 @@ function HomeScreen({nav}) {
<Surface elevation={2} mode="flat"> <Surface elevation={2} mode="flat">
<Identity openSettings={nav.settings.openDrawer} /> <Identity openSettings={nav.settings.openDrawer} />
</Surface> </Surface>
<Surface style={styles.channels} elevation={1} mode="flat"> <Surface style={styles.channels} elevation={1} mode="flat"></Surface>
</Surface>
</View> </View>
<View style={styles.right}> <View style={styles.right}>
<Text>CONVERSATION</Text> <Text>CONVERSATION</Text>

View File

@ -1,34 +1,34 @@
import { useEffect, useState, useContext, useRef } from 'react' import {useEffect, useState, useContext, useRef} from 'react';
import { AppContext } from '../context/AppContext' import {AppContext} from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext' import {DisplayContext} from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import {ContextType} from '../context/ContextType';
const DEBOUNCE_MS = 1000 const DEBOUNCE_MS = 1000;
export function useSession() { export function useSession() {
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const [state, setState] = useState({ const [state, setState] = useState({
layout: null, layout: null,
strings: {}, strings: {},
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState(s => ({...s, ...value}));
} };
useEffect(() => { useEffect(() => {
const { layout, strings } = display.state; const {layout, strings} = display.state;
updateState({ layout, strings }); updateState({layout, strings});
}, [display.state.layout, display.state.strings]); }, [display.state.layout, display.state.strings]);
const actions = { const actions = {
logout: async () => { logout: async () => {
await app.actions.accountLogout(); await app.actions.accountLogout();
} },
} };
return { state, actions } return {state, actions};
} }

View File

@ -1,5 +1,5 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import { Colors } from '../constants/Colors'; import {Colors} from '../constants/Colors';
export const styles = StyleSheet.create({ export const styles = StyleSheet.create({
modal: { modal: {
@ -260,10 +260,7 @@ export const styles = StyleSheet.create({
color: Colors.danger, color: Colors.danger,
}, },
controlSwitch: { controlSwitch: {
transform: [ transform: [{scaleX: 0.7}, {scaleY: 0.7}],
{ scaleX: 0.7 },
{scaleY: 0.7 },
]
}, },
input: { input: {
width: '100%', width: '100%',
@ -302,4 +299,4 @@ export const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
color: Colors.danger, color: Colors.danger,
}, },
}) });

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
import { useEffect, useState, useContext, useRef } from 'react' import {useEffect, useState, useContext, useRef} from 'react';
import { AppContext } from '../context/AppContext' import {AppContext} from '../context/AppContext';
import { DisplayContext } from '../context/DisplayContext' import {DisplayContext} from '../context/DisplayContext';
import { ContextType } from '../context/ContextType' import {ContextType} from '../context/ContextType';
const DEBOUNCE_MS = 1000 const DEBOUNCE_MS = 1000;
export function useSettings() { export function useSettings() {
const display = useContext(DisplayContext) as ContextType const display = useContext(DisplayContext) as ContextType;
const app = useContext(AppContext) as ContextType const app = useContext(AppContext) as ContextType;
const debounce = useRef(setTimeout(() => {}, 0)) const debounce = useRef(setTimeout(() => {}, 0));
const [state, setState] = useState({ const [state, setState] = useState({
config: {} as Config, config: {} as Config,
@ -36,32 +36,32 @@ export function useSettings() {
secretCopied: false, secretCopied: false,
monthFirstDate: true, monthFirstDate: true,
fullDayTime: false, fullDayTime: false,
}) });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => { const updateState = (value: any) => {
setState((s) => ({ ...s, ...value })) setState(s => ({...s, ...value}));
} };
const getSession = () => { const getSession = () => {
const session = app.state?.session const session = app.state?.session;
const settings = session?.getSettings() const settings = session?.getSettings();
const identity = session?.getIdentity() const identity = session?.getIdentity();
if (!settings || !identity) { if (!settings || !identity) {
console.log('session not set in settings hook') console.log('session not set in settings hook');
} }
return { settings, identity } return {settings, identity};
} };
useEffect(() => { useEffect(() => {
const { settings, identity } = getSession() const {settings, identity} = getSession();
const setConfig = (config: Config) => { const setConfig = (config: Config) => {
updateState({ config }) updateState({config});
} };
settings.addConfigListener(setConfig) settings.addConfigListener(setConfig);
const setProfile = (profile: Profile) => { const setProfile = (profile: Profile) => {
const { handle, name, location, description } = profile const {handle, name, location, description} = profile;
const url = identity.getProfileImageUrl() const url = identity.getProfileImageUrl();
updateState({ updateState({
profile, profile,
handle, handle,
@ -70,194 +70,188 @@ export function useSettings() {
description, description,
imageUrl: url, imageUrl: url,
profileSet: true, profileSet: true,
}) });
} };
identity.addProfileListener(setProfile) identity.addProfileListener(setProfile);
return () => { return () => {
settings.removeConfigListener(setConfig) settings.removeConfigListener(setConfig);
identity.removeProfileListener(setProfile) identity.removeProfileListener(setProfile);
} };
}, []) }, []);
useEffect(() => { useEffect(() => {
const { fullDayTime, monthFirstDate } = app.state; const {fullDayTime, monthFirstDate} = app.state;
updateState({ fullDayTime, monthFirstDate }); updateState({fullDayTime, monthFirstDate});
}, [app.state.fullDayTime, app.state.monthFirstDate]); }, [app.state.fullDayTime, app.state.monthFirstDate]);
useEffect(() => { useEffect(() => {
const { const {strings, dateFormat, timeFormat} = display.state;
strings,
dateFormat,
timeFormat,
} = display.state
updateState({ updateState({
strings, strings,
dateFormat, dateFormat,
timeFormat, timeFormat,
}) });
}, [display.state]) }, [display.state]);
const actions = { const actions = {
getUsernameStatus: async (username: string) => { getUsernameStatus: async (username: string) => {
const { settings } = getSession() const {settings} = getSession();
return await settings.getUsernameStatus(username) return await settings.getUsernameStatus(username);
}, },
setLogin: async () => { setLogin: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.setLogin(state.handle, state.password) await settings.setLogin(state.handle, state.password);
}, },
enableNotifications: async () => { enableNotifications: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.enableNotifications() await settings.enableNotifications();
}, },
disableNotifications: async () => { disableNotifications: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.disableNotifications() await settings.disableNotifications();
}, },
enableRegistry: async () => { enableRegistry: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.enableRegistry() await settings.enableRegistry();
}, },
disableRegistry: async () => { disableRegistry: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.disableRegistry() await settings.disableRegistry();
}, },
enableMFA: async () => { enableMFA: async () => {
const { settings } = getSession() const {settings} = getSession();
const { secretImage, secretText } = await settings.enableMFA() const {secretImage, secretText} = await settings.enableMFA();
updateState({ secretImage, secretText }); updateState({secretImage, secretText});
}, },
disableMFA: async () => { disableMFA: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.disableMFA() await settings.disableMFA();
}, },
confirmMFA: async () => { confirmMFA: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.confirmMFA(state.code) await settings.confirmMFA(state.code);
}, },
setCode: (code: string) => { setCode: (code: string) => {
updateState({ code }); updateState({code});
}, },
copySecret: () => { copySecret: () => {
navigator.clipboard.writeText(state.secretText); navigator.clipboard.writeText(state.secretText);
updateState({ secretCopied: true }); updateState({secretCopied: true});
setTimeout(() => { setTimeout(() => {
updateState({ secretCopied: false }); updateState({secretCopied: false});
}, 1000); }, 1000);
}, },
setSeal: async () => { setSeal: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.setSeal(state.sealPassword) await settings.setSeal(state.sealPassword);
}, },
clearSeal: async () => { clearSeal: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.clearSeal() await settings.clearSeal();
}, },
unlockSeal: async () => { unlockSeal: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.unlockSeal(state.sealPassword) await settings.unlockSeal(state.sealPassword);
}, },
forgetSeal: async () => { forgetSeal: async () => {
const { settings } = getSession() const {settings} = getSession();
await settings.forgetSeal() await settings.forgetSeal();
}, },
updateSeal: async () => { updateSeal: async () => {
const { settings } = getSession(); const {settings} = getSession();
await settings.updateSeal(state.sealPassword); await settings.updateSeal(state.sealPassword);
}, },
setProfileData: async ( setProfileData: async (
name: string, name: string,
location: string, location: string,
description: string description: string,
) => { ) => {
const { identity } = getSession() const {identity} = getSession();
await identity.setProfileData(name, location, description) await identity.setProfileData(name, location, description);
}, },
setProfileImage: async (image: string) => { setProfileImage: async (image: string) => {
const { identity } = getSession() const {identity} = getSession();
await identity.setProfileImage(image) await identity.setProfileImage(image);
}, },
getProfileImageUrl: () => { getProfileImageUrl: () => {
const { identity } = getSession() const {identity} = getSession();
return identity.getProfileImageUrl() return identity.getProfileImageUrl();
}, },
setDateFormat: (format: string) => { setDateFormat: (format: string) => {
display.actions.setDateFormat(format) display.actions.setDateFormat(format);
}, },
setTimeFormat: (format: string) => { setTimeFormat: (format: string) => {
display.actions.setTimeFormat(format) display.actions.setTimeFormat(format);
}, },
setAll: (all: boolean) => { setAll: (all: boolean) => {
updateState({ all }) updateState({all});
}, },
logout: async () => { logout: async () => {
await app.actions.accountLogout(state.all) await app.actions.accountLogout(state.all);
}, },
remove: async () => { remove: async () => {
await app.actions.accountRemove() await app.actions.accountRemove();
}, },
setHandle: (handle: string) => { setHandle: (handle: string) => {
updateState({ handle, taken: false, checked: false }) updateState({handle, taken: false, checked: false});
clearTimeout(debounce.current) clearTimeout(debounce.current);
if (!handle || handle === state.profile.handle) { if (!handle || handle === state.profile.handle) {
updateState({ available: true, checked: true }) updateState({available: true, checked: true});
} else { } else {
debounce.current = setTimeout(async () => { debounce.current = setTimeout(async () => {
const { settings } = getSession() const {settings} = getSession();
try { try {
const available = await settings.getUsernameStatus(handle) const available = await settings.getUsernameStatus(handle);
updateState({ taken: !available, checked: true }) updateState({taken: !available, checked: true});
} } catch (err) {
catch (err) {
console.log(err); console.log(err);
} }
}, DEBOUNCE_MS) }, DEBOUNCE_MS);
} }
}, },
setPassword: (password: string) => { setPassword: (password: string) => {
updateState({ password }) updateState({password});
}, },
setConfirm: (confirm: string) => { setConfirm: (confirm: string) => {
updateState({ confirm }) updateState({confirm});
}, },
setRemove: (remove: string) => { setRemove: (remove: string) => {
updateState({ remove }); updateState({remove});
}, },
setName: (name: string) => { setName: (name: string) => {
updateState({ name }) updateState({name});
}, },
setLocation: (location: string) => { setLocation: (location: string) => {
updateState({ location }) updateState({location});
}, },
setDescription: (description: string) => { setDescription: (description: string) => {
updateState({ description }) updateState({description});
}, },
setDetails: async () => { setDetails: async () => {
const { identity } = getSession() const {identity} = getSession();
const { name, location, description } = state const {name, location, description} = state;
await identity.setProfileData(name, location, description) await identity.setProfileData(name, location, description);
}, },
setSealDelete: (sealDelete: string) => { setSealDelete: (sealDelete: string) => {
updateState({ sealDelete }); updateState({sealDelete});
}, },
setSealPassword: (sealPassword: string) => { setSealPassword: (sealPassword: string) => {
updateState({ sealPassword }); updateState({sealPassword});
}, },
setSealConfirm: (sealConfirm: string) => { setSealConfirm: (sealConfirm: string) => {
updateState({ sealConfirm }); updateState({sealConfirm});
}, },
setFullDayTime: async (flag: boolean) => { setFullDayTime: async (flag: boolean) => {
try { try {
await app.actions.setFullDayTime(flag); await app.actions.setFullDayTime(flag);
} } catch (err) {
catch (err) {
console.log(err); console.log(err);
} }
}, },
setMonthFirstDate: async (flag: boolean) => { setMonthFirstDate: async (flag: boolean) => {
await app.actions.setMonthFirstDate(flag); await app.actions.setMonthFirstDate(flag);
}, },
} };
return { state, actions } return {state, actions};
} }

View File

@ -41,7 +41,8 @@ export function InputCode({onChangeText, style}) {
<Text style={styles.text}>{code.charAt(5)}</Text> <Text style={styles.text}>{code.charAt(5)}</Text>
</View> </View>
</View> </View>
<TextInput style={styles.input} <TextInput
style={styles.input}
keyboardType={Platform.OS === 'ios' ? 'numeric' : 'number-pad'} keyboardType={Platform.OS === 'ios' ? 'numeric' : 'number-pad'}
onChangeText={updateCode} onChangeText={updateCode}
autoCorrect={false} autoCorrect={false}