merging add channel support

This commit is contained in:
balzack 2023-02-27 13:30:11 -08:00
parent 84d8598f29
commit aaf7d38836
6 changed files with 354 additions and 1 deletions

View File

@ -1,15 +1,19 @@
import { useEffect } from 'react';
import { View, FlatList, Text, TextInput, TouchableOpacity } from 'react-native';
import { Switch, KeyboardAvoidingView, Modal, 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';
import { AddMember } from './addMember/AddMember';
export function Channels({ navigation, openConversation }) {
const { state, actions } = useChannels();
const addTopic = async () => {
};
useEffect(() => {
if (navigation) {
navigation.setOptions({
@ -63,6 +67,54 @@ export function Channels({ navigation, openConversation }) {
</TouchableOpacity>
</View>
)}
<Modal
animationType="fade"
transparent={true}
visible={state.adding}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideAdding}
>
<KeyboardAvoidingView behavior="height" style={styles.addWrapper}>
<View style={styles.addContainer}>
<Text style={styles.addHeader}>New Topic:</Text>
<View style={styles.addField}>
<TextInput style={styles.input} value={state.addSubject} onChangeText={actions.setAddSubject}
autoCapitalize="words" placeholder="Subject (optional)" placeholderTextColor={Colors.grey} />
</View>
<Text style={styles.label}>Members:</Text>
{ state.contacts.length == 0 && (
<View style={styles.emptyMembers}>
<Text style={styles.empty}>No Connected Contacts</Text>
</View>
)}
{ state.contacts.length > 0 && (
<FlatList style={styles.addMembers}
data={state.contacts}
renderItem={({ item }) => <AddMember members={state.addMembers} item={item}
setCard={actions.setAddMember} clearCard={actions.clearAddMember} />}
keyExtractor={item => item.cardId}
/>
)}
<View style={styles.addControls}>
<View style={styles.sealed}>
{ state.sealable && (
<>
<Switch style={styles.switch} trackColor={styles.track}
value={state.sealed} onValueChange={actions.setSealed} />
<Text>Sealed</Text>
</>
)}
</View>
<TouchableOpacity style={styles.cancel} onPress={actions.hideAdding}>
<Text>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.save} onPress={addTopic}>
<Text style={styles.saveText}>Create</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
</View>
);
}

View File

@ -33,6 +33,21 @@ export const styles = StyleSheet.create({
color: Colors.text,
fontSize: 14,
},
addField: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 8,
marginBottom: 8,
maxHeight: 92,
display: 'flex',
flexDirection: 'row',
},
input: {
fontSize: 14,
flexGrow: 1,
},
icon: {
paddingLeft: 8,
},
@ -77,5 +92,86 @@ export const styles = StyleSheet.create({
borderBottomWidth: 1,
borderColor: Colors.divider,
},
addWrapper: {
display: 'flex',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(52, 52, 52, 0.8)'
},
addContainer: {
backgroundColor: Colors.formBackground,
padding: 16,
width: '80%',
maxWidth: 400,
},
addHeader: {
fontSize: 18,
paddingBottom: 16,
},
addMembers: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
marginBottom: 8,
height: 200,
},
emptyMembers: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
marginBottom: 8,
height: 200,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
cancel: {
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 4,
marginRight: 8,
width: 72,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
save: {
backgroundColor: Colors.primary,
borderRadius: 4,
padding: 4,
marginRight: 8,
width: 72,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
saveText: {
color: Colors.white,
},
addControls: {
display: 'flex',
flexDirection: 'row',
},
sealed: {
display: 'flex',
flexDirection: 'row',
flexGrow: 1,
alignItems: 'center',
},
sealedText: {
color: Colors.text,
},
track: {
false: Colors.grey,
true: Colors.background,
},
switch: {
transform: [{ scaleX: .7 }, { scaleY: .7 }],
},
});

View File

@ -0,0 +1,31 @@
import { Alert, Text, Switch, TouchableOpacity, View } from 'react-native';
import { Logo } from 'utils/Logo';
import { styles } from './AddMember.styled';
import { useAddMember } from './useAddMember.hook';
import Colors from 'constants/Colors';
export function AddMember({ members, item, setCard, clearCard }) {
const { state, actions } = useAddMember(item, members);
const setMember = (set) => {
if (set) {
setCard(item.cardId);
}
else {
clearCard(item.cardId);
}
};
return (
<TouchableOpacity activeOpacity={1} style={styles.container} onPress={() => setMember(!state.member)}>
<Logo src={state.logo} width={32} height={32} radius={6} />
<View style={styles.detail}>
<Text style={styles.name} numberOfLines={1} ellipsizeMode={'tail'}>{ state.name }</Text>
<Text style={styles.handle} numberOfLines={1} ellipsizeMode={'tail'}>{ state.handle }</Text>
</View>
<Switch style={styles.switch} trackColor={styles.track}
value={state.member} onValueChange={setMember} />
</TouchableOpacity>
);
}

View File

@ -0,0 +1,73 @@
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,
},
connected: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.connected,
},
requested: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.requested,
},
connecting: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.connecting,
},
pending: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.pending,
},
confirmed: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.confirmed,
},
track: {
false: Colors.grey,
true: Colors.background,
},
switch: {
transform: [{ scaleX: .7 }, { scaleY: .7 }],
},
})

