[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:
Anurag Shivarathri 2022-06-11 02:57:34 +05:30 committed by GitHub
parent e56f172f9a
commit efd259a523
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 143 additions and 37 deletions

View file

@ -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};
}
};

View file

@ -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};

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);

View file

@ -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 {};
};

View file

@ -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

View file

@ -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

View file

@ -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
}
}