diff --git a/app/mobile/src/session/settings/Settings.jsx b/app/mobile/src/session/settings/Settings.jsx index ea4f744a..6bd8f432 100644 --- a/app/mobile/src/session/settings/Settings.jsx +++ b/app/mobile/src/session/settings/Settings.jsx @@ -1,15 +1,29 @@ -import { useState } from 'react'; -import { ScrollView, View, Text, Switch, TouchableOpacity } from 'react-native'; +import { ActivityIndicator, KeyboardAvoidingView, Modal, ScrollView, View, Switch, Text, TextInput, TouchableOpacity, Alert } from 'react-native'; import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; import { styles } from './Settings.styled'; import { useSettings } from './useSettings.hook'; import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import Ionicons from 'react-native-vector-icons/AntDesign'; import Colors from 'constants/Colors'; export function Settings() { 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 ( @@ -28,7 +42,7 @@ export function Settings() { - + @@ -172,6 +186,164 @@ export function Settings() { + + + + Sealed Topics: + + actions.setSealEnable(!state.sealEnabled)} activeOpacity={1}> + Enable Sealed Topics + + + + { state.sealMode === 'unlocking' && ( + <> + { !state.showSealUnlock && ( + + + + + + + )} + { state.showSealUnlock && ( + + + + + + + )} + + )} + { (state.sealMode === 'updating' || state.sealMode === 'enabling') && ( + <> + { !state.showSealPassword && ( + + + + + + + )} + { state.showSealPassword && ( + + + + + + + )} + { !state.showSealConfirm && ( + + + + + + + )} + { state.showSealConfirm && ( + + + + + + + )} + saving can take a few minutes + + )} + { state.sealMode === 'disabling' && ( + + + + + )} + { state.sealMode === 'unlocked' && ( + + + + + + )} + + + Cancel + + { state.canSaveSeal && ( + <> + { state.sealMode !== 'unlocking' && state.sealMode !== 'unlocked' && ( + + { state.saving && ( + + )} + Save + + )} + { state.sealMode === 'unlocked' && ( + + { state.saving && ( + + )} + Forget + + )} + { state.sealMode === 'unlocking' && ( + + { state.saving && ( + + )} + Unlock + + )} + + )} + { !state.canSaveSeal && ( + <> + { state.sealMode !== 'unlocking' && ( + + { state.saving && ( + + )} + Save + + )} + { state.sealMode === 'unlocking' && ( + + { state.saving && ( + + )} + Unlock + + )} + + )} + + + + + + ); diff --git a/app/mobile/src/session/settings/Settings.styled.js b/app/mobile/src/session/settings/Settings.styled.js index 85278c0e..3116913f 100644 --- a/app/mobile/src/session/settings/Settings.styled.js +++ b/app/mobile/src/session/settings/Settings.styled.js @@ -104,5 +104,127 @@ export const styles = StyleSheet.create({ notifications: { 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 }], + }, }); diff --git a/app/mobile/src/session/settings/useSettings.hook.js b/app/mobile/src/session/settings/useSettings.hook.js index 5cf92f6a..6f6b54c1 100644 --- a/app/mobile/src/session/settings/useSettings.hook.js +++ b/app/mobile/src/session/settings/useSettings.hook.js @@ -1,15 +1,34 @@ import { useState, useEffect, useRef, useContext } from 'react'; import { getLanguageStrings } from 'constants/Strings'; import { ProfileContext } from 'context/ProfileContext'; +import { AccountContext } from 'context/AccountContext'; +import { generateSeal, updateSeal, unlockSeal } from 'context/sealUtil'; export function useSettings() { const profile = useContext(ProfileContext); + const account = useContext(AccountContext); const [state, setState] = useState({ strings: getLanguageStrings(), timeFull: 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) => { @@ -19,7 +38,57 @@ export function useSettings() { useEffect(() => { const { timeFull, monthLast } = profile.state; 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 = { setTimeFull: async (flag) => { @@ -30,6 +99,89 @@ export function useSettings() { updateState({ monthLast: 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 };