diff --git a/app/mobile/src/constants/Colors.js b/app/mobile/src/constants/Colors.js index 4ea1cdc8..33e2bc65 100644 --- a/app/mobile/src/constants/Colors.js +++ b/app/mobile/src/constants/Colors.js @@ -21,7 +21,7 @@ export const Colors = { itemDivider: '#eeeeee', - connected: '#44cc44', + connected: '#4488FF', connecting: '#dd88ff', requested: '#4488ff', pending: '#22aaaa', diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index bfda79d2..41c1a93b 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -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 } diff --git a/app/mobile/src/context/useStoreContext.hook.js b/app/mobile/src/context/useStoreContext.hook.js index 56121205..e6e89a7b 100644 --- a/app/mobile/src/context/useStoreContext.hook.js +++ b/app/mobile/src/context/useStoreContext.hook.js @@ -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) => { diff --git a/app/mobile/src/session/cards/cardItem/CardItem.jsx b/app/mobile/src/session/cards/cardItem/CardItem.jsx index 62518e89..04efed97 100644 --- a/app/mobile/src/session/cards/cardItem/CardItem.jsx +++ b/app/mobile/src/session/cards/cardItem/CardItem.jsx @@ -21,7 +21,7 @@ export function CardItem({ item, openContact }) { { item.handle } { item.status === 'connected' && ( - + )} { item.status === 'requested' && ( diff --git a/app/mobile/src/session/cards/useCards.hook.js b/app/mobile/src/session/cards/useCards.hook.js index 167e98b8..428ab80b 100644 --- a/app/mobile/src/session/cards/useCards.hook.js +++ b/app/mobile/src/session/cards/useCards.hook.js @@ -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) { diff --git a/app/mobile/src/session/channels/useChannels.hook.js b/app/mobile/src/session/channels/useChannels.hook.js index bca44a31..95f79bc4 100644 --- a/app/mobile/src/session/channels/useChannels.hook.js +++ b/app/mobile/src/session/channels/useChannels.hook.js @@ -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())); diff --git a/app/mobile/src/session/contact/Contact.jsx b/app/mobile/src/session/contact/Contact.jsx index 23fd9b17..3231593a 100644 --- a/app/mobile/src/session/contact/Contact.jsx +++ b/app/mobile/src/session/contact/Contact.jsx @@ -232,9 +232,6 @@ export function Contact({ contact, closeContact }) { Save Contact - - Block Contact - )} diff --git a/app/mobile/src/session/contact/useContact.hook.js b/app/mobile/src/session/contact/useContact.hook.js index 12c01247..44a4342f 100644 --- a/app/mobile/src/session/contact/useContact.hook.js +++ b/app/mobile/src/session/contact/useContact.hook.js @@ -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 }; diff --git a/app/mobile/src/session/profile/Profile.jsx b/app/mobile/src/session/profile/Profile.jsx index bb52aab5..985bf340 100644 --- a/app/mobile/src/session/profile/Profile.jsx +++ b/app/mobile/src/session/profile/Profile.jsx @@ -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() { + + Manage Blocked Contacts + + + Manager Blocked Topics + Logout + + + + Blocked Contacts: + + + + + + Close + + + + + + + + + Blocked Topics: + + + + + + Close + + + + + { + Alert.alert( + 'Unblocking Contact', + 'Confirm?', + [ + { text: "Cancel", onPress: () => {}, }, + { text: "Unblock", onPress: () => actions.unblock(cardId) }, + ], + ); + }; + + const BlockedItem = ({ item }) => { + return ( + unblock(item.cardId)}> + + + { item.name } + { item.handle } + + + ) + } + + return ( + + { state.cards.length === 0 && ( + No Blocked Contacts + )} + { state.cards.length !== 0 && ( + } + keyExtractor={item => item.cardId} + /> + )} + + ); +} + diff --git a/app/mobile/src/session/profile/blockedContacts/BlockedContacts.styled.js b/app/mobile/src/session/profile/blockedContacts/BlockedContacts.styled.js new file mode 100644 index 00000000..18a5c33d --- /dev/null +++ b/app/mobile/src/session/profile/blockedContacts/BlockedContacts.styled.js @@ -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, + }, +}); diff --git a/app/mobile/src/session/profile/blockedContacts/useBlockedContacts.hook.js b/app/mobile/src/session/profile/blockedContacts/useBlockedContacts.hook.js new file mode 100644 index 00000000..6fe77f4e --- /dev/null +++ b/app/mobile/src/session/profile/blockedContacts/useBlockedContacts.hook.js @@ -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 }; +} + diff --git a/app/mobile/src/session/profile/blockedTopics/BlockedTopics.jsx b/app/mobile/src/session/profile/blockedTopics/BlockedTopics.jsx new file mode 100644 index 00000000..b28b34ab --- /dev/null +++ b/app/mobile/src/session/profile/blockedTopics/BlockedTopics.jsx @@ -0,0 +1,6 @@ +import { Text } from 'react-native'; + +export function BlockedTopics() { + return TOPICS +} + diff --git a/app/mobile/src/session/profile/useProfile.hook.js b/app/mobile/src/session/profile/useProfile.hook.js index 51842bd1..337364a0 100644 --- a/app/mobile/src/session/profile/useProfile.hook.js +++ b/app/mobile/src/session/profile/useProfile.hook.js @@ -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 }); },