UserStatus: Add button to change privacy or buy premium to see user status (#4346)

This commit is contained in:
Alexander Zinchuk 2024-03-08 12:48:50 +01:00
parent c96d05bd9f
commit 70dfa2e160
14 changed files with 152 additions and 59 deletions

BIN
src/assets/tgs/LastSeen.tgs Normal file

Binary file not shown.

View File

@ -30,7 +30,7 @@ export { default as PinMessageModal } from '../components/common/PinMessageModal
export { default as UnpinAllMessagesModal } from '../components/common/UnpinAllMessagesModal';
export { default as MessageSelectToolbar } from '../components/middle/MessageSelectToolbar';
export { default as SeenByModal } from '../components/common/SeenByModal';
export { default as ReadTimeModal } from '../components/common/ReadDateModal';
export { default as PrivacySettingsNoticeModal } from '../components/common/PrivacySettingsNoticeModal';
export { default as ReactorListModal } from '../components/middle/ReactorListModal';
export { default as EmojiInteractionAnimation } from '../components/middle/EmojiInteractionAnimation';
export { default as ChatLanguageModal } from '../components/middle/ChatLanguageModal';

View File

@ -0,0 +1,18 @@
import type { FC } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import type { OwnProps } from './PrivacySettingsNoticeModal';
import { Bundles } from '../../util/moduleLoader';
import useModuleLoader from '../../hooks/useModuleLoader';
const PrivacySettingsNoticeModalAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const PrivacySettingsNoticeModal = useModuleLoader(Bundles.Extra, 'PrivacySettingsNoticeModal', !isOpen);
// eslint-disable-next-line react/jsx-props-no-spreading
return PrivacySettingsNoticeModal ? <PrivacySettingsNoticeModal {...props} /> : undefined;
};
export default PrivacySettingsNoticeModalAsync;

View File

