From bc75dd7e15c634824fd6bd24e28b75dfef7327cb Mon Sep 17 00:00:00 2001 From: Pierre Dommerc Date: Thu, 16 Feb 2023 12:11:45 +0100 Subject: [PATCH] NC Android - gateway settings (#3041) * feat(nc-android): gateway settings (client) * feat(nc-android): gateway settings (client) * feat(nc-android): handle init_config failing * feat(nc-android): some UI changes * feat(nc-android): some UI changes --- nym-connect-android/src-tauri/src/state.rs | 10 ++- .../src/components/AppWindowFrame.tsx | 22 ++++- .../src/components/CustomTitleBar.tsx | 17 ++-- nym-connect-android/src/context/main.tsx | 51 ++++++++++-- .../src/context/mocks/main.tsx | 2 + nym-connect-android/src/pages/menu/Apps.tsx | 13 ++- .../src/pages/menu/Settings.tsx | 80 +++++++++++++++++++ nym-connect-android/src/pages/menu/index.tsx | 9 ++- nym-connect-android/src/routes/index.tsx | 2 + nym-connect-android/src/theme/mui-theme.d.ts | 1 + nym-connect-android/src/theme/theme.tsx | 1 + nym-connect-android/src/types/gateway.ts | 4 + 12 files changed, 184 insertions(+), 28 deletions(-) create mode 100644 nym-connect-android/src/pages/menu/Settings.tsx create mode 100644 nym-connect-android/src/types/gateway.ts diff --git a/nym-connect-android/src-tauri/src/state.rs b/nym-connect-android/src-tauri/src/state.rs index 2012406358..0af63ce1e0 100644 --- a/nym-connect-android/src-tauri/src/state.rs +++ b/nym-connect-android/src-tauri/src/state.rs @@ -175,10 +175,16 @@ impl State { self.set_state(ConnectionStatusKind::Connecting, window); let res = self.init_config().await; - match &res { + match res { Ok(_) => {} Err(e) => { - dbg!(e); + log::error!("Failed to initialize: {e}"); + + // Wait a little to give the user some rudimentary feedback that the click actually + // registered. + tokio::time::sleep(Duration::from_secs(1)).await; + self.set_state(ConnectionStatusKind::Disconnected, window); + return Err(e); } }; let (config, keys) = res.unwrap(); diff --git a/nym-connect-android/src/components/AppWindowFrame.tsx b/nym-connect-android/src/components/AppWindowFrame.tsx index 4b407c3e7b..88a423d15e 100644 --- a/nym-connect-android/src/components/AppWindowFrame.tsx +++ b/nym-connect-android/src/components/AppWindowFrame.tsx @@ -1,10 +1,28 @@ import React from 'react'; import { Box } from '@mui/material'; import { useLocation } from 'react-router-dom'; +import { useClientContext } from 'src/context/main'; import { CustomTitleBar } from './CustomTitleBar'; export const AppWindowFrame: FCWithChildren = ({ children }) => { const location = useLocation(); + const { userDefinedGateway, setUserDefinedGateway } = useClientContext(); + + // defined functions to be used when moving away from pages + const onBack = () => { + switch (location.pathname) { + case '/menu/settings': + return () => { + // when the user moves away from the settings page and the gateway is not valid + // set isActive to false + if (!userDefinedGateway?.gateway) { + setUserDefinedGateway((current) => ({ ...current, isActive: false })); + } + }; + default: + return undefined; + } + }; return ( { height: '100vh', }} > - - {children} + + {children} ); }; diff --git a/nym-connect-android/src/components/CustomTitleBar.tsx b/nym-connect-android/src/components/CustomTitleBar.tsx index 15c3def1ad..43540d743b 100644 --- a/nym-connect-android/src/components/CustomTitleBar.tsx +++ b/nym-connect-android/src/components/CustomTitleBar.tsx @@ -28,16 +28,20 @@ const MenuIcon = () => { return navigate('/menu')} />; }; -const ArrowBackIcon = () => { +const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => { const navigate = useNavigate(); - return navigate(-1)} />; + const handleBack = () => { + onBack?.(); + navigate(-1); + }; + return ; }; const getTitleIcon = (path: string) => { if (path !== '/') { const title = path.split('/').slice(-1); return ( - + {title} ); @@ -45,10 +49,11 @@ const getTitleIcon = (path: string) => { return ; }; -export const CustomTitleBar = ({ path = '/' }: { path?: string }) => ( - +export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?: () => void }) => ( + {/* set width to keep logo centered */} - {path === '/' ? : } + {path === '/' ? : } {getTitleIcon(path)} + {path !== '/' && } ); diff --git a/nym-connect-android/src/context/main.tsx b/nym-connect-android/src/context/main.tsx index 8c02df4e41..a933a8c36f 100644 --- a/nym-connect-android/src/context/main.tsx +++ b/nym-connect-android/src/context/main.tsx @@ -4,10 +4,14 @@ import { invoke } from '@tauri-apps/api'; import { Error } from 'src/types/error'; import { getVersion } from '@tauri-apps/api/app'; import { useEvents } from 'src/hooks/events'; +import { UserDefinedGateway } from 'src/types/gateway'; +import { forage } from '@tauri-apps/tauri-forage'; import { ConnectionStatusKind, GatewayPerformance } from '../types'; import { ConnectionStatsItem } from '../components/ConnectionStats'; import { ServiceProvider } from '../types/directory'; +const FORAGE_KEY = 'nym-connect-user-gateway'; + type ModeType = 'light' | 'dark'; export type TClientContext = { @@ -20,6 +24,7 @@ export type TClientContext = { gatewayPerformance: GatewayPerformance; selectedProvider?: ServiceProvider; showInfoModal: boolean; + userDefinedGateway?: UserDefinedGateway; setMode: (mode: ModeType) => void; clearError: () => void; setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; @@ -29,6 +34,7 @@ export type TClientContext = { setRandomSerivceProvider: () => void; startConnecting: () => Promise; startDisconnecting: () => Promise; + setUserDefinedGateway: React.Dispatch>; }; export const ClientContext = createContext({} as TClientContext); @@ -44,19 +50,43 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { const [appVersion, setAppVersion] = useState(); const [gatewayPerformance, setGatewayPerformance] = useState('Good'); const [showInfoModal, setShowInfoModal] = useState(false); + const [userDefinedGateway, setUserDefinedGateway] = useState({ isActive: false, gateway: '' }); const getAppVersion = async () => { const version = await getVersion(); return version; }; + const setUserGatewayInStorage = async (gateway: UserDefinedGateway) => { + try { + await forage.setItem({ + key: FORAGE_KEY, + value: gateway, + } as any)(); + } catch (e) { + console.warn(e); + } + return undefined; + }; + + const getUserGatewayFromStorage = async (): Promise => { + try { + const gatewayFromStorage = await forage.getItem({ key: FORAGE_KEY })(); + return gatewayFromStorage; + } catch (e) { + console.warn(e); + } + return undefined; + }; + const initialiseApp = async () => { const services = await invoke('get_services'); const AppVersion = await getAppVersion(); - console.log(services); + const storedUserDefinedGateway = await getUserGatewayFromStorage(); setAppVersion(AppVersion); setServiceProviders(services as ServiceProvider[]); + if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway); }; useEvents({ @@ -94,15 +124,19 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { } }, []); - const setServiceProvider = async (newServiceProvider?: ServiceProvider) => { - if (newServiceProvider) { - await invoke('set_gateway', { gateway: newServiceProvider.gateway }); - await invoke('set_service_provider', { serviceProvider: newServiceProvider.address }); - } + const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive; + + const setServiceProvider = async (newServiceProvider: ServiceProvider) => { + await invoke('set_gateway', { + gateway: newServiceProvider.gateway, + }); + await invoke('set_service_provider', { serviceProvider: newServiceProvider.address }); }; const getRandomSPFromList = (services: ServiceProvider[]) => { const randomSelection = services[Math.floor(Math.random() * services.length)]; + + if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider; return randomSelection; }; @@ -110,8 +144,10 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { if (serviceProviders) { const randomServiceProvider = getRandomSPFromList(serviceProviders); await setServiceProvider(randomServiceProvider); + await setUserGatewayInStorage(userDefinedGateway); setSelectedProvider(randomServiceProvider); } + return undefined; }; const clearError = () => setError(undefined); @@ -136,6 +172,8 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { startDisconnecting, gatewayPerformance, setShowInfoModal, + userDefinedGateway, + setUserDefinedGateway, }), [ mode, @@ -148,6 +186,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { connectedSince, gatewayPerformance, selectedProvider, + userDefinedGateway, ], ); diff --git a/nym-connect-android/src/context/mocks/main.tsx b/nym-connect-android/src/context/mocks/main.tsx index ea86bb365d..6d0f7d9a82 100644 --- a/nym-connect-android/src/context/mocks/main.tsx +++ b/nym-connect-android/src/context/mocks/main.tsx @@ -9,6 +9,7 @@ const mockValues: TClientContext = { selectedProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, gatewayPerformance: 'Good', showInfoModal: false, + userDefinedGateway: { isActive: false, gateway: '' }, setShowInfoModal: () => {}, setMode: () => {}, clearError: () => {}, @@ -18,6 +19,7 @@ const mockValues: TClientContext = { startConnecting: async () => {}, startDisconnecting: async () => {}, setRandomSerivceProvider: () => {}, + setUserDefinedGateway: () => {}, }; export const MockProvider: FCWithChildren<{ diff --git a/nym-connect-android/src/pages/menu/Apps.tsx b/nym-connect-android/src/pages/menu/Apps.tsx index ebe31deb44..d75a7a5536 100644 --- a/nym-connect-android/src/pages/menu/Apps.tsx +++ b/nym-connect-android/src/pages/menu/Apps.tsx @@ -9,28 +9,25 @@ const appsSchema = { export const CompatibleApps = () => ( - + Supported apps - + Messaging apps - - - + {appsSchema.messagingApps.map((app) => ( {app} ))} - + + Wallets - - {appsSchema.wallets.map((wallet) => ( diff --git a/nym-connect-android/src/pages/menu/Settings.tsx b/nym-connect-android/src/pages/menu/Settings.tsx new file mode 100644 index 0000000000..f200c2c605 --- /dev/null +++ b/nym-connect-android/src/pages/menu/Settings.tsx @@ -0,0 +1,80 @@ +import React, { ChangeEvent, useState } from 'react'; +import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; +import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material'; +import { useClientContext } from 'src/context/main'; +import { ConnectionStatusKind } from 'src/types'; +import { AppVersion } from 'src/components/AppVersion'; + +export const Settings = () => { + const { userDefinedGateway, setUserDefinedGateway } = useClientContext(); + const [gatewayKey, setGatewayKey] = useState(userDefinedGateway?.gateway); + + const handleIsValidGatewayKey = (isValid: boolean) => { + let gateway: string | undefined; + + if (isValid) { + gateway = gatewayKey; + } + + setUserDefinedGateway((current) => ({ ...current, gateway })); + }; + + const handleChange = (e: ChangeEvent) => { + console.warn('HANERE***'); + setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked })); + }; + + const { connectionStatus } = useClientContext(); + + return ( + + + + + Select your Gateway + + + Use a gateway of your choice + + + + } + label={userDefinedGateway?.isActive ? 'On' : 'Off'} + /> + {connectionStatus === ConnectionStatusKind.connected && ( + This setting is disabled during an active connection + )} + {userDefinedGateway?.isActive && ( + + )} + + + + + To find a gateway go to{' '} + + explorer.nymtech.net/network-components/gateways + + + + + + + ); +}; diff --git a/nym-connect-android/src/pages/menu/index.tsx b/nym-connect-android/src/pages/menu/index.tsx index d58a08c25f..47840d6668 100644 --- a/nym-connect-android/src/pages/menu/index.tsx +++ b/nym-connect-android/src/pages/menu/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Apps, HelpOutline } from '@mui/icons-material'; +import { Apps, HelpOutline, Settings } from '@mui/icons-material'; import { Stack, Link, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; import { AppVersion } from 'src/components/AppVersion'; @@ -7,15 +7,16 @@ import { AppVersion } from 'src/components/AppVersion'; const menuSchema = [ { title: 'Supported apps', icon: Apps, path: 'apps' }, { title: 'How to connect guide', icon: HelpOutline, path: 'guide' }, + { title: 'Settings', icon: Settings, path: 'settings' }, ]; export const Menu = () => ( - + {menuSchema.map((item) => ( - + - + {' '} diff --git a/nym-connect-android/src/routes/index.tsx b/nym-connect-android/src/routes/index.tsx index ffb0881876..ec24080c6c 100644 --- a/nym-connect-android/src/routes/index.tsx +++ b/nym-connect-android/src/routes/index.tsx @@ -4,6 +4,7 @@ import { ConnectionPage } from 'src/pages/connection'; import { Menu } from 'src/pages/menu'; import { CompatibleApps } from 'src/pages/menu/Apps'; import { HelpGuide } from 'src/pages/menu/Guide'; +import { Settings } from 'src/pages/menu/Settings'; export const AppRoutes = () => ( @@ -12,6 +13,7 @@ export const AppRoutes = () => ( } /> } /> } /> + } /> ); diff --git a/nym-connect-android/src/theme/mui-theme.d.ts b/nym-connect-android/src/theme/mui-theme.d.ts index 6c4d88fe39..76db4b1a99 100644 --- a/nym-connect-android/src/theme/mui-theme.d.ts +++ b/nym-connect-android/src/theme/mui-theme.d.ts @@ -29,6 +29,7 @@ declare module '@mui/material/styles' { */ interface NymPalette { highlight: string; + cta: string; success: string; warning: string; info: string; diff --git a/nym-connect-android/src/theme/theme.tsx b/nym-connect-android/src/theme/theme.tsx index 58a1becba1..23c8ff8b57 100644 --- a/nym-connect-android/src/theme/theme.tsx +++ b/nym-connect-android/src/theme/theme.tsx @@ -21,6 +21,7 @@ import { const nymPalette: NymPalette = { /** emphasises important elements */ highlight: '#21D072', + cta: '#FB6E4E', success: '#21D073', info: '#60D7EF', warning: '#FFE600', diff --git a/nym-connect-android/src/types/gateway.ts b/nym-connect-android/src/types/gateway.ts new file mode 100644 index 0000000000..735a3cdb03 --- /dev/null +++ b/nym-connect-android/src/types/gateway.ts @@ -0,0 +1,4 @@ +export interface UserDefinedGateway { + isActive: boolean; + gateway?: string; +}