View File

@ -0,0 +1,36 @@
import { useState, useEffect, useRef, useContext } from 'react';
import { CardContext } from 'context/CardContext';
export function useAddMember(item, members) {
const [state, setState] = useState({
name: null,
handle: null,
logo: null,
cardId: null,
member: false,
});
const card = useContext(CardContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
const member = members.filter(contact => item.cardId === contact);
updateState({ member: member.length > 0 });
}, [members]);
useEffect(() => {
const { cardId, revision, profile } = item;
const { name, handle, node } = profile;
updateState({ cardId, name, handle: `${handle}@${node}`,
logo: profile.imageSet ? card.actions.getCardImageUrl(cardId) : 'avatar' });
}, [card.state]);
const actions = {
};
return { state, actions };
}

View File

@ -11,6 +11,12 @@ export function useChannels() {
const [state, setState] = useState({
filter: null,
channels: [],
adding: false,
contacts: [],
addMembers: [],
addSubject: null,
sealed: false,
sealable: false,
});
const channel = useContext(ChannelContext);
@ -149,6 +155,45 @@ export function useChannels() {
return { cardId, channelId, subject, message, logo, timestamp, updated, locked, unlocked };
}
useEffect(() => {
const { status, sealKey } = account.state;
if (status?.seal?.publicKey && sealKey?.public && sealKey?.private && sealKey?.public === status.seal.publicKey) {
updateState({ sealable: true });
}
else {
updateState({ sealed: false, sealable: false });
}
}, [account.state]);
useEffect(() => {
const contacts = [];
card.state.cards.forEach(entry => {
contacts.push(entry.card);
});
const filtered = contacts.filter(contact => {
if (contact.detail.status !== 'connected') {
return false;
}
if (state.sealed && !contact.profile.seal) {
return false;
}
return true;
});
const sorted = filtered.sort((a, b) => {
const aName = a?.profile?.name;
const bName = b?.profile?.name;
if (aName === bName) {
return 0;
}
if (!aName || (aName < bName)) {
return -1;
}
return 1;
});
const addMembers = state.addMembers.filter(item => sorted.some(contact => contact.cardId === item));
updateState({ contacts: sorted, addMembers });
}, [card.state, state.sealed]);
useEffect(() => {
syncChannels();
}, [app.state, card.state, channel.state, state.filter]);
@ -205,9 +250,29 @@ export function useChannels() {
};
const actions = {
setSealed: (sealed) => {
updateState({ sealed });
},
setFilter: (filter) => {
updateState({ filter });
},
showAdding: () => {
updateState({ adding: true });
},
hideAdding: () => {
updateState({ adding: false });
},
setAddSubject: (addSubject) => {
updateState({ addSubject });
},
setAddMember: (cardId) => {
updateState({ addMembers: [ ...state.addMembers, cardId ] });
},
clearAddMember: (cardId) => {
updateState({ addMembers: state.addMembers.filter(item => item !== cardId) });
},
addTopic: () => {
},
};
return { state, actions };