merging refactored details screen

This commit is contained in:
balzack 2023-02-28 22:58:26 -08:00
parent 4479ba5dca
commit c062b84426
6 changed files with 593 additions and 26 deletions

View File

@ -14,6 +14,7 @@ export function useConversationContext() {
topics: new Map(),
card: null,
channel: null,
notification: null,
});
const card = useContext(CardContext);
const channel = useContext(ChannelContext);
@ -48,7 +49,16 @@ export function useConversationContext() {
reset.current = false;
loaded.current = false;
topics.current = new Map();
updateState({ offsync: false, channel: null, card: null, topics: topics.current });
let notification;
try {
notification = await getNotifications();
}
catch(err) {
console.log(err);
}
updateState({ offsync: false, channel: null, card: null, topics: topics.current, notification });
}
if (conversation) {
@ -167,23 +177,15 @@ export function useConversationContext() {
await channel.actions.removeChannel(channelId);
}
},
getNotifications: async () => {
setNotifications: async (notification) => {
const { cardId, channelId } = conversationId.current || {};
if (cardId) {
await card.actions.getChannelNotifications(cardId, channelId);
await card.actions.setChannelNotifications(cardId, channelId, notification);
}
else if (channelId) {
await channel.actions.getNotifications(channelId);
}
},
setNotifications: async (notify) => {
const { cardId, channelId } = conversationId.current || {};
if (cardId) {
await card.actions.setChannelNotifications(cardId, channelId);
}
else if (channelId) {
await channel.actions.setNotifications(channelId, notify);
await channel.actions.setNotifications(channelId, notification);
}
updateState({ notification });
},
setChannelCard: async (id) => {
const { cardId, channelId } = conversationId.current || {};
@ -381,6 +383,16 @@ export function useConversationContext() {
return await channel.actions.getTopic(channelId, topicId);
}
const getNotifications = async (notification) => {
const { cardId, channelId } = conversationId.current || {};
if (cardId) {
return await card.actions.getChannelNotifications(cardId, channelId);
}
else if (channelId) {
return await channel.actions.getNotifications(channelId);
}
}
const mapTopicEntry = (entry) => {
return {
topicId: entry.id,

View File

@ -13,7 +13,7 @@ import { Profile, ProfileHeader, ProfileBody } from './profile/Profile';
import { CardsHeader, CardsBody, Cards } from './cards/Cards';
import { RegistryHeader, RegistryBody, Registry } from './registry/Registry';
import { ContactHeader, ContactBody, Contact } from './contact/Contact';
import { Details, DetailsHeader, DetailsBody } from './details/Details';
import { Details } from './details/Details';
import { Conversation, ConversationHeader, ConversationBody } from './conversation/Conversation';
import { Welcome } from './welcome/Welcome';
import { Channels } from './channels/Channels';
@ -70,8 +70,10 @@ export function Session() {
{(props) => <Conversation navigation={props.navigation} cardId={cardId} channelId={channelId} openDetails={() => openDetails(props.navigation)} closeConversation={closeConversation} /> }
</ConversationStack.Screen>
<ConversationStack.Screen name="details" options={{ ...stackParams, headerTitle: (props) => <DetailsHeader /> }}>
{(props) => <DetailsBody clearConversation={() => clearConversation(props.navigation)} />}
<ConversationStack.Screen name="details" options={{ ...stackParams, headerTitle: (props) => (
<Text style={styles.headertext}>Details</Text>
)}}>
{(props) => <Details clearConversation={() => clearConversation(props.navigation)} />}
</ConversationStack.Screen>
</ConversationStack.Navigator>

View File

@ -134,4 +134,8 @@ export const styles = StyleSheet.create({
profileLabel: {
paddingLeft: 8,
},
headertext: {
fontSize: 18,
color: Colors.tetx,
},
});

View File

@ -1,14 +1,246 @@
import { Text } from 'react-native';
export function DetailsHeader() {
return <Text>DetailsHeader</Text>
}
export function DetailsBody({ channel, clearConversation }) {
return <Text>DetailsBody</Text>
}
import { KeyboardAvoidingView, FlatList, Alert, Modal, View, Text, Switch, TouchableOpacity, TextInput } from 'react-native';
import { styles } from './Details.styled';
import { useDetails } from './useDetails.hook';
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';
export function Details({ channel, clearConversation }) {
return <Text>Details</Text>
const { state, actions } = useDetails();
const saveSubject = async () => {
try {
await actions.saveSubject();
actions.hideEditSubject();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Save Subject',
'Please try again.'
)
}
}
const setNotifications = async (notify) => {
try {
await actions.setNotifications(notify);
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Update Notifications',
'Please try again.',
)
}
}
const remove = () => {
Alert.alert(
"Removing Topic",
"Confirm?",
[
{ text: "Cancel",
onPress: () => {},
},
{ text: "Remove",
onPress: async () => {
try {
await actions.remove();
clearConversation();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Delete Topic',
'Please try again.'
)
}
},
}
]
);
}
const block = () => {
Alert.alert(
"Blocking Topic",
"Confirm?",
[
{ text: "Cancel",
onPress: () => {},
},
{ text: "Block",
onPress: async () => {
try {
await actions.block();
clearConversation();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Block Topic',
'Please try again.'
)
}
},
}
]
);
}
const report = () => {
Alert.alert(
"Report Topic",
"Confirm?",
[
{ text: "Cancel",
onPress: () => {},
},
{ text: "Report",
onPress: async () => {
try {
await actions.report();
}
catch (err) {
console.log(err);
Alert.alert(
'Failed to Report Topic',
'Please try again.'
)
}
},
}
]
);
}
return (
<View style={styles.body}>
<View style={styles.details}>
<Logo src={state.logo} width={72} height={72} radius={8} />
<View style={styles.info}>
<View style={styles.subject}>
{ state.locked && !state.unlocked && (
<AntIcons name="lock" style={styles.subjectIcon} size={16} color={Colors.text} />
)}
{ state.locked && state.unlocked && (
<MatIcons name="lock-open-variant-outline" style={styles.subjectIcon} size={16} color={Colors.text} />
)}
<Text style={styles.subjectText} numberOfLines={1} ellipsizeMode={'tail'}>{ state.subject }</Text>
{ !state.hostId && (!state.locked || state.sealable) && (
<TouchableOpacity onPress={actions.showEditSubject}>
<AntIcons name="edit" size={16} color={Colors.text} />
</TouchableOpacity>
)}
</View>
<Text style={styles.created}>{ state.created }</Text>
<Text style={styles.mode}>{ state.hostId ? 'guest' : 'host' }</Text>
</View>
</View>
<View style={styles.controls}>
{ !state.hostId && (
<TouchableOpacity style={styles.button} onPress={remove}>
<Text style={styles.buttonText}>Delete Topic</Text>
</TouchableOpacity>
)}
{ state.hostId && (
<TouchableOpacity style={styles.button} onPress={remove}>
<Text style={styles.buttonText}>Leave Topic</Text>
</TouchableOpacity>
)}
<TouchableOpacity style={styles.button} onPress={block}>
<Text style={styles.buttonText}>Block Topic</Text>
</TouchableOpacity>
{ state.hostId && (
<TouchableOpacity style={styles.button} onPress={report}>
<Text style={styles.buttonText}>Report Topic</Text>
</TouchableOpacity>
)}
{ !state.hostId && !state.locked && (
<TouchableOpacity style={styles.button} onPress={actions.showEditMembers}>
<Text style={styles.buttonText}>Edit Membership</Text>
</TouchableOpacity>
)}
<View style={styles.notify}>
<TouchableOpacity onPress={() => setNotifications(!state.notification)} activeOpacity={1}>
<Text style={styles.notifyText}>Enable Notifications</Text>
</TouchableOpacity>
<Switch style={styles.switch} value={state.notification} onValueChange={setNotifications} trackColor={styles.track}/>
</View>
</View>
<View style={styles.members}>
<Text style={styles.membersLabel}>Members:</Text>
{ state.count - state.members.length > 0 && (
<Text style={styles.unknown}> (+ {state.count - state.contacts.length} unknown)</Text>
)}
</View>
<FlatList style={styles.cards}
data={state.contacts}
renderItem={({ item }) => <MemberItem hostId={state.hostId} editable={false} members={[]} item={item} />}
keyExtractor={item => item.cardId}
/>
<Modal
animationType="fade"
transparent={true}
visible={state.editSubject}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideEditSubject}
>
<KeyboardAvoidingView behavior="height" style={styles.editWrapper}>
<View style={styles.editContainer}>
<Text style={styles.editHeader}>Edit Subject:</Text>
<View style={styles.inputField}>
<TextInput style={styles.input} value={state.subjectUpdate} onChangeText={actions.setSubjectUpdate}
autoCapitalize="words" placeholder="Subject" placeholderTextColor={Colors.grey} />
</View>
<View style={styles.editControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideEditSubject}>
<Text>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.save} onPress={saveSubject}>
<Text style={styles.saveText}>Save</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
<Modal
animationType="fade"
transparent={true}
visible={state.editMembers}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideEditMembers}
>
<KeyboardAvoidingView behavior="height" style={styles.editWrapper}>
<View style={styles.editContainer}>
<Text style={styles.editHeader}>Channel Members:</Text>
<FlatList style={styles.editMembers}
data={state.connected}
renderItem={({ item }) => <Text>MEMBER</Text> }
keyExtractor={item => item.cardId}
/>
<View style={styles.editControls}>
<TouchableOpacity style={styles.cancel} onPress={actions.hideEditMembers}>
<Text>Done</Text>
</TouchableOpacity>
</View>
</View>
</KeyboardAvoidingView>
</Modal>
</View>
)
}

