mergin refactored channel

This commit is contained in:
balzack 2023-02-27 09:03:56 -08:00
parent 3efa83f1c6
commit 233bcb01e9
7 changed files with 250 additions and 8 deletions

View File

@ -48,7 +48,7 @@ export function useAppContext() {
} }
access.current = await store.actions.init(); access.current = await store.actions.init();
if (access.current) { if (access.current) {
await setSession(access.current); await setSession();
} }
else { else {
updateState({ session: false }); updateState({ session: false });
@ -58,7 +58,8 @@ export function useAppContext() {
}, []); }, []);
const setSession = async () => { 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 account.actions.setSession(access.current);
await profile.actions.setSession(access.current); await profile.actions.setSession(access.current);
await card.actions.setSession(access.current); await card.actions.setSession(access.current);

View File

@ -148,6 +148,10 @@ export function Session() {
conversation.actions.setConversation(cardId, channelId); conversation.actions.setConversation(cardId, channelId);
setChannel(true); setChannel(true);
}; };
const clearConversation = () => {
conversation.actions.clearConversation();
setChannel(false);
};
const openDetails = () => { const openDetails = () => {
navParams.detailNav.openDrawer(); navParams.detailNav.openDrawer();
}; };

View File

@ -1,9 +1,10 @@
import { useEffect } from 'react'; 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 Ionicons from 'react-native-vector-icons/AntDesign';
import { styles } from './Channels.styled'; import { styles } from './Channels.styled';
import { useChannels } from './useChannels.hook'; import { useChannels } from './useChannels.hook';
import { Colors } from 'constants/Colors'; import { Colors } from 'constants/Colors';
import { ChannelItem } from './channelItem/ChannelItem';
export function Channels({ navigation, openConversation }) { export function Channels({ navigation, openConversation }) {
@ -40,9 +41,20 @@ export function Channels({ navigation, openConversation }) {
</View> </View>
</View> </View>
)} )}
<View style={styles.content}> { state.channels.length == 0 && (
<Text>Channels</Text> <View style={styles.content}>
</View> <Text style={styles.notfoundtext}>No Topics Found</Text>
</View>
)}
{ state.channels.length != 0 && (
<FlatList
style={styles.content}
data={state.channels}
initialNumToRender={25}
renderItem={({ item }) => <ChannelItem item={item} openConversation={openConversation} />}
keyExtractor={item => (`${item.cardId}:${item.channelId}`)}
/>
)}
{ !navigation && ( { !navigation && (
<View style={styles.columnbottom}> <View style={styles.columnbottom}>
<TouchableOpacity style={styles.addbottom} onPress={actions.showAdding}> <TouchableOpacity style={styles.addbottom} onPress={actions.showAdding}>

View File

@ -60,6 +60,8 @@ export const styles = StyleSheet.create({
}, },
content: { content: {
flexGrow: 1, flexGrow: 1,
flexShrink: 1,
paddingLeft: 4,
}, },
columnbottom: { columnbottom: {
paddingLeft: 24, paddingLeft: 24,

View File

@ -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 (
<TouchableOpacity style={styles.container} activeOpacity={1} onPress={() => openConversation(item.cardId, item.channelId, item.revision)}>
<Logo src={item.logo} width={32} height={32} radius={6} />
<View style={styles.detail}>
<View style={styles.subject}>
{ item.locked && !item.unlocked && (
<Ionicons name="lock" style={styles.subjectIcon} size={16} color={Colors.text} />
)}
{ item.locked && item.unlocked && (
<Ionicons name="lock-open-variant-outline" style={styles.subjectIcon} size={16} color={Colors.grey} />
)}
<Text style={styles.subjectText} numberOfLines={1} ellipsizeMode={'tail'}>{ item.subject }</Text>
</View>
<Text style={styles.message} numberOfLines={1} ellipsizeMode={'tail'}>{ item.message }</Text>
</View>
{ item.updated && (
<View style={styles.dot} />
)}
</TouchableOpacity>
)
}

View File

@ -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,
}
})

View File

@ -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() { export function useChannels() {
const [state, setState] = useState({ const [state, setState] = useState({
filter: null, 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) => { const updateState = (value) => {
setState((s) => ({ ...s, ...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 = { const actions = {
setFilter: () => { setFilter: (filter) => {
updateState({ filter });
}, },
}; };