Settings: Private message permissions (#4245)

This commit is contained in:
Alexander Zinchuk 2024-02-23 14:06:36 +01:00
parent 3d4ba7c47b
commit 7569689fb2
20 changed files with 264 additions and 36 deletions

View File

@ -615,17 +615,24 @@ export async function fetchGlobalPrivacySettings() {
return { return {
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers), shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
shouldHideReadMarks: Boolean(result.hideReadMarks), shouldHideReadMarks: Boolean(result.hideReadMarks),
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
}; };
} }
export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonContact, shouldHideReadMarks }: { export async function updateGlobalPrivacySettings({
shouldArchiveAndMuteNewNonContact: boolean; shouldArchiveAndMuteNewNonContact,
shouldHideReadMarks: boolean; shouldHideReadMarks,
shouldNewNonContactPeersRequirePremium,
}: {
shouldArchiveAndMuteNewNonContact?: boolean;
shouldHideReadMarks?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean;
}) { }) {
const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({ const result = await invokeRequest(new GramJs.account.SetGlobalPrivacySettings({
settings: new GramJs.GlobalPrivacySettings({ settings: new GramJs.GlobalPrivacySettings({
...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }), ...(shouldArchiveAndMuteNewNonContact && { archiveAndMuteNewNoncontactPeers: true }),
...(shouldHideReadMarks && { hideReadMarks: true }), ...(shouldHideReadMarks && { hideReadMarks: true }),
...(shouldNewNonContactPeersRequirePremium && { newNoncontactPeersRequirePremium: true }),
}), }),
})); }));
@ -636,6 +643,7 @@ export async function updateGlobalPrivacySettings({ shouldArchiveAndMuteNewNonCo
return { return {
shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers), shouldArchiveAndMuteNewNonContact: Boolean(result.archiveAndMuteNewNoncontactPeers),
shouldHideReadMarks: Boolean(result.hideReadMarks), shouldHideReadMarks: Boolean(result.hideReadMarks),
shouldNewNonContactPeersRequirePremium: Boolean(result.newNoncontactPeersRequirePremium),
}; };
} }

View File

@ -194,6 +194,7 @@ function LeftColumn({
case SettingsScreens.PrivacyForwarding: case SettingsScreens.PrivacyForwarding:
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
case SettingsScreens.PrivacyVoiceMessages: case SettingsScreens.PrivacyVoiceMessages:
case SettingsScreens.PrivacyMessages:
case SettingsScreens.PrivacyBlockedUsers: case SettingsScreens.PrivacyBlockedUsers:
case SettingsScreens.ActiveWebsites: case SettingsScreens.ActiveWebsites:
case SettingsScreens.TwoFaDisabled: case SettingsScreens.TwoFaDisabled:

View File

@ -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 (
<div className="settings-item">
<ListItem
leftElement={<PremiumIcon className="icon" withGradient big />}
onClick={handleOpenPremiumModal}
>
{lang('PrivacyLastSeenPremium')}
</ListItem>
<p
className="settings-item-description-larger premium-info"
dir={lang.isRtl ? 'rtl' : undefined}
>
{lang('lng_messages_privacy_premium_about')}
</p>
</div>
);
}
export default memo(PremiumStatusItem);

View File

@ -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);
}

View File

@ -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 (
<div
className={styles.contactsAndPremiumOptionTitle}
onClick={() => showNotification({ message: lang('OptionPremiumRequiredMessage') })}
>
<span>{label}</span>
<Icon name="lock-badge" className={styles.lockIcon} />
</div>
);
}
export default memo(PrivacyLockedOption);

View File

@ -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')
) : (
<PrivacyLockedOption label={lang('PrivacyMessagesContactsAndPremium')} />
),
hidden: !isCurrentUserPremium,
},
];
}, [lang, isCurrentUserPremium]);
const handleChange = useLastCallback((privacy: string) => {
updateGlobalPrivacySettings({ shouldNewNonContactPeersRequirePremium: privacy === 'contacts_and_premium' });
});
useHistoryBack({
isActive,
onBack: onReset,
});
return (
<>
<div className="settings-item">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyMessagesTitle')}
</h4>
<RadioGroup
name="privacy-messages"
options={options}
onChange={handleChange}
selected={shouldNewNonContactPeersRequirePremium ? 'contacts_and_premium' : 'everybody'}
/>
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('Privacy.Messages.SectionFooter')}
</p>
</div>
{!isCurrentUserPremium && <PremiumStatusItem />}
</>
);
}
export default memo(withGlobal<OwnProps>((global): StateProps => {
return {
shouldNewNonContactPeersRequirePremium: selectNewNoncontactPeersRequirePremium(global),
isCurrentUserPremium: selectIsCurrentUserPremium(global),
};
})(PrivacyMessages));

