adding channel read indicator

This commit is contained in:
Roland Osborne 2022-09-20 13:17:20 -07:00
parent 61cb4fc903
commit 433781c5fa
8 changed files with 106 additions and 29 deletions

View File

@ -13,6 +13,7 @@ import { ChannelContext } from 'context/ChannelContext';
export function useAppContext() {
const [state, setState] = useState({
session: null,
loginTimestamp: null,
disconnected: null,
});
const store = useContext(StoreContext);
@ -47,7 +48,7 @@ export function useAppContext() {
await profile.actions.setSession(access);
await card.actions.setSession(access);
await channel.actions.setSession(access);
updateState({ session: true });
updateState({ session: true, loginTimestamp: access.created });
setWebsocket(access.server, access.appToken);
}

View File

@ -21,18 +21,21 @@ export function useChannelContext() {
}
const setChannel = (channelId, channel) => {
channels.current.set(channelId, {
channelId: channel?.id,
revision: channel?.revision,
detail: channel?.data?.channelDetail,
summary: channel?.data?.channelSummary,
detailRevision: channel?.data?.detailRevision,
topicRevision: channel?.data?.topicRevision,
});
let update = channels.current.get(channelId);
if (!update) {
update = { readRevision: 0 };
}
update.channelId = channel?.id;
update.revision = channel?.revision;
update.detail = channel?.data?.channelDetail;
update.summary = channel?.data?.channelSummary;
update.detailRevision = channel?.data?.detailRevision;
update.topicRevision = channel?.data?.topicRevision;
channels.current.set(channelId, channel);
}
const setChannelDetails = (channelId, detail, revision) => {
let channel = channels.current.get(channelId);
if (channel?.data) {
if (channel) {
channel.detail = detail;
channel.detailRevision = revision;
channels.current.set(channelId, channel);
@ -53,6 +56,13 @@ export function useChannelContext() {
channels.current.set(channelId, channel);
}
}
const setChannelReadRevision = (channelId, revision) => {
let channel = channels.current.get(channelId);
if (channel) {
channel.readRevision = revision;
channels.current.set(channelId, channel);
}
}
const sync = async () => {
@ -141,6 +151,11 @@ export function useChannelContext() {
curRevision.current = rev;
sync();
},
setReadRevision: async (channelId, rev) => {
await store.actions.setChannelItemReadRevision(session.current.guid, channelId, rev);
setChannelReadRevision(channelId, rev);
updateState({ channels: channels.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_v025.db';
const DATABAG_DB = 'databag_v026.db';
export function useStoreContext() {
const [state, setState] = useState({});
@ -12,10 +12,10 @@ 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, 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, 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, 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, detail text, summary text, offsync 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))`);
}
@ -174,6 +174,9 @@ export function useStoreContext() {
setChannelItemRevision: async (guid, channelId, revision) => {
await db.current.executeSql(`UPDATE channel_${guid} set revision=? where channel_id=?`, [revision, channelId]);
},
setChannelItemReadRevision: async (guid, channelId, revision) => {
await db.current.executeSql(`UPDATE channel_${guid} set read_revision=? where channel_id=?`, [revision, channelId]);
},
setChannelItemDetail: async (guid, channelId, revision, detail) => {
await db.current.executeSql(`UPDATE channel_${guid} set detail_revision=?, detail=? where channel_id=?`, [revision, encodeObject(detail), channelId]);
},
@ -192,10 +195,11 @@ export function useStoreContext() {
};
},
getChannelItems: async (guid) => {
const values = await getAppValues(db.current, `SELECT channel_id, revision, detail_revision, topic_revision, detail, summary FROM channel_${guid}`, []);
const values = await getAppValues(db.current, `SELECT channel_id, read_revision, revision, detail_revision, topic_revision, detail, summary FROM channel_${guid}`, []);
return values.map(channel => ({
channelId: channel.channel_id,
revision: channel.revision,
readRevision: channel.read_revision,
detailRevision: channel.detail_revision,
topicRevision: channel.topic_revision,
detail: decodeObject(channel.detail),
@ -213,6 +217,9 @@ export function useStoreContext() {
setCardChannelItemRevision: async (guid, cardId, channelId, revision) => {
await db.current.executeSql(`UPDATE card_channel_${guid} set revision=? where card_id=? and channel_id=?`, [revision, cardId, channelId]);
},
setCardChannelItemReadRevision: async (guid, cardId, channelId, revision) => {
await db.current.executeSql(`UPDATE card_channel_${guid} set read_revision=? where card_id=? and channel_id=?`, [revision, cardId, channelId]);
},
setCardChannelItemDetail: async (guid, cardId, channelId, revision, detail) => {
await db.current.executeSql(`UPDATE card_channel_${guid} set detail_revision=?, detail=? where card_id=? and channel_id=?`, [revision, encodeObject(detail), cardId, channelId]);
},
@ -231,11 +238,12 @@ export function useStoreContext() {
};
},
getCardChannelItems: async (guid) => {
const values = await getAppValues(db.current, `SELECT card_id, channel_id, 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, detail_revision, topic_revision, detail, summary FROM card_channel_${guid}`, []);
return values.map(channel => ({
cardId: channel.card_id,
channelId: channel.channel_id,
revision: channel.revision,
readRevision: channel.read_revision,
detailRevision: channel.detail_revision,
topicRevision: channel.topic_revision,
detail: decodeObject(channel.detail),

View File

@ -3,7 +3,7 @@ import { FlatList, ScrollView, View, TextInput, TouchableOpacity, Text } from 'r
import { styles } from './Channels.styled';
import { useChannels } from './useChannels.hook';
import Ionicons from '@expo/vector-icons/AntDesign';
import { MemoizedChannelItem } from './channelItem/ChannelItem';
import { ChannelItem } from './channelItem/ChannelItem';
export function Channels() {
const { state, actions } = useChannels();
@ -17,7 +17,7 @@ export function Channels() {
</View>
<FlatList style={styles.channels}
data={state.channels}
renderItem={({ item }) => <MemoizedChannelItem item={item} />}
renderItem={({ item }) => <ChannelItem item={item} />}
keyExtractor={item => (`${item.cardId}:${item.channelId}`)}
/>
</View>

View File

@ -1,18 +1,23 @@
import { Text, TouchableOpacity, View } from 'react-native';
import React from 'react';
import { Logo } from 'utils/Logo';
import { styles } from './ChannelItem.styled';
import { useChannelItem } from './useChannelItem.hook';
export function ChannelItem({ item }) {
const { state, actions } = useChannelItem(item);
return (
<TouchableOpacity style={styles.container} activeOpacity={1}>
<TouchableOpacity style={styles.container} activeOpacity={1} onPress={actions.setRead}>
<Logo src={item.logo} width={40} height={40} radius={6} />
<View style={styles.detail}>
<Text style={styles.subject} numberOfLines={1} ellipsizeMode={'tail'}>{ item.subject }</Text>
<Text style={styles.message} numberOfLines={1} ellipsizeMode={'tail'}>{ item.message }</Text>
</View>
{ item.updated && (
<View style={styles.dot} />
)}
</TouchableOpacity>
)
}
export const MemoizedChannelItem = React.memo(ChannelItem);

View File

@ -20,9 +20,16 @@ export const styles = StyleSheet.create({
flexShrink: 1,
},
subject: {
color: Colors.text,
},
message: {
color: Colors.disabled,
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.background,
}
})

View File

@ -0,0 +1,24 @@
import { useState, useEffect, useRef, useContext } from 'react';
import { useWindowDimensions } from 'react-native';
import { CardContext } from 'context/CardContext';
import { ChannelContext } from 'context/ChannelContext';
export function useChannelItem(item) {
const [state, setState] = useState({});
const channel = useContext(ChannelContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const actions = {
setRead: () => {
channel.actions.setReadRevision(item.channelId, item.revision);
},
};
return { state, actions };
}

View File

@ -3,6 +3,7 @@ import { useWindowDimensions } from 'react-native';
import { useNavigate } from 'react-router-dom';
import { CardContext } from 'context/CardContext';
import { ChannelContext } from 'context/ChannelContext';
import { AppContext } from 'context/AppContext';
import config from 'constants/Config';
export function useChannels() {
@ -15,6 +16,7 @@ export function useChannels() {
const items = useRef([]);
const channel = useContext(ChannelContext);
const card = useContext(CardContext);
const app = useContext(AppContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
@ -31,6 +33,16 @@ export function useChannels() {
}
const setChannelEntry = (item) => {
let updated = false;
const login = app.state.loginTimestamp;
const update = item?.summary?.lastTopic?.created;
if (update && login && login < update) {
if (!item.readRevision || item.readRevision < item.revision) {
updated = true;
}
}
let contacts = [];
if (item?.detail?.members) {
item.detail.members.forEach(guid => {
@ -91,18 +103,23 @@ export function useChannels() {
}
}
return {
channelId: item.channelId,
contacts: contacts,
logo: logo,
subject: subject,
message: message,
}
return { channelId: item.channelId, contacts, logo, subject, message, updated, revision: item.revision };
}
useEffect(() => {
const channels = Array.from(channel.state.channels.values()).map(item => setChannelEntry(item));
updateState({ channels: channels });
let channels = Array.from(channel.state.channels.values())
channels.sort((a, b) => {
const aCreated = a?.summary?.lastTopic?.created;
const bCreated = b?.summary?.lastTopic?.created;
if (aCreated === bCreated) {
return 0;
}
if (!aCreated || aCreated < bCreated) {
return 1;
}
return -1;
});
updateState({ channels: channels.map(item => setChannelEntry(item)) });
}, [channel, card]);
const actions = {