diff --git a/src/api/gramjs/methods/settings.ts b/src/api/gramjs/methods/settings.ts index d69d35fb9..f367adcb1 100644 --- a/src/api/gramjs/methods/settings.ts +++ b/src/api/gramjs/methods/settings.ts @@ -615,17 +615,24 @@ export async function fetchGlobalPrivacySettings() { return { shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers), shouldHideReadMarks: Boolean(result.hideReadMarks), + shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), }; } -export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonContact, shouldHideReadMarks }: { - shouldArchiveAndMuteNewNonContact: boolean; - shouldHideReadMarks: boolean; +export async function updateGlobalPrivacySettings({ + shouldArchiveAndMuteNewNonContact, + shouldHideReadMarks, + shouldNewNonContactPeersRequirePremium, +}: { + shouldArchiveAndMuteNewNonContact?: boolean; + shouldHideReadMarks?: boolean; + shouldNewNonContactPeersRequirePremium?: boolean; }) { const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({ settings: new GramJs.GlobalPrivacySettings({ ...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }), ...(shouldHideReadMarks && { hideReadMarks: true }), + ...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }), }), })); @@ -636,6 +643,7 @@ export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonCo return { shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers), shouldHideReadMarks: Boolean(result.hideReadMarks), + shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium), }; } diff --git a/src/components/left/LeftColumn.tsx b/src/components/left/LeftColumn.tsx index 63e06b7f9..d64426b40 100644 --- a/src/components/left/LeftColumn.tsx +++ b/src/components/left/LeftColumn.tsx @@ -194,6 +194,7 @@ function LeftColumn({ case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyVoiceMessages: + case SettingsScreens.PrivacyMessages: case SettingsScreens.PrivacyBlockedUsers: case SettingsScreens.ActiveWebsites: case SettingsScreens.TwoFaDisabled: diff --git a/src/components/left/settings/PremiumStatusItem.tsx b/src/components/left/settings/PremiumStatusItem.tsx new file mode 100644 index 000000000..b74749a98 --- /dev/null +++ b/src/components/left/settings/PremiumStatusItem.tsx @@ -0,0 +1,33 @@ +import React, { memo } from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; + +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import PremiumIcon from '../../common/PremiumIcon'; +import ListItem from '../../ui/ListItem'; + +function PremiumStatusItem() { + const { openPremiumModal } = getActions(); + const lang = useLang(); + const handleOpenPremiumModal = useLastCallback(() => openPremiumModal()); + + return ( +
+ } + onClick={handleOpenPremiumModal} + > + {lang('PrivacyLastSeenPremium')} + +

+ {lang('lng_messages_privacy_premium_about')} +

+
+ ); +} + +export default memo(PremiumStatusItem); diff --git a/src/components/left/settings/PrivacyLockedOption.module.scss b/src/components/left/settings/PrivacyLockedOption.module.scss new file mode 100644 index 000000000..be9e90ade --- /dev/null +++ b/src/components/left/settings/PrivacyLockedOption.module.scss @@ -0,0 +1,12 @@ +.contacts_and_premium_option-title { + cursor: pointer; +} + +.lock-icon { + font-size: 1.25rem; + position: absolute; + left: 1.0625rem; + top: 50%; + transform: translateY(-50%); + color: var(--color-gray); +} diff --git a/src/components/left/settings/PrivacyLockedOption.tsx b/src/components/left/settings/PrivacyLockedOption.tsx new file mode 100644 index 000000000..ac159de12 --- /dev/null +++ b/src/components/left/settings/PrivacyLockedOption.tsx @@ -0,0 +1,29 @@ +import React, { memo } from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; + +import useLang from '../../../hooks/useLang'; + +import Icon from '../../common/Icon'; + +import styles from './PrivacyLockedOption.module.scss'; + +type OwnProps = { + label: string; +}; + +function PrivacyLockedOption({ label }: OwnProps) { + const lang = useLang(); + const { showNotification } = getActions(); + + return ( +
showNotification({ message: lang('OptionPremiumRequiredMessage') })} + > + {label} + +
+ ); +} + +export default memo(PrivacyLockedOption); diff --git a/src/components/left/settings/PrivacyMessages.tsx b/src/components/left/settings/PrivacyMessages.tsx new file mode 100644 index 000000000..e2f2f0378 --- /dev/null +++ b/src/components/left/settings/PrivacyMessages.tsx @@ -0,0 +1,80 @@ +import React, { memo, useMemo } from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import { selectIsCurrentUserPremium, selectNewNoncontactPeersRequirePremium } from '../../../global/selectors'; + +import useHistoryBack from '../../../hooks/useHistoryBack'; +import useLang from '../../../hooks/useLang'; +import useLastCallback from '../../../hooks/useLastCallback'; + +import RadioGroup from '../../ui/RadioGroup'; +import PremiumStatusItem from './PremiumStatusItem'; +import PrivacyLockedOption from './PrivacyLockedOption'; + +type OwnProps = { + isActive?: boolean; + onReset: VoidFunction; +}; + +type StateProps = { + shouldNewNonContactPeersRequirePremium?: boolean; + isCurrentUserPremium?: boolean; +}; + +function PrivacyMessages({ + isActive, onReset, shouldNewNonContactPeersRequirePremium, isCurrentUserPremium, +}: OwnProps & StateProps) { + const { updateGlobalPrivacySettings } = getActions(); + const lang = useLang(); + + const options = useMemo(() => { + return [ + { value: 'everybody', label: lang('P2PEverybody') }, + { + value: 'contacts_and_premium', + label: isCurrentUserPremium ? ( + lang('PrivacyMessagesContactsAndPremium') + ) : ( + + ), + hidden: !isCurrentUserPremium, + }, + ]; + }, [lang, isCurrentUserPremium]); + + const handleChange = useLastCallback((privacy: string) => { + updateGlobalPrivacySettings({ shouldNewNonContactPeersRequirePremium: privacy === 'contacts_and_premium' }); + }); + + useHistoryBack({ + isActive, + onBack: onReset, + }); + + return ( + <> +
+

+ {lang('PrivacyMessagesTitle')} +

+ +

+ {lang('Privacy.Messages.SectionFooter')} +

+
+ {!isCurrentUserPremium && } + + ); +} + +export default memo(withGlobal((global): StateProps => { + return { + shouldNewNonContactPeersRequirePremium: selectNewNoncontactPeersRequirePremium(global), + isCurrentUserPremium: selectIsCurrentUserPremium(global), + }; +})(PrivacyMessages)); diff --git a/src/components/left/settings/Settings.scss b/src/components/left/settings/Settings.scss index 35e7968f5..08aeaf1ad 100644 --- a/src/components/left/settings/Settings.scss +++ b/src/components/left/settings/Settings.scss @@ -180,6 +180,10 @@ margin-top: 2rem; margin-bottom: 0.75rem; + &.premium-info { + margin-top: 1rem; + } + &[dir="rtl"] { text-align: right; } diff --git a/src/components/left/settings/Settings.tsx b/src/components/left/settings/Settings.tsx index bba21e58c..c5e4a2b2a 100644 --- a/src/components/left/settings/Settings.tsx +++ b/src/components/left/settings/Settings.tsx @@ -14,6 +14,7 @@ import useLastCallback from '../../../hooks/useLastCallback'; import Transition from '../../ui/Transition'; import SettingsFolders from './folders/SettingsFolders'; import SettingsPasscode from './passcode/SettingsPasscode'; +import PrivacyMessages from './PrivacyMessages'; import SettingsActiveSessions from './SettingsActiveSessions'; import SettingsActiveWebsites from './SettingsActiveWebsites'; import SettingsCustomEmoji from './SettingsCustomEmoji'; @@ -372,6 +373,14 @@ const Settings: FC = ({ /> ); + case SettingsScreens.PrivacyMessages: + return ( + + ); + case SettingsScreens.Folders: case SettingsScreens.FoldersCreateFolder: case SettingsScreens.FoldersEditFolder: diff --git a/src/components/left/settings/SettingsHeader.tsx b/src/components/left/settings/SettingsHeader.tsx index 30ebb9390..4425e4937 100644 --- a/src/components/left/settings/SettingsHeader.tsx +++ b/src/components/left/settings/SettingsHeader.tsx @@ -119,6 +119,8 @@ const SettingsHeader: FC = ({ return

{lang('PrivacyForwards')}

; case SettingsScreens.PrivacyVoiceMessages: return

{lang('PrivacyVoiceMessages')}

; + case SettingsScreens.PrivacyMessages: + return

{lang('PrivacyMessages')}

; case SettingsScreens.PrivacyGroupChats: return

{lang('AutodownloadGroupChats')}

; case SettingsScreens.PrivacyPhoneCall: diff --git a/src/components/left/settings/SettingsPrivacy.tsx b/src/components/left/settings/SettingsPrivacy.tsx index 980774b80..c8f3a3e06 100644 --- a/src/components/left/settings/SettingsPrivacy.tsx +++ b/src/components/left/settings/SettingsPrivacy.tsx @@ -10,6 +10,7 @@ import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../globa import useHistoryBack from '../../../hooks/useHistoryBack'; import useLang from '../../../hooks/useLang'; +import PremiumIcon from '../../common/PremiumIcon'; import Checkbox from '../../ui/Checkbox'; import ListItem from '../../ui/ListItem'; @@ -30,6 +31,7 @@ type StateProps = { canChangeSensitive?: boolean; canDisplayAutoarchiveSetting: boolean; shouldArchiveAndMuteNewNonContact?: boolean; + shouldNewNonContactPeersRequirePremium?: boolean; canDisplayChatInTitle?: boolean; privacyPhoneNumber?: ApiPrivacySettings; privacyLastSeen?: ApiPrivacySettings; @@ -52,6 +54,7 @@ const SettingsPrivacy: FC = ({ canChangeSensitive, canDisplayAutoarchiveSetting, shouldArchiveAndMuteNewNonContact, + shouldNewNonContactPeersRequirePremium, canDisplayChatInTitle, canSetPasscode, privacyPhoneNumber, @@ -73,7 +76,6 @@ const SettingsPrivacy: FC = ({ loadGlobalPrivacySettings, updateGlobalPrivacySettings, loadWebAuthorizations, - showNotification, setSettingOption, } = getActions(); @@ -103,16 +105,6 @@ const SettingsPrivacy: FC = ({ }); }, [updateGlobalPrivacySettings]); - const handleVoiceMessagesClick = useCallback(() => { - if (isCurrentUserPremium) { - onScreenSelect(SettingsScreens.PrivacyVoiceMessages); - } else { - showNotification({ - message: lang('PrivacyVoiceMessagesPremiumOnly'), - }); - } - }, [isCurrentUserPremium, lang, onScreenSelect, showNotification]); - const handleChatInTitleChange = useCallback((isChecked: boolean) => { setSettingOption({ canDisplayChatInTitle: isChecked, @@ -298,11 +290,11 @@ const SettingsPrivacy: FC = ({ } + rightElement={isCurrentUserPremium && } className="no-icon" - onClick={handleVoiceMessagesClick} + // eslint-disable-next-line react/jsx-no-bind + onClick={() => onScreenSelect(SettingsScreens.PrivacyVoiceMessages)} >
{lang('PrivacyVoiceMessagesTitle')} @@ -311,6 +303,22 @@ const SettingsPrivacy: FC = ({
+ } + className="no-icon" + // eslint-disable-next-line react/jsx-no-bind + onClick={() => onScreenSelect(SettingsScreens.PrivacyMessages)} + > +
+ {lang('PrivacyMessagesTitle')} + + {shouldNewNonContactPeersRequirePremium + ? lang('PrivacyMessagesContactsAndPremium') + : lang('P2PEverybody')} + +
+
{canDisplayAutoarchiveSetting && ( @@ -362,7 +370,7 @@ export default memo(withGlobal( settings: { byKey: { hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact, - canDisplayChatInTitle, + canDisplayChatInTitle, shouldNewNonContactPeersRequirePremium, }, privacy, }, @@ -383,6 +391,7 @@ export default memo(withGlobal( canDisplayAutoarchiveSetting: Boolean(appConfig?.canDisplayAutoarchiveSetting), shouldArchiveAndMuteNewNonContact, canChangeSensitive, + shouldNewNonContactPeersRequirePremium, privacyPhoneNumber: privacy.phoneNumber, privacyLastSeen: privacy.lastSeen, privacyProfilePhoto: privacy.profilePhoto, diff --git a/src/components/left/settings/SettingsPrivacyLastSeen.module.scss b/src/components/left/settings/SettingsPrivacyLastSeen.module.scss deleted file mode 100644 index 898838f0b..000000000 --- a/src/components/left/settings/SettingsPrivacyLastSeen.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -:global(.settings-item-description-larger).premiumInfo { - margin-top: 1rem; -} diff --git a/src/components/left/settings/SettingsPrivacyLastSeen.tsx b/src/components/left/settings/SettingsPrivacyLastSeen.tsx index 96964dbeb..ff7c90e0f 100644 --- a/src/components/left/settings/SettingsPrivacyLastSeen.tsx +++ b/src/components/left/settings/SettingsPrivacyLastSeen.tsx @@ -4,7 +4,6 @@ import { getActions, withGlobal } from '../../../global'; import type { PrivacyVisibility } from '../../../types'; import { selectIsCurrentUserPremium, selectShouldHideReadMarks } from '../../../global/selectors'; -import buildClassName from '../../../util/buildClassName'; import renderText from '../../common/helpers/renderText'; import useLang from '../../../hooks/useLang'; @@ -14,8 +13,6 @@ import PremiumIcon from '../../common/PremiumIcon'; import Checkbox from '../../ui/Checkbox'; import ListItem from '../../ui/ListItem'; -import styles from './SettingsPrivacyLastSeen.module.scss'; - type OwnProps = { visibility?: PrivacyVisibility; }; @@ -59,10 +56,7 @@ const SettingsPrivacyLastSeen = ({ {isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')}

{isCurrentUserPremium diff --git a/src/components/left/settings/SettingsPrivacyVisibility.tsx b/src/components/left/settings/SettingsPrivacyVisibility.tsx index 16556552a..e3042ecca 100644 --- a/src/components/left/settings/SettingsPrivacyVisibility.tsx +++ b/src/components/left/settings/SettingsPrivacyVisibility.tsx @@ -6,7 +6,7 @@ import type { ApiPhoto } from '../../../api/types'; import type { ApiPrivacySettings } from '../../../types'; import { SettingsScreens } from '../../../types'; -import { selectUserFullInfo } from '../../../global/selectors'; +import { selectIsCurrentUserPremium, selectUserFullInfo } from '../../../global/selectors'; import { getPrivacyKey } from './helpers/privacy'; import useHistoryBack from '../../../hooks/useHistoryBack'; @@ -15,6 +15,8 @@ import useLastCallback from '../../../hooks/useLastCallback'; import ListItem from '../../ui/ListItem'; import RadioGroup from '../../ui/RadioGroup'; +import PremiumStatusItem from './PremiumStatusItem'; +import PrivacyLockedOption from './PrivacyLockedOption'; import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen'; import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto'; @@ -31,6 +33,7 @@ type StateProps = { currentUserFallbackPhoto?: ApiPhoto; primaryPrivacy?: ApiPrivacySettings; secondaryPrivacy?: ApiPrivacySettings; + isPremiumRequired?: boolean; }; const SettingsPrivacyVisibility: FC = ({ @@ -41,6 +44,7 @@ const SettingsPrivacyVisibility: FC = ({ currentUserId, hasCurrentUserFullInfo, currentUserFallbackPhoto, + isPremiumRequired, onScreenSelect, onReset, }) => { @@ -67,6 +71,7 @@ const SettingsPrivacyVisibility: FC = ({ screen={screen} privacy={primaryPrivacy} onScreenSelect={onScreenSelect} + isPremiumRequired={isPremiumRequired} /> {screen === SettingsScreens.PrivacyProfilePhoto && primaryPrivacy?.visibility !== 'everybody' && ( void; }) { const { setPrivacyVisibility } = getActions(); @@ -105,13 +112,30 @@ function PrivacySubsection({ const hasNobody = screen !== SettingsScreens.PrivacyAddByPhone; const options = [ { value: 'everybody', label: lang('P2PEverybody') }, - { value: 'contacts', label: lang('P2PContacts') }, + { + value: 'contacts', + label: isPremiumRequired ? ( + + ) : ( + lang('P2PContacts') + ), + hidden: isPremiumRequired, + }, ]; + if (hasNobody) { - options.push({ value: 'nobody', label: lang('P2PNobody') }); + options.push({ + value: 'nobody', + label: isPremiumRequired ? ( + + ) : ( + lang('P2PNobody') + ), + hidden: isPremiumRequired, + }); } return options; - }, [lang, screen]); + }, [lang, screen, isPremiumRequired]); const primaryExceptionLists = useMemo(() => { if (screen === SettingsScreens.PrivacyAddByPhone) { @@ -136,6 +160,8 @@ function PrivacySubsection({ case SettingsScreens.PrivacyAddByPhone: { return privacy?.visibility === 'everybody' ? lang('PrivacyPhoneInfo') : lang('PrivacyPhoneInfo3'); } + case SettingsScreens.PrivacyVoiceMessages: + return lang('PrivacyVoiceMessagesInfo'); default: return undefined; } @@ -257,7 +283,7 @@ function PrivacySubsection({

{descriptionText}

)} - {(primaryExceptionLists.shouldShowAllowed || primaryExceptionLists.shouldShowDenied) && ( + {!isPremiumRequired && (primaryExceptionLists.shouldShowAllowed || primaryExceptionLists.shouldShowDenied) && (

{lang('PrivacyExceptions')} @@ -294,6 +320,7 @@ function PrivacySubsection({ )}

)} + {isPremiumRequired && } ); } @@ -361,6 +388,7 @@ export default memo(withGlobal( currentUserId: currentUserId!, hasCurrentUserFullInfo: Boolean(currentUserFullInfo), currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto, + isPremiumRequired: screen === SettingsScreens.PrivacyVoiceMessages && !selectIsCurrentUserPremium(global), }; }, )(SettingsPrivacyVisibility)); diff --git a/src/components/ui/Radio.tsx b/src/components/ui/Radio.tsx index 5747beba5..a1688c713 100644 --- a/src/components/ui/Radio.tsx +++ b/src/components/ui/Radio.tsx @@ -20,6 +20,7 @@ type OwnProps = { disabled?: boolean; hidden?: boolean; isLoading?: boolean; + className?: string; onChange: (e: ChangeEvent) => void; }; @@ -33,18 +34,20 @@ const Radio: FC = ({ disabled, hidden, isLoading, + className, onChange, }) => { const lang = useLang(); - const className = buildClassName( + const fullClassName = buildClassName( 'Radio', + className, disabled && 'disabled', hidden && 'hidden-widget', isLoading && 'loading', ); return ( -