diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index 619d45c3..3fa1fba3 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -366,7 +366,7 @@ export function useCardContext() { return getCardImageUrl(server, token, cardId, profileRevision); }, removeChannel: async (cardId, channelId) => { - const { detail, profile } = cards.current.get(cardId) || {}; + const { detail, profile } = cards.current.get(cardId)?.card || {}; const cardToken = `${profile?.guid}.${detail?.token}`; return await removeContactChannel(profile?.node, cardToken, channelId); }, diff --git a/app/mobile/src/context/useConversationContext.hook.js b/app/mobile/src/context/useConversationContext.hook.js index ec7ac727..a706dee2 100644 --- a/app/mobile/src/context/useConversationContext.hook.js +++ b/app/mobile/src/context/useConversationContext.hook.js @@ -157,7 +157,6 @@ export function useConversationContext() { clearConversation: async () => { conversationId.current = null; reset.current = true; - await sync(); }, setChannelSubject: async (type, subject) => { const { cardId, channelId } = conversationId.current || {}; diff --git a/app/mobile/src/session/Session.jsx b/app/mobile/src/session/Session.jsx index 61fffa12..1cad472e 100644 --- a/app/mobile/src/session/Session.jsx +++ b/app/mobile/src/session/Session.jsx @@ -168,6 +168,8 @@ export function Session() { useEffect(() => { navParams.detailNav.closeDrawer(); + setChannelId(null); + setCardId(null); setChannel(false); }, [navParams.closeCount]); @@ -263,20 +265,18 @@ export function Session() { const DetailDrawerScreen = ({ navParams }) => { const [closeCount, setCloseCount] = useState(0); - const closeConversation = () => { + const clearConversation = (navigation) => { setCloseCount(closeCount+1); }; return ( ( - - -
- - + +
clearConversation(props.navigation)} /> + )}> - {(props) => } + {(props) => } ); diff --git a/app/mobile/src/session/channels/useChannels.hook.js b/app/mobile/src/session/channels/useChannels.hook.js index 2339415a..d84ed9f7 100644 --- a/app/mobile/src/session/channels/useChannels.hook.js +++ b/app/mobile/src/session/channels/useChannels.hook.js @@ -26,7 +26,8 @@ export function useChannels() { const account = useContext(AccountContext); const profile = useContext(ProfileContext); const app = useContext(AppContext); - + + const filter = useRef(); const syncing = useRef(false); const resync = useRef(false); @@ -154,6 +155,7 @@ export function useChannels() { useEffect(() => { syncChannels(); + filter.current = state.filter; }, [app.state, card.state, channel.state, state.filter, state.sealable]); const syncChannels = async () => { @@ -179,10 +181,10 @@ export function useChannels() { channels.push(await setChannelItem(loginTimestamp, cardId, channelId, channelItem)); } const filtered = channels.filter(item => { - if (!state.filter) { + if (!filter.current) { return true; } - const filterCase = state.filter.toUpperCase(); + const filterCase = filter.current.toUpperCase(); const subjectCase = item.subject.toUpperCase(); return subjectCase.includes(filterCase); }); diff --git a/app/mobile/src/session/conversation/Conversation.jsx b/app/mobile/src/session/conversation/Conversation.jsx index eb595fe5..79c6104a 100644 --- a/app/mobile/src/session/conversation/Conversation.jsx +++ b/app/mobile/src/session/conversation/Conversation.jsx @@ -1,4 +1,4 @@ -import { useEffect, useContext } from 'react'; +import { useEffect, useState, useContext } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { ConversationContext } from 'context/ConversationContext'; import { useConversation } from './useConversation.hook'; @@ -9,6 +9,7 @@ import { Logo } from 'utils/Logo'; export function Conversation({ navigation, cardId, channelId, closeConversation, openDetails }) { + const [ready, setReady] = useState(false); const conversation = useContext(ConversationContext); const { state, actions } = useConversation(); @@ -30,21 +31,27 @@ export function Conversation({ navigation, cardId, channelId, closeConversation, }, [navigation, state.subject]); useEffect(() => { - conversation.actions.setConversation(cardId, channelId); - return () => { conversation.actions.clearConversation() }; + (async () => { + setReady(false); + await conversation.actions.setConversation(cardId, channelId); + setReady(true); + })(); + return () => { conversation.actions.clearConversation(); }; }, [cardId, channelId]); return ( { !navigation && ( - - - { state.subject } - - + { ready && ( + + + { state.subject } + + + )} - + )} diff --git a/app/mobile/src/session/conversation/Conversation.styled.js b/app/mobile/src/session/conversation/Conversation.styled.js index d00d6b75..f0b9c210 100644 --- a/app/mobile/src/session/conversation/Conversation.styled.js +++ b/app/mobile/src/session/conversation/Conversation.styled.js @@ -16,27 +16,31 @@ export const styles = StyleSheet.create({ flexGrow: 1, borderBottomWidth: 1, borderColor: Colors.divider, + height: 48, + marginLeft: 16, + marginRight: 16, }, headertitle: { display: 'flex', + flexShrink: 1, flexDirection: 'row', alignItems: 'center', - paddingLeft: 16, + paddingLeft: 8, paddingTop: 8, paddingBottom: 8, }, titletext: { fontSize: 18, + flexShrink: 1, paddingLeft: 16, paddingRight: 16, }, titlebutton: { - paddingRight: 16, + paddingRight: 8, }, headerclose: { flexGrow: 1, alignItems: 'flex-end', - paddingTop: 8, }, }); diff --git a/app/mobile/src/session/details/Details.jsx b/app/mobile/src/session/details/Details.jsx index e5e022d6..7c44be7a 100644 --- a/app/mobile/src/session/details/Details.jsx +++ b/app/mobile/src/session/details/Details.jsx @@ -5,11 +5,30 @@ import { Logo } from 'utils/Logo'; import AntIcons from 'react-native-vector-icons/AntDesign'; import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import Colors from 'constants/Colors'; +import { MemberItem } from './memberItem/MemberItem'; export function Details({ channel, clearConversation }) { const { state, actions } = useDetails(); + const toggle = async (cardId, selected) => { + try { + if (selected) { + await actions.clearCard(cardId); + } + else { + await actions.setCard(cardId); + } + } + catch (err) { + console.log(err); + Alert.alert( + 'Failed to Update Membership', + 'Please try again.' + ); + } + }; + const saveSubject = async () => { try { await actions.saveSubject(); @@ -199,8 +218,8 @@ export function Details({ channel, clearConversation }) { } + data={state.members} + renderItem={({ item }) => } keyExtractor={item => item.cardId} /> @@ -242,7 +261,7 @@ export function Details({ channel, clearConversation }) { Channel Members: MEMBER } + renderItem={({ item }) => } keyExtractor={item => item.cardId} /> diff --git a/app/mobile/src/session/details/memberItem/MemberItem.jsx b/app/mobile/src/session/details/memberItem/MemberItem.jsx new file mode 100644 index 00000000..1f880257 --- /dev/null +++ b/app/mobile/src/session/details/memberItem/MemberItem.jsx @@ -0,0 +1,31 @@ +import { TouchableOpacity, Switch, Text, View } from 'react-native'; +import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { styles } from './MemberItem.styled'; +import { Logo } from 'utils/Logo'; +import { Colors } from 'constants/Colors'; + +export function MemberItem({ item, hostId, toggle }) { + + const select = () => { + if (toggle) { + toggle(item.cardId, item.selected); + } + }; + + return ( + + + + { item.name } + { item.handle } + + { hostId === item.cardId && ( + + )} + { toggle && ( + + )} + + ); +} + diff --git a/app/mobile/src/session/details/memberItem/MemberItem.styled.js b/app/mobile/src/session/details/memberItem/MemberItem.styled.js new file mode 100644 index 00000000..936710bc --- /dev/null +++ b/app/mobile/src/session/details/memberItem/MemberItem.styled.js @@ -0,0 +1,42 @@ +import { StyleSheet } from 'react-native'; +import { Colors } from 'constants/Colors'; + +export const styles = StyleSheet.create({ + container: { + width: '100%', + display: 'flex', + flexDirection: 'row', + height: 48, + alignItems: 'center', + borderBottomWidth: 1, + borderColor: Colors.itemDivider, + paddingLeft: 8, + paddingRight: 8, + }, + detail: { + paddingLeft: 12, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + flexGrow: 1, + flexShrink: 1, + }, + space: { + height: 64, + }, + name: { + color: Colors.text, + fontSize: 14, + }, + handle: { + color: Colors.text, + fontSize: 12, + }, + track: { + false: Colors.grey, + true: Colors.background, + }, + switch: { + transform: [{ scaleX: .7 }, { scaleY: .7 }], + }, +}); diff --git a/app/mobile/src/session/details/useDetails.hook.js b/app/mobile/src/session/details/useDetails.hook.js index 4fdb132c..80ecda51 100644 --- a/app/mobile/src/session/details/useDetails.hook.js +++ b/app/mobile/src/session/details/useDetails.hook.js @@ -5,6 +5,7 @@ import { CardContext } from 'context/CardContext'; import { AccountContext } from 'context/AccountContext'; import { ProfileContext } from 'context/ProfileContext'; import { getChannelSubjectLogo } from 'context/channelUtil'; +import { getCardByGuid } from 'context/cardUtil'; import { getChannelSeals, isUnsealed, getContentKey, updateChannelSubject } from 'context/sealUtil'; import moment from 'moment'; @@ -23,11 +24,11 @@ export function useDetails() { pushEnabled: false, locked: false, unlocked: false, - count: 0, seals: null, sealKey: null, deleteBusy: false, blockBusy: false, + unknown: 0, }); const card = useContext(CardContext); @@ -64,18 +65,49 @@ export function useDetails() { updateState({ locked, unlocked, seals, sealKey, notification }); }, [account.state, conversation.state]); - useEffect(() => { - const connected = []; - card.state.cards.forEach(contact => { - if (contact?.card?.detail?.status === 'connected') { - connected.push(contact.card); - } - }); - updateState({ connected }); - }, [card.state]); + const setMemberItem = (contact, guids) => { + return { + cardId: contact?.cardId, + name: contact?.profile?.name, + handle: contact?.profile?.handle, + node: contact?.profile?.node, + logo: contact?.profile?.imageSet ? card.actions.getCardImageUrl(contact.cardId) : 'avatar', + selected: guids.includes(contact?.profile?.guid), + } + }; useEffect(() => { - const hostId = conversation.state.card?.cardId; + let unknown = 0; + let members = new Map(); + const host = conversation.state.card; + if (host) { + members.set(host.card?.cardId, setMemberItem(host.card, [])); + } + const guids = conversation.state.channel?.detail?.members || []; + guids.forEach(guid => { + if (guid !== profile.state.identity?.guid) { + const contact = getCardByGuid(card.state.cards, guid); + if (contact) { + members.set(contact.card?.cardId, setMemberItem(contact.card, [])); + } + else { + unknown++; + } + } + }); + + const connected = new Map(); + card.state.cards.forEach(contact => { + if (contact?.card?.detail?.status === 'connected') { + connected.set(contact.card?.cardId, setMemberItem(contact.card, guids)); + } + }); + + updateState({ connected: Array.from(connected.values()), members: Array.from(members.values()), unknown }); + }, [card.state, conversation.state]); + + useEffect(() => { + const hostId = conversation.state.card?.card.cardId; const profileGuid = profile.state.identity?.guid; const channel = conversation.state.channel; const cards = card.state.cards; @@ -128,6 +160,24 @@ export function useDetails() { setSubjectUpdate: (subjectUpdate) => { updateState({ subjectUpdate }); }, + setCard: async (cardId) => { + updateState({ connected: state.connected.map(contact => { + if(contact.cardId === cardId) { + return { ...contact, selected: true } + } + return contact; + }) }); + await conversation.actions.setChannelCard(cardId); + }, + clearCard: async (cardId) => { + updateState({ connected: state.connected.map(contact => { + if(contact.cardId === cardId) { + return { ...contact, selected: false } + } + return contact; + }) }); + await conversation.actions.clearChannelCard(cardId); + }, saveSubject: async () => { if (state.locked) { const contentKey = await getContentKey(state.seals, state.sealKey); diff --git a/app/mobile/src/session/profile/blockedTopics/useBlockedTopics.hook.js b/app/mobile/src/session/profile/blockedTopics/useBlockedTopics.hook.js index 70a6c63f..8f4ace38 100644 --- a/app/mobile/src/session/profile/blockedTopics/useBlockedTopics.hook.js +++ b/app/mobile/src/session/profile/blockedTopics/useBlockedTopics.hook.js @@ -69,7 +69,7 @@ export function useBlockedTopics() { }); channel.state.channels.forEach((channelItem, channelId, map) => { if (channelItem.blocked) { - marged.push({ channel: channelItem }); + merged.push({ channel: channelItem }); } }); const items = merged.map(setChannelItem);