View File

@ -180,6 +180,10 @@
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
&.premium-info {
margin-top: 1rem;
}
&[dir="rtl"] { &[dir="rtl"] {
text-align: right; text-align: right;
} }

View File

@ -14,6 +14,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
import Transition from '../../ui/Transition'; import Transition from '../../ui/Transition';
import SettingsFolders from './folders/SettingsFolders'; import SettingsFolders from './folders/SettingsFolders';
import SettingsPasscode from './passcode/SettingsPasscode'; import SettingsPasscode from './passcode/SettingsPasscode';
import PrivacyMessages from './PrivacyMessages';
import SettingsActiveSessions from './SettingsActiveSessions'; import SettingsActiveSessions from './SettingsActiveSessions';
import SettingsActiveWebsites from './SettingsActiveWebsites'; import SettingsActiveWebsites from './SettingsActiveWebsites';
import SettingsCustomEmoji from './SettingsCustomEmoji'; import SettingsCustomEmoji from './SettingsCustomEmoji';
@ -372,6 +373,14 @@ const Settings: FC<OwnProps> = ({
/> />
); );
case SettingsScreens.PrivacyMessages:
return (
<PrivacyMessages
isActive={isScreenActive}
onReset={handleReset}
/>
);
case SettingsScreens.Folders: case SettingsScreens.Folders:
case SettingsScreens.FoldersCreateFolder: case SettingsScreens.FoldersCreateFolder:
case SettingsScreens.FoldersEditFolder: case SettingsScreens.FoldersEditFolder:

View File