@ -18,7 +18,7 @@ import Separator from '../ui/Separator';
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
import Icon from './Icon';
import styles from './ReadDateModal.module.scss';
import styles from './PrivacySettingsNoticeModal.module.scss';
export type OwnProps = {
isOpen: boolean;
@ -26,28 +26,47 @@ export type OwnProps = {
type StateProps = {
user?: ApiUser;
isReadDate?: boolean;
};
const CLOSE_ANIMATION_DURATION = ANIMATION_DURATION + ANIMATION_END_DELAY;
const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
const PrivacySettingsNoticeModal = ({ isOpen, isReadDate, user }: OwnProps & StateProps) => {
const lang = useLang();
const {
updateGlobalPrivacySettings, openPremiumModal, closeGetReadDateModal, showNotification,
updateGlobalPrivacySettings,
openPremiumModal,
closePrivacySettingsNoticeModal,
showNotification,
setPrivacyVisibility,
loadUser,
} = getActions();
const userName = getUserFirstOrLastName(user);
const handleShowReadTime = useLastCallback(() => {
updateGlobalPrivacySettings({ shouldHideReadMarks: false });
closeGetReadDateModal();
closePrivacySettingsNoticeModal();
setTimeout(() => {
showNotification({ message: lang('PremiumReadSet') });
}, CLOSE_ANIMATION_DURATION);
});
const handleShowLastSeen = useLastCallback(() => {
setPrivacyVisibility({
privacyKey: 'lastSeen',
visibility: 'everybody',
onSuccess: () => loadUser({ userId: user!.id }),
});
closePrivacySettingsNoticeModal();
setTimeout(() => {
showNotification({ message: lang('PremiumLastSeenSet') });
}, CLOSE_ANIMATION_DURATION);
});
const handleOpenPremium = useLastCallback(() => {
closeGetReadDateModal();
closePrivacySettingsNoticeModal();
setTimeout(() => {
openPremiumModal();
@ -55,7 +74,7 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
});
const handleClose = useLastCallback(() => {
closeGetReadDateModal();
closePrivacySettingsNoticeModal();
});
return (
@ -72,25 +91,45 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
<Icon name="close" />
</Button>
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.ReadTime}
tgsUrl={isReadDate ? LOCAL_TGS_URLS.ReadTime : LOCAL_TGS_URLS.LastSeen}
size={84}
className={styles.icon}
nonInteractive
noLoop
/>
<h2 className={styles.header}>{lang('PremiumReadHeader1')}</h2>
<p className={styles.desc}>{renderText(lang('PremiumReadText1', userName), ['simple_markdown'])}</p>
<h2 className={styles.header}>
{lang(isReadDate ? 'PremiumReadHeader1' : 'PremiumLastSeenHeader1')}
</h2>
<p className={styles.desc}>
{renderText(
lang(
isReadDate ? 'PremiumReadText1' : 'PremiumLastSeenText1Locked',
userName,
),
['simple_markdown'],
)}
</p>
<Button
size="smaller"
onClick={handleShowReadTime}
onClick={isReadDate ? handleShowReadTime : handleShowLastSeen}
className={styles.button}
>
{lang('PremiumReadButton1')}
{lang(isReadDate ? 'PremiumReadButton1' : 'PremiumLastSeenButton1')}
</Button>
<Separator className={styles.separator}>{lang('PremiumOr')}</Separator>
<h2 className={styles.header}>{lang('PremiumReadHeader2')}</h2>
<p className={styles.desc}>{renderText(lang('PremiumReadText2', userName), ['simple_markdown'])}</p>
<Button withPremiumGradient size="smaller" onClick={handleOpenPremium} className={styles.button}>
<p className={styles.desc}>
{renderText(
lang(isReadDate ? 'PremiumReadText2' : 'PremiumLastSeenText2', userName),
['simple_markdown'],
)}
</p>
<Button
withPremiumGradient
size="smaller"
onClick={handleOpenPremium}
className={styles.button}
>
{lang('PremiumLastSeenButton2')}
</Button>
</div>
@ -98,11 +137,11 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => {
);
};
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const { chatId } = selectTabState(global).readDateModal || {};
export default memo(
withGlobal<OwnProps>((global): StateProps => {
const { chatId, isReadDate } = selectTabState(global).privacySettingsNoticeModal || {};
const user = chatId ? selectUser(global, chatId) : undefined;
return { user };
},
)(ReadDateModal));
return { user, isReadDate };
})(PrivacySettingsNoticeModal),
);

View File

@ -48,4 +48,14 @@
pointer-events: auto;
cursor: var(--custom-cursor, pointer);
}
.get-status {
cursor: var(--custom-cursor, pointer);
margin-left: 0.375rem;
border-radius: 0.5rem;
padding: 0.125rem 0.375rem;
background: var(--color-background-menu-separator);
pointer-events: all;
font-weight: 500;
}
}

View File

@ -90,6 +90,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
openMediaViewer,
openPremiumModal,
openStickerSet,
openPrivacySettingsNoticeModal,
} = getActions();
const lang = useLang();
@ -173,6 +174,10 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
setCurrentPhotoIndex(currentPhotoIndex + 1);
});
const handleOpenGetReadDateModal = useLastCallback(() => {
openPrivacySettingsNoticeModal({ chatId: chat!.id, isReadDate: false });
});
function handleSelectFallbackPhoto() {
if (!isFirst) return;
setHasSlideAnimation(true);
@ -264,8 +269,21 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
if (user) {
return (
<div className={buildClassName(styles.status, 'status', isUserOnline(user, userStatus) && 'online')}>
<span className="user-status" dir="auto">{getUserStatus(lang, user, userStatus)}</span>
<div
className={buildClassName(
styles.status,
'status',
isUserOnline(user, userStatus) && 'online',
)}
>
<span className="user-status" dir="auto">
{getUserStatus(lang, user, userStatus)}
</span>
{userStatus?.isReadDateRestrictedByMe && (
<span className="get-status" onClick={handleOpenGetReadDateModal}>
{lang('StatusHiddenShow')}
</span>
)}
</div>
);
}

View File

@ -1,18 +0,0 @@
import type { FC } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import type { OwnProps } from './ReadDateModal';
import { Bundles } from '../../util/moduleLoader';
import useModuleLoader from '../../hooks/useModuleLoader';
const ReadTimeModalAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const ReadTimeModal = useModuleLoader(Bundles.Extra, 'ReadTimeModal', !isOpen);
// eslint-disable-next-line react/jsx-props-no-spreading
return ReadTimeModal ? <ReadTimeModal {...props} /> : undefined;
};
export default ReadTimeModalAsync;

