supporting blocking contacts

This commit is contained in:
Roland Osborne 2022-09-26 14:39:01 -07:00
parent d3782750ea
commit 87da47a00d
15 changed files with 286 additions and 15 deletions

View File

@ -21,7 +21,7 @@ export const Colors = {
itemDivider: '#eeeeee',
connected: '#44cc44',
connected: '#4488FF',
connecting: '#dd88ff',
requested: '#4488ff',
pending: '#22aaaa',

View File

@ -84,6 +84,13 @@ export function useCardContext() {
cards.current.set(cardId, card);
}
}
const setCardBlocked = (cardId, blocked) => {
let card = cards.current.get(cardId);
if (card) {
card.blocked = blocked;
cards.current.set(cardId, card);
}
}
const clearCardChannels = (cardId) => {
let card = cards.current.get(cardId);
if (card) {
@ -385,6 +392,18 @@ export function useCardContext() {
setCardCloseMessage: async (server, message) => {
return await setCardCloseMessage(server, message);
},
setCardBlocked: async (cardId) => {
const { guid } = session.current;
setCardBlocked(cardId, true);
await store.actions.setCardItemBlocked(guid, cardId);
updateState({ cards: cards.current });
},
clearCardBlocked: async (cardId) => {
const { guid } = session.current;
setCardBlocked(cardId, false);
await store.actions.clearCardItemBlocked(guid, cardId);
updateState({ cards: cards.current });
}
}
return { state, actions }

View File

@ -1,7 +1,7 @@
import { useEffect, useState, useRef, useContext } from 'react';
import SQLite from "react-native-sqlite-storage";
const DATABAG_DB = 'databag_v033.db';
const DATABAG_DB = 'databag_v034.db';
export function useStoreContext() {
const [state, setState] = useState({});
@ -14,8 +14,8 @@ export function useStoreContext() {
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, 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 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, 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, detail text, summary text, offsync integer, read_revision integer, unique(card_id, channel_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, 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_topic_${guid} (card_id text, channel_id text, topic_id text, revision integer, detail_revision integer, detail text, unique(card_id, channel_id, topic_id))`);
}
@ -106,6 +106,12 @@ export function useStoreContext() {
clearCardItemOffsync: async (guid, cardId) => {
await db.current.executeSql(`UPDATE card_${guid} set offsync=? where card_id=?`, [0, cardId]);
},
setCardItemBlocked: async (guid, cardId) => {
await db.current.executeSql(`UPDATE card_${guid} set blocked=? where card_id=?`, [1, cardId]);
},
clearCardItemBlocked: async (guid, cardId) => {
await db.current.executeSql(`UPDATE card_${guid} set blocked=? where card_id=?`, [0, cardId]);
},
setCardItemDetail: async (guid, cardId, revision, detail) => {
await db.current.executeSql(`UPDATE card_${guid} set detail_revision=?, detail=? where card_id=?`, [revision, encodeObject(detail), cardId]);
},
@ -127,6 +133,7 @@ export function useStoreContext() {
notifiedProfile: values[0].notified_profile,
notifiedChannel: values[0].notified_channel,
offsync: values[0].offsync,
blocked: values[0].blocked,
};
},
getCardItemView: async (guid, cardId) => {
@ -141,7 +148,7 @@ export function useStoreContext() {
};
},
getCardItems: async (guid) => {
const values = await getAppValues(db.current, `SELECT card_id, revision, detail_revision, profile_revision, detail, profile, notified_view, notified_profile, notified_article, notified_channel FROM card_${guid}`, []);
const values = await getAppValues(db.current, `SELECT card_id, revision, detail_revision, profile_revision, detail, profile, offsync, blocked, notified_view, notified_profile, notified_article, notified_channel FROM card_${guid}`, []);
return values.map(card => ({
cardId: card.card_id,
revision: card.revision,
@ -153,6 +160,8 @@ export function useStoreContext() {
notifiedProfile: card.notified_profile,
notifiedArticle: card.notified_article,
notifiedChannel: card.notified_channel,
offsync: card.offsync,
blocked: card.blocked,
}));
},
@ -238,7 +247,7 @@ export function useStoreContext() {
};
},
getCardChannelItems: async (guid) => {
const values = await getAppValues(db.current, `SELECT card_id, channel_id, read_revision, revision, detail_revision, topic_revision, detail, summary FROM card_channel_${guid}`, []);
const values = await getAppValues(db.current, `SELECT card_id, channel_id, read_revision, revision, blocked, detail_revision, topic_revision, detail, summary FROM card_channel_${guid}`, []);
return values.map(channel => ({
cardId: channel.card_id,
channelId: channel.channel_id,
@ -248,6 +257,7 @@ export function useStoreContext() {
topicRevision: channel.topic_revision,
detail: decodeObject(channel.detail),
summary: decodeObject(channel.summary),
blocked: channel.blocked,
}));
},
clearCardChannelItems: async (guid, cardId) => {

View File

@ -21,7 +21,7 @@ export function CardItem({ item, openContact }) {
<Text style={styles.handle} numberOfLines={1} ellipsizeMode={'tail'}>{ item.handle }</Text>
</View>
{ item.status === 'connected' && (
<View style={styles.confirmed} />
<View style={styles.connected} />
)}
{ item.status === 'requested' && (
<View style={styles.requested} />

View File

@ -37,6 +37,8 @@ export function useCards() {
name: profile.name,
handle: `${profile.handle}@${profile.node}`,
status: detail.status,
offsync: item.offsync,
blocked: item.blocked,
updated: detail.statusUpdated,
logo: profile.imageSet ? card.actions.getCardLogo(item.cardId, profile.revision) : 'avatar',
}
@ -47,7 +49,7 @@ export function useCards() {
const items = cards.map(setCardItem);
const filtered = items.filter(item => {
if (!state.filter) {
return true;
return !item.blocked;
}
const lower = state.filter.toLowerCase();
if (item.name) {

View File

@ -130,7 +130,9 @@ export function useChannels() {
useEffect(() => {
let merged = [];
card.state.cards.forEach((card, cardId, map) => {
merged.push(...Array.from(card.channels.values()));
if (!card.blocked) {
merged.push(...Array.from(card.channels.values()));
}
});
merged.push(...Array.from(channel.state.channels.values()));

View File

@ -232,9 +232,6 @@ export function Contact({ contact, closeContact }) {
<TouchableOpacity style={styles.button} onPress={saveContact}>
<Text style={styles.buttonText}>Save Contact</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={blockContact}>
<Text style={styles.buttonText}>Block Contact</Text>
</TouchableOpacity>
</>
)}
</View>

View File

@ -154,7 +154,12 @@ export function useContact(contact, close) {
}
});
},
blockContact: async () => {},
blockContact: async () => {
await applyAction(async () => {
await card.actions.setCardBlocked(state.cardId);
close();
});
},
};
return { state, actions };

View File

@ -7,6 +7,8 @@ import Ionicons from '@expo/vector-icons/AntDesign';
import Colors from 'constants/Colors';
import ImagePicker from 'react-native-image-crop-picker'
import { SafeAreaView } from 'react-native-safe-area-context';
import { BlockedTopics } from './blockedTopics/BlockedTopics';
import { BlockedContacts } from './blockedContacts/BlockedContacts';
export function Profile() {
@ -127,11 +129,59 @@ export function Profile() {
</TouchableOpacity>
<Switch style={styles.visibleSwitch} value={state.searchable} onValueChange={setVisible} trackColor={styles.switch}/>
</View>
<TouchableOpacity style={styles.link} onPress={actions.showBlockedCards}>
<Text style={styles.linkText}>Manage Blocked Contacts</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.link} onPress={actions.showBlockedChannels}>
<Text style={styles.linkText}>Manager Blocked Topics</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.logout} onPress={logout}>
<Ionicons name="logout" size={14} color={Colors.white} />
<Text style={styles.logoutText}>Logout</Text>
</TouchableOpacity>
</SafeAreaView>
<Modal
animationType="fade"
transparent={true}
visible={state.blockedCards}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideBlockedCards}
>
<View style={styles.editWrapper}>
<View style={styles.editContainer}>
<Text style={styles.editHeader}>Blocked Contacts:</Text>
<View style={styles.editList}>
<BlockedContacts />
</View>
<View style={styles.editControls}>
<TouchableOpacity style={styles.close} onPress={actions.hideBlockedCards}>
<Text>Close</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
<Modal
animationType="fade"
transparent={true}
visible={state.blockedChannels}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={actions.hideBlockedChannels}
>
<View style={styles.editWrapper}>
<View style={styles.editContainer}>
<Text style={styles.editHeader}>Blocked Topics:</Text>
<View style={styles.editList}>
<BlockedTopics />
</View>
<View style={styles.editControls}>
<TouchableOpacity style={styles.close} onPress={actions.hideBlockedChannels}>
<Text>Close</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
<Modal
animationType="fade"
transparent={true}

View File

@ -117,9 +117,15 @@ export const styles = StyleSheet.create({
maxWidth: 400,
},
editHeader: {
fontSize: 20,
fontSize: 18,
paddingBottom: 16,
},
editList: {
width: '100%',
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 2,
},
inputField: {
width: '100%',
borderWidth: 1,
@ -132,7 +138,7 @@ export const styles = StyleSheet.create({
flexDirection: 'row',
},
input: {
fontSize: 16,
fontSize: 14,
flexGrow: 1,
},
editControls: {
@ -140,6 +146,16 @@ export const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'flex-end',
},
close: {
borderWidth: 1,
borderColor: Colors.lightgrey,
borderRadius: 4,
padding: 8,
marginTop: 8,
width: 72,
display: 'flex',
alignItems: 'center',
},
cancel: {
borderWidth: 1,
borderColor: Colors.lightgrey,
@ -170,6 +186,12 @@ export const styles = StyleSheet.create({
display: 'flex',
alignItems: 'center',
},
link: {
marginTop: 16,
},
linkText: {
color: Colors.primary,
},
saveText: {
color: Colors.white,
}

View File

@ -0,0 +1,48 @@
import { FlatList, View, Alert, TouchableOpacity, Text } from 'react-native';
import { styles } from './BlockedContacts.styled';
import { useBlockedContacts } from './useBlockedContacts.hook';
import { Logo } from 'utils/Logo';
export function BlockedContacts() {
const { state, actions } = useBlockedContacts();
const unblock = (cardId) => {
Alert.alert(
'Unblocking Contact',
'Confirm?',
[
{ text: "Cancel", onPress: () => {}, },
{ text: "Unblock", onPress: () => actions.unblock(cardId) },
],
);
};
const BlockedItem = ({ item }) => {
return (
<TouchableOpacity style={styles.item} onPress={() => unblock(item.cardId)}>
<Logo src={item.logo} width={32} height={32} radius={6} />
<View style={styles.detail}>
<Text style={styles.name} numberOfLines={1} ellipsizeMode={'tail'}>{ item.name }</Text>
<Text style={styles.handle} numberOfLines={1} ellipsizeMode={'tail'}>{ item.handle }</Text>
</View>
</TouchableOpacity>
)
}
return (
<View style={styles.container}>
{ state.cards.length === 0 && (
<Text style={styles.default}>No Blocked Contacts</Text>
)}
{ state.cards.length !== 0 && (
<FlatList
data={state.cards}
renderItem={({item}) => <BlockedItem item={item} />}
keyExtractor={item => item.cardId}
/>
)}
</View>
);
}

View File

@ -0,0 +1,43 @@
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: 48,
paddingLeft: 16,
alignItems: 'center',
borderBottomWidth: 1,
borderColor: Colors.itemDivider,
},
detail: {
paddingLeft: 12,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
flexGrow: 1,
flexShrink: 1,
},
name: {
color: Colors.text,
fontSize: 14,
},
handle: {
color: Colors.text,
fontSize: 12,
},
});

View File

@ -0,0 +1,53 @@
import { useState, useEffect, useContext } from 'react';
import { CardContext } from 'context/CardContext';
export function useBlockedContacts() {
const [state, setState] = useState({
cards: [],
});
const card = useContext(CardContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const setCardItem = (item) => {
const { profile } = item;
return {
cardId: item.cardId,
name: profile.name,
handle: `${profile.handle}@${profile.node}`,
blocked: item.blocked,
logo: profile.imageSet ? card.actions.getCardLogo(item.cardId, item.revision) : 'avatar',
}
};
useEffect(() => {
const cards = Array.from(card.state.cards.values());
const items = cards.map(setCardItem);
const filtered = items.filter(item => {
return item.blocked;
});
filtered.sort((a, b) => {
if (a.name === b.name) {
return 0;
}
if (!a.name || (a.name < b.name)) {
return -1;
}
return 1;
});
updateState({ cards: filtered });
}, [card]);
const actions = {
unblock: async (cardId) => {
await card.actions.clearCardBlocked(cardId);
}
};
return { state, actions };
}

View File

@ -0,0 +1,6 @@
import { Text } from 'react-native';
export function BlockedTopics() {
return <Text>TOPICS</Text>
}

View File

@ -26,6 +26,8 @@ export function useProfile() {
available: true,
showPassword: false,
showConfirm: false,
blockedChannels: false,
blockedCards: false,
});
const app = useContext(AppContext);
@ -60,6 +62,18 @@ export function useProfile() {
setProfileImage: async (data) => {
await profile.actions.setProfileImage(data);
},
showBlockedChannels: () => {
updateState({ blockedChannels: true });
},
hideBlockedChannels: () => {
updateState({ blockedChannels: false });
},
showBlockedCards: () => {
updateState({ blockedCards: true });
},
hideBlockedCards: () => {
updateState({ blockedCards: false });
},
showLoginEdit: () => {
updateState({ showLoginEdit: true });
},