support blocking and unblocking topics

This commit is contained in:
Roland Osborne 2022-10-11 12:18:08 -07:00
parent 445b364365
commit edcffa6a67
12 changed files with 348 additions and 27 deletions

View File

@ -190,6 +190,17 @@ export function useCardContext() {
} }
} }
} }
const setCardChannelBlocked = (cardId, channelId, blocked) => {
let card = cards.current.get(cardId);
if (card) {
let channel = card.channels.get(channelId);
if (channel) {
channel.blocked = blocked;
card.channels.set(channelId, channel);
cards.current.set(cardId, card);
}
}
}
const clearCardChannel = (cardId, channelId) => { const clearCardChannel = (cardId, channelId) => {
let card = cards.current.get(cardId); let card = cards.current.get(cardId);
if (card) { if (card) {
@ -438,6 +449,18 @@ export function useCardContext() {
setCardChannelSyncRevision(cardId, channelId, revision); setCardChannelSyncRevision(cardId, channelId, revision);
updateState({ cards: cards.current }); updateState({ cards: cards.current });
}, },
setChannelBlocked: async (cardId, channelId) => {
const { guid } = session.current;
await store.actions.setCardChannelItemBlocked(guid, cardId, channelId);
setCardChannelBlocked(cardId, channelId, true);
updateState({ cards: cards.current });
},
clearChannelBlocked: async (cardId, channelId) => {
const { guid } = session.current;
await store.actions.clearCardChannelItemBlocked(guid, cardId, channelId);
setCardChannelBlocked(cardId, channelId, false);
updateState({ cards: cards.current });
},
getChannelTopicItems: async (cardId, channelId) => { getChannelTopicItems: async (cardId, channelId) => {
const { guid } = session.current; const { guid } = session.current;
return await store.actions.getCardChannelTopicItems(guid, cardId, channelId); return await store.actions.getCardChannelTopicItems(guid, cardId, channelId);

View File

@ -83,6 +83,13 @@ export function useChannelContext() {
channels.current.set(channelId, channel); channels.current.set(channelId, channel);
} }
} }
const setChannelBlocked = (channelId, blocked) => {
let channel = channels.current.get(channelId);
if (channel) {
channel.blocked = blocked;
channels.current.set(channelId, channel);
}
}
const sync = async () => { const sync = async () => {
@ -182,6 +189,18 @@ export function useChannelContext() {
setChannelSyncRevision(channelId, revision); setChannelSyncRevision(channelId, revision);
updateState({ channels: channels.current }); updateState({ channels: channels.current });
}, },
setBlocked: async (channelId) => {
const { guid } = session.current;
await store.actions.setChannelItemBlocked(guid, channelId);
setChannelBlocked(channelId, 1);
updateState({ channels: channels.current });
},
clearBlocked: async (channelId) => {
const { guid } = session.current;
await store.actions.clearChannelItemBlocked(guid, channelId);
setChannelBlocked(channelId, 0);
updateState({ channels: channels.current });
},
getTopicItems: async (channelId) => { getTopicItems: async (channelId) => {
const { guid } = session.current; const { guid } = session.current;
return await store.actions.getChannelTopicItems(guid, channelId); return await store.actions.getChannelTopicItems(guid, channelId);

View File

@ -375,6 +375,17 @@ export function useConversationContext() {
await channel.actions.clearCard(channelId, id); await channel.actions.clearCard(channelId, id);
} }
}, },
setBlocked: async () => {
if (conversationId.current) {
const { cardId, channelId } = conversationId.current;
if (cardId) {
await card.actions.setChannelBlocked(cardId, channelId);
}
else {
await channel.actions.setBlocked(channelId);
}
}
},
} }
return { state, actions } return { state, actions }

View File

