updating sealing modal

This commit is contained in:
balzack 2023-08-28 11:36:00 -07:00
parent 249a96936a
commit 41b72427c4
3 changed files with 450 additions and 4 deletions

View File

@ -1,15 +1,29 @@
import { useState } from 'react'; import { ActivityIndicator, KeyboardAvoidingView, Modal, ScrollView, View, Switch, Text, TextInput, TouchableOpacity, Alert } from 'react-native';
import { ScrollView, View, Text, Switch, TouchableOpacity } from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { styles } from './Settings.styled'; import { styles } from './Settings.styled';
import { useSettings } from './useSettings.hook'; import { useSettings } from './useSettings.hook';
import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Ionicons from 'react-native-vector-icons/AntDesign';
import Colors from 'constants/Colors'; import Colors from 'constants/Colors';
export function Settings() { export function Settings() {
const { state, actions } = useSettings(); const { state, actions } = useSettings();
const saveSeal = async () => {
try {
await actions.saveSeal();
actions.hideEditSeal();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Update Topic Sealing',
'Please try again.',
)
}
}
return ( return (
<ScrollView style={styles.content}> <ScrollView style={styles.content}>
<SafeAreaView edges={['top']}> <SafeAreaView edges={['top']}>
@ -28,7 +42,7 @@ export function Settings() {
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<View style={styles.divider} /> <View style={styles.divider} />
<TouchableOpacity style={styles.entry} activeOpacity={1}> <TouchableOpacity style={styles.entry} activeOpacity={1} onPress={actions.showEditSeal}>
<View style={styles.icon}> <View style={styles.icon}>
<MatIcons name="lock-outline" size={20} color={Colors.linkText} /> <MatIcons name="lock-outline" size={20} color={Colors.linkText} />
</View> </View>
@ -172,6 +186,164 @@ export function Settings() {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<Modal
animationType="fade"
transparent={true}
visible={state.editSeal}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideEditSeal}
>
<KeyboardAvoidingView behavior="height" style={styles.modalWrapper}>
<View style={styles.modalContainer}>
<Text style={styles.modalHeader}>Sealed Topics:</Text>
<View style={styles.sealable}>
<TouchableOpacity onPress={() => actions.setSealEnable(!state.sealEnabled)} activeOpacity={1}>
<Text style={styles.sealableText}>Enable Sealed Topics</Text>
</TouchableOpacity>
<Switch style={styles.enableSwitch} value={state.sealEnabled} onValueChange={actions.setSealEnable} trackColor={styles.switch}/>
</View>
{ state.sealMode === 'unlocking' && (
<>
{ !state.showSealUnlock && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealUnlock} onChangeText={actions.setSealUnlock}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.showSealUnlock}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ state.showSealUnlock && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealUnlock} onChangeText={actions.setSealUnlock}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.hideSealUnlock}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
</>
)}
{ (state.sealMode === 'updating' || state.sealMode === 'enabling') && (
<>
{ !state.showSealPassword && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealPassword} onChangeText={actions.setSealPassword}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.showSealPassword}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ state.showSealPassword && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealPassword} onChangeText={actions.setSealPassword}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Password for Seal"
placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.hideSealPassword}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ !state.showSealConfirm && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealConfirm} onChangeText={actions.setSealConfirm}
autoCapitalize={'none'} secureTextEntry={true} placeholder="Confirm Password"
placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.showSealConfirm}>
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
{ state.showSealConfirm && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.sealConfirm} onChangeText={actions.setSealConfirm}
autoCapitalize={'none'} secureTextEntry={false} placeholder="Confirm Password"
placeholderTextColor={Colors.grey} />
<TouchableOpacity onPress={actions.hideSealConfirm}>
<Ionicons style={styles.icon} name="eye" size={18} color="#888888" />
</TouchableOpacity>
</View>
)}
<Text style={styles.notice}>saving can take a few minutes</Text>
</>
)}
{ state.sealMode === 'disabling' && (
<View style={styles.inputField}>
<Ionicons style={styles.warn} name="exclamationcircleo" size={18} color="#888888" />
<TextInput style={styles.input} value={state.sealDelete} onChangeText={actions.setSealDelete}
autoCapitalize={'none'} placeholder="Type 'delete' to remove sealing key"
placeholderTextColor={Colors.grey} />
</View>
)}
{ state.sealMode === 'unlocked' && (
<View style={styles.inputField}>
<TextInput style={styles.input} value={'xxxxxxxx'} editable={false} secureTextEntry={true} />
<Ionicons style={styles.icon} name="eyeo" size={18} color="#888888" />
<TouchableOpacity style={styles.sealUpdate} onPress={actions.updateSeal} />
</View>
)}
<View style={styles.modalControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideEditSeal}>
<Text style={styles.canceltext}>Cancel</Text>
</TouchableOpacity>
{ state.canSaveSeal && (
<>
{ state.sealMode !== 'unlocking' && state.sealMode !== 'unlocked' && (
<TouchableOpacity style={styles.save} onPress={saveSeal}>
{ state.saving && (
<ActivityIndicator style={styles.activity} color={Colors.white} />
)}
<Text style={styles.saveText}>Save</Text>
</TouchableOpacity>
)}
{ state.sealMode === 'unlocked' && (
<TouchableOpacity style={styles.save} onPress={saveSeal}>
{ state.saving && (
<ActivityIndicator style={styles.activity} color={Colors.white} />
)}
<Text style={styles.saveText}>Forget</Text>
</TouchableOpacity>
)}
{ state.sealMode === 'unlocking' && (
<TouchableOpacity style={styles.save} onPress={saveSeal}>
{ state.saving && (
<ActivityIndicator style={styles.activity} color={Colors.white} />
)}
<Text style={styles.saveText}>Unlock</Text>
</TouchableOpacity>
)}
</>
)}
{ !state.canSaveSeal && (
<>
{ state.sealMode !== 'unlocking' && (
<View style={styles.disabled}>
{ state.saving && (
<ActivityIndicator style={styles.activity} color={Colors.white} />
)}
<Text style={styles.disabledText}>Save</Text>
</View>
)}
{ state.sealMode === 'unlocking' && (
<View style={styles.disabled}>
{ state.saving && (
<ActivityIndicator style={styles.activity} color={Colors.white} />
)}
<Text style={styles.disabledText}>Unlock</Text>
</View>
)}
</>
)}
</View>
</View>
</KeyboardAvoidingView>
</Modal>
</SafeAreaView> </SafeAreaView>
</ScrollView> </ScrollView>
); );

