From 233bcb01e9c9a268369bfacd51464844272c86ec Mon Sep 17 00:00:00 2001 From: balzack Date: Mon, 27 Feb 2023 09:03:56 -0800 Subject: [PATCH] mergin refactored channel --- app/mobile/src/context/useAppContext.hook.js | 5 +- app/mobile/src/session/Session.jsx | 4 + app/mobile/src/session/channels/Channels.jsx | 20 ++- .../src/session/channels/Channels.styled.js | 2 + .../channels/channelItem/ChannelItem.jsx | 31 ++++ .../channelItem/ChannelItem.styled.js | 45 ++++++ .../src/session/channels/useChannels.hook.js | 151 +++++++++++++++++- 7 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 app/mobile/src/session/channels/channelItem/ChannelItem.jsx create mode 100644 app/mobile/src/session/channels/channelItem/ChannelItem.styled.js diff --git a/app/mobile/src/context/useAppContext.hook.js b/app/mobile/src/context/useAppContext.hook.js index c101612a..f1a57bec 100644 --- a/app/mobile/src/context/useAppContext.hook.js +++ b/app/mobile/src/context/useAppContext.hook.js @@ -48,7 +48,7 @@ export function useAppContext() { } access.current = await store.actions.init(); if (access.current) { - await setSession(access.current); + await setSession(); } else { updateState({ session: false }); @@ -58,7 +58,8 @@ export function useAppContext() { }, []); const setSession = async () => { - updateState({ session: true, status: 'connecting' }); + const { loginTimestamp } = access.current; + updateState({ session: true, loginTimestamp, status: 'connecting' }); await account.actions.setSession(access.current); await profile.actions.setSession(access.current); await card.actions.setSession(access.current); diff --git a/app/mobile/src/session/Session.jsx b/app/mobile/src/session/Session.jsx index 06effb2f..1701858f 100644 --- a/app/mobile/src/session/Session.jsx +++ b/app/mobile/src/session/Session.jsx @@ -148,6 +148,10 @@ export function Session() { conversation.actions.setConversation(cardId, channelId); setChannel(true); }; + const clearConversation = () => { + conversation.actions.clearConversation(); + setChannel(false); + }; const openDetails = () => { navParams.detailNav.openDrawer(); }; diff --git a/app/mobile/src/session/channels/Channels.jsx b/app/mobile/src/session/channels/Channels.jsx index 08478e92..4822c2f0 100644 --- a/app/mobile/src/session/channels/Channels.jsx +++ b/app/mobile/src/session/channels/Channels.jsx @@ -1,9 +1,10 @@ import { useEffect } from 'react'; -import { View, Text, TextInput, TouchableOpacity } from 'react-native'; +import { View, FlatList, Text, TextInput, TouchableOpacity } from 'react-native'; import Ionicons from 'react-native-vector-icons/AntDesign'; import { styles } from './Channels.styled'; import { useChannels } from './useChannels.hook'; import { Colors } from 'constants/Colors'; +import { ChannelItem } from './channelItem/ChannelItem'; export function Channels({ navigation, openConversation }) { @@ -40,9 +41,20 @@ export function Channels({ navigation, openConversation }) { )} - - Channels - + { state.channels.length == 0 && ( + + No Topics Found + + )} + { state.channels.length != 0 && ( + } + keyExtractor={item => (`${item.cardId}:${item.channelId}`)} + /> + )} { !navigation && ( diff --git a/app/mobile/src/session/channels/Channels.styled.js b/app/mobile/src/session/channels/Channels.styled.js index 42db3601..5a1d19ba 100644 --- a/app/mobile/src/session/channels/Channels.styled.js +++ b/app/mobile/src/session/channels/Channels.styled.js @@ -60,6 +60,8 @@ export const styles = StyleSheet.create({ }, content: { flexGrow: 1, + flexShrink: 1, + paddingLeft: 4, }, columnbottom: { paddingLeft: 24, diff --git a/app/mobile/src/session/channels/channelItem/ChannelItem.jsx b/app/mobile/src/session/channels/channelItem/ChannelItem.jsx new file mode 100644 index 00000000..829df563 --- /dev/null +++ b/app/mobile/src/session/channels/channelItem/ChannelItem.jsx @@ -0,0 +1,31 @@ +import { Text, View } from 'react-native'; +import { TouchableOpacity } from 'react-native-gesture-handler'; +import { Logo } from 'utils/Logo'; +import { styles } from './ChannelItem.styled'; +import { useChannelItem } from './useChannelItem.hook'; +import Colors from 'constants/Colors'; +import Ionicons from 'react-native-vector-icons/AntDesign'; + +export function ChannelItem({ item, openConversation }) { + + return ( + openConversation(item.cardId, item.channelId, item.revision)}> + + + + { item.locked && !item.unlocked && ( + + )} + { item.locked && item.unlocked && ( + + )} + { item.subject } + + { item.message } + + { item.updated && ( + + )} + + ) +} diff --git a/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js b/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js new file mode 100644 index 00000000..a8cbecde --- /dev/null +++ b/app/mobile/src/session/channels/channelItem/ChannelItem.styled.js @@ -0,0 +1,45 @@ +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: 16, + paddingRight: 16, + }, + detail: { + paddingLeft: 12, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + flexGrow: 1, + flexShrink: 1, + }, + subject: { + display: 'flex', + flexDirection: 'row', + }, + subjectIcon: { + paddingRight: 4, + }, + subjectText: { + color: Colors.text, + fontSize: 14, + }, + message: { + color: Colors.disabled, + fontSize: 12, + }, + dot: { + width: 8, + height: 8, + borderRadius: 4, + backgroundColor: Colors.background, + } +}) diff --git a/app/mobile/src/session/channels/useChannels.hook.js b/app/mobile/src/session/channels/useChannels.hook.js index f66d6ec3..318b03e5 100644 --- a/app/mobile/src/session/channels/useChannels.hook.js +++ b/app/mobile/src/session/channels/useChannels.hook.js @@ -1,16 +1,163 @@ -import { useState } from 'react'; +import { useState, useEffect, useContext } from 'react'; +import { ChannelContext } from 'context/ChannelContext'; +import { CardContext } from 'context/CardContext'; +import { AccountContext } from 'context/AccountContext'; +import { AppContext } from 'context/AppContext'; +import { ProfileContext } from 'context/ProfileContext'; +import { getChannelSeals, isUnsealed } from 'context/sealUtil'; +import { getCardByGuid } from 'context/cardUtil'; export function useChannels() { const [state, setState] = useState({ filter: null, + channels: [], }); + const channel = useContext(ChannelContext); + const card = useContext(CardContext); + const account = useContext(AccountContext); + const profile = useContext(ProfileContext); + const app = useContext(AppContext); + const updateState = (value) => { setState((s) => ({ ...s, ...value })); } + const setChannelItem = (loginTimestamp, cardId, channelId, item) => { + const timestamp = item.summary.lastTopic.created; + const { readRevision, topicRevision } = item; + + // extract or decrypt subject + let locked; + let unlocked; + let message; + let subject; + if (item.detail.dataType === 'sealed') { + locked = true; + const seals = getChannelSeals(item.detail.data); + if (isUnsealed(seals, account.state.sealKey)) { + unlocked = true; + if (item.detail.unsealedDetail) { + subject = item.detail.unsealedDetail.subject; + } + else { + // decrypt detail + } + if (item.summary.lastTopic.dataType === 'sealedtopic') { + if (item.summary.unsealedSummary) { + message = item.detail.unsealedSummary.message; + } + else { + // decrypt message + } + } + } + } + if (item.detail.dataType === 'superbasic') { + locked = false; + unlocked = false; + try { + subject = JSON.parse(item.detail.data).subject; + } + catch(err) { + console.log(err); + } + if (item.summary.lastTopic.dataType === 'superbasictopic') { + try { + message = JSON.parse(item.summary.lastTopic.data).text; + } + catch(err) { + console.log(err); + } + } + } + + const contacts = []; + if (cardId) { + contacts.push(cardId); + } + item.detail.members.forEach(guid => { + if (guid !== profile.state.identity.guid) { + contacts.push(getCardByGuid(card.state.cards, guid)?.cardId); + } + }) + + if (!subject) { + if (contacts.length === 0) { + subject = 'Notes'; + } + else { + const names = []; + contacts.forEach(id => { + const contact = card.state.cards.get(id); + if (contact?.card.profile?.name) { + names.push(contact.card.profile.name); + } + else { + names.push(contact?.card.profile?.handle); + } + }); + subject = names.join(', '); + } + } + + if (contacts.length === 0) { + logo = 'solution'; + } + else if (contacts.length === 1) { + const contact = card.state.cards.get(contacts[0]); + if (contact?.card?.profile?.imageSet) { + logo = card.actions.getCardImageUrl(contacts[0]) + } + else { + logo = 'avatar'; + } + } + else { + logo = 'appstore'; + } + + const updated = (loginTimestamp < timestamp) && (readRevision < topicRevision); + + return { cardId, channelId, subject, message, logo, updated, locked, unlocked }; + } + + useEffect(() => { + const { loginTimestamp } = app.state; + const channels = []; + channel.state.channels.forEach((item, channelId) => { + channels.push(setChannelItem(loginTimestamp, null, channelId, item)); + }); + card.state.cards.forEach((cardItem, cardId) => { + cardItem.channels.forEach((channelItem, channelId) => { + channels.push(setChannelItem(loginTimestamp, cardId, channelId, channelItem)); + }); + }); + const filtered = channels.filter(item => { + if (!state.filter) { + return true; + } + const filterCase = state.filter.toUpperCase(); + const subjectCase = item.subject.toUpperCase(); + return subjectCase.includes(filterCase); + }); + const sorted = filtered.sort((a, b) => { + const aCreated = a?.timestamp; + const bCreated = b?.timestamp; + if (aCreated === bCreated) { + return 0; + } + if (!aCreated || aCreated < bCreated) { + return 1; + } + return -1; + }); + updateState({ channels: sorted }); + }, [app.state, card.state, channel.state, state.filter]); + const actions = { - setFilter: () => { + setFilter: (filter) => { + updateState({ filter }); }, };