@ -119,6 +119,8 @@ const SettingsHeader: FC<OwnProps> = ({
return <h3>{lang('PrivacyForwards')}</h3>; return <h3>{lang('PrivacyForwards')}</h3>;
case SettingsScreens.PrivacyVoiceMessages: case SettingsScreens.PrivacyVoiceMessages:
return <h3>{lang('PrivacyVoiceMessages')}</h3>; return <h3>{lang('PrivacyVoiceMessages')}</h3>;
case SettingsScreens.PrivacyMessages:
return <h3>{lang('PrivacyMessages')}</h3>;
case SettingsScreens.PrivacyGroupChats: case SettingsScreens.PrivacyGroupChats:
return <h3>{lang('AutodownloadGroupChats')}</h3>; return <h3>{lang('AutodownloadGroupChats')}</h3>;
case SettingsScreens.PrivacyPhoneCall: case SettingsScreens.PrivacyPhoneCall:

View File

@ -10,6 +10,7 @@ import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../globa
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import PremiumIcon from '../../common/PremiumIcon';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -30,6 +31,7 @@ type StateProps = {
canChangeSensitive?: boolean; canChangeSensitive?: boolean;
canDisplayAutoarchiveSetting: boolean; canDisplayAutoarchiveSetting: boolean;
shouldArchiveAndMuteNewNonContact?: boolean; shouldArchiveAndMuteNewNonContact?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean;
canDisplayChatInTitle?: boolean; canDisplayChatInTitle?: boolean;
privacyPhoneNumber?: ApiPrivacySettings; privacyPhoneNumber?: ApiPrivacySettings;
privacyLastSeen?: ApiPrivacySettings; privacyLastSeen?: ApiPrivacySettings;
@ -52,6 +54,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
canChangeSensitive, canChangeSensitive,
canDisplayAutoarchiveSetting, canDisplayAutoarchiveSetting,
shouldArchiveAndMuteNewNonContact, shouldArchiveAndMuteNewNonContact,
shouldNewNonContactPeersRequirePremium,
canDisplayChatInTitle, canDisplayChatInTitle,
canSetPasscode, canSetPasscode,
privacyPhoneNumber, privacyPhoneNumber,
@ -73,7 +76,6 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
loadGlobalPrivacySettings, loadGlobalPrivacySettings,
updateGlobalPrivacySettings, updateGlobalPrivacySettings,
loadWebAuthorizations, loadWebAuthorizations,
showNotification,
setSettingOption, setSettingOption,
} = getActions(); } = getActions();
@ -103,16 +105,6 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
}); });
}, [updateGlobalPrivacySettings]); }, [updateGlobalPrivacySettings]);
const handleVoiceMessagesClick = useCallback(() => {
if (isCurrentUserPremium) {
onScreenSelect(SettingsScreens.PrivacyVoiceMessages);
} else {
showNotification({
message: lang('PrivacyVoiceMessagesPremiumOnly'),
});
}
}, [isCurrentUserPremium, lang, onScreenSelect, showNotification]);
const handleChatInTitleChange = useCallback((isChecked: boolean) => { const handleChatInTitleChange = useCallback((isChecked: boolean) => {
setSettingOption({ setSettingOption({
canDisplayChatInTitle: isChecked, canDisplayChatInTitle: isChecked,
@ -298,11 +290,11 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
</ListItem> </ListItem>
<ListItem <ListItem
narrow narrow
disabled={!isCurrentUserPremium}
allowDisabledClick allowDisabledClick
rightElement={!isCurrentUserPremium && <i className="icon icon-lock-badge settings-icon-locked" />} rightElement={isCurrentUserPremium && <PremiumIcon big withGradient />}
className="no-icon" className="no-icon"
onClick={handleVoiceMessagesClick} // eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyVoiceMessages)}
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('PrivacyVoiceMessagesTitle')}</span> <span className="title">{lang('PrivacyVoiceMessagesTitle')}</span>
@ -311,6 +303,22 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
</span> </span>
</div> </div>
</ListItem> </ListItem>
<ListItem
narrow
rightElement={isCurrentUserPremium && <PremiumIcon big withGradient />}
className="no-icon"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => onScreenSelect(SettingsScreens.PrivacyMessages)}
>
<div className="multiline-menu-item">
<span className="title">{lang('PrivacyMessagesTitle')}</span>
<span className="subtitle" dir="auto">
{shouldNewNonContactPeersRequirePremium
? lang('PrivacyMessagesContactsAndPremium')
: lang('P2PEverybody')}
</span>
</div>
</ListItem>
</div> </div>
{canDisplayAutoarchiveSetting && ( {canDisplayAutoarchiveSetting && (
@ -362,7 +370,7 @@ export default memo(withGlobal<OwnProps>(
settings: { settings: {
byKey: { byKey: {
hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact, hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact,
canDisplayChatInTitle, canDisplayChatInTitle, shouldNewNonContactPeersRequirePremium,
}, },
privacy, privacy,
}, },
@ -383,6 +391,7 @@ export default memo(withGlobal<OwnProps>(
canDisplayAutoarchiveSetting: Boolean(appConfig?.canDisplayAutoarchiveSetting), canDisplayAutoarchiveSetting: Boolean(appConfig?.canDisplayAutoarchiveSetting),
shouldArchiveAndMuteNewNonContact, shouldArchiveAndMuteNewNonContact,
canChangeSensitive, canChangeSensitive,
shouldNewNonContactPeersRequirePremium,
privacyPhoneNumber: privacy.phoneNumber, privacyPhoneNumber: privacy.phoneNumber,
privacyLastSeen: privacy.lastSeen, privacyLastSeen: privacy.lastSeen,
privacyProfilePhoto: privacy.profilePhoto, privacyProfilePhoto: privacy.profilePhoto,

View File

@ -1,3 +0,0 @@
:global(.settings-item-description-larger).premiumInfo {
margin-top: 1rem;
}

View File

@ -4,7 +4,6 @@ import { getActions, withGlobal } from '../../../global';
import type { PrivacyVisibility } from '../../../types'; import type { PrivacyVisibility } from '../../../types';
import { selectIsCurrentUserPremium, selectShouldHideReadMarks } from '../../../global/selectors'; import { selectIsCurrentUserPremium, selectShouldHideReadMarks } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -14,8 +13,6 @@ import PremiumIcon from '../../common/PremiumIcon';
import Checkbox from '../../ui/Checkbox'; import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import styles from './SettingsPrivacyLastSeen.module.scss';
type OwnProps = { type OwnProps = {
visibility?: PrivacyVisibility; visibility?: PrivacyVisibility;
}; };
@ -59,10 +56,7 @@ const SettingsPrivacyLastSeen = ({
{isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')} {isCurrentUserPremium ? lang('PrivacyLastSeenPremiumForPremium') : lang('PrivacyLastSeenPremium')}
</ListItem> </ListItem>
<p <p
className={buildClassName( className="settings-item-description-larger premium-info"
'settings-item-description-larger',
styles.premiumInfo,
)}
dir={lang.isRtl ? 'rtl' : undefined} dir={lang.isRtl ? 'rtl' : undefined}
> >
{isCurrentUserPremium {isCurrentUserPremium

View File

@ -6,7 +6,7 @@ import type { ApiPhoto } from '../../../api/types';
import type { ApiPrivacySettings } from '../../../types'; import type { ApiPrivacySettings } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectUserFullInfo } from '../../../global/selectors'; import { selectIsCurrentUserPremium, selectUserFullInfo } from '../../../global/selectors';
import { getPrivacyKey } from './helpers/privacy'; import { getPrivacyKey } from './helpers/privacy';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
@ -15,6 +15,8 @@ import useLastCallback from '../../../hooks/useLastCallback';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
import PremiumStatusItem from './PremiumStatusItem';
import PrivacyLockedOption from './PrivacyLockedOption';
import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen'; import SettingsPrivacyLastSeen from './SettingsPrivacyLastSeen';
import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto'; import SettingsPrivacyPublicProfilePhoto from './SettingsPrivacyPublicProfilePhoto';
@ -31,6 +33,7 @@ type StateProps = {
currentUserFallbackPhoto?: ApiPhoto; currentUserFallbackPhoto?: ApiPhoto;
primaryPrivacy?: ApiPrivacySettings; primaryPrivacy?: ApiPrivacySettings;
secondaryPrivacy?: ApiPrivacySettings; secondaryPrivacy?: ApiPrivacySettings;
isPremiumRequired?: boolean;
}; };
const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
@ -41,6 +44,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
currentUserId, currentUserId,
hasCurrentUserFullInfo, hasCurrentUserFullInfo,
currentUserFallbackPhoto, currentUserFallbackPhoto,
isPremiumRequired,
onScreenSelect, onScreenSelect,
onReset, onReset,
}) => { }) => {
@ -67,6 +71,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps> = ({
screen={screen} screen={screen}
privacy={primaryPrivacy} privacy={primaryPrivacy}
onScreenSelect={onScreenSelect} onScreenSelect={onScreenSelect}
isPremiumRequired={isPremiumRequired}
/> />
{screen === SettingsScreens.PrivacyProfilePhoto && primaryPrivacy?.visibility !== 'everybody' && ( {screen === SettingsScreens.PrivacyProfilePhoto && primaryPrivacy?.visibility !== 'everybody' && (
<SettingsPrivacyPublicProfilePhoto <SettingsPrivacyPublicProfilePhoto
@ -93,9 +98,11 @@ function PrivacySubsection({
screen, screen,
privacy, privacy,
onScreenSelect, onScreenSelect,
isPremiumRequired,
}: { }: {
screen: SettingsScreens; screen: SettingsScreens;
privacy?: ApiPrivacySettings; privacy?: ApiPrivacySettings;
isPremiumRequired?: boolean;
onScreenSelect: (screen: SettingsScreens) => void; onScreenSelect: (screen: SettingsScreens) => void;
}) { }) {
const { setPrivacyVisibility } = getActions(); const { setPrivacyVisibility } = getActions();
@ -105,13 +112,30 @@ function PrivacySubsection({
const hasNobody = screen !== SettingsScreens.PrivacyAddByPhone; const hasNobody = screen !== SettingsScreens.PrivacyAddByPhone;
const options = [ const options = [
{ value: 'everybody', label: lang('P2PEverybody') }, { value: 'everybody', label: lang('P2PEverybody') },
{ value: 'contacts', label: lang('P2PContacts') }, {
value: 'contacts',
label: isPremiumRequired ? (
<PrivacyLockedOption label={lang('P2PContacts')} />
) : (
lang('P2PContacts')
),
hidden: isPremiumRequired,
},
]; ];
if (hasNobody) { if (hasNobody) {
options.push({ value: 'nobody', label: lang('P2PNobody') }); options.push({
value: 'nobody',
label: isPremiumRequired ? (
<PrivacyLockedOption label={lang('P2PNobody')} />
) : (
lang('P2PNobody')
),
hidden: isPremiumRequired,
});
} }
return options; return options;
}, [lang, screen]); }, [lang, screen, isPremiumRequired]);
const primaryExceptionLists = useMemo(() => { const primaryExceptionLists = useMemo(() => {
if (screen === SettingsScreens.PrivacyAddByPhone) { if (screen === SettingsScreens.PrivacyAddByPhone) {
@ -136,6 +160,8 @@ function PrivacySubsection({
case SettingsScreens.PrivacyAddByPhone: { case SettingsScreens.PrivacyAddByPhone: {
return privacy?.visibility === 'everybody' ? lang('PrivacyPhoneInfo') : lang('PrivacyPhoneInfo3'); return privacy?.visibility === 'everybody' ? lang('PrivacyPhoneInfo') : lang('PrivacyPhoneInfo3');
} }
case SettingsScreens.PrivacyVoiceMessages:
return lang('PrivacyVoiceMessagesInfo');
default: default:
return undefined; return undefined;
} }
@ -257,7 +283,7 @@ function PrivacySubsection({
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>{descriptionText}</p> <p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>{descriptionText}</p>
)} )}
</div> </div>
{(primaryExceptionLists.shouldShowAllowed || primaryExceptionLists.shouldShowDenied) && ( {!isPremiumRequired && (primaryExceptionLists.shouldShowAllowed || primaryExceptionLists.shouldShowDenied) && (
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}> <h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('PrivacyExceptions')} {lang('PrivacyExceptions')}
@ -294,6 +320,7 @@ function PrivacySubsection({
)} )}
</div> </div>
)} )}
{isPremiumRequired && <PremiumStatusItem />}
</> </>
); );
} }
@ -361,6 +388,7 @@ export default memo(withGlobal<OwnProps>(
currentUserId: currentUserId!, currentUserId: currentUserId!,
hasCurrentUserFullInfo: Boolean(currentUserFullInfo), hasCurrentUserFullInfo: Boolean(currentUserFullInfo),
currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto, currentUserFallbackPhoto: currentUserFullInfo?.fallbackPhoto,
isPremiumRequired: screen === SettingsScreens.PrivacyVoiceMessages && !selectIsCurrentUserPremium(global),
}; };
}, },
)(SettingsPrivacyVisibility)); )(SettingsPrivacyVisibility));

View File

@ -20,6 +20,7 @@ type OwnProps = {
disabled?: boolean; disabled?: boolean;
hidden?: boolean; hidden?: boolean;
isLoading?: boolean; isLoading?: boolean;
className?: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void; onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}; };
@ -33,18 +34,20 @@ const Radio: FC<OwnProps> = ({
disabled, disabled,
hidden, hidden,
isLoading, isLoading,
className,
onChange, onChange,
}) => { }) => {
const lang = useLang(); const lang = useLang();
const className = buildClassName( const fullClassName = buildClassName(
'Radio', 'Radio',
className,
disabled && 'disabled', disabled && 'disabled',
hidden && 'hidden-widget', hidden && 'hidden-widget',
isLoading && 'loading', isLoading && 'loading',
); );
return ( return (
<label className={className} dir={lang.isRtl ? 'rtl' : undefined}> <label className={fullClassName} dir={lang.isRtl ? 'rtl' : undefined}>
<input <input
type="radio" type="radio"
name={name} name={name}

View File

@ -9,6 +9,7 @@ export type IRadioOption = {
subLabel?: string; subLabel?: string;
value: string; value: string;
hidden?: boolean; hidden?: boolean;
className?: string;
}; };
type OwnProps = { type OwnProps = {
@ -47,6 +48,7 @@ const RadioGroup: FC<OwnProps> = ({
hidden={option.hidden} hidden={option.hidden}
disabled={disabled} disabled={disabled}
isLoading={loadingOption ? loadingOption === option.value : undefined} isLoading={loadingOption ? loadingOption === option.value : undefined}
className={option.className}
onChange={handleChange} onChange={handleChange}
/> />
))} ))}

View File

@ -674,6 +674,8 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
const shouldArchiveAndMuteNewNonContact = payload.shouldArchiveAndMuteNewNonContact const shouldArchiveAndMuteNewNonContact = payload.shouldArchiveAndMuteNewNonContact
?? Boolean(global.settings.byKey.shouldArchiveAndMuteNewNonContact); ?? Boolean(global.settings.byKey.shouldArchiveAndMuteNewNonContact);
const shouldHideReadMarks = payload.shouldHideReadMarks ?? Boolean(global.settings.byKey.shouldHideReadMarks); const shouldHideReadMarks = payload.shouldHideReadMarks ?? Boolean(global.settings.byKey.shouldHideReadMarks);
const shouldNewNonContactPeersRequirePremium = payload.shouldNewNonContactPeersRequirePremium
?? Boolean(global.settings.byKey.shouldNewNonContactPeersRequirePremium);
global = replaceSettings(global, { shouldArchiveAndMuteNewNonContact, shouldHideReadMarks }); global = replaceSettings(global, { shouldArchiveAndMuteNewNonContact, shouldHideReadMarks });
setGlobal(global); setGlobal(global);
@ -681,6 +683,7 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
const result = await callApi('updateGlobalPrivacySettings', { const result = await callApi('updateGlobalPrivacySettings', {
shouldArchiveAndMuteNewNonContact, shouldArchiveAndMuteNewNonContact,
shouldHideReadMarks, shouldHideReadMarks,
shouldNewNonContactPeersRequirePremium,
}); });
global = getGlobal(); global = getGlobal();
@ -689,6 +692,9 @@ addActionHandler('updateGlobalPrivacySettings', async (global, actions, payload)
? !shouldArchiveAndMuteNewNonContact ? !shouldArchiveAndMuteNewNonContact
: result.shouldArchiveAndMuteNewNonContact, : result.shouldArchiveAndMuteNewNonContact,
shouldHideReadMarks: !result ? !shouldHideReadMarks : result.shouldHideReadMarks, shouldHideReadMarks: !result ? !shouldHideReadMarks : result.shouldHideReadMarks,
shouldNewNonContactPeersRequirePremium: !result
? !shouldNewNonContactPeersRequirePremium
: result.shouldNewNonContactPeersRequirePremium,
}); });
setGlobal(global); setGlobal(global);
}); });

