From d3782750eab899b10c40e60987d7eb3c2ee6e4c2 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Mon, 26 Sep 2022 12:14:06 -0700 Subject: [PATCH] adding contact controls --- app/mobile/src/api/addCard.js | 4 +- app/mobile/src/api/getCardCloseMessage.js | 4 +- app/mobile/src/api/getCardOpenMessage.js | 4 +- app/mobile/src/api/removeCard.js | 4 +- app/mobile/src/api/setCardCloseMessage.js | 7 +- app/mobile/src/api/setCardOpenMessage.js | 7 +- app/mobile/src/api/setCardStatus.js | 12 +- app/mobile/src/context/useCardContext.hook.js | 44 +++- .../src/context/useProfileContext.hook.js | 2 +- app/mobile/src/session/contact/Contact.jsx | 192 +++++++++++++++--- .../src/session/contact/Contact.styled.js | 3 +- .../src/session/contact/useContact.hook.js | 102 +++++++++- app/mobile/src/session/profile/Profile.jsx | 17 +- 13 files changed, 342 insertions(+), 60 deletions(-) diff --git a/app/mobile/src/api/addCard.js b/app/mobile/src/api/addCard.js index 8109c94b..684a8238 100644 --- a/app/mobile/src/api/addCard.js +++ b/app/mobile/src/api/addCard.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function addCard(token, message) { - let card = await fetchWithTimeout(`/contact/cards?agent=${token}`, { method: 'POST', body: JSON.stringify(message)} ); +export async function addCard(server, token, message) { + let card = await fetchWithTimeout(`https://${server}/contact/cards?agent=${token}`, { method: 'POST', body: JSON.stringify(message)} ); checkResponse(card); return await card.json(); } diff --git a/app/mobile/src/api/getCardCloseMessage.js b/app/mobile/src/api/getCardCloseMessage.js index a769c2f0..0363f027 100644 --- a/app/mobile/src/api/getCardCloseMessage.js +++ b/app/mobile/src/api/getCardCloseMessage.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function getCardCloseMessage(token, cardId) { - let message = await fetchWithTimeout(`/contact/cards/${cardId}/closeMessage?agent=${token}`, { method: 'GET' }); +export async function getCardCloseMessage(server, token, cardId) { + let message = await fetchWithTimeout(`https://${server}/contact/cards/${cardId}/closeMessage?agent=${token}`, { method: 'GET' }); checkResponse(message); return await message.json(); } diff --git a/app/mobile/src/api/getCardOpenMessage.js b/app/mobile/src/api/getCardOpenMessage.js index 130a6d89..24705605 100644 --- a/app/mobile/src/api/getCardOpenMessage.js +++ b/app/mobile/src/api/getCardOpenMessage.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function getCardOpenMessage(token, cardId) { - let message = await fetchWithTimeout(`/contact/cards/${cardId}/openMessage?agent=${token}`, { method: 'GET' }); +export async function getCardOpenMessage(server, token, cardId) { + let message = await fetchWithTimeout(`https://${server}/contact/cards/${cardId}/openMessage?agent=${token}`, { method: 'GET' }); checkResponse(message); return await message.json(); } diff --git a/app/mobile/src/api/removeCard.js b/app/mobile/src/api/removeCard.js index 3effc5c8..2ba2d513 100644 --- a/app/mobile/src/api/removeCard.js +++ b/app/mobile/src/api/removeCard.js @@ -1,7 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function removeCard(token, cardId) { - let card = await fetchWithTimeout(`/contact/cards/${cardId}?agent=${token}`, { method: 'DELETE' } ); +export async function removeCard(server, token, cardId) { + let card = await fetchWithTimeout(`https://${server}/contact/cards/${cardId}?agent=${token}`, { method: 'DELETE' } ); checkResponse(card); return await card.json(); } diff --git a/app/mobile/src/api/setCardCloseMessage.js b/app/mobile/src/api/setCardCloseMessage.js index f479224e..4e0a64b2 100644 --- a/app/mobile/src/api/setCardCloseMessage.js +++ b/app/mobile/src/api/setCardCloseMessage.js @@ -1,12 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function setCardCloseMessage(server, message) { - let host = ""; - if (server) { - host = `https://${server}`; - } - - let status = await fetchWithTimeout(`${host}/contact/closeMessage`, { method: 'PUT', body: JSON.stringify(message) }); + let status = await fetchWithTimeout(`https://${server}/contact/closeMessage`, { method: 'PUT', body: JSON.stringify(message) }); checkResponse(status); return await status.json(); } diff --git a/app/mobile/src/api/setCardOpenMessage.js b/app/mobile/src/api/setCardOpenMessage.js index f89bcd93..b9e39884 100644 --- a/app/mobile/src/api/setCardOpenMessage.js +++ b/app/mobile/src/api/setCardOpenMessage.js @@ -1,12 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function setCardOpenMessage(server, message) { - let host = ""; - if (server) { - host = `https://${server}`; - } - - let status = await fetchWithTimeout(`${host}/contact/openMessage`, { method: 'PUT', body: JSON.stringify(message) }); + let status = await fetchWithTimeout(`https://${server}/contact/openMessage`, { method: 'PUT', body: JSON.stringify(message) }); checkResponse(status); return await status.json(); } diff --git a/app/mobile/src/api/setCardStatus.js b/app/mobile/src/api/setCardStatus.js index 843bda2b..fde6cac4 100644 --- a/app/mobile/src/api/setCardStatus.js +++ b/app/mobile/src/api/setCardStatus.js @@ -1,19 +1,19 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function setCardConnecting(token, cardId) { - let card = await fetchWithTimeout(`/contact/cards/${cardId}/status?agent=${token}`, { method: 'PUT', body: JSON.stringify('connecting') } ); +export async function setCardConnecting(server, token, cardId) { + let card = await fetchWithTimeout(`https://${server}/contact/cards/${cardId}/status?agent=${token}`, { method: 'PUT', body: JSON.stringify('connecting') } ); checkResponse(card); return await card.json(); } -export async function setCardConnected(token, cardId, access, view, article, channel, profile) { - let card = await fetchWithTimeout(`/contact/cards/${cardId}/status?agent=${token}&token=${access}&viewRevision=${view}&articleRevision=${article}&channelRevision=${channel}&profileRevision=${profile}`, { method: 'PUT', body: JSON.stringify('connected') } ); +export async function setCardConnected(server, token, cardId, access, view, article, channel, profile) { + let card = await fetchWithTimeout(`https://${server}/contact/cards/${cardId}/status?agent=${token}&token=${access}&viewRevision=${view}&articleRevision=${article}&channelRevision=${channel}&profileRevision=${profile}`, { method: 'PUT', body: JSON.stringify('connected') } ); checkResponse(card); return await card.json(); } -export async function setCardConfirmed(token, cardId) { - let card = await fetchWithTimeout(`/contact/cards/${cardId}/status?agent=${token}`, { method: 'PUT', body: JSON.stringify('confirmed') } ); +export async function setCardConfirmed(server, token, cardId) { + let card = await fetchWithTimeout(`https://${server}/contact/cards/${cardId}/status?agent=${token}`, { method: 'PUT', body: JSON.stringify('confirmed') } ); checkResponse(card); return await card.json(); } diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index 371b88a2..bfda79d2 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -10,6 +10,14 @@ import { getContactChannelDetail } from 'api/getContactChannelDetail'; import { getContactChannelSummary } from 'api/getContactChannelSummary'; import { getCardImageUrl } from 'api/getCardImageUrl'; +import { addCard } from 'api/addCard'; +import { removeCard } from 'api/removeCard'; +import { setCardConnecting, setCardConnected, setCardConfirmed } from 'api/setCardStatus'; +import { getCardOpenMessage } from 'api/getCardOpenMessage'; +import { setCardOpenMessage } from 'api/setCardOpenMessage'; +import { getCardCloseMessage } from 'api/getCardCloseMessage'; +import { setCardCloseMessage } from 'api/setCardCloseMessage'; + export function useCardContext() { const [state, setState] = useState({ cards: new Map(), @@ -174,7 +182,6 @@ export function useCardContext() { else { const view = await store.actions.getCardItemView(guid, card.id); if (view == null) { - console.log('alert: expected card not synced'); let assembled = JSON.parse(JSON.stringify(card)); assembled.data.cardDetail = await getCardDetail(server, appToken, card.id); assembled.data.cardProfile = await getCardProfile(server, appToken, card.id); @@ -343,6 +350,41 @@ export function useCardContext() { }); return card; }, + addCard: async (message) => { + const { server, appToken } = session.current; + return await addCard(server, appToken, message); + }, + removeCard: async (cardId) => { + const { server, appToken } = session.current; + return await removeCard(server, appToken, cardId); + }, + setCardConnecting: async (cardId) => { + const { server, appToken } = session.current; + return await setCardConnecting(server, appToken, cardId); + }, + setCardConnected: async (cardId, token, rev) => { + const { server, appToken } = session.current; + return await setCardConnected(server, appToken, cardId, token, + rev.viewRevision, rev.articleRevision, rev.channelRevision, rev.profileRevision); + }, + setCardConfirmed: async (cardId) => { + const { server, appToken } = session.current; + return await setCardConfirmed(server, appToken, cardId); + }, + getCardOpenMessage: async (cardId) => { + const { server, appToken } = session.current; + return await getCardOpenMessage(server, appToken, cardId); + }, + setCardOpenMessage: async (server, message) => { + return await setCardOpenMessage(server, message); + }, + getCardCloseMessage: async (cardId) => { + const { server, appToken } = session.current; + return await getCardCloseMessage(server, appToken, cardId); + }, + setCardCloseMessage: async (server, message) => { + return await setCardCloseMessage(server, message); + }, } return { state, actions } diff --git a/app/mobile/src/context/useProfileContext.hook.js b/app/mobile/src/context/useProfileContext.hook.js index 8ac26b2c..1fdc9008 100644 --- a/app/mobile/src/context/useProfileContext.hook.js +++ b/app/mobile/src/context/useProfileContext.hook.js @@ -58,7 +58,7 @@ export function useProfileContext() { }, clearSession: () => { session.current = {}; - updateState({ profile: null }); + updateState({ profile: {} }); }, setRevision: (rev) => { curRevision.current = rev; diff --git a/app/mobile/src/session/contact/Contact.jsx b/app/mobile/src/session/contact/Contact.jsx index cf995dae..23fd9b17 100644 --- a/app/mobile/src/session/contact/Contact.jsx +++ b/app/mobile/src/session/contact/Contact.jsx @@ -1,5 +1,5 @@ import { useState, useContext } from 'react'; -import { ScrollView, View, TouchableOpacity, Text } from 'react-native'; +import { ScrollView, View, Alert, TouchableOpacity, Text } from 'react-native'; import { useContact } from './useContact.hook'; import { styles } from './Contact.styled'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -9,22 +9,114 @@ import Colors from 'constants/Colors'; export function Contact({ contact, closeContact }) { - const { state, actions } = useContact(contact); + const { state, actions } = useContact(contact, closeContact); const getStatusText = (status) => { if (status === 'confirmed') { - return 'Saved'; + return 'saved'; } if (status === 'pending') { - return 'Request Reveived'; + return 'request reveived'; } if (status === 'connecting') { - return 'Request Sent'; + return 'request sent'; } if (status === 'connected') { - return 'Connected'; + return 'connected'; } - return 'Unsaved'; + if (status === 'requested') { + return 'request received'; + } + return 'unsaved'; + } + + const setContact = async (action) => { + try { + await action(); + } + catch (err) { + console.log(err); + Alert.alert( + 'Failed to Update Contact', + 'Please try again.', + ); + } + } + + const disconnectContact = () => { + Alert.alert( + "Disconnecting Contact", + "Confirm?", + [ + { text: "Cancel", + onPress: () => {}, + }, + { text: "Disconnect", onPress: () => { + setContact(actions.disconnectContact); + }} + ] + ); + } + + const saveAndConnect = () => { + setContact(actions.saveAndConnect); + } + + const saveContact = () => { + setContact(actions.saveContact); + } + + const ignoreContact = () => { + setContact(actions.ignoreContact); + } + + const deleteContact = () => { + Alert.alert( + "Deleting Contact", + "Confirm?", + [ + { text: "Cancel", + onPress: () => {}, + }, + { text: "Delete", onPress: () => { + setContact(actions.deleteContact); + }} + ] + ); + } + + const closeDelete = () => { + Alert.alert( + "Deleting Contact", + "Confirm?", + [ + { text: "Cancel", + onPress: () => {}, + }, + { text: "Delete", onPress: () => { + setContact(actions.closeDelete); + }} + ] + ); + } + + const blockContact = () => { + Alert.alert( + "Blocking Contact", + "Confirm?", + [ + { text: "Cancel", + onPress: () => {}, + }, + { text: "Block", onPress: () => { + setContact(actions.blockContact); + }} + ] + ); + } + + const connectContact = () => { + setContact(actions.connectContact); } return ( @@ -40,7 +132,7 @@ export function Contact({ contact, closeContact }) { { `${state.handle}@${state.node}` } - { getStatusText(state.status) } + { `[${getStatusText(state.status)}]` } @@ -60,36 +152,90 @@ export function Contact({ contact, closeContact }) { { state.status === 'connected' && ( <> - + Disconnect - + Delete Contact - + Block Contact )} { state.status === 'connecting' && ( - - Block - + <> + + Cancel Request + + + Delete Contact + + + Block Contact + + )} { state.status === 'confirmed' && ( - - Block - + <> + + Request Connection + + + Delete Contact + + + Block Contact + + )} { state.status === 'pending' && ( - - Block - + <> + + Save and Connect + + + Save Contact + + + Ignore Request + + + Block Contact + + )} { state.status === 'requested' && ( - - Block - + <> + + Accept Connection + + + Ignore Request + + + Deny Request + + + Delete Contact + + + Block Contact + + + )} + { state.status == null && ( + <> + + Save and Connect + + + Save Contact + + + Block Contact + + )} diff --git a/app/mobile/src/session/contact/Contact.styled.js b/app/mobile/src/session/contact/Contact.styled.js index 06c51032..a538af98 100644 --- a/app/mobile/src/session/contact/Contact.styled.js +++ b/app/mobile/src/session/contact/Contact.styled.js @@ -25,7 +25,8 @@ export const styles = StyleSheet.create({ }, status: { color: Colors.grey, - paddingBottom: 24, + paddingBottom: 20, + paddingTop: 4, }, headerText: { fontSize: 16, diff --git a/app/mobile/src/session/contact/useContact.hook.js b/app/mobile/src/session/contact/useContact.hook.js index bada43ce..12c01247 100644 --- a/app/mobile/src/session/contact/useContact.hook.js +++ b/app/mobile/src/session/contact/useContact.hook.js @@ -2,9 +2,10 @@ import { useState, useEffect, useRef, useContext } from 'react'; import { useNavigate } from 'react-router-dom'; import { CardContext } from 'context/CardContext'; import { useWindowDimensions } from 'react-native' +import { getListingMessage } from 'api/getListingMessage'; import config from 'constants/Config'; -export function useContact(contact) { +export function useContact(contact, close) { const [state, setState] = useState({ tabbed: null, @@ -15,6 +16,9 @@ export function useContact(contact) { description: null, logo: null, status: null, + cardId: null, + guid: null, + busy: false }); const dimensions = useWindowDimensions(); @@ -39,9 +43,9 @@ export function useContact(contact) { const selected = card.state.cards.get(contact.card); if (selected) { const { profile, detail, cardId } = selected; - const { name, handle, node, location, description, imageSet, revision } = profile; + const { name, handle, node, location, description, guid, imageSet, revision } = profile; const logo = imageSet ? card.actions.getCardLogo(cardId, revision) : 'avatar'; - updateState({ name, handle, node, location, description, logo, status: detail.status }); + updateState({ name, handle, node, location, description, logo, cardId, guid, status: detail.status }); stateSet = true; } } @@ -50,14 +54,14 @@ export function useContact(contact) { const selected = card.actions.getByGuid(guid); if (selected) { const { cardId, profile, detail } = selected; - const { name, handle, node, location, description, imageSet, revision } = profile; + const { name, handle, node, location, description, guid, imageSet, revision } = profile; const logo = imageSet ? card.actions.getCardLogo(cardId, revision) : 'avatar'; - updateState({ name, handle, node, location, description, logo, status: detail.status }); + updateState({ name, handle, node, location, description, logo, cardId, guid, status: detail.status }); stateSet = true; } else { - const { name, handle, node, location, description, logo } = contact.account; - updateState({ name, handle, node, location, description, logo, status: null }); + const { name, handle, node, location, description, logo, guid } = contact.account; + updateState({ name, handle, node, location, description, logo, guid, cardId: null, status: null }); stateSet = true; } } @@ -66,7 +70,91 @@ export function useContact(contact) { } }, [contact, card]); + const applyAction = async (action) => { + if (!state.busy) { + try { + updateState({ busy: true }); + await action(); + updateState({ busy: false }); + } + catch (err) { + console.log(err); + updateState({ busy: false }); + throw new Error("failed to update contact"); + } + } + else { + throw new Error("operation in progress"); + } + } + const actions = { + saveAndConnect: async () => { + await applyAction(async () => { + let profile = await getListingMessage(state.node, state.guid); + let added = await card.actions.addCard(profile); + await card.actions.setCardConnecting(added.id); + let open = await card.actions.getCardOpenMessage(added.id); + let contact = await card.actions.setCardOpenMessage(state.node, open); + if (contact.status === 'connected') { + await card.actions.setCardConnected(added.id, contact.token, contact); + } + }); + }, + saveContact: async () => { + await applyAction(async () => { + let message = await getListingMessage(state.node, state.guid); + await card.actions.addCard(message); + }); + }, + disconnectContact: async () => { + await applyAction(async () => { + await card.actions.setCardConfirmed(state.cardId); + try { + let message = await card.actions.getCardCloseMessage(state.cardId); + await card.actions.setCardCloseMessage(state.node, message); + } + catch (err) { + console.log(err); + } + }); + }, + ignoreContact: async () => { + await applyAction(async () => { + await card.actions.setCardConfirmed(state.cardId); + }); + }, + closeDelete: async () => { + await applyAction(async () => { + await card.actions.setCardConfirmed(state.cardId); + try { + let message = await card.actions.getCardCloseMessage(state.cardId); + await card.actions.setCardCloseMessage(state.node, message); + } + catch (err) { + console.log(err); + } + await card.actions.removeCard(state.cardId); + close(); + }); + }, + deleteContact: async () => { + await applyAction(async () => { + await card.actions.removeCard(state.cardId); + close(); + }); + }, + connectContact: async () => { + await applyAction(async () => { + await card.actions.setCardConnecting(state.cardId); + let message = await card.actions.getCardOpenMessage(state.cardId); + let contact = await card.actions.setCardOpenMessage(state.node, message); + if (contact.status === 'connected') { + await card.actions.setCardConnected(state.cardId, contact.token, contact); + } + }); + }, + blockContact: async () => {}, }; return { state, actions }; diff --git a/app/mobile/src/session/profile/Profile.jsx b/app/mobile/src/session/profile/Profile.jsx index 2aae0cd4..bb52aab5 100644 --- a/app/mobile/src/session/profile/Profile.jsx +++ b/app/mobile/src/session/profile/Profile.jsx @@ -53,6 +53,21 @@ export function Profile() { } } + const logout = async () => { + Alert.alert( + "Logging Out", + "Confirm?", + [ + { text: "Cancel", + onPress: () => {}, + }, + { text: "Logout", onPress: () => { + actions.logout(); + }} + ] + ); + } + const onGallery = async () => { try { const full = await ImagePicker.openPicker({ mediaType: 'photo', width: 256, height: 256 }); @@ -112,7 +127,7 @@ export function Profile() { - + Logout