adding change login modal to new settings screen

This commit is contained in:
Roland Osborne 2023-08-31 13:18:28 -07:00
parent 894ac14a10
commit e87a5b86fb
5 changed files with 256 additions and 9 deletions

View File

@ -21,6 +21,8 @@ const LightColors = {
headerBar: '#eeeeee', headerBar: '#eeeeee',
primaryButton: '#448866', primaryButton: '#448866',
primaryButtonText: '#ffffff', primaryButtonText: '#ffffff',
cancelButton: '#888888',
cancelButtonText: '#aaaaaa',
disabledButton: '#dddddd', disabledButton: '#dddddd',
disabledButtonText: '#aaaaaa', disabledButtonText: '#aaaaaa',
dangerButton: '#ff5555', dangerButton: '#ff5555',
@ -65,6 +67,8 @@ const DarkColors = {
headerBar: '#555555', headerBar: '#555555',
primaryButton: '#448866', primaryButton: '#448866',
primaryButtonText: '#ffffff', primaryButtonText: '#ffffff',
cancelButton: '#888888',
cancelButtonText: '#eeeeee',
disabledButton: '#888888', disabledButton: '#888888',
disabledButtonText: '#eeeeee', disabledButtonText: '#eeeeee',
dangerButton: '#ff5555', dangerButton: '#ff5555',
@ -113,6 +117,8 @@ export const Colors = {
headerBar: getColor('headerBar'), headerBar: getColor('headerBar'),
primaryButton: getColor('primaryButton'), primaryButton: getColor('primaryButton'),
primaryButtonText: getColor('primaryButtonText'), primaryButtonText: getColor('primaryButtonText'),
cancelButton: getColor('cancelButton'),
cancelButtonText: getColor('cancelButtonText'),
disabledButton: getColor('disabledButton'), disabledButton: getColor('disabledButton'),
disabledButtonText: getColor('disabledButtonText'), disabledButtonText: getColor('disabledButtonText'),
dangerButton: getColor('dangerButton'), dangerButton: getColor('dangerButton'),

View File

@ -2,6 +2,7 @@ import { NativeModules, Platform } from 'react-native'
const Strings = [ const Strings = [
{ {
// settings screen
visibleRegistry: 'Visible in Registry', visibleRegistry: 'Visible in Registry',
edit: 'Edit', edit: 'Edit',
enableNotifications: 'Push Notifications', enableNotifications: 'Push Notifications',
@ -26,6 +27,7 @@ const Strings = [
monthStart: 'mm/dd', monthStart: 'mm/dd',
monthEnd: 'dd/mm', monthEnd: 'dd/mm',
// seal wizard
sealUnset: 'Generate a key to enable end-to-end encrypted topics.', sealUnset: 'Generate a key to enable end-to-end encrypted topics.',
sealUnlocked: 'Disabling the sealing key will block access to all end-to-end encrypted topics from this device until the key is unlocked again.', sealUnlocked: 'Disabling the sealing key will block access to all end-to-end encrypted topics from this device until the key is unlocked again.',
sealLocked: 'Unlock the sealing key to support end-to-end encrypted topics on this device.', sealLocked: 'Unlock the sealing key to support end-to-end encrypted topics on this device.',
@ -47,6 +49,13 @@ const Strings = [
update: 'Update', update: 'Update',
changeKey: 'Change Key Password', changeKey: 'Change Key Password',
delayMessage: 'Key generation can take several minutes.', delayMessage: 'Key generation can take several minutes.',
// settings modals
cancel: 'Cancel',
confirmLogout: 'Logout',
loggingOut: 'Logging Out',
username: 'Username',
save: 'Save',
}, },
{ {
visibleRegistry: 'Visible dans le Registre', visibleRegistry: 'Visible dans le Registre',
@ -57,8 +66,8 @@ const Strings = [
hourMode: 'Heure', hourMode: 'Heure',
dateMode: 'Date', dateMode: 'Date',
language: 'Langue', language: 'Langue',
logout: 'Se Déconnecter', logout: 'Déconnecter',
changeLogin: 'Changer le Mot de Passe', changeLogin: 'Modifier l\'Accès',
deleteAccount: 'Supprimer le Compte', deleteAccount: 'Supprimer le Compte',
contacts: 'Contacts', contacts: 'Contacts',
topics: 'Sujets', topics: 'Sujets',
@ -94,6 +103,12 @@ const Strings = [
update: 'Mise à jour', update: 'Mise à jour',
changeKey: 'Changer le mot de passe clé', changeKey: 'Changer le mot de passe clé',
delayMessage: 'La génération de clé peut prendre plusieurs minutes.', delayMessage: 'La génération de clé peut prendre plusieurs minutes.',
cancel: 'Annuler',
confirmLogout: 'Déconnecter',
loggingOut: 'Confirmation de la Déconnexion',
username: 'Nom d\'Utilisateur',
save: 'Engegistrer',
}, },
{ {
visibleRegistry: 'Visible en el Registro', visibleRegistry: 'Visible en el Registro',
@ -141,6 +156,12 @@ const Strings = [
update: 'Actualizar', update: 'Actualizar',
changeKey: 'Cambiar clave Contraseña', changeKey: 'Cambiar clave Contraseña',
delayMessage: 'La generación de claves puede tardar varios minutos.', delayMessage: 'La generación de claves puede tardar varios minutos.',
cancel: 'Cancelar',
confirmLogout: 'Cerrar',
loggingOut: 'Confirmando cierre de sesión',
username: 'Nombre de Usuario',
save: 'Guardar',
}, },
{ {
visibleRegistry: 'Sichtbar in der Registrierung', visibleRegistry: 'Sichtbar in der Registrierung',
@ -188,6 +209,12 @@ const Strings = [
update: 'Aktualisieren', update: 'Aktualisieren',
changeKey: 'Schlüsselpasswort ändern', changeKey: 'Schlüsselpasswort ändern',
delayMessage: 'Die Schlüsselgenerierung kann mehrere Minuten dauern.', delayMessage: 'Die Schlüsselgenerierung kann mehrere Minuten dauern.',
cancel: 'Stornieren',
confirmlogout: 'Ausloggen',
loggingOut: 'Abmelden bestätigen',
username: 'Nutzername',
save: 'Speichern',
} }
]; ];

View File

@ -42,6 +42,41 @@ export function Settings() {
} }
} }
const logout = async () => {
if (!busy) {
try {
setBusy(true);
await actions.logout();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Logout',
'Please try again.',
);
}
setBusy(false);
}
}
const changeLogin = async () => {
if (!busy) {
try {
setBusy(true);
await actions.changeLogin();
actions.hideLogin();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Change Login',
'Please try again.',
);
}
setBusy(false);
}
}
return ( return (
<ScrollView style={styles.content}> <ScrollView style={styles.content}>
<SafeAreaView edges={['top']}> <SafeAreaView edges={['top']}>
@ -134,7 +169,7 @@ export function Settings() {
<Text style={styles.label}>{ state.strings.account }</Text> <Text style={styles.label}>{ state.strings.account }</Text>
<View style={styles.group}> <View style={styles.group}>
<TouchableOpacity style={styles.entry} activeOpacity={1}> <TouchableOpacity style={styles.entry} activeOpacity={1} onPress={actions.showLogout}>
<View style={styles.icon}> <View style={styles.icon}>
<MatIcons name="logout" size={20} color={Colors.linkText} /> <MatIcons name="logout" size={20} color={Colors.linkText} />
</View> </View>
@ -144,7 +179,7 @@ export function Settings() {
<View style={styles.control} /> <View style={styles.control} />
</TouchableOpacity> </TouchableOpacity>
<View style={styles.divider} /> <View style={styles.divider} />
<TouchableOpacity style={styles.entry} activeOpacity={1}> <TouchableOpacity style={styles.entry} activeOpacity={1} onPress={actions.showLogin}>
<View style={styles.icon}> <View style={styles.icon}>
<MatIcons name="login" size={20} color={Colors.linkText} /> <MatIcons name="login" size={20} color={Colors.linkText} />
</View> </View>
@ -401,6 +436,113 @@ export function Settings() {
</KeyboardAvoidingView> </KeyboardAvoidingView>
</Modal> </Modal>
<Modal
animationType="fade"
transparent={true}
visible={state.logout}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideLogout}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContainer}>
<Text style={styles.modalHeader}>{ state.strings.loggingOut }</Text>
<View style={styles.buttons}>
<TouchableOpacity style={styles.cancelButton} activeOpacity={1} onPress={actions.hideLogout}>
<Text style={styles.enabledButtonText}>{ state.strings.cancel }</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.promptButton} activeOpacity={1} onPress={logout}>
<Text style={styles.enabledButtonText}>{ state.strings.confirmLogout }</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
<Modal
animationType="fade"
transparent={true}
visible={state.login}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideLogin}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContainer}>
<View style={styles.modalClose}>
<TouchableOpacity style={styles.closeButton} activeOpacity={1} onPress={actions.hideLogin}>
<MatIcons name="close" size={20} color={Colors.descriptionText} />
</TouchableOpacity>
</View>
<Text style={styles.modalHeader}>{ state.strings.changeLogin }</Text>
<ActivityIndicator style={styles.modalBusy} animating={busy} color={Colors.primary} />
<View style={styles.modalInput}>
<TextInput style={styles.inputText} value={state.username} onChangeText={actions.setUsername}
autoCapitalize={'none'} placeholder={state.strings.username}
placeholderTextColor={Colors.inputPlaceholder} />
{ !state.validated && (
<View style={styles.inputVisibility}>
<MatIcons name="refresh" size={16} color={Colors.inputPlaceholder} />
</View>
)}
{ state.validated && state.available && (
<View style={styles.inputVisibility} activeOpacity={1} onPress={actions.hidePassword}>
<MatIcons name="check" size={16} color={Colors.activeFill} />
</View>
)}
{ state.validated && !state.available && (
<View style={styles.inputVisibility} activeOpacity={1} onPress={actions.hidePassword}>
<MatIcons name="block-helper" size={15} color={Colors.dangerText} />
</View>
)}
</View>
<View style={styles.modalInput}>
<TextInput style={styles.inputText} value={state.password} onChangeText={actions.setPassword}
autoCapitalize={'none'} secureTextEntry={state.hidePassword} placeholder={state.strings.password}
placeholderTextColor={Colors.inputPlaceholder} />
{ state.hidePassword && (
<TouchableOpacity style={styles.inputVisibility} activeOpacity={1} onPress={actions.showPassword}>
<MatIcons name="eye-outline" size={16} color={Colors.inputPlaceholder} />
</TouchableOpacity>
)}
{ !state.hidePassword && (
<TouchableOpacity style={styles.inputVisibility} activeOpacity={1} onPress={actions.hidePassword}>
<MatIcons name="eye-off-outline" size={16} color={Colors.inputPlaceholder} />
</TouchableOpacity>
)}
</View>
<View style={styles.modalInput}>
<TextInput style={styles.inputText} value={state.confirm} onChangeText={actions.setConfirm}
autoCapitalize={'none'} secureTextEntry={state.hideConfirm} placeholder={state.strings.confirmPassword}
placeholderTextColor={Colors.inputPlaceholder} />
{ state.hideConfirm && (
<TouchableOpacity style={styles.inputVisibility} activeOpacity={1} onPress={actions.showConfirm}>
<MatIcons name="eye-outline" size={16} color={Colors.inputPlaceholder} />
</TouchableOpacity>
)}
{ !state.hideConfirm && (
<TouchableOpacity style={styles.inputVisibility} activeOpacity={1} onPress={actions.showConfirm}>
<MatIcons name="eye-off-outline" size={16} color={Colors.inputPlaceholder} />
</TouchableOpacity>
)}
</View>
<View style={styles.buttons}>
{ (state.password !== state.confirm || !state.password || !state.validated || !state.username) && (
<View style={styles.disabledButton}>
<Text style={styles.disabledButtonText}>{ state.strings.update }</Text>
</View>
)}
{ state.password === state.confirm && state.password && state.validated && state.username && (
<TouchableOpacity style={styles.promptButton} activeOpacity={1} onPress={changeLogin}>
<Text style={styles.enabledButtonText}>{ state.strings.update }</Text>
</TouchableOpacity>
)}
</View>
</View>
</View>
</Modal>
</SafeAreaView> </SafeAreaView>
</ScrollView> </ScrollView>
); );

View File

@ -203,8 +203,8 @@ export const styles = StyleSheet.create({
fontFamily: 'Roboto', fontFamily: 'Roboto',
}, },
disabledButton: { disabledButton: {
marginTop: 32, marginTop: 8,
marginBottom: 16, marginBottom: 8,
paddingTop: 8, paddingTop: 8,
paddingBottom: 8, paddingBottom: 8,
paddingLeft: 32, paddingLeft: 32,
@ -230,5 +230,28 @@ export const styles = StyleSheet.create({
color: Colors.dangerButtonText, color: Colors.dangerButtonText,
fontFamily: 'Roboto', fontFamily: 'Roboto',
}, },
cancelButton: {
margin: 8,
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 32,
paddingRight: 32,
borderRadius: 4,
backgroundColor: Colors.cancelButton,
},
promptButton: {
margin: 8,
paddingTop: 8,
paddingBottom: 8,
paddingLeft: 32,
paddingRight: 32,
borderRadius: 4,
backgroundColor: Colors.primaryButton,
},
buttons: {
display: 'flex',
flexDirection: 'row',
padding: 8,
},
}); });

