Extended media invoices
This commit is contained in:
parent
493897fbd3
commit
057a0638f2
|
@ -19,11 +19,12 @@ import {fastRaf} from '../helpers/schedulers';
|
|||
export type AnimationItemGroup = '' | 'none' | 'chat' | 'lock' |
|
||||
'STICKERS-POPUP' | 'emoticons-dropdown' | 'STICKERS-SEARCH' | 'GIFS-SEARCH' |
|
||||
`CHAT-MENU-REACTIONS-${number}` | 'INLINE-HELPER' | 'GENERAL-SETTINGS' | 'STICKER-VIEWER' | 'EMOJI' |
|
||||
'EMOJI-STATUS';
|
||||
'EMOJI-STATUS' | `chat-${number}`;
|
||||
export interface AnimationItem {
|
||||
el: HTMLElement,
|
||||
group: AnimationItemGroup,
|
||||
animation: AnimationItemWrapper
|
||||
animation: AnimationItemWrapper,
|
||||
controlled?: boolean
|
||||
};
|
||||
|
||||
export interface AnimationItemWrapper {
|
||||
|
@ -46,7 +47,7 @@ export class AnimationIntersector {
|
|||
private onlyOnePlayableGroup: AnimationItemGroup;
|
||||
|
||||
private intersectionLockedGroups: {[group in AnimationItemGroup]?: true};
|
||||
private videosLocked;
|
||||
private videosLocked: boolean;
|
||||
|
||||
constructor() {
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
|
@ -107,19 +108,19 @@ export class AnimationIntersector {
|
|||
appMediaPlaybackController.addEventListener('play', ({doc}) => {
|
||||
if(doc.type === 'round') {
|
||||
this.videosLocked = true;
|
||||
this.checkAnimations();
|
||||
this.checkAnimations2();
|
||||
}
|
||||
});
|
||||
|
||||
appMediaPlaybackController.addEventListener('pause', () => {
|
||||
if(this.videosLocked) {
|
||||
this.videosLocked = false;
|
||||
this.checkAnimations();
|
||||
this.checkAnimations2();
|
||||
}
|
||||
});
|
||||
|
||||
idleController.addEventListener('change', (idle) => {
|
||||
this.checkAnimations(idle);
|
||||
this.checkAnimations2(idle);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -174,40 +175,52 @@ export class AnimationIntersector {
|
|||
}
|
||||
}
|
||||
|
||||
public addAnimation(_animation: AnimationItem['animation'], group: AnimationItemGroup = '') {
|
||||
if(group === 'none' || this.byPlayer.has(_animation)) {
|
||||
public addAnimation(
|
||||
animation: AnimationItem['animation'],
|
||||
group: AnimationItemGroup = '',
|
||||
observeElement?: HTMLElement,
|
||||
controlled?: boolean
|
||||
) {
|
||||
if(group === 'none' || this.byPlayer.has(animation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let el: HTMLElement;
|
||||
if(_animation instanceof RLottiePlayer) {
|
||||
el = _animation.el[0];
|
||||
} else if(_animation instanceof CustomEmojiRendererElement) {
|
||||
el = _animation.canvas;
|
||||
} else if(_animation instanceof CustomEmojiElement) {
|
||||
el = _animation.placeholder ?? _animation;
|
||||
} else if(_animation instanceof HTMLElement) {
|
||||
el = _animation;
|
||||
}
|
||||
|
||||
const animation: AnimationItem = {
|
||||
el,
|
||||
animation: _animation,
|
||||
group
|
||||
};
|
||||
|
||||
if(_animation instanceof RLottiePlayer) {
|
||||
if(!rootScope.settings.stickers.loop && _animation.loop) {
|
||||
_animation.loop = rootScope.settings.stickers.loop;
|
||||
if(!observeElement) {
|
||||
if(animation instanceof RLottiePlayer) {
|
||||
observeElement = animation.el[0];
|
||||
} else if(animation instanceof CustomEmojiRendererElement) {
|
||||
observeElement = animation.canvas;
|
||||
} else if(animation instanceof CustomEmojiElement) {
|
||||
observeElement = animation.placeholder ?? animation;
|
||||
} else if(animation instanceof HTMLElement) {
|
||||
observeElement = animation;
|
||||
}
|
||||
}
|
||||
|
||||
(this.byGroups[group as AnimationItemGroup] ??= []).push(animation);
|
||||
this.observer.observe(animation.el);
|
||||
this.byPlayer.set(_animation, animation);
|
||||
const item: AnimationItem = {
|
||||
el: observeElement,
|
||||
animation: animation,
|
||||
group,
|
||||
controlled
|
||||
};
|
||||
|
||||
if(animation instanceof RLottiePlayer) {
|
||||
if(!rootScope.settings.stickers.loop && animation.loop) {
|
||||
animation.loop = rootScope.settings.stickers.loop;
|
||||
}
|
||||
}
|
||||
|
||||
(this.byGroups[group as AnimationItemGroup] ??= []).push(item);
|
||||
this.observer.observe(item.el);
|
||||
this.byPlayer.set(animation, item);
|
||||
}
|
||||
|
||||
public checkAnimations(blurred?: boolean, group?: AnimationItemGroup, destroy = false) {
|
||||
public checkAnimations(
|
||||
blurred?: boolean,
|
||||
group?: AnimationItemGroup,
|
||||
destroy?: boolean,
|
||||
imitateIntersection?: boolean
|
||||
) {
|
||||
// if(rootScope.idle.isIDLE) return;
|
||||
|
||||
if(group !== undefined && !this.byGroups[group]) {
|
||||
|
@ -218,6 +231,10 @@ export class AnimationIntersector {
|
|||
const groups = group !== undefined /* && false */ ? [group] : Object.keys(this.byGroups) as AnimationItemGroup[];
|
||||
|
||||
for(const group of groups) {
|
||||
if(imitateIntersection && this.intersectionLockedGroups[group]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const animations = this.byGroups[group];
|
||||
|
||||
forEachReverse(animations, (animation) => {
|
||||
|
@ -226,11 +243,18 @@ export class AnimationIntersector {
|
|||
}
|
||||
}
|
||||
|
||||
public checkAnimation(player: AnimationItem, blurred = false, destroy = false) {
|
||||
public checkAnimations2(blurred?: boolean) {
|
||||
this.checkAnimations(blurred, undefined, undefined, true);
|
||||
}
|
||||
|
||||
public checkAnimation(player: AnimationItem, blurred?: boolean, destroy?: boolean) {
|
||||
const {el, animation, group} = player;
|
||||
// return;
|
||||
if(destroy || (!this.lockedGroups[group] && !isInDOM(el))) {
|
||||
this.removeAnimation(player);
|
||||
if(!player.controlled || destroy) {
|
||||
this.removeAnimation(player);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -295,6 +319,11 @@ export class AnimationIntersector {
|
|||
delete this.intersectionLockedGroups[group];
|
||||
this.refreshGroup(group);
|
||||
}
|
||||
|
||||
public toggleIntersectionGroup(group: AnimationItemGroup, lock: boolean) {
|
||||
if(lock) this.lockIntersectionGroup(group);
|
||||
else this.unlockIntersectionGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
const animationIntersector = new AnimationIntersector();
|
||||
|
|
|
@ -18,6 +18,7 @@ import appDownloadManager from '../lib/appManagers/appDownloadManager';
|
|||
import appImManager from '../lib/appManagers/appImManager';
|
||||
import {MyMessage} from '../lib/appManagers/appMessagesManager';
|
||||
import {MyPhoto} from '../lib/appManagers/appPhotosManager';
|
||||
import canSaveMessageMedia from '../lib/appManagers/utils/messages/canSaveMessageMedia';
|
||||
import getMediaFromMessage from '../lib/appManagers/utils/messages/getMediaFromMessage';
|
||||
import wrapRichText from '../lib/richTextProcessor/wrapRichText';
|
||||
import {MediaSearchContext} from './appMediaPlaybackController';
|
||||
|
@ -260,13 +261,13 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
const media = getMediaFromMessage(message);
|
||||
|
||||
const cantForwardMessage = message._ === 'messageService' || ! await this.managers.appMessagesManager.canForward(message);
|
||||
const cantDownloadMessage = cantForwardMessage || !canSaveMessageMedia(message);
|
||||
[this.buttons.forward, this.btnMenuForward.element].forEach((button) => {
|
||||
button.classList.toggle('hide', cantForwardMessage);
|
||||
});
|
||||
|
||||
this.wholeDiv.classList.toggle('no-forwards', cantForwardMessage);
|
||||
this.wholeDiv.classList.toggle('no-forwards', cantDownloadMessage);
|
||||
|
||||
const cantDownloadMessage = cantForwardMessage;
|
||||
[this.buttons.download, this.btnMenuDownload.element].forEach((button) => {
|
||||
button.classList.toggle('hide', cantDownloadMessage);
|
||||
});
|
||||
|
|
|
@ -468,7 +468,7 @@ export default class AppMediaViewerBase<
|
|||
|
||||
protected toggleOverlay(active: boolean) {
|
||||
overlayCounter.isOverlayActive = active;
|
||||
animationIntersector.checkAnimations(active);
|
||||
animationIntersector.checkAnimations2(active);
|
||||
}
|
||||
|
||||
protected toggleGlobalListeners(active: boolean) {
|
||||
|
@ -1370,6 +1370,10 @@ export default class AppMediaViewerBase<
|
|||
appMediaPlaybackController.addMedia(message, false, true) as HTMLVideoElement :
|
||||
*/createVideo({pip: useController});
|
||||
|
||||
if(this.wholeDiv.classList.contains('no-forwards')) {
|
||||
video.addEventListener('contextmenu', cancelEvent);
|
||||
}
|
||||
|
||||
const set = () => this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
|
||||
// return; // set and don't move
|
||||
// if(wasActive) return;
|
||||
|
|
|
@ -8,7 +8,6 @@ import type {AppImManager, ChatSavedPosition} from '../../lib/appManagers/appImM
|
|||
import type {HistoryResult, MyMessage} from '../../lib/appManagers/appMessagesManager';
|
||||
import type {MyDocument} from '../../lib/appManagers/appDocsManager';
|
||||
import type Chat from './chat';
|
||||
import {CHAT_ANIMATION_GROUP} from '../../lib/appManagers/appImManager';
|
||||
import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
|
||||
import {logger} from '../../lib/logger';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
|
@ -121,6 +120,9 @@ import wrapPhoto from '../wrappers/photo';
|
|||
import wrapPoll from '../wrappers/poll';
|
||||
import wrapVideo from '../wrappers/video';
|
||||
import isRTL from '../../helpers/string/isRTL';
|
||||
import NBSP from '../../helpers/string/nbsp';
|
||||
import DotRenderer from '../dotRenderer';
|
||||
import toHHMMSS from '../../helpers/string/toHHMMSS';
|
||||
|
||||
export const USER_REACTIONS_INLINE = false;
|
||||
const USE_MEDIA_TAILS = false;
|
||||
|
@ -264,7 +266,7 @@ export default class ChatBubbles {
|
|||
private sliceViewportDebounced: DebounceReturnType<ChatBubbles['sliceViewport']>;
|
||||
private resizeObserver: ResizeObserver;
|
||||
private willScrollOnLoad: boolean;
|
||||
private observer: SuperIntersectionObserver;
|
||||
public observer: SuperIntersectionObserver;
|
||||
|
||||
private renderingMessages: Set<number> = new Set();
|
||||
private setPeerCached: boolean;
|
||||
|
@ -280,6 +282,9 @@ export default class ChatBubbles {
|
|||
private renderNewPromises: Set<Promise<any>> = new Set();
|
||||
private updateGradient: boolean;
|
||||
|
||||
private extendedMediaMessages: Set<number> = new Set();
|
||||
private pollExtendedMediaMessagesPromise: Promise<void>;
|
||||
|
||||
// private reactions: Map<number, ReactionsElement>;
|
||||
|
||||
constructor(
|
||||
|
@ -1030,22 +1035,6 @@ export default class ChatBubbles {
|
|||
|
||||
this.observer = new SuperIntersectionObserver({root: this.scrollable.container});
|
||||
|
||||
this.listenerSetter.add(this.chat.appImManager)('chat_changing', ({to}) => {
|
||||
const freeze = to !== this.chat;
|
||||
|
||||
const cb = () => {
|
||||
this.observer.toggleObservingNew(freeze);
|
||||
};
|
||||
|
||||
if(!freeze) {
|
||||
setTimeout(() => {
|
||||
cb();
|
||||
}, 400);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
|
||||
this.sendViewCountersDebounced = debounce(() => {
|
||||
const mids = [...this.viewsMids];
|
||||
this.viewsMids.clear();
|
||||
|
@ -1275,7 +1264,7 @@ export default class ChatBubbles {
|
|||
height: 18,
|
||||
needUpscale: true,
|
||||
middleware,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
group: this.chat.animationGroup,
|
||||
withThumb: false,
|
||||
needFadeIn: false
|
||||
}).then(({render}) => render).then((player) => {
|
||||
|
@ -1646,7 +1635,7 @@ export default class ChatBubbles {
|
|||
const message = await this.managers.appMessagesManager.getMessageByPeer(peerId.toPeerId(), +mid);
|
||||
if(message) {
|
||||
const inputInvoice = await this.managers.appPaymentsManager.getInputInvoiceByPeerId(this.peerId, +bubble.dataset.mid);
|
||||
new PopupPayment(message as Message.message, inputInvoice);
|
||||
new PopupPayment(message as Message.message, inputInvoice, undefined, true);
|
||||
}
|
||||
} else {
|
||||
this.chat.appImManager.setInnerPeer({
|
||||
|
@ -2206,7 +2195,7 @@ export default class ChatBubbles {
|
|||
this.chat.selection.deleteSelectedMids(this.peerId, mids);
|
||||
}
|
||||
|
||||
animationIntersector.checkAnimations(false, CHAT_ANIMATION_GROUP);
|
||||
animationIntersector.checkAnimations(false, this.chat.animationGroup);
|
||||
this.deleteEmptyDateGroups();
|
||||
|
||||
if(!ignoreOnScroll) {
|
||||
|
@ -2215,6 +2204,21 @@ export default class ChatBubbles {
|
|||
}
|
||||
}
|
||||
|
||||
private pollExtendedMediaMessages() {
|
||||
const mids = Array.from(this.extendedMediaMessages);
|
||||
return this.managers.appMessagesManager.getExtendedMedia(this.peerId, mids);
|
||||
}
|
||||
|
||||
private setExtendedMediaMessagesPollInterval() {
|
||||
if(this.pollExtendedMediaMessagesPromise || !this.extendedMediaMessages.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pollExtendedMediaMessagesPromise = pause(30000)
|
||||
.then(() => this.pollExtendedMediaMessages())
|
||||
.then(() => this.setExtendedMediaMessagesPollInterval());
|
||||
}
|
||||
|
||||
private setTopPadding(middleware = this.getMiddleware()) {
|
||||
let isPaddingNeeded = false;
|
||||
let setPaddingTo: HTMLElement;
|
||||
|
@ -2875,7 +2879,7 @@ export default class ChatBubbles {
|
|||
/* this.ladderDeferred && this.ladderDeferred.resolve();
|
||||
this.ladderDeferred = deferredPromise<void>(); */
|
||||
|
||||
animationIntersector.lockGroup(CHAT_ANIMATION_GROUP);
|
||||
animationIntersector.lockGroup(this.chat.animationGroup);
|
||||
const setPeerPromise = m(promise).then(async() => {
|
||||
log.warn('promise fulfilled');
|
||||
|
||||
|
@ -2919,8 +2923,8 @@ export default class ChatBubbles {
|
|||
|
||||
log.warn('mounted chat', this.chatInner === chatInner, this.chatInner.parentElement, performance.now() - perf);
|
||||
|
||||
animationIntersector.unlockGroup(CHAT_ANIMATION_GROUP);
|
||||
animationIntersector.checkAnimations(false, CHAT_ANIMATION_GROUP/* , true */);
|
||||
animationIntersector.unlockGroup(this.chat.animationGroup);
|
||||
animationIntersector.checkAnimations(false, this.chat.animationGroup/* , true */);
|
||||
|
||||
// fastRaf(() => {
|
||||
this.lazyLoadQueue.unlock();
|
||||
|
@ -3278,9 +3282,13 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
const item = this.bubbleGroups.getItemByBubble(bubble);
|
||||
item.mounted = false;
|
||||
if(!groups.includes(item.group)) {
|
||||
groups.push(item.group);
|
||||
if(!item) {
|
||||
this.log.error('NO ITEM BY BUBBLE', bubble);
|
||||
} else {
|
||||
item.mounted = false;
|
||||
if(!groups.includes(item.group)) {
|
||||
groups.push(item.group);
|
||||
}
|
||||
}
|
||||
|
||||
this.bubblesToReplace.delete(bubble);
|
||||
|
@ -3621,7 +3629,7 @@ export default class ChatBubbles {
|
|||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
customEmojiSize,
|
||||
middleware,
|
||||
animationGroup: CHAT_ANIMATION_GROUP
|
||||
animationGroup: this.chat.animationGroup
|
||||
});
|
||||
|
||||
let canHaveTail = true;
|
||||
|
@ -3690,14 +3698,16 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
const replyMarkup = isMessage && message.reply_markup;
|
||||
if(replyMarkup && replyMarkup._ === 'replyInlineMarkup' && replyMarkup.rows && replyMarkup.rows.length) {
|
||||
const rows = replyMarkup.rows;
|
||||
let replyMarkupRows = replyMarkup?._ === 'replyInlineMarkup' && replyMarkup.rows;
|
||||
if(replyMarkupRows) {
|
||||
replyMarkupRows = replyMarkupRows.filter((row) => row.buttons.length);
|
||||
}
|
||||
|
||||
if(replyMarkupRows) {
|
||||
const containerDiv = document.createElement('div');
|
||||
containerDiv.classList.add('reply-markup');
|
||||
rows.forEach((row) => {
|
||||
replyMarkupRows.forEach((row) => {
|
||||
const buttons = row.buttons;
|
||||
if(!buttons || !buttons.length) return;
|
||||
|
||||
const rowDiv = document.createElement('div');
|
||||
rowDiv.classList.add('reply-markup-row');
|
||||
|
@ -3761,13 +3771,16 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
case 'keyboardButtonBuy': {
|
||||
const mediaInvoice = messageMedia._ === 'messageMediaInvoice' ? messageMedia : undefined;
|
||||
if(mediaInvoice?.extended_media) {
|
||||
break;
|
||||
}
|
||||
|
||||
buttonEl = document.createElement('button');
|
||||
buttonEl.classList.add('is-buy');
|
||||
|
||||
if(messageMedia?._ === 'messageMediaInvoice') {
|
||||
if(messageMedia.receipt_msg_id) {
|
||||
text = i18n('Message.ReplyActionButtonShowReceipt');
|
||||
}
|
||||
if(mediaInvoice?.receipt_msg_id) {
|
||||
text = i18n('Message.ReplyActionButtonShowReceipt');
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -3779,6 +3792,10 @@ export default class ChatBubbles {
|
|||
}
|
||||
}
|
||||
|
||||
if(!buttonEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
buttonEl.classList.add('reply-markup-button', 'rp', 'tgico');
|
||||
if(typeof(text) === 'string') {
|
||||
buttonEl.insertAdjacentHTML('beforeend', text);
|
||||
|
@ -3791,10 +3808,16 @@ export default class ChatBubbles {
|
|||
rowDiv.append(buttonEl);
|
||||
});
|
||||
|
||||
if(!rowDiv.childElementCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
containerDiv.append(rowDiv);
|
||||
});
|
||||
|
||||
attachClickEvent(containerDiv, (e) => {
|
||||
const haveButtons = !!containerDiv.childElementCount;
|
||||
|
||||
haveButtons && attachClickEvent(containerDiv, (e) => {
|
||||
let target = e.target as HTMLElement;
|
||||
|
||||
if(!target.classList.contains('reply-markup-button')) target = findUpClassName(target, 'reply-markup-button');
|
||||
|
@ -3808,7 +3831,7 @@ export default class ChatBubbles {
|
|||
cancelEvent(e);
|
||||
|
||||
const column = whichChild(target);
|
||||
const row = rows[whichChild(target.parentElement)];
|
||||
const row = replyMarkupRows[whichChild(target.parentElement)];
|
||||
|
||||
if(!row.buttons || !row.buttons[column]) {
|
||||
this.log.warn('no such button', row, column, message);
|
||||
|
@ -3825,9 +3848,11 @@ export default class ChatBubbles {
|
|||
});
|
||||
});
|
||||
|
||||
canHaveTail = false;
|
||||
bubble.classList.add('with-reply-markup');
|
||||
contentWrapper.append(containerDiv);
|
||||
if(haveButtons) {
|
||||
canHaveTail = false;
|
||||
bubble.classList.add('with-reply-markup');
|
||||
contentWrapper.append(containerDiv);
|
||||
}
|
||||
}
|
||||
|
||||
const isOutgoing = message.pFlags.is_outgoing/* && this.peerId !== rootScope.myId */;
|
||||
|
@ -3968,7 +3993,7 @@ export default class ChatBubbles {
|
|||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware: this.getMiddleware(),
|
||||
isOut,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
group: this.chat.animationGroup,
|
||||
loadPromises,
|
||||
autoDownload: this.chat.autoDownload,
|
||||
noInfo: message.mid < 0
|
||||
|
@ -4115,7 +4140,7 @@ export default class ChatBubbles {
|
|||
div: attachmentDiv,
|
||||
middleware,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
group: this.chat.animationGroup,
|
||||
// play: !!message.pending || !multipleRender,
|
||||
play: true,
|
||||
loop: true,
|
||||
|
@ -4172,7 +4197,7 @@ export default class ChatBubbles {
|
|||
isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
group: this.chat.animationGroup,
|
||||
loadPromises,
|
||||
autoDownload: this.chat.autoDownload,
|
||||
searchContext: isRound ? {
|
||||
|
@ -4329,55 +4354,135 @@ export default class ChatBubbles {
|
|||
|
||||
case 'messageMediaInvoice': {
|
||||
const isTest = messageMedia.pFlags.test;
|
||||
const photo = messageMedia.photo;
|
||||
const extendedMedia = messageMedia.extended_media;
|
||||
const isAlreadyPaid = extendedMedia?._ === 'messageExtendedMedia';
|
||||
const isNotPaid = extendedMedia?._ === 'messageExtendedMediaPreview';
|
||||
let innerMedia = isAlreadyPaid ?
|
||||
(extendedMedia.media as MessageMedia.messageMediaPhoto).photo as Photo.photo ||
|
||||
(extendedMedia.media as MessageMedia.messageMediaDocument).document as Document.document :
|
||||
messageMedia.photo;
|
||||
|
||||
const priceEl = document.createElement(photo ? 'span' : 'div');
|
||||
const f = document.createDocumentFragment();
|
||||
const l = i18n(messageMedia.receipt_msg_id ? 'PaymentReceipt' : (isTest ? 'PaymentTestInvoice' : 'PaymentInvoice'));
|
||||
l.classList.add('text-uppercase');
|
||||
const joiner = ' ';
|
||||
const p = document.createElement('span');
|
||||
p.classList.add('text-bold');
|
||||
p.textContent = paymentsWrapCurrencyAmount(messageMedia.total_amount, messageMedia.currency) + joiner;
|
||||
f.append(p, l);
|
||||
if(isTest && messageMedia.receipt_msg_id) {
|
||||
const a = document.createElement('span');
|
||||
a.classList.add('text-uppercase', 'pre-wrap');
|
||||
a.append(joiner + '(Test)');
|
||||
f.append(a);
|
||||
const wrappedPrice = paymentsWrapCurrencyAmount(messageMedia.total_amount, messageMedia.currency);
|
||||
let priceEl: HTMLElement;
|
||||
if(!extendedMedia) {
|
||||
priceEl = document.createElement(innerMedia ? 'span' : 'div');
|
||||
const f = document.createDocumentFragment();
|
||||
const l = i18n(messageMedia.receipt_msg_id ? 'PaymentReceipt' : (isTest ? 'PaymentTestInvoice' : 'PaymentInvoice'));
|
||||
l.classList.add('text-uppercase');
|
||||
const joiner = ' ' + NBSP;
|
||||
const p = document.createElement('span');
|
||||
p.classList.add('text-bold');
|
||||
p.textContent = wrappedPrice + joiner;
|
||||
f.append(p, l);
|
||||
if(isTest && messageMedia.receipt_msg_id) {
|
||||
const a = document.createElement('span');
|
||||
a.classList.add('text-uppercase', 'pre-wrap');
|
||||
a.append(joiner + '(Test)');
|
||||
f.append(a);
|
||||
}
|
||||
setInnerHTML(priceEl, f);
|
||||
} else if(isNotPaid) {
|
||||
priceEl = document.createElement('span');
|
||||
priceEl.classList.add('extended-media-buy', 'tgico-premium_lock');
|
||||
attachmentDiv.classList.add('is-buy');
|
||||
_i18n(priceEl, 'Checkout.PayPrice', [wrappedPrice]);
|
||||
|
||||
if(extendedMedia.video_duration !== undefined) {
|
||||
const videoTime = document.createElement('span');
|
||||
videoTime.classList.add('video-time');
|
||||
videoTime.textContent = toHHMMSS(extendedMedia.video_duration, false);
|
||||
attachmentDiv.append(videoTime);
|
||||
}
|
||||
}
|
||||
setInnerHTML(priceEl, f);
|
||||
|
||||
if(photo) {
|
||||
const mediaSize = mediaSizes.active.invoice;
|
||||
wrapPhoto({
|
||||
photo,
|
||||
container: attachmentDiv,
|
||||
withTail: false,
|
||||
isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware,
|
||||
loadPromises,
|
||||
boxWidth: mediaSize.width,
|
||||
boxHeight: mediaSize.height
|
||||
});
|
||||
if(isNotPaid) {
|
||||
(extendedMedia.thumb as PhotoSize.photoStrippedSize).w = extendedMedia.w;
|
||||
(extendedMedia.thumb as PhotoSize.photoStrippedSize).h = extendedMedia.h;
|
||||
innerMedia = {
|
||||
_: 'photo',
|
||||
access_hash: '',
|
||||
pFlags: {},
|
||||
date: 0,
|
||||
dc_id: 0,
|
||||
file_reference: [],
|
||||
id: 0,
|
||||
sizes: [extendedMedia.thumb]
|
||||
};
|
||||
}
|
||||
|
||||
bubble.classList.add('photo');
|
||||
if(innerMedia) {
|
||||
const mediaSize = extendedMedia ? mediaSizes.active.extendedInvoice : mediaSizes.active.invoice;
|
||||
if(innerMedia._ === 'document') {
|
||||
wrapVideo({
|
||||
doc: innerMedia,
|
||||
container: attachmentDiv,
|
||||
withTail: false,
|
||||
isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware,
|
||||
loadPromises,
|
||||
boxWidth: mediaSize.width,
|
||||
boxHeight: mediaSize.height,
|
||||
group: this.chat.animationGroup,
|
||||
message: message as Message.message
|
||||
});
|
||||
bubble.classList.add('video');
|
||||
} else {
|
||||
wrapPhoto({
|
||||
photo: innerMedia,
|
||||
container: attachmentDiv,
|
||||
withTail: false,
|
||||
isOut,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
middleware,
|
||||
loadPromises,
|
||||
boxWidth: mediaSize.width,
|
||||
boxHeight: mediaSize.height,
|
||||
message: isAlreadyPaid ? message : undefined
|
||||
});
|
||||
bubble.classList.add('photo');
|
||||
}
|
||||
|
||||
priceEl.classList.add('video-time');
|
||||
attachmentDiv.append(priceEl);
|
||||
if(priceEl) {
|
||||
if(!extendedMedia) {
|
||||
priceEl.classList.add('video-time');
|
||||
}
|
||||
|
||||
attachmentDiv.append(priceEl);
|
||||
}
|
||||
} else {
|
||||
attachmentDiv = undefined;
|
||||
}
|
||||
|
||||
const titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('bubble-primary-color');
|
||||
setInnerHTML(titleDiv, wrapEmojiText(messageMedia.title));
|
||||
if(isNotPaid) {
|
||||
const {mid} = message;
|
||||
this.extendedMediaMessages.add(mid);
|
||||
middleware.onClean(() => {
|
||||
this.extendedMediaMessages.delete(mid);
|
||||
animationIntersector.removeAnimationByPlayer(dotRenderer);
|
||||
});
|
||||
this.setExtendedMediaMessagesPollInterval();
|
||||
|
||||
const richText = wrapEmojiText(messageMedia.description);
|
||||
messageDiv.prepend(...[titleDiv, !photo && priceEl, richText].filter(Boolean));
|
||||
const {width, height} = attachmentDiv.style;
|
||||
const dotRenderer = new DotRenderer(parseInt(width), parseInt(height));
|
||||
dotRenderer.renderFirstFrame();
|
||||
attachmentDiv.append(dotRenderer.canvas);
|
||||
|
||||
bubble.classList.remove('is-message-empty');
|
||||
animationIntersector.addAnimation(dotRenderer, this.chat.animationGroup, dotRenderer.canvas, true);
|
||||
}
|
||||
|
||||
let titleDiv: HTMLElement;
|
||||
if(!extendedMedia) {
|
||||
titleDiv = document.createElement('div');
|
||||
titleDiv.classList.add('bubble-primary-color');
|
||||
setInnerHTML(titleDiv, wrapEmojiText(messageMedia.title));
|
||||
}
|
||||
|
||||
const richText = isAlreadyPaid ? undefined : wrapEmojiText(messageMedia.description);
|
||||
messageDiv.prepend(...[titleDiv, !innerMedia && priceEl, richText].filter(Boolean));
|
||||
|
||||
if(!richText) canHaveTail = false;
|
||||
else bubble.classList.remove('is-message-empty');
|
||||
bubble.classList.add('is-invoice');
|
||||
|
||||
break;
|
||||
|
@ -5028,7 +5133,7 @@ export default class ChatBubbles {
|
|||
div: stickerDiv,
|
||||
middleware,
|
||||
lazyLoadQueue: this.lazyLoadQueue,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
group: this.chat.animationGroup,
|
||||
// play: !!message.pending || !multipleRender,
|
||||
play: true,
|
||||
loop: true,
|
||||
|
|
|
@ -35,6 +35,7 @@ import noop from '../../helpers/noop';
|
|||
import middlewarePromise from '../../helpers/middlewarePromise';
|
||||
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
|
||||
import {Message} from '../../layer';
|
||||
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
|
||||
|
||||
export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled';
|
||||
|
||||
|
@ -86,6 +87,8 @@ export default class Chat extends EventListenerBase<{
|
|||
public isAnyGroup: boolean;
|
||||
public isMegagroup: boolean;
|
||||
|
||||
public animationGroup: AnimationItemGroup;
|
||||
|
||||
constructor(
|
||||
public appImManager: AppImManager,
|
||||
public managers: AppManagers
|
||||
|
@ -93,6 +96,7 @@ export default class Chat extends EventListenerBase<{
|
|||
super();
|
||||
|
||||
this.type = 'chat';
|
||||
this.animationGroup = `chat-${Math.round(Math.random() * 65535)}`;
|
||||
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('chat', 'tabs-tab');
|
||||
|
@ -352,6 +356,26 @@ export default class Chat extends EventListenerBase<{
|
|||
this.appImManager.setPeer();
|
||||
}
|
||||
});
|
||||
|
||||
this.bubbles.listenerSetter.add(this.appImManager)('chat_changing', ({to}) => {
|
||||
const freeze = to !== this;
|
||||
|
||||
const cb = () => {
|
||||
this.bubbles.observer.toggleObservingNew(freeze);
|
||||
animationIntersector.toggleIntersectionGroup(this.animationGroup, freeze);
|
||||
if(freeze) {
|
||||
animationIntersector.checkAnimations(freeze, this.animationGroup);
|
||||
}
|
||||
};
|
||||
|
||||
if(!freeze) {
|
||||
setTimeout(() => {
|
||||
cb();
|
||||
}, 400);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public beforeDestroy() {
|
||||
|
|
|
@ -45,6 +45,8 @@ import replaceContent from '../../helpers/dom/replaceContent';
|
|||
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
|
||||
import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
|
||||
import PopupStickers from '../popups/stickers';
|
||||
import getMediaFromMessage from '../../lib/appManagers/utils/messages/getMediaFromMessage';
|
||||
import canSaveMessageMedia from '../../lib/appManagers/utils/messages/canSaveMessageMedia';
|
||||
|
||||
export default class ChatContextMenu {
|
||||
private buttons: (ButtonMenuItemOptions & {verify: () => boolean | Promise<boolean>, notDirect?: () => boolean, withSelection?: true, isSponsored?: true, localName?: 'views' | 'emojis'})[];
|
||||
|
@ -432,10 +434,10 @@ export default class ChatContextMenu {
|
|||
icon: 'download',
|
||||
text: 'MediaViewer.Context.Download',
|
||||
onClick: () => {
|
||||
appDownloadManager.downloadToDisc({media: (this.message as any).media?.document || (this.message as any).media.photo});
|
||||
appDownloadManager.downloadToDisc({media: getMediaFromMessage(this.message)});
|
||||
},
|
||||
verify: () => {
|
||||
if(this.message.pFlags.is_outgoing || this.noForwards) {
|
||||
if(!canSaveMessageMedia(this.message) || this.noForwards) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -620,7 +620,7 @@ export default class ChatInput {
|
|||
|
||||
this.rowsWrapper.append(this.replyElements.container);
|
||||
this.autocompleteHelperController = new AutocompleteHelperController();
|
||||
this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController, this.managers);
|
||||
this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController, this.chat, this.managers);
|
||||
this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.managers);
|
||||
this.commandsHelper = new CommandsHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.managers);
|
||||
this.mentionsHelper = new MentionsHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.managers);
|
||||
|
@ -2786,7 +2786,7 @@ export default class ChatInput {
|
|||
if(!message) { // load missing replying message
|
||||
peerTitleEl = i18n('Loading');
|
||||
|
||||
this.managers.appMessagesManager.wrapSingleMessage(this.chat.peerId, mid).then((_message) => {
|
||||
this.managers.appMessagesManager.reloadMessages(this.chat.peerId, mid).then((_message) => {
|
||||
if(this.replyToMsgId !== mid) {
|
||||
return;
|
||||
}
|
||||
|
@ -2880,7 +2880,7 @@ export default class ChatInput {
|
|||
const haveReply = oldReply.classList.contains('reply');
|
||||
|
||||
this.replyElements.iconBtn.replaceWith(this.replyElements.iconBtn = ButtonIcon((type === 'webpage' ? 'link' : type) + ' active reply-icon', {noRipple: true}));
|
||||
const {container} = wrapReply(title, subtitle, message);
|
||||
const {container} = wrapReply(title, subtitle, this.chat.animationGroup, message);
|
||||
if(haveReply) {
|
||||
oldReply.replaceWith(container);
|
||||
} else {
|
||||
|
|
|
@ -205,7 +205,7 @@ export namespace MessageRender {
|
|||
}).element;
|
||||
}
|
||||
|
||||
const {container, fillPromise} = wrapReply(originalPeerTitle, undefined, originalMessage, chat.isAnyGroup ? titlePeerId : undefined);
|
||||
const {container, fillPromise} = wrapReply(originalPeerTitle, undefined, chat.animationGroup, originalMessage, chat.isAnyGroup ? titlePeerId : undefined);
|
||||
await fillPromise;
|
||||
if(currentReplyDiv) {
|
||||
currentReplyDiv.replaceWith(container);
|
||||
|
|
|
@ -266,7 +266,7 @@ export default class ChatPinnedMessage {
|
|||
this.debug = true;
|
||||
this.isStatic = false;
|
||||
|
||||
const dAC = new ReplyContainer('pinned-message');
|
||||
const dAC = new ReplyContainer('pinned-message', chat.animationGroup);
|
||||
this.pinnedMessageContainer = new PinnedContainer({
|
||||
topbar,
|
||||
chat,
|
||||
|
@ -624,7 +624,8 @@ export default class ChatPinnedMessage {
|
|||
subtitleEl: writeTo,
|
||||
message,
|
||||
mediaEl: writeMediaTo,
|
||||
loadPromises
|
||||
loadPromises,
|
||||
animationGroup: this.chat.animationGroup
|
||||
});
|
||||
|
||||
await Promise.all(loadPromises);
|
||||
|
|
|
@ -8,7 +8,7 @@ import replaceContent from '../../helpers/dom/replaceContent';
|
|||
import {Middleware} from '../../helpers/middleware';
|
||||
import limitSymbols from '../../helpers/string/limitSymbols';
|
||||
import {Document, MessageMedia, Photo, WebPage} from '../../layer';
|
||||
import appImManager, {CHAT_ANIMATION_GROUP} from '../../lib/appManagers/appImManager';
|
||||
import appImManager from '../../lib/appManagers/appImManager';
|
||||
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
|
||||
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
|
||||
import DivAndCaption from '../divAndCaption';
|
||||
|
@ -16,6 +16,7 @@ import wrapMessageForReply from '../wrappers/messageForReply';
|
|||
import wrapPhoto from '../wrappers/photo';
|
||||
import wrapSticker from '../wrappers/sticker';
|
||||
import wrapVideo from '../wrappers/video';
|
||||
import {AnimationItemGroup} from '../animationIntersector';
|
||||
|
||||
const MEDIA_SIZE = 32;
|
||||
|
||||
|
@ -26,9 +27,10 @@ export async function wrapReplyDivAndCaption(options: {
|
|||
subtitleEl: HTMLElement,
|
||||
message: any,
|
||||
mediaEl: HTMLElement,
|
||||
loadPromises?: Promise<any>[]
|
||||
loadPromises?: Promise<any>[],
|
||||
animationGroup: AnimationItemGroup
|
||||
}) {
|
||||
let {title, titleEl, subtitle, subtitleEl, mediaEl, message, loadPromises} = options;
|
||||
let {title, titleEl, subtitle, subtitleEl, mediaEl, message, loadPromises, animationGroup} = options;
|
||||
if(title !== undefined) {
|
||||
if(typeof(title) === 'string') {
|
||||
title = limitSymbols(title, 140);
|
||||
|
@ -62,7 +64,7 @@ export async function wrapReplyDivAndCaption(options: {
|
|||
doc: document,
|
||||
div: mediaEl,
|
||||
lazyLoadQueue,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
group: animationGroup,
|
||||
// onlyThumb: document.sticker === 2,
|
||||
width: MEDIA_SIZE,
|
||||
height: MEDIA_SIZE,
|
||||
|
@ -84,7 +86,7 @@ export async function wrapReplyDivAndCaption(options: {
|
|||
loadPromises,
|
||||
withoutPreloader: true,
|
||||
videoSize: document.video_thumbs[0],
|
||||
group: CHAT_ANIMATION_GROUP
|
||||
group: animationGroup
|
||||
});
|
||||
} else {
|
||||
const m = photo || document;
|
||||
|
@ -138,7 +140,7 @@ export async function wrapReplyDivAndCaption(options: {
|
|||
export default class ReplyContainer extends DivAndCaption<(title: string | HTMLElement | DocumentFragment, subtitle: string | HTMLElement | DocumentFragment, message?: any) => Promise<void>> {
|
||||
private mediaEl: HTMLElement;
|
||||
|
||||
constructor(protected className: string) {
|
||||
constructor(protected className: string, protected animationGroup: AnimationItemGroup) {
|
||||
super(className, async(title, subtitle = '', message?) => {
|
||||
if(!this.mediaEl) {
|
||||
this.mediaEl = document.createElement('div');
|
||||
|
@ -151,7 +153,8 @@ export default class ReplyContainer extends DivAndCaption<(title: string | HTMLE
|
|||
subtitle,
|
||||
subtitleEl: this.subtitle,
|
||||
mediaEl: this.mediaEl,
|
||||
message
|
||||
message,
|
||||
animationGroup
|
||||
});
|
||||
|
||||
this.container.classList.toggle('is-media', isMediaSet);
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import type Chat from './chat';
|
||||
import ListenerSetter from '../../helpers/listenerSetter';
|
||||
import mediaSizes from '../../helpers/mediaSizes';
|
||||
import preloadAnimatedEmojiSticker from '../../helpers/preloadAnimatedEmojiSticker';
|
||||
import {MyDocument} from '../../lib/appManagers/appDocsManager';
|
||||
import {CHAT_ANIMATION_GROUP} from '../../lib/appManagers/appImManager';
|
||||
import {AppManagers} from '../../lib/appManagers/managers';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import {EmoticonsDropdown} from '../emoticonsDropdown';
|
||||
|
@ -29,6 +29,7 @@ export default class StickersHelper extends AutocompleteHelper {
|
|||
constructor(
|
||||
appendTo: HTMLElement,
|
||||
controller: AutocompleteHelperController,
|
||||
private chat: Chat,
|
||||
private managers: AppManagers
|
||||
) {
|
||||
super({
|
||||
|
@ -132,6 +133,6 @@ export default class StickersHelper extends AutocompleteHelper {
|
|||
|
||||
this.scrollable = new Scrollable(this.container);
|
||||
this.lazyLoadQueue = new LazyLoadQueue();
|
||||
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP, this.managers);
|
||||
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, this.chat.animationGroup, this.managers);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import {IS_MOBILE} from '../environment/userAgent';
|
||||
import {animate} from '../helpers/animation';
|
||||
import drawCircle, {drawCircleFromStart} from '../helpers/canvas/drawCircle';
|
||||
import clamp from '../helpers/number/clamp';
|
||||
import {AnimationItemWrapper} from './animationIntersector';
|
||||
|
||||
type DotRendererDot = {
|
||||
x: number,
|
||||
y: number,
|
||||
opacity: number,
|
||||
radius: number
|
||||
mOpacity: number,
|
||||
adding: boolean,
|
||||
counter: number,
|
||||
path: Path2D
|
||||
};
|
||||
export default class DotRenderer implements AnimationItemWrapper {
|
||||
public canvas: HTMLCanvasElement;
|
||||
private context: CanvasRenderingContext2D;
|
||||
private dots: DotRendererDot[];
|
||||
|
||||
public paused: boolean;
|
||||
public autoplay: boolean;
|
||||
public tempId: number;
|
||||
|
||||
private dpr: number;
|
||||
|
||||
constructor(private width: number, private height: number) {
|
||||
const canvas = this.canvas = document.createElement('canvas');
|
||||
const dpr = this.dpr = window.devicePixelRatio;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
canvas.style.cssText = `position: absolute; width: 100%; height: 100%; z-index: 1;`;
|
||||
|
||||
this.paused = true;
|
||||
this.autoplay = true;
|
||||
this.tempId = 0;
|
||||
this.context = canvas.getContext('2d');
|
||||
}
|
||||
|
||||
private prepare() {
|
||||
let count = Math.round(this.width * this.height / (35 * (IS_MOBILE ? 2 : 1)));
|
||||
count = Math.min(IS_MOBILE ? 1000 : 2200, count);
|
||||
const dots: DotRendererDot[] = this.dots = new Array(count);
|
||||
|
||||
for(let i = 0; i < count; ++i) {
|
||||
dots[i] = this.generateDot();
|
||||
}
|
||||
}
|
||||
|
||||
private generateDot(adding?: boolean): DotRendererDot {
|
||||
const x = Math.floor(Math.random() * this.canvas.width);
|
||||
const y = Math.floor(Math.random() * this.canvas.height);
|
||||
const opacity = adding ? 0 : Math.random();
|
||||
const radius = (Math.random() >= .8 ? 1 : 0.5) * this.dpr;
|
||||
const path = new Path2D();
|
||||
path.arc(x, y, radius, 0, 2 * Math.PI, false);
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
opacity,
|
||||
radius,
|
||||
mOpacity: opacity,
|
||||
adding: adding ?? Math.random() >= .5,
|
||||
counter: 0,
|
||||
path
|
||||
};
|
||||
}
|
||||
|
||||
private draw() {
|
||||
const {context, canvas, dots} = this;
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
context.fillStyle = '#fff';
|
||||
|
||||
const add = 0.02;
|
||||
for(let i = 0, length = dots.length; i < length; ++i) {
|
||||
const dot = dots[i];
|
||||
const addOpacity = dot.adding ? add : -add;
|
||||
|
||||
dot.mOpacity += addOpacity;
|
||||
// if(dot.mOpacity <= 0) dot.mOpacity = dot.opacity;
|
||||
|
||||
// const easedOpacity = easing(dot.mOpacity);
|
||||
const easedOpacity = clamp(dot.mOpacity, 0, 1);
|
||||
context.globalAlpha = easedOpacity;
|
||||
context.fill(dot.path);
|
||||
|
||||
if(dot.mOpacity <= 0) {
|
||||
dot.adding = true;
|
||||
|
||||
if(++dot.counter >= 1) {
|
||||
dots[i] = this.generateDot(dot.adding);
|
||||
}
|
||||
} else if(dot.mOpacity >= 1) {
|
||||
dot.adding = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public remove() {
|
||||
this.pause();
|
||||
}
|
||||
|
||||
public pause() {
|
||||
if(this.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.paused = true;
|
||||
++this.tempId;
|
||||
}
|
||||
|
||||
public renderFirstFrame() {
|
||||
if(!this.dots) {
|
||||
this.prepare();
|
||||
}
|
||||
|
||||
this.draw();
|
||||
}
|
||||
|
||||
public play() {
|
||||
if(!this.paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.paused = false;
|
||||
const tempId = ++this.tempId;
|
||||
|
||||
if(!this.dots) {
|
||||
this.prepare();
|
||||
}
|
||||
|
||||
animate(() => {
|
||||
if(this.tempId !== tempId || this.paused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.draw();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -101,7 +101,7 @@ export class SuperStickerRenderer {
|
|||
const players = animationIntersector.getAnimations(element);
|
||||
players.forEach((player) => {
|
||||
if(!visible) {
|
||||
animationIntersector.checkAnimation(player, true, true);
|
||||
animationIntersector.removeAnimation(player);
|
||||
} else {
|
||||
animationIntersector.checkAnimation(player, false);
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ export default class GifsMasonry {
|
|||
video.load();
|
||||
const animations = animationIntersector.getAnimations(video);
|
||||
animations.forEach((item) => {
|
||||
animationIntersector.checkAnimation(item, true, true);
|
||||
animationIntersector.removeAnimation(item);
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ export default class GifsMasonry {
|
|||
video.load();
|
||||
const animations = animationIntersector.getAnimations(video);
|
||||
animations.forEach((item) => {
|
||||
animationIntersector.checkAnimation(item, true, true);
|
||||
animationIntersector.removeAnimation(item);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -409,7 +409,7 @@ export default class PopupGroupCall extends PopupElement {
|
|||
this.btnClose.classList.toggle('hide', isFull);
|
||||
|
||||
if(isFull !== wasFullScreen) {
|
||||
animationIntersector.checkAnimations(isFull);
|
||||
animationIntersector.checkAnimations2(isFull);
|
||||
|
||||
themeController.setThemeColor(isFull ? '#000000' : undefined);
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ export default class PopupElement<T extends EventListenerListeners = {}> extends
|
|||
|
||||
if(!this.withoutOverlay) {
|
||||
overlayCounter.isOverlayActive = true;
|
||||
animationIntersector.checkAnimations(true);
|
||||
animationIntersector.checkAnimations2(true);
|
||||
}
|
||||
|
||||
// cannot add event instantly because keydown propagation will fire it
|
||||
|
@ -292,7 +292,7 @@ export default class PopupElement<T extends EventListenerListeners = {}> extends
|
|||
this.cleanup();
|
||||
|
||||
if(!this.withoutOverlay) {
|
||||
animationIntersector.checkAnimations(false);
|
||||
animationIntersector.checkAnimations2(false);
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
|
|
@ -105,7 +105,8 @@ export default class PopupPayment extends PopupElement {
|
|||
constructor(
|
||||
private message: Message.message,
|
||||
private inputInvoice: InputInvoice,
|
||||
private paymentForm?: PaymentsPaymentForm | PaymentsPaymentReceipt
|
||||
private paymentForm?: PaymentsPaymentForm | PaymentsPaymentReceipt,
|
||||
private isReceipt?: boolean
|
||||
) {
|
||||
super('popup-payment', {
|
||||
closable: true,
|
||||
|
@ -116,7 +117,10 @@ export default class PopupPayment extends PopupElement {
|
|||
});
|
||||
|
||||
this.tipButtonsMap = new Map();
|
||||
this.d();
|
||||
this.d().catch((err) => {
|
||||
console.error('payment popup error', err);
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
private async d() {
|
||||
|
@ -148,7 +152,12 @@ export default class PopupPayment extends PopupElement {
|
|||
}
|
||||
|
||||
const mediaInvoice = message?.media as MessageMedia.messageMediaInvoice;
|
||||
const isReceipt = mediaInvoice ? !!mediaInvoice.receipt_msg_id : paymentForm._ === 'payments.paymentReceipt';
|
||||
const isReceipt = this.isReceipt ??
|
||||
(
|
||||
mediaInvoice ?
|
||||
!!mediaInvoice.receipt_msg_id || mediaInvoice.extended_media?._ === 'messageExtendedMedia' :
|
||||
paymentForm._ === 'payments.paymentReceipt'
|
||||
);
|
||||
const isTest = mediaInvoice ? mediaInvoice.pFlags.test : paymentForm.invoice.pFlags.test;
|
||||
|
||||
const photo = mediaInvoice ? mediaInvoice.photo : paymentForm.photo;
|
||||
|
@ -215,7 +224,7 @@ export default class PopupPayment extends PopupElement {
|
|||
|
||||
const inputInvoice = this.inputInvoice;
|
||||
if(!paymentForm) {
|
||||
if(isReceipt) paymentForm = await this.managers.appPaymentsManager.getPaymentReceipt(message.peerId, mediaInvoice.receipt_msg_id);
|
||||
if(isReceipt) paymentForm = await this.managers.appPaymentsManager.getPaymentReceipt(message.peerId, mediaInvoice.receipt_msg_id || (inputInvoice as InputInvoice.inputInvoiceMessage).msg_id);
|
||||
else paymentForm = await this.managers.appPaymentsManager.getPaymentForm(inputInvoice);
|
||||
this.paymentForm = paymentForm;
|
||||
}
|
||||
|
@ -228,7 +237,7 @@ export default class PopupPayment extends PopupElement {
|
|||
wrapPeerTitle({peerId: paymentForm.provider_id.toPeerId()})
|
||||
]);
|
||||
|
||||
console.log(paymentForm, lastRequestedInfo);
|
||||
// console.log(paymentForm, lastRequestedInfo);
|
||||
|
||||
await peerTitle.update({peerId: paymentForm.bot_id.toPeerId()});
|
||||
preloaderContainer.remove();
|
||||
|
|
|
@ -169,7 +169,7 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter,
|
|||
|
||||
if(lockGroups) {
|
||||
animationIntersector.setOnlyOnePlayableGroup(group);
|
||||
animationIntersector.checkAnimations(true);
|
||||
animationIntersector.checkAnimations2(true);
|
||||
}
|
||||
|
||||
if(player instanceof RLottiePlayer) {
|
||||
|
@ -311,7 +311,7 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter,
|
|||
SetTransition(container, 'is-visible', false, openDuration, () => {
|
||||
container.remove();
|
||||
animationIntersector.setOnlyOnePlayableGroup(previousGroup);
|
||||
animationIntersector.checkAnimations(false);
|
||||
animationIntersector.checkAnimations2(false);
|
||||
hasViewer = false;
|
||||
});
|
||||
|
||||
|
|
|
@ -159,7 +159,12 @@ export default async function wrapMessageForReply(message: MyMessage | MyDraftMe
|
|||
}
|
||||
|
||||
case 'messageMediaInvoice': {
|
||||
addPart(undefined, plain ? media.title : wrapEmojiText(media.title));
|
||||
if(media.extended_media?._ === 'messageExtendedMediaPreview') {
|
||||
addPart(undefined, plain ? media.description : wrapEmojiText(media.description));
|
||||
} else {
|
||||
addPart(undefined, plain ? media.title : wrapEmojiText(media.title));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
|
|||
}
|
||||
// }
|
||||
|
||||
if(size?._ === 'photoSizeEmpty' && isDocument) {
|
||||
if((size?._ === 'photoSizeEmpty' && isDocument) || (size as PhotoSize.photoStrippedSize)?.bytes) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,17 @@
|
|||
import {hexToRgb} from '../../helpers/color';
|
||||
import {Message} from '../../layer';
|
||||
import getPeerColorById from '../../lib/appManagers/utils/peers/getPeerColorById';
|
||||
import {AnimationItemGroup} from '../animationIntersector';
|
||||
import ReplyContainer from '../chat/replyContainer';
|
||||
|
||||
export default function wrapReply(
|
||||
title: Parameters<ReplyContainer['fill']>[0],
|
||||
subtitle: Parameters<ReplyContainer['fill']>[1],
|
||||
animationGroup: AnimationItemGroup,
|
||||
message?: Message.message | Message.messageService,
|
||||
setColorPeerId?: PeerId
|
||||
) {
|
||||
const replyContainer = new ReplyContainer('reply');
|
||||
const replyContainer = new ReplyContainer('reply', animationGroup);
|
||||
const fillPromise = replyContainer.fill(title, subtitle, message);
|
||||
|
||||
if(setColorPeerId) {
|
||||
|
|
|
@ -21,7 +21,7 @@ const App = {
|
|||
version: process.env.VERSION,
|
||||
versionFull: process.env.VERSION_FULL,
|
||||
build: +process.env.BUILD,
|
||||
langPackVersion: '0.5.0',
|
||||
langPackVersion: '0.5.1',
|
||||
langPack: 'macos',
|
||||
langPackCode: 'en',
|
||||
domains: [MAIN_DOMAIN] as string[],
|
||||
|
|
|
@ -56,7 +56,8 @@ declare global {
|
|||
type ServerErrorType = 'FILE_REFERENCE_EXPIRED' | 'SESSION_REVOKED' | 'AUTH_KEY_DUPLICATED' |
|
||||
'SESSION_PASSWORD_NEEDED' | 'CONNECTION_NOT_INITED' | 'ERROR_EMPTY' | 'MTPROTO_CLUSTER_INVALID' |
|
||||
'BOT_PRECHECKOUT_TIMEOUT' | 'TMP_PASSWORD_INVALID' | 'PASSWORD_HASH_INVALID' | 'CHANNEL_PRIVATE' |
|
||||
'VOICE_MESSAGES_FORBIDDEN' | 'PHOTO_INVALID_DIMENSIONS' | 'PHOTO_SAVE_FILE_INVALID';
|
||||
'VOICE_MESSAGES_FORBIDDEN' | 'PHOTO_INVALID_DIMENSIONS' | 'PHOTO_SAVE_FILE_INVALID' |
|
||||
'USER_ALREADY_PARTICIPANT';
|
||||
|
||||
type ErrorType = LocalErrorType | ServerErrorType;
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ type MediaTypeSizes = {
|
|||
round: MediaSize,
|
||||
documentName: MediaSize,
|
||||
invoice: MediaSize,
|
||||
extendedInvoice: MediaSize,
|
||||
customEmoji: MediaSize,
|
||||
esgCustomEmoji: MediaSize,
|
||||
emojiStatus: MediaSize,
|
||||
|
@ -64,7 +65,8 @@ class MediaSizes extends EventListenerBase<{
|
|||
poll: makeMediaSize(240, 0),
|
||||
round: makeMediaSize(200, 200),
|
||||
documentName: makeMediaSize(200, 0),
|
||||
invoice: makeMediaSize(240, 240),
|
||||
invoice: makeMediaSize(270, 270),
|
||||
extendedInvoice: makeMediaSize(270, 270),
|
||||
customEmoji: CUSTOM_EMOJI_SIZE,
|
||||
esgCustomEmoji: ESG_CUSTOM_EMOJI_SIZE,
|
||||
emojiStatus: EMOJI_STATUS_SIZE,
|
||||
|
@ -81,7 +83,8 @@ class MediaSizes extends EventListenerBase<{
|
|||
poll: makeMediaSize(330, 0),
|
||||
round: makeMediaSize(280, 280),
|
||||
documentName: makeMediaSize(240, 0),
|
||||
invoice: makeMediaSize(320, 260),
|
||||
invoice: makeMediaSize(320, 320),
|
||||
extendedInvoice: makeMediaSize(420, 340),
|
||||
customEmoji: CUSTOM_EMOJI_SIZE,
|
||||
esgCustomEmoji: ESG_CUSTOM_EMOJI_SIZE,
|
||||
emojiStatus: EMOJI_STATUS_SIZE,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export default function getRandomInt(min: number, max: number) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
|
@ -949,6 +949,7 @@ const lang = {
|
|||
'Checkout.PasswordEntry.Title': 'Payment Confirmation',
|
||||
'Checkout.PasswordEntry.Pay': 'Pay',
|
||||
'Checkout.PasswordEntry.Text': 'Your card %@ is on file. To pay with this card, please enter your 2-Step-Verification password.',
|
||||
'Checkout.PayPrice': 'Pay %@',
|
||||
'Checkout.WebConfirmation.Title': 'Complete Payment',
|
||||
'ChatList.Context.Mute': 'Mute',
|
||||
'ChatList.Context.Unmute': 'Unmute',
|
||||
|
|
|
@ -1427,7 +1427,9 @@ export namespace PhotoSize {
|
|||
export type photoStrippedSize = {
|
||||
_: 'photoStrippedSize',
|
||||
type: string,
|
||||
bytes: Uint8Array
|
||||
bytes: Uint8Array,
|
||||
w?: number,
|
||||
h?: number
|
||||
};
|
||||
|
||||
export type photoSizeProgressive = {
|
||||
|
|
|
@ -92,8 +92,7 @@ import findUpClassName from '../../helpers/dom/findUpClassName';
|
|||
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
|
||||
import PopupPayment from '../../components/popups/payment';
|
||||
import wrapPeerTitle from '../../components/wrappers/peerTitle';
|
||||
|
||||
export const CHAT_ANIMATION_GROUP: AnimationItemGroup = 'chat';
|
||||
import NBSP from '../../helpers/string/nbsp';
|
||||
|
||||
export type ChatSavedPosition = {
|
||||
mids: number[],
|
||||
|
@ -215,10 +214,10 @@ export class AppImManager extends EventListenerBase<{
|
|||
|
||||
useHeavyAnimationCheck(() => {
|
||||
animationIntersector.setOnlyOnePlayableGroup('lock');
|
||||
animationIntersector.checkAnimations(true);
|
||||
animationIntersector.checkAnimations2(true);
|
||||
}, () => {
|
||||
animationIntersector.setOnlyOnePlayableGroup();
|
||||
animationIntersector.checkAnimations(false);
|
||||
animationIntersector.checkAnimations2(false);
|
||||
});
|
||||
|
||||
if(IS_FIREFOX && apiManagerProxy.oldVersion && compareVersion(apiManagerProxy.oldVersion, '1.4.3') === -1) {
|
||||
|
@ -1125,7 +1124,7 @@ export class AppImManager extends EventListenerBase<{
|
|||
* Opens thread when peerId of discussion group is known
|
||||
*/
|
||||
public openThread(peerId: PeerId, lastMsgId: number, threadId: number) {
|
||||
return this.managers.appMessagesManager.wrapSingleMessage(peerId, threadId).then((message) => {
|
||||
return this.managers.appMessagesManager.reloadMessages(peerId, threadId).then((message) => {
|
||||
// const message: Message = this.managers.appMessagesManager.getMessageByPeer(peerId, threadId);
|
||||
if(!message) {
|
||||
lastMsgId = undefined;
|
||||
|
@ -1382,7 +1381,7 @@ export class AppImManager extends EventListenerBase<{
|
|||
}, rootScope.settings.animationsEnabled ? 250 : 0, false, true);
|
||||
|
||||
lottieLoader.setLoop(rootScope.settings.stickers.loop);
|
||||
animationIntersector.checkAnimations(false);
|
||||
animationIntersector.checkAnimations2(false);
|
||||
|
||||
for(const chat of this.chats) {
|
||||
chat.setAutoDownloadMedia();
|
||||
|
@ -2177,7 +2176,7 @@ export class AppImManager extends EventListenerBase<{
|
|||
return () => replaceContent(element, subtitle || placeholder);
|
||||
};
|
||||
|
||||
const placeholder = useWhitespace ? '' : ''; // ! HERE U CAN FIND WHITESPACE
|
||||
const placeholder = useWhitespace ? NBSP : ''; // ! HERE U CAN FIND WHITESPACE
|
||||
if(!result || result.cached) {
|
||||
return await set();
|
||||
} else if(needClear) {
|
||||
|
|
|
@ -192,6 +192,7 @@ export class AppMessagesManager extends AppManager {
|
|||
|
||||
private needSingleMessages: Map<PeerId, Map<number, CancellablePromise<Message.message | Message.messageService>>> = new Map();
|
||||
private fetchSingleMessagesPromise: Promise<void> = null;
|
||||
private extendedMedia: Map<PeerId, Map<number, CancellablePromise<void>>> = new Map();
|
||||
|
||||
private maxSeenId = 0;
|
||||
|
||||
|
@ -280,7 +281,9 @@ export class AppMessagesManager extends AppManager {
|
|||
|
||||
updateNewScheduledMessage: this.onUpdateNewScheduledMessage,
|
||||
|
||||
updateDeleteScheduledMessages: this.onUpdateDeleteScheduledMessages
|
||||
updateDeleteScheduledMessages: this.onUpdateDeleteScheduledMessages,
|
||||
|
||||
updateMessageExtendedMedia: this.onUpdateMessageExtendedMedia
|
||||
});
|
||||
|
||||
// ! Invalidate notify settings, can optimize though
|
||||
|
@ -2608,9 +2611,7 @@ export class AppMessagesManager extends AppManager {
|
|||
return;
|
||||
}
|
||||
|
||||
if(message.pFlags === undefined) {
|
||||
message.pFlags = {};
|
||||
}
|
||||
message.pFlags ??= {};
|
||||
|
||||
// * exclude from state
|
||||
// defineNotNumerableProperties(message, ['rReply', 'mid', 'savedFrom', 'fwdFromId', 'fromId', 'peerId', 'reply_to_mid', 'viaBotId']);
|
||||
|
@ -2729,21 +2730,22 @@ export class AppMessagesManager extends AppManager {
|
|||
} */
|
||||
|
||||
let unsupported = false;
|
||||
if(isMessage && message.media) {
|
||||
switch(message.media._) {
|
||||
const media = isMessage && message.media;
|
||||
if(media) {
|
||||
switch(media._) {
|
||||
case 'messageMediaEmpty': {
|
||||
delete message.media;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'messageMediaPhoto': {
|
||||
if(message.media.ttl_seconds) {
|
||||
if(media.ttl_seconds) {
|
||||
unsupported = true;
|
||||
} else {
|
||||
message.media.photo = this.appPhotosManager.savePhoto(message.media.photo, mediaContext);
|
||||
media.photo = this.appPhotosManager.savePhoto(media.photo, mediaContext);
|
||||
}
|
||||
|
||||
if(!(message.media as MessageMedia.messageMediaPhoto).photo) { // * found this bug on test DC
|
||||
if(!(media as MessageMedia.messageMediaPhoto).photo) { // * found this bug on test DC
|
||||
delete message.media;
|
||||
}
|
||||
|
||||
|
@ -2751,20 +2753,20 @@ export class AppMessagesManager extends AppManager {
|
|||
}
|
||||
|
||||
case 'messageMediaPoll': {
|
||||
const result = this.appPollsManager.savePoll(message.media.poll, message.media.results, message);
|
||||
message.media.poll = result.poll;
|
||||
message.media.results = result.results;
|
||||
const result = this.appPollsManager.savePoll(media.poll, media.results, message);
|
||||
media.poll = result.poll;
|
||||
media.results = result.results;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'messageMediaDocument': {
|
||||
if(message.media.ttl_seconds) {
|
||||
if(media.ttl_seconds) {
|
||||
unsupported = true;
|
||||
} else {
|
||||
const originalDoc = message.media.document;
|
||||
message.media.document = this.appDocsManager.saveDoc(originalDoc, mediaContext); // 11.04.2020 warning
|
||||
const originalDoc = media.document;
|
||||
media.document = this.appDocsManager.saveDoc(originalDoc, mediaContext); // 11.04.2020 warning
|
||||
|
||||
if(!message.media.document && originalDoc._ !== 'documentEmpty') {
|
||||
if(!media.document && originalDoc._ !== 'documentEmpty') {
|
||||
unsupported = true;
|
||||
}
|
||||
}
|
||||
|
@ -2774,7 +2776,7 @@ export class AppMessagesManager extends AppManager {
|
|||
|
||||
case 'messageMediaWebPage': {
|
||||
const messageKey = this.appWebPagesManager.getMessageKeyForPendingWebPage(peerId, mid, options.isScheduled);
|
||||
message.media.webpage = this.appWebPagesManager.saveWebPage(message.media.webpage, messageKey, mediaContext);
|
||||
media.webpage = this.appWebPagesManager.saveWebPage(media.webpage, messageKey, mediaContext);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2784,7 +2786,13 @@ export class AppMessagesManager extends AppManager {
|
|||
break; */
|
||||
|
||||
case 'messageMediaInvoice': {
|
||||
message.media.photo = this.appWebDocsManager.saveWebDocument(message.media.photo);
|
||||
media.photo = this.appWebDocsManager.saveWebDocument(media.photo);
|
||||
const extendedMedia = media.extended_media;
|
||||
if(extendedMedia?._ === 'messageExtendedMedia') {
|
||||
const extendedMediaMedia = extendedMedia.media;
|
||||
(extendedMediaMedia as MessageMedia.messageMediaPhoto).photo = this.appPhotosManager.savePhoto((extendedMediaMedia as MessageMedia.messageMediaPhoto).photo, mediaContext);
|
||||
(extendedMediaMedia as MessageMedia.messageMediaDocument).document = this.appDocsManager.saveDoc((extendedMediaMedia as MessageMedia.messageMediaDocument).document, mediaContext);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3046,8 +3054,8 @@ export class AppMessagesManager extends AppManager {
|
|||
promise = this.appChatsManager.addChatUser(chatId, botId, 0);
|
||||
}
|
||||
|
||||
return promise.catch((error) => {
|
||||
if(error && error.type == 'USER_ALREADY_PARTICIPANT') {
|
||||
return promise.catch((error: ApiError) => {
|
||||
if(error?.type == 'USER_ALREADY_PARTICIPANT') {
|
||||
error.handled = true;
|
||||
return;
|
||||
}
|
||||
|
@ -4312,6 +4320,7 @@ export class AppMessagesManager extends AppManager {
|
|||
this.rootScope.dispatchEvent('dialog_flush', {peerId, dialog});
|
||||
}
|
||||
} else {
|
||||
let dispatchEditEvent = true;
|
||||
// no sense in dispatching message_edit since only reactions have changed
|
||||
if(oldMessage?._ === 'message' && !deepEqual(oldMessage.reactions, (newMessage as Message.message).reactions)) {
|
||||
const newReactions = (newMessage as Message.message).reactions;
|
||||
|
@ -4323,10 +4332,10 @@ export class AppMessagesManager extends AppManager {
|
|||
reactions: newReactions
|
||||
});
|
||||
|
||||
return;
|
||||
dispatchEditEvent = false;
|
||||
}
|
||||
|
||||
this.rootScope.dispatchEvent('message_edit', {
|
||||
dispatchEditEvent && this.rootScope.dispatchEvent('message_edit', {
|
||||
storageKey: storage.key,
|
||||
peerId,
|
||||
mid,
|
||||
|
@ -4693,7 +4702,7 @@ export class AppMessagesManager extends AppManager {
|
|||
|
||||
const storage = this.getHistoryMessagesStorage(peerId);
|
||||
const missingMessages = messages.filter((mid) => !storage.has(mid));
|
||||
const getMissingPromise = missingMessages.length ? Promise.all(missingMessages.map((mid) => this.wrapSingleMessage(peerId, mid))) : Promise.resolve();
|
||||
const getMissingPromise = missingMessages.length ? Promise.all(missingMessages.map((mid) => this.reloadMessages(peerId, mid))) : Promise.resolve();
|
||||
getMissingPromise.finally(() => {
|
||||
const werePinned = update.pFlags?.pinned;
|
||||
if(werePinned) {
|
||||
|
@ -4782,6 +4791,30 @@ export class AppMessagesManager extends AppManager {
|
|||
}
|
||||
};
|
||||
|
||||
private onUpdateMessageExtendedMedia = (update: Update.updateMessageExtendedMedia) => {
|
||||
const peerId = this.appPeersManager.getPeerId(update.peer);
|
||||
const mid = generateMessageId(update.msg_id);
|
||||
const storage = this.getHistoryMessagesStorage(peerId);
|
||||
if(!storage.has(mid)) {
|
||||
// this.fixDialogUnreadMentionsIfNoMessage(peerId);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = this.getMessageFromStorage(storage, mid) as Message.message;
|
||||
const messageMedia = message.media as MessageMedia.messageMediaInvoice;
|
||||
if(messageMedia.extended_media?._ === 'messageExtendedMedia') {
|
||||
return;
|
||||
}
|
||||
|
||||
messageMedia.extended_media = update.extended_media;
|
||||
this.onUpdateEditMessage({
|
||||
_: 'updateEditMessage',
|
||||
message,
|
||||
pts: 0,
|
||||
pts_count: 0
|
||||
});
|
||||
};
|
||||
|
||||
public setDialogToStateIfMessageIsTop(message: MyMessage) {
|
||||
if(this.isMessageIsTopMessage(message)) {
|
||||
this.dialogsStorage.setDialogToState(this.getDialogOnly(message.peerId));
|
||||
|
@ -4820,7 +4853,7 @@ export class AppMessagesManager extends AppManager {
|
|||
}
|
||||
|
||||
public updateMessage(peerId: PeerId, mid: number, broadcastEventName?: 'replies_updated'): Promise<Message.message> {
|
||||
const promise: Promise<Message.message> = this.wrapSingleMessage(peerId, mid, true).then(() => {
|
||||
const promise: Promise<Message.message> = this.reloadMessages(peerId, mid, true).then(() => {
|
||||
const message = this.getMessageByPeer(peerId, mid) as Message.message;
|
||||
if(!message) {
|
||||
return;
|
||||
|
@ -5621,7 +5654,15 @@ export class AppMessagesManager extends AppManager {
|
|||
});
|
||||
}
|
||||
|
||||
public wrapSingleMessage(peerId: PeerId, mid: number, overwrite = false) {
|
||||
public reloadMessages(peerId: PeerId, mid: number, overwrite?: boolean): Promise<MyMessage>;
|
||||
public reloadMessages(peerId: PeerId, mid: number[], overwrite?: boolean): Promise<MyMessage[]>;
|
||||
public reloadMessages(peerId: PeerId, mid: number | number[], overwrite?: boolean): Promise<MyMessage | MyMessage[]> {
|
||||
if(Array.isArray(mid)) {
|
||||
return Promise.all(mid.map((mid) => {
|
||||
return this.reloadMessages(peerId, mid, overwrite);
|
||||
}));
|
||||
}
|
||||
|
||||
const message = this.getMessageByPeer(peerId, mid);
|
||||
if(message && !overwrite) {
|
||||
this.rootScope.dispatchEvent('messages_downloaded', {peerId, mids: [mid]});
|
||||
|
@ -5644,10 +5685,50 @@ export class AppMessagesManager extends AppManager {
|
|||
}
|
||||
}
|
||||
|
||||
public getExtendedMedia(peerId: PeerId, mids: number[]) {
|
||||
let map = this.extendedMedia.get(peerId);
|
||||
if(!map) {
|
||||
this.extendedMedia.set(peerId, map = new Map());
|
||||
}
|
||||
|
||||
const deferred = deferredPromise<void>();
|
||||
const toRequest: number[] = [];
|
||||
const promises = mids.map((mid) => {
|
||||
let promise = map.get(mid);
|
||||
if(!promise) {
|
||||
map.set(mid, promise = deferred);
|
||||
toRequest.push(mid);
|
||||
|
||||
promise.then(() => {
|
||||
map.delete(mid);
|
||||
if(!map.size && this.extendedMedia.get(peerId) === map) {
|
||||
this.extendedMedia.delete(peerId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
if(!toRequest.length) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
this.apiManager.invokeApi('messages.getExtendedMedia', {
|
||||
peer: this.appPeersManager.getInputPeerById(peerId),
|
||||
id: toRequest.map((mid) => getServerMessageId(mid))
|
||||
}).then((updates) => {
|
||||
this.apiUpdatesManager.processUpdateMessage(updates);
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
public fetchMessageReplyTo(message: MyMessage) {
|
||||
if(!message.reply_to_mid) return Promise.resolve(this.generateEmptyMessage(0));
|
||||
const replyToPeerId = message.reply_to.reply_to_peer_id ? this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : message.peerId;
|
||||
return this.wrapSingleMessage(replyToPeerId, message.reply_to_mid).then((originalMessage) => {
|
||||
return this.reloadMessages(replyToPeerId, message.reply_to_mid).then((originalMessage) => {
|
||||
if(!originalMessage) { // ! break the infinite loop
|
||||
message = this.getMessageByPeer(message.peerId, message.mid); // message can come from other thread
|
||||
delete message.reply_to_mid; // ! WARNING!
|
||||
|
|
|
@ -116,7 +116,7 @@ export class AppReactionsManager extends AppManager {
|
|||
}
|
||||
|
||||
private unshiftQuickReactionInner(availableReactions: AvailableReaction[], quickReaction: Reaction | AvailableReaction) {
|
||||
if(quickReaction._ !== 'reactionEmoji' && quickReaction._ !== 'availableReaction') return availableReactions;
|
||||
if(quickReaction && quickReaction._ !== 'reactionEmoji' && quickReaction._ !== 'availableReaction') return availableReactions;
|
||||
const emoticon = (quickReaction as Reaction.reactionEmoji).emoticon || (quickReaction as AvailableReaction).reaction;
|
||||
const availableReaction = findAndSplice(availableReactions, (availableReaction) => availableReaction.reaction === emoticon);
|
||||
if(availableReaction) {
|
||||
|
@ -155,7 +155,7 @@ export class AppReactionsManager extends AppManager {
|
|||
this.getAvailableReactions()
|
||||
], ([config, availableReactions]) => {
|
||||
const reaction = config.reactions_default;
|
||||
if(reaction._ === 'reactionEmoji') {
|
||||
if(reaction?._ === 'reactionEmoji') {
|
||||
return availableReactions.find((availableReaction) => availableReaction.reaction === reaction.emoticon);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import {Message, MessageMedia} from '../../../../layer';
|
||||
|
||||
export default function canSaveMessageMedia(message: Message.message | Message.messageService) {
|
||||
return message &&
|
||||
!message.pFlags.is_outgoing &&
|
||||
!(message as Message.message).pFlags.noforwards &&
|
||||
!((message as Message.message).media as MessageMedia.messageMediaInvoice)?.extended_media
|
||||
}
|
|
@ -1,19 +1,22 @@
|
|||
import {Document, Message, MessageAction, MessageMedia, Photo, WebPage} from '../../../../layer';
|
||||
import {Document, Message, MessageAction, MessageExtendedMedia, MessageMedia, Photo, WebPage} from '../../../../layer';
|
||||
|
||||
export default function getMediaFromMessage(message: Message) {
|
||||
if(!message) return;
|
||||
|
||||
const media = (message as Message.messageService).action ?
|
||||
((message as Message.messageService).action as MessageAction.messageActionChannelEditPhoto).photo :
|
||||
(message as Message.message).media && (
|
||||
((message as Message.message).media as MessageMedia.messageMediaPhoto).photo ||
|
||||
((message as Message.message).media as MessageMedia.messageMediaDocument).document || (
|
||||
((message as Message.message).media as MessageMedia.messageMediaWebPage).webpage && (
|
||||
(((message as Message.message).media as MessageMedia.messageMediaWebPage).webpage as WebPage.webPage).document ||
|
||||
(((message as Message.message).media as MessageMedia.messageMediaWebPage).webpage as WebPage.webPage).photo
|
||||
)
|
||||
)
|
||||
);
|
||||
let media: any;
|
||||
if((message as Message.messageService).action) {
|
||||
media = ((message as Message.messageService).action as MessageAction.messageActionChannelEditPhoto).photo;
|
||||
} else if((message as Message.message).media) {
|
||||
let messageMedia = (message as Message.message).media;
|
||||
if((messageMedia as MessageMedia.messageMediaWebPage).webpage) {
|
||||
messageMedia = (messageMedia as MessageMedia.messageMediaWebPage).webpage as any as MessageMedia;
|
||||
} else if((messageMedia as MessageMedia.messageMediaInvoice).extended_media?._ === 'messageExtendedMedia') {
|
||||
messageMedia = ((messageMedia as MessageMedia.messageMediaInvoice).extended_media as MessageExtendedMedia.messageExtendedMedia).media;
|
||||
}
|
||||
|
||||
media = (messageMedia as MessageMedia.messageMediaPhoto).photo ||
|
||||
(messageMedia as MessageMedia.messageMediaDocument).document;
|
||||
}
|
||||
|
||||
return media as Photo.photo | Document.document;
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ export class ReferenceDatabase extends AppManager {
|
|||
let promise: Promise<any>;
|
||||
switch(context?.type) {
|
||||
case 'message': {
|
||||
promise = this.appMessagesManager.wrapSingleMessage(context.peerId, context.messageId, true);
|
||||
promise = this.appMessagesManager.reloadMessages(context.peerId, context.messageId, true);
|
||||
break;
|
||||
// .then(() => {
|
||||
// console.log('FILE_REFERENCE_EXPIRED: got message', context, appMessagesManager.getMessage((context as ReferenceContext.referenceContextMessage).messageId).media, reference);
|
||||
|
|
|
@ -211,7 +211,7 @@ export class LottieLoader {
|
|||
// ! will need refactoring later, this is not the best way to remove the animation
|
||||
const animations = animationIntersector.getAnimations(player.el[0]);
|
||||
animations.forEach((animation) => {
|
||||
animationIntersector.checkAnimation(animation, true, true);
|
||||
animationIntersector.removeAnimation(animation);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -22,6 +22,12 @@
|
|||
"params": [
|
||||
{"name": "size", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "photoStrippedSize",
|
||||
"params": [
|
||||
{"name": "w", "type": "number"},
|
||||
{"name": "h", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "dialog",
|
||||
"params": [
|
||||
|
|
|
@ -1902,6 +1902,30 @@ $bubble-beside-button-width: 38px;
|
|||
}
|
||||
}
|
||||
|
||||
.extended-media-buy {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
height: 2.5rem;
|
||||
padding: 0 1rem;
|
||||
background-color: rgba(0, 0, 0, .3);
|
||||
backdrop-filter: var(--menu-backdrop-filter);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: #fff;
|
||||
z-index: 2;
|
||||
font-size: var(--font-size-14);
|
||||
border-radius: 2rem;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:before {
|
||||
margin-right: .25rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
|
|
Loading…
Reference in New Issue