View File

@ -11,6 +11,7 @@ import Flame from '../../../assets/tgs/general/Flame.tgs';
import PartyPopper from '../../../assets/tgs/general/PartyPopper.tgs';
import Invite from '../../../assets/tgs/invites/Invite.tgs';
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
import LastSeen from '../../../assets/tgs/LastSeen.tgs';
import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.tgs';
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
import MonkeyPeek from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyPeek.tgs';
@ -52,4 +53,5 @@ export const LOCAL_TGS_URLS = {
Flame,
ReadTime,
Unlock,
LastSeen,
};

View File

@ -79,7 +79,7 @@ import useWindowSize from '../../hooks/window/useWindowSize';
import usePinnedMessage from './hooks/usePinnedMessage';
import Composer from '../common/Composer';
import ReadTimeModal from '../common/ReadTimeModal.async';
import PrivacySettingsNoticeModal from '../common/PrivacySettingsNoticeModal.async';
import SeenByModal from '../common/SeenByModal.async';
import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async';
import GiftPremiumModal from '../main/premium/GiftPremiumModal.async';
@ -131,7 +131,7 @@ type StateProps = {
hasCurrentTextSearch?: boolean;
isSelectModeActive?: boolean;
isSeenByModalOpen: boolean;
isReadDateModalOpen: boolean;
isPrivacySettingsNoticeModalOpen: boolean;
isReactorListModalOpen: boolean;
isGiftPremiumModalOpen?: boolean;
isChatLanguageModalOpen?: boolean;
@ -191,7 +191,7 @@ function MiddleColumn({
hasCurrentTextSearch,
isSelectModeActive,
isSeenByModalOpen,
isReadDateModalOpen,
isPrivacySettingsNoticeModalOpen,
isReactorListModalOpen,
isGiftPremiumModalOpen,
isChatLanguageModalOpen,
@ -686,7 +686,7 @@ function MiddleColumn({
canPost={renderingCanPost}
/>
<SeenByModal isOpen={isSeenByModalOpen} />
<ReadTimeModal isOpen={isReadDateModalOpen} />
<PrivacySettingsNoticeModal isOpen={isPrivacySettingsNoticeModalOpen} />
<ReactorListModal isOpen={isReactorListModalOpen} />
{IS_TRANSLATION_SUPPORTED && <ChatLanguageModal isOpen={isChatLanguageModalOpen} />}
</div>
@ -734,7 +734,7 @@ export default memo(withGlobal<OwnProps>(
const {
messageLists, isLeftColumnShown, activeEmojiInteractions,
seenByModal, giftPremiumModal, reactorModal, audioPlayer, shouldSkipHistoryAnimations,
chatLanguageModal, readDateModal,
chatLanguageModal, privacySettingsNoticeModal,
} = selectTabState(global);
const currentMessageList = selectCurrentMessageList(global);
const { leftColumnWidth } = global;
@ -750,7 +750,7 @@ export default memo(withGlobal<OwnProps>(
hasCurrentTextSearch: Boolean(selectCurrentTextSearch(global)),
isSelectModeActive: selectIsInSelectMode(global),
isSeenByModalOpen: Boolean(seenByModal),
isReadDateModalOpen: Boolean(readDateModal),
isPrivacySettingsNoticeModalOpen: Boolean(privacySettingsNoticeModal),
isReactorListModalOpen: Boolean(reactorModal),
isGiftPremiumModalOpen: giftPremiumModal?.isOpen,
isChatLanguageModalOpen: Boolean(chatLanguageModal),

View File

@ -25,14 +25,14 @@ type OwnProps = {
function ReadTimeMenuItem({
message, shouldRenderShowWhen, canLoadReadDate, closeContextMenu, menuSeparatorSize,
}: OwnProps) {
const { openGetReadDateModal } = getActions();
const { openPrivacySettingsNoticeModal } = getActions();
const lang = useLang();
const { readDate } = message;
const shouldRenderSkeleton = canLoadReadDate && !readDate && !shouldRenderShowWhen;
const handleOpenModal = () => {
closeContextMenu();
openGetReadDateModal({ chatId: message.chatId, messageId: message.id });
openPrivacySettingsNoticeModal({ chatId: message.chatId, isReadDate: true });
};
return (

View File

@ -470,7 +470,28 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
});
addActionHandler('setPrivacyVisibility', async (global, actions, payload): Promise<void> => {
const { privacyKey, visibility } = payload!;
const { privacyKey, visibility, onSuccess } = payload!;
if (!global.settings.privacy[privacyKey]) {
const result = await callApi('fetchPrivacySettings', privacyKey);
if (!result) {
return;
}
global = getGlobal();
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
global = {
...global,
settings: {
...global.settings,
privacy: {
...global.settings.privacy,
[privacyKey]: result.rules,
},
},
};
setGlobal(global);
}
const {
privacy: { [privacyKey]: settings },
@ -491,6 +512,8 @@ addActionHandler('setPrivacyVisibility', async (global, actions, payload): Promi
return;
}
onSuccess?.();
global = getGlobal();
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
global = {

View File

@ -781,19 +781,19 @@ addActionHandler('closeSeenByModal', (global, actions, payload): ActionReturnTyp
}, tabId);
});
addActionHandler('openGetReadDateModal', (global, actions, payload): ActionReturnType => {
const { chatId, messageId, tabId = getCurrentTabId() } = payload;
addActionHandler('openPrivacySettingsNoticeModal', (global, actions, payload): ActionReturnType => {
const { chatId, isReadDate, tabId = getCurrentTabId() } = payload;
return updateTabState(global, {
readDateModal: { chatId, messageId },
privacySettingsNoticeModal: { chatId, isReadDate },
}, tabId);
});
addActionHandler('closeGetReadDateModal', (global, actions, payload): ActionReturnType => {
addActionHandler('closePrivacySettingsNoticeModal', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
return updateTabState(global, {
readDateModal: undefined,
privacySettingsNoticeModal: undefined,
}, tabId);
});

View File

@ -292,9 +292,9 @@ export type TabState = {
messageId: number;
};
readDateModal?: {
privacySettingsNoticeModal?: {
chatId: string;
messageId: number;
isReadDate: boolean;
};
reactorModal?: {
@ -1170,6 +1170,7 @@ export interface ActionPayloads {
setPrivacyVisibility: {
privacyKey: ApiPrivacyKey;
visibility: PrivacyVisibility;
onSuccess?: VoidFunction;
};
setPrivacySettings: {
@ -1632,11 +1633,11 @@ export interface ActionPayloads {
messageId: number;
} & WithTabId;
closeSeenByModal: WithTabId | undefined;
openGetReadDateModal: {
openPrivacySettingsNoticeModal: {
chatId: string;
messageId: number;
isReadDate: boolean;
} & WithTabId;
closeGetReadDateModal: WithTabId | undefined;
closePrivacySettingsNoticeModal: WithTabId | undefined;
closeReactorListModal: WithTabId | undefined;
openReactorListModal: {
chatId: string;