View File

@ -104,5 +104,127 @@ export const styles = StyleSheet.create({
notifications: { notifications: {
transform: [{ scaleX: .6 }, { scaleY: .6 }], transform: [{ scaleX: .6 }, { scaleY: .6 }],
}, },
modalWrapper: {
display: 'flex',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(52, 52, 52, 0.8)'
},
modalContainer: {
backgroundColor: Colors.formBackground,
padding: 16,
width: '80%',
maxWidth: 400,
},
modalHeader: {
fontSize: 18,
paddingBottom: 16,
color: Colors.text,
},
input: {
fontSize: 14,
flexGrow: 1,
color: Colors.text,
},
inputField: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 8,
marginBottom: 8,
maxHeight: 92,
display: 'flex',
flexDirection: 'row',
},
canceltext: {
color: Colors.text,
},
modalList: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 2,
},
modalControls: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
cancel: {
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 8,
marginRight: 8,
width: 88,
display: 'flex',
alignItems: 'center',
},
save: {
padding: 8,
borderRadius: 4,
backgroundColor: Colors.primary,
width: 88,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
saveText: {
color: Colors.white,
},
disabled: {
borderWidth: 1,
borderColor: Colors.lightgrey,
padding: 8,
borderRadius: 4,
width: 88,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
activity: {
paddingRight: 4,
},
disabledText: {
color: Colors.disabled,
},
sealUpdate: {
position: 'absolute',
top: 0,
height: 36,
left: 8,
width: '100%',
},
sealableText: {
color: Colors.text,
},
sealable: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 16,
},
enable: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
marginBottom: 4,
},
enableText: {
color: Colors.primary,
fontSize: 16,
},
enableSwitch: {
transform: [{ scaleX: .6 }, { scaleY: .6 }],
},
}); });

View File