View File

@ -242,6 +242,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
wasTimeFormatSetManually: false, wasTimeFormatSetManually: false,
isConnectionStatusMinimized: true, isConnectionStatusMinimized: true,
shouldArchiveAndMuteNewNonContact: false, shouldArchiveAndMuteNewNonContact: false,
shouldNewNonContactPeersRequirePremium: false,
shouldHideReadMarks: false, shouldHideReadMarks: false,
canTranslate: false, canTranslate: false,
canTranslateChats: true, canTranslateChats: true,

View File

@ -20,6 +20,10 @@ export function selectTranslationLanguage<T extends GlobalState>(global: T) {
return global.settings.byKey.translationLanguage || selectLanguageCode(global); return global.settings.byKey.translationLanguage || selectLanguageCode(global);
} }
export function selectNewNoncontactPeersRequirePremium<T extends GlobalState>(global: T) {
return global.settings.byKey.shouldNewNonContactPeersRequirePremium;
}
export function selectShouldHideReadMarks<T extends GlobalState>(global: T) { export function selectShouldHideReadMarks<T extends GlobalState>(global: T) {
return global.settings.byKey.shouldHideReadMarks; return global.settings.byKey.shouldHideReadMarks;
} }

View File

@ -2873,7 +2873,11 @@ export interface ActionPayloads {
} & WithTabId; } & WithTabId;
closeShareChatFolderModal: undefined | WithTabId; closeShareChatFolderModal: undefined | WithTabId;
loadGlobalPrivacySettings: undefined; loadGlobalPrivacySettings: undefined;
updateGlobalPrivacySettings: { shouldArchiveAndMuteNewNonContact?: boolean; shouldHideReadMarks?: boolean }; updateGlobalPrivacySettings: {
shouldArchiveAndMuteNewNonContact?: boolean;
shouldHideReadMarks?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean;
};
// Premium // Premium
openPremiumModal: ({ openPremiumModal: ({

View File

@ -101,6 +101,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
wasTimeFormatSetManually: boolean; wasTimeFormatSetManually: boolean;
isConnectionStatusMinimized: boolean; isConnectionStatusMinimized: boolean;
shouldArchiveAndMuteNewNonContact?: boolean; shouldArchiveAndMuteNewNonContact?: boolean;
shouldNewNonContactPeersRequirePremium?: boolean;
shouldHideReadMarks?: boolean; shouldHideReadMarks?: boolean;
canTranslate: boolean; canTranslate: boolean;
canTranslateChats: boolean; canTranslateChats: boolean;
@ -183,6 +184,7 @@ export enum SettingsScreens {
PrivacyPhoneP2P, PrivacyPhoneP2P,
PrivacyForwarding, PrivacyForwarding,
PrivacyVoiceMessages, PrivacyVoiceMessages,
PrivacyMessages,
PrivacyGroupChats, PrivacyGroupChats,
PrivacyPhoneNumberAllowedContacts, PrivacyPhoneNumberAllowedContacts,
PrivacyPhoneNumberDeniedContacts, PrivacyPhoneNumberDeniedContacts,