Mini Apps: Support close confirmation (#5150)
This commit is contained in:
parent
5e74d13044
commit
dcfd14f85b
@ -1370,4 +1370,7 @@
|
|||||||
"StarsSubscribeInfoLinkText" = "Terms of Service";
|
"StarsSubscribeInfoLinkText" = "Terms of Service";
|
||||||
"StarsSubscribeInfoLink" = "https://telegram.org/tos/stars";
|
"StarsSubscribeInfoLink" = "https://telegram.org/tos/stars";
|
||||||
"StarsPerMonth" = "⭐️{amount}/month";
|
"StarsPerMonth" = "⭐️{amount}/month";
|
||||||
|
"AreYouSureCloseMiniApps" = "Are you sure you want to close all Mini Apps?";
|
||||||
|
"CloseMiniApps" = "Close Mini Apps";
|
||||||
|
"DoNotAskAgain" = "Don't ask again";
|
||||||
"PaymentInfoDone" = "Proceed to checkout";
|
"PaymentInfoDone" = "Proceed to checkout";
|
||||||
|
|||||||
@ -90,3 +90,4 @@ export { default as PaymentModal } from '../components/payment/PaymentModal';
|
|||||||
export { default as ReceiptModal } from '../components/payment/ReceiptModal';
|
export { default as ReceiptModal } from '../components/payment/ReceiptModal';
|
||||||
export { default as InviteViaLinkModal } from '../components/modals/inviteViaLink/InviteViaLinkModal';
|
export { default as InviteViaLinkModal } from '../components/modals/inviteViaLink/InviteViaLinkModal';
|
||||||
export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal';
|
export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal';
|
||||||
|
export { default as WebAppsCloseConfirmationModal } from '../components/main/WebAppsCloseConfirmationModal';
|
||||||
|
|||||||
16
src/components/main/WebAppsCloseConfirmationModal.async.tsx
Normal file
16
src/components/main/WebAppsCloseConfirmationModal.async.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import React from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import { Bundles } from '../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const WebAppsCloseConfirmationModalAsync: FC = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const WebAppsCloseConfirmationModal = useModuleLoader(Bundles.Extra, 'WebAppsCloseConfirmationModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return WebAppsCloseConfirmationModal ? <WebAppsCloseConfirmationModal isOpen={modal} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebAppsCloseConfirmationModalAsync;
|
||||||
82
src/components/main/WebAppsCloseConfirmationModal.tsx
Normal file
82
src/components/main/WebAppsCloseConfirmationModal.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo, useCallback, useRef, useState,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
|
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Button from '../ui/Button';
|
||||||
|
import Checkbox from '../ui/Checkbox';
|
||||||
|
import Modal from '../ui/Modal';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
isOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WebAppsCloseConfirmationModal: FC<OwnProps> = ({
|
||||||
|
isOpen,
|
||||||
|
}) => {
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
const { closeWebAppsCloseConfirmationModal, closeWebAppModal } = getActions();
|
||||||
|
|
||||||
|
const [shouldSkipInFuture, setShouldSkipInFuture] = useState(false);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const onClose = useCallback(() => {
|
||||||
|
closeWebAppsCloseConfirmationModal({ shouldSkipInFuture });
|
||||||
|
}, [shouldSkipInFuture]);
|
||||||
|
|
||||||
|
const confirmHandler = useCallback(() => {
|
||||||
|
closeWebAppModal({ shouldSkipConfirmation: true });
|
||||||
|
closeWebAppsCloseConfirmationModal({ shouldSkipInFuture });
|
||||||
|
}, [shouldSkipInFuture]);
|
||||||
|
|
||||||
|
const handleSelectWithEnter = useCallback((index: number) => {
|
||||||
|
if (index === -1) confirmHandler();
|
||||||
|
}, [confirmHandler]);
|
||||||
|
|
||||||
|
const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, handleSelectWithEnter, '.Button');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className={buildClassName('confirm')}
|
||||||
|
title={lang('CloseMiniApps')}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<p>{lang('AreYouSureCloseMiniApps')}</p>
|
||||||
|
<Checkbox
|
||||||
|
label={lang('DoNotAskAgain')}
|
||||||
|
checked={shouldSkipInFuture}
|
||||||
|
onCheck={setShouldSkipInFuture}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="dialog-buttons mt-2"
|
||||||
|
ref={containerRef}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
isText
|
||||||
|
onClick={confirmHandler}
|
||||||
|
color="danger"
|
||||||
|
>
|
||||||
|
{oldLang('Confirm')}
|
||||||
|
</Button>
|
||||||
|
<Button className="confirm-dialog-button" isText onClick={onClose}>
|
||||||
|
{oldLang('Cancel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(WebAppsCloseConfirmationModal);
|
||||||
@ -6,6 +6,7 @@ import type { TabState } from '../../global/types';
|
|||||||
import { selectTabState } from '../../global/selectors';
|
import { selectTabState } from '../../global/selectors';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
|
|
||||||
|
import WebAppsCloseConfirmationModal from '../main/WebAppsCloseConfirmationModal.async';
|
||||||
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
||||||
import BoostModal from './boost/BoostModal.async';
|
import BoostModal from './boost/BoostModal.async';
|
||||||
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
||||||
@ -53,6 +54,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'starsGiftModal' |
|
'starsGiftModal' |
|
||||||
'giftModal' |
|
'giftModal' |
|
||||||
'isGiftRecipientPickerOpen' |
|
'isGiftRecipientPickerOpen' |
|
||||||
|
'isWebAppsCloseConfirmationModalOpen' |
|
||||||
'giftInfoModal'
|
'giftInfoModal'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -90,6 +92,7 @@ const MODALS: ModalRegistry = {
|
|||||||
starsGiftModal: StarsGiftModal,
|
starsGiftModal: StarsGiftModal,
|
||||||
giftModal: PremiumGiftModal,
|
giftModal: PremiumGiftModal,
|
||||||
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
||||||
|
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
||||||
giftInfoModal: GiftInfoModal,
|
giftInfoModal: GiftInfoModal,
|
||||||
};
|
};
|
||||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||||
|
|||||||
@ -30,8 +30,8 @@ import {
|
|||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
activateWebAppIfOpen,
|
activateWebAppIfOpen,
|
||||||
addWebAppToOpenList, clearOpenedWebApps, hasOpenedWebApps,
|
addWebAppToOpenList, clearOpenedWebApps, hasOpenedMoreThanOneWebApps,
|
||||||
removeActiveWebAppFromOpenList, removeWebAppFromOpenList,
|
hasOpenedWebApps, removeActiveWebAppFromOpenList, removeWebAppFromOpenList,
|
||||||
replaceInlineBotSettings, replaceInlineBotsIsLoading,
|
replaceInlineBotSettings, replaceInlineBotsIsLoading,
|
||||||
replaceIsWebAppModalOpen, replaceWebAppModalState, updateWebApp,
|
replaceIsWebAppModalOpen, replaceWebAppModalState, updateWebApp,
|
||||||
} from '../../reducers/bots';
|
} from '../../reducers/bots';
|
||||||
@ -704,6 +704,35 @@ addActionHandler('openWebAppTab', (global, actions, payload): ActionReturnType =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openWebAppsCloseConfirmationModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
isWebAppsCloseConfirmationModalOpen: true,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeWebAppsCloseConfirmationModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { shouldSkipInFuture, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
settings: {
|
||||||
|
...global.settings,
|
||||||
|
byKey: {
|
||||||
|
...global.settings.byKey,
|
||||||
|
shouldSkipWebAppCloseConfirmation: Boolean(shouldSkipInFuture),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
isWebAppsCloseConfirmationModalOpen: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('requestAppWebView', async (global, actions, payload): Promise<void> => {
|
addActionHandler('requestAppWebView', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
botId, appName, startApp, theme, isWriteAllowed, isFromConfirm, shouldSkipBotTrustRequest,
|
botId, appName, startApp, theme, isWriteAllowed, isFromConfirm, shouldSkipBotTrustRequest,
|
||||||
@ -869,7 +898,15 @@ addActionHandler('closeWebApp', (global, actions, payload): ActionReturnType =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('closeWebAppModal', (global, actions, payload): ActionReturnType => {
|
addActionHandler('closeWebAppModal', (global, actions, payload): ActionReturnType => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { shouldSkipConfirmation, tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
const shouldShowConfirmation = !shouldSkipConfirmation
|
||||||
|
&& !global.settings.byKey.shouldSkipWebAppCloseConfirmation && hasOpenedMoreThanOneWebApps(global, tabId);
|
||||||
|
|
||||||
|
if (shouldShowConfirmation) {
|
||||||
|
actions.openWebAppsCloseConfirmationModal({ tabId });
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
global = clearOpenedWebApps(global, tabId);
|
global = clearOpenedWebApps(global, tabId);
|
||||||
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
||||||
|
|||||||
@ -269,6 +269,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
notificationSoundVolume: 5,
|
notificationSoundVolume: 5,
|
||||||
shouldSuggestStickers: true,
|
shouldSuggestStickers: true,
|
||||||
shouldSuggestCustomEmoji: true,
|
shouldSuggestCustomEmoji: true,
|
||||||
|
shouldSkipWebAppCloseConfirmation: false,
|
||||||
shouldUpdateStickerSetOrder: true,
|
shouldUpdateStickerSetOrder: true,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
timeFormat: '24h',
|
timeFormat: '24h',
|
||||||
@ -386,6 +387,8 @@ export const INITIAL_TAB_STATE: TabState = {
|
|||||||
|
|
||||||
isShareMessageModalShown: false,
|
isShareMessageModalShown: false,
|
||||||
|
|
||||||
|
isWebAppsCloseConfirmationModalOpen: false,
|
||||||
|
|
||||||
forwardMessages: {},
|
forwardMessages: {},
|
||||||
|
|
||||||
replyingMessage: {},
|
replyingMessage: {},
|
||||||
|
|||||||
@ -246,6 +246,12 @@ export function hasOpenedWebApps<T extends GlobalState>(
|
|||||||
return Object.keys(selectTabState(global, tabId).webApps.openedWebApps).length > 0;
|
return Object.keys(selectTabState(global, tabId).webApps.openedWebApps).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasOpenedMoreThanOneWebApps<T extends GlobalState>(
|
||||||
|
global: T, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
): boolean {
|
||||||
|
return Object.keys(selectTabState(global, tabId).webApps.openedWebApps).length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
export function replaceWebAppModalState<T extends GlobalState>(
|
export function replaceWebAppModalState<T extends GlobalState>(
|
||||||
global: T, modalState: WebAppModalStateType,
|
global: T, modalState: WebAppModalStateType,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
|
|||||||
@ -772,6 +772,8 @@ export type TabState = {
|
|||||||
onConfirm?: NoneToVoidFunction;
|
onConfirm?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isWebAppsCloseConfirmationModalOpen?: boolean;
|
||||||
|
|
||||||
isGiftRecipientPickerOpen?: boolean;
|
isGiftRecipientPickerOpen?: boolean;
|
||||||
|
|
||||||
starsGiftingPickerModal?: {
|
starsGiftingPickerModal?: {
|
||||||
@ -3243,7 +3245,9 @@ export interface ActionPayloads {
|
|||||||
webApp: WebApp;
|
webApp: WebApp;
|
||||||
skipClosingConfirmation?: boolean;
|
skipClosingConfirmation?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
closeWebAppModal: WithTabId | undefined;
|
closeWebAppModal: ({
|
||||||
|
shouldSkipConfirmation?: boolean;
|
||||||
|
} & WithTabId) | undefined;
|
||||||
changeWebAppModalState: WithTabId | undefined;
|
changeWebAppModalState: WithTabId | undefined;
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
@ -3454,6 +3458,12 @@ export interface ActionPayloads {
|
|||||||
openGiftRecipientPicker: WithTabId | undefined;
|
openGiftRecipientPicker: WithTabId | undefined;
|
||||||
closeGiftRecipientPicker: WithTabId | undefined;
|
closeGiftRecipientPicker: WithTabId | undefined;
|
||||||
|
|
||||||
|
openWebAppsCloseConfirmationModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
closeWebAppsCloseConfirmationModal: ({
|
||||||
|
shouldSkipInFuture?: boolean;
|
||||||
|
} & WithTabId);
|
||||||
|
|
||||||
openStarsGiftingPickerModal: WithTabId | undefined;
|
openStarsGiftingPickerModal: WithTabId | undefined;
|
||||||
closeStarsGiftingPickerModal: WithTabId | undefined;
|
closeStarsGiftingPickerModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
|||||||
@ -131,6 +131,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
|
|||||||
shouldCollectDebugLogs?: boolean;
|
shouldCollectDebugLogs?: boolean;
|
||||||
shouldDebugExportedSenders?: boolean;
|
shouldDebugExportedSenders?: boolean;
|
||||||
shouldWarnAboutSvg?: boolean;
|
shouldWarnAboutSvg?: boolean;
|
||||||
|
shouldSkipWebAppCloseConfirmation: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiPrivacySettings {
|
export interface ApiPrivacySettings {
|
||||||
|
|||||||
5
src/types/language.d.ts
vendored
5
src/types/language.d.ts
vendored
@ -1141,6 +1141,10 @@ export interface LangPair {
|
|||||||
'StarGiftAvailability': undefined;
|
'StarGiftAvailability': undefined;
|
||||||
'StarsSubscribeInfoLinkText': undefined;
|
'StarsSubscribeInfoLinkText': undefined;
|
||||||
'StarsSubscribeInfoLink': undefined;
|
'StarsSubscribeInfoLink': undefined;
|
||||||
|
'AreYouSureCloseMiniApps': undefined;
|
||||||
|
'CloseMiniApps': undefined;
|
||||||
|
'DoNotAskAgain': undefined;
|
||||||
|
'PaymentInfoDone': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||||
@ -1530,7 +1534,6 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
|||||||
'StarsPerMonth': {
|
'StarsPerMonth': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
'PaymentInfoDone': undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairPlural {
|
export interface LangPairPlural {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user