[Gekidou] Open CRT notifications (#6369)
* Open CRT notification * Changes * fix own DM * Removed unwanted functions * WS post commit createThreadFromNewPost as soon as possible * Addressing the feedback Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
e56f172f9a
commit
efd259a523
9 changed files with 143 additions and 37 deletions
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPostById, queryPostsInChannel} from '@queries/servers/post';
|
||||
import {getPostById, queryPostsInChannel, queryPostsInThread} from '@queries/servers/post';
|
||||
|
||||
export const updatePostSinceCache = async (serverUrl: string, notification: NotificationWithData) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
|
|
@ -32,3 +32,30 @@ export const updatePostSinceCache = async (serverUrl: string, notification: Noti
|
|||
}
|
||||
};
|
||||
|
||||
export const updatePostsInThreadsSinceCache = async (serverUrl: string, notification: NotificationWithData) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
if (notification.payload?.root_id) {
|
||||
const {database} = operator;
|
||||
const chunks = await queryPostsInThread(database, notification.payload.root_id).fetch();
|
||||
if (chunks.length) {
|
||||
const recent = chunks[0];
|
||||
const lastPost = await getPostById(database, notification.payload.post_id);
|
||||
if (lastPost) {
|
||||
await operator.database.write(async () => {
|
||||
await recent.update(() => {
|
||||
recent.latest = lastPost.createAt;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,19 +3,21 @@
|
|||
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {ActionType, General, Navigation, Screens} from '@constants';
|
||||
import {ActionType, General, Navigation, Preferences, Screens} from '@constants';
|
||||
import {getDefaultThemeByAppearance} from '@context/theme';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getTranslations, t} from '@i18n';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getPostById} from '@queries/servers/post';
|
||||
import {getCurrentTeamId, getCurrentUserId, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {addChannelToTeamHistory} from '@queries/servers/team';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getCommonSystemValues, getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, PrepareCommonSystemValuesArgs, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {addChannelToTeamHistory, addTeamToTeamHistory} from '@queries/servers/team';
|
||||
import {getIsCRTEnabled, getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {dismissAllModalsAndPopToRoot, goToScreen} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
import {changeOpacity, setThemeDefaults, updateThemeIfNeeded} from '@utils/theme';
|
||||
|
||||
import type Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
|
|
@ -59,12 +61,14 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string,
|
|||
return {models};
|
||||
};
|
||||
|
||||
export const switchToThread = async (serverUrl: string, rootId: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
|
||||
try {
|
||||
const user = await getCurrentUser(database);
|
||||
if (!user) {
|
||||
|
|
@ -80,9 +84,37 @@ export const switchToThread = async (serverUrl: string, rootId: string) => {
|
|||
return {error: 'Channel not found'};
|
||||
}
|
||||
|
||||
const theme = EphemeralStore.theme;
|
||||
const system = await getCommonSystemValues(database);
|
||||
const isTabletDevice = await isTablet();
|
||||
const teamId = channel.teamId || system.currentTeamId;
|
||||
|
||||
let switchingTeams = false;
|
||||
if (system.currentTeamId !== teamId) {
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
switchingTeams = true;
|
||||
modelPromises.push(addTeamToTeamHistory(operator, teamId, true));
|
||||
const commonValues: PrepareCommonSystemValuesArgs = {
|
||||
currentTeamId: teamId,
|
||||
};
|
||||
modelPromises.push(prepareCommonSystemValues(operator, commonValues));
|
||||
const models = (await Promise.all(modelPromises)).flat();
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
}
|
||||
}
|
||||
|
||||
let theme = EphemeralStore.theme;
|
||||
if (!theme) {
|
||||
return {error: 'Theme not found'};
|
||||
theme = getDefaultThemeByAppearance();
|
||||
|
||||
// When opening the app from a push notification the theme may not be set in the EphemeralStore
|
||||
// causing the goToScreen to use the Appearance theme instead and that causes the screen background color to potentially
|
||||
// not match the theme
|
||||
const themes = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME, teamId).fetch();
|
||||
if (themes.length) {
|
||||
theme = setThemeDefaults(JSON.parse(themes[0].value) as Theme);
|
||||
}
|
||||
updateThemeIfNeeded(theme!, true);
|
||||
}
|
||||
|
||||
// Modal right buttons
|
||||
|
|
@ -120,13 +152,21 @@ export const switchToThread = async (serverUrl: string, rootId: string) => {
|
|||
}
|
||||
|
||||
EphemeralStore.setLastViewedThreadId(rootId);
|
||||
|
||||
if (isFromNotification) {
|
||||
await dismissAllModalsAndPopToRoot();
|
||||
await EphemeralStore.waitUntilScreenIsTop(Screens.HOME);
|
||||
if (switchingTeams && isTabletDevice) {
|
||||
DeviceEventEmitter.emit(Navigation.NAVIGATION_HOME, Screens.GLOBAL_THREADS);
|
||||
}
|
||||
}
|
||||
goToScreen(Screens.THREAD, '', {rootId}, {
|
||||
topBar: {
|
||||
title: {
|
||||
text: title,
|
||||
},
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
|
||||
color: changeOpacity(theme!.sidebarHeaderTextColor, 0.72),
|
||||
text: subtitle,
|
||||
},
|
||||
noBorder: true,
|
||||
|
|
@ -136,6 +176,7 @@ export const switchToThread = async (serverUrl: string, rootId: string) => {
|
|||
rightButtons,
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
|
|
|
|||
|
|
@ -490,6 +490,7 @@ export async function fetchMissingDirectChannelsInfo(serverUrl: string, directCh
|
|||
const ownDirectChannel = dms.find((dm) => dm.name === getDirectChannelName(currentUserId, currentUserId));
|
||||
if (ownDirectChannel) {
|
||||
ownDirectChannel.display_name = displayUsername(currentUser, locale, teammateDisplayNameSetting, false);
|
||||
ownDirectChannel.fake = true;
|
||||
updatedChannels.add(ownDirectChannel);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {fetchAndSwitchToThread} from '@actions/remote/thread';
|
||||
import {Screens} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getMyChannel} from '@queries/servers/channel';
|
||||
|
|
@ -25,6 +26,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
|||
|
||||
// We only reach this point if we have a channel Id in the notification payload
|
||||
const channelId = notification.payload!.channel_id!;
|
||||
const rootId = notification.payload!.root_id!;
|
||||
const {database} = operator;
|
||||
const currentTeamId = await getCurrentTeamId(database);
|
||||
const lastDisconnectedAt = await getWebSocketLastDisconnected(database);
|
||||
|
|
@ -45,14 +47,22 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
|||
// To make the switch faster we determine if we already have the team & channel
|
||||
const myChannel = await getMyChannel(database, channelId);
|
||||
const myTeam = await getMyTeamById(database, teamId);
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isThreadNotification = isCRTEnabled && Boolean(rootId);
|
||||
|
||||
await EphemeralStore.waitUntilScreenHasLoaded(Screens.HOME);
|
||||
|
||||
let switchedToChannel = isCRTEnabled;
|
||||
if (myChannel && myTeam && !switchedToChannel) {
|
||||
await switchToChannelById(serverUrl, channelId, teamId);
|
||||
switchedToChannel = true;
|
||||
let switchedToScreen = false;
|
||||
let switchedToChannel = false;
|
||||
if (myChannel && myTeam) {
|
||||
if (isThreadNotification) {
|
||||
await fetchAndSwitchToThread(serverUrl, rootId, true);
|
||||
} else {
|
||||
switchedToChannel = true;
|
||||
await switchToChannelById(serverUrl, channelId, teamId);
|
||||
}
|
||||
switchedToScreen = true;
|
||||
}
|
||||
|
||||
const entryData = await entry(serverUrl, teamId, channelId);
|
||||
|
|
@ -76,11 +86,16 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
|||
}
|
||||
}
|
||||
|
||||
if (!switchedToChannel) {
|
||||
if (!switchedToScreen) {
|
||||
if (isTabletDevice || (selectedChannelId === channelId)) {
|
||||
// Make switch again to get the missing data and make sure the team is the correct one
|
||||
switchedToChannel = true;
|
||||
switchToChannelById(serverUrl, selectedChannelId, selectedTeamId);
|
||||
switchedToScreen = true;
|
||||
if (isThreadNotification) {
|
||||
fetchAndSwitchToThread(serverUrl, rootId, true);
|
||||
} else {
|
||||
switchedToChannel = true;
|
||||
switchToChannelById(serverUrl, selectedChannelId, selectedTeamId);
|
||||
}
|
||||
} else if (selectedTeamId !== teamId || selectedChannelId !== channelId) {
|
||||
// If in the end the selected team or channel is different than the one from the notification
|
||||
// we switch again
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {updatePostSinceCache} from '@actions/local/notification';
|
||||
import {updatePostSinceCache, updatePostsInThreadsSinceCache} from '@actions/local/notification';
|
||||
import {fetchDirectChannelsInfo, fetchMyChannel, switchToChannelById} from '@actions/remote/channel';
|
||||
import {forceLogoutIfNecessary} from '@actions/remote/session';
|
||||
import {fetchMyTeam} from '@actions/remote/team';
|
||||
import {fetchAndSwitchToThread} from '@actions/remote/thread';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getMyChannel, getChannelById} from '@queries/servers/channel';
|
||||
import {getCommonSystemValues, getWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
|
|
@ -85,15 +86,16 @@ export const backgroundNotification = async (serverUrl: string, notification: No
|
|||
return;
|
||||
}
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
if (isCRTEnabled && notification.payload?.root_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastDisconnectedAt = await getWebSocketLastDisconnected(database);
|
||||
if (lastDisconnectedAt) {
|
||||
if (Platform.OS === 'ios') {
|
||||
updatePostSinceCache(serverUrl, notification);
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
const isThreadNotification = isCRTEnabled && Boolean(notification.payload?.root_id);
|
||||
if (isThreadNotification) {
|
||||
updatePostsInThreadsSinceCache(serverUrl, notification);
|
||||
} else {
|
||||
updatePostSinceCache(serverUrl, notification);
|
||||
}
|
||||
}
|
||||
|
||||
await fetchNotificationData(serverUrl, notification, true);
|
||||
|
|
@ -109,10 +111,11 @@ export const openNotification = async (serverUrl: string, notification: Notifica
|
|||
try {
|
||||
const {database} = operator;
|
||||
const channelId = notification.payload!.channel_id!;
|
||||
const rootId = notification.payload!.root_id!;
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
if (isCRTEnabled && notification.payload?.root_id) {
|
||||
return {error: 'Opening CRT notifications not implemented yet'};
|
||||
}
|
||||
const isThreadNotification = isCRTEnabled && Boolean(rootId);
|
||||
|
||||
const system = await getCommonSystemValues(database);
|
||||
const currentServerUrl = await DatabaseManager.getActiveServerUrl();
|
||||
let teamId = notification.payload?.team_id;
|
||||
|
|
@ -131,6 +134,9 @@ export const openNotification = async (serverUrl: string, notification: Notifica
|
|||
const myTeam = await getMyTeamById(database, teamId);
|
||||
|
||||
if (myChannel && myTeam) {
|
||||
if (isThreadNotification) {
|
||||
return fetchAndSwitchToThread(serverUrl, rootId, true);
|
||||
}
|
||||
return switchToChannelById(serverUrl, channelId, teamId);
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +145,9 @@ export const openNotification = async (serverUrl: string, notification: Notifica
|
|||
return {error: result.error};
|
||||
}
|
||||
|
||||
if (isThreadNotification) {
|
||||
return fetchAndSwitchToThread(serverUrl, rootId, true);
|
||||
}
|
||||
return switchToChannelById(serverUrl, channelId, teamId);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ type FetchThreadsOptions = {
|
|||
totalsOnly?: boolean;
|
||||
};
|
||||
|
||||
export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string) => {
|
||||
export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
|
|
@ -49,7 +49,7 @@ export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string)
|
|||
const post = await getPostById(database, rootId);
|
||||
if (post) {
|
||||
const thread = await getThreadById(database, rootId);
|
||||
if (thread?.unreadReplies || thread?.unreadMentions) {
|
||||
if (thread) {
|
||||
const channel = await getChannelById(database, post.channelId);
|
||||
if (channel) {
|
||||
markThreadAsRead(serverUrl, channel.teamId, thread.id);
|
||||
|
|
@ -58,7 +58,7 @@ export const fetchAndSwitchToThread = async (serverUrl: string, rootId: string)
|
|||
}
|
||||
}
|
||||
|
||||
switchToThread(serverUrl, rootId);
|
||||
switchToThread(serverUrl, rootId, isFromNotification);
|
||||
|
||||
return {};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
|||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
if (isCRTEnabled) {
|
||||
const {models: threadModels} = await createThreadFromNewPost(serverUrl, post, true);
|
||||
if (threadModels?.length) {
|
||||
models.push(...threadModels);
|
||||
}
|
||||
await createThreadFromNewPost(serverUrl, post, false);
|
||||
}
|
||||
|
||||
// Ensure the channel membership
|
||||
|
|
|
|||
|
|
@ -122,6 +122,22 @@ class EphemeralStore {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits until a passed screen is the top screen
|
||||
* Use this function only if you know what you are doing
|
||||
* this function will run until the screen is in the top
|
||||
* @param componentId string
|
||||
*/
|
||||
waitUntilScreenIsTop = async (componentId: string) => {
|
||||
let found = false;
|
||||
while (!found) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await (new Promise((r) => requestAnimationFrame(r)));
|
||||
|
||||
found = this.getNavigationTopComponentId() === componentId;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits until a screen has been removed as part of the stack.
|
||||
* Use this function only if you know what you are doing
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ extension Network {
|
|||
let userId = participant.id
|
||||
if (userId != currentUserId) {
|
||||
threadParticipantUserIds.insert(userId)
|
||||
if (threadParticipantUsers[userId] != nil) {
|
||||
if (threadParticipantUsers[userId] == nil) {
|
||||
threadParticipantUsers[userId] = participant
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue