diff --git a/src/assets/tgs/LastSeen.tgs b/src/assets/tgs/LastSeen.tgs new file mode 100644 index 000000000..dcefc4de2 Binary files /dev/null and b/src/assets/tgs/LastSeen.tgs differ diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index 3b717b834..2fd6b9156 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -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'; diff --git a/src/components/common/PrivacySettingsNoticeModal.async.tsx b/src/components/common/PrivacySettingsNoticeModal.async.tsx new file mode 100644 index 000000000..42b845310 --- /dev/null +++ b/src/components/common/PrivacySettingsNoticeModal.async.tsx @@ -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 = (props) => { + const { isOpen } = props; + const PrivacySettingsNoticeModal = useModuleLoader(Bundles.Extra, 'PrivacySettingsNoticeModal', !isOpen); + + // eslint-disable-next-line react/jsx-props-no-spreading + return PrivacySettingsNoticeModal ? : undefined; +}; + +export default PrivacySettingsNoticeModalAsync; diff --git a/src/components/common/ReadDateModal.module.scss b/src/components/common/PrivacySettingsNoticeModal.module.scss similarity index 100% rename from src/components/common/ReadDateModal.module.scss rename to src/components/common/PrivacySettingsNoticeModal.module.scss diff --git a/src/components/common/ReadDateModal.tsx b/src/components/common/PrivacySettingsNoticeModal.tsx similarity index 54% rename from src/components/common/ReadDateModal.tsx rename to src/components/common/PrivacySettingsNoticeModal.tsx index ce24d683f..2e4311dfc 100644 --- a/src/components/common/ReadDateModal.tsx +++ b/src/components/common/PrivacySettingsNoticeModal.tsx @@ -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) => { -

{lang('PremiumReadHeader1')}

-

{renderText(lang('PremiumReadText1', userName), ['simple_markdown'])}

+

+ {lang(isReadDate ? 'PremiumReadHeader1' : 'PremiumLastSeenHeader1')} +

+

+ {renderText( + lang( + isReadDate ? 'PremiumReadText1' : 'PremiumLastSeenText1Locked', + userName, + ), + ['simple_markdown'], + )} +

{lang('PremiumOr')}

{lang('PremiumReadHeader2')}

-

{renderText(lang('PremiumReadText2', userName), ['simple_markdown'])}

- @@ -98,11 +137,11 @@ const ReadDateModal = ({ isOpen, user }: OwnProps & StateProps) => { ); }; -export default memo(withGlobal( - (global): StateProps => { - const { chatId } = selectTabState(global).readDateModal || {}; +export default memo( + withGlobal((global): StateProps => { + const { chatId, isReadDate } = selectTabState(global).privacySettingsNoticeModal || {}; const user = chatId ? selectUser(global, chatId) : undefined; - return { user }; - }, -)(ReadDateModal)); + return { user, isReadDate }; + })(PrivacySettingsNoticeModal), +); diff --git a/src/components/common/ProfileInfo.scss b/src/components/common/ProfileInfo.scss index 33139e38f..c514bfb3f 100644 --- a/src/components/common/ProfileInfo.scss +++ b/src/components/common/ProfileInfo.scss @@ -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; + } } diff --git a/src/components/common/ProfileInfo.tsx b/src/components/common/ProfileInfo.tsx index d7cffb4b9..0198083b9 100644 --- a/src/components/common/ProfileInfo.tsx +++ b/src/components/common/ProfileInfo.tsx @@ -90,6 +90,7 @@ const ProfileInfo: FC = ({ openMediaViewer, openPremiumModal, openStickerSet, + openPrivacySettingsNoticeModal, } = getActions(); const lang = useLang(); @@ -173,6 +174,10 @@ const ProfileInfo: FC = ({ 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 = ({ if (user) { return ( -
- {getUserStatus(lang, user, userStatus)} +
+ + {getUserStatus(lang, user, userStatus)} + + {userStatus?.isReadDateRestrictedByMe && ( + + {lang('StatusHiddenShow')} + + )}
); } diff --git a/src/components/common/ReadTimeModal.async.tsx b/src/components/common/ReadTimeModal.async.tsx deleted file mode 100644 index a1410967d..000000000 --- a/src/components/common/ReadTimeModal.async.tsx +++ /dev/null @@ -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 = (props) => { - const { isOpen } = props; - const ReadTimeModal = useModuleLoader(Bundles.Extra, 'ReadTimeModal', !isOpen); - - // eslint-disable-next-line react/jsx-props-no-spreading - return ReadTimeModal ? : undefined; -}; - -export default ReadTimeModalAsync; diff --git a/src/components/common/helpers/animatedAssets.ts b/src/components/common/helpers/animatedAssets.ts index 316469fed..b87170fc0 100644 --- a/src/components/common/helpers/animatedAssets.ts +++ b/src/components/common/helpers/animatedAssets.ts @@ -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, }; diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 80d9370cd..e4389bf39 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -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} /> - + {IS_TRANSLATION_SUPPORTED && }
@@ -734,7 +734,7 @@ export default memo(withGlobal( 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( 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), diff --git a/src/components/middle/message/ReadTimeMenuItem.tsx b/src/components/middle/message/ReadTimeMenuItem.tsx index 4aa962fa4..b5984efbb 100644 --- a/src/components/middle/message/ReadTimeMenuItem.tsx +++ b/src/components/middle/message/ReadTimeMenuItem.tsx @@ -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 ( diff --git a/src/global/actions/api/settings.ts b/src/global/actions/api/settings.ts index 6b71c3c7f..918cfa863 100644 --- a/src/global/actions/api/settings.ts +++ b/src/global/actions/api/settings.ts @@ -470,7 +470,28 @@ addActionHandler('loadPrivacySettings', async (global): Promise => { }); addActionHandler('setPrivacyVisibility', async (global, actions, payload): Promise => { - 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 = { diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index 3144d7f0d..fa4658acd 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -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); }); diff --git a/src/global/types.ts b/src/global/types.ts index 7b2734262..a88de4726 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -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;