mattermost-mobile/app/actions/websocket/users.ts
Mattermost Build e8f1d0c729
Add autotranslations (#9324) (#9500)
* Add autotranslations

* Develop latest changes and missing features

* i18n-extract

* Fix tests and update api behavior

* Fix minor bugs

* adjustments to shimmer animation, showtranslation modal style

* Update post.tsx

* Update show_translation.tsx

* adjust sizing and opacity of translate icon

* Add channel header translated icon

* Disable auto translation item if it is not a supported language

* Move channel info to the top

* Update edit channel to channel info

* Fix align of option items

* Fix i18n

* Add detox related changes for channel settings changes

* Add tests

* Address changes around the my channel column change

* Address feedback

* Fix test

* Fix bad import

* Fix set my channel autotranslation

* Fix test

* Address feedback

* Add missing files from last commit

* Remove unneeded change

---------


(cherry picked from commit 23565b5135)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
Co-authored-by: Matthew Birtch <mattbirtch@gmail.com>
2026-02-11 08:09:39 +02:00

207 lines
8 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {DeviceEventEmitter} from 'react-native';
import {deletePostsForChannelsWithAutotranslation, updateChannelsDisplayName} from '@actions/local/channel';
import {setCurrentUserStatus} from '@actions/local/user';
import {fetchMe, fetchUsersByIds} from '@actions/remote/user';
import {General, Events, Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import WebsocketManager from '@managers/websocket_manager';
import {queryChannelsByTypes, queryUserChannelsByTypes} from '@queries/servers/channel';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {getConfig, getLicense} from '@queries/servers/system';
import {getCurrentUser} from '@queries/servers/user';
import {customProfileAttributeId} from '@utils/custom_profile_attribute';
import {logError} from '@utils/log';
import {displayUsername} from '@utils/user';
import type {Model} from '@nozbe/watermelondb';
import type {CustomProfileField} from '@typings/api/custom_profile_attributes';
export async function handleUserUpdatedEvent(serverUrl: string, msg: WebSocketMessage) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const {database} = operator;
const currentUser = await getCurrentUser(database);
if (!currentUser) {
return;
}
const user: UserProfile = msg.data.user;
const modelsToBatch: Model[] = [];
let userToSave = user;
if (user.id === currentUser.id) {
if (user.update_at > (currentUser?.updateAt || 0)) {
// ESR: 6.5
if (!user.notify_props || !Object.keys(user.notify_props).length) {
// Current user is sanitized so we fetch it from the server
// Need to request me to make sure we don't override with sanitized fields from the
// websocket event
const me = await fetchMe(serverUrl, true);
if (me.user) {
userToSave = me.user;
}
}
// Update GMs display name if locale has changed
if (user.locale !== currentUser.locale) {
const channels = await queryChannelsByTypes(database, [General.GM_CHANNEL]).fetch();
if (channels.length) {
const {models} = await updateChannelsDisplayName(serverUrl, channels, [user], true);
if (models?.length) {
modelsToBatch.push(...models);
}
}
// Delete posts for all channels with user autotranslation enabled when locale changes
await deletePostsForChannelsWithAutotranslation(serverUrl);
}
}
} else {
const channels = await queryUserChannelsByTypes(database, user.id, [General.DM_CHANNEL, General.GM_CHANNEL]).fetch();
if (channels.length) {
const {models} = await updateChannelsDisplayName(serverUrl, channels, [user], true);
if (models?.length) {
modelsToBatch.push(...models);
}
}
}
const userModel = await operator.handleUsers({users: [userToSave], prepareRecordsOnly: true});
modelsToBatch.push(...userModel);
try {
await operator.batchRecords(modelsToBatch, 'handleUserUpdatedEvent');
} catch {
// do nothing
}
}
export async function handleUserTypingEvent(serverUrl: string, msg: WebSocketMessage) {
const currentServerUrl = await DatabaseManager.getActiveServerUrl();
if (currentServerUrl === serverUrl) {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return;
}
const license = await getLicense(database);
const config = await getConfig(database);
const {users, existingUsers} = await fetchUsersByIds(serverUrl, [msg.data.user_id]);
const user = users?.[0] || existingUsers?.[0];
const namePreference = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT)?.fetch();
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(namePreference, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
const currentUser = await getCurrentUser(database);
const username = displayUsername(user, currentUser?.locale, teammateDisplayNameSetting);
const data = {
channelId: msg.broadcast.channel_id,
rootId: msg.data.parent_id,
userId: msg.data.user_id,
username,
now: Date.now(),
};
DeviceEventEmitter.emit(Events.USER_TYPING, data);
setTimeout(() => {
DeviceEventEmitter.emit(Events.USER_STOP_TYPING, data);
}, parseInt(config.TimeBetweenUserTypingUpdatesMilliseconds, 10));
}
}
export const userTyping = async (serverUrl: string, channelId: string, rootId?: string) => {
const client = WebsocketManager.getClient(serverUrl);
client?.sendUserTypingEvent(channelId, rootId);
};
export async function handleStatusChangedEvent(serverUrl: string, msg: WebSocketMessage) {
const newStatus = msg.data.status;
setCurrentUserStatus(serverUrl, newStatus);
}
export async function handleCustomProfileAttributesValuesUpdatedEvent(serverUrl: string, msg: WebSocketMessage) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const {user_id, values} = msg.data;
const attributesForDatabase = Object.entries(values).map(([fieldId, value]) => ({
id: customProfileAttributeId(fieldId, user_id),
field_id: fieldId,
user_id,
value: value as string,
}));
try {
await operator.handleCustomProfileAttributes({
attributes: attributesForDatabase,
prepareRecordsOnly: false,
});
} catch (error) {
logError('Error handling custom profile attributes values updated event', error);
}
} catch (error) {
logError('Error getting the operator for the custom profile attributes values updated event', error);
}
}
export async function handleCustomProfileAttributesFieldUpdatedEvent(serverUrl: string, msg: WebSocketMessage) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const {field} = msg.data;
try {
await operator.handleCustomProfileFields({
fields: [field],
prepareRecordsOnly: false,
});
} catch (error) {
logError('Error handling custom profile attributes field updated event', error);
}
} catch (error) {
logError('Error getting the operator for the custom profile field updated event', error);
}
}
export async function handleCustomProfileAttributesFieldDeletedEvent(serverUrl: string, msg: WebSocketMessage) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const {field_id} = msg.data;
try {
// Delete the field from the database
const fieldForDatabase: CustomProfileField = {
id: field_id,
group_id: '',
name: '',
type: '',
target_id: '',
target_type: '',
create_at: 0,
update_at: 0,
delete_at: Date.now(),
attrs: {},
};
await operator.handleCustomProfileFields({
fields: [fieldForDatabase],
prepareRecordsOnly: false,
});
} catch (error) {
logError('Error handling custom profile field deleted event', error);
}
} catch (error) {
logError('Error getting the operator for the custom profile field deleted event', error);
}
}