View File

@ -2,12 +2,17 @@ import { useState, useEffect, useRef, useContext } from 'react';
import { getLanguageStrings } from 'constants/Strings'; import { getLanguageStrings } from 'constants/Strings';
import { ProfileContext } from 'context/ProfileContext'; import { ProfileContext } from 'context/ProfileContext';
import { AccountContext } from 'context/AccountContext'; import { AccountContext } from 'context/AccountContext';
import { AppContext } from 'context/AppContext';
import { generateSeal, updateSeal, unlockSeal } from 'context/sealUtil'; import { generateSeal, updateSeal, unlockSeal } from 'context/sealUtil';
export function useSettings() { export function useSettings() {
const profile = useContext(ProfileContext); const profile = useContext(ProfileContext);
const account = useContext(AccountContext); const account = useContext(AccountContext);
const app = useContext(AppContext);
const debounce = useRef(null);
const checking = useRef(null);
const [state, setState] = useState({ const [state, setState] = useState({
strings: getLanguageStrings(), strings: getLanguageStrings(),
@ -15,6 +20,14 @@ export function useSettings() {
monthLast: false, monthLast: false,
pushEnabled: null, pushEnabled: null,
login: false,
username: null,
validated: false,
available: true,
password: null,
confirm: null,
logout: false,
editSeal: false, editSeal: false,
sealEnabled: false, sealEnabled: false,
sealUnlocked: false, sealUnlocked: false,
@ -33,8 +46,9 @@ export function useSettings() {
useEffect(() => { useEffect(() => {
const { timeFull, monthLast } = profile.state; const { timeFull, monthLast } = profile.state;
updateState({ timeFull, monthLast }); const handle = profile.state.identity.handle;
}, [profile.state.timeFull, profile.state.monthLast]); updateState({ timeFull, monthLast, handle });
}, [profile.state]);
useEffect(() => { useEffect(() => {
const { seal, sealable, pushEnabled } = account.state.status; const { seal, sealable, pushEnabled } = account.state.status;
@ -42,7 +56,6 @@ export function useSettings() {
const sealEnabled = seal?.publicKey != null; const sealEnabled = seal?.publicKey != null;
const sealUnlocked = seal?.publicKey === sealKey?.public && sealKey?.private && sealKey?.public; const sealUnlocked = seal?.publicKey === sealKey?.public && sealKey?.private && sealKey?.public;
updateState({ sealable, seal, sealKey, sealEnabled, sealUnlocked, pushEnabled }); updateState({ sealable, seal, sealKey, sealEnabled, sealUnlocked, pushEnabled });
}, [account.state]); }, [account.state]);
const unlockKey = async () => { const unlockKey = async () => {
@ -80,6 +93,42 @@ export function useSettings() {
setNotifications: async (flag) => { setNotifications: async (flag) => {
await account.actions.setNotifications(flag); await account.actions.setNotifications(flag);
}, },
showLogin: () => {
updateState({ login: true, username: state.handle, password: null, confirm: null, validated: true });
},
hideLogin: () => {
updateState({ login: false });
},
changeLogin: async () => {
await account.actions.setLogin(state.username, state.password);
},
setUsername: (username) => {
clearTimeout(debounce.current);
checking.current = username;
updateState({ username, validated: false });
debounce.current = setTimeout(async () => {
const cur = JSON.parse(JSON.stringify(username));
const available = await profile.actions.getHandleStatus(cur);
if (checking.current === cur) {
updateState({ available, validated: true });
}
}, 1000);
},
setPassword: (password) => {
updateState({ password });
},
setConfirm: (confirm) => {
updateState({ confirm });
},
logout: async () => {
await app.actions.logout();
},
showLogout: () => {
updateState({ logout: true });
},
hideLogout: () => {
updateState({ logout: false });
},
showEditSeal: () => { showEditSeal: () => {
updateState({ editSeal: true, sealPassword: null, sealConfirm: null, hidePassword: true, hideConfirm: true, updateState({ editSeal: true, sealPassword: null, sealConfirm: null, hidePassword: true, hideConfirm: true,
sealDelete: null, sealRemove: false, sealUpdate: false }); sealDelete: null, sealRemove: false, sealUpdate: false });