From 32aaa05200fc1b3a5cd2729a4650c89dc9be03c1 Mon Sep 17 00:00:00 2001 From: balzack Date: Tue, 22 Oct 2024 22:08:23 -0700 Subject: [PATCH] adding contact profile screen --- app/client/mobile/App.tsx | 2 +- app/client/mobile/src/constants/Strings.ts | 48 +++++ .../mobile/src/profile/Profile.styled.ts | 181 ++++++++++++++++++ app/client/mobile/src/profile/Profile.tsx | 108 ++++++++++- .../mobile/src/profile/useProfile.hook.ts | 146 ++++++++++++++ app/client/mobile/src/session/Session.tsx | 42 +++- app/client/mobile/src/settings/Settings.tsx | 4 +- 7 files changed, 512 insertions(+), 19 deletions(-) create mode 100644 app/client/mobile/src/profile/Profile.styled.ts create mode 100644 app/client/mobile/src/profile/useProfile.hook.ts diff --git a/app/client/mobile/App.tsx b/app/client/mobile/App.tsx index dfa393bd..4e089af3 100644 --- a/app/client/mobile/App.tsx +++ b/app/client/mobile/App.tsx @@ -86,7 +86,7 @@ const databagColors = { inverseOnSurface: 'rgb(46, 49, 46)', inversePrimary: 'rgb(0, 108, 71)', elevation: { - level0: 'rgb(25, 28, 26)', + level0: 'rgb(0, 0, 0)', level1: 'rgb(29, 38, 33)', level2: 'rgb(32, 43, 37)', level3: 'rgb(35, 49, 41)', diff --git a/app/client/mobile/src/constants/Strings.ts b/app/client/mobile/src/constants/Strings.ts index 3531554c..ea3bd347 100644 --- a/app/client/mobile/src/constants/Strings.ts +++ b/app/client/mobile/src/constants/Strings.ts @@ -2,6 +2,14 @@ import {NativeModules, Platform} from 'react-native'; const Strings = [ { + unknownStatus: 'Unsaved Contact', + savedStatus: 'Saved Contact', + pendingStatus: 'Unknown Contact Request', + connectingStatus: 'Connection Requested', + requestedStatus: 'Connection Requested by Contact', + connectedStatus: 'Connected Contact', + offsyncStatus: 'Offsync Contact', + // settings screen languageCode: 'en', visibleRegistry: 'Visible in Registry', @@ -219,6 +227,14 @@ const Strings = [ 'Are you sure you want to disable multi-factor authentication', }, { + unknownStatus: 'Contact Inconnu', + savedStatus: 'Contact Enregistré', + pendingStatus: 'Demande de Contact Inconnue', + connectingStatus: 'Demande de Connexion Envoyée', + requestedStatus: 'Demande de Connexion par le Contact', + connectedStatus: 'Contact Connecté', + offsyncStatus: 'Contact Désynchronisé', + languageCode: 'fr', visibleRegistry: 'Visible dans le Registre', edit: 'Modifier', @@ -434,6 +450,14 @@ const Strings = [ "Êtes-vous sûr de vouloir désactiver l'authentification multi-facteurs", }, { + unknownStatus: 'Contacto Desconocido', + savedStatus: 'Contacto Guardado', + pendingStatus: 'Solicitud de Contacto Desconocido', + connectingStatus: 'Solicitud de Conexión Enviada', + requestedStatus: 'Solicitud de Conexión por el Contacto', + connectedStatus: 'Contacto Conectado', + offsyncStatus: 'Contacto Fuera de Sincronización', + languageCode: 'es', visibleRegistry: 'Visible en el Registro', edit: 'Editar', @@ -648,6 +672,14 @@ const Strings = [ '¿Estás seguro de que quieres desactivar la autenticación de dos factores?', }, { + unknownStatus: 'Unbekannter Kontakt', + savedStatus: 'Gespeicherter Kontakt', + pendingStatus: 'Unbekannte Kontaktanfrage', + connectingStatus: 'Verbindungsanfrage Gesendet', + requestedStatus: 'Verbindungsanfrage vom Kontakt', + connectedStatus: 'Verbunden Kontakt', + offsyncStatus: 'Unsynchronisierter Kontakt', + languageCode: 'de', visibleRegistry: 'Sichtbar in der Registrierung', edit: 'Bearbeiten', @@ -863,6 +895,14 @@ const Strings = [ 'Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung deaktivieren möchten?', }, { + unknownStatus: 'Contato Desconhecido', + savedStatus: 'Contato Salvo', + pendingStatus: 'Solicitação de Contato Desconhecido', + connectingStatus: 'Solicitação de Conexão Enviada', + requestedStatus: 'Solicitação de Conexão pelo Contato', + connectedStatus: 'Contato Conectado', + offsyncStatus: 'Contato Fora de Sincronização', + languageCode: 'pt', visibleRegistry: 'Visível no Registro', edit: 'Editar', @@ -1063,6 +1103,14 @@ const Strings = [ 'Tem certeza de que deseja desativar a autenticação de dois fatores?', }, { + unknownStatus: 'Неизвестный Контакт', + savedStatus: 'Сохранённый Контакт', + pendingStatus: 'Запрос Неизвестного Контакта', + connectingStatus: 'Запрос на Подключение Отправлен', + requestedStatus: 'Запрос на Подключение от Контакта', + connectedStatus: 'Подключённый Контакт', + offsyncStatus: 'Несинхронизированный Контакт', + languageCode: 'ru', visibleRegistry: 'Видимый в реестре', edit: 'Редактировать', diff --git a/app/client/mobile/src/profile/Profile.styled.ts b/app/client/mobile/src/profile/Profile.styled.ts new file mode 100644 index 00000000..56f35765 --- /dev/null +++ b/app/client/mobile/src/profile/Profile.styled.ts @@ -0,0 +1,181 @@ +import {StyleSheet} from 'react-native'; +import {Colors} from '../constants/Colors'; + +export const styles = StyleSheet.create({ + profile: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + width: '100%', + height: '100%', + }, + body: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + width: '100%', + height: '100%', + paddingLeft: 16, + paddingRight: 16, + }, + header: { + position: 'relative', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingRight: 8, + paddingLeft: 8, + width: '100%', + zIndex: 1, + }, + headerLabel: { + flexShrink: 1, + fontSize: 22, + textAlign: 'center', + textWrap: 'nowrap', + textOverflow: 'ellipsis', + overflow: 'hidden', + paddingTop: 8, + flexGrow: 1, + verticalAlign: 'baseline', + lineHeight: 22, + }, + spaceHolder: { + width: 64, + }, + back: { + flexShrink: 0, + marginRight: 0, + marginLeft: 0, + backgroundColor: 'transparent', + }, + image: { + position: 'relative', + width: '90%', + maxWidth: 250, + marginTop: 16, + marginBottom: 8, + }, + logo: { + aspectRatio: 1, + resizeMode: 'contain', + borderRadius: 8, + width: null, + height: null, + borderWidth: 1, + }, + line: { + marginTop: 16, + marginBottom: 16, + height: 2, + width: '100%', + }, + attributes: { + display: 'flex', + flexDirection: 'column', + gap: 8, + width: '100%', + paddingTop: 12, + }, + attribute: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingLeft: 32, + paddingRight: 32, + }, + icon: { + flexShrink: 0, + width: 32, + display: 'flex', + justifyContent: 'flex-begin', + height: '100%', + backgroundColor: 'transparent', + }, + label: { + fontSize: 16, + }, + labelUnset: { + fontSize: 16, + fontStyle: 'italic', + flexGrow: 1, + }, + labelSet: { + fontSize: 16, + flexGrow: 1, + }, + nameSet: { + fontSize: 24, + width: '100%', + paddingLeft: 32, + paddingRight: 72, + }, + nameUnset: { + fontSize: 24, + width: '100%', + paddingLeft: 32, + paddingRight: 32, + fontStyle: 'italic', + }, + status: { + flexShrink: 1, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + unknownStatus: { + color: Colors.unsaved, + fontSize: 14, + }, + savedStatus: { + color: Colors.confirmed, + fontSize: 14, + }, + pendingStatus: { + color: Colors.pending, + fontSize: 14, + }, + requestedStatus: { + color: Colors.requested, + fontSize: 14, + }, + connectingStatus: { + color: Colors.connecting, + fontSize: 14, + }, + connectedStatus: { + color: Colors.connected, + fontSize: 14, + }, + offsyncStatus: { + color: Colors.offsync, + fontSize: 14, + }, + modal: { + display: 'flex', + width: '100%', + height: '100%', + alignItems: 'center', + }, + blur: { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + }, + content: { + display: 'flex', + justifyContent: 'center', + height: '100%', + gap: 8, + }, + close: { + paddingTop: 8, + }, + surface: { + padding: 16, + }, +}); diff --git a/app/client/mobile/src/profile/Profile.tsx b/app/client/mobile/src/profile/Profile.tsx index 808cf8f5..cec0b67e 100644 --- a/app/client/mobile/src/profile/Profile.tsx +++ b/app/client/mobile/src/profile/Profile.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { IconButton } from 'react-native-paper'; -import { SafeAreaView, View } from 'react-native'; +import React, {useState} from 'react'; +import { Button, Surface, Icon, Text, IconButton, Divider } from 'react-native-paper'; +import { Modal, Image, SafeAreaView, View } from 'react-native'; import { styles } from './Profile.styled'; import { useProfile } from './useProfile.hook'; +import {BlurView} from '@react-native-community/blur'; export type ContactParams = { guid: string; @@ -17,14 +18,107 @@ export type ContactParams = { offsync?: boolean; } -export function Profile({ close }) { +export function Profile({ close, params }) { + const [ alert, setAlert ] = useState(false); + const { state, actions } = useProfile(params); + return ( - { close && ( - - )} + + { close && ( + + )} + + {`${state.handle}${ + state.node ? '/' + state.node : '' + }`} + + + + + + + + + + {!state.name && ( + {state.strings.name} + )} + {state.name && ( + + {state.name} + + )} + + + + + {!state.location && ( + {state.strings.location} + )} + {state.location && ( + {state.location} + )} + + + + + + {!state.description && ( + + {state.strings.description} + + )} + {state.description && ( + {state.description} + )} + + + + + { state.strings[state.statusLabel] } + + + setAlert(false)}> + + + + + {state.strings.error} + {state.strings.tryAgain} + + + + + ) } + diff --git a/app/client/mobile/src/profile/useProfile.hook.ts b/app/client/mobile/src/profile/useProfile.hook.ts new file mode 100644 index 00000000..01fe3fae --- /dev/null +++ b/app/client/mobile/src/profile/useProfile.hook.ts @@ -0,0 +1,146 @@ +import { useState, useContext, useEffect } from 'react' +import { AppContext } from '../context/AppContext' +import { DisplayContext } from '../context/DisplayContext'; +import { ContextType } from '../context/ContextType' +import { Card } from 'databag-client-sdk' +import { ContactParams } from './Profile'; + +export function useProfile(params: ProfileParams) { + + const app = useContext(AppContext) as ContextType + const display = useContext(DisplayContext) as ContextType + const [state, setState] = useState({ + strings: display.state.strings, + cards: [] as Card[], + guid: '', + name: '', + handle: '', + node: '', + location: '', + description: '', + imageUrl: null as string | null, + cardId: null as string | null, + status: '', + offsync: false, + statusLabel: '', + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const updateState = (value: any) => { + setState((s) => ({ ...s, ...value })) + } + + useEffect(() => { + const guid = params.guid; + const handle = params.handle ? params.handle : ''; + const node = params.node ? params.node : ''; + const name = params.name ? params.name : ''; + const location = params.location ? params.location : ''; + const description = params.description ? params.description : ''; + const imageUrl = params.imageUrl ? params.imageUrl : null; + const cardId = params.cardId ? params.cardId : null; + const status = params.status ? params.status : ''; + const offsync = params.offsync ? params.offsync : false; + updateState({ guid, handle, node, name, location, description, imageUrl, cardId, status, offsync }); + }, [params]); + + const getStatusLabel = (card?: Card) => { + if (card) { + const { status, offsync } = card; + if (status === 'confirmed') { + return 'savedStatus' + } + if (status === 'pending') { + return 'pendingStatus' + } + if (status === 'requested') { + return 'requestedStatus' + } + if (status === 'connecting') { + return 'connectingStatus' + } + if (status === 'connected' && !offsync) { + return 'connectedStatus' + } + if (status === 'connected' && offsync) { + return 'offsyncStatus' + } + } + return 'unknownStatus' + } + + useEffect(() => { + const card = state.cards.find(card => card.guid === state.guid); + const statusLabel = getStatusLabel(card); + if (card) { + const { handle, node, name, location, description, imageUrl, cardId, status, offsync } = card; + updateState({ handle, node, name, location, description, imageUrl, cardId, status, offsync, statusLabel }); + } else { + updateState({ cardId: null, status: '', offsync: false, statusLabel }); + } + }, [state.cards, state.guid]); + + useEffect(() => { + const contact = app.state.session?.getContact(); + const setCards = (cards: Card[]) => { + updateState({ cards }); + }; + contact.addCardListener(setCards); + return () => { + contact.removeCardListener(setCards); + } + }, []) + + const actions = { + save: async () => { + const contact = app.state.session?.getContact(); + await contact.addCard(state.node, state.guid); + }, + remove: async () => { + const contact = app.state.session?.getContact(); + await contact.removeCard(state.cardId); + }, + connect: async () => { + const contact = app.state.session?.getContact(); + await contact.connectCard(state.cardId); + }, + disconnect: async () => { + const contact = app.state.session?.getContact(); + await contact.disconnectCard(state.cardId); + }, + ignore: async () => { + const contact = app.state.session?.getContact(); + await contact.ignoreCard(state.cardId); + }, + deny: async () => { + const contact = app.state.session?.getContact(); + await contact.denyCard(state.cardId); + }, + confirm: async () => { + const contact = app.state.session?.getContact(); + await contact.confirmCard(state.cardId); + }, + cancel: async () => { + const contact = app.state.session?.getContact(); + await contact.disconnectCard(state.cardId); + }, + accept: async () => { + const contact = app.state.session?.getContact(); + await contact.connectCard(state.cardId); + }, + resync: async () => { + const contact = app.state.session?.getContact(); + await contact.resyncCard(state.cardId); + }, + block: async () => { + const contact = app.state.session?.getContact(); + await contact.setBlockedCard(state.cardId, true); + }, + report: async () => { + const contact = app.state.session?.getContact(); + await contact.flagCard(state.cardId); + }, + } + + return { state, actions } +} diff --git a/app/client/mobile/src/session/Session.tsx b/app/client/mobile/src/session/Session.tsx index 9dee446c..039a8ddf 100644 --- a/app/client/mobile/src/session/Session.tsx +++ b/app/client/mobile/src/session/Session.tsx @@ -53,7 +53,9 @@ export function Session() { - + + + { tab === 'content' && ( @@ -104,17 +106,29 @@ function ContentTab({ scheme }: { scheme: string }) { } function ContactTab({ scheme }: { scheme: string }) { + const [contactParams, setContactParams] = useState({ guid: '' } as ContactParams); + const openContact = (params: ContactParams, nav) => { + setContactParams(params); + nav.navigate('profile'); + } + return ( - {(props) => {props.navigation.navigate('registry')}} openContact={(params: ContactParams)=>{props.navigation.navigate('profile')}} />} + {(props) => {props.navigation.navigate('registry')}} openContact={(params: ContactParams)=>{ + setContactParams(params); + props.navigation.navigate('profile') + }} />} - {(props) => {props.navigation.navigate('profile')}} />} + {(props) => { + setContactParams(params); + props.navigation.navigate('profile') + }} />} - {(props) => } + {(props) => } @@ -141,10 +155,16 @@ function DetailsScreen({nav}) { } function ProfileScreen({nav}) { + const [contactParams, setContactParams] = useState({ guid: '' } as ContactParams); + const openContact = (params: ContactParams, open: ()=>{}) => { + setContactParams(params); + open(); + } + return ( ()} screenOptions={{ drawerStyle: {width: 300}, drawerPosition: 'right', @@ -153,7 +173,7 @@ function ProfileScreen({nav}) { }}> {({navigation}) => ( - + )} @@ -166,7 +186,7 @@ function RegistryScreen({nav}) { id="RegistryDrawer" drawerContent={() => ( - {nav.profile.openDrawer()}} /> + {nav.openContact(params, nav.profile.openDrawer)}} /> )} screenOptions={{ @@ -190,7 +210,7 @@ function ContactsScreen({nav}) { id="ContactsDrawer" drawerContent={() => ( - {nav.profile.openDrawer()}} /> + {nav.openContact(params, nav.profile.openDrawer)}} /> )} screenOptions={{ @@ -212,7 +232,11 @@ function SettingsScreen({nav}) { return ( ()} + drawerContent={() => ( + + + + )} screenOptions={{ drawerStyle: {width: '50%'}, drawerPosition: 'right', diff --git a/app/client/mobile/src/settings/Settings.tsx b/app/client/mobile/src/settings/Settings.tsx index 0dfb2df8..d5801a1a 100644 --- a/app/client/mobile/src/settings/Settings.tsx +++ b/app/client/mobile/src/settings/Settings.tsx @@ -326,7 +326,7 @@ export function Settings({showLogout}: {showLogout: boolean}) { }; return ( - + - + ); }