View File

@ -0,0 +1,181 @@
import { StyleSheet } from 'react-native';
import { Colors } from 'constants/Colors';
export const styles = StyleSheet.create({
body: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
details: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
paddingTop: 16,
flexShrink: 1,
minWidth: 0,
},
info: {
paddingLeft: 8,
display: 'flex',
flexDirection: 'column',
minWidth: 0,
flexShrink: 1,
},
subject: {
fontSize: 18,
display: 'flex',
flexDirection: 'row',
paddingRight: 8,
color: Colors.text,
alignItems: 'center',
minWidth: 0,
flexShrink: 1,
},
subjectIcon: {
paddingRight: 4
},
subjectText: {
fontSize: 18,
flexShrink: 1,
minWidth: 0,
color: Colors.text,
paddingRight: 4,
},
created: {
fontSize: 16,
color: Colors.text,
},
mode: {
fontSize: 16,
color: Colors.text,
},
title: {
fontSize: 20,
},
controls: {
paddingTop: 16,
},
button: {
width: 128,
backgroundColor: Colors.primary,
borderRadius: 4,
margin: 8,
},
buttonText: {
width: '100%',
textAlign: 'center',
color: Colors.white,
padding: 4,
},
members: {
paddingBottom: 4,
paddingTop: 24,
width: '100%',
borderBottomWidth: 1,
borderColor: Colors.divider,
display: 'flex',
flexDirection: 'row',
},
membersLabel: {
paddingLeft: 16,
},
unknown: {
color: Colors.grey,
paddingLeft: 8,
},
cards: {
width: '100%',
},
save: {
padding: 8,
borderRadius: 4,
backgroundColor: Colors.primary,
width: 72,
display: 'flex',
alignItems: 'center',
},
link: {
marginTop: 16,
},
linkText: {
color: Colors.primary,
},
saveText: {
color: Colors.white,
},
cancel: {
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 8,
marginRight: 8,
width: 72,
display: 'flex',
alignItems: 'center',
},
inputField: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 8,
marginBottom: 8,
maxHeight: 92,
display: 'flex',
flexDirection: 'row',
},
input: {
fontSize: 14,
flexGrow: 1,
},
editControls: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
editWrapper: {
display: 'flex',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(52, 52, 52, 0.8)'
},
editContainer: {
backgroundColor: Colors.formBackground,
padding: 16,
width: '80%',
maxWidth: 400,
},
editHeader: {
fontSize: 18,
paddingBottom: 16,
},
editMembers: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
marginBottom: 8,
height: 250,
},
notify: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingTop: 16,
},
notifyText: {
fontSize: 16,
color: Colors.text,
},
track: {
false: Colors.grey,
true: Colors.background,
},
switch: {
transform: [{ scaleX: .7 }, { scaleY: .7 }],
},
})

View File

@ -0,0 +1,136 @@
import { useState, useEffect, useRef, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { ConversationContext } from 'context/ConversationContext';
import { CardContext } from 'context/CardContext';
import { AccountContext } from 'context/AccountContext';
import { ProfileContext } from 'context/ProfileContext';
import { getChannelSubjectLogo } from 'context/channelUtil';
import { getChannelSeals, isUnsealed } from 'context/sealUtil';
import moment from 'moment';
export function useDetails() {
const [state, setState] = useState({
subject: null,
created: null,
logo: null,
hostId: null,
connected: [],
members: [],
editSubject: false,
editMembers: false,
subjectUpdate: null,
pushEnabled: false,
locked: false,
unlocked: false,
count: 0,
});
const card = useContext(CardContext);
const account = useContext(AccountContext);
const conversation = useContext(ConversationContext);
const profile = useContext(ProfileContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
let locked;
let unlocked;
const { channel, notification } = conversation.state;
if (channel.detail.dataType === 'sealed') {
locked = true;
try {
const { sealKey } = account.state;
const seals = getChannelSeals(channel.detail.data);
unlocked = isUnsealed(seals, sealKey);
}
catch(err) {
console.log(err);
unlocked = false;
}
}
else {
locked = false;
unlocked = false;
}
updateState({ locked, unlocked, 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]);
useEffect(() => {
const cardId = conversation.state.card?.cardId;
const profileGuid = profile.state.identity?.guid;
const channel = conversation.state.channel;
const cards = card.state.cards;
const cardImageUrl = card.actions.getCardImageUrl;
const { logo, subject } = getChannelSubjectLogo(cardId, profileGuid, channel, cards, cardImageUrl);
const timestamp = conversation.state.channel?.detail?.created;
let created;
const date = new Date(item.detail.created * 1000);
const now = new Date();
const offset = now.getTime() - date.getTime();
if(offset < 86400000) {
created = moment(date).format('h:mma');
}
else if (offset < 31449600000) {
created = moment(date).format('M/DD');
}
else {
created = moment(date).format('M/DD/YYYY');
}
updateState({ logo, subject, created });
}, [conversation]);
const actions = {
showEditMembers: () => {
updateState({ editMembers: true });
},
hideEditMembers: () => {
updateState({ editMembers: false });
},
showEditSubject: () => {
updateState({ editSubject: true });
},
hideEditSubject: () => {
updateState({ editSubject: false });
},
setSubjectUpdate: (subjectUpdate) => {
updateState({ subjectUpdate });
},
saveSubject: async () => {
if (state.locked) {
await conversation.actions.setSealedSubject(state.subjectUpdate, account.state.sealKey);
}
else {
await conversation.actions.setSubject(state.subjectUpdate);
}
},
remove: async () => {
await conversation.actions.removeChannel();
},
block: async() => {
await conversation.actions.setChannelFlag();
},
report: async() => {
await conversation.actions.addChannelAlert();
},
setNotifications: async (notification) => {
await conversation.actions.setNotifications(notification);
},
};
return { state, actions };
}