@ -1,15 +1,34 @@
import { useState, useEffect, useRef, useContext } from 'react'; 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 { 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 [state, setState] = useState({ const [state, setState] = useState({
strings: getLanguageStrings(), strings: getLanguageStrings(),
timeFull: false, timeFull: false,
monthLast: false, monthLast: false,
editSeal: false,
sealMode: null,
sealUnlock: null,
sealPassword: null,
sealConfirm: null,
sealDelete: null,
seal: null,
sealKey: null,
sealEnabled: false,
sealUnlocked: false,
sealable: false,
canSaveSeal: false,
showSealUnlock: false,
showSealConfirm: false,
showSealPassword: false,
}); });
const updateState = (value) => { const updateState = (value) => {
@ -19,7 +38,57 @@ export function useSettings() {
useEffect(() => { useEffect(() => {
const { timeFull, monthLast } = profile.state; const { timeFull, monthLast } = profile.state;
updateState({ timeFull, monthLast }); updateState({ timeFull, monthLast });
}, [profile.state]); }, [profile.state.timeFull, profile.state.monthLast]);
useEffect(() => {
const { seal, sealable } = account.state.status;
const sealKey = account.state.sealKey;
const sealEnabled = seal?.publicKey != null;
const sealUnlocked = seal?.publicKey === sealKey?.public && sealKey?.private && sealKey?.public;
updateState({ sealable, seal, sealKey, sealEnabled, sealUnlocked });
}, [account.state]);
const unlock = async () => {
const sealKey = unlockSeal(state.seal, state.sealUnlock);
await account.actions.unlockAccountSeal(sealKey);
};
const forget = async () => {
await account.actions.unlockAccountSeal({});
}
const update = async () => {
const updated = updateSeal(state.seal, state.sealKey, state.sealPassword);
await account.actions.setAccountSeal(updated.seal, updated.sealKey);
}
const enable = async () => {
const created = await generateSeal(state.sealPassword);
await account.actions.setAccountSeal(created.seal, created.sealKey);
}
const disable = async () => {
await account.actions.setAccountSeal({}, {});
}
useEffect(() => {
if (state.sealMode === 'unlocked') {
return updateState({ canSaveSeal: true });
}
if (state.sealMode === 'unlocking' && state.sealUnlock != null && state.sealUnlock !== '') {
return updateState({ canSaveSeal: true });
}
if (state.sealMode === 'enabling' && state.sealPassword != null && state.sealPassword === state.sealConfirm) {
return updateState({ canSaveSeal: true });
}
if (state.sealMode === 'disabling' && state.sealDelete === 'delete') {
return updateState({ canSaveSeal: true });
}
if (state.sealMode === 'updating' && state.sealPassword != null && state.sealPassword === state.sealConfirm) {
return updateState({ canSaveSeal: true });
}
updateState({ canSaveSeal: false });
}, [state.sealMode, state.sealable, state.sealUnlock, state.sealPassword, state.sealConfirm, state.sealDelete]);
const actions = { const actions = {
setTimeFull: async (flag) => { setTimeFull: async (flag) => {
@ -30,6 +99,89 @@ export function useSettings() {
updateState({ monthLast: flag }); updateState({ monthLast: flag });
await profile.actions.setMonthLast(flag); await profile.actions.setMonthLast(flag);
}, },
showEditSeal: () => {
let sealMode = null;
const sealable = state.sealEnabled;
if (state.sealEnabled && !state.sealUnlocked) {
sealMode = 'unlocking';
}
else if (state.sealEnabled && state.sealUnlocked) {
sealMode = 'unlocked';
}
else {
sealMode = 'disabled';
}
updateState({ editSeal: true, sealMode, sealUnlock: null, sealPassword: null, sealConfirm: null, sealDelete: null });
},
hideEditSeal: () => {
updateState({ editSeal: false });
},
setSealUnlock: (sealUnlock) => {
updateState({ sealUnlock });
},
setSealPassword: (sealPassword) => {
updateState({ sealPassword });
},
setSealConfirm: (sealConfirm) => {
updateState({ sealConfirm });
},
setSealDelete: (sealDelete) => {
updateState({ sealDelete });
},
showSealUnlock: () => {
updateState({ showSealUnlock: true });
},
hideSealUnlock: () => {
updateState({ showSealUnlock: false });
},
showSealPassword: () => {
updateState({ showSealPassword: true });
},
hideSealPassword: () => {
updateState({ showSealPassword: false });
},
showSealConfirm: () => {
updateState({ showSealConfirm: true });
},
hideSealConfirm: () => {
updateState({ showSealConfirm: false });
},
updateSeal: () => {
updateState({ sealMode: 'updating' });
},
saveSeal: async () => {
if (!state.saving) {
try {
updateState({ saving: true });
if (state.sealMode === 'enabling') {
await enable();
}
else if (state.sealMode === 'disabling') {
await disable();
}
else if (state.sealMode === 'unlocking') {
await unlock();
}
else if (state.sealMode === 'unlocked') {
await forget();
}
else if (state.sealMode === 'updating') {
await update();
}
else {
console.log(state.sealMode);
}
updateState({ saving: false });
}
catch(err) {
console.log(err);
updateState({ saving: false });
throw new Error('seal operation failed');
}
}
},
}; };
return { state, actions }; return { state, actions };