@ -1,7 +1,7 @@
import { useEffect, useState, useRef, useContext } from 'react'; import { useEffect, useState, useRef, useContext } from 'react';
import SQLite from "react-native-sqlite-storage"; import SQLite from "react-native-sqlite-storage";
const DATABAG_DB = 'databag_v038.db'; const DATABAG_DB = 'databag_v039.db';
export function useStoreContext() { export function useStoreContext() {
const [state, setState] = useState({}); const [state, setState] = useState({});
@ -12,7 +12,7 @@ export function useStoreContext() {
} }
const initSession = async (guid) => { const initSession = async (guid) => {
await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_${guid} (channel_id text, revision integer, detail_revision integer, topic_revision integer, sync_revision integer, detail text, summary text, offsync integer, read_revision integer, unique(channel_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_${guid} (channel_id text, revision integer, detail_revision integer, topic_revision integer, blocked integer, sync_revision integer, detail text, summary text, offsync integer, read_revision integer, unique(channel_id))`);
await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_topic_${guid} (channel_id text, topic_id text, revision integer, detail_revision integer, detail text, unique(channel_id, topic_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS channel_topic_${guid} (channel_id text, topic_id text, revision integer, detail_revision integer, detail text, unique(channel_id, topic_id))`);
await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_${guid} (card_id text, revision integer, detail_revision integer, profile_revision integer, detail text, profile text, notified_view integer, notified_article integer, notified_profile integer, notified_channel integer, offsync integer, blocked integer, unique(card_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_${guid} (card_id text, revision integer, detail_revision integer, profile_revision integer, detail text, profile text, notified_view integer, notified_article integer, notified_profile integer, notified_channel integer, offsync integer, blocked integer, unique(card_id))`);
await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_channel_${guid} (card_id text, channel_id text, revision integer, detail_revision integer, topic_revision integer, sync_revision integer, detail text, summary text, offsync integer, blocked integer, read_revision integer, unique(card_id, channel_id))`); await db.current.executeSql(`CREATE TABLE IF NOT EXISTS card_channel_${guid} (card_id text, channel_id text, revision integer, detail_revision integer, topic_revision integer, sync_revision integer, detail text, summary text, offsync integer, blocked integer, read_revision integer, unique(card_id, channel_id))`);
@ -189,6 +189,12 @@ export function useStoreContext() {
setChannelItemSyncRevision: async (guid, channelId, revision) => { setChannelItemSyncRevision: async (guid, channelId, revision) => {
await db.current.executeSql(`UPDATE channel_${guid} set sync_revision=? where channel_id=?`, [revision, channelId]); await db.current.executeSql(`UPDATE channel_${guid} set sync_revision=? where channel_id=?`, [revision, channelId]);
}, },
setChannelItemBlocked: async (guid, channelId) => {
await db.current.executeSql(`UPDATE channel_${guid} set blocked=? where channel_id=?`, [1, channelId]);
},
clearChannelItemBlocked: async (guid, channelId) => {
await db.current.executeSql(`UPDATE channel_${guid} set blocked=? where channel_id=?`, [0, channelId]);
},
setChannelItemDetail: async (guid, channelId, revision, detail) => { setChannelItemDetail: async (guid, channelId, revision, detail) => {
await db.current.executeSql(`UPDATE channel_${guid} set detail_revision=?, detail=? where channel_id=?`, [revision, encodeObject(detail), channelId]); await db.current.executeSql(`UPDATE channel_${guid} set detail_revision=?, detail=? where channel_id=?`, [revision, encodeObject(detail), channelId]);
}, },
@ -207,7 +213,7 @@ export function useStoreContext() {
}; };
}, },
getChannelItems: async (guid) => { getChannelItems: async (guid) => {
const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, sync_revision, detail_revision, topic_revision, detail, summary FROM channel_${guid}`, []); const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, sync_revision, blocked, detail_revision, topic_revision, detail, summary FROM channel_${guid}`, []);
return values.map(channel => ({ return values.map(channel => ({
channelId: channel.channel_id, channelId: channel.channel_id,
revision: channel.revision, revision: channel.revision,
@ -215,6 +221,7 @@ export function useStoreContext() {
detailRevision: channel.detail_revision, detailRevision: channel.detail_revision,
topicRevision: channel.topic_revision, topicRevision: channel.topic_revision,
syncRevision: channel.sync_revision, syncRevision: channel.sync_revision,
blocked: channel.blocked,
detail: decodeObject(channel.detail), detail: decodeObject(channel.detail),
summary: decodeObject(channel.summary), summary: decodeObject(channel.summary),
})); }));

View File

@ -126,7 +126,7 @@ export function useChannels() {
const timestamp = item?.summary?.lastTopic?.created; const timestamp = item?.summary?.lastTopic?.created;
return { cardId: item.cardId, channelId: item.channelId, contacts, logo, subject, message, updated, revision: item.revision, timestamp }; return { cardId: item.cardId, channelId: item.channelId, contacts, logo, subject, message, updated, revision: item.revision, timestamp, blocked: item.blocked === 1 };
} }
useEffect(() => { useEffect(() => {
@ -141,6 +141,10 @@ export function useChannels() {
const items = merged.map(setChannelEntry); const items = merged.map(setChannelEntry);
const filtered = items.filter(item => { const filtered = items.filter(item => {
if (item.blocked === true) {
return false;
}
if (!state.filter) { if (!state.filter) {
return true; return true;
} }

View File

@ -28,7 +28,16 @@ export function DetailsBody({ channel, clearConversation }) {
} }
} }
const remove = async () => { const remove = () => {
Alert.alert(
"Removing Topic",
"Confirm?",
[
{ text: "Cancel",
onPress: () => {},
},
{ text: "Remove",
onPress: async () => {
try { try {
await actions.remove(); await actions.remove();
clearConversation(); clearConversation();
@ -40,6 +49,37 @@ export function DetailsBody({ channel, clearConversation }) {
'Please try again.' '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.'
)
}
},
}
]
);
} }
return ( return (
@ -64,16 +104,19 @@ export function DetailsBody({ channel, clearConversation }) {
<Text style={styles.buttonText}>Delete Topic</Text> <Text style={styles.buttonText}>Delete Topic</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
{ !state.hostId && (
<TouchableOpacity style={styles.button} onPress={actions.showEditMembers}>
<Text style={styles.buttonText}>Edit Membership</Text>
</TouchableOpacity>
)}
{ state.hostId && ( { state.hostId && (
<TouchableOpacity style={styles.button} onPress={remove}> <TouchableOpacity style={styles.button} onPress={remove}>
<Text style={styles.buttonText}>Leave Topic</Text> <Text style={styles.buttonText}>Leave Topic</Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
<TouchableOpacity style={styles.button} onPress={block}>
<Text style={styles.buttonText}>Block Topic</Text>
</TouchableOpacity>
{ !state.hostId && (
<TouchableOpacity style={styles.button} onPress={actions.showEditMembers}>
<Text style={styles.buttonText}>Edit Membership</Text>
</TouchableOpacity>
)}
</View> </View>
<View style={styles.members}> <View style={styles.members}>

View File

@ -36,6 +36,8 @@ export const styles = StyleSheet.create({
fontSize: 18, fontSize: 18,
flexShrink: 1, flexShrink: 1,
minWidth: 0, minWidth: 0,
color: Colors.text,
paddingRight: 4,
}, },
created: { created: {
fontSize: 16, fontSize: 16,

View File

@ -58,6 +58,9 @@ export function useDetails() {
remove: async () => { remove: async () => {
await conversation.actions.remove(); await conversation.actions.remove();
}, },
block: async() => {
await conversation.actions.setBlocked();
},
}; };
return { state, actions }; return { state, actions };

View File

@ -1,6 +1,47 @@
import { Text } from 'react-native'; import { FlatList, View, Alert, TouchableOpacity, Text } from 'react-native';
import { styles } from './BlockedTopics.styled';
import { useBlockedTopics } from './useBlockedTopics.hook';
import { Logo } from 'utils/Logo';
export function BlockedTopics() { export function BlockedTopics() {
return <Text>TOPICS</Text>
const { state, actions } = useBlockedTopics();
const unblock = (cardId, channelId) => {
Alert.alert(
'Unblocking Contact',
'Confirm?',
[
{ text: "Cancel", onPress: () => {}, },
{ text: "Unblock", onPress: () => actions.unblock(cardId, channelId) },
],
);
};
const BlockedItem = ({ item }) => {
return (
<TouchableOpacity style={styles.item} onPress={() => unblock(item.cardId, item.channelId)}>
<View style={styles.detail}>
<Text style={styles.name} numberOfLines={1} ellipsizeMode={'tail'}>{ item.name }</Text>
<Text style={styles.created} numberOfLines={1} ellipsizeMode={'tail'}>{ item.created }</Text>
</View>
</TouchableOpacity>
)
}
return (
<View style={styles.container}>
{ state.channels.length === 0 && (
<Text style={styles.default}>No Blocked Topics</Text>
)}
{ state.channels.length !== 0 && (
<FlatList
data={state.channels}
renderItem={({item}) => <BlockedItem item={item} />}
keyExtractor={item => item.id}
/>
)}
</View>
);
} }

View File

@ -0,0 +1,46 @@
import { StyleSheet } from 'react-native';
import { Colors } from 'constants/Colors';
export const styles = StyleSheet.create({
container: {
backgroundColor: Colors.white,
display: 'flex',
width: '100%',
justifyContent: 'center',
fontSize: 14,
height: 200,
},
default: {
textAlign: 'center',
color: Colors.grey,
},
item: {
width: '100%',
display: 'flex',
flexDirection: 'row',
height: 32,
paddingLeft: 16,
alignItems: 'center',
borderBottomWidth: 1,
borderColor: Colors.itemDivider,
},
detail: {
paddingLeft: 12,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
width: '100%',
},
name: {
color: Colors.text,
fontSize: 14,
flexGrow: 1,
flexShrink: 1,
minWidth: 0,
},
created: {
color: Colors.text,
fontSize: 12,
paddingRight: 16,
},
});

View File

@ -0,0 +1,122 @@
import { useState, useEffect, useContext } from 'react';
import { CardContext } from 'context/CardContext';
import { ChannelContext } from 'context/ChannelContext';
import { ProfileContext } from 'context/ProfileContext';
import moment from 'moment';
export function useBlockedTopics() {
const [state, setState] = useState({
channels: []
});
const profile = useContext(ProfileContext);
const card = useContext(CardContext);
const channel = useContext(ChannelContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const getCard = (guid) => {
let contact = null
card.state.cards.forEach((card, cardId, map) => {
if (card?.profile?.guid === guid) {
contact = card;
}
});
return contact;
}
const setChannelItem = (item) => {
let timestamp;
const date = new Date(item.detail.created * 1000);
const now = new Date();
const offset = now.getTime() - date.getTime();
if(offset < 86400000) {
timestamp = moment(date).format('h:mma');
}
else if (offset < 31449600000) {
timestamp = moment(date).format('M/DD');
}
else {
timestamp = moment(date).format('M/DD/YYYY');
}
let contacts = [];
if (item.cardId) {
contacts.push(card.state.cards.get(item.cardId));
}
if (item?.detail?.members) {
const profileGuid = profile.state.profile.guid;
item.detail.members.forEach(guid => {
if (profileGuid !== guid) {
const contact = getCard(guid);
contacts.push(contact);
}
})
}
let subject;
if (item?.detail?.data) {
try {
topic = JSON.parse(item?.detail?.data).subject;
subject = topic;
}
catch (err) {
console.log(err);
}
}
if (!subject) {
if (contacts.length) {
let names = [];
for (let contact of contacts) {
if (contact?.profile?.name) {
names.push(contact.profile.name);
}
else if (contact?.profile?.handle) {
names.push(contact?.profile?.handle);
}
}
subject = names.join(', ');
}
else {
subject = "Notes";
}
}
return {
id: `${item.cardId}:${item.channelId}`,
cardId: item.cardId,
channelId: item.channelId,
name: subject,
blocked: item.blocked,
created: timestamp,
}
};
useEffect(() => {
let merged = [];
card.state.cards.forEach((card, cardId, map) => {
merged.push(...Array.from(card.channels.values()));
});
merged.push(...Array.from(channel.state.channels.values()));
const items = merged.map(setChannelItem);
const filtered = items.filter(item => item.blocked);
updateState({ channels: filtered });
}, [card, channel]);
const actions = {
unblock: async (cardId, channelId) => {
if (cardId) {
await card.actions.clearChannelBlocked(cardId, channelId);
}
else {
await channel.actions.clearBlocked(channelId);
}
}
};
return { state, actions };
}

View File

@ -69,9 +69,9 @@ export function useProfile() {
app.actions.logout(); app.actions.logout();
navigate('/'); navigate('/');
}, },
setVisible: async (visible) => { setVisible: async (searchable) => {
updateState({ visible }); updateState({ searchable });
await account.actions.setSearchable(visible); await account.actions.setSearchable(searchable);
}, },
setProfileImage: async (data) => { setProfileImage: async (data) => {
await profile.actions.setProfileImage(data); await profile.actions.setProfileImage(data);