diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts
index 525876325..2138269e2 100644
--- a/src/api/types/misc.ts
+++ b/src/api/types/misc.ts
@@ -1,7 +1,15 @@
import type { CallbackAction } from '../../global/types';
import type { IconName } from '../../types/icons';
import type { LangFnParameters, RegularLangFnParameters } from '../../util/localization';
-import type { ApiDocument, ApiFormattedText, ApiMessageEntity, ApiPhoto, ApiReaction, ApiVideo } from './messages';
+import type {
+ ApiContact,
+ ApiDocument,
+ ApiFormattedText,
+ ApiMessageEntity,
+ ApiPhoto,
+ ApiReaction,
+ ApiVideo,
+} from './messages';
import type { ApiPremiumSection } from './payments';
import type { ApiBotVerification } from './peers';
import type { ApiStarsSubscriptionPricing } from './stars';
@@ -143,6 +151,27 @@ export type ApiNotification = {
messageEntities?: undefined;
});
+export type ApiDialogError = {
+ type: 'error';
+} & ApiError;
+
+export type ApiDialogMessage = {
+ type: 'message';
+ text: ApiFormattedText;
+};
+
+export type ApiDialogContact = {
+ type: 'contact';
+ contact: ApiContact;
+};
+
+export type ApiDialogLocalizedMessage = {
+ type: 'localized';
+ text: LangFnParameters;
+};
+
+export type ApiDialog = ApiDialogError | ApiDialogMessage | ApiDialogContact | ApiDialogLocalizedMessage;
+
export type ApiError = {
message: string;
entities?: ApiMessageEntity[];
diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings
index 37ad714bf..693d75f67 100644
--- a/src/assets/localization/fallback.strings
+++ b/src/assets/localization/fallback.strings
@@ -663,7 +663,8 @@
"AttachDocument" = "File";
"Poll" = "Poll";
"SlowModePlaceholder" = "Next message in {timer}";
-"SlowModeHint" = "Slow Mode is active. You can send\nyour next message in {time}.";
+"SlowModeHint" = "Slow Mode is active. You can send your next message in {time}.";
+"NoVoiceMessagesAllowed" = "**{user}** doesn't accept voice messages.";
"SendMessageAsTitle" = "Send message as...";
"Message" = "Message";
"RecentStickers" = "Recently Used";
@@ -737,6 +738,8 @@
"ErrorPasswordChanged" = "Password has been changed, please try again";
"ErrorPasswordMissing" = "You must set 2FA password to use this feature";
"ErrorPasskeyUnknown" = "This passkey is not assigned to any account";
+"ErrorMessageTooLong_one" = "The provided message is too long. Please remove {count} character.";
+"ErrorMessageTooLong_other" = "The provided message is too long. Please remove {count} characters.";
"ErrorUrlExpired" = "This link has expired";
"ErrorUnspecified" = "Error";
"NoStickers" = "No stickers yet";
diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx
index 91e69eb8f..b662fb411 100644
--- a/src/components/common/Composer.tsx
+++ b/src/components/common/Composer.tsx
@@ -1050,12 +1050,16 @@ const Composer = ({
const extraLength = text.length - maxLength;
showDialog({
data: {
- message: 'MESSAGE_TOO_LONG_PLEASE_REMOVE_CHARACTERS',
- textParams: {
- '{EXTRA_CHARS_COUNT}': extraLength.toString(),
- '{PLURAL_S}': extraLength > 1 ? 's' : '',
+ type: 'localized',
+ text: {
+ key: 'ErrorMessageTooLong',
+ variables: {
+ count: extraLength,
+ },
+ options: {
+ pluralValue: extraLength,
+ },
},
- hasErrorKey: true,
},
});
@@ -1080,11 +1084,16 @@ const Composer = ({
const secondsRemaining = nextSendDateNotReached
? slowMode.nextSendDate! - nowSeconds
: slowMode.seconds - secondsSinceLastMessage!;
+
showDialog({
data: {
- message: oldLang('SlowModeHint', formatMediaDuration(secondsRemaining)),
- isSlowMode: true,
- hasErrorKey: false,
+ type: 'localized',
+ text: {
+ key: 'SlowModeHint',
+ variables: {
+ time: formatMediaDuration(secondsRemaining),
+ },
+ },
},
});
diff --git a/src/components/common/helpers/renderTextWithEntities.tsx b/src/components/common/helpers/renderTextWithEntities.tsx
index 283d5bc4e..4b9c264f1 100644
--- a/src/components/common/helpers/renderTextWithEntities.tsx
+++ b/src/components/common/helpers/renderTextWithEntities.tsx
@@ -733,7 +733,7 @@ function processEntityAsHtml(
case ApiMessageEntityTypes.TextUrl:
return `${renderedContent}`;
diff --git a/src/components/main/Dialogs.tsx b/src/components/main/Dialogs.tsx
index 89ed0aeda..90e6cc99b 100644
--- a/src/components/main/Dialogs.tsx
+++ b/src/components/main/Dialogs.tsx
@@ -1,8 +1,13 @@
+import type { TeactNode } from '../../lib/teact/teact';
import { memo, useEffect } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type {
- ApiContact, ApiError,
+ ApiContact,
+ ApiDialog,
+ ApiDialogError,
+ ApiDialogLocalizedMessage,
+ ApiDialogMessage,
} from '../../api/types';
import type { MessageList } from '../../types';
@@ -18,7 +23,7 @@ import Modal from '../ui/Modal';
type StateProps = {
currentMessageList?: MessageList;
- dialogs: (ApiError | ApiContact)[];
+ dialogs: ApiDialog[];
};
const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
@@ -81,17 +86,16 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
);
};
- const renderError = (error: ApiError) => {
+ const renderTextDialog = (renderedText: TeactNode, title = 'Telegram') => {
return (
- {error.hasErrorKey ? getReadableErrorText(error)
- : renderTextWithEntities({ text: error.message, entities: error.entities })}
+ {renderedText}
@@ -99,9 +103,37 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
);
};
- const renderDialog = (dialog: ApiError | ApiContact) => {
- if ('phoneNumber' in dialog) {
- return renderContactRequest(dialog);
+ const renderFormattedTextDialog = (dialog: ApiDialogMessage, title?: string) => {
+ const renderedText = renderTextWithEntities(dialog.text);
+
+ return renderTextDialog(renderedText, title);
+ };
+
+ const renderLocalizedDialog = (dialog: ApiDialogLocalizedMessage, title?: string) => {
+ return renderTextDialog(lang.with(dialog.text), title);
+ };
+
+ const renderError = (error: ApiDialogError) => {
+ const renderedErrorMessage = error.hasErrorKey
+ ? getReadableErrorText(error)
+ : error.entities?.length
+ ? renderTextWithEntities({ text: error.message, entities: error.entities })
+ : error.message;
+
+ return renderTextDialog(renderedErrorMessage, getErrorHeader(error));
+ };
+
+ const renderDialog = (dialog: ApiDialog) => {
+ if (dialog.type === 'contact') {
+ return renderContactRequest(dialog.contact);
+ }
+
+ if (dialog.type === 'message') {
+ return renderFormattedTextDialog(dialog);
+ }
+
+ if (dialog.type === 'localized') {
+ return renderLocalizedDialog(dialog);
}
return renderError(dialog);
@@ -110,7 +142,7 @@ const Dialogs = ({ dialogs, currentMessageList }: StateProps) => {
return Boolean(dialogs.length) && renderDialog(dialogs[dialogs.length - 1]);
};
-function getErrorHeader(error: ApiError) {
+function getErrorHeader(error: ApiDialogError) {
if (error.isSlowMode) {
return 'Slowmode enabled';
}
diff --git a/src/components/middle/message/SponsoredContextMenuContainer.tsx b/src/components/middle/message/SponsoredContextMenuContainer.tsx
index 08264de53..6cb8bea90 100644
--- a/src/components/middle/message/SponsoredContextMenuContainer.tsx
+++ b/src/components/middle/message/SponsoredContextMenuContainer.tsx
@@ -71,7 +71,10 @@ const SponsoredMessageContextMenuContainer: FC = ({
const handleSponsorInfo = useLastCallback(() => {
showDialog({
data: {
- message: [sponsorInfo, additionalInfo].filter(Boolean).join('\n'),
+ type: 'message',
+ text: {
+ text: [sponsorInfo, additionalInfo].filter(Boolean).join('\n'),
+ },
},
});
handleItemClick();
diff --git a/src/components/middle/message/_message-content.scss b/src/components/middle/message/_message-content.scss
index e2f9cfc47..e2b0ea1f7 100644
--- a/src/components/middle/message/_message-content.scss
+++ b/src/components/middle/message/_message-content.scss
@@ -402,8 +402,9 @@
user-select: none;
display: flex;
- align-items: center;
gap: 0.25rem;
+ align-items: center;
+
margin-left: 1rem;
}
@@ -428,10 +429,8 @@
.sender-boosts {
user-select: none;
-
display: flex;
align-items: center;
-
font-size: 0.75rem;
}
}
diff --git a/src/global/actions/api/bots.ts b/src/global/actions/api/bots.ts
index cdc1b103d..358e600cc 100644
--- a/src/global/actions/api/bots.ts
+++ b/src/global/actions/api/bots.ts
@@ -6,7 +6,6 @@ import type {
} from '../../types';
import {
type ApiChat,
- type ApiContact,
type ApiInputMessageReplyInfo,
type ApiPeer,
type ApiUrlAuthResult,
@@ -138,11 +137,15 @@ addActionHandler('clickBotInlineButton', (global, actions, payload): ActionRetur
}
actions.showDialog({
data: {
- phoneNumber: user.phoneNumber,
- firstName: user.firstName || '',
- lastName: user.lastName || '',
- userId: user.id,
- } as ApiContact,
+ type: 'contact',
+ contact: {
+ mediaType: 'contact',
+ phoneNumber: user.phoneNumber,
+ firstName: user.firstName || '',
+ lastName: user.lastName || '',
+ userId: user.id,
+ },
+ },
tabId,
});
break;
@@ -1438,7 +1441,7 @@ async function answerCallbackButton(
const { message, alert: isError, url } = result;
if (isError) {
- showDialog({ data: { message: message || 'Error' }, tabId });
+ showDialog({ data: { type: 'error', message: message || 'Error' }, tabId });
} else if (message) {
showNotification({ message, tabId });
} else if (url) {
diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts
index 862f4f82c..f5a770df8 100644
--- a/src/global/actions/api/chats.ts
+++ b/src/global/actions/api/chats.ts
@@ -789,7 +789,7 @@ addActionHandler('createChannel', async (global, actions, payload): Promise
if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'channels', tabId });
} else {
- actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
+ actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
}
}
});
@@ -2651,7 +2651,7 @@ addActionHandler('toggleForum', async (global, actions, payload): Promise
if ((error as ApiError).message === 'FLOOD') {
actions.showNotification({ message: langProvider.oldTranslate('FloodWait'), tabId });
} else {
- actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
+ actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
}
}
@@ -2860,7 +2860,7 @@ addActionHandler('joinChatlistInvite', async (global, actions, payload): Promise
if ((error as ApiError).message === 'CHATLISTS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'chatlistJoined', tabId });
} else {
- actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
+ actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
}
}
});
@@ -2946,7 +2946,7 @@ addActionHandler('createChatlistInvite', async (global, actions, payload): Promi
actions.openLimitReachedModal({ limit: 'chatlistInvites', tabId });
actions.openSettingsScreen({ screen: SettingsScreens.Folders, tabId });
} else {
- actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
+ actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
}
}
@@ -3031,7 +3031,7 @@ addActionHandler('editChatlistInvite', async (global, actions, payload): Promise
};
setGlobal(global);
} catch (error) {
- actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
+ actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
} finally {
global = getGlobal();
@@ -3532,7 +3532,7 @@ export async function migrateChat(
if ((error as ApiError).message === 'CHANNELS_TOO_MUCH') {
actions.openLimitReachedModal({ limit: 'channels', tabId });
} else {
- actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
+ actions.showDialog({ data: { type: 'error', ...(error as ApiError), hasErrorKey: true }, tabId });
}
return undefined;
diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts
index 22f4d5917..6d9de7b4b 100644
--- a/src/global/actions/api/messages.ts
+++ b/src/global/actions/api/messages.ts
@@ -2567,7 +2567,17 @@ addActionHandler('setForwardChatOrTopic', async (global, actions, payload): Prom
if (isSelectForwardsContainVoiceMessages && user && !await checkIfVoiceMessagesAllowed(global, user, chatId)) {
actions.showDialog({
data: {
- message: oldTranslate('VoiceMessagesRestrictedByPrivacy', getUserFullName(user)),
+ type: 'localized',
+ text: {
+ key: 'NoVoiceMessagesAllowed',
+ variables: {
+ user: getUserFullName(user),
+ },
+ options: {
+ withNodes: true,
+ withMarkdown: true,
+ },
+ },
},
tabId,
});
diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts
index 0a7d6450b..1d75ed5e6 100644
--- a/src/global/actions/api/payments.ts
+++ b/src/global/actions/api/payments.ts
@@ -1329,5 +1329,5 @@ function handlePaymentFormError(error: string, tabId: number) {
return;
}
- getActions().showDialog({ data: { message: error, hasErrorKey: true }, tabId });
+ getActions().showDialog({ data: { type: 'error', message: error, hasErrorKey: true }, tabId });
}
diff --git a/src/global/actions/apiUpdaters/initial.ts b/src/global/actions/apiUpdaters/initial.ts
index d0554c81c..e980c49be 100644
--- a/src/global/actions/apiUpdaters/initial.ts
+++ b/src/global/actions/apiUpdaters/initial.ts
@@ -101,7 +101,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
} else if (shouldClosePaymentModal(update.error)) {
actions.closePaymentModal({ tabId });
} else if (actions.showDialog) {
- actions.showDialog({ data: update.error, tabId });
+ actions.showDialog({ data: { type: 'error', ...update.error }, tabId });
}
});
diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts
index 316612e7f..a5c5179d5 100644
--- a/src/global/actions/ui/misc.ts
+++ b/src/global/actions/ui/misc.ts
@@ -1,7 +1,6 @@
import { addCallback } from '../../../lib/teact/teactn';
import type { ActionReturnType, GlobalState } from '../../types';
-import { type ApiError } from '../../../api/types';
import {
ANIMATION_WAVE_MIN_INTERVAL,
@@ -389,13 +388,15 @@ addActionHandler('showDialog', (global, actions, payload): ActionReturnType => {
const { data, tabId = getCurrentTabId() } = payload;
// Filter out errors that we don't want to show to the user
- if ('message' in data && data.hasErrorKey && !getReadableErrorText(data)) {
+ if (data.type === 'error' && data.hasErrorKey && !getReadableErrorText(data)) {
return global;
}
const newDialogs = [...selectTabState(global, tabId).dialogs];
- if ('message' in data) {
- const existingErrorIndex = newDialogs.findIndex((err) => (err as ApiError).message === data.message);
+ if (data.type === 'error') {
+ const existingErrorIndex = newDialogs.findIndex((dialog) => {
+ return dialog.type === 'error' && dialog.message === data.message;
+ });
if (existingErrorIndex !== -1) {
newDialogs.splice(existingErrorIndex, 1);
}
diff --git a/src/global/types/tabState.ts b/src/global/types/tabState.ts
index 1921ebfc3..76df72ebd 100644
--- a/src/global/types/tabState.ts
+++ b/src/global/types/tabState.ts
@@ -10,9 +10,8 @@ import type {
ApiChatType,
ApiCheckedGiftCode,
ApiCollectibleInfo,
- ApiContact,
+ ApiDialog,
ApiEmojiStatusCollectible,
- ApiError,
ApiFormattedText,
ApiGeoPoint,
ApiGlobalMessageSearchType,
@@ -490,7 +489,7 @@ export type TabState = {
};
notifications: ApiNotification[];
- dialogs: (ApiError | ApiContact)[];
+ dialogs: ApiDialog[];
safeLinkModalUrl?: string;
mapModal?: {
diff --git a/src/types/language.d.ts b/src/types/language.d.ts
index 3e7b5dc7f..375fc3f50 100644
--- a/src/types/language.d.ts
+++ b/src/types/language.d.ts
@@ -2222,6 +2222,9 @@ export interface LangPairWithVariables {
'SlowModeHint': {
'time': V;
};
+ 'NoVoiceMessagesAllowed': {
+ 'user': V;
+ };
'ErrorFloodTime': {
'time': V;
};
@@ -3663,6 +3666,9 @@ export interface LangPairPluralWithVariables {
'PreviewSenderSendFile': {
'count': V;
};
+ 'ErrorMessageTooLong': {
+ 'count': V;
+ };
'PinnedMessageTitle': {
'index': V;
};
diff --git a/src/util/setupServiceWorker.ts b/src/util/setupServiceWorker.ts
index 148c5f184..a0972bb25 100644
--- a/src/util/setupServiceWorker.ts
+++ b/src/util/setupServiceWorker.ts
@@ -86,7 +86,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
}
if (!IS_IOS && !IS_ANDROID && !IS_TEST) {
- getActions().showDialog?.({ data: { message: 'SERVICE_WORKER_DISABLED', hasErrorKey: true } });
+ getActions().showDialog?.({ data: { type: 'error', message: 'SERVICE_WORKER_DISABLED', hasErrorKey: true } });
}
}
} catch (err) {