Mini Apps: Support close confirmation (#5150)
This commit is contained in:
parent
5e74d13044
commit
dcfd14f85b
@ -1370,4 +1370,7 @@
|
||||
"StarsSubscribeInfoLinkText" = "Terms of Service";
|
||||
"StarsSubscribeInfoLink" = "https://telegram.org/tos/stars";
|
||||
"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";
|
||||
|
||||
@ -90,3 +90,4 @@ export { default as PaymentModal } from '../components/payment/PaymentModal';
|
||||
export { default as ReceiptModal } from '../components/payment/ReceiptModal';
|
||||
export { default as InviteViaLinkModal } from '../components/modals/inviteViaLink/InviteViaLinkModal';
|
||||
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 { pick } from '../../util/iteratees';
|
||||
|
||||
import WebAppsCloseConfirmationModal from '../main/WebAppsCloseConfirmationModal.async';
|
||||
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
||||
import BoostModal from './boost/BoostModal.async';
|
||||
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
||||
@ -53,6 +54,7 @@ type ModalKey = keyof Pick<TabState,
|
||||
'starsGiftModal' |
|
||||
'giftModal' |
|
||||
'isGiftRecipientPickerOpen' |
|
||||
'isWebAppsCloseConfirmationModalOpen' |
|
||||
'giftInfoModal'
|
||||
>;
|
||||
|
||||
@ -90,6 +92,7 @@ const MODALS: ModalRegistry = {
|
||||
starsGiftModal: StarsGiftModal,
|
||||
giftModal: PremiumGiftModal,
|
||||
isGiftRecipientPickerOpen: GiftRecipientPicker,
|
||||
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
||||
giftInfoModal: GiftInfoModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
|
||||
@ -30,8 +30,8 @@ import {
|
||||
} from '../../reducers';
|
||||
import {
|
||||
activateWebAppIfOpen,
|
||||
addWebAppToOpenList, clearOpenedWebApps, hasOpenedWebApps,
|
||||
removeActiveWebAppFromOpenList, removeWebAppFromOpenList,
|
||||
addWebAppToOpenList, clearOpenedWebApps, hasOpenedMoreThanOneWebApps,
|
||||
hasOpenedWebApps, removeActiveWebAppFromOpenList, removeWebAppFromOpenList,
|
||||
replaceInlineBotSettings, replaceInlineBotsIsLoading,
|
||||
replaceIsWebAppModalOpen, replaceWebAppModalState, updateWebApp,
|
||||
} 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> => {
|
||||
const {
|
||||
botId, appName, startApp, theme, isWriteAllowed, isFromConfirm, shouldSkipBotTrustRequest,
|
||||
@ -869,7 +898,15 @@ addActionHandler('closeWebApp', (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);
|
||||
if (!hasOpenedWebApps(global, tabId)) return replaceIsWebAppModalOpen(global, false, tabId);
|
||||
|
||||
@ -269,6 +269,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
notificationSoundVolume: 5,
|
||||
shouldSuggestStickers: true,
|
||||
shouldSuggestCustomEmoji: true,
|
||||
shouldSkipWebAppCloseConfirmation: false,
|
||||
shouldUpdateStickerSetOrder: true,
|
||||
language: 'en',
|
||||
timeFormat: '24h',
|
||||
@ -386,6 +387,8 @@ export const INITIAL_TAB_STATE: TabState = {
|
||||
|
||||
isShareMessageModalShown: false,
|
||||
|
||||
isWebAppsCloseConfirmationModalOpen: false,
|
||||
|
||||
forwardMessages: {},
|
||||
|
||||
replyingMessage: {},
|
||||
|
||||
@ -246,6 +246,12 @@ export function hasOpenedWebApps<T extends GlobalState>(
|
||||
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>(
|
||||
global: T, modalState: WebAppModalStateType,
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
|
||||
@ -772,6 +772,8 @@ export type TabState = {
|
||||
onConfirm?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
isWebAppsCloseConfirmationModalOpen?: boolean;
|
||||
|
||||
isGiftRecipientPickerOpen?: boolean;
|
||||
|
||||
starsGiftingPickerModal?: {
|
||||
@ -3243,7 +3245,9 @@ export interface ActionPayloads {
|
||||
webApp: WebApp;
|
||||
skipClosingConfirmation?: boolean;
|
||||
} & WithTabId;
|
||||
closeWebAppModal: WithTabId | undefined;
|
||||
closeWebAppModal: ({
|
||||
shouldSkipConfirmation?: boolean;
|
||||
} & WithTabId) | undefined;
|
||||
changeWebAppModalState: WithTabId | undefined;
|
||||
|
||||
// Misc
|
||||
@ -3454,6 +3458,12 @@ export interface ActionPayloads {
|
||||
openGiftRecipientPicker: WithTabId | undefined;
|
||||
closeGiftRecipientPicker: WithTabId | undefined;
|
||||
|
||||
openWebAppsCloseConfirmationModal: WithTabId | undefined;
|
||||
|
||||
closeWebAppsCloseConfirmationModal: ({
|
||||
shouldSkipInFuture?: boolean;
|
||||
} & WithTabId);
|
||||
|
||||
openStarsGiftingPickerModal: WithTabId | undefined;
|
||||
closeStarsGiftingPickerModal: WithTabId | undefined;
|
||||
|
||||
|
||||
@ -131,6 +131,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
|
||||
shouldCollectDebugLogs?: boolean;
|
||||
shouldDebugExportedSenders?: boolean;
|
||||
shouldWarnAboutSvg?: boolean;
|
||||
shouldSkipWebAppCloseConfirmation: boolean;
|
||||
}
|
||||
|
||||
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;
|
||||
'StarsSubscribeInfoLinkText': undefined;
|
||||
'StarsSubscribeInfoLink': undefined;
|
||||
'AreYouSureCloseMiniApps': undefined;
|
||||
'CloseMiniApps': undefined;
|
||||
'DoNotAskAgain': undefined;
|
||||
'PaymentInfoDone': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
@ -1530,7 +1534,6 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'StarsPerMonth': {
|
||||
'amount': V;
|
||||
};
|
||||
'PaymentInfoDone': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user