Remove tand prevent double tap (#9078)
* Remove the t function and all wrong uses of preventDoubleTap * Fix existing typo * Fix tests * Address comments * Fix test
This commit is contained in:
parent
1965b4777d
commit
0c4a42a06a
142 changed files with 1716 additions and 1106 deletions
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {ActionType, General, Navigation, Screens} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getTranslations, t} from '@i18n';
|
||||
import {getTranslations} from '@i18n';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getPostById} from '@queries/servers/post';
|
||||
import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, type PrepareCommonSystemValuesArgs, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
|
|
@ -57,6 +58,17 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string,
|
|||
}
|
||||
};
|
||||
|
||||
const threadMessages = defineMessages({
|
||||
thread: {
|
||||
id: 'thread.header.thread',
|
||||
defaultMessage: 'Thread',
|
||||
},
|
||||
threadIn: {
|
||||
id: 'thread.header.thread_in',
|
||||
defaultMessage: 'in {channelName}',
|
||||
},
|
||||
});
|
||||
|
||||
export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => {
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
|
|
@ -112,14 +124,14 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
|||
const translations = getTranslations(user.locale);
|
||||
|
||||
// Get title translation or default title message
|
||||
const title = translations[t('thread.header.thread')] || 'Thread';
|
||||
const title = translations[threadMessages.thread.id] || 'Thread';
|
||||
|
||||
let subtitle = '';
|
||||
if (channel?.type === General.DM_CHANNEL) {
|
||||
subtitle = channel.displayName;
|
||||
} else {
|
||||
// Get translation or default message
|
||||
subtitle = translations[t('thread.header.thread_in')] || 'in {channelName}';
|
||||
subtitle = translations[threadMessages.threadIn.id] || 'in {channelName}';
|
||||
subtitle = subtitle.replace('{channelName}', channel.displayName);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export async function doAppSubmit<Res=unknown>(serverUrl: string, inCall: AppCal
|
|||
if (!res.form?.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.form.no_form',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in the response.',
|
||||
});
|
||||
return {error: makeCallErrorResponse<Res>(errMsg)};
|
||||
}
|
||||
|
|
@ -133,7 +133,7 @@ export async function doAppFetchForm<Res=unknown>(serverUrl: string, call: AppCa
|
|||
if (!res.form?.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.form.no_form',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in the response.',
|
||||
});
|
||||
return {error: makeCallErrorResponse<Res>(errMsg)};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ describe('executeAppCommand', () => {
|
|||
|
||||
expect(parser.composeCommandSubmitCall).toHaveBeenCalledWith(msg);
|
||||
expect(doAppSubmit).toHaveBeenCalledWith(serverUrl, {}, intl);
|
||||
expect(result).toEqual({error: {message: 'Unknown error.'}});
|
||||
expect(result).toEqual({error: {message: 'Unknown error occurred.'}});
|
||||
});
|
||||
|
||||
it('should handle a form response', async () => {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export const executeAppCommand = async (serverUrl: string, intl: IntlShape, pars
|
|||
const errorResponse = res.error;
|
||||
return createErrorMessage(errorResponse.text || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
}));
|
||||
}
|
||||
const callResp = res.data;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessage} from 'react-intl';
|
||||
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import {PUSH_PROXY_RESPONSE_VERIFIED, PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {t} from '@i18n';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getDeviceToken} from '@queries/app/global';
|
||||
import {getExpandedLinks, getPushVerificationStatus} from '@queries/servers/system';
|
||||
|
|
@ -41,15 +42,15 @@ export const doPing = async (serverUrl: string, verifyPushProxy: boolean, timeou
|
|||
return {error};
|
||||
}
|
||||
|
||||
const certificateError = {
|
||||
id: t('mobile.server_requires_client_certificate'),
|
||||
const certificateError = defineMessage({
|
||||
id: 'mobile.server_requires_client_certificate',
|
||||
defaultMessage: 'Server requires client certificate for authentication.',
|
||||
};
|
||||
});
|
||||
|
||||
const pingError = {
|
||||
id: t('mobile.server_ping_failed'),
|
||||
const pingError = defineMessage({
|
||||
id: 'mobile.server_ping_failed',
|
||||
defaultMessage: 'Cannot connect to the server.',
|
||||
};
|
||||
});
|
||||
|
||||
const deviceId = await getDeviceIdForPing(serverUrl, verifyPushProxy);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessage} from 'react-intl';
|
||||
import {DeviceEventEmitter, Platform} from 'react-native';
|
||||
|
||||
import {CollectNetworkMetrics} from '@assets/config.json';
|
||||
import {Events} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
import {setServerCredentials} from '@init/credentials';
|
||||
import PerformanceMetricsManager from '@managers/performance_metrics_manager';
|
||||
import {NetworkRequestMetrics} from '@managers/performance_metrics_manager/constant';
|
||||
|
|
@ -363,10 +363,10 @@ export default class ClientTracking {
|
|||
default:
|
||||
return {error: new ClientError(this.apiClient.baseUrl, {
|
||||
message: 'Invalid request method',
|
||||
intl: {
|
||||
id: t('mobile.request.invalid_request_method'),
|
||||
intl: defineMessage({
|
||||
id: 'mobile.request.invalid_request_method',
|
||||
defaultMessage: 'Invalid request method',
|
||||
},
|
||||
}),
|
||||
url,
|
||||
})};
|
||||
}
|
||||
|
|
@ -382,10 +382,10 @@ export default class ClientTracking {
|
|||
const status_code = isErrorWithStatusCode(error) ? error.status_code : undefined;
|
||||
throw new ClientError(this.apiClient.baseUrl, {
|
||||
message: 'Received invalid response from the server.',
|
||||
intl: {
|
||||
id: t('mobile.request.invalid_response'),
|
||||
intl: defineMessage({
|
||||
id: 'mobile.request.invalid_response',
|
||||
defaultMessage: 'Received invalid response from the server.',
|
||||
},
|
||||
}),
|
||||
url,
|
||||
details: error,
|
||||
status_code,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
import {nativeApplicationVersion, nativeBuildVersion} from 'expo-application';
|
||||
import React, {useEffect} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, type TextStyle, View} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {t} from '@i18n';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
info: {
|
||||
|
|
@ -26,6 +26,13 @@ type AppVersionProps = {
|
|||
textStyle?: TextStyle;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
appVersion: {
|
||||
id: 'mobile.about.appVersion',
|
||||
defaultMessage: 'App Version: {version} (Build {number})',
|
||||
},
|
||||
});
|
||||
|
||||
const AppVersion = ({isWrapped = true, textStyle = {}}: AppVersionProps) => {
|
||||
const opacity = useSharedValue(1);
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
|
|
@ -50,8 +57,7 @@ const AppVersion = ({isWrapped = true, textStyle = {}}: AppVersionProps) => {
|
|||
|
||||
const appVersion = (
|
||||
<FormattedText
|
||||
id={t('mobile.about.appVersion')}
|
||||
defaultMessage='App Version: {version} (Build {number})'
|
||||
{...messages.appVersion}
|
||||
style={StyleSheet.flatten([style.version, textStyle])}
|
||||
values={{
|
||||
version: nativeApplicationVersion,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import {searchGroupsByName, searchGroupsByNameInChannel, searchGroupsByNameInTeam} from '@actions/local/group';
|
||||
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from '@constants/autocomplete';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {t} from '@i18n';
|
||||
import {getUsersFromDMSorted, queryAllUsers} from '@queries/servers/user';
|
||||
import {hasTrailingSpaces} from '@utils/helpers';
|
||||
|
||||
|
|
@ -39,19 +40,31 @@ export const getMatchTermForAtMention = (() => {
|
|||
};
|
||||
})();
|
||||
|
||||
const specialMentionsMessages = defineMessages({
|
||||
all: {
|
||||
id: 'suggestion.mention.all',
|
||||
defaultMessage: 'Notifies everyone in this channel',
|
||||
},
|
||||
channel: {
|
||||
id: 'suggestion.mention.channel',
|
||||
defaultMessage: 'Notifies everyone in this channel',
|
||||
},
|
||||
here: {
|
||||
id: 'suggestion.mention.here',
|
||||
defaultMessage: 'Notifies everyone online in this channel',
|
||||
},
|
||||
});
|
||||
|
||||
const getSpecialMentions: () => SpecialMention[] = () => {
|
||||
return [{
|
||||
completeHandle: 'all',
|
||||
id: t('suggestion.mention.all'),
|
||||
defaultMessage: 'Notifies everyone in this channel',
|
||||
...specialMentionsMessages.all,
|
||||
}, {
|
||||
completeHandle: 'channel',
|
||||
id: t('suggestion.mention.channel'),
|
||||
defaultMessage: 'Notifies everyone in this channel',
|
||||
...specialMentionsMessages.channel,
|
||||
}, {
|
||||
completeHandle: 'here',
|
||||
id: t('suggestion.mention.here'),
|
||||
defaultMessage: 'Notifies everyone online in this channel',
|
||||
...specialMentionsMessages.here,
|
||||
}];
|
||||
};
|
||||
|
||||
|
|
@ -115,14 +128,36 @@ export const filterResults = (users: Array<UserModel | UserProfile>, term: strin
|
|||
});
|
||||
};
|
||||
|
||||
const sectionMessages = defineMessages({
|
||||
members: {
|
||||
id: 'mobile.suggestion.members',
|
||||
defaultMessage: 'Members',
|
||||
},
|
||||
groups: {
|
||||
id: 'suggestion.mention.groups',
|
||||
defaultMessage: 'Group Mentions',
|
||||
},
|
||||
special: {
|
||||
id: 'suggestion.mention.special',
|
||||
defaultMessage: 'Special Mentions',
|
||||
},
|
||||
users: {
|
||||
id: 'suggestion.mention.users',
|
||||
defaultMessage: 'Users',
|
||||
},
|
||||
nonmembers: {
|
||||
id: 'suggestion.mention.nonmembers',
|
||||
defaultMessage: 'Not in Channel',
|
||||
},
|
||||
});
|
||||
|
||||
export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersInChannel: Array<UserProfile | UserModel>, usersOutOfChannel: Array<UserProfile | UserModel>, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => {
|
||||
const newSections: UserMentionSections = [];
|
||||
|
||||
if (isSearch) {
|
||||
if (teamMembers.length) {
|
||||
newSections.push({
|
||||
id: t('mobile.suggestion.members'),
|
||||
defaultMessage: 'Members',
|
||||
...sectionMessages.members,
|
||||
data: teamMembers,
|
||||
key: SECTION_KEY_TEAM_MEMBERS,
|
||||
});
|
||||
|
|
@ -130,8 +165,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
} else if (isLocal) {
|
||||
if (teamMembers.length) {
|
||||
newSections.push({
|
||||
id: t('mobile.suggestion.members'),
|
||||
defaultMessage: 'Members',
|
||||
...sectionMessages.members,
|
||||
data: teamMembers,
|
||||
key: SECTION_KEY_TEAM_MEMBERS,
|
||||
});
|
||||
|
|
@ -139,8 +173,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
|
||||
if (groups.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.groups'),
|
||||
defaultMessage: 'Group Mentions',
|
||||
...sectionMessages.groups,
|
||||
data: groups,
|
||||
key: SECTION_KEY_GROUPS,
|
||||
});
|
||||
|
|
@ -148,8 +181,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
|
||||
if (showSpecialMentions) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.special'),
|
||||
defaultMessage: 'Special Mentions',
|
||||
...sectionMessages.special,
|
||||
data: getSpecialMentions(),
|
||||
key: SECTION_KEY_SPECIAL,
|
||||
});
|
||||
|
|
@ -157,8 +189,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
} else {
|
||||
if (usersInChannel.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.users'),
|
||||
defaultMessage: 'Users',
|
||||
...sectionMessages.users,
|
||||
data: usersInChannel,
|
||||
key: SECTION_KEY_IN_CHANNEL,
|
||||
});
|
||||
|
|
@ -166,8 +197,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
|
||||
if (groups.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.groups'),
|
||||
defaultMessage: 'Group Mentions',
|
||||
...sectionMessages.groups,
|
||||
data: groups,
|
||||
key: SECTION_KEY_GROUPS,
|
||||
});
|
||||
|
|
@ -175,8 +205,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
|
||||
if (showSpecialMentions) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.special'),
|
||||
defaultMessage: 'Special Mentions',
|
||||
...sectionMessages.special,
|
||||
data: getSpecialMentions(),
|
||||
key: SECTION_KEY_SPECIAL,
|
||||
});
|
||||
|
|
@ -184,8 +213,7 @@ export const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersI
|
|||
|
||||
if (usersOutOfChannel.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.nonmembers'),
|
||||
defaultMessage: 'Not in Channel',
|
||||
...sectionMessages.nonmembers,
|
||||
data: usersOutOfChannel,
|
||||
key: SECTION_KEY_OUT_OF_CHANNEL,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import {debounce} from 'lodash';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {Platform, SectionList, type SectionListData, type SectionListRenderItemInfo, type StyleProp, type ViewStyle} from 'react-native';
|
||||
|
||||
import {searchChannels} from '@actions/remote/channel';
|
||||
|
|
@ -13,7 +14,6 @@ import {CHANNEL_MENTION_REGEX, CHANNEL_MENTION_SEARCH_REGEX} from '@constants/au
|
|||
import {useServerUrl} from '@context/server';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import {t} from '@i18n';
|
||||
import {getUserById} from '@queries/servers/user';
|
||||
import {hasTrailingSpaces} from '@utils/helpers';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
|
|
@ -57,14 +57,36 @@ const reduceChannelsForAutocomplete = (channels: Array<Channel | ChannelModel>,
|
|||
}, [[], []]);
|
||||
};
|
||||
|
||||
const channelMentionMessages = defineMessages({
|
||||
public: {
|
||||
id: 'suggestion.search.public',
|
||||
defaultMessage: 'Public Channels',
|
||||
},
|
||||
private: {
|
||||
id: 'suggestion.search.private',
|
||||
defaultMessage: 'Private Channels',
|
||||
},
|
||||
direct: {
|
||||
id: 'suggestion.search.direct',
|
||||
defaultMessage: 'Direct Messages',
|
||||
},
|
||||
channels: {
|
||||
id: 'suggestion.mention.channels',
|
||||
defaultMessage: 'My Channels',
|
||||
},
|
||||
morechannels: {
|
||||
id: 'suggestion.mention.morechannels',
|
||||
defaultMessage: 'Other Channels',
|
||||
},
|
||||
});
|
||||
|
||||
const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChannelModel[], loading: boolean, isSearch = false) => {
|
||||
const newSections = [];
|
||||
if (isSearch) {
|
||||
const [publicChannels, privateChannels, directAndGroupMessages] = reduceChannelsForSearch(channels, myMembers);
|
||||
if (publicChannels.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.search.public'),
|
||||
defaultMessage: 'Public Channels',
|
||||
...channelMentionMessages.public,
|
||||
data: publicChannels,
|
||||
key: 'publicChannels',
|
||||
hideLoadingIndicator: true,
|
||||
|
|
@ -73,8 +95,7 @@ const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChan
|
|||
|
||||
if (privateChannels.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.search.private'),
|
||||
defaultMessage: 'Private Channels',
|
||||
...channelMentionMessages.private,
|
||||
data: privateChannels,
|
||||
key: 'privateChannels',
|
||||
hideLoadingIndicator: true,
|
||||
|
|
@ -83,8 +104,7 @@ const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChan
|
|||
|
||||
if (directAndGroupMessages.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.search.direct'),
|
||||
defaultMessage: 'Direct Messages',
|
||||
...channelMentionMessages.direct,
|
||||
data: directAndGroupMessages,
|
||||
key: 'directAndGroupMessages',
|
||||
hideLoadingIndicator: true,
|
||||
|
|
@ -94,8 +114,7 @@ const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChan
|
|||
const [myChannels, otherChannels] = reduceChannelsForAutocomplete(channels, myMembers);
|
||||
if (myChannels.length) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.channels'),
|
||||
defaultMessage: 'My Channels',
|
||||
...channelMentionMessages.channels,
|
||||
data: myChannels,
|
||||
key: 'myChannels',
|
||||
hideLoadingIndicator: true,
|
||||
|
|
@ -104,8 +123,7 @@ const makeSections = (channels: Array<Channel | ChannelModel>, myMembers: MyChan
|
|||
|
||||
if (otherChannels.length || (!myChannels.length && loading)) {
|
||||
newSections.push({
|
||||
id: t('suggestion.mention.morechannels'),
|
||||
defaultMessage: 'Other Channels',
|
||||
...channelMentionMessages.morechannels,
|
||||
data: otherChannels,
|
||||
key: 'otherChannels',
|
||||
hideLoadingIndicator: true,
|
||||
|
|
@ -251,7 +269,7 @@ const ChannelMention = ({
|
|||
setSections(emptySections);
|
||||
setRemoteChannels(emptyChannels);
|
||||
latestSearchAt.current = Date.now();
|
||||
}, [value, localCursorPosition, isSearch, currentUserId]);
|
||||
}, [currentUserId, value, localCursorPosition, isSearch, updateValue, onShowingChange, serverUrl]);
|
||||
|
||||
const renderItem = useCallback(({item}: SectionListRenderItemInfo<Channel | ChannelModel>) => {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -739,7 +739,7 @@ export class ParsedCommand {
|
|||
if (this.incompleteStart === this.i - 1) {
|
||||
return this.asError(this.intl.formatMessage({
|
||||
id: 'apps.error.parser.empty_value',
|
||||
defaultMessage: 'empty values are not allowed',
|
||||
defaultMessage: 'Empty values are not allowed.',
|
||||
}));
|
||||
}
|
||||
this.i++;
|
||||
|
|
@ -779,7 +779,7 @@ export class ParsedCommand {
|
|||
if (this.incompleteStart === this.i - 1) {
|
||||
return this.asError(this.intl.formatMessage({
|
||||
id: 'apps.error.parser.empty_value',
|
||||
defaultMessage: 'empty values are not allowed',
|
||||
defaultMessage: 'Empty values are not allowed.',
|
||||
}));
|
||||
}
|
||||
this.i++;
|
||||
|
|
@ -1569,7 +1569,7 @@ export class AppCommandParser {
|
|||
const errorResponse = res.error;
|
||||
return {error: errorResponse.text || this.intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
})};
|
||||
}
|
||||
|
||||
|
|
@ -1884,7 +1884,7 @@ export class AppCommandParser {
|
|||
const errorResponse = res.error;
|
||||
return this.makeDynamicSelectSuggestionError(errorResponse.text || this.intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import {Screens, View as ViewConstants} from '@constants';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getUserById, observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {secureGetFromRecord} from '@utils/types';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
|
@ -157,11 +157,6 @@ function AutoCompleteSelector({
|
|||
const title = placeholder || intl.formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'});
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const goToSelectorScreen = useCallback(preventDoubleTap(() => {
|
||||
const screen = Screens.INTEGRATION_SELECTOR;
|
||||
goToScreen(screen, title, {dataSource, handleSelect, options, getDynamicOptions, selected, isMultiselect});
|
||||
}), [dataSource, options, getDynamicOptions]);
|
||||
|
||||
const handleSelect = useCallback((newSelection?: Selection) => {
|
||||
if (!newSelection) {
|
||||
return;
|
||||
|
|
@ -184,6 +179,11 @@ function AutoCompleteSelector({
|
|||
}
|
||||
}, [teammateNameDisplay, intl, dataSource, onSelected]);
|
||||
|
||||
const goToSelectorScreen = usePreventDoubleTap(useCallback((() => {
|
||||
const screen = Screens.INTEGRATION_SELECTOR;
|
||||
goToScreen(screen, title, {dataSource, handleSelect, options, getDynamicOptions, selected, isMultiselect});
|
||||
}), [title, dataSource, handleSelect, options, getDynamicOptions, selected, isMultiselect]));
|
||||
|
||||
// Handle the text for the default value.
|
||||
useEffect(() => {
|
||||
if (!selected) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {leaveChannel} from '@actions/remote/channel';
|
||||
|
|
@ -23,6 +23,49 @@ type Props = {
|
|||
testID?: string;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
closeDirectMessage: {
|
||||
id: 'channel_info.close_dm',
|
||||
defaultMessage: 'Close direct message',
|
||||
},
|
||||
closeGroupMessage: {
|
||||
id: 'channel_info.close_gm',
|
||||
defaultMessage: 'Close group message',
|
||||
},
|
||||
closeDirectMessageChannel: {
|
||||
id: 'channel_info.close_dm_channel',
|
||||
defaultMessage: 'Are you sure you want to close this direct message? This will remove it from your home screen, but you can always open it again.',
|
||||
},
|
||||
closeGroupMessageChannel: {
|
||||
id: 'channel_info.close_gm_channel',
|
||||
defaultMessage: 'Are you sure you want to close this group message? This will remove it from your home screen, but you can always open it again.',
|
||||
},
|
||||
leaveChannel: {
|
||||
id: 'channel_info.leave_channel',
|
||||
defaultMessage: 'Leave channel',
|
||||
},
|
||||
leavePrivateChannel: {
|
||||
id: 'channel_info.leave_private_channel',
|
||||
defaultMessage: "Are you sure you want to leave the private channel {displayName}? You cannot rejoin the channel unless you're invited again.",
|
||||
},
|
||||
leavePublicChannel: {
|
||||
id: 'channel_info.leave_public_channel',
|
||||
defaultMessage: 'Are you sure you want to leave the public channel {displayName}? You can always rejoin.',
|
||||
},
|
||||
cancel: {
|
||||
id: 'mobile.post.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
close: {
|
||||
id: 'channel_info.close',
|
||||
defaultMessage: 'Close',
|
||||
},
|
||||
leave: {
|
||||
id: 'channel_info.leave',
|
||||
defaultMessage: 'Leave',
|
||||
},
|
||||
});
|
||||
|
||||
const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type, testID}: Props) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
|
|
@ -37,16 +80,13 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
|
|||
|
||||
const closeDirectMessage = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({id: 'channel_info.close_dm', defaultMessage: 'Close direct message'}),
|
||||
intl.formatMessage({
|
||||
id: 'channel_info.close_dm_channel',
|
||||
defaultMessage: 'Are you sure you want to close this direct message? This will remove it from your home screen, but you can always open it again.',
|
||||
}),
|
||||
intl.formatMessage(messages.closeDirectMessage),
|
||||
intl.formatMessage(messages.closeDirectMessageChannel),
|
||||
[{
|
||||
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
|
||||
text: intl.formatMessage(messages.cancel),
|
||||
style: 'cancel',
|
||||
}, {
|
||||
text: intl.formatMessage({id: 'channel_info.close', defaultMessage: 'Close'}),
|
||||
text: intl.formatMessage(messages.close),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
setDirectChannelVisible(serverUrl, channelId, false);
|
||||
|
|
@ -58,16 +98,13 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
|
|||
|
||||
const closeGroupMessage = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({id: 'channel_info.close_gm', defaultMessage: 'Close group message'}),
|
||||
intl.formatMessage({
|
||||
id: 'channel_info.close_gm_channel',
|
||||
defaultMessage: 'Are you sure you want to close this group message? This will remove it from your home screen, but you can always open it again.',
|
||||
}),
|
||||
intl.formatMessage(messages.closeGroupMessage),
|
||||
intl.formatMessage(messages.closeGroupMessageChannel),
|
||||
[{
|
||||
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
|
||||
text: intl.formatMessage(messages.cancel),
|
||||
style: 'cancel',
|
||||
}, {
|
||||
text: intl.formatMessage({id: 'channel_info.close', defaultMessage: 'Close'}),
|
||||
text: intl.formatMessage(messages.close),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
setDirectChannelVisible(serverUrl, channelId, false);
|
||||
|
|
@ -79,16 +116,13 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
|
|||
|
||||
const leavePublicChannel = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({id: 'channel_info.leave_channel', defaultMessage: 'Leave Channel'}),
|
||||
intl.formatMessage({
|
||||
id: 'channel_info.leave_public_channel',
|
||||
defaultMessage: 'Are you sure you want to leave the public channel {displayName}? You can always rejoin.',
|
||||
}, {displayName}),
|
||||
intl.formatMessage(messages.leaveChannel),
|
||||
intl.formatMessage(messages.leavePublicChannel, {displayName}),
|
||||
[{
|
||||
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
|
||||
text: intl.formatMessage(messages.cancel),
|
||||
style: 'cancel',
|
||||
}, {
|
||||
text: intl.formatMessage({id: 'channel_info.leave', defaultMessage: 'Leave'}),
|
||||
text: intl.formatMessage(messages.leave),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
leaveChannel(serverUrl, channelId);
|
||||
|
|
@ -100,16 +134,13 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
|
|||
|
||||
const leavePrivateChannel = () => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({id: 'channel_info.leave_channel', defaultMessage: 'Leave Channel'}),
|
||||
intl.formatMessage({
|
||||
id: 'channel_info.leave_private_channel',
|
||||
defaultMessage: "Are you sure you want to leave the private channel {displayName}? You cannot rejoin the channel unless you're invited again.",
|
||||
}, {displayName}),
|
||||
intl.formatMessage(messages.leaveChannel),
|
||||
intl.formatMessage(messages.leavePrivateChannel, {displayName}),
|
||||
[{
|
||||
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
|
||||
text: intl.formatMessage(messages.cancel),
|
||||
style: 'cancel',
|
||||
}, {
|
||||
text: intl.formatMessage({id: 'channel_info.leave', defaultMessage: 'Leave'}),
|
||||
text: intl.formatMessage(messages.leave),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
leaveChannel(serverUrl, channelId);
|
||||
|
|
@ -144,15 +175,15 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
|
|||
let icon;
|
||||
switch (type) {
|
||||
case General.DM_CHANNEL:
|
||||
leaveText = intl.formatMessage({id: 'channel_info.close_dm', defaultMessage: 'Close direct message'});
|
||||
leaveText = intl.formatMessage(messages.closeDirectMessage);
|
||||
icon = 'close';
|
||||
break;
|
||||
case General.GM_CHANNEL:
|
||||
leaveText = intl.formatMessage({id: 'channel_info.close_gm', defaultMessage: 'Close group message'});
|
||||
leaveText = intl.formatMessage(messages.closeGroupMessage);
|
||||
icon = 'close';
|
||||
break;
|
||||
default:
|
||||
leaveText = intl.formatMessage({id: 'channel_info.leave_channel', defaultMessage: 'Leave channel'});
|
||||
leaveText = intl.formatMessage(messages.leaveChannel);
|
||||
icon = 'exit-to-app';
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const messages = defineMessages({
|
|||
},
|
||||
remove_title: {
|
||||
id: 'mobile.manage_members.remove_member',
|
||||
defaultMessage: 'Remove From Channel',
|
||||
defaultMessage: 'Remove from Channel',
|
||||
},
|
||||
remove_message: {
|
||||
id: 'mobile.manage_members.message',
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ const AddBookmark = ({bookmarksCount, channelId, currentUserId, canUploadFiles,
|
|||
formatMessage({id: 'channel_info.add_bookmark', defaultMessage: 'Add a bookmark'}),
|
||||
formatMessage({
|
||||
id: 'channel_info.add_bookmark.max_reached',
|
||||
defaultMessage: 'This channel has reached the maximum number of bookmarks ({count}).',
|
||||
}, {count: MAX_BOOKMARKS_PER_CHANNEL}),
|
||||
defaultMessage: 'This channel has reached the maximum number of bookmarks.',
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useIntl, type MessageDescriptor} from 'react-intl';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
|
||||
type BaseOptionType = {
|
||||
defaultMessage: string;
|
||||
i18nId: string;
|
||||
message: MessageDescriptor;
|
||||
iconName: string;
|
||||
isDestructive?: boolean;
|
||||
onPress: () => void;
|
||||
|
|
@ -16,8 +15,7 @@ type BaseOptionType = {
|
|||
}
|
||||
|
||||
const BaseOption = ({
|
||||
defaultMessage,
|
||||
i18nId,
|
||||
message,
|
||||
iconName,
|
||||
isDestructive = false,
|
||||
onPress,
|
||||
|
|
@ -30,7 +28,7 @@ const BaseOption = ({
|
|||
action={onPress}
|
||||
destructive={isDestructive}
|
||||
icon={iconName}
|
||||
label={intl.formatMessage({id: i18nId, defaultMessage})}
|
||||
label={intl.formatMessage(message)}
|
||||
testID={testID}
|
||||
type='default'
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import {BaseOption} from '@components/common_post_options';
|
||||
import {SNACK_BAR_TYPE} from '@constants/snack_bar';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {showSnackBar} from '@utils/snack_bar';
|
||||
|
||||
|
|
@ -20,6 +20,14 @@ type Props = {
|
|||
post: PostModel;
|
||||
teamName: string;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
copyLink: {
|
||||
id: 'get_post_link_modal.title',
|
||||
defaultMessage: 'Copy Link',
|
||||
},
|
||||
});
|
||||
|
||||
const CopyPermalinkOption = ({bottomSheetId, teamName, post, sourceScreen}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
|
|
@ -28,12 +36,11 @@ const CopyPermalinkOption = ({bottomSheetId, teamName, post, sourceScreen}: Prop
|
|||
Clipboard.setString(permalink);
|
||||
await dismissBottomSheet(bottomSheetId);
|
||||
showSnackBar({barType: SNACK_BAR_TYPE.LINK_COPIED, sourceScreen});
|
||||
}, [teamName, post.id, bottomSheetId]);
|
||||
}, [serverUrl, teamName, post.id, bottomSheetId, sourceScreen]);
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('get_post_link_modal.title')}
|
||||
defaultMessage='Copy Link'
|
||||
message={messages.copyLink}
|
||||
onPress={handleCopyLink}
|
||||
iconName='link-variant'
|
||||
testID='post_options.copy_permalink.option'
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages, type MessageDescriptor} from 'react-intl';
|
||||
|
||||
import {updateThreadFollowing} from '@actions/remote/thread';
|
||||
import {BaseOption} from '@components/common_post_options';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
|
||||
import type ThreadModel from '@typings/database/models/servers/thread';
|
||||
|
|
@ -18,28 +18,41 @@ type FollowThreadOptionProps = {
|
|||
teamId?: string;
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollowThread: {
|
||||
id: 'threads.unfollowThread',
|
||||
defaultMessage: 'Unfollow Thread',
|
||||
},
|
||||
followThread: {
|
||||
id: 'threads.followThread',
|
||||
defaultMessage: 'Follow Thread',
|
||||
},
|
||||
followMessage: {
|
||||
id: 'threads.followMessage',
|
||||
defaultMessage: 'Follow Message',
|
||||
},
|
||||
unfollowMessage: {
|
||||
id: 'threads.unfollowMessage',
|
||||
defaultMessage: 'Unfollow Message',
|
||||
},
|
||||
});
|
||||
const FollowThreadOption = ({bottomSheetId, thread, teamId}: FollowThreadOptionProps) => {
|
||||
let id: string;
|
||||
let defaultMessage: string;
|
||||
let message: MessageDescriptor;
|
||||
let icon: string;
|
||||
|
||||
if (thread.isFollowing) {
|
||||
icon = 'message-minus-outline';
|
||||
if (thread.replyCount) {
|
||||
id = t('threads.unfollowThread');
|
||||
defaultMessage = 'Unfollow Thread';
|
||||
message = messages.unfollowThread;
|
||||
} else {
|
||||
id = t('threads.unfollowMessage');
|
||||
defaultMessage = 'Unfollow Message';
|
||||
message = messages.unfollowMessage;
|
||||
}
|
||||
} else {
|
||||
icon = 'message-plus-outline';
|
||||
if (thread.replyCount) {
|
||||
id = t('threads.followThread');
|
||||
defaultMessage = 'Follow Thread';
|
||||
message = messages.followThread;
|
||||
} else {
|
||||
id = t('threads.followMessage');
|
||||
defaultMessage = 'Follow Message';
|
||||
message = messages.followMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,14 +64,13 @@ const FollowThreadOption = ({bottomSheetId, thread, teamId}: FollowThreadOptionP
|
|||
}
|
||||
await dismissBottomSheet(bottomSheetId);
|
||||
updateThreadFollowing(serverUrl, teamId, thread.id, !thread.isFollowing, true);
|
||||
}, [bottomSheetId, teamId, thread]);
|
||||
}, [bottomSheetId, serverUrl, teamId, thread.id, thread.isFollowing]);
|
||||
|
||||
const followThreadOptionTestId = thread.isFollowing ? 'post_options.following_thread.option' : 'post_options.follow_thread.option';
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={id}
|
||||
defaultMessage={defaultMessage}
|
||||
message={message}
|
||||
testID={followThreadOptionTestId}
|
||||
iconName={icon}
|
||||
onPress={handleToggleFollow}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import {fetchAndSwitchToThread} from '@actions/remote/thread';
|
||||
import {BaseOption} from '@components/common_post_options';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
|
@ -16,6 +16,13 @@ type Props = {
|
|||
post: PostModel;
|
||||
bottomSheetId: AvailableScreens;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
reply: {
|
||||
id: 'mobile.post_info.reply',
|
||||
defaultMessage: 'Reply',
|
||||
},
|
||||
});
|
||||
const ReplyOption = ({post, bottomSheetId}: Props) => {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
|
|
@ -27,8 +34,7 @@ const ReplyOption = ({post, bottomSheetId}: Props) => {
|
|||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('mobile.post_info.reply')}
|
||||
defaultMessage='Reply'
|
||||
message={messages.reply}
|
||||
iconName='reply-outline'
|
||||
onPress={handleReply}
|
||||
testID='post_options.reply_post.option'
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import {deleteSavedPost, savePostPreference} from '@actions/remote/preference';
|
||||
import {BaseOption} from '@components/common_post_options';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
|
@ -17,6 +17,17 @@ type CopyTextProps = {
|
|||
postId: string;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
save: {
|
||||
id: 'mobile.post_info.save',
|
||||
defaultMessage: 'Save',
|
||||
},
|
||||
unsave: {
|
||||
id: 'mobile.post_info.unsave',
|
||||
defaultMessage: 'Unsave',
|
||||
},
|
||||
});
|
||||
|
||||
const SaveOption = ({bottomSheetId, isSaved, postId}: CopyTextProps) => {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
|
|
@ -24,18 +35,16 @@ const SaveOption = ({bottomSheetId, isSaved, postId}: CopyTextProps) => {
|
|||
const remoteAction = isSaved ? deleteSavedPost : savePostPreference;
|
||||
await dismissBottomSheet(bottomSheetId);
|
||||
remoteAction(serverUrl, postId);
|
||||
}, [bottomSheetId, postId, serverUrl]);
|
||||
}, [bottomSheetId, isSaved, postId, serverUrl]);
|
||||
|
||||
const id = isSaved ? t('mobile.post_info.unsave') : t('mobile.post_info.save');
|
||||
const defaultMessage = isSaved ? 'Unsave' : 'Save';
|
||||
const message = isSaved ? messages.unsave : messages.save;
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={id}
|
||||
defaultMessage={defaultMessage}
|
||||
message={message}
|
||||
iconName='bookmark-outline'
|
||||
onPress={onHandlePress}
|
||||
testID={`post_options.${defaultMessage.toLocaleLowerCase()}_post.option`}
|
||||
testID={`post_options.${message.defaultMessage.toLocaleLowerCase()}_post.option`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import {BaseOption} from '@components/common_post_options';
|
||||
import {SNACK_BAR_TYPE} from '@constants/snack_bar';
|
||||
import {t} from '@i18n';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {showSnackBar} from '@utils/snack_bar';
|
||||
|
||||
|
|
@ -18,6 +18,14 @@ type Props = {
|
|||
sourceScreen: AvailableScreens;
|
||||
postMessage: string;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
copyText: {
|
||||
id: 'mobile.post_info.copy_text',
|
||||
defaultMessage: 'Copy Text',
|
||||
},
|
||||
});
|
||||
|
||||
const CopyTextOption = ({bottomSheetId, postMessage, sourceScreen}: Props) => {
|
||||
const handleCopyText = useCallback(async () => {
|
||||
Clipboard.setString(postMessage);
|
||||
|
|
@ -25,12 +33,11 @@ const CopyTextOption = ({bottomSheetId, postMessage, sourceScreen}: Props) => {
|
|||
if ((Platform.OS === 'android' && Platform.Version < 33) || Platform.OS === 'ios') {
|
||||
showSnackBar({barType: SNACK_BAR_TYPE.MESSAGE_COPIED, sourceScreen});
|
||||
}
|
||||
}, [postMessage]);
|
||||
}, [bottomSheetId, postMessage, sourceScreen]);
|
||||
|
||||
return (
|
||||
<BaseOption
|
||||
i18nId={t('mobile.post_info.copy_text')}
|
||||
defaultMessage='Copy Text'
|
||||
message={messages.copyText}
|
||||
iconName='content-copy'
|
||||
onPress={handleCopyText}
|
||||
testID='post_options.copy_text.option'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import React from 'react';
|
|||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -34,9 +34,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
const ClearButton = ({handlePress, iconName = 'close-circle', size = 20, containerSize = 40, theme, testID}: Props) => {
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const onPress = usePreventDoubleTap(handlePress);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={preventDoubleTap(handlePress)}
|
||||
onPress={onPress}
|
||||
style={[style.container, {height: containerSize, width: containerSize}]}
|
||||
testID={testID}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {type ListRenderItemInfo, View} from 'react-native';
|
||||
import {FlatList} from 'react-native-gesture-handler';
|
||||
|
||||
|
|
@ -10,7 +10,6 @@ import OptionItem, {ITEM_HEIGHT} from '@components/option_item';
|
|||
import {useTheme} from '@context/theme';
|
||||
import {useBottomSheetListsFix} from '@hooks/bottom_sheet_lists_fix';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import BottomSheetContent from '@screens/bottom_sheet/content';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {type FileFilter, FileFilters} from '@utils/file';
|
||||
|
|
@ -25,6 +24,41 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const filterMessages = defineMessages({
|
||||
allFileTypes: {
|
||||
id: 'screen.search.results.filter.all_file_types',
|
||||
defaultMessage: 'All file types',
|
||||
},
|
||||
documents: {
|
||||
id: 'screen.search.results.filter.documents',
|
||||
defaultMessage: 'Documents',
|
||||
},
|
||||
spreadsheets: {
|
||||
id: 'screen.search.results.filter.spreadsheets',
|
||||
defaultMessage: 'Spreadsheets',
|
||||
},
|
||||
presentations: {
|
||||
id: 'screen.search.results.filter.presentations',
|
||||
defaultMessage: 'Presentations',
|
||||
},
|
||||
code: {
|
||||
id: 'screen.search.results.filter.code',
|
||||
defaultMessage: 'Code',
|
||||
},
|
||||
images: {
|
||||
id: 'screen.search.results.filter.images',
|
||||
defaultMessage: 'Images',
|
||||
},
|
||||
audio: {
|
||||
id: 'screen.search.results.filter.audio',
|
||||
defaultMessage: 'Audio',
|
||||
},
|
||||
videos: {
|
||||
id: 'screen.search.results.filter.videos',
|
||||
defaultMessage: 'Videos',
|
||||
},
|
||||
});
|
||||
|
||||
type FilterItem = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
|
|
@ -33,43 +67,35 @@ type FilterItem = {
|
|||
}
|
||||
export const FilterData = {
|
||||
[FileFilters.ALL]: {
|
||||
id: t('screen.search.results.filter.all_file_types'),
|
||||
defaultMessage: 'All file types',
|
||||
...filterMessages.allFileTypes,
|
||||
filterType: FileFilters.ALL,
|
||||
},
|
||||
[FileFilters.DOCUMENTS]: {
|
||||
id: t('screen.search.results.filter.documents'),
|
||||
defaultMessage: 'Documents',
|
||||
...filterMessages.documents,
|
||||
filterType: FileFilters.DOCUMENTS,
|
||||
},
|
||||
[FileFilters.SPREADSHEETS]: {
|
||||
id: t('screen.search.results.filter.spreadsheets'),
|
||||
defaultMessage: 'Spreadsheets',
|
||||
...filterMessages.spreadsheets,
|
||||
filterType: FileFilters.SPREADSHEETS,
|
||||
},
|
||||
[FileFilters.PRESENTATIONS]: {
|
||||
id: t('screen.search.results.filter.presentations'),
|
||||
defaultMessage: 'Presentations',
|
||||
...filterMessages.presentations,
|
||||
filterType: FileFilters.PRESENTATIONS,
|
||||
},
|
||||
[FileFilters.CODE]: {
|
||||
id: t('screen.search.results.filter.code'),
|
||||
defaultMessage: 'Code',
|
||||
...filterMessages.code,
|
||||
filterType: FileFilters.CODE,
|
||||
},
|
||||
[FileFilters.IMAGES]: {
|
||||
id: t('screen.search.results.filter.images'),
|
||||
defaultMessage: 'Images',
|
||||
...filterMessages.images,
|
||||
filterType: FileFilters.IMAGES,
|
||||
},
|
||||
[FileFilters.AUDIO]: {
|
||||
id: t('screen.search.results.filter.audio'),
|
||||
defaultMessage: 'Audio',
|
||||
...filterMessages.audio,
|
||||
filterType: FileFilters.AUDIO,
|
||||
},
|
||||
[FileFilters.VIDEOS]: {
|
||||
id: t('screen.search.results.filter.videos'),
|
||||
defaultMessage: 'Videos',
|
||||
...filterMessages.videos,
|
||||
filterType: FileFilters.VIDEOS,
|
||||
separator: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
import {type StyleProp, Text, type TextStyle} from 'react-native';
|
||||
|
||||
import {joinChannel, switchToChannelById} from '@actions/remote/channel';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {secureGetFromRecord, isRecordOf} from '@utils/types';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
|
@ -90,16 +89,16 @@ const ChannelMention = ({
|
|||
const serverUrl = useServerUrl();
|
||||
const channel = getChannelFromChannelName(channelName, channels, channelMentions, team.name);
|
||||
|
||||
const handlePress = preventDoubleTap(async () => {
|
||||
const handlePress = usePreventDoubleTap(useCallback((async () => {
|
||||
let c = channel;
|
||||
|
||||
if (!c?.id && c?.display_name) {
|
||||
const result = await joinChannel(serverUrl, currentTeamId, undefined, channelName);
|
||||
if (result.error || !result.channel) {
|
||||
const joinFailedMessage = {
|
||||
id: t('mobile.join_channel.error'),
|
||||
const joinFailedMessage = defineMessage({
|
||||
id: 'mobile.join_channel.error',
|
||||
defaultMessage: "We couldn't join the channel {displayName}.",
|
||||
};
|
||||
});
|
||||
alertErrorWithFallback(intl, result.error || {}, joinFailedMessage, {displayName: c.display_name});
|
||||
} else if (result.channel) {
|
||||
c = {
|
||||
|
|
@ -113,7 +112,7 @@ const ChannelMention = ({
|
|||
if (c?.id) {
|
||||
switchToChannelById(serverUrl, c.id);
|
||||
}
|
||||
});
|
||||
}), [channel, channelName, currentTeamId, intl, serverUrl]));
|
||||
|
||||
if (!channel) {
|
||||
return <Text style={textStyle}>{`~${channelName}`}</Text>;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import FormattedText from '@components/formatted_text';
|
|||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {bottomSheet, dismissBottomSheet, goToScreen} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {getHighlightLanguageFromNameOrAlias, getHighlightLanguageName} from '@utils/markdown';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type {SyntaxHiglightProps} from '@typings/components/syntax_highlight';
|
||||
|
|
@ -79,7 +79,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
|||
return syntaxHighlighter;
|
||||
}, []);
|
||||
|
||||
const handlePress = useCallback(preventDoubleTap(() => {
|
||||
const handlePress = usePreventDoubleTap(useCallback(() => {
|
||||
const screen = Screens.CODE;
|
||||
const passProps = {
|
||||
code: content,
|
||||
|
|
@ -110,7 +110,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
|||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
}), [content, intl.locale, language]);
|
||||
}, [content, intl, language, textStyle]));
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
if (managedConfig?.copyAndPasteProtection !== 'true') {
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import MathView from '@components/math_view';
|
|||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {Screens} from '@constants';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {bottomSheet, dismissBottomSheet, goToScreen} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {getHighlightLanguageName} from '@utils/markdown';
|
||||
import {splitLatexCodeInLines} from '@utils/markdown/latex';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ const LatexCodeBlock = ({content, theme}: Props) => {
|
|||
};
|
||||
}, [content]);
|
||||
|
||||
const handlePress = useCallback(preventDoubleTap(() => {
|
||||
const handlePress = usePreventDoubleTap(useCallback(() => {
|
||||
const screen = Screens.LATEX;
|
||||
const passProps = {
|
||||
content,
|
||||
|
|
@ -126,7 +126,7 @@ const LatexCodeBlock = ({content, theme}: Props) => {
|
|||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
}), [content, languageDisplayName, intl.locale]);
|
||||
}, [content, intl, languageDisplayName]));
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
if (managedConfig?.copyAndPasteProtection !== 'true') {
|
||||
|
|
@ -168,7 +168,7 @@ const LatexCodeBlock = ({content, theme}: Props) => {
|
|||
|
||||
const onRenderErrorMessage = useCallback(({error}: {error: Error}) => {
|
||||
return <Text style={styles.errorText}>{'Render error: ' + error.message}</Text>;
|
||||
}, []);
|
||||
}, [styles.errorText]);
|
||||
|
||||
let plusMoreLines = null;
|
||||
if (split.numberOfLines > MAX_LINES) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {TabTypes, type TabType} from '@utils/search';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
|
@ -42,24 +42,28 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const messages = defineMessages({
|
||||
files: {
|
||||
id: 'mobile.no_results_with_term.files',
|
||||
defaultMessage: 'No files matching “{term}”',
|
||||
},
|
||||
messages: {
|
||||
id: 'mobile.no_results_with_term.messages',
|
||||
defaultMessage: 'No matches found for “{term}”',
|
||||
},
|
||||
});
|
||||
|
||||
const NoResultsWithTerm = ({term, type}: Props) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
const [titleId, setTitleId] = useState(t('mobile.no_results_with_term'));
|
||||
const [defaultMessage, setDefaultMessage] = useState('No results for “{term}”');
|
||||
|
||||
useEffect(() => {
|
||||
setTitleId(type === TabTypes.FILES ? t('mobile.no_results_with_term.files') : t('mobile.no_results_with_term.messages'));
|
||||
setDefaultMessage(type === TabTypes.FILES ? 'No files matching “{term}”' : 'No matches found for “{term}”');
|
||||
}, [type]);
|
||||
const titleMessage = type === TabTypes.FILES ? messages.files : messages.messages;
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
{type === TabTypes.FILES ? <SearchFilesIllustration/> : <SearchIllustration/>}
|
||||
<FormattedText
|
||||
id={titleId}
|
||||
defaultMessage={defaultMessage}
|
||||
{...titleMessage}
|
||||
style={style.result}
|
||||
values={{term}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import {Button} from '@rneui/base';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {type Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {switchToPenultimateChannel} from '@actions/remote/channel';
|
||||
|
|
@ -11,7 +12,6 @@ import FormattedText from '@components/formatted_text';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {popToRoot} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
|
@ -57,6 +57,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
|||
|
||||
const edges: Edge[] = ['bottom'];
|
||||
|
||||
const messages = defineMessages({
|
||||
archivedChannelMessage: {
|
||||
id: 'archivedChannelMessage',
|
||||
defaultMessage: 'You are viewing an **archived channel**. New messages cannot be posted.',
|
||||
},
|
||||
deactivatedChannelMessage: {
|
||||
id: 'create_post.deactivated',
|
||||
defaultMessage: 'You are viewing an archived channel with a deactivated user.',
|
||||
},
|
||||
});
|
||||
|
||||
export default function Archived({
|
||||
testID,
|
||||
deactivated,
|
||||
|
|
@ -75,17 +86,11 @@ export default function Archived({
|
|||
}
|
||||
}, [serverUrl, isTablet]);
|
||||
|
||||
let message = {
|
||||
id: t('archivedChannelMessage'),
|
||||
defaultMessage: 'You are viewing an **archived channel**. New messages cannot be posted.',
|
||||
};
|
||||
let message = messages.archivedChannelMessage;
|
||||
|
||||
if (deactivated) {
|
||||
// only applies to DM's when the user was deactivated
|
||||
message = {
|
||||
id: t('create_post.deactivated'),
|
||||
defaultMessage: 'You are viewing an archived channel with a deactivated user.',
|
||||
};
|
||||
message = messages.deactivatedChannelMessage;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {useHardwareKeyboardEvents} from '@mattermost/hardware-keyboard';
|
|||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import PasteableTextInput, {type PastedFile, type PasteInputRef} from '@mattermost/react-native-paste-input';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {type IntlShape, useIntl} from 'react-intl';
|
||||
import {defineMessage, type IntlShape, useIntl} from 'react-intl';
|
||||
import {
|
||||
Alert, AppState, type AppStateStatus, DeviceEventEmitter, type EmitterSubscription, Keyboard,
|
||||
type NativeSyntheticEvent, Platform, type TextInputSelectionChangeEventData,
|
||||
|
|
@ -19,7 +19,6 @@ import {useServerUrl} from '@context/server';
|
|||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useInputPropagation} from '@hooks/input';
|
||||
import {t} from '@i18n';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {handleDraftUpdate} from '@utils/draft';
|
||||
import {extractFileInfo} from '@utils/file';
|
||||
|
|
@ -72,9 +71,9 @@ const getPlaceHolder = (rootId?: string) => {
|
|||
let placeholder;
|
||||
|
||||
if (rootId) {
|
||||
placeholder = {id: t('create_post.thread_reply'), defaultMessage: 'Reply to this thread...'};
|
||||
placeholder = defineMessage({id: 'create_post.thread_reply', defaultMessage: 'Reply to this thread...'});
|
||||
} else {
|
||||
placeholder = {id: t('create_post.write'), defaultMessage: 'Write to {channelDisplayName}'};
|
||||
placeholder = defineMessage({id: 'create_post.write', defaultMessage: 'Write to {channelDisplayName}'});
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
|
|
@ -214,12 +213,16 @@ export default function PostInput({
|
|||
lastTypingEventSent.current = Date.now();
|
||||
}
|
||||
}, [
|
||||
shouldProcessEvent,
|
||||
updateValue,
|
||||
checkMessageLength,
|
||||
timeBetweenUserTypingUpdatesMilliseconds,
|
||||
membersInChannel,
|
||||
maxNotificationsPerChannel,
|
||||
enableUserTypingMessage,
|
||||
serverUrl,
|
||||
channelId,
|
||||
rootId,
|
||||
(membersInChannel < maxNotificationsPerChannel) && enableUserTypingMessage,
|
||||
]);
|
||||
|
||||
const onPaste = useCallback(async (error: string | null | undefined, files: PastedFile[]) => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import {Post} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
|
||||
const {
|
||||
JOIN_CHANNEL, ADD_TO_CHANNEL, REMOVE_FROM_CHANNEL, LEAVE_CHANNEL,
|
||||
|
|
@ -10,183 +11,183 @@ const {
|
|||
} = Post.POST_TYPES;
|
||||
|
||||
export const postTypeMessages = {
|
||||
[JOIN_CHANNEL]: {
|
||||
[JOIN_CHANNEL]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.joined_channel.one'),
|
||||
id: 'combined_system_message.joined_channel.one',
|
||||
defaultMessage: '{firstUser} **joined the channel**.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.joined_channel.one_you'),
|
||||
id: 'combined_system_message.joined_channel.one_you',
|
||||
defaultMessage: 'You **joined the channel**.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.joined_channel.two'),
|
||||
id: 'combined_system_message.joined_channel.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} **joined the channel**.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.joined_channel.many_expanded'),
|
||||
id: 'combined_system_message.joined_channel.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} **joined the channel**.',
|
||||
},
|
||||
},
|
||||
[ADD_TO_CHANNEL]: {
|
||||
}),
|
||||
[ADD_TO_CHANNEL]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.added_to_channel.one'),
|
||||
id: 'combined_system_message.added_to_channel.one',
|
||||
defaultMessage: '{firstUser} **added to the channel** by {actor}.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.added_to_channel.one_you'),
|
||||
id: 'combined_system_message.added_to_channel.one_you',
|
||||
defaultMessage: 'You were **added to the channel** by {actor}.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.added_to_channel.two'),
|
||||
id: 'combined_system_message.added_to_channel.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} **added to the channel** by {actor}.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.added_to_channel.many_expanded'),
|
||||
id: 'combined_system_message.added_to_channel.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} were **added to the channel** by {actor}.',
|
||||
},
|
||||
},
|
||||
[REMOVE_FROM_CHANNEL]: {
|
||||
}),
|
||||
[REMOVE_FROM_CHANNEL]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.removed_from_channel.one'),
|
||||
id: 'combined_system_message.removed_from_channel.one',
|
||||
defaultMessage: '{firstUser} was **removed from the channel**.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.removed_from_channel.one_you'),
|
||||
id: 'combined_system_message.removed_from_channel.one_you',
|
||||
defaultMessage: 'You were **removed from the channel**.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.removed_from_channel.two'),
|
||||
id: 'combined_system_message.removed_from_channel.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} were **removed from the channel**.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.removed_from_channel.many_expanded'),
|
||||
id: 'combined_system_message.removed_from_channel.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} were **removed from the channel**.',
|
||||
},
|
||||
},
|
||||
[LEAVE_CHANNEL]: {
|
||||
}),
|
||||
[LEAVE_CHANNEL]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.left_channel.one'),
|
||||
id: 'combined_system_message.left_channel.one',
|
||||
defaultMessage: '{firstUser} **left the channel**.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.left_channel.one_you'),
|
||||
id: 'combined_system_message.left_channel.one_you',
|
||||
defaultMessage: 'You **left the channel**.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.left_channel.two'),
|
||||
id: 'combined_system_message.left_channel.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} **left the channel**.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.left_channel.many_expanded'),
|
||||
id: 'combined_system_message.left_channel.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} **left the channel**.',
|
||||
},
|
||||
},
|
||||
[JOIN_TEAM]: {
|
||||
}),
|
||||
[JOIN_TEAM]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.joined_team.one'),
|
||||
id: 'combined_system_message.joined_team.one',
|
||||
defaultMessage: '{firstUser} **joined the team**.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.joined_team.one_you'),
|
||||
id: 'combined_system_message.joined_team.one_you',
|
||||
defaultMessage: 'You **joined the team**.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.joined_team.two'),
|
||||
id: 'combined_system_message.joined_team.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} **joined the team**.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.joined_team.many_expanded'),
|
||||
id: 'combined_system_message.joined_team.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} **joined the team**.',
|
||||
},
|
||||
},
|
||||
[ADD_TO_TEAM]: {
|
||||
}),
|
||||
[ADD_TO_TEAM]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.added_to_team.one'),
|
||||
id: 'combined_system_message.added_to_team.one',
|
||||
defaultMessage: '{firstUser} **added to the team** by {actor}.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.added_to_team.one_you'),
|
||||
id: 'combined_system_message.added_to_team.one_you',
|
||||
defaultMessage: 'You were **added to the team** by {actor}.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.added_to_team.two'),
|
||||
id: 'combined_system_message.added_to_team.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} **added to the team** by {actor}.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.added_to_team.many_expanded'),
|
||||
id: 'combined_system_message.added_to_team.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} were **added to the team** by {actor}.',
|
||||
},
|
||||
},
|
||||
[REMOVE_FROM_TEAM]: {
|
||||
}),
|
||||
[REMOVE_FROM_TEAM]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.removed_from_team.one'),
|
||||
id: 'combined_system_message.removed_from_team.one',
|
||||
defaultMessage: '{firstUser} was **removed from the team**.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.removed_from_team.one_you'),
|
||||
id: 'combined_system_message.removed_from_team.one_you',
|
||||
defaultMessage: 'You were **removed from the team**.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.removed_from_team.two'),
|
||||
id: 'combined_system_message.removed_from_team.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} were **removed from the team**.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.removed_from_team.many_expanded'),
|
||||
id: 'combined_system_message.removed_from_team.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} were **removed from the team**.',
|
||||
},
|
||||
},
|
||||
[LEAVE_TEAM]: {
|
||||
}),
|
||||
[LEAVE_TEAM]: defineMessages({
|
||||
one: {
|
||||
id: t('combined_system_message.left_team.one'),
|
||||
id: 'combined_system_message.left_team.one',
|
||||
defaultMessage: '{firstUser} **left the team**.',
|
||||
},
|
||||
one_you: {
|
||||
id: t('combined_system_message.left_team.one_you'),
|
||||
id: 'combined_system_message.left_team.one_you',
|
||||
defaultMessage: 'You **left the team**.',
|
||||
},
|
||||
two: {
|
||||
id: t('combined_system_message.left_team.two'),
|
||||
id: 'combined_system_message.left_team.two',
|
||||
defaultMessage: '{firstUser} and {secondUser} **left the team**.',
|
||||
},
|
||||
many_expanded: {
|
||||
id: t('combined_system_message.left_team.many_expanded'),
|
||||
id: 'combined_system_message.left_team.many_expanded',
|
||||
defaultMessage: '{users} and {lastUser} **left the team**.',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export const systemMessages = {
|
||||
export const systemMessages = defineMessages({
|
||||
[ADD_TO_CHANNEL]: {
|
||||
id: t('last_users_message.added_to_channel.type'),
|
||||
id: 'last_users_message.added_to_channel.type',
|
||||
defaultMessage: 'were **added to the channel** by {actor}.',
|
||||
},
|
||||
[JOIN_CHANNEL]: {
|
||||
id: t('last_users_message.joined_channel.type'),
|
||||
id: 'last_users_message.joined_channel.type',
|
||||
defaultMessage: '**joined the channel**.',
|
||||
},
|
||||
[LEAVE_CHANNEL]: {
|
||||
id: t('last_users_message.left_channel.type'),
|
||||
id: 'last_users_message.left_channel.type',
|
||||
defaultMessage: '**left the channel**.',
|
||||
},
|
||||
[REMOVE_FROM_CHANNEL]: {
|
||||
id: t('last_users_message.removed_from_channel.type'),
|
||||
id: 'last_users_message.removed_from_channel.type',
|
||||
defaultMessage: 'were **removed from the channel**.',
|
||||
},
|
||||
[ADD_TO_TEAM]: {
|
||||
id: t('last_users_message.added_to_team.type'),
|
||||
id: 'last_users_message.added_to_team.type',
|
||||
defaultMessage: 'were **added to the team** by {actor}.',
|
||||
},
|
||||
[JOIN_TEAM]: {
|
||||
id: t('last_users_message.joined_team.type'),
|
||||
id: 'last_users_message.joined_team.type',
|
||||
defaultMessage: '**joined the team**.',
|
||||
},
|
||||
[LEAVE_TEAM]: {
|
||||
id: t('last_users_message.left_team.type'),
|
||||
id: 'last_users_message.left_team.type',
|
||||
defaultMessage: '**left the team**.',
|
||||
},
|
||||
[REMOVE_FROM_TEAM]: {
|
||||
id: t('last_users_message.removed_from_team.type'),
|
||||
id: 'last_users_message.removed_from_team.type',
|
||||
defaultMessage: 'were **removed from the team**.',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Image} from 'expo-image';
|
||||
import React, {type ReactNode} from 'react';
|
||||
import React, {useCallback, type ReactNode} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native';
|
||||
|
||||
|
|
@ -12,8 +12,8 @@ import ProfilePicture from '@components/profile_picture';
|
|||
import {View as ViewConstant} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {openUserProfileModal} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {ensureString} from '@utils/types';
|
||||
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
|
@ -88,7 +88,7 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, location, post}:
|
|||
);
|
||||
}
|
||||
|
||||
const openUserProfile = preventDoubleTap(() => {
|
||||
const openUserProfile = usePreventDoubleTap(useCallback(() => {
|
||||
if (!author) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ const Avatar = ({author, enablePostIconOverride, isAutoReponse, location, post}:
|
|||
userIconOverride: propsIconUrl,
|
||||
usernameOverride: propsUsername,
|
||||
});
|
||||
});
|
||||
}, [author, intl, location, post.channelId, propsIconUrl, propsUsername, theme]));
|
||||
|
||||
let component = (
|
||||
<ProfilePicture
|
||||
|
|
|
|||
|
|
@ -81,11 +81,11 @@ const definedMessages = defineMessages({
|
|||
},
|
||||
messageOne: {
|
||||
id: 'post_body.check_for_out_of_channel_mentions.message.one',
|
||||
defaultMessage: '{username} was mentioned but is not in the channel. Would you like to ',
|
||||
defaultMessage: 'was mentioned but is not in the channel. Would you like to ',
|
||||
},
|
||||
messageMultiple: {
|
||||
id: 'post_body.check_for_out_of_channel_mentions.message.multiple',
|
||||
defaultMessage: '{usernames} were mentioned but they are not in the channel. Would you like to ',
|
||||
defaultMessage: 'were mentioned but they are not in the channel. Would you like to ',
|
||||
},
|
||||
outOfGroupsMessage: {
|
||||
id: 'post_body.check_for_out_of_channel_groups_mentions.message',
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import {handleBindingClick, postEphemeralCallResponseForPost} from '@actions/rem
|
|||
import {handleGotoLocation} from '@actions/remote/command';
|
||||
import {AppBindingLocations, AppCallResponseTypes} from '@constants/apps';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {showAppForm} from '@screens/navigation';
|
||||
import {createCallContext} from '@utils/apps';
|
||||
import {getStatusColors} from '@utils/message_attachment';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
import ButtonBindingText from './button_binding_text';
|
||||
|
|
@ -62,7 +62,7 @@ const ButtonBinding = ({currentTeamId, binding, post, teamID, theme}: Props) =>
|
|||
const serverUrl = useServerUrl();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(async () => {
|
||||
const onPress = usePreventDoubleTap(useCallback(async () => {
|
||||
if (pressed.current) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ const ButtonBinding = ({currentTeamId, binding, post, teamID, theme}: Props) =>
|
|||
postEphemeralCallResponseForPost(serverUrl, callResp, errorMessage, post);
|
||||
}
|
||||
}
|
||||
}), []);
|
||||
}, [binding, currentTeamId, intl, post, serverUrl, teamID]));
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import React, {useCallback, useRef} from 'react';
|
|||
|
||||
import {postActionWithCookie} from '@actions/remote/integrations';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {getStatusColors} from '@utils/message_attachment';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {secureGetFromRecord} from '@utils/types';
|
||||
|
||||
|
|
@ -56,13 +56,13 @@ const ActionButton = ({buttonColor, cookie, disabled, id, name, postId, theme}:
|
|||
let customButtonStyle;
|
||||
let customButtonTextStyle;
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(async () => {
|
||||
const onPress = usePreventDoubleTap(useCallback(async () => {
|
||||
if (!presssed.current) {
|
||||
presssed.current = true;
|
||||
await postActionWithCookie(serverUrl, postId, id, cookie || '');
|
||||
presssed.current = false;
|
||||
}
|
||||
}), [id, postId, cookie]);
|
||||
}, [serverUrl, postId, id, cookie]));
|
||||
|
||||
if (buttonColor) {
|
||||
const STATUS_COLORS = getStatusColors(theme);
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import {MAX_ALLOWED_REACTIONS} from '@constants/emoji';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import useDidUpdate from '@hooks/did_update';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {bottomSheetModalOptions, openAsBottomSheet, showModal, showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {getEmojiFirstAlias} from '@utils/emoji/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import Reaction from './reaction';
|
||||
|
|
@ -100,11 +100,11 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
return {reactionsByName, highlightedReactions};
|
||||
}, [reactions, currentUserId]);
|
||||
|
||||
const handleToggleReactionToPost = (emoji: string) => {
|
||||
const handleToggleReactionToPost = useCallback((emoji: string) => {
|
||||
toggleReaction(serverUrl, postId, emoji);
|
||||
};
|
||||
}, [postId, serverUrl]);
|
||||
|
||||
const handleAddReaction = useCallback(preventDoubleTap(() => {
|
||||
const handleAddReaction = usePreventDoubleTap(useCallback(() => {
|
||||
openAsBottomSheet({
|
||||
closeButtonId: 'close-add-reaction',
|
||||
screen: Screens.EMOJI_PICKER,
|
||||
|
|
@ -112,7 +112,7 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
title: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
|
||||
props: {onEmojiPress: handleToggleReactionToPost},
|
||||
});
|
||||
}), [formatMessage, theme]);
|
||||
}, [formatMessage, handleToggleReactionToPost, theme]));
|
||||
|
||||
const handleReactionPress = useCallback(async (emoji: string, remove: boolean) => {
|
||||
pressed.current = true;
|
||||
|
|
@ -123,7 +123,7 @@ const Reactions = ({currentUserId, canAddReaction, canRemoveReaction, disabled,
|
|||
}
|
||||
|
||||
pressed.current = false;
|
||||
}, [canRemoveReaction, canAddReaction, disabled]);
|
||||
}, [canRemoveReaction, disabled, canAddReaction, serverUrl, postId]);
|
||||
|
||||
const showReactionList = useCallback((initialEmoji: string) => {
|
||||
const screen = Screens.REACTIONS;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type PreHeaderProps = {
|
||||
|
|
@ -56,6 +56,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const messages = defineMessages({
|
||||
pinnedSaved: {
|
||||
id: 'mobile.post_pre_header.pinned_saved',
|
||||
defaultMessage: 'Pinned and Saved',
|
||||
},
|
||||
pinned: {
|
||||
id: 'mobile.post_pre_header.pinned',
|
||||
defaultMessage: 'Pinned',
|
||||
},
|
||||
saved: {
|
||||
id: 'mobile.post_pre_header.saved',
|
||||
defaultMessage: 'Saved',
|
||||
},
|
||||
});
|
||||
|
||||
const PreHeader = ({isConsecutivePost, isSaved, isPinned, skipSavedHeader, skipPinnedHeader}: PreHeaderProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -63,20 +78,11 @@ const PreHeader = ({isConsecutivePost, isSaved, isPinned, skipSavedHeader, skipP
|
|||
|
||||
let text;
|
||||
if (isPinnedAndSaved) {
|
||||
text = {
|
||||
id: t('mobile.post_pre_header.pinned_saved'),
|
||||
defaultMessage: 'Pinned and Saved',
|
||||
};
|
||||
text = messages.pinnedSaved;
|
||||
} else if (isPinned && !skipPinnedHeader) {
|
||||
text = {
|
||||
id: t('mobile.post_pre_header.pinned'),
|
||||
defaultMessage: 'Pinned',
|
||||
};
|
||||
text = messages.pinned;
|
||||
} else if (isSaved && !skipSavedHeader) {
|
||||
text = {
|
||||
id: t('mobile.post_pre_header.saved'),
|
||||
defaultMessage: 'Saved',
|
||||
};
|
||||
text = messages.saved;
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {type IntlShape, useIntl} from 'react-intl';
|
||||
import {defineMessages, type IntlShape, useIntl} from 'react-intl';
|
||||
import {type StyleProp, Text, type TextStyle, View, type ViewStyle} from 'react-native';
|
||||
|
||||
import Markdown from '@components/markdown';
|
||||
import {postTypeMessages} from '@components/post_list/combined_user_activity/messages';
|
||||
import {Post} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {getMarkdownTextStyles} from '@utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {secureGetFromRecord, ensureString} from '@utils/types';
|
||||
|
|
@ -94,6 +93,21 @@ const renderMessage = ({location, post, styles, intl, localeHolder, theme, value
|
|||
);
|
||||
};
|
||||
|
||||
const headerMessages = defineMessages({
|
||||
updatedFrom: {
|
||||
id: 'mobile.system_message.update_channel_header_message_and_forget.updated_from',
|
||||
defaultMessage: '{username} updated the channel header from: {oldHeader} to: {newHeader}',
|
||||
},
|
||||
updatedTo: {
|
||||
id: 'mobile.system_message.update_channel_header_message_and_forget.updated_to',
|
||||
defaultMessage: '{username} updated the channel header to: {newHeader}',
|
||||
},
|
||||
removed: {
|
||||
id: 'mobile.system_message.update_channel_header_message_and_forget.removed',
|
||||
defaultMessage: '{username} removed the channel header (was: {oldHeader})',
|
||||
},
|
||||
});
|
||||
|
||||
const renderHeaderChangeMessage = ({post, author, location, styles, intl, theme}: RenderersProps) => {
|
||||
let values;
|
||||
|
||||
|
|
@ -108,27 +122,18 @@ const renderHeaderChangeMessage = ({post, author, location, styles, intl, theme}
|
|||
|
||||
if (post.props?.new_header) {
|
||||
if (post.props?.old_header) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_header_message_and_forget.updated_from'),
|
||||
defaultMessage: '{username} updated the channel header from: {oldHeader} to: {newHeader}',
|
||||
};
|
||||
localeHolder = headerMessages.updatedFrom;
|
||||
|
||||
values = {username, oldHeader, newHeader};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
}
|
||||
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_header_message_and_forget.updated_to'),
|
||||
defaultMessage: '{username} updated the channel header to: {newHeader}',
|
||||
};
|
||||
localeHolder = headerMessages.updatedTo;
|
||||
|
||||
values = {username, oldHeader, newHeader};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
} else if (post.props?.old_header) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_header_message_and_forget.removed'),
|
||||
defaultMessage: '{username} removed the channel header (was: {oldHeader})',
|
||||
};
|
||||
localeHolder = headerMessages.removed;
|
||||
|
||||
values = {username, oldHeader, newHeader};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
|
|
@ -137,6 +142,21 @@ const renderHeaderChangeMessage = ({post, author, location, styles, intl, theme}
|
|||
return null;
|
||||
};
|
||||
|
||||
const purposeMessages = defineMessages({
|
||||
updatedFrom: {
|
||||
id: 'mobile.system_message.update_channel_purpose_message.updated_from',
|
||||
defaultMessage: '{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}',
|
||||
},
|
||||
updatedTo: {
|
||||
id: 'mobile.system_message.update_channel_purpose_message.updated_to',
|
||||
defaultMessage: '{username} updated the channel purpose to: {newPurpose}',
|
||||
},
|
||||
removed: {
|
||||
id: 'mobile.system_message.update_channel_purpose_message.removed',
|
||||
defaultMessage: '{username} removed the channel purpose (was: {oldPurpose})',
|
||||
},
|
||||
});
|
||||
|
||||
const renderPurposeChangeMessage = ({post, author, location, styles, intl, theme}: RenderersProps) => {
|
||||
let values;
|
||||
|
||||
|
|
@ -151,27 +171,18 @@ const renderPurposeChangeMessage = ({post, author, location, styles, intl, theme
|
|||
|
||||
if (newPurpose) {
|
||||
if (oldPurpose) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_purpose_message.updated_from'),
|
||||
defaultMessage: '{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}',
|
||||
};
|
||||
localeHolder = purposeMessages.updatedFrom;
|
||||
|
||||
values = {username, oldPurpose, newPurpose};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, skipMarkdown: true, theme});
|
||||
}
|
||||
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_purpose_message.updated_to'),
|
||||
defaultMessage: '{username} updated the channel purpose to: {newPurpose}',
|
||||
};
|
||||
localeHolder = purposeMessages.updatedTo;
|
||||
|
||||
values = {username, oldPurpose, newPurpose};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, skipMarkdown: true, theme});
|
||||
} else if (oldPurpose) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_purpose_message.removed'),
|
||||
defaultMessage: '{username} removed the channel purpose (was: {oldPurpose})',
|
||||
};
|
||||
localeHolder = purposeMessages.removed;
|
||||
|
||||
values = {username, oldPurpose, newPurpose};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, skipMarkdown: true, theme});
|
||||
|
|
@ -180,6 +191,13 @@ const renderPurposeChangeMessage = ({post, author, location, styles, intl, theme
|
|||
return null;
|
||||
};
|
||||
|
||||
const displaynameMessages = defineMessages({
|
||||
updatedFrom: {
|
||||
id: 'mobile.system_message.update_channel_displayname_message_and_forget.updated_from',
|
||||
defaultMessage: '{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}',
|
||||
},
|
||||
});
|
||||
|
||||
const renderDisplayNameChangeMessage = ({post, author, location, styles, intl, theme}: RenderersProps) => {
|
||||
const oldDisplayName = ensureString(post.props?.old_displayname);
|
||||
const newDisplayName = ensureString(post.props?.new_displayname);
|
||||
|
|
@ -189,41 +207,53 @@ const renderDisplayNameChangeMessage = ({post, author, location, styles, intl, t
|
|||
}
|
||||
|
||||
const username = renderUsername(author.username);
|
||||
const localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_displayname_message_and_forget.updated_from'),
|
||||
defaultMessage: '{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}',
|
||||
};
|
||||
const localeHolder = displaynameMessages.updatedFrom;
|
||||
|
||||
const values = {username, oldDisplayName, newDisplayName};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
};
|
||||
|
||||
const archivedMessages = defineMessages({
|
||||
archived: {
|
||||
id: 'mobile.system_message.channel_archived_message',
|
||||
defaultMessage: '{username} archived the channel',
|
||||
},
|
||||
});
|
||||
|
||||
const renderArchivedMessage = ({post, author, location, styles, intl, theme}: RenderersProps) => {
|
||||
const username = renderUsername(author?.username);
|
||||
const localeHolder = {
|
||||
id: t('mobile.system_message.channel_archived_message'),
|
||||
defaultMessage: '{username} archived the channel',
|
||||
};
|
||||
const localeHolder = archivedMessages.archived;
|
||||
|
||||
const values = {username};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
};
|
||||
|
||||
const unarchivedMessages = defineMessages({
|
||||
unarchived: {
|
||||
id: 'mobile.system_message.channel_unarchived_message',
|
||||
defaultMessage: '{username} unarchived the channel',
|
||||
},
|
||||
});
|
||||
|
||||
const renderUnarchivedMessage = ({post, author, location, styles, intl, theme}: RenderersProps) => {
|
||||
if (!author?.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(author.username);
|
||||
const localeHolder = {
|
||||
id: t('mobile.system_message.channel_unarchived_message'),
|
||||
defaultMessage: '{username} unarchived the channel',
|
||||
};
|
||||
const localeHolder = unarchivedMessages.unarchived;
|
||||
|
||||
const values = {username};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
};
|
||||
|
||||
const addGuestToChannelMessages = defineMessages({
|
||||
added: {
|
||||
id: 'api.channel.add_guest.added',
|
||||
defaultMessage: '{addedUsername} added to the channel as a guest by {username}.',
|
||||
},
|
||||
});
|
||||
|
||||
const renderAddGuestToChannelMessage = ({post, location, styles, intl, theme}: RenderersProps, hideGuestTags: boolean) => {
|
||||
const username = renderUsername(ensureString(post.props?.username));
|
||||
const addedUsername = renderUsername(ensureString(post.props?.addedUsername));
|
||||
|
|
@ -232,25 +262,26 @@ const renderAddGuestToChannelMessage = ({post, location, styles, intl, theme}: R
|
|||
return null;
|
||||
}
|
||||
|
||||
const localeHolder = hideGuestTags ? postTypeMessages[Post.POST_TYPES.ADD_TO_CHANNEL].one : {
|
||||
id: t('api.channel.add_guest.added'),
|
||||
defaultMessage: '{addedUsername} added to the channel as a guest by {username}.',
|
||||
};
|
||||
const localeHolder = hideGuestTags ? postTypeMessages[Post.POST_TYPES.ADD_TO_CHANNEL].one : addGuestToChannelMessages.added;
|
||||
|
||||
const values = hideGuestTags ? {firstUser: addedUsername, actor: username} : {username, addedUsername};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
};
|
||||
|
||||
const guestJoinChannelMessages = defineMessages({
|
||||
joined: {
|
||||
id: 'api.channel.guest_join_channel.post_and_forget',
|
||||
defaultMessage: '{username} joined the channel as a guest.',
|
||||
},
|
||||
});
|
||||
|
||||
const renderGuestJoinChannelMessage = ({post, styles, location, intl, theme}: RenderersProps, hideGuestTags: boolean) => {
|
||||
const username = renderUsername(ensureString(post.props?.username));
|
||||
if (!username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const localeHolder = hideGuestTags ? postTypeMessages[Post.POST_TYPES.JOIN_CHANNEL].one : {
|
||||
id: t('api.channel.guest_join_channel.post_and_forget'),
|
||||
defaultMessage: '{username} joined the channel as a guest.',
|
||||
};
|
||||
const localeHolder = hideGuestTags ? postTypeMessages[Post.POST_TYPES.JOIN_CHANNEL].one : guestJoinChannelMessages.joined;
|
||||
|
||||
const values = hideGuestTags ? {firstUser: username} : {username};
|
||||
return renderMessage({post, styles, intl, location, localeHolder, values, theme});
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import {Screens} from '@constants';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {bottomSheetModalOptions, showModal, showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -65,14 +65,14 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
|
|||
const isTablet = useIsTablet();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const onHandleSavePress = useCallback(preventDoubleTap(() => {
|
||||
const onHandleSavePress = usePreventDoubleTap(useCallback(() => {
|
||||
if (rootPost?.id) {
|
||||
const remoteAction = isSaved ? deleteSavedPost : savePostPreference;
|
||||
remoteAction(serverUrl, rootPost.id);
|
||||
}
|
||||
}), [isSaved, rootPost, serverUrl]);
|
||||
}, [isSaved, rootPost, serverUrl]));
|
||||
|
||||
const showPostOptions = useCallback(preventDoubleTap(() => {
|
||||
const showPostOptions = usePreventDoubleTap(useCallback(() => {
|
||||
Keyboard.dismiss();
|
||||
if (rootPost?.id) {
|
||||
const passProps = {sourceScreen: Screens.THREAD, post: rootPost, showAddReaction: true};
|
||||
|
|
@ -84,7 +84,7 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
|
|||
showModalOverCurrentContext(Screens.POST_OPTIONS, passProps, bottomSheetModalOptions(theme));
|
||||
}
|
||||
}
|
||||
}), [rootPost]);
|
||||
}, [intl, isTablet, rootPost, theme]));
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
const container: StyleProp<ViewStyle> = [styles.container];
|
||||
|
|
@ -95,7 +95,7 @@ const ThreadOverview = ({isSaved, repliesCount, rootPost, style, testID}: Props)
|
|||
}
|
||||
container.push(style);
|
||||
return container;
|
||||
}, [repliesCount, style]);
|
||||
}, [repliesCount, style, styles.container]);
|
||||
|
||||
const saveButtonTestId = isSaved ? `${testID}.unsave.button` : `${testID}.save.button`;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Image, type ImageSource, type ImageStyle} from 'expo-image';
|
||||
import React, {useCallback} from 'react';
|
||||
import React from 'react';
|
||||
import {type StyleProp, Text, type TextStyle, TouchableHighlight, View, type ViewStyle} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {isValidUrl} from '@utils/url';
|
||||
|
|
@ -86,7 +86,7 @@ const SlideUpPanelItem = ({
|
|||
const {image: leftImage, iconStyle: leftIconStyle} = useImageAndStyle(leftIcon, leftImageStyles, leftIconStyles, destructive);
|
||||
const {image: rightImage, iconStyle: rightIconStyle} = useImageAndStyle(rightIcon, rightImageStyles, rightIconStyles, destructive);
|
||||
|
||||
const handleOnPress = useCallback(preventDoubleTap(onPress, 500), []);
|
||||
const handleOnPress = usePreventDoubleTap(onPress);
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {General} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -28,37 +28,53 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const messages = defineMessages({
|
||||
setAway: {
|
||||
id: 'status_dropdown.set_away',
|
||||
defaultMessage: 'Away',
|
||||
},
|
||||
setDnd: {
|
||||
id: 'status_dropdown.set_dnd',
|
||||
defaultMessage: 'Do Not Disturb',
|
||||
},
|
||||
setOnline: {
|
||||
id: 'status_dropdown.set_online',
|
||||
defaultMessage: 'Online',
|
||||
},
|
||||
setOffline: {
|
||||
id: 'status_dropdown.set_offline',
|
||||
defaultMessage: 'Offline',
|
||||
},
|
||||
setOoo: {
|
||||
id: 'status_dropdown.set_ooo',
|
||||
defaultMessage: 'Out Of Office',
|
||||
},
|
||||
});
|
||||
const StatusLabel = ({status = General.OFFLINE, labelStyle}: StatusLabelProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let i18nId = t('status_dropdown.set_offline');
|
||||
let defaultMessage = 'Offline';
|
||||
let message = messages.setOffline;
|
||||
|
||||
switch (status) {
|
||||
case General.AWAY:
|
||||
i18nId = t('status_dropdown.set_away');
|
||||
defaultMessage = 'Away';
|
||||
message = messages.setAway;
|
||||
break;
|
||||
case General.DND:
|
||||
i18nId = t('status_dropdown.set_dnd');
|
||||
defaultMessage = 'Do Not Disturb';
|
||||
message = messages.setDnd;
|
||||
break;
|
||||
case General.ONLINE:
|
||||
i18nId = t('status_dropdown.set_online');
|
||||
defaultMessage = 'Online';
|
||||
message = messages.setOnline;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status === General.OUT_OF_OFFICE) {
|
||||
i18nId = t('status_dropdown.set_ooo');
|
||||
defaultMessage = 'Out Of Office';
|
||||
message = messages.setOoo;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedText
|
||||
id={i18nId}
|
||||
defaultMessage={defaultMessage}
|
||||
{...message}
|
||||
style={[style.label, labelStyle]}
|
||||
testID={`user_status.label.${status}`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import CompassIcon from '@components/compass_icon';
|
|||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {showModal} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
|
@ -40,7 +40,7 @@ export default function AddTeam() {
|
|||
const styles = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(() => {
|
||||
const onPress = usePreventDoubleTap(useCallback(() => {
|
||||
const title = intl.formatMessage({id: 'mobile.add_team.join_team', defaultMessage: 'Join Another Team'});
|
||||
const closeButton = CompassIcon.getImageSourceSync('close', 24, theme.sidebarHeaderTextColor);
|
||||
const closeButtonId = 'close-join-team';
|
||||
|
|
@ -54,7 +54,7 @@ export default function AddTeam() {
|
|||
},
|
||||
};
|
||||
showModal(Screens.JOIN_TEAM, title, {closeButtonId}, options);
|
||||
}), [intl]);
|
||||
}, [intl, theme.sidebarHeaderTextColor]));
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {type StyleProp, View, type ViewStyle} from 'react-native';
|
|||
|
||||
import Emoji from '@components/emoji';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
|
||||
import SkinnedEmoji from './skinned_emoji';
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ const CATEGORIES_WITH_SKINS = ['people-body'];
|
|||
const hitSlop = {top: 10, bottom: 10, left: 10, right: 10};
|
||||
|
||||
const TouchableEmoji = ({category, name, onEmojiPress, size = 30, style}: Props) => {
|
||||
const onPress = useCallback(preventDoubleTap(() => onEmojiPress(name)), []);
|
||||
const onPress = usePreventDoubleTap(useCallback(() => onEmojiPress(name), [name, onEmojiPress]));
|
||||
|
||||
if (category && CATEGORIES_WITH_SKINS.includes(category)) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import {type StyleProp, View, type ViewStyle} from 'react-native';
|
|||
import Emoji from '@components/emoji';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useEmojiSkinTone} from '@hooks/emoji_category_bar';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {skinCodes} from '@utils/emoji';
|
||||
import {isValidNamedEmoji} from '@utils/emoji/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
|
|
@ -30,9 +30,9 @@ const SkinnedEmoji = ({name, onEmojiPress, size = 30, style}: Props) => {
|
|||
return skinnedEmoji;
|
||||
}, [name, skinTone]);
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(() => {
|
||||
const onPress = usePreventDoubleTap(useCallback(() => {
|
||||
onEmojiPress(emojiName);
|
||||
}), [emojiName]);
|
||||
}, [emojiName, onEmojiPress]));
|
||||
|
||||
return (
|
||||
<View
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useLayoutEffect, useMemo, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {
|
||||
InteractionManager,
|
||||
Platform,
|
||||
|
|
@ -17,7 +17,6 @@ import TutorialLongPress from '@components/tutorial_highlight/long_press';
|
|||
import UserItem from '@components/user_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -69,6 +68,17 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
|
||||
const DEFAULT_ICON_OPACITY = 0.32;
|
||||
|
||||
const messages = defineMessages({
|
||||
admin: {
|
||||
id: 'mobile.manage_members.admin',
|
||||
defaultMessage: 'Admin',
|
||||
},
|
||||
member: {
|
||||
id: 'mobile.manage_members.member',
|
||||
defaultMessage: 'Member',
|
||||
},
|
||||
});
|
||||
|
||||
function UserListRow({
|
||||
id,
|
||||
includeMargin,
|
||||
|
|
@ -124,15 +134,13 @@ function UserListRow({
|
|||
}
|
||||
|
||||
const color = changeOpacity(theme.centerChannelColor, 0.64);
|
||||
const i18nId = isChannelAdmin ? t('mobile.manage_members.admin') : t('mobile.manage_members.member');
|
||||
const defaultMessage = isChannelAdmin ? 'Admin' : 'Member';
|
||||
const message = isChannelAdmin ? messages.admin : messages.member;
|
||||
|
||||
return (
|
||||
<View style={style.selectorManage}>
|
||||
<FormattedText
|
||||
id={i18nId}
|
||||
{...message}
|
||||
style={style.manageText}
|
||||
defaultMessage={defaultMessage}
|
||||
/>
|
||||
<CompassIcon
|
||||
name={'chevron-down'}
|
||||
|
|
@ -141,7 +149,7 @@ function UserListRow({
|
|||
/>
|
||||
</View>
|
||||
);
|
||||
}, [isChannelAdmin, showManageMode, theme]);
|
||||
}, [isChannelAdmin, isMyUser, showManageMode, style.manageText, style.selectorManage, theme.centerChannelColor]);
|
||||
|
||||
const onLayout = useCallback(() => {
|
||||
if (highlight && !tutorialWatched) {
|
||||
|
|
@ -153,7 +161,7 @@ function UserListRow({
|
|||
setShowTutorial(true);
|
||||
});
|
||||
}
|
||||
}, [showTutorial]);
|
||||
}, [highlight, isTablet, tutorialWatched]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (showTutorial && !tutorialShown.current) {
|
||||
|
|
@ -177,7 +185,7 @@ function UserListRow({
|
|||
/>
|
||||
</View>
|
||||
);
|
||||
}, [selectable, disabled, selected, theme]);
|
||||
}, [selectable, selected, theme.buttonBg, theme.centerChannelColor, style.selector]);
|
||||
|
||||
const userItemTestID = `${testID}.${id}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {t} from '@i18n';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
export enum CustomStatusDurationEnum {
|
||||
DONT_CLEAR = '',
|
||||
|
|
@ -23,36 +23,36 @@ const {
|
|||
DATE_AND_TIME,
|
||||
} = CustomStatusDurationEnum;
|
||||
|
||||
export const CST: {[key in CustomStatusDuration]: {id: string; defaultMessage: string}} = {
|
||||
export const CST: {[key in CustomStatusDuration]: {id: string; defaultMessage: string}} = defineMessages({
|
||||
[DONT_CLEAR]: {
|
||||
id: t('custom_status.expiry_dropdown.dont_clear'),
|
||||
id: 'custom_status.expiry_dropdown.dont_clear',
|
||||
defaultMessage: "Don't clear",
|
||||
},
|
||||
[THIRTY_MINUTES]: {
|
||||
id: t('custom_status.expiry_dropdown.thirty_minutes'),
|
||||
id: 'custom_status.expiry_dropdown.thirty_minutes',
|
||||
defaultMessage: '30 minutes',
|
||||
},
|
||||
[ONE_HOUR]: {
|
||||
id: t('custom_status.expiry_dropdown.one_hour'),
|
||||
id: 'custom_status.expiry_dropdown.one_hour',
|
||||
defaultMessage: '1 hour',
|
||||
},
|
||||
[FOUR_HOURS]: {
|
||||
id: t('custom_status.expiry_dropdown.four_hours'),
|
||||
id: 'custom_status.expiry_dropdown.four_hours',
|
||||
defaultMessage: '4 hours',
|
||||
},
|
||||
[TODAY]: {
|
||||
id: t('custom_status.expiry_dropdown.today'),
|
||||
id: 'custom_status.expiry_dropdown.today',
|
||||
defaultMessage: 'Today',
|
||||
},
|
||||
[THIS_WEEK]: {
|
||||
id: t('custom_status.expiry_dropdown.this_week'),
|
||||
id: 'custom_status.expiry_dropdown.this_week',
|
||||
defaultMessage: 'This week',
|
||||
},
|
||||
[DATE_AND_TIME]: {
|
||||
id: t('custom_status.expiry_dropdown.date_and_time'),
|
||||
id: 'custom_status.expiry_dropdown.date_and_time',
|
||||
defaultMessage: 'Date and Time',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const CUSTOM_STATUS_TEXT_CHARACTER_LIMIT = 100;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|||
|
||||
import {Screens} from '@constants';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ export const useExtraKeyboardContext = (): ExtraKeyboardContextProps|undefined =
|
|||
export const useHideExtraKeyboardIfNeeded = (callback: (...args: any) => void, dependencies: React.DependencyList = []) => {
|
||||
const keyboardContext = useExtraKeyboardContext();
|
||||
|
||||
return useCallback(preventDoubleTap((...args: any) => {
|
||||
return usePreventDoubleTap(useCallback((...args: any) => {
|
||||
if (keyboardContext?.isExtraKeyboardVisible) {
|
||||
keyboardContext.hideExtraKeyboard();
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ export const useHideExtraKeyboardIfNeeded = (callback: (...args: any) => void, d
|
|||
}
|
||||
|
||||
callback(...args);
|
||||
}), [keyboardContext, ...dependencies]);
|
||||
}, [keyboardContext, ...dependencies]));
|
||||
};
|
||||
|
||||
const ExtraKeyboardComponent = () => {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export function privateChannelJoinPrompt(displayName: string, intl: IntlShape):
|
|||
}),
|
||||
intl.formatMessage({
|
||||
id: 'permalink.show_dialog_warn.description',
|
||||
defaultMessage: 'You are about to join {channel} without explicitly being added by the Channel Admin. Are you sure you wish to join this private channel?',
|
||||
defaultMessage: 'You are about to join {channel} without explicitly being added by the channel admin. Are you sure you wish to join this private channel?',
|
||||
}, {
|
||||
channel: displayName,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -245,7 +245,3 @@ export function getLocalizedMessage(lang: string, id: string, defaultMessage?: s
|
|||
|
||||
return translations[id] || defaultMessage || '';
|
||||
}
|
||||
|
||||
export function t(v: string): string {
|
||||
return v;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import RNUtils from '@mattermost/rnutils/src';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {AppState, DeviceEventEmitter, Platform, type EmitterSubscription} from 'react-native';
|
||||
import {
|
||||
Notification,
|
||||
|
|
@ -23,7 +24,7 @@ import {backgroundNotification, openNotification} from '@actions/remote/notifica
|
|||
import {isCallsStartedMessage} from '@calls/utils';
|
||||
import {Device, Events, Navigation, PushNotification, Screens} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {DEFAULT_LOCALE, getLocalizedMessage, t} from '@i18n';
|
||||
import {DEFAULT_LOCALE, getLocalizedMessage} from '@i18n';
|
||||
import {getServerDisplayName} from '@queries/app/servers';
|
||||
import {getCurrentChannelId} from '@queries/servers/system';
|
||||
import {getIsCRTEnabled, getThreadById} from '@queries/servers/thread';
|
||||
|
|
@ -35,6 +36,21 @@ import {isMainActivity, isTablet} from '@utils/helpers';
|
|||
import {logDebug, logInfo} from '@utils/log';
|
||||
import {convertToNotificationData} from '@utils/notification';
|
||||
|
||||
const messages = defineMessages({
|
||||
replyTitle: {
|
||||
id: 'mobile.push_notification_reply.title',
|
||||
defaultMessage: 'Reply',
|
||||
},
|
||||
replyButton: {
|
||||
id: 'mobile.push_notification_reply.button',
|
||||
defaultMessage: 'Send',
|
||||
},
|
||||
replyPlaceholder: {
|
||||
id: 'mobile.push_notification_reply.placeholder',
|
||||
defaultMessage: 'Write a reply...',
|
||||
},
|
||||
});
|
||||
|
||||
class PushNotificationsSingleton {
|
||||
configured = false;
|
||||
subscriptions?: EmitterSubscription[];
|
||||
|
|
@ -64,9 +80,9 @@ class PushNotificationsSingleton {
|
|||
}
|
||||
|
||||
createReplyCategory = () => {
|
||||
const replyTitle = getLocalizedMessage(DEFAULT_LOCALE, t('mobile.push_notification_reply.title'));
|
||||
const replyButton = getLocalizedMessage(DEFAULT_LOCALE, t('mobile.push_notification_reply.button'));
|
||||
const replyPlaceholder = getLocalizedMessage(DEFAULT_LOCALE, t('mobile.push_notification_reply.placeholder'));
|
||||
const replyTitle = getLocalizedMessage(DEFAULT_LOCALE, messages.replyTitle.id);
|
||||
const replyButton = getLocalizedMessage(DEFAULT_LOCALE, messages.replyButton.id);
|
||||
const replyPlaceholder = getLocalizedMessage(DEFAULT_LOCALE, messages.replyPlaceholder.id);
|
||||
const replyTextInput: NotificationTextInput = {buttonTitle: replyButton, placeholder: replyPlaceholder};
|
||||
const replyAction = new NotificationAction(PushNotification.REPLY_ACTION, 'background', replyTitle, true, replyTextInput);
|
||||
return new NotificationCategory(PushNotification.CATEGORY, [replyAction]);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import RNUtils, {type SplitViewResult} from '@mattermost/rnutils';
|
||||
import {defineMessages} from 'react-intl';
|
||||
import {Alert, DeviceEventEmitter, Linking, NativeEventEmitter} from 'react-native';
|
||||
import semver from 'semver';
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ import {switchToChannelById} from '@actions/remote/channel';
|
|||
import {Device, Events, Sso} from '@constants';
|
||||
import {MIN_REQUIRED_VERSION} from '@constants/supported_server';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
|
||||
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
|
||||
import {getServerCredentials} from '@init/credentials';
|
||||
import {getActiveServerUrl} from '@queries/app/servers';
|
||||
import {queryTeamDefaultChannel} from '@queries/servers/channel';
|
||||
|
|
@ -23,6 +24,21 @@ type LinkingCallbackArg = {url: string};
|
|||
|
||||
const splitViewEmitter = new NativeEventEmitter(RNUtils);
|
||||
|
||||
const messages = defineMessages({
|
||||
serverUpgradeTitle: {
|
||||
id: 'mobile.server_upgrade.title',
|
||||
defaultMessage: 'Server upgrade required',
|
||||
},
|
||||
serverUpgradeDescription: {
|
||||
id: 'mobile.server_upgrade.description',
|
||||
defaultMessage: '\nA server upgrade is required to use the Mattermost app. Please ask your System Administrator for details.\n',
|
||||
},
|
||||
serverUpgradeButton: {
|
||||
id: 'mobile.server_upgrade.button',
|
||||
defaultMessage: 'OK',
|
||||
},
|
||||
});
|
||||
|
||||
class GlobalEventHandlerSingleton {
|
||||
JavascriptAndNativeErrorHandler: jsAndNativeErrorHandler | undefined;
|
||||
|
||||
|
|
@ -59,10 +75,10 @@ class GlobalEventHandlerSingleton {
|
|||
if (version) {
|
||||
if (semver.valid(version) && semver.lt(version, MIN_REQUIRED_VERSION)) {
|
||||
Alert.alert(
|
||||
translations[t('mobile.server_upgrade.title')],
|
||||
translations[t('mobile.server_upgrade.description')],
|
||||
translations[messages.serverUpgradeTitle.id],
|
||||
translations[messages.serverUpgradeDescription.id],
|
||||
[{
|
||||
text: translations[t('mobile.server_upgrade.button')],
|
||||
text: translations[messages.serverUpgradeButton.id],
|
||||
onPress: () => this.serverUpgradeNeeded(serverUrl),
|
||||
}],
|
||||
{cancelable: false},
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ import {Alert, type AppStateStatus} from 'react-native';
|
|||
import {switchToServer} from '@actions/app/server';
|
||||
import {logout} from '@actions/remote/session';
|
||||
import {Screens} from '@constants';
|
||||
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
|
||||
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
|
||||
import {getServerCredentials} from '@init/credentials';
|
||||
import TestHelper from '@test/test_helper';
|
||||
import {toMilliseconds} from '@utils/datetime';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
import SecurityManager from '.';
|
||||
import SecurityManager, {exportsForTesting} from '.';
|
||||
|
||||
const messages = exportsForTesting.messages;
|
||||
|
||||
jest.mock('@mattermost/react-native-emm', () => ({
|
||||
isDeviceSecured: jest.fn(),
|
||||
|
|
@ -442,7 +444,7 @@ describe('SecurityManager', () => {
|
|||
const translations = getTranslations(DEFAULT_LOCALE);
|
||||
const buttons = await SecurityManager.buildAlertOptions('server-12', translations);
|
||||
expect(buttons.length).toBeGreaterThan(0);
|
||||
const logoutButton = buttons.find((button) => button.text === translations[t('mobile.managed.logout')]);
|
||||
const logoutButton = buttons.find((button) => button.text === translations[messages.logout.id]);
|
||||
expect(logoutButton).toBeDefined();
|
||||
logoutButton?.onPress?.();
|
||||
expect(logout).toHaveBeenCalledWith('server-12', undefined);
|
||||
|
|
@ -458,8 +460,8 @@ describe('SecurityManager', () => {
|
|||
await SecurityManager.showDeviceNotTrustedAlert(server, siteName, translations);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', siteName),
|
||||
translations[t('mobile.managed.jailbreak')].replace('{vendor}', siteName),
|
||||
translations[messages.blocked_by.id].replace('{vendor}', siteName),
|
||||
translations[messages.jailbreak.id].replace('{vendor}', siteName),
|
||||
expect.any(Array),
|
||||
{cancelable: false},
|
||||
);
|
||||
|
|
@ -475,8 +477,8 @@ describe('SecurityManager', () => {
|
|||
await SecurityManager.showBiometricFailureAlert(server, false, siteName, translations);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', siteName),
|
||||
translations[t('mobile.managed.biometric_failed')],
|
||||
translations[messages.blocked_by.id].replace('{vendor}', siteName),
|
||||
translations[messages.biometric_failed.id],
|
||||
expect.any(Array),
|
||||
{cancelable: false},
|
||||
);
|
||||
|
|
@ -495,8 +497,8 @@ describe('SecurityManager', () => {
|
|||
expect(result).toBe(true);
|
||||
await TestHelper.wait(300);
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
translations[t('mobile.managed.blocked_by')].replace('{vendor}', siteName),
|
||||
translations[t('mobile.managed.jailbreak')].replace('{vendor}', siteName),
|
||||
translations[messages.blocked_by.id].replace('{vendor}', siteName),
|
||||
translations[messages.jailbreak.id].replace('{vendor}', siteName),
|
||||
expect.any(Array),
|
||||
{cancelable: false},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -513,3 +513,7 @@ class SecurityManagerSingleton {
|
|||
|
||||
const SecurityManager = new SecurityManagerSingleton();
|
||||
export default SecurityManager;
|
||||
|
||||
export const exportsForTesting = {
|
||||
messages,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -811,7 +811,7 @@ describe('Actions.Calls', () => {
|
|||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Error',
|
||||
'You don\'t have permission to end the call. Please ask the call owner to end the call.',
|
||||
'You don\'t have permission to end the call. Please ask the call host to end the call.',
|
||||
);
|
||||
expect(mockClient.endCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -399,8 +399,8 @@ export const getEndCallMessage = async (serverUrl: string, channelId: string, cu
|
|||
|
||||
msg = intl.formatMessage({
|
||||
id: 'mobile.calls_end_msg_channel',
|
||||
defaultMessage: 'Are you sure you want to end a call with {numSessions} participants in {displayName}?',
|
||||
}, {numSessions, displayName: channel.displayName});
|
||||
defaultMessage: 'Are you sure you want to end a call with {numParticipants} participants in {displayName}?',
|
||||
}, {numParticipants: numSessions, displayName: channel.displayName});
|
||||
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
const otherID = getUserIdFromChannelName(currentUserId, channel.name);
|
||||
|
|
@ -615,7 +615,7 @@ const handleEndCall = async (serverUrl: string, channelId: string, currentUserId
|
|||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.calls_end_permission_msg',
|
||||
defaultMessage: 'You don\'t have permission to end the call. Please ask the call owner to end the call.',
|
||||
defaultMessage: 'You don\'t have permission to end the call. Please ask the call host to end the call.',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ describe('alerts', () => {
|
|||
|
||||
expect(mockAlert).toHaveBeenCalledWith(
|
||||
'You are recording',
|
||||
'Consider letting everyone know that this meeting is being recorded.',
|
||||
'You are recording this meeting. Consider letting everyone know that this meeting is being recorded.',
|
||||
[{
|
||||
text: 'Dismiss',
|
||||
}],
|
||||
|
|
@ -139,7 +139,7 @@ describe('alerts', () => {
|
|||
|
||||
expect(mockAlert).toHaveBeenCalledWith(
|
||||
'You are recording',
|
||||
'Consider letting everyone know that this meeting is being recorded.',
|
||||
'You are recording this meeting. Consider letting everyone know that this meeting is being recorded.',
|
||||
[{
|
||||
text: 'Dismiss',
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export const leaveAndJoinWithAlert = async (
|
|||
}, {leaveChannelName, joinChannelName});
|
||||
if (newCall) {
|
||||
joinMessage = formatMessage({
|
||||
id: 'mobile.leave_and_join_message',
|
||||
id: 'mobile.leave_and_create_message',
|
||||
defaultMessage: 'You are already on a channel call in ~{leaveChannelName}. Do you want to leave your current call and start a new call in ~{joinChannelName}?',
|
||||
}, {leaveChannelName, joinChannelName});
|
||||
}
|
||||
|
|
@ -268,7 +268,7 @@ export const recordingAlert = (isHost: boolean, transcriptionsEnabled: boolean,
|
|||
});
|
||||
const hostMessage = formatMessage({
|
||||
id: 'mobile.calls_host_rec',
|
||||
defaultMessage: 'Consider letting everyone know that this meeting is being recorded.',
|
||||
defaultMessage: 'You are recording this meeting. Consider letting everyone know that this meeting is being recorded.',
|
||||
});
|
||||
|
||||
const participantTitle = formatMessage({
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {Pressable, type StyleProp, Text, type TextStyle, type ViewStyle} from 'r
|
|||
import {setSpeakerphoneOn} from '@calls/actions';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
||||
import {messages} from './messages';
|
||||
|
||||
import type {CurrentCall} from '@calls/types/calls';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -19,7 +21,7 @@ type Props = {
|
|||
|
||||
export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, currentCall}: Props) => {
|
||||
const intl = useIntl();
|
||||
const speakerLabel = intl.formatMessage({id: 'mobile.calls_speaker', defaultMessage: 'SpeakerPhone'});
|
||||
const speakerLabel = intl.formatMessage(messages.speaker);
|
||||
|
||||
const toggleSpeakerPhone = useCallback(() => {
|
||||
setSpeakerphoneOn(!currentCall?.speakerphoneOn);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import {bottomSheet, dismissBottomSheet} from '@screens/navigation';
|
|||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import {messages} from './messages';
|
||||
|
||||
type Props = {
|
||||
pressableStyle: StyleProp<ViewStyle>;
|
||||
iconStyle: StyleProp<TextStyle>;
|
||||
|
|
@ -36,7 +38,7 @@ export const AudioDeviceButton = ({pressableStyle, iconStyle, buttonTextStyle, c
|
|||
const audioDeviceInfo = currentCall.audioDeviceInfo;
|
||||
const phoneLabel = intl.formatMessage({id: 'mobile.calls_phone', defaultMessage: 'Phone'});
|
||||
const tabletLabel = intl.formatMessage({id: 'mobile.calls_tablet', defaultMessage: 'Tablet'});
|
||||
const speakerLabel = intl.formatMessage({id: 'mobile.calls_speaker', defaultMessage: 'SpeakerPhone'});
|
||||
const speakerLabel = intl.formatMessage(messages.speaker);
|
||||
const bluetoothLabel = intl.formatMessage({id: 'mobile.calls_bluetooth', defaultMessage: 'Bluetooth'});
|
||||
const headsetLabel = intl.formatMessage({id: 'mobile.calls_headset', defaultMessage: 'Headset'});
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {useTryCallsFunction} from '@calls/hooks';
|
|||
import {getCallsConfig} from '@calls/state';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
|
||||
interface Props {
|
||||
channelId: string;
|
||||
|
|
@ -34,9 +34,11 @@ const ChannelInfoEnableCalls = ({channelId, enabled}: Props) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const onPress = usePreventDoubleTap(tryOnPress);
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
action={preventDoubleTap(tryOnPress)}
|
||||
action={onPress}
|
||||
label={(enabled ? disableText : enableText) + msgPostfix}
|
||||
icon='phone'
|
||||
type='default'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {useTryCallsFunction} from '@calls/hooks';
|
|||
import Loading from '@components/loading';
|
||||
import OptionBox, {OPTIONS_HEIGHT} from '@components/option_box';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -84,9 +84,24 @@ const ChannelInfoStartButton = ({
|
|||
setConnecting(false);
|
||||
dismissChannelInfo();
|
||||
}
|
||||
}, [isLimitRestricted, alreadyInCall, dismissChannelInfo, intl, serverUrl, channelId, isACallInCurrentChannel, otherParticipants]);
|
||||
}, [
|
||||
alreadyInCall,
|
||||
isLimitRestricted,
|
||||
intl,
|
||||
otherParticipants,
|
||||
isAdmin,
|
||||
isHost,
|
||||
serverUrl,
|
||||
channelId,
|
||||
dismissChannelInfo,
|
||||
limitRestrictedInfo,
|
||||
isACallInCurrentChannel,
|
||||
joining,
|
||||
starting,
|
||||
]);
|
||||
|
||||
const [tryJoin, msgPostfix] = useTryCallsFunction(toggleJoinLeave);
|
||||
const onPress = usePreventDoubleTap(tryJoin);
|
||||
|
||||
const joinText = intl.formatMessage({id: 'mobile.calls_join_call', defaultMessage: 'Join call'});
|
||||
const startText = intl.formatMessage({id: 'mobile.calls_start_call', defaultMessage: 'Start call'});
|
||||
|
|
@ -108,7 +123,7 @@ const ChannelInfoStartButton = ({
|
|||
|
||||
return (
|
||||
<OptionBox
|
||||
onPress={preventDoubleTap(tryJoin)}
|
||||
onPress={onPress}
|
||||
text={text}
|
||||
iconName={icon}
|
||||
activeText={text}
|
||||
|
|
|
|||
11
app/products/calls/components/messages.tsx
Normal file
11
app/products/calls/components/messages.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
export const messages = defineMessages({
|
||||
speaker: {
|
||||
id: 'mobile.calls_speaker',
|
||||
defaultMessage: 'Speaker',
|
||||
},
|
||||
});
|
||||
|
|
@ -236,7 +236,7 @@ function AppsFormComponent({
|
|||
case AppCallResponseTypes.NAVIGATE:
|
||||
updateErrors([], undefined, intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_type',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}.',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}',
|
||||
}, {
|
||||
type: callResponse.type,
|
||||
}));
|
||||
|
|
@ -340,7 +340,7 @@ function AppsFormComponent({
|
|||
const errorResponse = res.error;
|
||||
const errMsg = errorResponse.text || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
});
|
||||
setErrors({[field.name]: errMsg});
|
||||
return [];
|
||||
|
|
@ -357,7 +357,7 @@ function AppsFormComponent({
|
|||
case AppCallResponseTypes.NAVIGATE: {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_type',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}.',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}',
|
||||
}, {
|
||||
type: callResp.type,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ function AppsFormContainer({
|
|||
case AppCallResponseTypes.NAVIGATE:
|
||||
return {error: makeCallErrorResponse(makeErrorMsg(intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_type',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}.',
|
||||
defaultMessage: 'App response type was not expected. Response type: {type}',
|
||||
}, {
|
||||
type: callResp.type,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
import {fetchChannelCreator} from '@actions/remote/channel';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {General, Permissions} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {hasPermission} from '@utils/role';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
|
@ -55,6 +54,33 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const messages = defineMessages({
|
||||
publicChannel: {
|
||||
id: 'intro.public_channel',
|
||||
defaultMessage: 'Public Channel',
|
||||
},
|
||||
privateChannel: {
|
||||
id: 'intro.private_channel',
|
||||
defaultMessage: 'Private Channel',
|
||||
},
|
||||
welcomePublic: {
|
||||
id: 'intro.welcome.public',
|
||||
defaultMessage: 'Add some more team members to the channel or start a conversation below.',
|
||||
},
|
||||
welcomePrivate: {
|
||||
id: 'intro.welcome.private',
|
||||
defaultMessage: 'Only invited members can see messages posted in this private channel.',
|
||||
},
|
||||
welcome: {
|
||||
id: 'intro.welcome',
|
||||
defaultMessage: 'Welcome to {displayName} channel.',
|
||||
},
|
||||
createdBy: {
|
||||
id: 'intro.created_by',
|
||||
defaultMessage: 'created by {creator} on {date}.',
|
||||
},
|
||||
});
|
||||
|
||||
const PublicOrPrivateChannel = ({channel, creator, roles, theme}: Props) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
|
|
@ -90,35 +116,30 @@ const PublicOrPrivateChannel = ({channel, creator, roles, theme}: Props) => {
|
|||
}, [channel.type, roles, channel.deleteAt]);
|
||||
|
||||
const createdBy = useMemo(() => {
|
||||
const id = channel.type === General.OPEN_CHANNEL ? t('intro.public_channel') : t('intro.private_channel');
|
||||
const defaultMessage = channel.type === General.OPEN_CHANNEL ? 'Public Channel' : 'Private Channel';
|
||||
const channelType = `${intl.formatMessage({id, defaultMessage})} `;
|
||||
const message = channel.type === General.OPEN_CHANNEL ? messages.publicChannel : messages.privateChannel;
|
||||
const channelType = `${intl.formatMessage(message)} `;
|
||||
|
||||
const date = intl.formatDate(channel.createAt, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
const by = intl.formatMessage({id: 'intro.created_by', defaultMessage: 'created by {creator} on {date}.'}, {
|
||||
const by = intl.formatMessage(messages.createdBy, {
|
||||
creator,
|
||||
date,
|
||||
});
|
||||
|
||||
return `${channelType} ${by}`;
|
||||
}, [channel.type, creator, theme]);
|
||||
}, [channel.createAt, channel.type, creator, intl]);
|
||||
|
||||
const message = useMemo(() => {
|
||||
const id = channel.type === General.OPEN_CHANNEL ? t('intro.welcome.public') : t('intro.welcome.private');
|
||||
const msg = channel.type === General.OPEN_CHANNEL ? 'Add some more team members to the channel or start a conversation below.' : 'Only invited members can see messages posted in this private channel.';
|
||||
const mainMessage = intl.formatMessage({
|
||||
id: 'intro.welcome',
|
||||
defaultMessage: 'Welcome to {displayName} channel.',
|
||||
}, {displayName: channel.displayName});
|
||||
const msg = channel.type === General.OPEN_CHANNEL ? messages.welcomePublic : messages.welcomePrivate;
|
||||
const mainMessage = intl.formatMessage(messages.welcome, {displayName: channel.displayName});
|
||||
|
||||
const suffix = intl.formatMessage({id, defaultMessage: msg});
|
||||
const suffix = intl.formatMessage(msg);
|
||||
|
||||
return `${mainMessage} ${suffix}`;
|
||||
}, [channel.displayName, channel.type, theme]);
|
||||
}, [channel.displayName, channel.type, intl]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
import {Keyboard, type LayoutChangeEvent, Platform, View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
|
|
@ -21,7 +21,6 @@ import {useAccessControlAttributes} from '@hooks/access_control_attributes';
|
|||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {useKeyboardOverlap} from '@hooks/device';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {t} from '@i18n';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {dismissModal} from '@screens/navigation';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
|
|
@ -174,7 +173,7 @@ export default function ChannelAddMembers({
|
|||
const result = await addMembersToChannel(serverUrl, channel.id, idsToUse);
|
||||
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(intl, result.error, {id: t('mobile.channel_add_members.error'), defaultMessage: 'There has been an error and we could not add those users to the channel.'});
|
||||
alertErrorWithFallback(intl, result.error, defineMessage({id: 'mobile.channel_add_members.error', defaultMessage: 'There has been an error and we could not add those users to the channel.'}));
|
||||
setAddingMembers(false);
|
||||
} else {
|
||||
close();
|
||||
|
|
@ -194,7 +193,7 @@ export default function ChannelAddMembers({
|
|||
|
||||
return newSelectedIds;
|
||||
});
|
||||
}, [currentUserId, clearSearch]);
|
||||
}, [clearSearch]);
|
||||
|
||||
const onTextChange = useCallback((searchTerm: string) => {
|
||||
setTerm(searchTerm);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
import Badge from '@components/badge';
|
||||
|
|
@ -9,7 +9,6 @@ import CompassIcon from '@components/compass_icon';
|
|||
import Filter, {DIVIDERS_HEIGHT, FILTER_ITEM_HEIGHT, FilterData, NUMBER_FILTER_ITEMS} from '@components/files/file_filter';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {TITLE_SEPARATOR_MARGIN, TITLE_SEPARATOR_MARGIN_TABLET, TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import {type FileFilter, FileFilters} from '@utils/file';
|
||||
|
|
@ -62,10 +61,10 @@ const Header = ({
|
|||
const messageObject = hasFilters ? {
|
||||
id: FilterData[selectedFilter].id,
|
||||
defaultMessage: FilterData[selectedFilter].defaultMessage,
|
||||
} : {
|
||||
id: t('screen.channel_files.header.recent_files'),
|
||||
} : defineMessage({
|
||||
id: 'screen.channel_files.header.recent_files',
|
||||
defaultMessage: 'Recent Files',
|
||||
};
|
||||
});
|
||||
|
||||
const messagesText = intl.formatMessage(messageObject);
|
||||
const title = intl.formatMessage({id: 'screen.channel_files.results.filter.title', defaultMessage: 'Filter by file type'});
|
||||
|
|
@ -97,7 +96,7 @@ const Header = ({
|
|||
theme,
|
||||
title,
|
||||
});
|
||||
}, [onFilterChanged, selectedFilter]);
|
||||
}, [onFilterChanged, selectedFilter, snapPoints, theme, title]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {type MessageDescriptor, useIntl} from 'react-intl';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages, type MessageDescriptor, useIntl} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {archiveChannel, unarchiveChannel} from '@actions/remote/channel';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {dismissModal, popToRoot} from '@screens/navigation';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
|
@ -26,6 +25,54 @@ type Props = {
|
|||
type?: ChannelType;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
publicChannel: {
|
||||
id: 'channel_info.public_channel',
|
||||
defaultMessage: 'Public Channel',
|
||||
},
|
||||
privateChannel: {
|
||||
id: 'channel_info.private_channel',
|
||||
defaultMessage: 'Private Channel',
|
||||
},
|
||||
alertNo: {
|
||||
id: 'channel_info.alertNo',
|
||||
defaultMessage: 'No',
|
||||
},
|
||||
alertYes: {
|
||||
id: 'channel_info.alertYes',
|
||||
defaultMessage: 'Yes',
|
||||
},
|
||||
archiveTitle: {
|
||||
id: 'channel_info.archive_title',
|
||||
defaultMessage: 'Archive {term}',
|
||||
},
|
||||
archiveDescriptionCanViewArchived: {
|
||||
id: 'channel_info.archive_description.can_view_archived',
|
||||
defaultMessage: 'This will archive the channel from the team. Channel contents will still be accessible by channel members.\n\nAre you sure you wish to archive the {term} {name}?',
|
||||
},
|
||||
archiveDescriptionCannotViewArchived: {
|
||||
id: 'channel_info.archive_description.cannot_view_archived',
|
||||
defaultMessage: 'This will archive the channel from the team and remove it from the user interface. Archived channels can be unarchived if needed again.\n\nAre you sure you wish to archive the {term} {name}?',
|
||||
},
|
||||
archiveFailed: {
|
||||
id: 'channel_info.archive_failed',
|
||||
defaultMessage: 'An error occurred trying to archive the channel {displayName}',
|
||||
},
|
||||
unarchiveTitle: {
|
||||
id: 'channel_info.unarchive_title',
|
||||
defaultMessage: 'Unarchive {term}',
|
||||
},
|
||||
unarchiveDescription: {
|
||||
id: 'channel_info.unarchive_description',
|
||||
defaultMessage: 'Are you sure you want to unarchive the {term} {name}?',
|
||||
},
|
||||
unarchiveFailed: {
|
||||
id: 'channel_info.unarchive_failed',
|
||||
defaultMessage: 'An error occurred trying to unarchive the channel {displayName}',
|
||||
},
|
||||
});
|
||||
|
||||
const Archive = ({
|
||||
canArchive, canUnarchive, canViewArchivedChannels,
|
||||
channelId, componentId, displayName, type,
|
||||
|
|
@ -33,43 +80,37 @@ const Archive = ({
|
|||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const close = async (pop: boolean) => {
|
||||
const close = useCallback(async (pop: boolean) => {
|
||||
await dismissModal({componentId});
|
||||
if (pop) {
|
||||
popToRoot();
|
||||
}
|
||||
};
|
||||
}, [componentId]);
|
||||
|
||||
const alertAndHandleYesAction = (title: MessageDescriptor, message: MessageDescriptor, onPressAction: () => void) => {
|
||||
const alertAndHandleYesAction = useCallback((title: MessageDescriptor, message: MessageDescriptor, onPressAction: () => void) => {
|
||||
const {formatMessage} = intl;
|
||||
let term: string;
|
||||
if (type === General.OPEN_CHANNEL) {
|
||||
term = formatMessage({id: 'channel_info.public_channel', defaultMessage: 'Public Channel'});
|
||||
term = formatMessage(messages.publicChannel);
|
||||
} else {
|
||||
term = formatMessage({id: 'channel_info.private_channel', defaultMessage: 'Private Channel'});
|
||||
term = formatMessage(messages.privateChannel);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
formatMessage(title, {term}),
|
||||
formatMessage(message, {term: term.toLowerCase(), name: displayName}),
|
||||
[{
|
||||
text: formatMessage({id: 'channel_info.alertNo', defaultMessage: 'No'}),
|
||||
text: formatMessage(messages.alertNo),
|
||||
}, {
|
||||
text: formatMessage({id: 'channel_info.alertYes', defaultMessage: 'Yes'}),
|
||||
text: formatMessage(messages.alertYes),
|
||||
onPress: onPressAction,
|
||||
}],
|
||||
);
|
||||
};
|
||||
}, [displayName, intl, type]);
|
||||
|
||||
const onArchive = preventDoubleTap(() => {
|
||||
const title = {id: t('channel_info.archive_title'), defaultMessage: 'Archive {term}'};
|
||||
const message = canViewArchivedChannels ? {
|
||||
id: t('channel_info.archive_description.can_view_archived'),
|
||||
defaultMessage: 'This will archive the channel from the team. Channel contents will still be accessible by channel members.\n\nAre you sure you wish to archive the {term} {name}?',
|
||||
} : {
|
||||
id: t('channel_info.archive_description.cannot_view_archived'),
|
||||
defaultMessage: 'This will archive the channel from the team and remove it from the user interface. Archived channels can be unarchived if needed again.\n\nAre you sure you wish to archive the {term} {name}?',
|
||||
};
|
||||
const onArchive = usePreventDoubleTap(useCallback(() => {
|
||||
const title = messages.archiveTitle;
|
||||
const message = canViewArchivedChannels ? messages.archiveDescriptionCanViewArchived : messages.archiveDescriptionCannotViewArchived;
|
||||
|
||||
const onPressAction = async () => {
|
||||
const result = await archiveChannel(serverUrl, channelId);
|
||||
|
|
@ -77,41 +118,34 @@ const Archive = ({
|
|||
alertErrorWithFallback(
|
||||
intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('channel_info.archive_failed'),
|
||||
defaultMessage: 'An error occurred trying to archive the channel {displayName}',
|
||||
}, {displayName},
|
||||
messages.archiveFailed,
|
||||
{displayName},
|
||||
);
|
||||
} else {
|
||||
close(!canViewArchivedChannels);
|
||||
}
|
||||
};
|
||||
alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
}, [alertAndHandleYesAction, canViewArchivedChannels, channelId, close, displayName, intl, serverUrl]));
|
||||
|
||||
const onUnarchive = preventDoubleTap(() => {
|
||||
const title = {id: t('channel_info.unarchive_title'), defaultMessage: 'Unarchive {term}'};
|
||||
const message = {
|
||||
id: t('channel_info.unarchive_description'),
|
||||
defaultMessage: 'Are you sure you want to unarchive the {term} {name}?',
|
||||
};
|
||||
const onUnarchive = usePreventDoubleTap(useCallback(() => {
|
||||
const title = messages.unarchiveTitle;
|
||||
const message = messages.unarchiveDescription;
|
||||
const onPressAction = async () => {
|
||||
const result = await unarchiveChannel(serverUrl, channelId);
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('channel_info.unarchive_failed'),
|
||||
defaultMessage: 'An error occurred trying to unarchive the channel {displayName}',
|
||||
}, {displayName},
|
||||
messages.unarchiveFailed,
|
||||
{displayName},
|
||||
);
|
||||
} else {
|
||||
close(false);
|
||||
}
|
||||
};
|
||||
alertAndHandleYesAction(title, message, onPressAction);
|
||||
});
|
||||
}, [alertAndHandleYesAction, channelId, close, displayName, intl, serverUrl]));
|
||||
|
||||
if (!canArchive && !canUnarchive) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages, useIntl, type IntlShape} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {convertChannelToPrivate} from '@actions/remote/channel';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
|
|
@ -18,55 +17,85 @@ type Props = {
|
|||
displayName: string;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
convertPrivateTitle: {
|
||||
id: 'channel_info.convert_private_title',
|
||||
defaultMessage: 'Convert {displayName} to a private channel?',
|
||||
},
|
||||
convertPrivateDescription: {
|
||||
id: 'channel_info.convert_private_description',
|
||||
defaultMessage: 'When you convert {displayName} to a private channel, history and membership are preserved. Publicly shared files remain accessible to anyone with the link. Membership in a private channel is by invitation only.\n\nThe change is permanent and cannot be undone.\n\nAre you sure you want to convert {displayName} to a private channel?',
|
||||
},
|
||||
alertNo: {
|
||||
id: 'channel_info.alertNo',
|
||||
defaultMessage: 'No',
|
||||
},
|
||||
alertYes: {
|
||||
id: 'channel_info.alertYes',
|
||||
defaultMessage: 'Yes',
|
||||
},
|
||||
convertFailed: {
|
||||
id: 'channel_info.convert_failed',
|
||||
defaultMessage: 'We were unable to convert {displayName} to a private channel.',
|
||||
},
|
||||
convertPrivateSuccess: {
|
||||
id: 'channel_info.convert_private_success',
|
||||
defaultMessage: '{displayName} is now a private channel.',
|
||||
},
|
||||
errorClose: {
|
||||
id: 'channel_info.error_close',
|
||||
defaultMessage: 'Close',
|
||||
},
|
||||
alertRetry: {
|
||||
id: 'channel_info.alert_retry',
|
||||
defaultMessage: 'Try Again',
|
||||
},
|
||||
});
|
||||
|
||||
const convertToPrivate = preventDoubleTap(async (serverUrl: string, channelId: string, displayName: string, intl: IntlShape) => {
|
||||
const result = await convertChannelToPrivate(serverUrl, channelId);
|
||||
const {formatMessage} = intl;
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
intl,
|
||||
result.error,
|
||||
messages.convertFailed,
|
||||
{displayName},
|
||||
[{
|
||||
text: formatMessage(messages.errorClose),
|
||||
}, {
|
||||
text: formatMessage(messages.alertRetry),
|
||||
onPress: () => convertToPrivate(serverUrl, channelId, displayName, intl),
|
||||
}],
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'',
|
||||
formatMessage(messages.convertPrivateSuccess, {displayName}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const ConvertPrivate = ({canConvert, channelId, displayName}: Props) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const onConfirmConvertToPrivate = () => {
|
||||
const onConfirmConvertToPrivate = useCallback(() => {
|
||||
const {formatMessage} = intl;
|
||||
const title = {id: t('channel_info.convert_private_title'), defaultMessage: 'Convert {displayName} to a private channel?'};
|
||||
const message = {
|
||||
id: t('channel_info.convert_private_description'),
|
||||
defaultMessage: 'When you convert {displayName} to a private channel, history and membership are preserved. Publicly shared files remain accessible to anyone with the link. Membership in a private channel is by invitation only.\n\nThe change is permanent and cannot be undone.\n\nAre you sure you want to convert {displayName} to a private channel?',
|
||||
};
|
||||
const title = messages.convertPrivateTitle;
|
||||
const message = messages.convertPrivateDescription;
|
||||
|
||||
Alert.alert(
|
||||
formatMessage(title, {displayName}),
|
||||
formatMessage(message, {displayName}),
|
||||
[{
|
||||
text: formatMessage({id: 'channel_info.alertNo', defaultMessage: 'No'}),
|
||||
text: formatMessage(messages.alertNo),
|
||||
}, {
|
||||
text: formatMessage({id: 'channel_info.alertYes', defaultMessage: 'Yes'}),
|
||||
onPress: convertToPrivate,
|
||||
text: formatMessage(messages.alertYes),
|
||||
onPress: () => convertToPrivate(serverUrl, channelId, displayName, intl),
|
||||
}],
|
||||
);
|
||||
};
|
||||
|
||||
const convertToPrivate = preventDoubleTap(async () => {
|
||||
const result = await convertChannelToPrivate(serverUrl, channelId);
|
||||
const {formatMessage} = intl;
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(
|
||||
intl,
|
||||
result.error,
|
||||
{
|
||||
id: t('channel_info.convert_failed'),
|
||||
defaultMessage: 'We were unable to convert {displayName} to a private channel.',
|
||||
}, {displayName},
|
||||
[{
|
||||
text: formatMessage({id: 'channel_info.error_close', defaultMessage: 'Close'}),
|
||||
}, {
|
||||
text: formatMessage({id: 'channel_info.alert_retry', defaultMessage: 'Try Again'}),
|
||||
onPress: convertToPrivate,
|
||||
}],
|
||||
);
|
||||
} else {
|
||||
Alert.alert(
|
||||
'',
|
||||
formatMessage({id: t('channel_info.convert_private_success'), defaultMessage: '{displayName} is now a private channel.'}, {displayName}),
|
||||
);
|
||||
}
|
||||
});
|
||||
}, [channelId, displayName, intl, serverUrl]);
|
||||
|
||||
if (!canConvert) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {getHeaderOptions} from '@screens/channel_add_members/channel_add_members';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
|
|
@ -22,10 +22,10 @@ const AddMembers = ({displayName, channelId}: Props) => {
|
|||
const theme = useTheme();
|
||||
const title = formatMessage({id: 'channel_info.add_members', defaultMessage: 'Add members'});
|
||||
|
||||
const goToAddMembers = preventDoubleTap(async () => {
|
||||
const goToAddMembers = usePreventDoubleTap(useCallback(async () => {
|
||||
const options = await getHeaderOptions(theme, displayName, true);
|
||||
goToScreen(Screens.CHANNEL_ADD_MEMBERS, title, {channelId, inModal: true}, options);
|
||||
});
|
||||
}, [channelId, displayName, theme, title]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {updateChannelNotifyProps} from '@actions/remote/channel';
|
||||
|
|
@ -11,8 +11,8 @@ import {
|
|||
CHANNEL_AUTO_FOLLOW_THREADS_TRUE,
|
||||
} from '@constants/channel';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
|
|
@ -25,7 +25,7 @@ const AutoFollowThreads = ({channelId, displayName, followedStatus}: Props) => {
|
|||
const serverUrl = useServerUrl();
|
||||
const intl = useIntl();
|
||||
|
||||
const toggleFollow = preventDoubleTap(async () => {
|
||||
const toggleFollow = usePreventDoubleTap(useCallback(async () => {
|
||||
const props: Partial<ChannelNotifyProps> = {
|
||||
channel_auto_follow_threads: followedStatus ? CHANNEL_AUTO_FOLLOW_THREADS_FALSE : CHANNEL_AUTO_FOLLOW_THREADS_TRUE,
|
||||
};
|
||||
|
|
@ -43,7 +43,7 @@ const AutoFollowThreads = ({channelId, displayName, followedStatus}: Props) => {
|
|||
);
|
||||
setAutoFollow((v) => !v);
|
||||
}
|
||||
});
|
||||
}, [channelId, displayName, followedStatus, intl, serverUrl]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -23,7 +23,7 @@ const ChannelFiles = ({channelId, count, displayName}: Props) => {
|
|||
const {formatMessage} = useIntl();
|
||||
const title = formatMessage({id: 'channel_info.channel_files', defaultMessage: 'Files'});
|
||||
|
||||
const goToChannelFiles = preventDoubleTap(() => {
|
||||
const goToChannelFiles = usePreventDoubleTap(useCallback(() => {
|
||||
const options = {
|
||||
topBar: {
|
||||
title: {
|
||||
|
|
@ -36,7 +36,7 @@ const ChannelFiles = ({channelId, count, displayName}: Props) => {
|
|||
},
|
||||
};
|
||||
goToScreen(Screens.CHANNEL_FILES, title, {channelId}, options);
|
||||
});
|
||||
}, [channelId, displayName, theme.sidebarHeaderTextColor, title]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
|
|
@ -18,9 +18,9 @@ const EditChannel = ({channelId}: Props) => {
|
|||
const {formatMessage} = useIntl();
|
||||
const title = formatMessage({id: 'screens.channel_edit', defaultMessage: 'Edit Channel'});
|
||||
|
||||
const goToEditChannel = preventDoubleTap(async () => {
|
||||
const goToEditChannel = usePreventDoubleTap(useCallback(async () => {
|
||||
goToScreen(Screens.CREATE_OR_EDIT_CHANNEL, title, {channelId});
|
||||
});
|
||||
}, [channelId, title]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {defineMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {updateChannelNotifyProps} from '@actions/remote/channel';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
|
|
@ -21,7 +21,7 @@ const IgnoreMentions = ({channelId, ignoring, displayName}: Props) => {
|
|||
const serverUrl = useServerUrl();
|
||||
const intl = useIntl();
|
||||
|
||||
const toggleIgnore = preventDoubleTap(async () => {
|
||||
const toggleIgnore = usePreventDoubleTap(useCallback(async () => {
|
||||
const props: Partial<ChannelNotifyProps> = {
|
||||
ignore_channel_mentions: ignoring ? 'off' : 'on',
|
||||
};
|
||||
|
|
@ -39,7 +39,7 @@ const IgnoreMentions = ({channelId, ignoring, displayName}: Props) => {
|
|||
);
|
||||
setIgnored((v) => !v);
|
||||
}
|
||||
});
|
||||
}, [channelId, displayName, ignoring, intl, serverUrl]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -22,7 +22,7 @@ const Members = ({displayName, channelId, count}: Props) => {
|
|||
const {formatMessage} = useIntl();
|
||||
const theme = useTheme();
|
||||
const title = formatMessage({id: 'channel_info.members', defaultMessage: 'Members'});
|
||||
const goToChannelMembers = preventDoubleTap(() => {
|
||||
const goToChannelMembers = usePreventDoubleTap(useCallback(() => {
|
||||
const options = {
|
||||
topBar: {
|
||||
subtitle: {
|
||||
|
|
@ -33,7 +33,7 @@ const Members = ({displayName, channelId, count}: Props) => {
|
|||
};
|
||||
|
||||
goToScreen(Screens.MANAGE_CHANNEL_MEMBERS, title, {channelId}, options);
|
||||
});
|
||||
}, [channelId, displayName, theme.sidebarHeaderTextColor, title]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {NotificationLevel, Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {isTypeDMorGM} from '@utils/channel';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
import type {Options} from 'react-native-navigation';
|
||||
|
|
@ -25,32 +24,46 @@ type Props = {
|
|||
hasGMasDMFeature: boolean;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
notificationAll: {
|
||||
id: 'channel_info.notification.all',
|
||||
defaultMessage: 'All',
|
||||
},
|
||||
notificationMention: {
|
||||
id: 'channel_info.notification.mention',
|
||||
defaultMessage: 'Mentions',
|
||||
},
|
||||
notificationNone: {
|
||||
id: 'channel_info.notification.none',
|
||||
defaultMessage: 'Never',
|
||||
},
|
||||
notificationDefault: {
|
||||
id: 'channel_info.notification.default',
|
||||
defaultMessage: 'Default',
|
||||
},
|
||||
});
|
||||
|
||||
const notificationLevel = (notifyLevel: NotificationLevel) => {
|
||||
let id = '';
|
||||
let defaultMessage = '';
|
||||
let message;
|
||||
switch (notifyLevel) {
|
||||
case NotificationLevel.ALL: {
|
||||
id = t('channel_info.notification.all');
|
||||
defaultMessage = 'All';
|
||||
message = messages.notificationAll;
|
||||
break;
|
||||
}
|
||||
case NotificationLevel.MENTION: {
|
||||
id = t('channel_info.notification.mention');
|
||||
defaultMessage = 'Mentions';
|
||||
message = messages.notificationMention;
|
||||
break;
|
||||
}
|
||||
case NotificationLevel.NONE: {
|
||||
id = t('channel_info.notification.none');
|
||||
defaultMessage = 'Never';
|
||||
message = messages.notificationNone;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
id = t('channel_info.notification.default');
|
||||
defaultMessage = 'Default';
|
||||
message = messages.notificationDefault;
|
||||
break;
|
||||
}
|
||||
|
||||
return {id, defaultMessage};
|
||||
return message;
|
||||
};
|
||||
|
||||
const NotificationPreference = ({
|
||||
|
|
@ -65,7 +78,7 @@ const NotificationPreference = ({
|
|||
const theme = useTheme();
|
||||
const title = formatMessage({id: 'channel_info.mobile_notifications', defaultMessage: 'Mobile Notifications'});
|
||||
|
||||
const goToChannelNotificationPreferences = preventDoubleTap(() => {
|
||||
const goToChannelNotificationPreferences = usePreventDoubleTap(useCallback(() => {
|
||||
const options: Options = {
|
||||
topBar: {
|
||||
title: {
|
||||
|
|
@ -81,7 +94,7 @@ const NotificationPreference = ({
|
|||
},
|
||||
};
|
||||
goToScreen(Screens.CHANNEL_NOTIFICATION_PREFERENCES, title, {channelId}, options);
|
||||
});
|
||||
}, [channelId, displayName, theme.sidebarHeaderTextColor, title]));
|
||||
|
||||
const notificationLevelToText = () => {
|
||||
let notifyLevelToUse = notifyLevel;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -23,7 +23,7 @@ const PinnedMessages = ({channelId, count, displayName}: Props) => {
|
|||
const {formatMessage} = useIntl();
|
||||
const title = formatMessage({id: 'channel_info.pinned_messages', defaultMessage: 'Pinned Messages'});
|
||||
|
||||
const goToPinnedMessages = preventDoubleTap(() => {
|
||||
const goToPinnedMessages = usePreventDoubleTap(useCallback(() => {
|
||||
const options = {
|
||||
topBar: {
|
||||
title: {
|
||||
|
|
@ -36,7 +36,7 @@ const PinnedMessages = ({channelId, count, displayName}: Props) => {
|
|||
},
|
||||
};
|
||||
goToScreen(Screens.PINNED_MESSAGES, title, {channelId}, options);
|
||||
});
|
||||
}, [channelId, displayName, theme.sidebarHeaderTextColor, title]));
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {type LayoutChangeEvent, View} from 'react-native';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {NotificationLevel} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import type {SharedValue} from 'react-native-reanimated';
|
||||
|
||||
|
|
@ -30,24 +29,40 @@ type NotifPrefOptions = {
|
|||
|
||||
export const BLOCK_TITLE_HEIGHT = 13;
|
||||
|
||||
const NOTIFY_ABOUT = {id: t('channel_notification_preferences.notify_about'), defaultMessage: 'Notify me about...'};
|
||||
const messages = defineMessages({
|
||||
notifyAbout: {
|
||||
id: 'channel_notification_preferences.notify_about',
|
||||
defaultMessage: 'Notify me about...',
|
||||
},
|
||||
[NotificationLevel.ALL]: {
|
||||
id: 'channel_notification_preferences.notification.all',
|
||||
defaultMessage: 'All new messages',
|
||||
},
|
||||
[NotificationLevel.MENTION]: {
|
||||
id: 'channel_notification_preferences.notification.mention',
|
||||
defaultMessage: 'Mentions only',
|
||||
},
|
||||
[NotificationLevel.NONE]: {
|
||||
id: 'channel_notification_preferences.notification.none',
|
||||
defaultMessage: 'Nothing',
|
||||
},
|
||||
});
|
||||
|
||||
const NOTIFY_ABOUT = messages.notifyAbout;
|
||||
|
||||
const NOTIFY_OPTIONS: Record<string, NotifPrefOptions> = {
|
||||
[NotificationLevel.ALL]: {
|
||||
defaultMessage: 'All new messages',
|
||||
id: t('channel_notification_preferences.notification.all'),
|
||||
...messages[NotificationLevel.ALL],
|
||||
testID: 'channel_notification_preferences.notification.all',
|
||||
value: NotificationLevel.ALL,
|
||||
},
|
||||
[NotificationLevel.MENTION]: {
|
||||
defaultMessage: 'Mentions only',
|
||||
id: t('channel_notification_preferences.notification.mention'),
|
||||
...messages[NotificationLevel.MENTION],
|
||||
testID: 'channel_notification_preferences.notification.mention',
|
||||
value: NotificationLevel.MENTION,
|
||||
},
|
||||
[NotificationLevel.NONE]: {
|
||||
defaultMessage: 'Nothing',
|
||||
id: t('channel_notification_preferences.notification.none'),
|
||||
...messages[NotificationLevel.NONE],
|
||||
testID: 'channel_notification_preferences.notification.none',
|
||||
value: NotificationLevel.NONE,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
|
||||
import SettingBlock from '@components/settings/block';
|
||||
import SettingOption from '@components/settings/option';
|
||||
import SettingSeparator from '@components/settings/separator';
|
||||
import {NotificationLevel} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
|
||||
type Props = {
|
||||
isSelected: boolean;
|
||||
|
|
@ -22,12 +21,21 @@ type NotifPrefOptions = {
|
|||
testID: string;
|
||||
value: string;
|
||||
}
|
||||
const messages = defineMessages({
|
||||
threadReplies: {
|
||||
id: 'channel_notification_preferences.thread_replies',
|
||||
defaultMessage: 'Thread replies',
|
||||
},
|
||||
threadRepliesDescription: {
|
||||
id: 'channel_notification_preferences.notification.thread_replies',
|
||||
defaultMessage: 'Notify me about replies to threads I’m following in this channel',
|
||||
},
|
||||
});
|
||||
|
||||
const THREAD_REPLIES = {id: t('channel_notification_preferences.thread_replies'), defaultMessage: 'Thread replies'};
|
||||
const THREAD_REPLIES = messages.threadReplies;
|
||||
const NOTIFY_OPTIONS_THREAD: Record<string, NotifPrefOptions> = {
|
||||
THREAD_REPLIES: {
|
||||
defaultMessage: 'Notify me about replies to threads I’m following in this channel',
|
||||
id: t('channel_notification_preferences.notification.thread_replies'),
|
||||
...messages.threadRepliesDescription,
|
||||
testID: 'channel_notification_preferences.notification.thread_replies',
|
||||
value: 'thread_replies',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import Button from '@components/button';
|
|||
import {ServerErrors} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {isErrorWithMessage, isServerError} from '@utils/errors';
|
||||
import {logError} from '@utils/log';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ export const ConvertGMToChannelForm = ({
|
|||
const userDisplayNames = useMemo(() => profiles.map((profile) => displayUsername(profile, locale, teammateNameDisplay)), [profiles, teammateNameDisplay, locale]);
|
||||
const submitButtonEnabled = !conversionInProgress && selectedTeam && newChannelName.trim();
|
||||
|
||||
const handleOnPress = useCallback(preventDoubleTap(async () => {
|
||||
const handleOnPress = usePreventDoubleTap(useCallback(async () => {
|
||||
if (!submitButtonEnabled) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ export const ConvertGMToChannelForm = ({
|
|||
setErrorMessage('');
|
||||
switchToChannelById(serverUrl, updatedChannel.id, selectedTeam.id);
|
||||
setConversionInProgress(false);
|
||||
}), [selectedTeam, newChannelName, submitButtonEnabled]);
|
||||
}, [submitButtonEnabled, serverUrl, channelId, selectedTeam.id, newChannelName, formatMessage]));
|
||||
|
||||
if (commonTeams.length === 0) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import {useTheme} from '@context/theme';
|
|||
import {useAutocompleteDefaultAnimatedValues} from '@hooks/autocomplete';
|
||||
import {useKeyboardHeight, useKeyboardOverlap} from '@hooks/device';
|
||||
import {useInputPropagation} from '@hooks/input';
|
||||
import {t} from '@i18n';
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
|
|
@ -133,17 +132,17 @@ export default function ChannelInfoForm({
|
|||
const [headerFieldHeight, setHeaderFieldHeight] = useState(0);
|
||||
const [headerPosition, setHeaderPosition] = useState(0);
|
||||
|
||||
const optionalText = formatMessage({id: t('channel_modal.optional'), defaultMessage: '(optional)'});
|
||||
const labelDisplayName = formatMessage({id: t('channel_modal.name'), defaultMessage: 'Name'});
|
||||
const labelPurpose = formatMessage({id: t('channel_modal.purpose'), defaultMessage: 'Purpose'}) + ' ' + optionalText;
|
||||
const labelHeader = formatMessage({id: t('channel_modal.header'), defaultMessage: 'Header'}) + ' ' + optionalText;
|
||||
const optionalText = formatMessage({id: 'channel_modal.optional', defaultMessage: '(optional)'});
|
||||
const labelDisplayName = formatMessage({id: 'channel_modal.name', defaultMessage: 'Name'});
|
||||
const labelPurpose = formatMessage({id: 'channel_modal.purpose', defaultMessage: 'Purpose'}) + ' ' + optionalText;
|
||||
const labelHeader = formatMessage({id: 'channel_modal.header', defaultMessage: 'Header'}) + ' ' + optionalText;
|
||||
|
||||
const placeholderDisplayName = formatMessage({id: t('channel_modal.nameEx'), defaultMessage: 'Bugs, Marketing'});
|
||||
const placeholderPurpose = formatMessage({id: t('channel_modal.purposeEx'), defaultMessage: 'A channel to file bugs and improvements'});
|
||||
const placeholderHeader = formatMessage({id: t('channel_modal.headerEx'), defaultMessage: 'Use Markdown to format header text'});
|
||||
const placeholderDisplayName = formatMessage({id: 'channel_modal.nameEx', defaultMessage: 'Bugs, Marketing'});
|
||||
const placeholderPurpose = formatMessage({id: 'channel_modal.purposeEx', defaultMessage: 'A channel to file bugs and improvements'});
|
||||
const placeholderHeader = formatMessage({id: 'channel_modal.headerEx', defaultMessage: 'Use Markdown to format header text'});
|
||||
|
||||
const makePrivateLabel = formatMessage({id: t('channel_modal.makePrivate.label'), defaultMessage: 'Make Private'});
|
||||
const makePrivateDescription = formatMessage({id: t('channel_modal.makePrivate.description'), defaultMessage: 'When a channel is set to private, only invited team members can access and participate in that channel.'});
|
||||
const makePrivateLabel = formatMessage({id: 'channel_modal.makePrivate.label', defaultMessage: 'Make Private'});
|
||||
const makePrivateDescription = formatMessage({id: 'channel_modal.makePrivate.description', defaultMessage: 'When a channel is set to private, only invited team members can access and participate in that channel'});
|
||||
|
||||
const displayHeaderOnly = headerOnly || channelType === General.DM_CHANNEL || channelType === General.GM_CHANNEL;
|
||||
const showSelector = !displayHeaderOnly && !editing;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import ClearButton from '@components/custom_status/clear_button';
|
|||
import CustomStatusText from '@components/custom_status/custom_status_text';
|
||||
import Emoji from '@components/emoji';
|
||||
import {CST} from '@constants/custom_status';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -67,15 +67,15 @@ const CustomStatusSuggestion = ({duration, emoji, expires_at, handleClear, handl
|
|||
const style = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
const handleClick = useCallback(preventDoubleTap(() => {
|
||||
const handleClick = usePreventDoubleTap(useCallback(() => {
|
||||
handleSuggestionClick({emoji, text, duration});
|
||||
}), []);
|
||||
}, [duration, emoji, handleSuggestionClick, text]));
|
||||
|
||||
const handleSuggestionClear = useCallback(() => {
|
||||
if (handleClear) {
|
||||
handleClear({emoji, text, duration, expires_at});
|
||||
}
|
||||
}, []);
|
||||
}, [duration, emoji, expires_at, handleClear, text]);
|
||||
|
||||
const showCustomStatus = Boolean(duration && duration !== 'date_and_time' && isExpirySupported);
|
||||
const customStatusSuggestionTestId = `custom_status.custom_status_suggestion.${text}`;
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {defineMessages, type IntlShape, type MessageDescriptor} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import CustomStatusSuggestion from './custom_status_suggestion';
|
||||
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
type Props = {
|
||||
intl: IntlShape;
|
||||
onHandleCustomStatusSuggestionClick: (status: UserCustomStatus) => void;
|
||||
|
|
@ -21,8 +19,7 @@ type Props = {
|
|||
|
||||
type DefaultUserCustomStatus = {
|
||||
emoji: string;
|
||||
message: string;
|
||||
messageDefault: string;
|
||||
message: MessageDescriptor;
|
||||
durationDefault: CustomStatusDuration;
|
||||
};
|
||||
|
||||
|
|
@ -47,12 +44,35 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const messages = defineMessages({
|
||||
inAMeeting: {
|
||||
id: 'custom_status.suggestions.in_a_meeting',
|
||||
defaultMessage: 'In a meeting',
|
||||
},
|
||||
outForLunch: {
|
||||
id: 'custom_status.suggestions.out_for_lunch',
|
||||
defaultMessage: 'Out for lunch',
|
||||
},
|
||||
outSick: {
|
||||
id: 'custom_status.suggestions.out_sick',
|
||||
defaultMessage: 'Out sick',
|
||||
},
|
||||
workingFromHome: {
|
||||
id: 'custom_status.suggestions.working_from_home',
|
||||
defaultMessage: 'Working from home',
|
||||
},
|
||||
onAVacation: {
|
||||
id: 'custom_status.suggestions.on_a_vacation',
|
||||
defaultMessage: 'On a vacation',
|
||||
},
|
||||
});
|
||||
|
||||
const defaultCustomStatusSuggestions: DefaultUserCustomStatus[] = [
|
||||
{emoji: 'calendar', message: t('custom_status.suggestions.in_a_meeting'), messageDefault: 'In a meeting', durationDefault: 'one_hour'},
|
||||
{emoji: 'hamburger', message: t('custom_status.suggestions.out_for_lunch'), messageDefault: 'Out for lunch', durationDefault: 'thirty_minutes'},
|
||||
{emoji: 'sneezing_face', message: t('custom_status.suggestions.out_sick'), messageDefault: 'Out sick', durationDefault: 'today'},
|
||||
{emoji: 'house', message: t('custom_status.suggestions.working_from_home'), messageDefault: 'Working from home', durationDefault: 'today'},
|
||||
{emoji: 'palm_tree', message: t('custom_status.suggestions.on_a_vacation'), messageDefault: 'On a vacation', durationDefault: 'this_week'},
|
||||
{emoji: 'calendar', message: messages.inAMeeting, durationDefault: 'one_hour'},
|
||||
{emoji: 'hamburger', message: messages.outForLunch, durationDefault: 'thirty_minutes'},
|
||||
{emoji: 'sneezing_face', message: messages.outSick, durationDefault: 'today'},
|
||||
{emoji: 'house', message: messages.workingFromHome, durationDefault: 'today'},
|
||||
{emoji: 'palm_tree', message: messages.onAVacation, durationDefault: 'this_week'},
|
||||
];
|
||||
|
||||
const CustomStatusSuggestions = ({
|
||||
|
|
@ -67,7 +87,7 @@ const CustomStatusSuggestions = ({
|
|||
const customStatusSuggestions = defaultCustomStatusSuggestions.
|
||||
map((status) => ({
|
||||
emoji: status.emoji,
|
||||
text: intl.formatMessage({id: status.message, defaultMessage: status.messageDefault}),
|
||||
text: intl.formatMessage(status.message),
|
||||
duration: status.durationDefault,
|
||||
})).
|
||||
filter((status) => !recentCustomStatusTexts.has(status.text)).
|
||||
|
|
@ -92,7 +112,7 @@ const CustomStatusSuggestions = ({
|
|||
<View style={style.separator}/>
|
||||
<View testID='custom_status.suggestions'>
|
||||
<FormattedText
|
||||
id={t('custom_status.suggestions.title')}
|
||||
id={'custom_status.suggestions.title'}
|
||||
defaultMessage='Suggestions'
|
||||
style={style.title}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import React from 'react';
|
|||
import {View} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {t} from '@i18n';
|
||||
import CustomStatusSuggestion from '@screens/custom_status/components/custom_status_suggestion';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
|
|
@ -49,7 +48,7 @@ const RecentCustomStatuses = ({onHandleClear, onHandleSuggestionClick, recentCus
|
|||
<View style={style.separator}/>
|
||||
<View testID='custom_status.recents'>
|
||||
<FormattedText
|
||||
id={t('custom_status.suggestions.recent_title')}
|
||||
id={'custom_status.suggestions.recent_title'}
|
||||
defaultMessage='Recent'
|
||||
style={style.title}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ import {useTheme} from '@context/theme';
|
|||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {dismissModal, goToScreen, openAsBottomSheet, showModal} from '@screens/navigation';
|
||||
import {getCurrentMomentForTimezone, getRoundedTime} from '@utils/helpers';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {mergeNavigationOptions} from '@utils/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {
|
||||
getTimezone,
|
||||
|
|
@ -304,9 +304,18 @@ const CustomStatus = ({
|
|||
}
|
||||
}
|
||||
dismissModalAndKeyboard(isTablet, {componentId});
|
||||
}, [newStatus, isStatusSet, storedStatus, currentUser, isTablet]);
|
||||
}, [
|
||||
currentUser,
|
||||
isStatusSet,
|
||||
storedStatus,
|
||||
isTablet,
|
||||
componentId,
|
||||
newStatus,
|
||||
customStatusExpirySupported,
|
||||
serverUrl,
|
||||
]);
|
||||
|
||||
const openEmojiPicker = useCallback(preventDoubleTap(() => {
|
||||
const openEmojiPicker = usePreventDoubleTap(useCallback(() => {
|
||||
openAsBottomSheet({
|
||||
closeButtonId: 'close-emoji-picker',
|
||||
screen: Screens.EMOJI_PICKER,
|
||||
|
|
@ -314,7 +323,7 @@ const CustomStatus = ({
|
|||
title: intl.formatMessage({id: 'mobile.custom_status.choose_emoji', defaultMessage: 'Choose an emoji'}),
|
||||
props: {onEmojiPress: handleEmojiClick},
|
||||
});
|
||||
}), [theme, intl, handleEmojiClick]);
|
||||
}, [theme, intl, handleEmojiClick]));
|
||||
|
||||
const handleBackButton = useCallback(() => {
|
||||
dismissModalAndKeyboard(isTablet, {componentId});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import CustomStatusText from '@components/custom_status/custom_status_text';
|
|||
import DateTimePicker from '@components/data_time_selector';
|
||||
import {CST, CustomStatusDurationEnum} from '@constants/custom_status';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getTimezone} from '@utils/user';
|
||||
|
||||
|
|
@ -82,9 +82,9 @@ const ClearAfterMenuItem = ({currentUser, duration, expiryTime = '', handleItemC
|
|||
[CustomStatusDurationEnum.DATE_AND_TIME]: intl.formatMessage({id: 'custom_status.expiry_dropdown.custom', defaultMessage: 'Custom'}),
|
||||
};
|
||||
|
||||
const handleClick = preventDoubleTap(() => {
|
||||
const handleClick = usePreventDoubleTap(useCallback(() => {
|
||||
handleItemClick(duration, expiryTime);
|
||||
});
|
||||
}, [duration, expiryTime, handleItemClick]));
|
||||
|
||||
const handleCustomExpiresAtChange = useCallback((expiresAt: Moment) => {
|
||||
handleItemClick(duration, expiresAt.toISOString());
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {type MessageDescriptor, useIntl} from 'react-intl';
|
||||
import {defineMessages, type MessageDescriptor, useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, View} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import useFieldRefs from '@hooks/field_refs';
|
||||
import {t} from '@i18n';
|
||||
import {getErrorMessage} from '@utils/errors';
|
||||
import {logError} from '@utils/log';
|
||||
import {sortCustomProfileAttributes, formatOptionsForSelector, isCustomFieldSamlLinked} from '@utils/user';
|
||||
|
|
@ -40,32 +39,32 @@ type Props = {
|
|||
const includesSsoService = (sso: string) => ['gitlab', 'google', 'office365'].includes(sso);
|
||||
const isSAMLOrLDAP = (protocol: string) => ['ldap', 'saml'].includes(protocol);
|
||||
|
||||
const FIELDS: { [id: string]: MessageDescriptor } = {
|
||||
const FIELDS: {[id: string]: MessageDescriptor} = defineMessages({
|
||||
firstName: {
|
||||
id: t('user.settings.general.firstName'),
|
||||
id: 'user.settings.general.firstName',
|
||||
defaultMessage: 'First Name',
|
||||
},
|
||||
lastName: {
|
||||
id: t('user.settings.general.lastName'),
|
||||
id: 'user.settings.general.lastName',
|
||||
defaultMessage: 'Last Name',
|
||||
},
|
||||
username: {
|
||||
id: t('user.settings.general.username'),
|
||||
id: 'user.settings.general.username',
|
||||
defaultMessage: 'Username',
|
||||
},
|
||||
nickname: {
|
||||
id: t('user.settings.general.nickname'),
|
||||
id: 'user.settings.general.nickname',
|
||||
defaultMessage: 'Nickname',
|
||||
},
|
||||
position: {
|
||||
id: t('user.settings.general.position'),
|
||||
id: 'user.settings.general.position',
|
||||
defaultMessage: 'Position',
|
||||
},
|
||||
email: {
|
||||
id: t('user.settings.general.email'),
|
||||
id: 'user.settings.general.email',
|
||||
defaultMessage: 'Email',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
footer: {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import PanelItem from '@screens/edit_profile/components/panel_item';
|
||||
import {bottomSheet} from '@screens/navigation';
|
||||
import PickerUtil from '@utils/file/file_picker';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ const ProfileImagePicker = ({
|
|||
const styles = getStyleSheet(theme);
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const showFileAttachmentOptions = useCallback(preventDoubleTap(() => {
|
||||
const showFileAttachmentOptions = usePreventDoubleTap(useCallback(() => {
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -116,7 +116,7 @@ const ProfileImagePicker = ({
|
|||
title: 'Change profile photo',
|
||||
theme,
|
||||
});
|
||||
}), [canRemovePicture, onRemoveProfileImage, pictureUtils, theme]);
|
||||
}, [canRemovePicture, isTablet, onRemoveProfileImage, pictureUtils, styles.title, theme]));
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ import {useServerUrl} from '@context/server';
|
|||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {dismissModal, popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {logError} from '@utils/log';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {isCustomFieldSamlLinked} from '@utils/user';
|
||||
|
||||
import ProfileForm, {CUSTOM_ATTRS_PREFIX} from './components/form';
|
||||
|
|
@ -152,7 +152,22 @@ const EditProfile = ({
|
|||
loadCustomAttributes();
|
||||
}, [currentUser, serverUrl, enableCustomAttributes, customAttributesSet]);
|
||||
|
||||
const submitUser = useCallback(preventDoubleTap(async () => {
|
||||
const resetScreenForProfileError = useCallback((resetError: unknown) => {
|
||||
setUsernameError(resetError);
|
||||
Keyboard.dismiss();
|
||||
setUpdating(false);
|
||||
enableSaveButton(true);
|
||||
}, [enableSaveButton]);
|
||||
|
||||
const resetScreen = useCallback((resetError: unknown) => {
|
||||
setError(resetError);
|
||||
Keyboard.dismiss();
|
||||
setUpdating(false);
|
||||
enableSaveButton(true);
|
||||
scrollViewRef.current?.scrollToPosition(0, 0, true);
|
||||
}, [enableSaveButton]);
|
||||
|
||||
const submitUser = usePreventDoubleTap(useCallback(async () => {
|
||||
if (!currentUser) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -243,7 +258,22 @@ const EditProfile = ({
|
|||
} catch (e) {
|
||||
resetScreen(e);
|
||||
}
|
||||
}), [userInfo, enableSaveButton, currentUser, lockedFirstName, lockedLastName, lockedNickname, lockedPosition, customAttributesSet, enableCustomAttributes, customFields, serverUrl]);
|
||||
}, [
|
||||
currentUser,
|
||||
enableSaveButton,
|
||||
userInfo,
|
||||
lockedFirstName,
|
||||
lockedLastName,
|
||||
lockedNickname,
|
||||
lockedPosition,
|
||||
enableCustomAttributes,
|
||||
close,
|
||||
serverUrl,
|
||||
resetScreen,
|
||||
resetScreenForProfileError,
|
||||
customFields,
|
||||
customAttributesSet,
|
||||
]));
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, close);
|
||||
useNavButtonPressed(UPDATE_BUTTON_ID, componentId, submitUser, [userInfo]);
|
||||
|
|
@ -298,21 +328,6 @@ const EditProfile = ({
|
|||
enableSaveButton(didChange);
|
||||
}, [userInfo, currentUser, enableSaveButton]);
|
||||
|
||||
const resetScreenForProfileError = useCallback((resetError: unknown) => {
|
||||
setUsernameError(resetError);
|
||||
Keyboard.dismiss();
|
||||
setUpdating(false);
|
||||
enableSaveButton(true);
|
||||
}, [enableSaveButton]);
|
||||
|
||||
const resetScreen = useCallback((resetError: unknown) => {
|
||||
setError(resetError);
|
||||
Keyboard.dismiss();
|
||||
setUpdating(false);
|
||||
enableSaveButton(true);
|
||||
scrollViewRef.current?.scrollToPosition(0, 0, true);
|
||||
}, [enableSaveButton]);
|
||||
|
||||
const content = currentUser ? (
|
||||
<KeyboardAwareScrollView
|
||||
bounces={false}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import React, {useCallback} from 'react';
|
|||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -37,7 +37,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
|
||||
const EmojiCategoryBarIcon = ({currentIndex, icon, index, scrollToIndex, theme}: Props) => {
|
||||
const style = getStyleSheet(theme);
|
||||
const onPress = useCallback(preventDoubleTap(() => scrollToIndex(index)), []);
|
||||
const onPress = usePreventDoubleTap(useCallback(() => scrollToIndex(index), [index, scrollToIndex]));
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Platform, SectionList, type SectionListRenderItemInfo, StyleSheet} from 'react-native';
|
||||
import Animated, {FadeInDown, FadeOutUp} from 'react-native-reanimated';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import ChannelItem from '@components/channel_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
|
||||
import FindChannelsHeader from './header';
|
||||
|
||||
|
|
@ -23,12 +22,12 @@ type Props = {
|
|||
testID?: string;
|
||||
}
|
||||
|
||||
const sectionNames = {
|
||||
const sectionNames = defineMessages({
|
||||
recent: {
|
||||
id: t('mobile.channel_list.recent'),
|
||||
id: 'mobile.channel_list.recent',
|
||||
defaultMessage: 'Recent',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const style = StyleSheet.create({
|
||||
flex: {flex: 1},
|
||||
|
|
@ -59,7 +58,7 @@ const UnfilteredList = ({close, keyboardOverlap, recentChannels, showTeamName, t
|
|||
|
||||
const renderSectionHeader = useCallback(({section}: SectionListRenderItemInfo<ChannelModel>) => (
|
||||
<FindChannelsHeader sectionName={intl.formatMessage({id: section.id, defaultMessage: section.defaultMessage})}/>
|
||||
), [intl.locale]);
|
||||
), [intl]);
|
||||
|
||||
const renderSectionItem = useCallback(({item}: SectionListRenderItemInfo<ChannelModel>) => {
|
||||
return (
|
||||
|
|
@ -72,7 +71,7 @@ const UnfilteredList = ({close, keyboardOverlap, recentChannels, showTeamName, t
|
|||
testID={`${testID}.channel_item`}
|
||||
/>
|
||||
);
|
||||
}, [onPress, showTeamName]);
|
||||
}, [onPress, showTeamName, testID]);
|
||||
|
||||
useEffect(() => {
|
||||
setSections(buildSections(recentChannels));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Keyboard, Platform, Text, View} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
|
@ -91,6 +91,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const messages = defineMessages({
|
||||
reset: {
|
||||
id: 'password_send.reset',
|
||||
defaultMessage: 'Reset Your Password',
|
||||
},
|
||||
});
|
||||
|
||||
const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [error, setError] = useState<string>('');
|
||||
|
|
@ -191,8 +198,7 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
testID={'password_send.link.prepare'}
|
||||
>
|
||||
<FormattedText
|
||||
defaultMessage='Reset Your Password'
|
||||
id='password_send.reset'
|
||||
{...messages.reset}
|
||||
testID='password_send.reset'
|
||||
style={styles.header}
|
||||
/>
|
||||
|
|
@ -225,7 +231,7 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
|
|||
disabled={!email}
|
||||
onPress={submitResetPassword}
|
||||
size='lg'
|
||||
text={formatMessage({id: 'password_send.reset', defaultMessage: 'Reset my password'})}
|
||||
text={formatMessage(messages.reset)}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ describe('GlobalThreads', () => {
|
|||
expect.any(String),
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({id: 'unreads', name: expect.objectContaining({defaultMessage: 'Unreads'})}),
|
||||
expect.objectContaining({id: 'all', name: expect.objectContaining({defaultMessage: 'All your threads'})}),
|
||||
expect.objectContaining({id: 'all', name: expect.objectContaining({defaultMessage: 'All Your Threads'})}),
|
||||
]),
|
||||
expect.any(Function),
|
||||
expect.any(String),
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const GlobalThreads = ({componentId, globalThreadsTab, hasUnreads, teamId}: Prop
|
|||
{
|
||||
name: defineMessage({
|
||||
id: 'global_threads.allThreads',
|
||||
defaultMessage: 'All your threads',
|
||||
defaultMessage: 'All Your Threads',
|
||||
}),
|
||||
id: 'all',
|
||||
requiresUserAttention: false,
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import {Screens} from '@constants';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {bottomSheetModalOptions, showModal, showModalOverCurrentContext} from '@screens/navigation';
|
||||
import {getMarkdownTextStyles} from '@utils/markdown';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
|
@ -142,9 +142,9 @@ const Thread = ({author, channel, location, post, teammateNameDisplay, testID, t
|
|||
setIsChannelNamePressed((prevState) => !prevState);
|
||||
}, []);
|
||||
|
||||
const showThread = useCallback(preventDoubleTap(() => {
|
||||
const showThread = usePreventDoubleTap(useCallback(() => {
|
||||
fetchAndSwitchToThread(serverUrl, thread.id);
|
||||
}), [serverUrl, thread.id]);
|
||||
}, [serverUrl, thread.id]));
|
||||
|
||||
const onChannelNamePressed = useCallback(() => {
|
||||
if (channel?.id) {
|
||||
|
|
@ -161,7 +161,7 @@ const Thread = ({author, channel, location, post, teammateNameDisplay, testID, t
|
|||
} else {
|
||||
showModalOverCurrentContext(Screens.THREAD_OPTIONS, passProps);
|
||||
}
|
||||
}, [isTablet, theme, thread]);
|
||||
}, [intl, isTablet, theme, thread]);
|
||||
|
||||
if (!post || !channel) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import {Events, Screens} from '@constants';
|
|||
import {SET_CUSTOM_STATUS_FAILURE} from '@constants/custom_status';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {showModal} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {getUserCustomStatus, isCustomStatusExpired as checkCustomStatusIsExpired} from '@utils/user';
|
||||
|
|
@ -62,7 +62,7 @@ const CustomStatus = ({isTablet, currentUser}: CustomStatusProps) => {
|
|||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
const clearCustomStatus = useCallback(preventDoubleTap(async () => {
|
||||
const clearCustomStatus = usePreventDoubleTap(useCallback(async () => {
|
||||
setShowRetryMessage(false);
|
||||
|
||||
const {error} = await unsetCustomStatus(serverUrl);
|
||||
|
|
@ -72,16 +72,16 @@ const CustomStatus = ({isTablet, currentUser}: CustomStatusProps) => {
|
|||
}
|
||||
|
||||
updateLocalCustomStatus(serverUrl, currentUser, undefined);
|
||||
}), []);
|
||||
}, [currentUser, serverUrl]));
|
||||
|
||||
const goToCustomStatusScreen = useCallback(preventDoubleTap(() => {
|
||||
const goToCustomStatusScreen = usePreventDoubleTap(useCallback(() => {
|
||||
if (isTablet) {
|
||||
DeviceEventEmitter.emit(Events.ACCOUNT_SELECT_TABLET_VIEW, Screens.CUSTOM_STATUS);
|
||||
} else {
|
||||
showModal(Screens.CUSTOM_STATUS, intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a custom status'}));
|
||||
}
|
||||
setShowRetryMessage(false);
|
||||
}), [isTablet]);
|
||||
}, [intl, isTablet]));
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const LogOut = () => {
|
|||
return (
|
||||
<OptionItem
|
||||
action={onLogout}
|
||||
description={intl.formatMessage({id: 'account.logout_from', defaultMessage: 'Log out from'}, {serverName: serverDisplayName})}
|
||||
description={intl.formatMessage({id: 'account.logout_from', defaultMessage: 'Log out of {serverName}'}, {serverName: serverDisplayName})}
|
||||
destructive={true}
|
||||
icon='exit-to-app'
|
||||
label={intl.formatMessage({id: 'account.logout', defaultMessage: 'Log out'})}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import General from '@constants/general';
|
|||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {TITLE_HEIGHT} from '@screens/bottom_sheet/content';
|
||||
import {bottomSheet, dismissBottomSheet, dismissModal} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {confirmOutOfOfficeDisabled} from '@utils/user';
|
||||
|
|
@ -62,7 +62,29 @@ const UserStatus = ({currentUser}: Props) => {
|
|||
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const handleSetStatus = useCallback(preventDoubleTap(() => {
|
||||
const updateStatus = useCallback((status: string) => {
|
||||
const userStatus = {
|
||||
user_id: currentUser.id,
|
||||
status,
|
||||
manual: true,
|
||||
last_activity_at: Date.now(),
|
||||
};
|
||||
|
||||
setStatus(serverUrl, userStatus);
|
||||
}, [currentUser.id, serverUrl]);
|
||||
|
||||
const setUserStatus = useCallback((status: string) => {
|
||||
if (currentUser.status === OUT_OF_OFFICE) {
|
||||
dismissModal();
|
||||
return confirmOutOfOfficeDisabled(intl, status, updateStatus);
|
||||
}
|
||||
|
||||
updateStatus(status);
|
||||
dismissBottomSheet();
|
||||
return null;
|
||||
}, [currentUser.status, intl, updateStatus]);
|
||||
|
||||
const handleSetStatus = usePreventDoubleTap(useCallback(() => {
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -131,29 +153,13 @@ const UserStatus = ({currentUser}: Props) => {
|
|||
title: intl.formatMessage({id: 'user_status.title', defaultMessage: 'Status'}),
|
||||
theme,
|
||||
});
|
||||
}), [theme]);
|
||||
|
||||
const updateStatus = useCallback((status: string) => {
|
||||
const userStatus = {
|
||||
user_id: currentUser.id,
|
||||
status,
|
||||
manual: true,
|
||||
last_activity_at: Date.now(),
|
||||
};
|
||||
|
||||
setStatus(serverUrl, userStatus);
|
||||
}, []);
|
||||
|
||||
const setUserStatus = useCallback((status: string) => {
|
||||
if (currentUser.status === OUT_OF_OFFICE) {
|
||||
dismissModal();
|
||||
return confirmOutOfOfficeDisabled(intl, status, updateStatus);
|
||||
}
|
||||
|
||||
updateStatus(status);
|
||||
dismissBottomSheet();
|
||||
return null;
|
||||
}, []);
|
||||
}, [
|
||||
intl,
|
||||
isTablet,
|
||||
setUserStatus,
|
||||
styles,
|
||||
theme,
|
||||
]));
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const UnreadCategories = ({onChannelSwitch, onlyUnreads, unreadChannels, unreadT
|
|||
<Text
|
||||
style={styles.heading}
|
||||
>
|
||||
{intl.formatMessage({id: 'mobile.channel_list.unreads', defaultMessage: 'UNREADS'})}
|
||||
{intl.formatMessage({id: 'mobile.channel_list.unreads', defaultMessage: 'Unreads'})}
|
||||
</Text>
|
||||
}
|
||||
<FlatList
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import {TouchableHighlight} from 'react-native';
|
|||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {findChannels} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -42,12 +42,12 @@ const SearchField = () => {
|
|||
const intl = useIntl();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const onPress = useCallback(preventDoubleTap(() => {
|
||||
const onPress = usePreventDoubleTap(useCallback(() => {
|
||||
findChannels(
|
||||
intl.formatMessage({id: 'find_channels.title', defaultMessage: 'Find Channels'}),
|
||||
theme,
|
||||
);
|
||||
}), [intl.locale, theme]);
|
||||
}, [intl, theme]));
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
|
|
|
|||
|
|
@ -52,22 +52,22 @@ const getModifiersSectionsData = (intl: IntlShape, teamId: string): ModifierItem
|
|||
sectionsData.push({
|
||||
term: 'From:',
|
||||
testID: 'search.modifier.from',
|
||||
description: formatMessage({id: 'mobile.search.modifier.from', defaultMessage: ' a specific user'}),
|
||||
description: formatMessage({id: 'mobile.search.modifier.from', defaultMessage: 'a specific user'}),
|
||||
}, {
|
||||
term: 'In:',
|
||||
testID: 'search.modifier.in',
|
||||
description: formatMessage({id: 'mobile.search.modifier.in', defaultMessage: ' a specific channel'}),
|
||||
description: formatMessage({id: 'mobile.search.modifier.in', defaultMessage: 'a specific channel'}),
|
||||
});
|
||||
}
|
||||
|
||||
sectionsData.push({
|
||||
term: '-',
|
||||
testID: 'search.modifier.exclude',
|
||||
description: formatMessage({id: 'mobile.search.modifier.exclude', defaultMessage: ' exclude search terms'}),
|
||||
description: formatMessage({id: 'mobile.search.modifier.exclude', defaultMessage: 'exclude search terms'}),
|
||||
}, {
|
||||
term: '""',
|
||||
testID: 'search.modifier.phrases',
|
||||
description: formatMessage({id: 'mobile.search.modifier.phrases', defaultMessage: ' messages with phrases'}),
|
||||
description: formatMessage({id: 'mobile.search.modifier.phrases', defaultMessage: 'messages with phrases'}),
|
||||
cursorPosition: -1,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import {DeviceEventEmitter, Platform, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
|
||||
import {GestureDetector, Gesture, GestureHandlerRootView} from 'react-native-gesture-handler';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
|
@ -12,9 +12,9 @@ import {openNotification} from '@actions/remote/notifications';
|
|||
import {Navigation as NavigationTypes} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {dismissOverlay} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
import {secureGetFromRecord} from '@utils/types';
|
||||
|
||||
|
|
@ -95,15 +95,15 @@ const InAppNotification = ({componentId, serverName, serverUrl, notification}: I
|
|||
}
|
||||
};
|
||||
|
||||
const dismiss = () => {
|
||||
const dismiss = useCallback(() => {
|
||||
cancelDismissTimer();
|
||||
dismissOverlay(componentId);
|
||||
};
|
||||
}, [componentId]);
|
||||
|
||||
const notificationTapped = preventDoubleTap(() => {
|
||||
const notificationTapped = usePreventDoubleTap(useCallback(() => {
|
||||
tapped.current = true;
|
||||
dismiss();
|
||||
});
|
||||
}, [dismiss]));
|
||||
|
||||
useEffect(() => {
|
||||
initial.value = 0;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue