diff --git a/src/components/appNavigationController.ts b/src/components/appNavigationController.ts index fbd3274a..1ef0ed1a 100644 --- a/src/components/appNavigationController.ts +++ b/src/components/appNavigationController.ts @@ -16,7 +16,7 @@ import isSwipingBackSafari from "../helpers/dom/isSwipingBackSafari"; export type NavigationItem = { type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg' | 'multiselect' | 'input-helper' | 'autocomplete-helper' | 'markup' | - 'global-search' | 'voice' | 'mobile-search', + 'global-search' | 'voice' | 'mobile-search' | 'filters', onPop: (canAnimate: boolean) => boolean | void, onEscape?: () => boolean, noHistory?: boolean, @@ -169,8 +169,7 @@ export class AppNavigationController { //} } - public pushItem(item: NavigationItem) { - this.navigations.push(item); + private onItemAdded(item: NavigationItem) { this.debug && this.log('pushstate', item, this.navigations); if(!item.noHistory) { @@ -178,6 +177,16 @@ export class AppNavigationController { } } + public pushItem(item: NavigationItem) { + this.navigations.push(item); + this.onItemAdded(item); + } + + public unshiftItem(item: NavigationItem) { + this.navigations.unshift(item); + this.onItemAdded(item); + } + private pushState() { this.manual = false; history.pushState(this.id, ''); diff --git a/src/components/buttonMenu.ts b/src/components/buttonMenu.ts index 54cecd26..da358d31 100644 --- a/src/components/buttonMenu.ts +++ b/src/components/buttonMenu.ts @@ -47,7 +47,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => { const keepOpen = !!checkboxField || !!options.keepOpen; // * cancel mobile keyboard close - attachClickEvent(el, /* CLICK_EVENT_NAME !== 'click' || keepOpen ? */ (e) => { + onClick && attachClickEvent(el, /* CLICK_EVENT_NAME !== 'click' || keepOpen ? */ (e) => { cancelEvent(e); const result = onClick(e); diff --git a/src/components/buttonMenuToggle.ts b/src/components/buttonMenuToggle.ts index 4dcfbd2d..1a334754 100644 --- a/src/components/buttonMenuToggle.ts +++ b/src/components/buttonMenuToggle.ts @@ -11,13 +11,26 @@ import ButtonIcon from "./buttonIcon"; import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu"; import { closeBtnMenu, openBtnMenu } from "./misc"; -const ButtonMenuToggle = (options: Partial<{noRipple: true, onlyMobile: true, listenerSetter: ListenerSetter, asDiv: boolean}> = {}, direction: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right', buttons: ButtonMenuItemOptions[], onOpen?: (e: Event) => void) => { +const ButtonMenuToggle = ( + options: Partial<{ + noRipple: true, + onlyMobile: true, + listenerSetter: ListenerSetter, + asDiv: boolean, + container: HTMLElement + }> = {}, + direction: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right', + buttons: ButtonMenuItemOptions[], + onOpen?: (e: Event) => void, + onClose?: () => void +) => { options.asDiv = true; - const button = ButtonIcon('more btn-menu-toggle', options); + const button = options.container ?? ButtonIcon('more', options); + button.classList.add('btn-menu-toggle'); const btnMenu = ButtonMenu(buttons, options.listenerSetter); btnMenu.classList.add(direction); - ButtonMenuToggleHandler(button, onOpen, options); + ButtonMenuToggleHandler(button, onOpen, options, onClose); button.append(btnMenu); return button; }; diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 6307d472..a8ca5dde 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -190,7 +190,7 @@ export default class Chat extends EventListenerBase<{ this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager, this.appProfileManager, this.appUsersManager, this.appGroupCallsManager); this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager, this.appProfileManager, this.appDraftsManager, this.appMessagesIdsManager, this.appChatsManager, this.appReactionsManager); - this.input = new ChatInput(this, this.appMessagesManager, this.appMessagesIdsManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager, this.appUsersManager, this.appInlineBotsManager); + this.input = new ChatInput(this, this.appMessagesManager, this.appMessagesIdsManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager, this.appUsersManager, this.appInlineBotsManager, this.appProfileManager); this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager); this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appPeersManager, this.appPollsManager, this.appDocsManager, this.appMessagesIdsManager, this.appReactionsManager); @@ -425,4 +425,14 @@ export default class Chat extends EventListenerBase<{ !this.appMessagesManager.getDialogOnly(this.peerId) && !this.appMessagesManager.getHistoryStorage(this.peerId).history.length; } + + public getMessageSendingParams() { + return { + threadId: this.threadId, + replyToMsgId: this.input.replyToMsgId, + scheduleDate: this.input.scheduleDate, + sendSilent: this.input.sendSilent, + sendAsPeerId: this.input.sendAsPeerId + }; + } } diff --git a/src/components/chat/inlineHelper.ts b/src/components/chat/inlineHelper.ts index 319b5c29..7fc7d51d 100644 --- a/src/components/chat/inlineHelper.ts +++ b/src/components/chat/inlineHelper.ts @@ -53,10 +53,8 @@ export default class InlineHelper extends AutocompleteHelper { return this.chat.input.getReadyToSend(() => { const queryAndResultIds = this.appInlineBotsManager.generateQId(queryId, (target as HTMLElement).dataset.resultId); this.appInlineBotsManager.sendInlineResult(peerId.toPeerId(), botId, queryAndResultIds, { + ...this.chat.getMessageSendingParams(), clearDraft: true, - scheduleDate: this.chat.input.scheduleDate, - silent: this.chat.input.sendSilent, - replyToMsgId: this.chat.input.replyToMsgId }); this.chat.input.onMessageSent(true, true); diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 4ca199cf..bcbe11bf 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -32,7 +32,7 @@ import PopupNewMedia from '../popups/newMedia'; import { toast } from "../toast"; import { wrapReply } from "../wrappers"; import InputField from '../inputField'; -import { MessageEntity, DraftMessage, WebPage, Message } from '../../layer'; +import { MessageEntity, DraftMessage, WebPage, Message, ChatFull } from '../../layer'; import StickersHelper from './stickersHelper'; import ButtonIcon from '../buttonIcon'; import ButtonMenuToggle from '../buttonMenuToggle'; @@ -87,10 +87,16 @@ import DropdownHover from '../../helpers/dropdownHover'; import RadioForm from '../radioForm'; import findUpTag from '../../helpers/dom/findUpTag'; import toggleDisability from '../../helpers/dom/toggleDisability'; +import AvatarElement from '../avatar'; +import type { AppProfileManager } from '../../lib/appManagers/appProfileManager'; +import { indexOfAndSplice } from '../../helpers/array'; +import callbackify from '../../helpers/callbackify'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; +const SEND_AS_ANIMATION_DURATION = 300; + type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; export default class ChatInput { @@ -115,7 +121,7 @@ export default class ChatInput { private replyKeyboard: ReplyKeyboard; - private attachMenu: HTMLButtonElement; + private attachMenu: HTMLElement; private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: PeerId, threadId: number) => boolean})[]; private sendMenu: SendMenu; @@ -208,9 +214,17 @@ export default class ChatInput { private fakeWrapperTo: HTMLElement; private toggleBotStartBtnDisability: () => void; + private sendAsAvatar: AvatarElement; + private sendAsContainer: HTMLElement; + private sendAsCloseBtn: HTMLElement; + private sendAsBtnMenu: HTMLElement; + private sendAsPeerIds: PeerId[]; + public sendAsPeerId: PeerId; + // private activeContainer: HTMLElement; - constructor(private chat: Chat, + constructor( + private chat: Chat, private appMessagesManager: AppMessagesManager, private appMessagesIdsManager: AppMessagesIdsManager, private appDocsManager: AppDocsManager, @@ -223,7 +237,8 @@ export default class ChatInput { private appNotificationsManager: AppNotificationsManager, private appEmojiManager: AppEmojiManager, private appUsersManager: AppUsersManager, - private appInlineBotsManager: AppInlineBotsManager + private appInlineBotsManager: AppInlineBotsManager, + private appProfileManager: AppProfileManager ) { this.listenerSetter = new ListenerSetter(); } @@ -461,6 +476,46 @@ export default class ChatInput { this.newMessageWrapper = document.createElement('div'); this.newMessageWrapper.classList.add('new-message-wrapper'); + this.sendAsContainer = document.createElement('div'); + this.sendAsContainer.classList.add('new-message-send-as-container'); + + this.sendAsCloseBtn = document.createElement('div'); + this.sendAsCloseBtn.classList.add('new-message-send-as-close', 'new-message-send-as-avatar', 'tgico-close'); + + const sendAsButtons: ButtonMenuItemOptions[] = [{ + text: 'SendMessageAsTitle', + onClick: undefined + }]; + + let previousAvatar: HTMLElement; + const onSendAsMenuToggle = (visible: boolean) => { + if(visible) { + previousAvatar = this.sendAsAvatar; + } + + const isChanged = this.sendAsAvatar !== previousAvatar; + const useRafs = !visible && isChanged ? 2 : 0; + + SetTransition(this.sendAsCloseBtn, 'is-visible', visible, SEND_AS_ANIMATION_DURATION, undefined, useRafs); + if(!isChanged) { + SetTransition(previousAvatar, 'is-visible', !visible, SEND_AS_ANIMATION_DURATION, undefined, useRafs); + } + }; + + ButtonMenuToggle({ + noRipple: true, + listenerSetter: this.listenerSetter, + container: this.sendAsContainer + }, 'top-right', sendAsButtons, () => { + onSendAsMenuToggle(true); + }, () => { + onSendAsMenuToggle(false); + }); + + sendAsButtons[0].element.classList.add('btn-menu-item-header'); + this.sendAsBtnMenu = this.sendAsContainer.firstElementChild as any; + this.sendAsContainer.append(this.sendAsCloseBtn); + this.btnToggleEmoticons = ButtonIcon('none toggle-emoticons', {noRipple: true}); this.inputMessageContainer = document.createElement('div'); @@ -562,7 +617,7 @@ export default class ChatInput { this.fileInput.multiple = true; this.fileInput.style.display = 'none'; - this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.btnToggleReplyMarkup, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean)); + this.newMessageWrapper.append(...[this.sendAsContainer, this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.btnToggleReplyMarkup, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean)); this.rowsWrapper.append(this.replyElements.container); this.autocompleteHelperController = new AutocompleteHelperController(); @@ -664,6 +719,14 @@ export default class ChatInput { } }); + if(this.sendAsContainer) { + this.listenerSetter.add(rootScope)('peer_full_update', (peerId) => { + if(peerId.isChannel() && this.chat.peerId === peerId) { + this.updateSendAs(); + } + }); + } + if(this.chat.type === 'scheduled') { this.listenerSetter.add(rootScope)('scheduled_delete', ({peerId, mids}) => { if(this.chat.peerId === peerId && mids.includes(this.editMsgId)) { @@ -1129,7 +1192,7 @@ export default class ChatInput { public finishPeerChange(startParam?: string) { const peerId = this.chat.peerId; - const {forwardElements, btnScheduled, replyKeyboard, sendMenu, goDownBtn, chatInput} = this; + const {forwardElements, btnScheduled, replyKeyboard, sendMenu, goDownBtn, chatInput, sendAsContainer} = this; chatInput.style.display = ''; const isBroadcast = this.appPeersManager.isBroadcast(peerId); @@ -1160,6 +1223,19 @@ export default class ChatInput { }); } + if(sendAsContainer) { + if(this.sendAsAvatar) { + this.sendAsAvatar.remove(); + this.sendAsAvatar = undefined; + } + + sendAsContainer.remove(); + SetTransition(this.newMessageWrapper, 'has-send-as', false, 0); + this.sendAsPeerId = undefined; + + this.updateSendAs(true); + } + if(replyKeyboard) { replyKeyboard.setPeer(peerId); } @@ -1182,6 +1258,157 @@ export default class ChatInput { this.center(false); } + private updateSendAsButtons(peerIds: PeerId[]) { + const buttons: ButtonMenuItemOptions[] = peerIds.map((sendAsPeerId, idx) => { + const textElement = document.createElement('div'); + + const subtitle = document.createElement('div'); + subtitle.classList.add('btn-menu-item-subtitle'); + if(sendAsPeerId.isUser()) { + subtitle.append(i18n('Chat.SendAs.PersonalAccount')); + } else { + subtitle.append(this.appProfileManager.getChatMembersString(sendAsPeerId.toChatId())); + } + + textElement.append( + new PeerTitle({peerId: sendAsPeerId}).element, + subtitle + ); + + return { + onClick: idx ? () => { + const currentPeerId = this.chat.peerId; + if(currentPeerId.isChannel()) { + const channelFull = this.appProfileManager.getCachedFullChat(currentPeerId.toChatId()) as ChatFull.channelFull; + if(channelFull) { + channelFull.default_send_as = this.appPeersManager.getOutputPeer(sendAsPeerId); + this.sendAsPeerId = sendAsPeerId; + this.updateSendAsAvatar(sendAsPeerId); + + const middleware = this.chat.bubbles.getMiddleware(); + const executeButtonsUpdate = () => { + if(this.sendAsPeerId !== sendAsPeerId || !middleware()) return; + const peerIds = this.sendAsPeerIds.slice(); + indexOfAndSplice(peerIds, sendAsPeerId); + peerIds.unshift(sendAsPeerId); + this.updateSendAsButtons(peerIds); + }; + + if(rootScope.settings.animationsEnabled) { + setTimeout(executeButtonsUpdate, 250); + } else { + executeButtonsUpdate(); + } + } + } + + // return; + apiManager.invokeApi('messages.saveDefaultSendAs', { + peer: this.appPeersManager.getInputPeerById(currentPeerId), + send_as: this.appPeersManager.getInputPeerById(sendAsPeerId) + }); + } : undefined, + textElement + }; + }); + + const btnMenu = ButtonMenu(buttons/* , this.listenerSetter */); + buttons.forEach((button, idx) => { + const peerId = peerIds[idx]; + const avatar = new AvatarElement(); + avatar.classList.add('avatar-32', 'btn-menu-item-icon'); + avatar.setAttribute('peer', '' + peerId); + + if(!idx) { + avatar.classList.add('active'); + } + + button.element.prepend(avatar); + }); + + Array.from(this.sendAsBtnMenu.children).slice(1).forEach(node => node.remove()); + this.sendAsBtnMenu.append(...Array.from(btnMenu.children)); + } + + private updateSendAsAvatar(sendAsPeerId: PeerId, skipAnimation?: boolean) { + const previousAvatar = this.sendAsAvatar; + if(previousAvatar) { + if(+previousAvatar.getAttribute('peer') === sendAsPeerId) { + return; + } + } + + if(!previousAvatar) { + skipAnimation = true; + } + + let useRafs = skipAnimation ? 0 : 2; + const duration = skipAnimation ? 0 : SEND_AS_ANIMATION_DURATION; + const avatar = this.sendAsAvatar = new AvatarElement(); + avatar.setAttribute('dialog', '0'); + avatar.setAttribute('peer', '' + sendAsPeerId); + avatar.classList.add('new-message-send-as-avatar', 'avatar-30'); + + SetTransition(avatar, 'is-visible', true, duration, undefined, useRafs); + if(previousAvatar) { + SetTransition(previousAvatar, 'is-visible', false, duration, () => { + previousAvatar.remove(); + }, useRafs); + } + + this.sendAsContainer.append(avatar); + } + + private getDefaultSendAs() { + // return rootScope.myId; + return callbackify(this.appProfileManager.getChannelFull(this.chat.peerId.toChatId()), (channelFull) => { + return channelFull.default_send_as ? this.appPeersManager.getPeerId(channelFull.default_send_as) : undefined; + }); + } + + private updateSendAs(skipAnimation?: boolean) { + const peerId = this.chat.peerId; + if(!peerId.isChannel()) { + return; + } + + const middleware = this.chat.bubbles.getMiddleware(); + const {sendAsContainer} = this; + const chatId = peerId.toChatId(); + const result = this.getDefaultSendAs(); + // const result = Promise.resolve(this.getDefaultSendAs()); + + if(result instanceof Promise) { + skipAnimation = undefined; + } + + callbackify(result, (sendAsPeerId) => { + if(!middleware() || sendAsPeerId === undefined) return; + + this.sendAsPeerId = sendAsPeerId; + this.updateSendAsAvatar(sendAsPeerId, skipAnimation); + + this.appChatsManager.getSendAs(chatId).then(peers => { + if(!middleware()) return; + + const peerIds = peers.map((peer) => this.appPeersManager.getPeerId(peer)); + this.sendAsPeerIds = peerIds.slice(); + + indexOfAndSplice(peerIds, sendAsPeerId); + peerIds.unshift(sendAsPeerId); + this.updateSendAsButtons(peerIds); + }); + + let useRafs = 0; + if(!sendAsContainer.parentElement) { + this.newMessageWrapper.prepend(sendAsContainer); + useRafs = 2; + } + + SetTransition(this.newMessageWrapper, 'has-send-as', true, skipAnimation ? 0 : SEND_AS_ANIMATION_DURATION, undefined, useRafs); + }); + } + public updateMessageInput() { const {chatInput, attachMenu, messageInput} = this; const {peerId, threadId} = this.chat; @@ -2128,8 +2355,9 @@ export default class ChatInput { return; } - const {threadId, peerId} = chat; - const {replyToMsgId, noWebPage, sendSilent, scheduleDate} = this; + const {peerId} = chat; + const {noWebPage} = this; + const sendingParams = this.chat.getMessageSendingParams(); const {value, entities} = getRichValue(this.messageInputField.input); @@ -2151,12 +2379,9 @@ export default class ChatInput { } else if(value.trim()) { this.appMessagesManager.sendText(peerId, value, { entities, - replyToMsgId: replyToMsgId, - threadId: threadId, + ...sendingParams, noWebPage: noWebPage, webPage: this.getWebPagePromise ? undefined : this.willSendWebPage, - scheduleDate: scheduleDate, - silent: sendSilent, clearDraft: true }); @@ -2170,8 +2395,7 @@ export default class ChatInput { setTimeout(() => { for(const fromPeerId in forwarding) { this.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), forwarding[fromPeerId], { - silent: sendSilent, - scheduleDate: scheduleDate, + ...sendingParams, dropAuthor: this.forwardElements && this.forwardElements.hideSender.checkboxField.checked, dropCaptions: this.isDroppingCaptions() }); @@ -2202,11 +2426,8 @@ export default class ChatInput { if(document) { this.appMessagesManager.sendFile(this.chat.peerId, document, { + ...this.chat.getMessageSendingParams(), isMedia: true, - replyToMsgId: this.replyToMsgId, - threadId: this.chat.threadId, - silent: this.sendSilent, - scheduleDate: this.scheduleDate, clearDraft: clearDraft || undefined }); this.onMessageSent(clearDraft, true); diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index 9a986857..593458c1 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -67,7 +67,7 @@ export default class ChatTopbar { private btnGroupCall: HTMLButtonElement; private btnMute: HTMLButtonElement; private btnSearch: HTMLButtonElement; - private btnMore: HTMLButtonElement; + private btnMore: HTMLElement; private chatAudio: ChatAudio; public pinnedMessage: ChatPinnedMessage; diff --git a/src/components/peerProfile.ts b/src/components/peerProfile.ts index b571e175..e54bef07 100644 --- a/src/components/peerProfile.ts +++ b/src/components/peerProfile.ts @@ -5,8 +5,10 @@ */ import IS_PARALLAX_SUPPORTED from "../environment/parallaxSupport"; +import callbackify from "../helpers/callbackify"; import { copyTextToClipboard } from "../helpers/clipboard"; import replaceContent from "../helpers/dom/replaceContent"; +import ListenerSetter from "../helpers/listenerSetter"; import { fastRaf } from "../helpers/schedulers"; import { ChatFull, User } from "../layer"; import { Channel } from "../lib/appManagers/appChatsManager"; @@ -38,7 +40,7 @@ let setText = (text: string, row: Row) => { export default class PeerProfile { public element: HTMLElement; - public avatars: PeerProfileAvatars; + private avatars: PeerProfileAvatars; private avatar: AvatarElement; private section: SettingSection; private name: HTMLDivElement; @@ -48,6 +50,7 @@ export default class PeerProfile { private phone: Row; private notifications: Row; private location: Row; + private link: Row; private cleaned: boolean; private setMoreDetailsTimeout: number; @@ -56,10 +59,18 @@ export default class PeerProfile { private peerId: PeerId; private threadId: number; - constructor(public scrollable: Scrollable) { + constructor( + public scrollable: Scrollable, + private listenerSetter?: ListenerSetter, + private isDialog = true + ) { if(!IS_PARALLAX_SUPPORTED) { this.scrollable.container.classList.add('no-parallax'); } + + if(!listenerSetter) { + this.listenerSetter = new ListenerSetter(); + } } public init() { @@ -75,7 +86,7 @@ export default class PeerProfile { this.avatar = new AvatarElement(); this.avatar.classList.add('profile-avatar', 'avatar-120'); - this.avatar.setAttribute('dialog', '1'); + this.avatar.setAttribute('dialog', '' + +this.isDialog); this.avatar.setAttribute('clickable', ''); this.name = document.createElement('div'); @@ -124,68 +135,85 @@ export default class PeerProfile { } }); + this.link = new Row({ + title: ' ', + subtitleLangKey: 'SetUrlPlaceholder', + icon: 'link', + clickable: () => { + Promise.resolve(appProfileManager.getChatFull(this.peerId.toChatId())).then(chatFull => { + copyTextToClipboard(chatFull.exported_invite.link); + toast(I18n.format('LinkCopied', true)); + }); + } + }); + this.location = new Row({ title: ' ', subtitleLangKey: 'ChatLocation', icon: 'location' }); - this.notifications = new Row({ - checkboxField: new CheckboxField({toggle: true}), - titleLangKey: 'Notifications', - icon: 'unmute' - }); - this.section.content.append( this.phone.container, this.username.container, this.location.container, this.bio.container, - this.notifications.container + this.link.container ); + const {listenerSetter} = this; + if(this.isDialog) { + this.notifications = new Row({ + checkboxField: new CheckboxField({toggle: true}), + titleLangKey: 'Notifications', + icon: 'unmute' + }); + + listenerSetter.add(this.notifications.checkboxField.input)('change', (e) => { + if(!e.isTrusted) { + return; + } + + //let checked = this.notificationsCheckbox.checked; + appMessagesManager.togglePeerMute(this.peerId); + }); + + listenerSetter.add(rootScope)('dialog_notify_settings', (dialog) => { + if(this.peerId === dialog.peerId) { + const muted = appNotificationsManager.isPeerLocalMuted(this.peerId, false); + this.notifications.checkboxField.checked = !muted; + } + }); + + this.section.content.append(this.notifications.container); + } + this.element.append(this.section.container); if(IS_PARALLAX_SUPPORTED) { this.element.append(generateDelimiter()); } - this.notifications.checkboxField.input.addEventListener('change', (e) => { - if(!e.isTrusted) { - return; - } - - //let checked = this.notificationsCheckbox.checked; - appMessagesManager.togglePeerMute(this.peerId); - }); - - rootScope.addEventListener('dialog_notify_settings', (dialog) => { - if(this.peerId === dialog.peerId) { - const muted = appNotificationsManager.isPeerLocalMuted(this.peerId, false); - this.notifications.checkboxField.checked = !muted; - } - }); - - rootScope.addEventListener('peer_typings', ({peerId}) => { + listenerSetter.add(rootScope)('peer_typings', ({peerId}) => { if(this.peerId === peerId) { this.setPeerStatus(); } }); - rootScope.addEventListener('peer_bio_edit', (peerId) => { + listenerSetter.add(rootScope)('peer_bio_edit', (peerId) => { if(peerId === this.peerId) { this.setMoreDetails(true); } }); - rootScope.addEventListener('user_update', (userId) => { - if(this.peerId === userId) { + listenerSetter.add(rootScope)('user_update', (userId) => { + if(this.peerId === userId.toPeerId()) { this.setPeerStatus(); } }); - rootScope.addEventListener('contacts_update', (userId) => { - if(this.peerId === userId) { + listenerSetter.add(rootScope)('contacts_update', (userId) => { + if(this.peerId === userId.toPeerId()) { const user = appUsersManager.getUser(userId); if(!user.pFlags.self) { if(user.phone) { @@ -204,24 +232,37 @@ export default class PeerProfile { if(!this.peerId) return; const peerId = this.peerId; - appImManager.setPeerStatus(this.peerId, this.subtitle, needClear, true, () => peerId === this.peerId); + appImManager.setPeerStatus(this.peerId, this.subtitle, needClear, true, () => peerId === this.peerId, !this.isDialog); }; public cleanupHTML() { - this.bio.container.style.display = 'none'; - this.phone.container.style.display = 'none'; - this.username.container.style.display = 'none'; - this.location.container.style.display = 'none'; - this.notifications.container.style.display = ''; - this.notifications.checkboxField.checked = true; + [ + this.bio, + this.phone, + this.username, + this.location, + this.link + ].forEach(row => { + row.container.style.display = 'none'; + }); + + if(this.notifications) { + this.notifications.container.style.display = ''; + this.notifications.checkboxField.checked = true; + } + if(this.setMoreDetailsTimeout) { window.clearTimeout(this.setMoreDetailsTimeout); this.setMoreDetailsTimeout = 0; } } + private canBeDetailed() { + return this.peerId !== rootScope.myId || !this.isDialog; + } + public setAvatar() { - if(this.peerId !== rootScope.myId) { + if(this.canBeDetailed()) { const photo = appPeersManager.getPeerPhoto(this.peerId); if(photo) { @@ -268,15 +309,17 @@ export default class PeerProfile { this.setAvatar(); // username - if(peerId !== rootScope.myId) { + if(this.canBeDetailed()) { let username = appPeersManager.getPeerUsername(peerId); if(username) { setText(appPeersManager.getPeerUsername(peerId), this.username); } - const muted = appNotificationsManager.isPeerLocalMuted(peerId, false); - this.notifications.checkboxField.checked = !muted; - } else { + if(this.notifications) { + const muted = appNotificationsManager.isPeerLocalMuted(peerId, false); + this.notifications.checkboxField.checked = !muted; + } + } else if(this.notifications) { fastRaf(() => { this.notifications.container.style.display = 'none'; }); @@ -287,7 +330,7 @@ export default class PeerProfile { //membersLi.style.display = 'none'; let user = appUsersManager.getUser(peerId); - if(user.phone && peerId !== rootScope.myId) { + if(user.phone && this.canBeDetailed()) { setText(appUsersManager.formatUserPhone(user.phone), this.phone); } }/* else { @@ -298,7 +341,7 @@ export default class PeerProfile { replaceContent(this.name, new PeerTitle({ peerId, - dialog: true, + dialog: this.isDialog, }).element); const peer = appPeersManager.getPeer(peerId); @@ -318,11 +361,11 @@ export default class PeerProfile { const peerId = this.peerId; const threadId = this.threadId; - if(!peerId || appPeersManager.isRestricted(peerId) || peerId === rootScope.myId) { + if(!peerId || appPeersManager.isRestricted(peerId) || !this.canBeDetailed()) { return; } - Promise.resolve(appProfileManager.getProfileByPeerId(peerId, override)).then((peerFull) => { + callbackify(appProfileManager.getProfileByPeerId(peerId, override), (peerFull) => { if(this.peerId !== peerId || this.threadId !== threadId || appPeersManager.isRestricted(peerId)) { //this.log.warn('peer changed'); return; @@ -334,9 +377,14 @@ export default class PeerProfile { setText(RichTextProcessor.wrapRichText(peerFull.about), this.bio); } - if((peerFull as ChatFull.channelFull)?.location?._ == 'channelLocation') { - // @ts-ignore - setText(chatFull.location.address, this.location); + const exportedInvite = (peerFull as ChatFull.channelFull).exported_invite; + if(exportedInvite) { + setText(exportedInvite.link, this.link); + } + + const location = (peerFull as ChatFull.channelFull).location; + if(location?._ == 'channelLocation') { + setText(location.address, this.location); } this.setMoreDetailsTimeout = window.setTimeout(() => this.setMoreDetails(true), 60e3); diff --git a/src/components/popups/createPoll.ts b/src/components/popups/createPoll.ts index 9a373874..6645d46a 100644 --- a/src/components/popups/createPoll.ts +++ b/src/components/popups/createPoll.ts @@ -286,10 +286,7 @@ export default class PopupCreatePoll extends PopupElement { //console.log('Will try to create poll:', inputMediaPoll); this.chat.appMessagesManager.sendOther(this.chat.peerId, inputMediaPoll, { - threadId: this.chat.threadId, - replyToMsgId: this.chat.input.replyToMsgId, - scheduleDate: this.chat.input.scheduleDate, - silent: this.chat.input.sendSilent + ...this.chat.getMessageSendingParams() }); if(this.chat.input.helperType === 'reply') { diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index 9ba18a14..330fc586 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -232,21 +232,17 @@ export default class PopupNewMedia extends PopupElement { //console.log('will send files with options:', willAttach); const {peerId, input} = this.chat; - const {sendSilent, scheduleDate} = input; sendFileDetails.forEach(d => { d.itemDiv = undefined; }); const {length} = sendFileDetails; - const replyToMsgId = input.replyToMsgId; + const sendingParams = this.chat.getMessageSendingParams(); this.iterate((sendFileDetails) => { if(caption && sendFileDetails.length !== length) { this.chat.appMessagesManager.sendText(peerId, caption, { - replyToMsgId, - threadId: this.chat.threadId, - silent: sendSilent, - scheduleDate, + ...sendingParams, clearDraft: true }); @@ -259,12 +255,9 @@ export default class PopupNewMedia extends PopupElement { }; this.chat.appMessagesManager.sendAlbum(peerId, w.sendFileDetails.map(d => d.file), Object.assign({ + ...sendingParams, caption, - replyToMsgId, - threadId: this.chat.threadId, isMedia: isMedia, - silent: sendSilent, - scheduleDate, clearDraft: true as true }, w)); diff --git a/src/components/row.ts b/src/components/row.ts index 77cab554..51a23668 100644 --- a/src/components/row.ts +++ b/src/components/row.ts @@ -16,6 +16,7 @@ import setInnerHTML from "../helpers/dom/setInnerHTML"; export default class Row { public container: HTMLElement; public title: HTMLDivElement; + public titleRight: HTMLElement; public subtitle: HTMLElement; public media: HTMLElement; @@ -35,6 +36,7 @@ export default class Row { title: string | HTMLElement, titleLangKey: LangPackKey, titleRight: string | HTMLElement, + titleRightSecondary: string | HTMLElement, clickable: boolean | ((e: Event) => void), navigationTab: SliderSuperTab, havePadding: boolean, @@ -90,7 +92,8 @@ export default class Row { if(options.title || options.titleLangKey) { let c: HTMLElement; - if(options.titleRight) { + const titleRight = options.titleRight || options.titleRightSecondary; + if(titleRight) { c = document.createElement('div'); c.classList.add('row-title-row'); this.container.append(c); @@ -112,17 +115,21 @@ export default class Row { } c.append(this.title); - if(options.titleRight) { - const titleRight = document.createElement('div'); - titleRight.classList.add('row-title', 'row-title-right'); + if(titleRight) { + const titleRightEl = this.titleRight = document.createElement('div'); + titleRightEl.classList.add('row-title', 'row-title-right'); - if(typeof(options.titleRight) === 'string') { - titleRight.innerHTML = options.titleRight; + if(options.titleRightSecondary) { + titleRightEl.classList.add('row-title-right-secondary'); + } + + if(typeof(titleRight) === 'string') { + titleRightEl.innerHTML = titleRight; } else { - titleRight.append(options.titleRight); + titleRightEl.append(titleRight); } - c.append(titleRight); + c.append(titleRightEl); } } diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index f3a80bbc..92cf8dc1 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -52,7 +52,7 @@ import { ripple } from "../ripple"; export const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown'; export class AppSidebarLeft extends SidebarSlider { - private toolsBtn: HTMLButtonElement; + private toolsBtn: HTMLElement; private backBtn: HTMLButtonElement; //private searchInput = document.getElementById('global-search') as HTMLInputElement; private inputSearch: InputSearch; @@ -671,7 +671,9 @@ export type SettingSectionOptions = { caption?: LangPackKey | true, noDelimiter?: boolean, fakeGradientDelimiter?: boolean, - noShadow?: boolean + noShadow?: boolean, + // fullWidth?: boolean, + // noPaddingTop?: boolean }; const className = 'sidebar-left-section'; @@ -682,6 +684,8 @@ export class SettingSection { public title: HTMLElement; public caption: HTMLElement; + private fullWidth: boolean; + constructor(options: SettingSectionOptions = {}) { const container = this.container = document.createElement('div'); container.classList.add(className + '-container'); @@ -703,6 +707,14 @@ export class SettingSection { innerContainer.classList.add('no-delimiter'); } + // if(options.fullWidth) { + // this.fullWidth = true; + // } + + // if(options.noPaddingTop) { + // innerContainer.classList.add('no-padding-top'); + // } + const content = this.content = this.generateContentElement(); if(options.name) { @@ -728,6 +740,11 @@ export class SettingSection { public generateContentElement() { const content = document.createElement('div'); content.classList.add(className + '-content'); + + // if(this.fullWidth) { + // content.classList.add('full-width'); + // } + this.innerContainer.append(content); return content; } diff --git a/src/components/sidebarLeft/tabs/activeSessions.ts b/src/components/sidebarLeft/tabs/activeSessions.ts index c60fa737..95088a82 100644 --- a/src/components/sidebarLeft/tabs/activeSessions.ts +++ b/src/components/sidebarLeft/tabs/activeSessions.ts @@ -20,9 +20,9 @@ import PopupPeer from "../../popups/peer"; import findUpClassName from "../../../helpers/dom/findUpClassName"; import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import toggleDisability from "../../../helpers/dom/toggleDisability"; +import { SliderSuperTabEventable } from "../../sliderTab"; -export default class AppActiveSessionsTab extends SliderSuperTab { - public privacyTab: AppPrivacyAndSecurityTab; +export default class AppActiveSessionsTab extends SliderSuperTabEventable { public authorizations: Authorization.authorization[]; private menuElement: HTMLElement; @@ -76,7 +76,6 @@ export default class AppActiveSessionsTab extends SliderSuperTab { //toggleDisability([btnTerminate], false); btnTerminate.remove(); otherSection.container.remove(); - this.privacyTab.updateActiveSessions(); }, onError).finally(() => { toggle(); }); @@ -127,7 +126,6 @@ export default class AppActiveSessionsTab extends SliderSuperTab { .then(value => { if(value) { target.remove(); - this.privacyTab.updateActiveSessions(); } }, onError); } diff --git a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts index c242a162..9909e0f2 100644 --- a/src/components/sidebarLeft/tabs/privacyAndSecurity.ts +++ b/src/components/sidebarLeft/tabs/privacyAndSecurity.ts @@ -93,8 +93,10 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { subtitleLangKey: SUBTITLE, clickable: () => { const tab = new AppActiveSessionsTab(this.slider); - tab.privacyTab = this; tab.authorizations = this.authorizations; + tab.eventListener.addEventListener('destroy', () => { + this.updateActiveSessions(); + }, {once: true}); tab.open(); } }); diff --git a/src/components/sidebarLeft/tabs/settings.ts b/src/components/sidebarLeft/tabs/settings.ts index 5dec2e2c..7ce59303 100644 --- a/src/components/sidebarLeft/tabs/settings.ts +++ b/src/components/sidebarLeft/tabs/settings.ts @@ -5,9 +5,7 @@ */ import { SliderSuperTab } from "../../slider"; -import AvatarElement from "../../avatar"; import apiManager from "../../../lib/mtproto/mtprotoworker"; -import appUsersManager from "../../../lib/appManagers/appUsersManager"; import ButtonMenuToggle from "../../buttonMenuToggle"; import Button from "../../button"; import AppPrivacyAndSecurityTab from "./privacyAndSecurity"; @@ -15,18 +13,24 @@ import AppGeneralSettingsTab from "./generalSettings"; import AppEditProfileTab from "./editProfile"; import AppChatFoldersTab from "./chatFolders"; import AppNotificationsTab from "./notifications"; -import PeerTitle from "../../peerTitle"; import AppLanguageTab from "./language"; import lottieLoader from "../../../lib/rlottie/lottieLoader"; import PopupPeer from "../../popups/peer"; import AppDataAndStorageTab from "./dataAndStorage"; +import ButtonIcon from "../../buttonIcon"; +import PeerProfile from "../../peerProfile"; +import rootScope from "../../../lib/rootScope"; +import { SettingSection } from ".."; +import Row from "../../row"; +import AppActiveSessionsTab from "./activeSessions"; +import { i18n, LangPackKey } from "../../../lib/langPack"; +import { AccountAuthorizations, Authorization } from "/Users/kuzmenko/Documents/projects/tweb/src/layer"; +import { SliderSuperTabConstructable } from "../../sliderTab"; +import PopupAvatar from "../../popups/avatar"; +import appProfileManager from "../../../lib/appManagers/appProfileManager"; //import AppMediaViewer from "../../appMediaViewerNew"; export default class AppSettingsTab extends SliderSuperTab { - private avatarElem: AvatarElement; - private nameDiv: HTMLElement; - private phoneDiv: HTMLElement; - private buttons: { edit: HTMLButtonElement, folders: HTMLButtonElement, @@ -34,8 +38,14 @@ export default class AppSettingsTab extends SliderSuperTab { notifications: HTMLButtonElement, storage: HTMLButtonElement, privacy: HTMLButtonElement, - language: HTMLButtonElement } = {} as any; + private profile: PeerProfile; + + private languageRow: Row; + private devicesRow: Row; + + private authorizations: Authorization.authorization[]; + private getAuthorizationsPromise: Promise; protected init() { this.container.classList.add('settings-container'); @@ -59,11 +69,25 @@ export default class AppSettingsTab extends SliderSuperTab { } }]); - this.header.append(btnMenu); + this.buttons.edit = ButtonIcon('edit'); + + this.header.append(this.buttons.edit, btnMenu); - this.avatarElem = new AvatarElement(); - this.avatarElem.setAttribute('clickable', ''); - this.avatarElem.classList.add('profile-avatar', 'avatar-120'); + this.profile = new PeerProfile(this.scrollable, this.listenerSetter, false); + this.profile.init(); + this.profile.setPeer(rootScope.myId); + this.profile.fillProfileElements(); + + const changeAvatarBtn = Button('btn-circle btn-corner z-depth-1 profile-change-avatar', {icon: 'cameraadd'}); + changeAvatarBtn.addEventListener('click', () => { + const canvas = document.createElement('canvas'); + new PopupAvatar().open(canvas, (upload) => { + upload().then(inputFile => { + return appProfileManager.uploadProfilePhoto(inputFile); + }); + }); + }); + this.profile.element.lastElementChild.firstElementChild.append(changeAvatarBtn); /* const div = document.createElement('div'); //div.style.cssText = 'border-radius: 8px; overflow: hidden; width: 396px; height: 264px; flex: 0 0 auto; position: relative; margin: 10rem 0 10rem auto;'; @@ -107,28 +131,66 @@ export default class AppSettingsTab extends SliderSuperTab { this.scrollable.append(div); */ - this.nameDiv = document.createElement('div'); - this.nameDiv.classList.add('profile-name'); - - this.phoneDiv = document.createElement('div'); - this.phoneDiv.classList.add('profile-subtitle'); - const buttonsDiv = document.createElement('div'); buttonsDiv.classList.add('profile-buttons'); - const className = 'profile-button btn-primary btn-transparent'; - buttonsDiv.append( - this.buttons.edit = Button(className, {icon: 'edit', text: 'EditAccount.Title'}), - this.buttons.folders = Button(className, {icon: 'folder', text: 'AccountSettings.Filters'}), - this.buttons.general = Button(className, {icon: 'settings', text: 'Telegram.GeneralSettingsViewController'}), - this.buttons.storage = Button(className, {icon: 'data', text: 'DataSettings'}), - this.buttons.notifications = Button(className, {icon: 'unmute', text: 'AccountSettings.Notifications'}), - this.buttons.privacy = Button(className, {icon: 'lock', text: 'AccountSettings.PrivacyAndSecurity'}), - this.buttons.language = Button(className, {icon: 'language', text: 'AccountSettings.Language'}) + const b: [string, LangPackKey, SliderSuperTabConstructable][] = [ + ['unmute', 'AccountSettings.Notifications', AppNotificationsTab], + ['data', 'DataSettings', AppDataAndStorageTab], + ['lock', 'AccountSettings.PrivacyAndSecurity', AppPrivacyAndSecurityTab], + ['settings', 'Telegram.GeneralSettingsViewController', AppGeneralSettingsTab], + ['folder', 'AccountSettings.Filters', AppChatFoldersTab], + ]; + + const rows = b.map(([icon, langPackKey, tabConstructor]) => { + return new Row({ + titleLangKey: langPackKey, + icon, + clickable: () => { + new tabConstructor(this.slider, true).open(); + } + }); + }); + + rows.push( + this.devicesRow = new Row({ + titleLangKey: 'Devices', + titleRightSecondary: ' ', + icon: 'activesessions', + clickable: async() => { + if(!this.authorizations) { + await this.updateActiveSessions(); + } + + const tab = new AppActiveSessionsTab(this.slider); + tab.authorizations = this.authorizations; + tab.eventListener.addEventListener('destroy', () => { + this.authorizations = undefined; + this.updateActiveSessions(true); + }, {once: true}); + tab.open(); + } + }), + + this.languageRow = new Row({ + titleLangKey: 'AccountSettings.Language', + titleRightSecondary: i18n('LanguageName'), + icon: 'language', + clickable: () => { + new AppLanguageTab(this.slider).open(); + } + }) ); - - this.scrollable.append(this.avatarElem, this.nameDiv, this.phoneDiv, buttonsDiv); - this.scrollable.container.classList.add('profile-content-wrapper'); + + buttonsDiv.append(...rows.map(row => row.container)); + + // const profileSection = new SettingSection({fullWidth: true, noPaddingTop: true}); + // profileSection.content.append(this.profile.element); + + const buttonsSection = new SettingSection(); + buttonsSection.content.append(buttonsDiv); + + this.scrollable.append(this.profile.element/* profileSection.container */, buttonsSection.container); /* rootScope.$on('user_auth', (e) => { this.fillElements(); @@ -139,41 +201,28 @@ export default class AppSettingsTab extends SliderSuperTab { tab.open(); }); - this.buttons.folders.addEventListener('click', () => { - new AppChatFoldersTab(this.slider).open(); - }); - - this.buttons.general.addEventListener('click', () => { - new AppGeneralSettingsTab(this.slider).open(); - }); - - this.buttons.storage.addEventListener('click', () => { - new AppDataAndStorageTab(this.slider).open(); - }); + lottieLoader.loadLottieWorkers(); - this.buttons.notifications.addEventListener('click', () => { - new AppNotificationsTab(this.slider).open(); - }); + this.updateActiveSessions(); + } - this.buttons.privacy.addEventListener('click', () => { - new AppPrivacyAndSecurityTab(this.slider).open(); - }); + private getAuthorizations(overwrite?: boolean) { + if(this.getAuthorizationsPromise && !overwrite) return this.getAuthorizationsPromise; - this.buttons.language.addEventListener('click', () => { - new AppLanguageTab(this.slider).open(); + const promise = this.getAuthorizationsPromise = apiManager.invokeApi('account.getAuthorizations') + .finally(() => { + if(this.getAuthorizationsPromise === promise) { + this.getAuthorizationsPromise = undefined; + } }); - lottieLoader.loadLottieWorkers(); - - this.fillElements(); + return promise; } - public fillElements() { - const user = appUsersManager.getSelf(); - const peerId = user.id.toPeerId(false); - this.avatarElem.setAttribute('peer', '' + peerId); - - this.nameDiv.append(new PeerTitle({peerId: peerId}).element); - this.phoneDiv.innerHTML = user.phone ? appUsersManager.formatUserPhone(user.phone) : ''; + public updateActiveSessions(overwrite?: boolean) { + return this.getAuthorizations(overwrite).then(auths => { + this.authorizations = auths.authorizations; + this.devicesRow.titleRight.textContent = '' + this.authorizations.length; + }); } } diff --git a/src/lang.ts b/src/lang.ts index 8908acbb..b4986e40 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -666,7 +666,7 @@ const lang = { "AutoDownloadPm": "PM", "AutoDownloadGroups": "Groups", "AutoDownloadChannels": "Channels", - "AutoDownloadAudioInfo": "Voice messages are tiny, so they\'re always downloaded automatically.", + "AutoDownloadAudioInfo": "Voice messages are tiny, so they're always downloaded automatically.", "AutoplayMedia": "Auto-play media", "AutoDownloadPhotosTitle": "Auto-download photos", "AutoDownloadVideosTitle": "Auto-download videos and GIFs", @@ -677,6 +677,9 @@ const lang = { "ResetAutomaticMediaDownloadAlertTitle": "Reset settings", "ResetAutomaticMediaDownloadAlert": "Are you sure you want to reset auto-download settings?", "Reset": "Reset", + "SendMessageAsTitle": "Send message as...", + "Devices": "Devices", + "LanguageName": "English", // * macos "AccountSettings.Filters": "Chat Folders", @@ -811,6 +814,7 @@ const lang = { "Chat.Send.WithoutSound": "Send Without Sound", "Chat.Send.SetReminder": "Set a Reminder", "Chat.Send.ScheduledMessage": "Schedule Message", + "Chat.SendAs.PersonalAccount": "personal account", "Chat.UnpinAllMessagesConfirmation": { "one_value": "Do you want to unpin %d message in this chat?", "other_value": "Do you want to unpin all %d messages in this chat?" diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 75c803ba..a1caaacf 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -12,7 +12,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; import { isObject, safeReplaceObject, copy, deepEqual } from "../../helpers/object"; import { isRestricted } from "../../helpers/restrictions"; -import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatParticipant, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, Update, Updates, ChannelsCreateChannel } from "../../layer"; +import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatParticipant, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, Update, Updates, ChannelsCreateChannel, Peer } from "../../layer"; import apiManagerProxy from "../mtproto/mtprotoworker"; import apiManager from '../mtproto/mtprotoworker'; import { RichTextProcessor } from "../richtextprocessor"; @@ -787,6 +787,21 @@ export class AppChatsManager { return !!(chat.pFlags.restricted && restrictionReasons && isRestricted(restrictionReasons)); } + + public getSendAs(channelId: ChatId) { + return apiManager.invokeApiSingleProcess({ + method: 'channels.getSendAs', + params: { + peer: this.getChannelInputPeer(channelId) + }, + processResult: (sendAsPeers) => { + appUsersManager.saveApiUsers(sendAsPeers.users); + appChatsManager.saveApiChats(sendAsPeers.chats); + + return sendAsPeers.peers; + } + }); + } } const appChatsManager = new AppChatsManager(); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 2377556e..f979b55e 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -15,7 +15,7 @@ import { ripple } from "../../components/ripple"; //import Scrollable from "../../components/scrollable"; import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable"; import { formatDateAccordingToTodayNew } from "../../helpers/date"; -import { IS_SAFARI } from "../../environment/userAgent"; +import { IS_MOBILE_SAFARI, IS_SAFARI } from "../../environment/userAgent"; import { logger, LogTypes } from "../logger"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -59,6 +59,7 @@ import groupCallActiveIcon from "../../components/groupCallActiveIcon"; import { Chat } from "../../layer"; import IS_GROUP_CALL_SUPPORTED from "../../environment/groupCallSupport"; import mediaSizes from "../../helpers/mediaSizes"; +import appNavigationController, { NavigationItem } from "../../components/appNavigationController"; export type DialogDom = { avatarEl: AvatarElement, @@ -181,6 +182,8 @@ export class AppDialogsManager { private emptyDialogsPlaceholderSubtitle: I18n.IntlElement; private updateContactsLengthPromise: Promise; + private filtersNavigationItem: NavigationItem; + constructor() { this.chatsPreloader = putPreloader(null, true); @@ -298,6 +301,25 @@ export class AppDialogsManager { id = +tabContent.dataset.filterId || 0; + if(!IS_MOBILE_SAFARI) { + if(id) { + if(!this.filtersNavigationItem) { + this.filtersNavigationItem = { + type: 'filters', + onPop: () => { + selectTab(0); + this.filtersNavigationItem = undefined; + } + }; + + appNavigationController.unshiftItem(this.filtersNavigationItem); + } + } else if(this.filtersNavigationItem) { + appNavigationController.removeItem(this.filtersNavigationItem); + this.filtersNavigationItem = undefined; + } + } + if(this.filterId === id) return; this.sortedLists[id].clear(); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 9a906eb7..df6cc80c 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -1793,7 +1793,7 @@ export class AppImManager { } } - public async getPeerStatus(peerId: PeerId) { + public async getPeerStatus(peerId: PeerId, ignoreSelf?: boolean) { let subtitle: HTMLElement; if(!peerId) return; @@ -1828,7 +1828,7 @@ export class AppImManager { } else { // user const user = appUsersManager.getUser(peerId); - if(rootScope.myId === peerId) { + if(rootScope.myId === peerId && !ignoreSelf) { return; } else if(user) { subtitle = appUsersManager.getUserStatusString(user.id); @@ -1851,7 +1851,7 @@ export class AppImManager { } } - public setPeerStatus(peerId: PeerId, element: HTMLElement, needClear: boolean, useWhitespace: boolean, middleware: () => boolean) { + public setPeerStatus(peerId: PeerId, element: HTMLElement, needClear: boolean, useWhitespace: boolean, middleware: () => boolean, ignoreSelf?: boolean) { if(needClear) { element.innerHTML = useWhitespace ? '‎' : ''; // ! HERE U CAN FIND WHITESPACE } @@ -1862,7 +1862,7 @@ export class AppImManager { return; } - this.getPeerStatus(peerId).then((subtitle) => { + this.getPeerStatus(peerId, ignoreSelf).then((subtitle) => { if(!middleware()) { return; } diff --git a/src/lib/appManagers/appInlineBotsManager.ts b/src/lib/appManagers/appInlineBotsManager.ts index 33a29fca..c403307d 100644 --- a/src/lib/appManagers/appInlineBotsManager.ts +++ b/src/lib/appManagers/appInlineBotsManager.ts @@ -279,6 +279,7 @@ export class AppInlineBotsManager { clearDraft: true, scheduleDate: number, silent: true, + sendAsPeerId: PeerId, geoPoint: GeoPoint }> = {}) { const inlineResult = this.inlineResults[queryAndResultIds]; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index ab38970b..f3c44164 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -449,7 +449,8 @@ export class AppMessagesManager { clearDraft: true, webPage: WebPage, scheduleDate: number, - silent: true + silent: true, + sendAsPeerId: PeerId, }> = {}) { if(!text.trim()) { return Promise.resolve(); @@ -520,6 +521,7 @@ export class AppMessagesManager { sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId; } + const sendAs = options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined let apiPromise: any; if(options.viaBotId) { apiPromise = apiManager.invokeApiAfter('messages.sendInlineBotResult', { @@ -528,7 +530,8 @@ export class AppMessagesManager { reply_to_msg_id: replyToMsgId || undefined, query_id: options.queryId, id: options.resultId, - clear_draft: options.clearDraft + clear_draft: options.clearDraft, + send_as: sendAs }, sentRequestOptions); } else { apiPromise = apiManager.invokeApiAfter('messages.sendMessage', { @@ -540,7 +543,8 @@ export class AppMessagesManager { entities: sendEntites, clear_draft: options.clearDraft, schedule_date: options.scheduleDate || undefined, - silent: options.silent + silent: options.silent, + send_as: sendAs }, sentRequestOptions); } @@ -633,6 +637,7 @@ export class AppMessagesManager { isMedia: true, replyToMsgId: number, + sendAsPeerId: PeerId, threadId: number, groupId: string, caption: string, @@ -1026,7 +1031,8 @@ export class AppMessagesManager { schedule_date: options.scheduleDate, silent: options.silent, entities, - clear_draft: options.clearDraft + clear_draft: options.clearDraft, + send_as: options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined }).then((updates) => { apiUpdatesManager.processUpdateMessage(updates); }, (error) => { @@ -1055,6 +1061,7 @@ export class AppMessagesManager { isMedia: true, entities: MessageEntity[], replyToMsgId: number, + sendAsPeerId: PeerId, threadId: number, caption: string, sendFileDetails: Partial<{ @@ -1101,6 +1108,7 @@ export class AppMessagesManager { silent: options.silent, replyToMsgId, threadId: options.threadId, + sendAsPeerId: options.sendAsPeerId, groupId, ...details }; @@ -1146,7 +1154,8 @@ export class AppMessagesManager { reply_to_msg_id: replyToMsgId, schedule_date: options.scheduleDate, silent: options.silent, - clear_draft: options.clearDraft + clear_draft: options.clearDraft, + send_as: options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined }).then((updates) => { apiUpdatesManager.processUpdateMessage(updates); deferred.resolve(); @@ -1222,7 +1231,8 @@ export class AppMessagesManager { resultId: string, scheduleDate: number, silent: true, - geoPoint: GeoPoint + geoPoint: GeoPoint, + sendAsPeerId: PeerId, }> = {}) { peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; @@ -1337,6 +1347,7 @@ export class AppMessagesManager { sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId; } + const sendAs = options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined; let apiPromise: Promise; if(options.viaBotId) { apiPromise = apiManager.invokeApiAfter('messages.sendInlineBotResult', { @@ -1347,7 +1358,8 @@ export class AppMessagesManager { id: options.resultId, clear_draft: options.clearDraft, schedule_date: options.scheduleDate, - silent: options.silent + silent: options.silent, + send_as: sendAs }, sentRequestOptions); } else { apiPromise = apiManager.invokeApiAfter('messages.sendMedia', { @@ -1358,7 +1370,8 @@ export class AppMessagesManager { message: '', clear_draft: options.clearDraft, schedule_date: options.scheduleDate, - silent: options.silent + silent: options.silent, + send_as: sendAs }, sentRequestOptions); } @@ -1463,6 +1476,7 @@ export class AppMessagesManager { private generateOutgoingMessage(peerId: PeerId, options: Partial<{ scheduleDate: number, replyToMsgId: number, + sendAsPeerId: PeerId, threadId: number, viaBotId: BotId, groupId: string, @@ -1486,7 +1500,7 @@ export class AppMessagesManager { const message: Message.message = { _: 'message', id: this.generateTempMessageId(peerId), - from_id: this.generateFromId(peerId), + from_id: options.sendAsPeerId ? appPeersManager.getOutputPeer(options.sendAsPeerId) : this.generateFromId(peerId), peer_id: appPeersManager.getOutputPeer(peerId), post_author: postAuthor, pFlags: this.generateFlags(peerId), @@ -1937,7 +1951,8 @@ export class AppMessagesManager { silent: true, scheduleDate: number, dropAuthor: boolean, - dropCaptions: boolean + dropCaptions: boolean, + sendAsPeerId: PeerId, }> = {}) { peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; mids = mids.slice().sort((a, b) => a - b); @@ -2044,7 +2059,8 @@ export class AppMessagesManager { silent: options.silent, schedule_date: options.scheduleDate, drop_author: options.dropAuthor, - drop_media_captions: options.dropCaptions + drop_media_captions: options.dropCaptions, + send_as: options.sendAsPeerId ? appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined }, sentRequestOptions).then((updates) => { this.log('forwardMessages updates:', updates); apiUpdatesManager.processUpdateMessage(updates); @@ -5047,6 +5063,8 @@ export class AppMessagesManager { this.dialogsStorage.dropDialogOnDeletion(peerId); } } + + rootScope.dispatchEvent('channel_update', channelId); }; private onUpdateChannelReload = (update: Update.updateChannelReload) => { diff --git a/src/lib/appManagers/appProfileManager.ts b/src/lib/appManagers/appProfileManager.ts index f9930a82..535f8174 100644 --- a/src/lib/appManagers/appProfileManager.ts +++ b/src/lib/appManagers/appProfileManager.ts @@ -121,6 +121,10 @@ export class AppProfileManager { } }); + rootScope.addEventListener('channel_update', (chatId) => { + this.refreshFullPeer(chatId.toPeerId(true)); + }); + // * genius rootScope.addEventListener('chat_full_update', (chatId) => { rootScope.dispatchEvent('peer_full_update', chatId.toPeerId(true)); @@ -477,10 +481,15 @@ export class AppProfileManager { } private refreshFullPeer(peerId: PeerId) { - if(peerId.isUser()) delete this.usersFull[peerId.toUserId()]; - else delete this.chatsFull[peerId.toChatId()]; - - rootScope.dispatchEvent('peer_full_update', peerId); + if(peerId.isUser()) { + const userId = peerId.toUserId(); + delete this.usersFull[userId]; + rootScope.dispatchEvent('user_full_update', userId); + } else { + const chatId = peerId.toChatId(); + delete this.chatsFull[chatId]; + rootScope.dispatchEvent('chat_full_update', chatId); + } // ! эта строчка будет создавать race condition: // ! запрос вернёт chat с установленным флагом call_not_empty, хотя сам апдейт уже будет применён diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 0f8189f8..782583f3 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -28,6 +28,8 @@ import { MTAppConfig } from "./mtproto/appConfig"; export type BroadcastEvents = { 'chat_full_update': ChatId, 'chat_update': ChatId, + + 'channel_update': ChatId, 'user_update': UserId, 'user_auth': UserAuth, diff --git a/src/scripts/out/langPack.strings b/src/scripts/out/langPack.strings index 932b3978..35bcdfa2 100644 --- a/src/scripts/out/langPack.strings +++ b/src/scripts/out/langPack.strings @@ -12,8 +12,6 @@ "FilterAllNonContacts" = "All Non-Contacts"; "FilterAllChannels" = "All Channels"; "FilterAllBots" = "All Bots"; -"WordDelimiter" = ", "; -"WordDelimiterLast" = " and "; "EditContact.OriginalName" = "original name"; "EditProfile.FirstNameLabel" = "Name"; "EditProfile.BioLabel" = "Bio (optional)"; @@ -55,7 +53,6 @@ "General.SendShortcut.CtrlEnter" = "Send by %s + Enter"; "General.SendShortcut.NewLine.ShiftEnter" = "New line by Shift + Enter"; "General.SendShortcut.NewLine.Enter" = "New line by Enter"; -"General.AutoplayMedia" = "Auto-Play Media"; "General.TimeFormat" = "Time Format"; "General.TimeFormat.h12" = "12-hour"; "General.TimeFormat.h23" = "24-hour"; @@ -564,6 +561,44 @@ "RequestToJoinSent" = "Join request sent"; "RequestToJoinGroupApproved" = "Your request to join the group was approved"; "RequestToJoinChannelApproved" = "Your request to join the channel was approved"; +"Update" = "UPDATE"; +"Reactions" = "Reactions"; +"DoubleTapSetting" = "Quick Reaction"; +"EnableReactions" = "Enable Reactions"; +"EnableReactionsChannelInfo" = "Allow subscribers to react to channel posts."; +"EnableReactionsGroupInfo" = "Allow members to react to group messages."; +"AvailableReactions" = "Available reactions"; +"NobodyViewed" = "Nobody viewed"; +"MessageSeen_one" = "Seen"; +"MessageSeen_other" = "%1$d Seen"; +"DataSettings" = "Data and Storage"; +"GroupsAndChannelsHelp" = "Change who can add you to groups and channels."; +"SessionsInfo" = "Control your sessions on other devices."; +"StickersBotInfo" = "Artists are welcome to add their own sticker sets using our @stickers bot."; +"AutomaticMediaDownload" = "Automatic media download"; +"AutoDownloadPhotos" = "Photos"; +"AutoDownloadVideos" = "Videos"; +"AutoDownloadFiles" = "Files"; +"AutoDownloadOnAllChats" = "On in all chats"; +"AutoDownloadUpToOnAllChats" = "Up to %1$s in all chats"; +"AutoDownloadOff" = "Off"; +"AutoDownloadOnUpToFor" = "Up to %1$s for %2$s"; +"AutoDownloadOnFor" = "On for %1$s"; +"AutoDownloadContacts" = "Contacts"; +"AutoDownloadPm" = "PM"; +"AutoDownloadGroups" = "Groups"; +"AutoDownloadChannels" = "Channels"; +"AutoDownloadAudioInfo" = "Voice messages are tiny, so they're always downloaded automatically."; +"AutoplayMedia" = "Auto-play media"; +"AutoDownloadPhotosTitle" = "Auto-download photos"; +"AutoDownloadVideosTitle" = "Auto-download videos and GIFs"; +"AutoDownloadFilesTitle" = "Auto-download files and music"; +"AutoDownloadMaxFileSize" = "Maximum file size"; +"AutodownloadSizeLimitUpTo" = "up to %1$s"; +"ResetAutomaticMediaDownload" = "Reset Auto-Download Settings"; +"ResetAutomaticMediaDownloadAlertTitle" = "Reset settings"; +"ResetAutomaticMediaDownloadAlert" = "Are you sure you want to reset auto-download settings?"; +"Reset" = "Reset"; "AccountSettings.Filters" = "Chat Folders"; "AccountSettings.Notifications" = "Notifications and Sounds"; "AccountSettings.PrivacyAndSecurity" = "Privacy and Security"; @@ -571,6 +606,8 @@ "Alert.UserDoesntExists" = "Sorry, this user doesn't seem to exist."; "Alert.Confirm.Discard" = "Discard"; "Appearance.Reset" = "Reset to Defaults"; +"AutoDownloadSettings.Delimeter" = ", "; +"AutoDownloadSettings.LastDelimeter" = " and "; "Bio.Description" = "Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco"; "Call.Accept" = "Accept"; "Call.Decline" = "Decline"; @@ -614,6 +651,8 @@ "Chat.Alert.Forward.Action.HideCaption_other" = "Hide Captions"; "Chat.CopySelectedText" = "Copy Selected Text"; "Chat.Confirm.Unpin" = "Would you like to unpin this message?"; +"Chat.Context.Reacted" = "%1$@/%2$@ Reacted"; +"Chat.Context.ReactedFast_other" = "%d Reacted"; "Chat.Date.ScheduledFor" = "Scheduled for %@"; "Chat.Date.ScheduledForToday" = "Scheduled for today"; "Chat.DropTitle" = "Drop files here to send them"; @@ -708,6 +747,12 @@ "ChatList.Filter.Exclude.LimitReached" = "Sorry, you can only add up to 100 individual chats. Try using chat types."; "ChatList.Filter.Confirm.Remove.Header" = "Remove Folder"; "ChatList.Filter.Confirm.Remove.Text" = "Are you sure you want to remove this folder? Your chats will not be deleted."; +"ChatList.Mute.1Hour" = "For 1 Hour"; +"ChatList.Mute.4Hours" = "For 4 Hours"; +"ChatList.Mute.8Hours" = "For 8 Hours"; +"ChatList.Mute.1Day" = "For 1 Day"; +"ChatList.Mute.3Days" = "For 3 Days"; +"ChatList.Mute.Forever" = "Forever"; "Channel.DescriptionHolderDescrpiton" = "You can provide an optional description for your channel."; "CreateGroup.NameHolder" = "Group Name"; "Date.Today" = "Today"; @@ -724,20 +769,26 @@ "Emoji.TravelAndPlaces" = "Travel & Places"; "Emoji.Objects" = "Objects"; "Emoji.Flags" = "Flags"; +"FileSize.B" = "%@ B"; +"FileSize.KB" = "%@ KB"; +"FileSize.MB" = "%@ MB"; +"FileSize.GB" = "%@ GB"; +"InstalledStickers.LoopAnimated" = "Loop Animated Stickers"; "LastSeen.HoursAgo_one" = "last seen %d hour ago"; "LastSeen.HoursAgo_other" = "last seen %d hours ago"; "Login.Register.LastName.Placeholder" = "Last Name"; +"Message.Context.Select" = "Select"; +"Message.Context.Pin" = "Pin"; +"Message.Context.Unpin" = "Unpin"; +"Message.Context.Goto" = "Show Message"; +"MessageContext.CopyMessageLink1" = "Copy Message Link"; "Modal.Send" = "Send"; -"Telegram.GeneralSettingsViewController" = "General Settings"; -"Telegram.InstalledStickerPacksController" = "Stickers"; -"Telegram.NotificationSettingsViewController" = "Notifications"; -"Telegram.LanguageViewController" = "Language"; -"Stickers.SearchAdd" = "Add"; -"Stickers.SearchAdded" = "Added"; -"Stickers.SuggestStickers" = "Suggest Stickers by Emoji"; -"ShareModal.Search.Placeholder" = "Share to..."; -"ShareModal.Search.ForwardPlaceholder" = "Forward to..."; -"InstalledStickers.LoopAnimated" = "Loop Animated Stickers"; +"NewPoll.Anonymous" = "Anonymous Voting"; +"NewPoll.Explanation.Placeholder" = "Add a Comment (Optional)"; +"NewPoll.OptionsAddOption" = "Add an Option"; +"NewPoll.MultipleChoice" = "Multiple Answers"; +"NewPoll.Quiz" = "Quiz Mode"; +"Notification.Contact.Reacted" = "%1$@ to your \"%2$@\""; "Peer.Activity.User.PlayingGame" = "playing a game"; "Peer.Activity.User.TypingText" = "typing"; "Peer.Activity.User.SendingPhoto" = "sending a photo"; @@ -746,6 +797,7 @@ "Peer.Activity.User.RecordingAudio" = "recording voice"; "Peer.Activity.User.SendingFile" = "sending file"; "Peer.Activity.User.ChoosingSticker" = "choosing a sticker"; +"Peer.Activity.User.EnjoyingAnimations" = "watching %@"; "Peer.Activity.Chat.PlayingGame" = "%@ is playing a game"; "Peer.Activity.Chat.TypingText" = "%@ is typing"; "Peer.Activity.Chat.SendingPhoto" = "%@ is sending a photo"; @@ -754,6 +806,7 @@ "Peer.Activity.Chat.RecordingAudio" = "%@ is recording voice"; "Peer.Activity.Chat.SendingFile" = "%@ is sending a file"; "Peer.Activity.Chat.ChoosingSticker" = "%@ is choosing a sticker"; +"Peer.Activity.Chat.EnjoyingAnimations" = "%@ is watching %@"; "Peer.Activity.Chat.Multi.PlayingGame1" = "%@ and %d others are playing a game"; "Peer.Activity.Chat.Multi.TypingText1" = "%@ and %d others are typing"; "Peer.Activity.Chat.Multi.SendingPhoto1" = "%@ and %d others are sending photos"; @@ -824,16 +877,15 @@ "PrivacySettingsController.UserCount_other" = "%d users"; "RecentSessions.Error.FreshReset" = "For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours."; "RequestJoin.Button" = "Request to Join"; -"Message.Context.Select" = "Select"; -"Message.Context.Pin" = "Pin"; -"Message.Context.Unpin" = "Unpin"; -"Message.Context.Goto" = "Show Message"; -"MessageContext.CopyMessageLink1" = "Copy Message Link"; -"NewPoll.Anonymous" = "Anonymous Voting"; -"NewPoll.Explanation.Placeholder" = "Add a Comment (Optional)"; -"NewPoll.OptionsAddOption" = "Add an Option"; -"NewPoll.MultipleChoice" = "Multiple Answers"; -"NewPoll.Quiz" = "Quiz Mode"; +"Stickers.SearchAdd" = "Add"; +"Stickers.SearchAdded" = "Added"; +"Stickers.SuggestStickers" = "Suggest Stickers by Emoji"; +"ShareModal.Search.Placeholder" = "Share to..."; +"ShareModal.Search.ForwardPlaceholder" = "Forward to..."; +"Telegram.GeneralSettingsViewController" = "General Settings"; +"Telegram.InstalledStickerPacksController" = "Stickers"; +"Telegram.NotificationSettingsViewController" = "Notifications"; +"Telegram.LanguageViewController" = "Language"; "GeneralSettings.BigEmoji" = "Large Emoji"; "GeneralSettings.EmojiPrediction" = "Suggest Emoji"; "GroupPermission.Delete" = "Delete Exception"; @@ -845,6 +897,24 @@ "Schedule.SendDate" = "Send on %@ at %@"; "Schedule.SendWhenOnline" = "Send When Online"; "Stickers.Recent" = "Recent"; +"StickerSet.DontExist" = "Sorry, this sticker set doesn't seem to exist."; +"Text.Context.Copy.Username" = "Copy Username"; +"Text.Context.Copy.Hashtag" = "Copy Hashtag"; +"Time.TomorrowAt" = "tomorrow at %@"; +"TwoStepAuth.SetPasswordHelp" = "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS."; +"TwoStepAuth.GenericHelp" = "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account."; +"TwoStepAuth.ChangePassword" = "Change Password"; +"TwoStepAuth.RemovePassword" = "Turn Password Off"; +"TwoStepAuth.SetupEmail" = "Set Recovery Email"; +"TwoStepAuth.ChangeEmail" = "Change Recovery Email"; +"TwoStepAuth.ConfirmEmailCodeDesc" = "Please enter the code we've just emailed to %@."; +"TwoStepAuth.RecoveryTitle" = "Email Code"; +"TwoStepAuth.RecoveryCode" = "Code"; +"TwoStepAuth.RecoveryCodeInvalid" = "Invalid code. Please try again."; +"TwoStepAuth.RecoveryCodeExpired" = "Code Expired"; +"TwoStepAuth.SetupHintTitle" = "Password Hint"; +"TwoStepAuth.SetupHintPlaceholder" = "Hint"; +"UsernameSettings.ChangeDescription" = "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters."; "VoiceChat.Chat.StartNew" = "Video chat ended. Start a new one?"; "VoiceChat.Chat.StartNew.OK" = "Start"; "VoiceChat.Chat.Ended" = "Video chat ended."; @@ -875,23 +945,6 @@ "VoiceChat.RemovePeer.Confirm.Channel" = "Do you want to remove %1$@ from the channel?"; "VoiceChat.RemovePeer.Confirm" = "Are you sure you want to remove %1$@ from the group?"; "VoiceChat.RemovePeer.Confirm.OK" = "Remove"; -"Text.Context.Copy.Username" = "Copy Username"; -"Text.Context.Copy.Hashtag" = "Copy Hashtag"; -"Time.TomorrowAt" = "tomorrow at %@"; -"TwoStepAuth.SetPasswordHelp" = "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS."; -"TwoStepAuth.GenericHelp" = "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account."; -"TwoStepAuth.ChangePassword" = "Change Password"; -"TwoStepAuth.RemovePassword" = "Turn Password Off"; -"TwoStepAuth.SetupEmail" = "Set Recovery Email"; -"TwoStepAuth.ChangeEmail" = "Change Recovery Email"; -"TwoStepAuth.ConfirmEmailCodeDesc" = "Please enter the code we've just emailed to %@."; -"TwoStepAuth.RecoveryTitle" = "Email Code"; -"TwoStepAuth.RecoveryCode" = "Code"; -"TwoStepAuth.RecoveryCodeInvalid" = "Invalid code. Please try again."; -"TwoStepAuth.RecoveryCodeExpired" = "Code Expired"; -"TwoStepAuth.SetupHintTitle" = "Password Hint"; -"TwoStepAuth.SetupHintPlaceholder" = "Hint"; -"UsernameSettings.ChangeDescription" = "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters."; "Login.Title" = "Sign in to Telegram"; "Login.CountrySelectorLabel" = "Country"; "Login.PhoneLabel" = "Phone Number"; diff --git a/src/scss/partials/_avatar.scss b/src/scss/partials/_avatar.scss index e8be52d9..1f308c44 100644 --- a/src/scss/partials/_avatar.scss +++ b/src/scss/partials/_avatar.scss @@ -205,7 +205,8 @@ avatar-element { &.avatar-30 { --size: 30px; - --multiplier: 1.8; + // --multiplier: 1.8; // text won't be centered + --multiplier: 1.6875; } &.avatar-24 { diff --git a/src/scss/partials/_button.scss b/src/scss/partials/_button.scss index 95263eec..c61a8691 100644 --- a/src/scss/partials/_button.scss +++ b/src/scss/partials/_button.scss @@ -200,6 +200,10 @@ &:before { color: var(--secondary-text-color); font-size: var(--icon-size); + } + + &-icon, + &:before { margin-right: var(--icon-margin); position: relative; } @@ -227,6 +231,18 @@ } } + &-subtitle { + font-size: .875rem; + color: var(--secondary-text-color); + } + + &-header { + color: var(--primary-color); + height: 2rem; + font-weight: 500; + pointer-events: none !important; + } + .stacked-avatars { --margin-right: -.6875rem; flex: 0 0 auto; diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index ec1ae54c..8545fd9e 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1018,19 +1018,6 @@ $background-transition-total-time: #{$input-transition-time - $background-transi background-color: var(--light-primary-color) !important; } - .btn-menu { - right: calc(var(--padding-horizontal) * -1 - .125rem); - bottom: calc(100% + 1.125rem); - - @include respond-to(esg-bottom-new) { - bottom: calc(100% + .875rem); - } - - &-item { - padding: 0 38px 0 16px; - } - } - &.btn-disabled { opacity: var(--disabled-opacity); } @@ -1175,10 +1162,123 @@ $background-transition-total-time: #{$input-transition-time - $background-transi } .new-message-wrapper { + --button-size: 2.125rem; + --button-horizontal-margin: .125rem; + --send-as-size: 1.875rem; + --send-as-margin-left: .25rem; + --send-as-margin-right: .375rem; + --send-as-total-size: calc(var(--send-as-size) + var(--send-as-margin-left) + var(--send-as-margin-right)); //padding: 4.5px 0; //padding-bottom: 4.5px; align-items: flex-end; min-height: var(--chat-input-size); + + .new-message-send-as { + &-container { + width: var(--send-as-size); + height: var(--send-as-size); + position: absolute; + flex: 0 0 auto; + // margin: 0 0.375rem .4375rem var(--send-as-margin-left); + margin: 0 0 .4375rem var(--send-as-margin-left); + cursor: pointer; + transform: scale(0); + background: none !important; + + .btn-menu-item-icon { + margin-right: calc(var(--icon-margin) - .5rem); + + &.active:before { + --offset: -.25rem; + content: " "; + position: absolute; + top: var(--offset); + right: var(--offset); + bottom: var(--offset); + left: var(--offset); + border: .125rem solid var(--primary-color); + border-radius: 50%; + } + } + } + + &-avatar { + position: absolute; + transform: scale(0); + opacity: 0; + + &.is-visible { + &:not(.backwards) { + transform: scale(1); + opacity: 1; + } + + &.animating { + transition: transform var(--transition-standard-in), opacity var(--transition-standard-in); + } + } + } + + &-close { + width: inherit; + height: inherit; + background-color: var(--primary-color); + font-size: 1.375rem; + color: #fff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + } + } + + &.has-send-as { + .toggle-emoticons, + .input-message-container { + transform: translateX(0); + } + + &:not(.backwards) { + .toggle-emoticons { + transform: translateX(var(--send-as-total-size)); + } + + .input-message-container { + --translateX: calc(var(--send-as-total-size)); + padding-right: var(--translateX); + transform: translate(var(--translateX)); + } + + .new-message-send-as-container { + transform: scale(1); + } + } + + &.animating { + .toggle-emoticons, + .input-message-container, + .new-message-send-as-container { + transition: transform var(--transition-standard-in); + } + } + } + + .btn-menu { + bottom: calc(100% + 1.125rem); + + @include respond-to(esg-bottom-new) { + bottom: calc(100% + .875rem); + } + + &.top-left { + right: calc(var(--padding-horizontal) * -1 - .125rem); + } + + &.top-right { + left: calc(var(--padding-horizontal) * -1 - .125rem); + } + } } .input-message-container { @@ -1199,14 +1299,14 @@ $background-transition-total-time: #{$input-transition-time - $background-transi .btn-icon { flex: 0 0 auto; - font-size: 24px; + font-size: 1.5rem; color: var(--secondary-text-color); // ! EXPERIMENTAL - margin: 0 .125rem 5px; + margin: 0 var(--button-horizontal-margin) 5px; padding: 0; - width: 34px; - height: 34px; + width: var(--button-size); + height: var(--button-size); &.active { color: var(--primary-color); diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 32de5505..fec24ac7 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -584,21 +584,9 @@ .settings-container { .profile { - &-button { - @include respond-to(handhelds) { - border-radius: 0; - } - } - - &-buttons { - margin-top: 1.1875rem; - width: 100%; - padding: 0 .4375rem; - - @include respond-to(handhelds) { - margin-top: .6875rem; - padding: 0; - } + &-avatars-container { + padding-bottom: 0; + height: 15rem; } } } @@ -851,6 +839,10 @@ } } + &.no-padding-top { + padding-top: 0 !important; + } + @include respond-to(handhelds) { padding-bottom: .5rem; } @@ -876,6 +868,10 @@ border-radius: 0; } } + + &.full-width { + margin: 0 !important; + } } &-name { @@ -956,7 +952,6 @@ #chats-archived-container, #contacts-container, .add-members-container, -.settings-container, #search-private-container, #stickers-container, #poll-results-container, diff --git a/src/scss/partials/_profile.scss b/src/scss/partials/_profile.scss index eeae1628..536b109a 100644 --- a/src/scss/partials/_profile.scss +++ b/src/scss/partials/_profile.scss @@ -214,7 +214,7 @@ padding-left: 54px; } */ - &-wrapper { + /* &-wrapper { flex: 1 1 auto; display: flex; flex-direction: column; @@ -223,7 +223,7 @@ @include respond-to(not-handhelds) { padding-top: 15px; } - } + } */ .sidebar-left-section { position: relative; @@ -258,6 +258,21 @@ } } + &-button { + @include respond-to(handhelds) { + border-radius: 0; + } + } + + &-change-avatar { + --size: 3.375rem; + position: absolute !important; + top: calc(var(--size) / -2); + right: 1.25rem; + transform: none; + transition: none !important; + } + &-container { > .scrollable { display: flex; diff --git a/src/scss/partials/_row.scss b/src/scss/partials/_row.scss index 23d9a9c7..6045a8e1 100644 --- a/src/scss/partials/_row.scss +++ b/src/scss/partials/_row.scss @@ -50,6 +50,10 @@ &-right { flex: 0 0 auto !important; margin-left: 1rem; + + &-secondary { + color: var(--secondary-text-color); + } } } diff --git a/src/scss/partials/_slider.scss b/src/scss/partials/_slider.scss index 0d734682..c6da0ab7 100644 --- a/src/scss/partials/_slider.scss +++ b/src/scss/partials/_slider.scss @@ -93,6 +93,7 @@ display: inline-flex; align-items: center; overflow: visible; + pointer-events: none; } }