preparing conversation screen

This commit is contained in:
Roland Osborne 2023-02-28 15:25:04 -08:00
parent 8d5209396a
commit 4479ba5dca
12 changed files with 173 additions and 152 deletions

View File

@ -43,34 +43,31 @@ export function Session() {
const screenParams = { headerShown: true, headerTintColor: Colors.primary }; const screenParams = { headerShown: true, headerTintColor: Colors.primary };
const ConversationStackScreen = () => { const ConversationStackScreen = () => {
const conversation = useContext(ConversationContext); const [cardId, setCardId] = useState();
const [channelId, setChannelId] = useState();
const setConversation = async (navigation, cardId, channelId) => { const openConversation = async (navigation, card, channel) => {
setCardId(card);
setChannelId(channel);
navigation.navigate('conversation'); navigation.navigate('conversation');
await conversation.actions.setConversation(cardId, channelId);
} }
const clearConversation = (navigation) => { const closeConversation = (navigation) => {
navigation.dispatch( setCardId(null);
CommonActions.reset({ index: 0, routes: [ { name: 'channels' }, ], }) setChannelId(null);
);
conversation.actions.clearConversation();
} }
const openDetails = (navigation) => { const openDetails = (navigation) => {
navigation.navigate('details'); navigation.navigate('details');
} }
return ( return (
<ConversationStack.Navigator <ConversationStack.Navigator initialRouteName="channels" screenOptions={({ route }) => (screenParams)} >
initialRouteName="channels"
screenOptions={({ route }) => (screenParams)}
screenListeners={{ state: (e) => { if (e?.data?.state?.index === 0) { conversation.actions.clearConversation() }} }}>
<ConversationStack.Screen name="channels" options={stackParams}> <ConversationStack.Screen name="channels" options={stackParams}>
{(props) => <Channels navigation={props.navigation} openConversation={(cardId, channelId) => setConversation(props.navigation, cardId, channelId)} />} {(props) => <Channels navigation={props.navigation} openConversation={(cardId, channelId) => openConversation(props.navigation, cardId, channelId)} />}
</ConversationStack.Screen> </ConversationStack.Screen>
<ConversationStack.Screen name="conversation" options={{ ...stackParams, headerTitle: (props) => <ConversationHeader closeConversation={clearConversation} openDetails={openDetails} /> }}> <ConversationStack.Screen name="conversation" options={stackParams}>
{(props) => <ConversationBody />} {(props) => <Conversation navigation={props.navigation} cardId={cardId} channelId={channelId} openDetails={() => openDetails(props.navigation)} closeConversation={closeConversation} /> }
</ConversationStack.Screen> </ConversationStack.Screen>
<ConversationStack.Screen name="details" options={{ ...stackParams, headerTitle: (props) => <DetailsHeader /> }}> <ConversationStack.Screen name="details" options={{ ...stackParams, headerTitle: (props) => <DetailsHeader /> }}>
@ -143,13 +140,17 @@ export function Session() {
const conversation = useContext(ConversationContext); const conversation = useContext(ConversationContext);
const [channel, setChannel] = useState(false); const [channel, setChannel] = useState(false);
const [cardId, setCardId] = useState();
const [channelId, setChannelId] = useState();
const setConversation = (cardId, channelId) => { const setConversation = (card, channel) => {
conversation.actions.setConversation(cardId, channelId); setCardId(card);
setChannelId(channel);
setChannel(true); setChannel(true);
}; };
const clearConversation = () => { const closeConversation = () => {
conversation.actions.clearConversation(); setCardId(null);
setChannelId(null);
setChannel(false); setChannel(false);
}; };
const openDetails = () => { const openDetails = () => {
@ -169,8 +170,8 @@ export function Session() {
return ( return (
<View style={styles.home}> <View style={styles.home}>
<SafeAreaView edges={['top', 'bottom']} style={styles.sidebar}> <SafeAreaView edges={['top', 'bottom', 'left']} style={styles.sidebar}>
<SafeAreaView edges={['left']} style={styles.options}> <View edges={['left']} style={styles.options}>
<TouchableOpacity style={styles.option} onPress={openProfile}> <TouchableOpacity style={styles.option} onPress={openProfile}>
<ProfileIcon color={Colors.text} size={20} /> <ProfileIcon color={Colors.text} size={20} />
<Text style={styles.profileLabel}>Profile</Text> <Text style={styles.profileLabel}>Profile</Text>
@ -179,14 +180,16 @@ export function Session() {
<CardsIcon color={Colors.text} size={20} /> <CardsIcon color={Colors.text} size={20} />
<Text style={styles.profileLabel}>Contacts</Text> <Text style={styles.profileLabel}>Contacts</Text>
</TouchableOpacity> </TouchableOpacity>
</SafeAreaView> </View>
<View style={styles.channels}> <View style={styles.channels}>
<Channels openConversation={setConversation} /> <Channels cardId={cardId} channelId={channelId} openConversation={setConversation} />
</View> </View>
</SafeAreaView> </SafeAreaView>
<View style={styles.conversation}> <View style={styles.conversation}>
{ channel && ( { channel && (
<Conversation closeConversation={clearConversation} openDetails={openDetails} /> <SafeAreaView edges={['top', 'bottom', 'right']}>
<Conversation cardId={cardId} channelId={channelId} closeConversation={closeConversation} openDetails={openDetails} />
</SafeAreaView>
)} )}
{ !channel && ( { !channel && (
<Welcome /> <Welcome />
@ -262,9 +265,13 @@ export function Session() {
}; };
return ( return (
<DetailDrawer.Navigator screenOptions={{ ...drawerParams, drawerStyle: { width: '45%' } }} <DetailDrawer.Navigator screenOptions={{ ...drawerParams, drawerStyle: { width: '45%' } }} drawerContent={(props) => (
drawerContent={(props) => <Details closeConversation={closeConversation} />} <ScrollView style={styles.drawer}>
> <SafeAreaView edges={['top', 'bottom', 'right']}>
<Details closeConversation={closeConversation} />
</SafeAreaView>
</ScrollView>
)}>
<DetailDrawer.Screen name="contact"> <DetailDrawer.Screen name="contact">
{(props) => <ContactDrawerScreen navParams={{...navParams, detailNav: props.navigation}} />} {(props) => <ContactDrawerScreen navParams={{...navParams, detailNav: props.navigation}} />}
</DetailDrawer.Screen> </DetailDrawer.Screen>

View File

@ -80,9 +80,12 @@ export const styles = StyleSheet.create({
height: '100%', height: '100%',
width: '33%', width: '33%',
maxWidth: 500, maxWidth: 500,
borderRightWidth: 1,
borderColor: Colors.divider,
}, },
conversation: { conversation: {
width: '67%', width: '67%',
backgroundColor: Colors.formFocus,
}, },
drawer: { drawer: {
width: '100%', width: '100%',

View File

@ -7,14 +7,15 @@ import { Colors } from 'constants/Colors';
import { ChannelItem } from './channelItem/ChannelItem'; import { ChannelItem } from './channelItem/ChannelItem';
import { AddMember } from './addMember/AddMember'; import { AddMember } from './addMember/AddMember';
export function Channels({ navigation, openConversation }) { export function Channels({ cardId, channelId, navigation, openConversation }) {
const { state, actions } = useChannels(); const { state, actions } = useChannels();
const addChannel = async () => { const addChannel = async () => {
try { try {
await actions.addChannel(); const channelId = await actions.addChannel();
actions.hideAdding(); actions.hideAdding();
openConversation(null, channelId);
} }
catch (err) { catch (err) {
console.log(err); console.log(err);
@ -57,7 +58,7 @@ export function Channels({ navigation, openConversation }) {
</View> </View>
)} )}
{ state.channels.length == 0 && ( { state.channels.length == 0 && (
<View style={styles.content}> <View style={styles.notfound}>
<Text style={styles.notfoundtext}>No Topics Found</Text> <Text style={styles.notfoundtext}>No Topics Found</Text>
</View> </View>
)} )}
@ -66,7 +67,7 @@ export function Channels({ navigation, openConversation }) {
style={styles.content} style={styles.content}
data={state.channels} data={state.channels}
initialNumToRender={25} initialNumToRender={25}
renderItem={({ item }) => <ChannelItem item={item} openConversation={openConversation} />} renderItem={({ item }) => <ChannelItem cardId={cardId} channelId={channelId} item={item} openConversation={openConversation} />}
keyExtractor={item => (`${item.cardId}:${item.channelId}`)} keyExtractor={item => (`${item.cardId}:${item.channelId}`)}
/> />
)} )}

View File

@ -78,10 +78,23 @@ export const styles = StyleSheet.create({
flexShrink: 1, flexShrink: 1,
paddingLeft: 4, paddingLeft: 4,
}, },
notfound: {
flexGrow: 1,
flexShrink: 1,
display: 'flex',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
},
notfoundtext: {
fontSize: 18,
color: Colors.disabled,
},
columnbottom: { columnbottom: {
paddingLeft: 24, paddingLeft: 24,
paddingRight: 16, paddingRight: 16,
paddingTop: 8, paddingTop: 8,
paddingBottom: 16,
borderTopWidth: 1, borderTopWidth: 1,
borderColor: Colors.divider, borderColor: Colors.divider,
}, },

View File

@ -6,10 +6,12 @@ import { useChannelItem } from './useChannelItem.hook';
import Colors from 'constants/Colors'; import Colors from 'constants/Colors';
import Ionicons from 'react-native-vector-icons/MaterialCommunityIcons'; import Ionicons from 'react-native-vector-icons/MaterialCommunityIcons';
export function ChannelItem({ item, openConversation }) { export function ChannelItem({ cardId, channelId, item, openConversation }) {
const container = (cardId === item.cardId && channelId === item.channelId) ? styles.active : styles.container;
return ( return (
<TouchableOpacity style={styles.container} activeOpacity={1} onPress={() => openConversation(item.cardId, item.channelId, item.revision)}> <TouchableOpacity style={container} activeOpacity={1} onPress={() => openConversation(item.cardId, item.channelId, item.revision)}>
<Logo src={item.logo} width={32} height={32} radius={3} /> <Logo src={item.logo} width={32} height={32} radius={3} />
<View style={styles.detail}> <View style={styles.detail}>
<View style={styles.subject}> <View style={styles.subject}>

View File

@ -13,6 +13,18 @@ export const styles = StyleSheet.create({
paddingLeft: 16, paddingLeft: 16,
paddingRight: 16, paddingRight: 16,
}, },
active: {
width: '100%',
display: 'flex',
flexDirection: 'row',
height: 48,
alignItems: 'center',
borderBottomWidth: 1,
borderColor: Colors.itemDivider,
paddingLeft: 16,
paddingRight: 16,
backgroundColor: Colors.formFocus,
},
detail: { detail: {
paddingLeft: 12, paddingLeft: 12,
display: 'flex', display: 'flex',

View File

@ -6,6 +6,7 @@ import { AppContext } from 'context/AppContext';
import { ProfileContext } from 'context/ProfileContext'; import { ProfileContext } from 'context/ProfileContext';
import { getChannelSeals, isUnsealed, getContentKey, encryptChannelSubject, decryptChannelSubject, decryptTopicSubject } from 'context/sealUtil'; import { getChannelSeals, isUnsealed, getContentKey, encryptChannelSubject, decryptChannelSubject, decryptTopicSubject } from 'context/sealUtil';
import { getCardByGuid } from 'context/cardUtil'; import { getCardByGuid } from 'context/cardUtil';
import { getChannelSubjectLogo } from 'context/channelUtil';
export function useChannels() { export function useChannels() {
const [state, setState] = useState({ const [state, setState] = useState({
@ -37,20 +38,15 @@ export function useChannels() {
const timestamp = item.summary.lastTopic.created; const timestamp = item.summary.lastTopic.created;
const { readRevision, topicRevision } = item; const { readRevision, topicRevision } = item;
// extract or decrypt subject // decrypt subject and message
let locked; let locked = false;
let unlocked; let unlocked = false;
let message;
let subject;
if (item.detail.dataType === 'sealed') { if (item.detail.dataType === 'sealed') {
locked = true; locked = true;
const seals = getChannelSeals(item.detail.data); const seals = getChannelSeals(item.detail.data);
if (isUnsealed(seals, account.state.sealKey)) { if (isUnsealed(seals, account.state.sealKey)) {
unlocked = true; unlocked = true;
if (item.unsealedDetail) { if (!item.unsealedDetail) {
subject = item.unsealedDetail.subject;
}
else {
try { try {
const contentKey = await getContentKey(seals, account.state.sealKey); const contentKey = await getContentKey(seals, account.state.sealKey);
const unsealed = decryptChannelSubject(item.detail.data, contentKey); const unsealed = decryptChannelSubject(item.detail.data, contentKey);
@ -66,10 +62,7 @@ export function useChannels() {
} }
} }
if (item.summary.lastTopic.dataType === 'sealedtopic') { if (item.summary.lastTopic.dataType === 'sealedtopic') {
if (item.unsealedSummary) { if (!item.unsealedSummary) {
message = item.unsealedSummary.message.text;
}
else {
try { try {
const contentKey = await getContentKey(seals, account.state.sealKey); const contentKey = await getContentKey(seals, account.state.sealKey);
const unsealed = decryptTopicSubject(item.summary.lastTopic.data, contentKey); const unsealed = decryptTopicSubject(item.summary.lastTopic.data, contentKey);
@ -87,18 +80,20 @@ export function useChannels() {
} }
} }
} }
let message;
if (item?.detail?.dataType === 'sealed') {
if (typeof item?.unsealedSummary?.message?.text === 'string') {
message = item.unsealedSummary.message.text;
}
}
if (item.detail.dataType === 'superbasic') { 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') { if (item.summary.lastTopic.dataType === 'superbasictopic') {
try { try {
message = JSON.parse(item.summary.lastTopic.data).text; const data = JSON.parse(item.summary.lastTopic.data);
if (typeof data.text === 'string') {
message = data.text;
}
} }
catch(err) { catch(err) {
console.log(err); console.log(err);
@ -106,50 +101,8 @@ export function useChannels() {
} }
} }
const contacts = []; const profileGuid = profile.state?.identity?.guid;
if (cardId) { const { logo, subject } = getChannelSubjectLogo(cardId, profileGuid, item, card.state.cards, card.actions.getCardImageUrl);
contacts.push(cardId);
}
item.detail.members.forEach(guid => {
if (guid !== profile.state.identity.guid) {
contacts.push(getCardByGuid(card.state.cards, guid)?.card?.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); const updated = (loginTimestamp < timestamp) && (readRevision < topicRevision);

View File

@ -1,13 +1,54 @@
import { Text, } from 'react-native'; import { useEffect, useContext } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { ConversationContext } from 'context/ConversationContext';
import { useConversation } from './useConversation.hook';
import { styles } from './Conversation.styled';
import { Colors } from 'constants/Colors';
import Ionicons from 'react-native-vector-icons/AntDesign';
import { Logo } from 'utils/Logo';
export function ConversationHeader({ closeConversation, openDetails, state, actions }) { export function Conversation({ navigation, cardId, channelId, closeConversation, openDetails }) {
return <Text>ConversationHeader</Text>;
}
export function ConversationBody({ state, actions }) { const conversation = useContext(ConversationContext);
return <Text>ConversationBody</Text> const { state, actions } = useConversation();
}
export function Conversation({ closeConversation, openDetails }) { useEffect(() => {
return <Text>Conversation</Text>; if (navigation) {
navigation.setOptions({
headerTitle: () => (
<View style={styles.title}>
<Text style={styles.titletext}>{ state.subject }</Text>
</View>
),
headerRight: () => (
<TouchableOpacity onPress={openDetails}>
<Ionicons name={'setting'} size={24} color={Colors.primary} style={styles.titlebutton} />
</TouchableOpacity>
),
});
}
}, [navigation, state.subject]);
useEffect(() => {
conversation.actions.setConversation(cardId, channelId);
return () => { conversation.actions.clearConversation() };
}, [cardId, channelId]);
return (
<View>
{ !navigation && (
<View style={styles.header}>
<TouchableOpacity style={styles.headertitle} onPress={openDetails}>
<Logo src={state.logo} width={32} height={32} radius={2} />
<Text style={styles.titletext}>{ state.subject }</Text>
<Ionicons name={'setting'} size={24} color={Colors.primary} style={styles.titlebutton} />
</TouchableOpacity>
<TouchableOpacity style={styles.headerclose} onPress={closeConversation}>
<Ionicons name={'close'} size={28} color={Colors.grey} style={styles.titlebutton} />
</TouchableOpacity>
</View>
)}
<Text>Conversation</Text>
</View>
);
} }

View File

@ -22,7 +22,7 @@ export const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingLeft: 16, paddingLeft: 16,
paddingTop: 16, paddingTop: 8,
paddingBottom: 8, paddingBottom: 8,
}, },
titletext: { titletext: {

View File

@ -1,7 +1,33 @@
import { useState } from 'react'; import { useEffect, useState, useContext } from 'react';
import { ProfileContext } from 'context/ProfileContext';
import { CardContext } from 'context/CardContext';
import { ConversationContext } from 'context/ConversationContext';
import { getChannelSubjectLogo } from 'context/channelUtil';
export function useConversation() { export function useConversation() {
const [state, setState] = useState({}); const [state, setState] = useState({
subject: null,
logo: null,
});
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const profile = useContext(ProfileContext);
const card = useContext(CardContext);
const conversation = useContext(ConversationContext);
useEffect(() => {
const cardId = conversation.state.card?.cardId;
const profileGuid = profile.state.identity?.guid;
const channel = conversation.state.channel;
const cards = card.state.cards;
cardImageUrl = card.actions.getCardImageUrl;
const { logo, subject } = getChannelSubjectLogo(cardId, profileGuid, channel, cards, cardImageUrl);
updateState({ logo, subject });
}, [conversation.state, card.state, profile.state]);
const actions = {}; const actions = {};

View File

@ -3,6 +3,7 @@ import { CardContext } from 'context/CardContext';
import { ChannelContext } from 'context/ChannelContext'; import { ChannelContext } from 'context/ChannelContext';
import { ProfileContext } from 'context/ProfileContext'; import { ProfileContext } from 'context/ProfileContext';
import { getCardByGuid } from 'context/cardUtil'; import { getCardByGuid } from 'context/cardUtil';
import { getChannelSubjectLogo } from 'context/channelUtil';
import moment from 'moment'; import moment from 'moment';
export function useBlockedTopics() { export function useBlockedTopics() {
@ -44,47 +45,9 @@ export function useBlockedTopics() {
timestamp = moment(date).format('M/DD/YYYY'); timestamp = moment(date).format('M/DD/YYYY');
} }
let subject;
if (item?.detail?.data) {
try {
topic = JSON.parse(item?.detail?.data).subject;
subject = topic;
}
catch (err) {
console.log(err);
}
}
if (!subject) {
let contacts = [];
if (item.cardId) {
contacts.push(card.state.cards.get(item.cardId));
}
if (item.channel.detail?.members) {
const profileGuid = profile.state.identity.guid;
item.channel.detail.members.forEach(guid => {
if (profileGuid !== guid) {
const contact = getCardByGuid(card.state.cards, guid);
contacts.push(contact);
}
})
}
if (contacts.length) { const profileGuid = profile.state?.identity?.guid;
let names = []; const { logo, subject } = getChannelSubjectLogo(item.cardId, profileGuid, item.channel, card.state.cards, card.actions.getCardImageUrl);
for (let contact of contacts) {
if (contact?.card?.profile?.name) {
names.push(contact.card.profile.name);
}
else if (contact?.card?.profile?.handle) {
names.push(contact.card.profile.handle);
}
}
subject = names.join(', ');
}
else {
subject = "Notes";
}
}
return { return {
id: `${item.cardId}:${item.channel.channelId}`, id: `${item.cardId}:${item.channel.channelId}`,

View File

@ -12,7 +12,7 @@ export function Welcome() {
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<Text style={styles.header}>Databag</Text> <Text style={styles.header}>Databag</Text>
<Text style={styles.label}>Communication for the decentralized web</Text> <Text style={styles.label}>Communication for the Decentralized Web</Text>
<Image style={styles.image} source={session} /> <Image style={styles.image} source={session} />
<View style={styles.steps}> <View style={styles.steps}>
<Text style={styles.stepstext}>Setup your profile</Text> <Text style={styles.stepstext}>Setup your profile</Text>