Mini Apps: Request for rights (#5238)
This commit is contained in:
parent
cdbb5e15ed
commit
a414faa985
@ -25,6 +25,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
||||||
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
||||||
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
|
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
|
||||||
|
botCanManageEmojiStatus,
|
||||||
},
|
},
|
||||||
users,
|
users,
|
||||||
} = mtpUserFull;
|
} = mtpUserFull;
|
||||||
@ -54,6 +55,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
botVerification: botVerification && buildApiBotVerification(botVerification),
|
botVerification: botVerification && buildApiBotVerification(botVerification),
|
||||||
areAdsEnabled: sponsoredEnabled,
|
areAdsEnabled: sponsoredEnabled,
|
||||||
starGiftCount: stargiftsCount,
|
starGiftCount: stargiftsCount,
|
||||||
|
isBotCanManageEmojiStatus: botCanManageEmojiStatus,
|
||||||
hasScheduledMessages: hasScheduled,
|
hasScheduledMessages: hasScheduled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -611,6 +611,15 @@ export function checkBotDownloadFileParams({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleUserEmojiStatusPermission({ bot, isEnabled } : { bot: ApiUser; isEnabled: boolean }) {
|
||||||
|
return invokeRequest(new GramJs.bots.ToggleUserEmojiStatusPermission({
|
||||||
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
|
enabled: isEnabled,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
||||||
return results.map((result) => {
|
return results.map((result) => {
|
||||||
if (result instanceof GramJs.BotInlineMediaResult) {
|
if (result instanceof GramJs.BotInlineMediaResult) {
|
||||||
|
|||||||
@ -62,6 +62,8 @@ export interface ApiUserFullInfo {
|
|||||||
businessWorkHours?: ApiBusinessWorkHours;
|
businessWorkHours?: ApiBusinessWorkHours;
|
||||||
businessIntro?: ApiBusinessIntro;
|
businessIntro?: ApiBusinessIntro;
|
||||||
starGiftCount?: number;
|
starGiftCount?: number;
|
||||||
|
isBotCanManageEmojiStatus?: boolean;
|
||||||
|
isBotAccessEmojiGranted?: boolean;
|
||||||
hasScheduledMessages?: boolean;
|
hasScheduledMessages?: boolean;
|
||||||
botVerification?: ApiBotVerification;
|
botVerification?: ApiBotVerification;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1405,6 +1405,7 @@
|
|||||||
"CloseMiniApps" = "Close Mini Apps";
|
"CloseMiniApps" = "Close Mini Apps";
|
||||||
"DoNotAskAgain" = "Don't ask again";
|
"DoNotAskAgain" = "Don't ask again";
|
||||||
"PaymentInfoDone" = "Proceed to checkout";
|
"PaymentInfoDone" = "Proceed to checkout";
|
||||||
|
"EmojiStatusAccessText" = "**{name}** requests access to set your **emoji status**. You will be able to revoke this access in the profile page of **{name}**.";
|
||||||
"VideoConversionTitle" = "Improving Video...";
|
"VideoConversionTitle" = "Improving Video...";
|
||||||
"VideoConversionText" = "The video will be published after it's optimized for the best viewing experience.";
|
"VideoConversionText" = "The video will be published after it's optimized for the best viewing experience.";
|
||||||
"VideoConversionDone" = "Video published.";
|
"VideoConversionDone" = "Video published.";
|
||||||
@ -1473,3 +1474,5 @@
|
|||||||
"ProfileTabVoice" = "Voice";
|
"ProfileTabVoice" = "Voice";
|
||||||
"ProfileTabSharedGroups" = "Groups";
|
"ProfileTabSharedGroups" = "Groups";
|
||||||
"ProfileTabSimilarChannels" = "Similar Channels";
|
"ProfileTabSimilarChannels" = "Similar Channels";
|
||||||
|
"LocationPermissionText" = "**{name}** requests access to set your **location**. You will be able to revoke this access in the profile page of **{name}**.";
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export { default as ChatInviteModal } from '../components/modals/chatInvite/Chat
|
|||||||
export { default as AboutAdsModal } from '../components/modals/aboutAds/AboutAdsModal';
|
export { default as AboutAdsModal } from '../components/modals/aboutAds/AboutAdsModal';
|
||||||
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
export { default as AboutMonetizationModal } from '../components/common/AboutMonetizationModal';
|
||||||
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
export { default as VerificationMonetizationModal } from '../components/common/VerificationMonetizationModal';
|
||||||
|
export { default as EmojiStatusAccessModal } from '../components/modals/emojiStatusAccess/EmojiStatusAccessModal';
|
||||||
|
export { default as LocationAccessModal } from '../components/modals/locationAccess/LocationAccessModal';
|
||||||
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
export { default as ReportAdModal } from '../components/modals/reportAd/ReportAdModal';
|
||||||
export { default as ReportModal } from '../components/modals/reportModal/ReportModal';
|
export { default as ReportModal } from '../components/modals/reportModal/ReportModal';
|
||||||
export { default as CalendarModal } from '../components/common/CalendarModal';
|
export { default as CalendarModal } from '../components/common/CalendarModal';
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
.root {
|
.root {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
|
||||||
:global(.custom-emoji) {
|
:global(.custom-emoji) {
|
||||||
@ -17,8 +18,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ellipsis {
|
.transition {
|
||||||
white-space: nowrap;
|
width: 1.5rem;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import renderText from './helpers/renderText';
|
|||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Transition from '../ui/Transition';
|
||||||
import CustomEmoji from './CustomEmoji';
|
import CustomEmoji from './CustomEmoji';
|
||||||
import FakeIcon from './FakeIcon';
|
import FakeIcon from './FakeIcon';
|
||||||
import StarIcon from './icons/StarIcon';
|
import StarIcon from './icons/StarIcon';
|
||||||
@ -44,7 +45,6 @@ type OwnProps = {
|
|||||||
noLoopLimit?: boolean;
|
noLoopLimit?: boolean;
|
||||||
canCopyTitle?: boolean;
|
canCopyTitle?: boolean;
|
||||||
iconElement?: React.ReactNode;
|
iconElement?: React.ReactNode;
|
||||||
allowMultiLine?: boolean;
|
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
};
|
};
|
||||||
@ -61,7 +61,6 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
noLoopLimit,
|
noLoopLimit,
|
||||||
canCopyTitle,
|
canCopyTitle,
|
||||||
iconElement,
|
iconElement,
|
||||||
allowMultiLine,
|
|
||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
}) => {
|
}) => {
|
||||||
@ -125,7 +124,6 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'fullName',
|
'fullName',
|
||||||
styles.fullName,
|
styles.fullName,
|
||||||
!allowMultiLine && styles.ellipsis,
|
|
||||||
canCopyTitle && styles.canCopy,
|
canCopyTitle && styles.canCopy,
|
||||||
)}
|
)}
|
||||||
onClick={handleTitleClick}
|
onClick={handleTitleClick}
|
||||||
@ -137,13 +135,22 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
{!noVerified && peer?.isVerified && <VerifiedIcon />}
|
{!noVerified && peer?.isVerified && <VerifiedIcon />}
|
||||||
{!noFake && peer?.fakeType && <FakeIcon fakeType={peer.fakeType} />}
|
{!noFake && peer?.fakeType && <FakeIcon fakeType={peer.fakeType} />}
|
||||||
{canShowEmojiStatus && realPeer.emojiStatus && (
|
{canShowEmojiStatus && realPeer.emojiStatus && (
|
||||||
<CustomEmoji
|
<Transition
|
||||||
documentId={realPeer.emojiStatus.documentId}
|
className={styles.transition}
|
||||||
size={emojiStatusSize}
|
activeKey={Number(realPeer.emojiStatus.documentId)}
|
||||||
loopLimit={!noLoopLimit ? EMOJI_STATUS_LOOP_LIMIT : undefined}
|
name="fade"
|
||||||
observeIntersectionForLoading={observeIntersection}
|
shouldCleanup
|
||||||
onClick={onEmojiStatusClick}
|
shouldRestoreHeight
|
||||||
/>
|
>
|
||||||
|
<CustomEmoji
|
||||||
|
forceAlways
|
||||||
|
documentId={realPeer.emojiStatus.documentId}
|
||||||
|
size={emojiStatusSize}
|
||||||
|
loopLimit={!noLoopLimit ? EMOJI_STATUS_LOOP_LIMIT : undefined}
|
||||||
|
observeIntersectionForLoading={observeIntersection}
|
||||||
|
onClick={onEmojiStatusClick}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
)}
|
)}
|
||||||
{canShowEmojiStatus && !realPeer.emojiStatus && isPremium && <StarIcon />}
|
{canShowEmojiStatus && !realPeer.emojiStatus && isPremium && <StarIcon />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -45,6 +45,10 @@
|
|||||||
max-width: unset;
|
max-width: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.notClickable {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar,
|
.avatar,
|
||||||
.iconWrapper {
|
.iconWrapper {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
|
|||||||
@ -38,6 +38,7 @@ type OwnProps<T = undefined> = {
|
|||||||
withEmojiStatus?: boolean;
|
withEmojiStatus?: boolean;
|
||||||
clickArg?: T;
|
clickArg?: T;
|
||||||
onClick?: (arg: T) => void;
|
onClick?: (arg: T) => void;
|
||||||
|
itemClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -62,6 +63,7 @@ const PeerChip = <T,>({
|
|||||||
withPeerColors,
|
withPeerColors,
|
||||||
withEmojiStatus,
|
withEmojiStatus,
|
||||||
onClick,
|
onClick,
|
||||||
|
itemClassName,
|
||||||
}: OwnProps<T> & StateProps) => {
|
}: OwnProps<T> & StateProps) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
@ -105,6 +107,7 @@ const PeerChip = <T,>({
|
|||||||
canClose && styles.closeable,
|
canClose && styles.closeable,
|
||||||
isCloseNonDestructive && styles.nonDestructive,
|
isCloseNonDestructive && styles.nonDestructive,
|
||||||
fluid && styles.fluid,
|
fluid && styles.fluid,
|
||||||
|
!onClick && styles.notClickable,
|
||||||
withPeerColors && getPeerColorClass(customPeer || peer),
|
withPeerColors && getPeerColorClass(customPeer || peer),
|
||||||
className,
|
className,
|
||||||
);
|
);
|
||||||
@ -118,7 +121,7 @@ const PeerChip = <T,>({
|
|||||||
>
|
>
|
||||||
{iconElement}
|
{iconElement}
|
||||||
{!isMinimized && (
|
{!isMinimized && (
|
||||||
<div className={styles.name} dir="auto">
|
<div className={buildClassName(styles.name, itemClassName)} dir="auto">
|
||||||
{titleElement}
|
{titleElement}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useMemo, useState,
|
memo, useMemo,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiChat,
|
||||||
|
ApiCountryCode,
|
||||||
|
ApiUser,
|
||||||
|
ApiUserFullInfo,
|
||||||
|
ApiUsername,
|
||||||
ApiBotVerification,
|
ApiBotVerification,
|
||||||
ApiChat, ApiCountryCode, ApiUser, ApiUserFullInfo, ApiUsername,
|
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import type { BotAppPermissions } from '../../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH } from '../../../config';
|
import { FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH } from '../../../config';
|
||||||
@ -20,6 +25,7 @@ import {
|
|||||||
selectIsChatMuted,
|
selectIsChatMuted,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
|
selectBotAppPermissions,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
@ -31,7 +37,6 @@ import {
|
|||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||||
import { debounce } from '../../../util/schedulers';
|
|
||||||
import stopEvent from '../../../util/stopEvent';
|
import stopEvent from '../../../util/stopEvent';
|
||||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
import { ChatAnimationTypes } from '../../left/main/hooks';
|
import { ChatAnimationTypes } from '../../left/main/hooks';
|
||||||
@ -77,6 +82,8 @@ type StateProps = {
|
|||||||
hasSavedMessages?: boolean;
|
hasSavedMessages?: boolean;
|
||||||
personalChannel?: ApiChat;
|
personalChannel?: ApiChat;
|
||||||
hasMainMiniApp?: boolean;
|
hasMainMiniApp?: boolean;
|
||||||
|
isBotCanManageEmojiStatus?: boolean;
|
||||||
|
botAppPermissions?: BotAppPermissions;
|
||||||
botVerification?: ApiBotVerification;
|
botVerification?: ApiBotVerification;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,7 +93,6 @@ const DEFAULT_MAP_CONFIG = {
|
|||||||
zoom: 15,
|
zoom: 15,
|
||||||
};
|
};
|
||||||
|
|
||||||
const runDebounced = debounce((cb) => cb(), 500, false);
|
|
||||||
const BOT_VERIFICATION_ICON_SIZE = 16;
|
const BOT_VERIFICATION_ICON_SIZE = 16;
|
||||||
|
|
||||||
const ChatExtra: FC<OwnProps & StateProps> = ({
|
const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||||
@ -105,6 +111,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
hasSavedMessages,
|
hasSavedMessages,
|
||||||
personalChannel,
|
personalChannel,
|
||||||
hasMainMiniApp,
|
hasMainMiniApp,
|
||||||
|
isBotCanManageEmojiStatus,
|
||||||
|
botAppPermissions,
|
||||||
botVerification,
|
botVerification,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@ -116,6 +124,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
openMapModal,
|
openMapModal,
|
||||||
requestCollectibleInfo,
|
requestCollectibleInfo,
|
||||||
requestMainWebView,
|
requestMainWebView,
|
||||||
|
toggleUserEmojiStatusPermission,
|
||||||
|
toggleUserLocationPermission,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -135,12 +145,6 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const [areNotificationsEnabled, setAreNotificationsEnabled] = useState(!isMuted);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setAreNotificationsEnabled(!isMuted);
|
|
||||||
}, [isMuted]);
|
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevPeerId]) => {
|
useEffectWithPrevDeps(([prevPeerId]) => {
|
||||||
if (!peerId || prevPeerId === peerId) return;
|
if (!peerId || prevPeerId === peerId) return;
|
||||||
if (user || (chat && isChatChannel(chat))) {
|
if (user || (chat && isChatChannel(chat))) {
|
||||||
@ -198,23 +202,25 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleNotificationChange = useLastCallback(() => {
|
const handleNotificationChange = useLastCallback(() => {
|
||||||
setAreNotificationsEnabled((current) => {
|
if (isTopicInfo) {
|
||||||
const newAreNotificationsEnabled = !current;
|
updateTopicMutedState({
|
||||||
|
chatId: chatId!,
|
||||||
runDebounced(() => {
|
topicId: topicId!,
|
||||||
if (isTopicInfo) {
|
isMuted: !isMuted,
|
||||||
updateTopicMutedState({
|
|
||||||
chatId: chatId!,
|
|
||||||
topicId: topicId!,
|
|
||||||
isMuted: !newAreNotificationsEnabled,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateChatMutedState({ chatId: chatId!, isMuted: !newAreNotificationsEnabled });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
updateChatMutedState({ chatId: chatId!, isMuted: !isMuted });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return newAreNotificationsEnabled;
|
const manageEmojiStatusChange = useLastCallback(() => {
|
||||||
});
|
if (!user) return;
|
||||||
|
toggleUserEmojiStatusPermission({ botId: user.id, isEnabled: !isBotCanManageEmojiStatus });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLocationPermissionChange = useLastCallback(() => {
|
||||||
|
if (!user) return;
|
||||||
|
toggleUserLocationPermission({ botId: user.id, isAccessGranted: !botAppPermissions?.geolocation });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenSavedDialog = useLastCallback(() => {
|
const handleOpenSavedDialog = useLastCallback(() => {
|
||||||
@ -416,7 +422,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
<Switcher
|
<Switcher
|
||||||
id="group-notifications"
|
id="group-notifications"
|
||||||
label={userId ? 'Toggle User Notifications' : 'Toggle Chat Notifications'}
|
label={userId ? 'Toggle User Notifications' : 'Toggle Chat Notifications'}
|
||||||
checked={areNotificationsEnabled}
|
checked={isMuted}
|
||||||
inactive
|
inactive
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -442,6 +448,26 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
<span>{oldLang('SavedMessagesTab')}</span>
|
<span>{oldLang('SavedMessagesTab')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{userFullInfo && 'isBotAccessEmojiGranted' in userFullInfo && (
|
||||||
|
<ListItem icon="user" narrow ripple onClick={manageEmojiStatusChange}>
|
||||||
|
<span>{oldLang('BotProfilePermissionEmojiStatus')}</span>
|
||||||
|
<Switcher
|
||||||
|
label={oldLang('BotProfilePermissionEmojiStatus')}
|
||||||
|
checked={isBotCanManageEmojiStatus}
|
||||||
|
inactive
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
{botAppPermissions?.geolocation !== undefined && (
|
||||||
|
<ListItem icon="location" narrow ripple onClick={handleLocationPermissionChange}>
|
||||||
|
<span>{oldLang('BotProfilePermissionLocation')}</span>
|
||||||
|
<Switcher
|
||||||
|
label={oldLang('BotProfilePermissionLocation')}
|
||||||
|
checked={botAppPermissions?.geolocation}
|
||||||
|
inactive
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
{botVerification && (
|
{botVerification && (
|
||||||
<div className={styles.botVerificationSection}>
|
<div className={styles.botVerificationSection}>
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
@ -462,6 +488,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
|
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
|
||||||
const user = chatOrUserId ? selectUser(global, chatOrUserId) : undefined;
|
const user = chatOrUserId ? selectUser(global, chatOrUserId) : undefined;
|
||||||
|
const botAppPermissions = chatOrUserId ? selectBotAppPermissions(global, chatOrUserId) : undefined;
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
|
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
|
||||||
const { threadId } = selectCurrentMessageList(global) || {};
|
const { threadId } = selectCurrentMessageList(global) || {};
|
||||||
@ -473,7 +500,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const botVerification = userFullInfo?.botVerification || chatFullInfo?.botVerification;
|
const botVerification = userFullInfo?.botVerification || chatFullInfo?.botVerification;
|
||||||
|
|
||||||
const chatInviteLink = chatFullInfo?.inviteLink;
|
const chatInviteLink = chatFullInfo?.inviteLink;
|
||||||
|
|
||||||
const description = userFullInfo?.bio || chatFullInfo?.about;
|
const description = userFullInfo?.bio || chatFullInfo?.about;
|
||||||
|
|
||||||
const canInviteUsers = chat && !user && (
|
const canInviteUsers = chat && !user && (
|
||||||
@ -497,6 +523,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
user,
|
user,
|
||||||
userFullInfo,
|
userFullInfo,
|
||||||
canInviteUsers,
|
canInviteUsers,
|
||||||
|
botAppPermissions,
|
||||||
isMuted,
|
isMuted,
|
||||||
topicId,
|
topicId,
|
||||||
chatInviteLink,
|
chatInviteLink,
|
||||||
@ -505,6 +532,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
hasSavedMessages,
|
hasSavedMessages,
|
||||||
personalChannel,
|
personalChannel,
|
||||||
hasMainMiniApp,
|
hasMainMiniApp,
|
||||||
|
isBotCanManageEmojiStatus: userFullInfo?.isBotCanManageEmojiStatus,
|
||||||
botVerification,
|
botVerification,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -13,11 +13,13 @@ import BoostModal from './boost/BoostModal.async';
|
|||||||
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
|
||||||
import ChatlistModal from './chatlist/ChatlistModal.async';
|
import ChatlistModal from './chatlist/ChatlistModal.async';
|
||||||
import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
||||||
|
import EmojiStatusAccessModal from './emojiStatusAccess/EmojiStatusAccessModal.async';
|
||||||
import PremiumGiftModal from './gift/GiftModal.async';
|
import PremiumGiftModal from './gift/GiftModal.async';
|
||||||
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
import GiftInfoModal from './gift/info/GiftInfoModal.async';
|
||||||
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
import GiftRecipientPicker from './gift/recipient/GiftRecipientPicker.async';
|
||||||
import GiftCodeModal from './giftcode/GiftCodeModal.async';
|
import GiftCodeModal from './giftcode/GiftCodeModal.async';
|
||||||
import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async';
|
import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async';
|
||||||
|
import LocationAccessModal from './locationAccess/LocationAccessModal.async';
|
||||||
import MapModal from './map/MapModal.async';
|
import MapModal from './map/MapModal.async';
|
||||||
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
||||||
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
||||||
@ -59,6 +61,8 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'isWebAppsCloseConfirmationModalOpen' |
|
'isWebAppsCloseConfirmationModalOpen' |
|
||||||
'giftInfoModal' |
|
'giftInfoModal' |
|
||||||
'suggestedStatusModal' |
|
'suggestedStatusModal' |
|
||||||
|
'emojiStatusAccessModal' |
|
||||||
|
'locationAccessModal' |
|
||||||
'aboutAdsModal'
|
'aboutAdsModal'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -99,6 +103,8 @@ const MODALS: ModalRegistry = {
|
|||||||
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
isWebAppsCloseConfirmationModalOpen: WebAppsCloseConfirmationModal,
|
||||||
giftInfoModal: GiftInfoModal,
|
giftInfoModal: GiftInfoModal,
|
||||||
suggestedStatusModal: SuggestedStatusModal,
|
suggestedStatusModal: SuggestedStatusModal,
|
||||||
|
emojiStatusAccessModal: EmojiStatusAccessModal,
|
||||||
|
locationAccessModal: LocationAccessModal,
|
||||||
aboutAdsModal: AboutAdsModal,
|
aboutAdsModal: AboutAdsModal,
|
||||||
};
|
};
|
||||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './EmojiStatusAccessModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const EmojiStatusAccessModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const EmojiStatusAccessModal = useModuleLoader(Bundles.Extra, 'EmojiStatusAccessModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return EmojiStatusAccessModal ? <EmojiStatusAccessModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmojiStatusAccessModalAsync;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatItem {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
@ -0,0 +1,204 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo, useEffect,
|
||||||
|
useMemo, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiStickerSet, ApiUser } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
|
import { selectIsCurrentUserPremium, selectStickerSet, selectUser } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useInterval from '../../../hooks/schedulers/useInterval';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import PeerChip from '../../common/PeerChip';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
|
||||||
|
import styles from './EmojiStatusAccessModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['emojiStatusAccessModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StateProps = {
|
||||||
|
currentUser?: ApiUser;
|
||||||
|
stickerSet?: ApiStickerSet;
|
||||||
|
isPremium?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INTERVAL = 3000;
|
||||||
|
|
||||||
|
const EmojiStatusAccessModal: FC<OwnProps & StateProps> = ({
|
||||||
|
modal,
|
||||||
|
currentUser,
|
||||||
|
stickerSet,
|
||||||
|
isPremium,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
closeEmojiStatusAccessModal,
|
||||||
|
toggleUserEmojiStatusPermission,
|
||||||
|
sendWebAppEvent,
|
||||||
|
openPremiumModal,
|
||||||
|
loadDefaultStatusIcons,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [currentStatusIndex, setCurrentStatusIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && !stickerSet?.stickers) {
|
||||||
|
loadDefaultStatusIcons();
|
||||||
|
}
|
||||||
|
}, [isOpen, stickerSet]);
|
||||||
|
|
||||||
|
const mockPeerWithStatus = useMemo(() => {
|
||||||
|
if (!currentUser || !stickerSet?.stickers) return undefined;
|
||||||
|
return {
|
||||||
|
...currentUser,
|
||||||
|
emojiStatus: {
|
||||||
|
documentId: stickerSet.stickers[currentStatusIndex].id,
|
||||||
|
},
|
||||||
|
} satisfies ApiUser;
|
||||||
|
}, [currentUser, stickerSet, currentStatusIndex]);
|
||||||
|
|
||||||
|
const totalCount = stickerSet?.stickers?.length;
|
||||||
|
useInterval(
|
||||||
|
() => {
|
||||||
|
if (!totalCount) return;
|
||||||
|
setCurrentStatusIndex((prevIndex) => (prevIndex + 1) % totalCount);
|
||||||
|
},
|
||||||
|
totalCount ? INTERVAL : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderPickerItem = useLastCallback(() => {
|
||||||
|
return (
|
||||||
|
<PeerChip
|
||||||
|
withEmojiStatus
|
||||||
|
className={styles.chatItem}
|
||||||
|
itemClassName={styles.itemName}
|
||||||
|
mockPeer={mockPeerWithStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirmHandler = useLastCallback(() => {
|
||||||
|
if (!modal?.bot?.id) return;
|
||||||
|
closeEmojiStatusAccessModal();
|
||||||
|
if (modal?.webAppKey) {
|
||||||
|
if (isPremium) {
|
||||||
|
sendWebAppEvent({
|
||||||
|
webAppKey: modal.webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_access_requested',
|
||||||
|
eventData: {
|
||||||
|
status: 'allowed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
toggleUserEmojiStatusPermission({ botId: modal.bot.id, isEnabled: true, isBotAccessEmojiGranted: true });
|
||||||
|
} else {
|
||||||
|
openPremiumModal();
|
||||||
|
sendWebAppEvent({
|
||||||
|
webAppKey: modal.webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_access_requested',
|
||||||
|
eventData: {
|
||||||
|
status: 'cancelled',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCloseHandler = useLastCallback(() => {
|
||||||
|
if (!modal?.bot?.id) return;
|
||||||
|
closeEmojiStatusAccessModal();
|
||||||
|
if (modal?.webAppKey) {
|
||||||
|
sendWebAppEvent({
|
||||||
|
webAppKey: modal.webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'emoji_status_access_requested',
|
||||||
|
eventData: {
|
||||||
|
status: 'cancelled',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isPremium) {
|
||||||
|
toggleUserEmojiStatusPermission({ botId: modal.bot.id, isEnabled: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderStatusText = useLastCallback(() => {
|
||||||
|
if (!modal?.bot) return undefined;
|
||||||
|
return lang('EmojiStatusAccessText', {
|
||||||
|
name: getUserFullName(modal?.bot!),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className={buildClassName('confirm')}
|
||||||
|
contentClassName={styles.content}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onCloseHandler}
|
||||||
|
>
|
||||||
|
{renderPickerItem()}
|
||||||
|
<div>
|
||||||
|
{renderStatusText()}
|
||||||
|
<div
|
||||||
|
className="dialog-buttons mt-2"
|
||||||
|
ref={containerRef}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
isText
|
||||||
|
onClick={confirmHandler}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{oldLang('lng_bot_allow_write_confirm')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
isText
|
||||||
|
onClick={onCloseHandler}
|
||||||
|
>
|
||||||
|
{lang('Cancel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
const currentUser = selectUser(global, global.currentUserId!);
|
||||||
|
const isPremium = selectIsCurrentUserPremium(global);
|
||||||
|
const stickerSet = global.defaultStatusIconsId ? selectStickerSet(global, global.defaultStatusIconsId) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentUser,
|
||||||
|
stickerSet,
|
||||||
|
isPremium,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(EmojiStatusAccessModal));
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './LocationAccessModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const LocationAccessModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const LocationAccessModal = useModuleLoader(Bundles.Extra, 'LocationAccessModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return LocationAccessModal ? <LocationAccessModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocationAccessModalAsync;
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
.avatars {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
168
src/components/modals/locationAccess/LocationAccessModal.tsx
Normal file
168
src/components/modals/locationAccess/LocationAccessModal.tsx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo, useRef,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiUser } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
|
import { selectUser } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { getGeolocationStatus } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
|
||||||
|
import styles from './LocationAccessModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['locationAccessModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StateProps = {
|
||||||
|
currentUser?: ApiUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LocationAccessModal: FC<OwnProps & StateProps> = ({
|
||||||
|
modal,
|
||||||
|
currentUser,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
closeLocationAccessModal, toggleUserLocationPermission, sendWebAppEvent,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const isOpen = Boolean(modal);
|
||||||
|
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const confirmHandler = useLastCallback(async () => {
|
||||||
|
const geolocationData = await getGeolocationStatus();
|
||||||
|
const { geolocation } = geolocationData;
|
||||||
|
if (!modal?.bot?.id) return;
|
||||||
|
closeLocationAccessModal();
|
||||||
|
if (modal?.webAppKey) {
|
||||||
|
toggleUserLocationPermission({
|
||||||
|
botId: modal.bot.id,
|
||||||
|
isAccessGranted: true,
|
||||||
|
});
|
||||||
|
sendWebAppEvent({
|
||||||
|
webAppKey: modal.webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'location_requested',
|
||||||
|
eventData: {
|
||||||
|
available: true,
|
||||||
|
latitude: geolocation?.latitude,
|
||||||
|
longitude: geolocation?.longitude,
|
||||||
|
altitude: geolocation?.altitude,
|
||||||
|
course: geolocation?.heading,
|
||||||
|
speed: geolocation?.speed,
|
||||||
|
horizontal_accuracy: geolocation?.accuracy,
|
||||||
|
vertical_accuracy: geolocation?.accuracy,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCloseHandler = useLastCallback(() => {
|
||||||
|
if (!modal?.bot?.id) return;
|
||||||
|
closeLocationAccessModal();
|
||||||
|
if (modal?.webAppKey) {
|
||||||
|
toggleUserLocationPermission({
|
||||||
|
botId: modal.bot.id,
|
||||||
|
isAccessGranted: false,
|
||||||
|
});
|
||||||
|
sendWebAppEvent({
|
||||||
|
webAppKey: modal.webAppKey,
|
||||||
|
event: {
|
||||||
|
eventType: 'location_requested',
|
||||||
|
eventData: {
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderInfo = useLastCallback(() => {
|
||||||
|
if (!modal?.bot) return undefined;
|
||||||
|
return (
|
||||||
|
<div className={styles.avatars}>
|
||||||
|
<Avatar
|
||||||
|
size="large"
|
||||||
|
peer={currentUser}
|
||||||
|
/>
|
||||||
|
<Icon name="next" className={styles.arrow} />
|
||||||
|
<Avatar
|
||||||
|
size="large"
|
||||||
|
peer={modal.bot}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderStatusText = useLastCallback(() => {
|
||||||
|
if (!modal?.bot) return undefined;
|
||||||
|
return lang('LocationPermissionText', {
|
||||||
|
name: getUserFullName(modal?.bot!),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className={buildClassName('confirm')}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onCloseHandler}
|
||||||
|
>
|
||||||
|
{renderInfo()}
|
||||||
|
<div>
|
||||||
|
{renderStatusText()}
|
||||||
|
<div
|
||||||
|
className="dialog-buttons mt-2"
|
||||||
|
ref={containerRef}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
isText
|
||||||
|
onClick={confirmHandler}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{oldLang('lng_bot_allow_write_confirm')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="confirm-dialog-button"
|
||||||
|
isText
|
||||||
|
onClick={onCloseHandler}
|
||||||
|
>
|
||||||
|
{lang('Cancel')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
const currentUser = selectUser(global, global.currentUserId!);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentUser,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(LocationAccessModal));
|
||||||
@ -7,24 +7,33 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiAttachBot, ApiBotAppSettings, ApiChat, ApiUser,
|
ApiAttachBot, ApiBotAppSettings, ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { TabState } from '../../../global/types';
|
import type { TabState } from '../../../global/types';
|
||||||
import type { ThemeKey } from '../../../types';
|
import type { BotAppPermissions, ThemeKey } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
PopupOptions, WebApp, WebAppInboundEvent, WebAppModalStateType, WebAppOutboundEvent,
|
PopupOptions,
|
||||||
|
WebApp,
|
||||||
|
WebAppInboundEvent,
|
||||||
|
WebAppModalStateType,
|
||||||
|
WebAppOutboundEvent,
|
||||||
} from '../../../types/webapp';
|
} from '../../../types/webapp';
|
||||||
|
|
||||||
import { TME_LINK_PREFIX } from '../../../config';
|
import { TME_LINK_PREFIX } from '../../../config';
|
||||||
import { convertToApiChatType } from '../../../global/helpers';
|
import { convertToApiChatType } from '../../../global/helpers';
|
||||||
import { getWebAppKey } from '../../../global/helpers/bots';
|
import { getWebAppKey } from '../../../global/helpers/bots';
|
||||||
import {
|
import {
|
||||||
selectCurrentChat, selectTabState, selectTheme, selectUser, selectUserFullInfo,
|
selectBotAppPermissions,
|
||||||
|
selectTabState,
|
||||||
|
selectTheme,
|
||||||
|
selectUser,
|
||||||
|
selectUserFullInfo,
|
||||||
selectWebApp,
|
selectWebApp,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import download from '../../../util/download';
|
import download from '../../../util/download';
|
||||||
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
|
||||||
|
import { getGeolocationStatus, IS_GEOLOCATION_SUPPORTED } from '../../../util/windowEnvironment';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
@ -71,14 +80,16 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
|
||||||
bot?: ApiUser;
|
bot?: ApiUser;
|
||||||
|
currentUser?: ApiUser;
|
||||||
botAppSettings?: ApiBotAppSettings;
|
botAppSettings?: ApiBotAppSettings;
|
||||||
attachBot?: ApiAttachBot;
|
attachBot?: ApiAttachBot;
|
||||||
theme?: ThemeKey;
|
theme?: ThemeKey;
|
||||||
isPaymentModalOpen?: boolean;
|
isPaymentModalOpen?: boolean;
|
||||||
paymentStatus?: TabState['payment']['status'];
|
paymentStatus?: TabState['payment']['status'];
|
||||||
|
isPremium?: boolean;
|
||||||
modalState?: WebAppModalStateType;
|
modalState?: WebAppModalStateType;
|
||||||
|
botAppPermissions?: BotAppPermissions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NBSP = '\u00A0';
|
const NBSP = '\u00A0';
|
||||||
@ -117,6 +128,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
modalState,
|
modalState,
|
||||||
isMultiTabSupported,
|
isMultiTabSupported,
|
||||||
onContextMenuButtonClick,
|
onContextMenuButtonClick,
|
||||||
|
botAppPermissions,
|
||||||
botAppSettings,
|
botAppSettings,
|
||||||
modalHeight,
|
modalHeight,
|
||||||
}) => {
|
}) => {
|
||||||
@ -130,6 +142,10 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
sharePhoneWithBot,
|
sharePhoneWithBot,
|
||||||
updateWebApp,
|
updateWebApp,
|
||||||
resetPaymentStatus,
|
resetPaymentStatus,
|
||||||
|
openChatWithInfo,
|
||||||
|
showNotification,
|
||||||
|
openEmojiStatusAccessModal,
|
||||||
|
openLocationAccessModal,
|
||||||
changeWebAppModalState,
|
changeWebAppModalState,
|
||||||
closeWebAppModal,
|
closeWebAppModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
@ -190,6 +206,10 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const isActive = (activeWebApp && webApp) && activeWebAppKey === webAppKey;
|
const isActive = (activeWebApp && webApp) && activeWebAppKey === webAppKey;
|
||||||
|
|
||||||
|
const isAvailable = IS_GEOLOCATION_SUPPORTED;
|
||||||
|
const isAccessRequested = botAppPermissions?.geolocation !== undefined;
|
||||||
|
const isAccessGranted = Boolean(botAppPermissions?.geolocation);
|
||||||
|
|
||||||
const updateCurrentWebApp = useLastCallback((updatedPartialWebApp: Partial<WebApp>) => {
|
const updateCurrentWebApp = useLastCallback((updatedPartialWebApp: Partial<WebApp>) => {
|
||||||
if (!webAppKey) return;
|
if (!webAppKey) return;
|
||||||
updateWebApp({ key: webAppKey, update: updatedPartialWebApp });
|
updateWebApp({ key: webAppKey, update: updatedPartialWebApp });
|
||||||
@ -425,7 +445,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleAcceptWriteAccess = useLastCallback(async () => {
|
const handleAcceptWriteAccess = useLastCallback(async () => {
|
||||||
const result = await callApi('allowBotSendMessages', { bot: bot! });
|
if (!bot) return;
|
||||||
|
const result = await callApi('allowBotSendMessages', { bot });
|
||||||
if (!result) {
|
if (!result) {
|
||||||
handleRejectWriteAccess();
|
handleRejectWriteAccess();
|
||||||
return;
|
return;
|
||||||
@ -442,8 +463,9 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleRequestWriteAccess() {
|
async function handleRequestWriteAccess() {
|
||||||
|
if (!bot) return;
|
||||||
const canWrite = await callApi('fetchBotCanSendMessage', {
|
const canWrite = await callApi('fetchBotCanSendMessage', {
|
||||||
bot: bot!,
|
bot,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canWrite) {
|
if (canWrite) {
|
||||||
@ -454,7 +476,6 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsRequestingWriteAccess(!canWrite);
|
setIsRequestingWriteAccess(!canWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,6 +548,10 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const handleOpenChat = useLastCallback(() => {
|
||||||
|
openChatWithInfo({ id: bot!.id });
|
||||||
|
});
|
||||||
|
|
||||||
function handleEvent(event: WebAppInboundEvent) {
|
function handleEvent(event: WebAppInboundEvent) {
|
||||||
const { eventType, eventData } = event;
|
const { eventType, eventData } = event;
|
||||||
|
|
||||||
@ -616,8 +641,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (eventType === 'web_app_open_popup') {
|
if (eventType === 'web_app_open_popup') {
|
||||||
if (popupParameters || !eventData.message.trim().length || !eventData.buttons?.length
|
if (popupParameters || !eventData.message.trim().length || !eventData.buttons?.length
|
||||||
|| eventData.buttons.length > 3 || isRequestingPhone || isRequestingWriteAccess
|
|| eventData.buttons.length > 3 || isRequestingPhone || isRequestingWriteAccess
|
||||||
|| unlockPopupsAt > Date.now()) {
|
|| unlockPopupsAt > Date.now()) {
|
||||||
handleAppPopupClose(undefined);
|
handleAppPopupClose(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -672,6 +697,74 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
handleCheckDownloadFile(eventData.url, eventData.file_name);
|
handleCheckDownloadFile(eventData.url, eventData.file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_request_emoji_status_access') {
|
||||||
|
if (!bot) return;
|
||||||
|
openEmojiStatusAccessModal({ bot, webAppKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_check_location') {
|
||||||
|
const handleGeolocationCheck = () => {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'location_checked',
|
||||||
|
eventData: {
|
||||||
|
available: isAvailable,
|
||||||
|
access_requested: isAccessRequested,
|
||||||
|
access_granted: isAccessGranted,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleGeolocationCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_request_location') {
|
||||||
|
const handleRequestLocation = async () => {
|
||||||
|
const geolocationData = await getGeolocationStatus();
|
||||||
|
const { accessRequested, accessGranted, geolocation } = geolocationData;
|
||||||
|
|
||||||
|
if (!accessGranted || !accessRequested) {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'location_requested',
|
||||||
|
eventData: {
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
showNotification({ message: oldLang('PermissionNoLocationPosition') });
|
||||||
|
handleAppPopupClose(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAvailable) {
|
||||||
|
if (isAccessRequested) {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'location_requested',
|
||||||
|
eventData: {
|
||||||
|
available: botAppPermissions?.geolocation!,
|
||||||
|
latitude: geolocation?.latitude,
|
||||||
|
longitude: geolocation?.longitude,
|
||||||
|
altitude: geolocation?.altitude,
|
||||||
|
course: geolocation?.heading,
|
||||||
|
speed: geolocation?.speed,
|
||||||
|
horizontal_accuracy: geolocation?.accuracy,
|
||||||
|
vertical_accuracy: geolocation?.accuracy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
openLocationAccessModal({ bot, webAppKey });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showNotification({ message: oldLang('PermissionNoLocationPosition') });
|
||||||
|
handleAppPopupClose(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRequestLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_open_location_settings') {
|
||||||
|
handleOpenChat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true);
|
const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true);
|
||||||
@ -989,7 +1082,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
{!secondaryButton?.isProgressVisible && secondaryButtonCurrentText}
|
{!secondaryButton?.isProgressVisible && secondaryButtonCurrentText}
|
||||||
{secondaryButton?.isProgressVisible
|
{secondaryButton?.isProgressVisible
|
||||||
&& <Spinner className={styles.mainButtonSpinner} color="blue" />}
|
&& <Spinner className={styles.mainButtonSpinner} color="blue" />}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
@ -1056,7 +1149,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={Boolean(requestedFileDownload)}
|
isOpen={Boolean(requestedFileDownload)}
|
||||||
title={lang('BotDownloadFileTitle')}
|
title={oldLang('BotDownloadFileTitle')}
|
||||||
textParts={lang('BotDownloadFileDescription', {
|
textParts={lang('BotDownloadFileDescription', {
|
||||||
bot: bot?.firstName,
|
bot: bot?.firstName,
|
||||||
filename: requestedFileDownload?.fileName,
|
filename: requestedFileDownload?.fileName,
|
||||||
@ -1064,7 +1157,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
withNodes: true,
|
withNodes: true,
|
||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
})}
|
})}
|
||||||
confirmLabel={lang('BotDownloadFileButton')}
|
confirmLabel={oldLang('BotDownloadFileButton')}
|
||||||
onClose={handleRejectFileDownload}
|
onClose={handleRejectFileDownload}
|
||||||
confirmHandler={handleDownloadFile}
|
confirmHandler={handleDownloadFile}
|
||||||
/>
|
/>
|
||||||
@ -1100,21 +1193,23 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const bot = activeBotId ? selectUser(global, activeBotId) : undefined;
|
const bot = activeBotId ? selectUser(global, activeBotId) : undefined;
|
||||||
const userFullInfo = activeBotId ? selectUserFullInfo(global, activeBotId) : undefined;
|
const userFullInfo = activeBotId ? selectUserFullInfo(global, activeBotId) : undefined;
|
||||||
const botAppSettings = userFullInfo?.botInfo?.appSettings;
|
const botAppSettings = userFullInfo?.botInfo?.appSettings;
|
||||||
const chat = selectCurrentChat(global);
|
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||||
const theme = selectTheme(global);
|
const theme = selectTheme(global);
|
||||||
const { isPaymentModalOpen, status: regularPaymentStatus } = selectTabState(global).payment;
|
const { isPaymentModalOpen, status: regularPaymentStatus } = selectTabState(global).payment;
|
||||||
const { status: starsPaymentStatus, inputInvoice: starsInputInvoice } = selectTabState(global).starsPayment;
|
const { status: starsPaymentStatus, inputInvoice: starsInputInvoice } = selectTabState(global).starsPayment;
|
||||||
|
const botAppPermissions = bot ? selectBotAppPermissions(global, bot.id) : undefined;
|
||||||
|
|
||||||
const paymentStatus = starsPaymentStatus || regularPaymentStatus;
|
const paymentStatus = starsPaymentStatus || regularPaymentStatus;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attachBot,
|
attachBot,
|
||||||
bot,
|
bot,
|
||||||
chat,
|
currentUser,
|
||||||
theme,
|
theme,
|
||||||
isPaymentModalOpen: isPaymentModalOpen || Boolean(starsInputInvoice),
|
isPaymentModalOpen: isPaymentModalOpen || Boolean(starsInputInvoice),
|
||||||
paymentStatus,
|
paymentStatus,
|
||||||
modalState,
|
modalState,
|
||||||
|
botAppPermissions,
|
||||||
botAppSettings,
|
botAppSettings,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -60,7 +60,7 @@ const ConfirmDialog: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className={buildClassName('confirm', className)}
|
className={buildClassName('confirm', className)}
|
||||||
title={title || lang('Telegram')}
|
title={(title || lang('Telegram'))}
|
||||||
header={header}
|
header={header}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
|||||||
@ -340,8 +340,8 @@ function Transition({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { clientHeight } = activeElement || {};
|
const { clientHeight, clientWidth } = activeElement || {};
|
||||||
if (!clientHeight) {
|
if (!clientHeight || !clientWidth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,11 @@ import {
|
|||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import {
|
import {
|
||||||
removeBlockedUser, updateManagementProgress, updateUser, updateUserFullInfo,
|
removeBlockedUser,
|
||||||
|
updateBotAppPermissions,
|
||||||
|
updateManagementProgress,
|
||||||
|
updateUser,
|
||||||
|
updateUserFullInfo,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
activateWebAppIfOpen,
|
activateWebAppIfOpen,
|
||||||
@ -1318,6 +1322,44 @@ addActionHandler('setBotInfo', async (global, actions, payload): Promise<void> =
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleUserEmojiStatusPermission', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
botId, isEnabled, isBotAccessEmojiGranted,
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const bot = selectBot(global, botId);
|
||||||
|
|
||||||
|
if (!botId || !bot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('toggleUserEmojiStatusPermission', {
|
||||||
|
bot, isEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateUserFullInfo(global, botId, {
|
||||||
|
isBotCanManageEmojiStatus: isEnabled,
|
||||||
|
isBotAccessEmojiGranted,
|
||||||
|
});
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleUserLocationPermission', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
botId, isAccessGranted,
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const bot = selectUser(global, botId);
|
||||||
|
if (!bot) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateBotAppPermissions(global, bot.id, { geolocation: isAccessGranted });
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('startBotFatherConversation', async (global, actions, payload): Promise<void> => {
|
addActionHandler('startBotFatherConversation', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
param,
|
param,
|
||||||
|
|||||||
@ -224,3 +224,53 @@ addActionHandler('cancelAttachBotInChat', (global, actions, payload): ActionRetu
|
|||||||
requestedAttachBotInChat: undefined,
|
requestedAttachBotInChat: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openEmojiStatusAccessModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
bot, webAppKey, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
if (!bot || !webAppKey) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
emojiStatusAccessModal: {
|
||||||
|
bot,
|
||||||
|
webAppKey,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeEmojiStatusAccessModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
emojiStatusAccessModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('openLocationAccessModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
bot, webAppKey, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
if (!bot || !webAppKey) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
locationAccessModal: {
|
||||||
|
bot,
|
||||||
|
webAppKey,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeLocationAccessModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
locationAccessModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|||||||
@ -2,7 +2,13 @@
|
|||||||
import { getIsHeavyAnimating, onFullyIdle } from '../lib/teact/teact';
|
import { getIsHeavyAnimating, onFullyIdle } from '../lib/teact/teact';
|
||||||
import { addCallback, removeCallback } from '../lib/teact/teactn';
|
import { addCallback, removeCallback } from '../lib/teact/teactn';
|
||||||
|
|
||||||
import type { ApiAvailableReaction, ApiMessage } from '../api/types';
|
import type {
|
||||||
|
ApiAvailableReaction,
|
||||||
|
ApiBotPreviewMedia,
|
||||||
|
ApiMessage,
|
||||||
|
ApiUserCommonChats,
|
||||||
|
ApiUserGifts,
|
||||||
|
} from '../api/types';
|
||||||
import type { MessageList, ThreadId } from '../types';
|
import type { MessageList, ThreadId } from '../types';
|
||||||
import type { ActionReturnType, GlobalState } from './types';
|
import type { ActionReturnType, GlobalState } from './types';
|
||||||
import { MAIN_THREAD_ID } from '../api/types';
|
import { MAIN_THREAD_ID } from '../api/types';
|
||||||
@ -253,6 +259,9 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
if (!cached.users.commonChatsById) {
|
if (!cached.users.commonChatsById) {
|
||||||
cached.users.commonChatsById = initialState.users.commonChatsById;
|
cached.users.commonChatsById = initialState.users.commonChatsById;
|
||||||
}
|
}
|
||||||
|
if (!cached.users.botAppPermissionsById) {
|
||||||
|
cached.users.botAppPermissionsById = initialState.users.botAppPermissionsById;
|
||||||
|
}
|
||||||
if (!cached.chats.topicsInfoById) {
|
if (!cached.chats.topicsInfoById) {
|
||||||
cached.chats.topicsInfoById = initialState.chats.topicsInfoById;
|
cached.chats.topicsInfoById = initialState.chats.topicsInfoById;
|
||||||
}
|
}
|
||||||
@ -373,10 +382,18 @@ function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['cust
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function reduceUsers<T extends GlobalState>(global: T): GlobalState['users'] {
|
function reduceUsers<T extends GlobalState>(global: T): {
|
||||||
|
commonChatsById: Record<string, ApiUserCommonChats>;
|
||||||
|
giftsById: Record<string, ApiUserGifts>;
|
||||||
|
botAppPermissionsById: any;
|
||||||
|
statusesById: any;
|
||||||
|
fullInfoById: any;
|
||||||
|
byId: any;
|
||||||
|
previewMediaByBotId: Record<string, ApiBotPreviewMedia[]>;
|
||||||
|
} {
|
||||||
const {
|
const {
|
||||||
users: {
|
users: {
|
||||||
byId, statusesById, fullInfoById,
|
byId, statusesById, fullInfoById, botAppPermissionsById,
|
||||||
}, currentUserId,
|
}, currentUserId,
|
||||||
} = global;
|
} = global;
|
||||||
const currentChatIds = compact(
|
const currentChatIds = compact(
|
||||||
@ -413,6 +430,7 @@ function reduceUsers<T extends GlobalState>(global: T): GlobalState['users'] {
|
|||||||
byId: pickTruthy(byId, idsToSave),
|
byId: pickTruthy(byId, idsToSave),
|
||||||
statusesById: pickTruthy(statusesById, idsToSave),
|
statusesById: pickTruthy(statusesById, idsToSave),
|
||||||
fullInfoById: pickTruthy(fullInfoById, idsToSave),
|
fullInfoById: pickTruthy(fullInfoById, idsToSave),
|
||||||
|
botAppPermissionsById,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,6 +103,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
previewMediaByBotId: {},
|
previewMediaByBotId: {},
|
||||||
commonChatsById: {},
|
commonChatsById: {},
|
||||||
giftsById: {},
|
giftsById: {},
|
||||||
|
botAppPermissionsById: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
chats: {
|
chats: {
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiMissingInvitedUser, ApiUser, ApiUserCommonChats, ApiUserFullInfo, ApiUserStatus,
|
ApiMissingInvitedUser,
|
||||||
|
ApiUser,
|
||||||
|
ApiUserCommonChats,
|
||||||
|
ApiUserFullInfo,
|
||||||
|
ApiUserStatus,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
|
import type { BotAppPermissions } from '../../types';
|
||||||
import type { GlobalState, TabArgs, TabState } from '../types';
|
import type { GlobalState, TabArgs, TabState } from '../types';
|
||||||
|
|
||||||
import { areDeepEqual } from '../../util/areDeepEqual';
|
import { areDeepEqual } from '../../util/areDeepEqual';
|
||||||
@ -295,3 +300,25 @@ export function updateMissingInvitedUsers<T extends GlobalState>(
|
|||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateBotAppPermissions<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
botId: string,
|
||||||
|
permissions: BotAppPermissions,
|
||||||
|
): T {
|
||||||
|
const { botAppPermissionsById } = global.users;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
users: {
|
||||||
|
...global.users,
|
||||||
|
botAppPermissionsById: {
|
||||||
|
...botAppPermissionsById,
|
||||||
|
[botId]: {
|
||||||
|
...botAppPermissionsById[botId],
|
||||||
|
...permissions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiUser, ApiUserCommonChats, ApiUserFullInfo, ApiUserStatus,
|
ApiUser, ApiUserCommonChats,
|
||||||
|
ApiUserFullInfo, ApiUserStatus,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
|
import type { BotAppPermissions } from '../../types';
|
||||||
import type { GlobalState } from '../types';
|
import type { GlobalState } from '../types';
|
||||||
|
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||||
@ -67,3 +69,9 @@ export function selectCanGift<T extends GlobalState>(global: T, userId: string)
|
|||||||
return !selectIsPremiumPurchaseBlocked(global) && user && !bot
|
return !selectIsPremiumPurchaseBlocked(global) && user && !bot
|
||||||
&& !user.isSelf && userId !== SERVICE_NOTIFICATIONS_USER_ID;
|
&& !user.isSelf && userId !== SERVICE_NOTIFICATIONS_USER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectBotAppPermissions<T extends GlobalState>(
|
||||||
|
global: T, userId: string,
|
||||||
|
): BotAppPermissions | undefined {
|
||||||
|
return global.users.botAppPermissionsById[userId];
|
||||||
|
}
|
||||||
|
|||||||
@ -84,11 +84,7 @@ import type {
|
|||||||
ThreadId,
|
ThreadId,
|
||||||
WebPageMediaSize,
|
WebPageMediaSize,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import type {
|
import type { WebApp, WebAppModalStateType, WebAppOutboundEvent } from '../../types/webapp';
|
||||||
WebApp,
|
|
||||||
WebAppModalStateType,
|
|
||||||
WebAppOutboundEvent,
|
|
||||||
} from '../../types/webapp';
|
|
||||||
import type { DownloadableMedia } from '../helpers';
|
import type { DownloadableMedia } from '../helpers';
|
||||||
import type { TabState } from './tabState';
|
import type { TabState } from './tabState';
|
||||||
|
|
||||||
@ -2424,6 +2420,29 @@ export interface ActionPayloads {
|
|||||||
file?: File;
|
file?: File;
|
||||||
isSuggest?: boolean;
|
isSuggest?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
|
openEmojiStatusAccessModal: {
|
||||||
|
bot?: ApiUser;
|
||||||
|
webAppKey?: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeEmojiStatusAccessModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
openLocationAccessModal: {
|
||||||
|
bot?: ApiUser;
|
||||||
|
webAppKey?: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeLocationAccessModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
toggleUserEmojiStatusPermission: {
|
||||||
|
botId: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isBotAccessEmojiGranted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleUserLocationPermission: {
|
||||||
|
botId: string;
|
||||||
|
isAccessGranted: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequiredActionPayloads {
|
export interface RequiredActionPayloads {
|
||||||
|
|||||||
@ -47,6 +47,7 @@ import type {
|
|||||||
ApiWebSession,
|
ApiWebSession,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
|
BotAppPermissions,
|
||||||
ChatListType,
|
ChatListType,
|
||||||
ChatTranslatedMessages,
|
ChatTranslatedMessages,
|
||||||
EmojiKeywords,
|
EmojiKeywords,
|
||||||
@ -172,6 +173,7 @@ export type GlobalState = {
|
|||||||
previewMediaByBotId: Record<string, ApiBotPreviewMedia[]>;
|
previewMediaByBotId: Record<string, ApiBotPreviewMedia[]>;
|
||||||
commonChatsById: Record<string, ApiUserCommonChats>;
|
commonChatsById: Record<string, ApiUserCommonChats>;
|
||||||
giftsById: Record<string, ApiUserGifts>;
|
giftsById: Record<string, ApiUserGifts>;
|
||||||
|
botAppPermissionsById: Record<string, BotAppPermissions>;
|
||||||
};
|
};
|
||||||
profilePhotosById: Record<string, ApiPeerPhotos>;
|
profilePhotosById: Record<string, ApiPeerPhotos>;
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import type {
|
|||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiTypePrepaidGiveaway,
|
ApiTypePrepaidGiveaway,
|
||||||
ApiTypeStoryView,
|
ApiTypeStoryView,
|
||||||
|
ApiUser,
|
||||||
ApiUserStarGift,
|
ApiUserStarGift,
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
ApiWebPage,
|
ApiWebPage,
|
||||||
@ -510,6 +511,16 @@ export type TabState = {
|
|||||||
startParam?: string;
|
startParam?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
emojiStatusAccessModal?: {
|
||||||
|
bot: ApiUser;
|
||||||
|
webAppKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
locationAccessModal?: {
|
||||||
|
bot: ApiUser;
|
||||||
|
webAppKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
confetti?: {
|
confetti?: {
|
||||||
lastConfettiTime?: number;
|
lastConfettiTime?: number;
|
||||||
top?: number;
|
top?: number;
|
||||||
|
|||||||
@ -1686,6 +1686,7 @@ bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params
|
|||||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||||
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
||||||
bots.checkDownloadFileParams#50077589 bot:InputUser file_name:string url:string = Bool;
|
bots.checkDownloadFileParams#50077589 bot:InputUser file_name:string url:string = Bool;
|
||||||
|
bots.toggleUserEmojiStatusPermission#6de6392 bot:InputUser enabled:Bool = Bool;
|
||||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||||
@ -1776,4 +1777,4 @@ premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:
|
|||||||
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
||||||
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
||||||
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;
|
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;
|
||||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;
|
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;
|
||||||
|
|||||||
@ -277,6 +277,7 @@
|
|||||||
"bots.setBotInfo",
|
"bots.setBotInfo",
|
||||||
"bots.getPreviewMedias",
|
"bots.getPreviewMedias",
|
||||||
"bots.checkDownloadFileParams",
|
"bots.checkDownloadFileParams",
|
||||||
|
"bots.toggleUserEmojiStatusPermission",
|
||||||
"payments.getPaymentForm",
|
"payments.getPaymentForm",
|
||||||
"payments.getPaymentReceipt",
|
"payments.getPaymentReceipt",
|
||||||
"payments.validateRequestedInfo",
|
"payments.validateRequestedInfo",
|
||||||
|
|||||||
@ -636,3 +636,7 @@ export type StarGiftCategory = number | 'all' | 'limited' | 'stock';
|
|||||||
export type CallSound = (
|
export type CallSound = (
|
||||||
'join' | 'allowTalk' | 'leave' | 'connecting' | 'incoming' | 'end' | 'connect' | 'busy' | 'ringing'
|
'join' | 'allowTalk' | 'leave' | 'connecting' | 'incoming' | 'end' | 'connect' | 'busy' | 'ringing'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type BotAppPermissions = {
|
||||||
|
geolocation?: boolean;
|
||||||
|
};
|
||||||
|
|||||||
6
src/types/language.d.ts
vendored
6
src/types/language.d.ts
vendored
@ -1631,6 +1631,12 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
|||||||
'StarsPerMonth': {
|
'StarsPerMonth': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
|
'EmojiStatusAccessText': {
|
||||||
|
'name': V;
|
||||||
|
};
|
||||||
|
'LocationPermissionText': {
|
||||||
|
'name': V;
|
||||||
|
};
|
||||||
'BotSuggestedStatusFor': {
|
'BotSuggestedStatusFor': {
|
||||||
'bot': V;
|
'bot': V;
|
||||||
'duration': V;
|
'duration': V;
|
||||||
|
|||||||
@ -139,8 +139,9 @@ export type WebAppInboundEvent =
|
|||||||
}> |
|
}> |
|
||||||
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
||||||
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
||||||
| 'web_app_request_write_access' | 'web_app_request_phone' | 'iframe_will_reload'
|
| 'web_app_request_write_access' | 'iframe_will_reload'
|
||||||
| 'web_app_biometry_get_info' | 'web_app_biometry_open_settings'
|
| 'web_app_biometry_get_info' | 'web_app_biometry_open_settings' | 'web_app_request_emoji_status_access'
|
||||||
|
| 'web_app_check_location' | 'web_app_request_location' | 'web_app_open_location_settings'
|
||||||
| 'web_app_request_fullscreen' | 'web_app_exit_fullscreen'
|
| 'web_app_request_fullscreen' | 'web_app_exit_fullscreen'
|
||||||
| 'web_app_request_safe_area' | 'web_app_request_content_safe_area',
|
| 'web_app_request_safe_area' | 'web_app_request_content_safe_area',
|
||||||
null>;
|
null>;
|
||||||
@ -224,6 +225,33 @@ export type WebAppOutboundEvent =
|
|||||||
WebAppEvent<'biometry_token_updated', {
|
WebAppEvent<'biometry_token_updated', {
|
||||||
status: 'updated' | 'removed' | 'failed';
|
status: 'updated' | 'removed' | 'failed';
|
||||||
}> |
|
}> |
|
||||||
|
WebAppEvent<'location_checked', {
|
||||||
|
available: false;
|
||||||
|
} | {
|
||||||
|
available: boolean;
|
||||||
|
access_requested: boolean;
|
||||||
|
access_granted?: boolean;
|
||||||
|
}> |
|
||||||
|
WebAppEvent<'location_requested', {
|
||||||
|
available: boolean;
|
||||||
|
} | {
|
||||||
|
available: boolean;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
altitude: number | null;
|
||||||
|
course: number | null;
|
||||||
|
speed: number | null;
|
||||||
|
horizontal_accuracy: number | null;
|
||||||
|
vertical_accuracy: number | null;
|
||||||
|
course_accuracy: number | null;
|
||||||
|
speed_accuracy: number | null;
|
||||||
|
}> |
|
||||||
|
WebAppEvent<'emoji_status_access_requested', {
|
||||||
|
status: 'allowed' | 'cancelled';
|
||||||
|
}> |
|
||||||
|
WebAppEvent<'access_requested', {
|
||||||
|
available: true;
|
||||||
|
}> |
|
||||||
WebAppEvent<'emoji_status_failed', {
|
WebAppEvent<'emoji_status_failed', {
|
||||||
error: 'UNSUPPORTED' | 'USER_DECLINED' | 'SUGGESTED_EMOJI_INVALID'
|
error: 'UNSUPPORTED' | 'USER_DECLINED' | 'SUGGESTED_EMOJI_INVALID'
|
||||||
| 'DURATION_INVALID' | 'SERVER_ERROR' | 'UNKNOWN_ERROR';
|
| 'DURATION_INVALID' | 'SERVER_ERROR' | 'UNKNOWN_ERROR';
|
||||||
|
|||||||
@ -140,3 +140,28 @@ function isLastEmojiVersionSupported() {
|
|||||||
|
|
||||||
return Math.abs(newEmojiWidth - legacyEmojiWidth) < ALLOWABLE_CALCULATION_ERROR_SIZE;
|
return Math.abs(newEmojiWidth - legacyEmojiWidth) < ALLOWABLE_CALCULATION_ERROR_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const IS_GEOLOCATION_SUPPORTED = 'geolocation' in navigator;
|
||||||
|
|
||||||
|
export const getGeolocationStatus = async () => {
|
||||||
|
try {
|
||||||
|
const permissionStatus = await navigator.permissions.query({ name: 'geolocation' });
|
||||||
|
|
||||||
|
if (permissionStatus.state === 'granted' || permissionStatus.state === 'prompt') {
|
||||||
|
const geolocation = await new Promise<GeolocationCoordinates>((resolve, reject) => {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => resolve(position.coords),
|
||||||
|
(error) => reject(error),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return { accessRequested: true, accessGranted: true, geolocation };
|
||||||
|
}
|
||||||
|
if (permissionStatus.state === 'denied') {
|
||||||
|
return { accessRequested: true, accessGranted: false };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { accessRequested: false, accessGranted: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { accessRequested: false, accessGranted: false };
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user