forked from Ivasoft/mattermost-mobile
[Gekidou MM-40089 MM-39318] CRT New Messages Line (#6489)
* New Messages Line + More Messages * Misc * Update app/actions/local/thread.ts Co-authored-by: Avinash Lingaloo <avinashlng1080@gmail.com> Co-authored-by: Avinash Lingaloo <avinashlng1080@gmail.com>
This commit is contained in:
committed by
GitHub
parent
d8c77855b1
commit
102789bbd9
@@ -127,7 +127,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
|||||||
subtitle = subtitle.replace('{channelName}', channel.displayName);
|
subtitle = subtitle.replace('{channelName}', channel.displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
EphemeralStore.setLastViewedThreadId(rootId);
|
EphemeralStore.setCurrentThreadId(rootId);
|
||||||
|
|
||||||
if (isFromNotification) {
|
if (isFromNotification) {
|
||||||
await dismissAllModalsAndPopToRoot();
|
await dismissAllModalsAndPopToRoot();
|
||||||
@@ -267,6 +267,7 @@ export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, p
|
|||||||
record.unreadMentions = 0;
|
record.unreadMentions = 0;
|
||||||
record.unreadReplies = 0;
|
record.unreadReplies = 0;
|
||||||
record.lastViewedAt = Date.now();
|
record.lastViewedAt = Date.now();
|
||||||
|
record.viewedAt = Date.now();
|
||||||
}));
|
}));
|
||||||
if (!prepareRecordsOnly) {
|
if (!prepareRecordsOnly) {
|
||||||
await operator.batchRecords(models);
|
await operator.batchRecords(models);
|
||||||
@@ -278,7 +279,31 @@ export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateThread(serverUrl: string, threadId: string, updatedThread: Partial<Thread>, prepareRecordsOnly = false) {
|
export async function markThreadAsViewed(serverUrl: string, threadId: string, prepareRecordsOnly = false) {
|
||||||
|
try {
|
||||||
|
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||||
|
const thread = await getThreadById(database, threadId);
|
||||||
|
if (!thread) {
|
||||||
|
return {error: 'Thread not found'};
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.prepareUpdate((th) => {
|
||||||
|
th.viewedAt = thread.lastViewedAt;
|
||||||
|
th.lastViewedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!prepareRecordsOnly) {
|
||||||
|
await operator.batchRecords([thread]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {model: thread};
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed markThreadAsViewed', error);
|
||||||
|
return {error};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateThread(serverUrl: string, threadId: string, updatedThread: Partial<ThreadWithViewedAt>, prepareRecordsOnly = false) {
|
||||||
try {
|
try {
|
||||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||||
const thread = await getThreadById(database, threadId);
|
const thread = await getThreadById(database, threadId);
|
||||||
@@ -289,8 +314,8 @@ export async function updateThread(serverUrl: string, threadId: string, updatedT
|
|||||||
const model = thread.prepareUpdate((record) => {
|
const model = thread.prepareUpdate((record) => {
|
||||||
record.isFollowing = updatedThread.is_following ?? record.isFollowing;
|
record.isFollowing = updatedThread.is_following ?? record.isFollowing;
|
||||||
record.replyCount = updatedThread.reply_count ?? record.replyCount;
|
record.replyCount = updatedThread.reply_count ?? record.replyCount;
|
||||||
|
|
||||||
record.lastViewedAt = updatedThread.last_viewed_at ?? record.lastViewedAt;
|
record.lastViewedAt = updatedThread.last_viewed_at ?? record.lastViewedAt;
|
||||||
|
record.viewedAt = updatedThread.viewed_at ?? record.viewedAt;
|
||||||
record.unreadMentions = updatedThread.unread_mentions ?? record.unreadMentions;
|
record.unreadMentions = updatedThread.unread_mentions ?? record.unreadMentions;
|
||||||
record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies;
|
record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {markTeamThreadsAsRead, processReceivedThreads, switchToThread, updateThread} from '@actions/local/thread';
|
import {markTeamThreadsAsRead, markThreadAsViewed, processReceivedThreads, switchToThread, updateThread} from '@actions/local/thread';
|
||||||
import {fetchPostThread} from '@actions/remote/post';
|
import {fetchPostThread} from '@actions/remote/post';
|
||||||
import {General} from '@constants';
|
import {General} from '@constants';
|
||||||
import DatabaseManager from '@database/manager';
|
import DatabaseManager from '@database/manager';
|
||||||
import PushNotifications from '@init/push_notifications';
|
import PushNotifications from '@init/push_notifications';
|
||||||
import NetworkManager from '@managers/network_manager';
|
import NetworkManager from '@managers/network_manager';
|
||||||
import {getChannelById} from '@queries/servers/channel';
|
|
||||||
import {getPostById} from '@queries/servers/post';
|
import {getPostById} from '@queries/servers/post';
|
||||||
import {getCommonSystemValues, getCurrentTeamId} from '@queries/servers/system';
|
import {getCommonSystemValues, getCurrentTeamId} from '@queries/servers/system';
|
||||||
import {getIsCRTEnabled, getNewestThreadInTeam, getThreadById} from '@queries/servers/thread';
|
import {getIsCRTEnabled, getNewestThreadInTeam, getThreadById} from '@queries/servers/thread';
|
||||||
@@ -51,10 +50,7 @@ export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string,
|
|||||||
if (post) {
|
if (post) {
|
||||||
const thread = await getThreadById(database, rootId);
|
const thread = await getThreadById(database, rootId);
|
||||||
if (thread?.isFollowing) {
|
if (thread?.isFollowing) {
|
||||||
const channel = await getChannelById(database, post.channelId);
|
markThreadAsViewed(serverUrl, thread.id);
|
||||||
if (channel) {
|
|
||||||
markThreadAsRead(serverUrl, channel.teamId, thread.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,10 +221,11 @@ export const markThreadAsUnread = async (serverUrl: string, teamId: string, thre
|
|||||||
const data = await client.markThreadAsUnread('me', threadTeamId, threadId, postId);
|
const data = await client.markThreadAsUnread('me', threadTeamId, threadId, postId);
|
||||||
|
|
||||||
// Update locally
|
// Update locally
|
||||||
const post = await getPostById(database, threadId);
|
const post = await getPostById(database, postId);
|
||||||
if (post) {
|
if (post) {
|
||||||
await updateThread(serverUrl, threadId, {
|
await updateThread(serverUrl, threadId, {
|
||||||
last_viewed_at: post.createAt - 1,
|
last_viewed_at: post.createAt - 1,
|
||||||
|
viewed_at: post.createAt - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {markTeamThreadsAsRead, processReceivedThreads, updateThread} from '@actions/local/thread';
|
import {markTeamThreadsAsRead, processReceivedThreads, updateThread} from '@actions/local/thread';
|
||||||
|
import EphemeralStore from '@store/ephemeral_store';
|
||||||
|
|
||||||
export async function handleThreadUpdatedEvent(serverUrl: string, msg: WebSocketMessage): Promise<void> {
|
export async function handleThreadUpdatedEvent(serverUrl: string, msg: WebSocketMessage): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -20,11 +21,19 @@ export async function handleThreadReadChangedEvent(serverUrl: string, msg: WebSo
|
|||||||
try {
|
try {
|
||||||
const {thread_id, timestamp, unread_mentions, unread_replies} = msg.data;
|
const {thread_id, timestamp, unread_mentions, unread_replies} = msg.data;
|
||||||
if (thread_id) {
|
if (thread_id) {
|
||||||
await updateThread(serverUrl, thread_id, {
|
const data: Partial<ThreadWithViewedAt> = {
|
||||||
last_viewed_at: timestamp,
|
|
||||||
unread_mentions,
|
unread_mentions,
|
||||||
unread_replies,
|
unread_replies,
|
||||||
});
|
last_viewed_at: timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Do not update viewing data if the user is currently in the same thread
|
||||||
|
const isThreadVisible = EphemeralStore.getCurrentThreadId() === thread_id;
|
||||||
|
if (!isThreadVisible) {
|
||||||
|
data.viewed_at = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateThread(serverUrl, thread_id, data);
|
||||||
} else {
|
} else {
|
||||||
await markTeamThreadsAsRead(serverUrl, msg.broadcast.team_id);
|
await markTeamThreadsAsRead(serverUrl, msg.broadcast.team_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,33 @@
|
|||||||
|
|
||||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||||
import withObservables from '@nozbe/with-observables';
|
import withObservables from '@nozbe/with-observables';
|
||||||
import {of as of$} from 'rxjs';
|
import {of as of$, first as first$} from 'rxjs';
|
||||||
import {switchMap} from 'rxjs/operators';
|
import {switchMap} from 'rxjs/operators';
|
||||||
|
|
||||||
import {observeMyChannel} from '@queries/servers/channel';
|
import {observeMyChannel} from '@queries/servers/channel';
|
||||||
|
import {observeThreadById} from '@queries/servers/thread';
|
||||||
|
|
||||||
import MoreMessages from './more_messages';
|
import MoreMessages from './more_messages';
|
||||||
|
|
||||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||||
|
|
||||||
const enhanced = withObservables(['channelId'], ({channelId, database}: {channelId: string} & WithDatabaseArgs) => {
|
type Props = {
|
||||||
|
channelId: string;
|
||||||
|
isCRTEnabled?: boolean;
|
||||||
|
rootId?: string;
|
||||||
|
} & WithDatabaseArgs;
|
||||||
|
|
||||||
|
const enhanced = withObservables(['channelId', 'isCRTEnabled', 'rootId'], ({channelId, isCRTEnabled, rootId, database}: Props) => {
|
||||||
|
if (isCRTEnabled && rootId) {
|
||||||
|
const thread = observeThreadById(database, rootId);
|
||||||
|
|
||||||
|
// Just take the first value emited as we set unreadReplies to 0 on viewing the thread.
|
||||||
|
const unreadCount = thread.pipe(first$(), switchMap((th) => of$(th?.unreadReplies)));
|
||||||
|
return {
|
||||||
|
unreadCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const myChannel = observeMyChannel(database, channelId);
|
const myChannel = observeMyChannel(database, channelId);
|
||||||
const isManualUnread = myChannel.pipe(switchMap((ch) => of$(ch?.manuallyUnread)));
|
const isManualUnread = myChannel.pipe(switchMap((ch) => of$(ch?.manuallyUnread)));
|
||||||
const unreadCount = myChannel.pipe(switchMap((ch) => of$(ch?.messageCount)));
|
const unreadCount = myChannel.pipe(switchMap((ch) => of$(ch?.messageCount)));
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ import type PostModel from '@typings/database/models/servers/post';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
isManualUnread: boolean;
|
isCRTEnabled?: boolean;
|
||||||
|
isManualUnread?: boolean;
|
||||||
newMessageLineIndex: number;
|
newMessageLineIndex: number;
|
||||||
posts: Array<string | PostModel>;
|
posts: Array<string | PostModel>;
|
||||||
registerScrollEndIndexListener: (fn: (endIndex: number) => void) => () => void;
|
registerScrollEndIndexListener: (fn: (endIndex: number) => void) => () => void;
|
||||||
registerViewableItemsListener: (fn: (viewableItems: ViewToken[]) => void) => () => void;
|
registerViewableItemsListener: (fn: (viewableItems: ViewToken[]) => void) => () => void;
|
||||||
|
rootId?: string;
|
||||||
scrollToIndex: (index: number, animated?: boolean, applyOffset?: boolean) => void;
|
scrollToIndex: (index: number, animated?: boolean, applyOffset?: boolean) => void;
|
||||||
unreadCount: number;
|
unreadCount: number;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
@@ -91,11 +93,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||||||
|
|
||||||
const MoreMessages = ({
|
const MoreMessages = ({
|
||||||
channelId,
|
channelId,
|
||||||
|
isCRTEnabled,
|
||||||
isManualUnread,
|
isManualUnread,
|
||||||
newMessageLineIndex,
|
newMessageLineIndex,
|
||||||
posts,
|
posts,
|
||||||
registerViewableItemsListener,
|
registerViewableItemsListener,
|
||||||
registerScrollEndIndexListener,
|
registerScrollEndIndexListener,
|
||||||
|
rootId,
|
||||||
scrollToIndex,
|
scrollToIndex,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
testID,
|
testID,
|
||||||
@@ -110,7 +114,7 @@ const MoreMessages = ({
|
|||||||
const [remaining, setRemaining] = useState(0);
|
const [remaining, setRemaining] = useState(0);
|
||||||
const underlayColor = useMemo(() => `hsl(${hexToHue(theme.buttonBg)}, 50%, 38%)`, [theme]);
|
const underlayColor = useMemo(() => `hsl(${hexToHue(theme.buttonBg)}, 50%, 38%)`, [theme]);
|
||||||
const top = useSharedValue(0);
|
const top = useSharedValue(0);
|
||||||
const shownTop = isTablet ? 5 : SHOWN_TOP;
|
const shownTop = isTablet || isCRTEnabled ? 5 : SHOWN_TOP;
|
||||||
const BARS_FACTOR = Math.abs((1) / (HIDDEN_TOP - SHOWN_TOP));
|
const BARS_FACTOR = Math.abs((1) / (HIDDEN_TOP - SHOWN_TOP));
|
||||||
const styles = getStyleSheet(theme);
|
const styles = getStyleSheet(theme);
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
@@ -134,8 +138,18 @@ const MoreMessages = ({
|
|||||||
}],
|
}],
|
||||||
}), [isTablet, shownTop]);
|
}), [isTablet, shownTop]);
|
||||||
|
|
||||||
|
// Due to the implementation differences "unreadCount" gets updated for a channel on reset but not for a thread.
|
||||||
|
// So we maintain a localUnreadCount to hide the indicator when the count is reset.
|
||||||
|
// If we don't maintain the local counter, in the case of a thread, the indicator will be shown again once we scroll down after we reach the top.
|
||||||
|
const localUnreadCount = useRef(unreadCount);
|
||||||
|
useEffect(() => {
|
||||||
|
localUnreadCount.current = unreadCount;
|
||||||
|
}, [unreadCount]);
|
||||||
|
|
||||||
const resetCount = async () => {
|
const resetCount = async () => {
|
||||||
if (resetting.current) {
|
localUnreadCount.current = 0;
|
||||||
|
|
||||||
|
if (resetting.current || (isCRTEnabled && rootId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +179,7 @@ const MoreMessages = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const readCount = posts.slice(0, lastViewableIndex).filter((v) => typeof v !== 'string').length;
|
const readCount = posts.slice(0, lastViewableIndex).filter((v) => typeof v !== 'string').length;
|
||||||
const totalUnread = unreadCount - readCount;
|
const totalUnread = localUnreadCount.current - readCount;
|
||||||
if (lastViewableIndex >= newMessageLineIndex) {
|
if (lastViewableIndex >= newMessageLineIndex) {
|
||||||
resetCount();
|
resetCount();
|
||||||
top.value = 0;
|
top.value = 0;
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ const Post = ({
|
|||||||
|
|
||||||
let unreadDot;
|
let unreadDot;
|
||||||
let footer;
|
let footer;
|
||||||
if (isCRTEnabled && thread) {
|
if (isCRTEnabled && thread && location !== Screens.THREAD) {
|
||||||
if (thread.replyCount > 0 || thread.isFollowing) {
|
if (thread.replyCount > 0 || thread.isFollowing) {
|
||||||
footer = (
|
footer = (
|
||||||
<Footer
|
<Footer
|
||||||
|
|||||||
@@ -379,10 +379,12 @@ const PostList = ({
|
|||||||
{showMoreMessages &&
|
{showMoreMessages &&
|
||||||
<MoreMessages
|
<MoreMessages
|
||||||
channelId={channelId}
|
channelId={channelId}
|
||||||
|
isCRTEnabled={isCRTEnabled}
|
||||||
newMessageLineIndex={initialIndex}
|
newMessageLineIndex={initialIndex}
|
||||||
posts={orderedPosts}
|
posts={orderedPosts}
|
||||||
registerScrollEndIndexListener={registerScrollEndIndexListener}
|
registerScrollEndIndexListener={registerScrollEndIndexListener}
|
||||||
registerViewableItemsListener={registerViewableItemsListener}
|
registerViewableItemsListener={registerViewableItemsListener}
|
||||||
|
rootId={rootId}
|
||||||
scrollToIndex={scrollToIndex}
|
scrollToIndex={scrollToIndex}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
testID={`${testID}.more_messages_button`}
|
testID={`${testID}.more_messages_button`}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class PushNotifications {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isDifferentChannel = payload?.channel_id !== channelId;
|
const isDifferentChannel = payload?.channel_id !== channelId;
|
||||||
const isVisibleThread = payload?.root_id === EphemeralStore.getLastViewedThreadId() && NavigationStore.getNavigationTopComponentId() === Screens.THREAD;
|
const isVisibleThread = payload?.root_id === EphemeralStore.getCurrentThreadId();
|
||||||
let isChannelScreenVisible = NavigationStore.getNavigationTopComponentId() === Screens.CHANNEL;
|
let isChannelScreenVisible = NavigationStore.getNavigationTopComponentId() === Screens.CHANNEL;
|
||||||
if (isTabletDevice) {
|
if (isTabletDevice) {
|
||||||
isChannelScreenVisible = NavigationStore.getVisibleTab() === Screens.HOME;
|
isChannelScreenVisible = NavigationStore.getVisibleTab() === Screens.HOME;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {useRef} from 'react';
|
import React, {useEffect, useRef} from 'react';
|
||||||
import {StyleSheet, View} from 'react-native';
|
import {StyleSheet, View} from 'react-native';
|
||||||
import {KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
import {KeyboardTrackingViewRef} from 'react-native-keyboard-tracking-view';
|
||||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||||
@@ -13,6 +13,7 @@ import {THREAD_ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
|
|||||||
import {useAppState} from '@hooks/device';
|
import {useAppState} from '@hooks/device';
|
||||||
import useDidUpdate from '@hooks/did_update';
|
import useDidUpdate from '@hooks/did_update';
|
||||||
import {popTopScreen} from '@screens/navigation';
|
import {popTopScreen} from '@screens/navigation';
|
||||||
|
import EphemeralStore from '@store/ephemeral_store';
|
||||||
|
|
||||||
import ThreadPostList from './thread_post_list';
|
import ThreadPostList from './thread_post_list';
|
||||||
|
|
||||||
@@ -33,6 +34,12 @@ const Thread = ({componentId, rootPost}: ThreadProps) => {
|
|||||||
const appState = useAppState();
|
const appState = useAppState();
|
||||||
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
EphemeralStore.setCurrentThreadId('');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useDidUpdate(() => {
|
useDidUpdate(() => {
|
||||||
if (!rootPost) {
|
if (!rootPost) {
|
||||||
popTopScreen(componentId);
|
popTopScreen(componentId);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type Props = WithDatabaseArgs & {
|
|||||||
const enhanced = withObservables(['forceQueryAfterAppState', 'rootPost'], ({database, rootPost}: Props) => {
|
const enhanced = withObservables(['forceQueryAfterAppState', 'rootPost'], ({database, rootPost}: Props) => {
|
||||||
return {
|
return {
|
||||||
isCRTEnabled: observeIsCRTEnabled(database),
|
isCRTEnabled: observeIsCRTEnabled(database),
|
||||||
lastViewedAt: observeMyChannel(database, rootPost.channelId).pipe(
|
channelLastViewedAt: observeMyChannel(database, rootPost.channelId).pipe(
|
||||||
switchMap((myChannel) => of$(myChannel?.viewedAt)),
|
switchMap((myChannel) => of$(myChannel?.viewedAt)),
|
||||||
),
|
),
|
||||||
posts: queryPostsInThread(database, rootPost.id, true, true).observeWithColumns(['earliest', 'latest']).pipe(
|
posts: queryPostsInThread(database, rootPost.id, true, true).observeWithColumns(['earliest', 'latest']).pipe(
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import type PostModel from '@typings/database/models/servers/post';
|
|||||||
import type ThreadModel from '@typings/database/models/servers/thread';
|
import type ThreadModel from '@typings/database/models/servers/thread';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
channelLastViewedAt: number;
|
||||||
isCRTEnabled: boolean;
|
isCRTEnabled: boolean;
|
||||||
lastViewedAt: number;
|
|
||||||
nativeID: string;
|
nativeID: string;
|
||||||
posts: PostModel[];
|
posts: PostModel[];
|
||||||
rootPost: PostModel;
|
rootPost: PostModel;
|
||||||
@@ -33,7 +33,7 @@ const styles = StyleSheet.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const ThreadPostList = ({
|
const ThreadPostList = ({
|
||||||
isCRTEnabled, lastViewedAt,
|
channelLastViewedAt, isCRTEnabled,
|
||||||
nativeID, posts, rootPost, teamId, thread,
|
nativeID, posts, rootPost, teamId, thread,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const isTablet = useIsTablet();
|
const isTablet = useIsTablet();
|
||||||
@@ -43,7 +43,14 @@ const ThreadPostList = ({
|
|||||||
return [...posts, rootPost];
|
return [...posts, rootPost];
|
||||||
}, [posts, rootPost]);
|
}, [posts, rootPost]);
|
||||||
|
|
||||||
// If CRT is enabled, When new post arrives and thread modal is open, mark thread as read
|
// If CRT is enabled, mark the thread as read on mount.
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCRTEnabled) {
|
||||||
|
markThreadAsRead(serverUrl, teamId, rootPost.id);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// If CRT is enabled, When new post arrives and thread modal is open, mark thread as read.
|
||||||
const oldPostsCount = useRef<number>(posts.length);
|
const oldPostsCount = useRef<number>(posts.length);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCRTEnabled && thread?.isFollowing && oldPostsCount.current < posts.length) {
|
if (isCRTEnabled && thread?.isFollowing && oldPostsCount.current < posts.length) {
|
||||||
@@ -52,18 +59,20 @@ const ThreadPostList = ({
|
|||||||
}
|
}
|
||||||
}, [isCRTEnabled, posts, rootPost, serverUrl, teamId, thread]);
|
}, [isCRTEnabled, posts, rootPost, serverUrl, teamId, thread]);
|
||||||
|
|
||||||
|
const lastViewedAt = isCRTEnabled ? (thread?.viewedAt ?? 0) : channelLastViewedAt;
|
||||||
|
|
||||||
const postList = (
|
const postList = (
|
||||||
<PostList
|
<PostList
|
||||||
channelId={rootPost.channelId}
|
channelId={rootPost.channelId}
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
|
isCRTEnabled={isCRTEnabled}
|
||||||
lastViewedAt={lastViewedAt}
|
lastViewedAt={lastViewedAt}
|
||||||
location={Screens.THREAD}
|
location={Screens.THREAD}
|
||||||
nativeID={nativeID}
|
nativeID={nativeID}
|
||||||
posts={threadPosts}
|
posts={threadPosts}
|
||||||
rootId={rootPost.id}
|
rootId={rootPost.id}
|
||||||
shouldShowJoinLeaveMessages={false}
|
shouldShowJoinLeaveMessages={false}
|
||||||
showMoreMessages={false}
|
showMoreMessages={isCRTEnabled}
|
||||||
showNewMessageLine={false}
|
|
||||||
footer={<View style={styles.footer}/>}
|
footer={<View style={styles.footer}/>}
|
||||||
testID='thread.post_list'
|
testID='thread.post_list'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class EphemeralStore {
|
|||||||
private archivingChannels = new Set<string>();
|
private archivingChannels = new Set<string>();
|
||||||
private convertingChannels = new Set<string>();
|
private convertingChannels = new Set<string>();
|
||||||
private switchingToChannel = new Set<string>();
|
private switchingToChannel = new Set<string>();
|
||||||
private lastViewedThreadId = '';
|
private currentThreadId = '';
|
||||||
|
|
||||||
// Ephemeral control when (un)archiving a channel locally
|
// Ephemeral control when (un)archiving a channel locally
|
||||||
addArchivingChannel = (channelId: string) => {
|
addArchivingChannel = (channelId: string) => {
|
||||||
@@ -95,12 +95,12 @@ class EphemeralStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Ephemeral for the last viewed thread
|
// Ephemeral for the last viewed thread
|
||||||
getLastViewedThreadId = () => {
|
getCurrentThreadId = () => {
|
||||||
return this.lastViewedThreadId;
|
return this.currentThreadId;
|
||||||
};
|
};
|
||||||
|
|
||||||
setLastViewedThreadId = (id: string) => {
|
setCurrentThreadId = (id: string) => {
|
||||||
this.lastViewedThreadId = id;
|
this.currentThreadId = id;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ephemeral control when (un)archiving a channel locally
|
// Ephemeral control when (un)archiving a channel locally
|
||||||
|
|||||||
4
types/api/threads.d.ts
vendored
4
types/api/threads.d.ts
vendored
@@ -18,6 +18,10 @@ type ThreadWithLastFetchedAt = Thread & {
|
|||||||
lastFetchedAt: number;
|
lastFetchedAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ThreadWithViewedAt = Thread & {
|
||||||
|
viewed_at: number;
|
||||||
|
};
|
||||||
|
|
||||||
type ThreadParticipant = {
|
type ThreadParticipant = {
|
||||||
id: $ID<User>;
|
id: $ID<User>;
|
||||||
thread_id: $ID<Thread>;
|
thread_id: $ID<Thread>;
|
||||||
|
|||||||
Reference in New Issue
Block a user