Browse Source

Emoji animations

Fix performance of extra saving of state
master
Eduard Kuzmenko 2 years ago
parent
commit
f9fce9116b
  1. 149
      src/components/wrappers.ts
  2. 3
      src/environment/vibrateSupport.ts
  3. 2
      src/lang.ts
  4. 10
      src/lib/appManagers/appDialogsManager.ts
  5. 74
      src/lib/appManagers/appImManager.ts
  6. 9
      src/lib/appManagers/appMessagesManager.ts
  7. 3
      src/lib/appManagers/appNotificationsManager.ts
  8. 28
      src/lib/appManagers/appStickersManager.ts
  9. 11
      src/lib/rlottie/rlottiePlayer.ts
  10. 23
      src/lib/storage.ts
  11. 56
      src/lib/storages/dialogs.ts
  12. 29
      src/scss/partials/_emojiAnimation.scss
  13. 1
      src/scss/style.scss
  14. 5
      src/types.d.ts

149
src/components/wrappers.ts

@ -49,6 +49,13 @@ import { MiddleEllipsisElement } from './middleEllipsis'; @@ -49,6 +49,13 @@ import { MiddleEllipsisElement } from './middleEllipsis';
import { joinElementsWith } from '../lib/langPack';
import throttleWithRaf from '../helpers/schedulers/throttleWithRaf';
import { NULL_PEER_ID } from '../lib/mtproto/mtproto_config';
import findUpClassName from '../helpers/dom/findUpClassName';
import RLottiePlayer from '../lib/rlottie/rlottiePlayer';
import assumeType from '../helpers/assumeType';
import appMessagesIdsManager from '../lib/appManagers/appMessagesIdsManager';
import throttle from '../helpers/schedulers/throttle';
import { SendMessageEmojiInteractionData } from '../types';
import IS_VIBRATE_SUPPORTED from '../environment/vibrateSupport';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -1127,7 +1134,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1127,7 +1134,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
loadPromises?: Promise<any>[],
needFadeIn?: boolean,
needUpscale?: boolean
}) {
}): Promise<RLottiePlayer | void> {
const stickerType = doc.sticker;
if(!width) {
@ -1160,7 +1167,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1160,7 +1167,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
let loadThumbPromise = deferredPromise<void>();
let haveThumbCached = false;
if((doc.thumbs?.length || doc.stickerCachedThumbs) && !div.firstElementChild && (!downloaded || stickerType === 2 || onlyThumb)/* && doc.thumbs[0]._ !== 'photoSizeEmpty' */) {
if((doc.thumbs?.length || doc.stickerCachedThumbs) && !div.firstElementChild && (!downloaded || stickerType === 2 || onlyThumb) && withThumb !== false/* && doc.thumbs[0]._ !== 'photoSizeEmpty' */) {
let thumb = doc.stickerCachedThumbs && doc.stickerCachedThumbs[toneIndex] || doc.thumbs[0];
//console.log('wrap sticker', thumb, div);
@ -1267,7 +1274,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1267,7 +1274,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
//appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => {
//fetch(doc.url).then(res => res.json()).then(async(json) => {
/* return */ await appDocsManager.downloadDoc(doc, /* undefined, */lazyLoadQueue?.queueId)
return await appDocsManager.downloadDoc(doc, /* undefined, */lazyLoadQueue?.queueId)
.then(readBlobAsText)
//.then(JSON.parse)
.then(async(json) => {
@ -1324,6 +1331,13 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1324,6 +1331,13 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}, {once: true});
if(emoji) {
const data: SendMessageEmojiInteractionData = {
a: [],
v: 1
};
let sendInteractionThrottled: () => void;
attachClickEvent(div, (e) => {
cancelEvent(e);
let animation = LottieLoader.getAnimation(div);
@ -1332,9 +1346,134 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1332,9 +1346,134 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
animation.autoplay = true;
animation.restart();
}
const doc = appStickersManager.getAnimatedEmojiSticker(emoji, true);
if(!doc) {
return;
}
const animationDiv = document.createElement('div');
animationDiv.classList.add('emoji-animation');
const size = 280;
animationDiv.style.width = size + 'px';
animationDiv.style.height = size + 'px';
wrapSticker({
div: animationDiv,
doc,
middleware,
withThumb: false,
needFadeIn: false,
loop: false,
width: size,
height: size,
play: true,
group: 'none'
}).then(animation => {
assumeType<RLottiePlayer>(animation);
animation.addEventListener('enterFrame', (frameNo) => {
if(frameNo === animation.maxFrame) {
animation.remove();
// animationDiv.remove();
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
}
});
if(IS_VIBRATE_SUPPORTED) {
animation.addEventListener('firstFrame', () => {
navigator.vibrate(100);
}, {once: true});
}
});
const generateRandomSigned = (max: number) => {
const r = Math.random() * max * 2;
return r > max ? -r % max : r;
};
const bubble = findUpClassName(div, 'bubble');
const isOut = bubble.classList.contains('is-out');
const randomOffsetX = generateRandomSigned(16);
const randomOffsetY = generateRandomSigned(4);
const stableOffsetX = size / 8 * (isOut ? 1 : -1);
const setPosition = () => {
const rect = div.getBoundingClientRect();
/* const boxWidth = Math.max(rect.width, rect.height);
const boxHeight = Math.max(rect.width, rect.height);
const x = rect.left + ((boxWidth - size) / 2);
const y = rect.top + ((boxHeight - size) / 2); */
const rectX = isOut ? rect.right : rect.left;
const addOffsetX = (isOut ? -size : 0) + stableOffsetX + randomOffsetX;
const x = rectX + addOffsetX;
// const y = rect.bottom - size + size / 4;
const y = rect.top + ((rect.height - size) / 2) + randomOffsetY;
// animationDiv.style.transform = `translate(${x}px, ${y}px)`;
animationDiv.style.top = y + 'px';
animationDiv.style.left = x + 'px';
};
const onScroll = throttleWithRaf(setPosition);
appImManager.chat.bubbles.scrollable.container.addEventListener('scroll', onScroll);
setPosition();
if(bubble) {
if(isOut) {
animationDiv.classList.add('is-out');
} else {
animationDiv.classList.add('is-in');
}
}
appImManager.emojiAnimationContainer.append(animationDiv);
if(!sendInteractionThrottled) {
sendInteractionThrottled = throttle(() => {
const length = data.a.length;
if(!length) {
return;
}
const firstTime = data.a[0].t;
data.a.forEach((a) => {
a.t = (a.t - firstTime) / 1000;
});
const bubble = findUpClassName(div, 'bubble');
appMessagesManager.setTyping(appImManager.chat.peerId, {
_: 'sendMessageEmojiInteraction',
msg_id: appMessagesIdsManager.getServerMessageId(+bubble.dataset.mid),
emoticon: emoji,
interaction: {
_: 'dataJSON',
data: JSON.stringify(data)
}
}, true);
data.a.length = 0;
}, 1000, false);
}
// using a trick here: simulated event from interlocutor's interaction won't fire ours
if(e.isTrusted) {
data.a.push({
i: 1,
t: Date.now()
});
sendInteractionThrottled();
}
});
}
return animation;
//return deferred;
//await new Promise((resolve) => setTimeout(resolve, 5e3));
});
@ -1384,12 +1523,12 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1384,12 +1523,12 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}
};
const loadPromise: Promise<any> = lazyLoadQueue && (!downloaded || stickerType === 2) ?
const loadPromise: Promise<RLottiePlayer | void> = lazyLoadQueue && (!downloaded || stickerType === 2) ?
(lazyLoadQueue.push({div, load}), Promise.resolve()) :
load();
if(downloaded && stickerType === 1) {
loadThumbPromise = loadPromise;
loadThumbPromise = loadPromise as any;
if(loadPromises) {
loadPromises.push(loadThumbPromise);
}

3
src/environment/vibrateSupport.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
const IS_VIBRATE_SUPPORTED = !!navigator?.vibrate;
export default IS_VIBRATE_SUPPORTED;

2
src/lang.ts

@ -854,6 +854,7 @@ const lang = { @@ -854,6 +854,7 @@ const lang = {
"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",
@ -862,6 +863,7 @@ const lang = { @@ -862,6 +863,7 @@ const lang = {
"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",

10
src/lib/appManagers/appDialogsManager.ts

@ -1899,12 +1899,10 @@ export class AppDialogsManager { @@ -1899,12 +1899,10 @@ export class AppDialogsManager {
return;
}
let typingElement = dom.lastMessageSpan.querySelector('.peer-typing-container') as HTMLElement;
if(typingElement) {
appImManager.getPeerTyping(dialog.peerId, typingElement);
} else {
typingElement = appImManager.getPeerTyping(dialog.peerId);
replaceContent(dom.lastMessageSpan, typingElement);
const oldTypingElement = dom.lastMessageSpan.querySelector('.peer-typing-container') as HTMLElement;
const newTypingElement = appImManager.getPeerTyping(dialog.peerId, oldTypingElement);
if(!oldTypingElement && newTypingElement) {
replaceContent(dom.lastMessageSpan, newTypingElement);
dom.lastMessageSpan.classList.add('user-typing');
}
}

74
src/lib/appManagers/appImManager.ts

@ -79,7 +79,10 @@ import IS_GROUP_CALL_SUPPORTED from '../../environment/groupCallSupport'; @@ -79,7 +79,10 @@ import IS_GROUP_CALL_SUPPORTED from '../../environment/groupCallSupport';
import appAvatarsManager from './appAvatarsManager';
import IS_CALL_SUPPORTED from '../../environment/callSupport';
import { CallType } from '../calls/types';
import { Modify } from '../../types';
import { Modify, SendMessageEmojiInteractionData } from '../../types';
import htmlToSpan from '../../helpers/dom/htmlToSpan';
import getVisibleRect from '../../helpers/dom/getVisibleRect';
import { simulateClickEvent } from '../../helpers/dom/clickEvent';
//console.log('appImManager included33!');
@ -127,6 +130,7 @@ export class AppImManager { @@ -127,6 +130,7 @@ export class AppImManager {
private backgroundPromises: {[slug: string]: Promise<string>} = {};
private topbarCall: TopbarCall;
emojiAnimationContainer: HTMLDivElement;
get myId() {
return rootScope.myId;
@ -177,6 +181,10 @@ export class AppImManager { @@ -177,6 +181,10 @@ export class AppImManager {
this.chatsContainer.classList.add('chats-container', 'tabs-container');
this.chatsContainer.dataset.animation = 'navigation';
this.emojiAnimationContainer = document.createElement('div');
this.emojiAnimationContainer.classList.add('emoji-animation-container');
document.body.append(this.emojiAnimationContainer);
this.columnEl.append(this.chatsContainer);
this.createNewChat();
@ -204,6 +212,12 @@ export class AppImManager { @@ -204,6 +212,12 @@ export class AppImManager {
&& document.body.classList.contains(RIGHT_COLUMN_ACTIVE_CLASSNAME)) {
appSidebarRight.toggleSidebar(false);
}
if(from === ScreenSize.mobile) {
document.body.append(this.emojiAnimationContainer);
} else if(to === ScreenSize.mobile) {
this.columnEl.append(this.emojiAnimationContainer);
}
});
rootScope.addEventListener('history_focus', (e) => {
@ -232,6 +246,41 @@ export class AppImManager { @@ -232,6 +246,41 @@ export class AppImManager {
this.setChoosingStickerTyping(!choosing);
});
rootScope.addEventListener('peer_typings', ({peerId, typings}) => {
const chat = this.chat;
if(
!chat ||
chat.peerId !== peerId ||
rootScope.overlaysActive || (
mediaSizes.activeScreen === ScreenSize.mobile &&
this.tabId !== 1
)
) {
return;
}
const typing = typings.find(typing => typing.action._ === 'sendMessageEmojiInteraction');
if(typing?.action?._ === 'sendMessageEmojiInteraction') {
const action = typing.action;
const bubble = chat.bubbles.bubbles[appMessagesIdsManager.generateMessageId(typing.action.msg_id)];
if(bubble && getVisibleRect(bubble, chat.bubbles.scrollable.container)) {
const stickerWrapper: HTMLElement = bubble.querySelector('.media-sticker-wrapper');
const data: SendMessageEmojiInteractionData = JSON.parse(action.interaction.data);
data.a.forEach(a => {
setTimeout(() => {
simulateClickEvent(stickerWrapper);
}, a.t * 1000);
});
appMessagesManager.setTyping(peerId, {
_: 'sendMessageEmojiInteractionSeen',
emoticon: action.emoticon
});
}
}
});
rootScope.addEventListener('instance_deactivated', () => {
const popup = new PopupElement('popup-instance-deactivated', undefined, {overlayClosable: true});
const c = document.createElement('div');
@ -1599,6 +1648,7 @@ export class AppImManager { @@ -1599,6 +1648,7 @@ export class AppImManager {
break;
}
case 'sendMessageEmojiInteractionSeen':
case 'sendMessageChooseStickerAction': {
c += '-choosing-sticker';
for(let i = 0; i < 2; ++i) {
@ -1638,7 +1688,8 @@ export class AppImManager { @@ -1638,7 +1688,8 @@ export class AppImManager {
'sendMessageRecordAudioAction': 'Peer.Activity.User.RecordingAudio',
'sendMessageRecordRoundAction': 'Peer.Activity.User.RecordingVideo',
'sendMessageGamePlayAction': 'Peer.Activity.User.PlayingGame',
'sendMessageChooseStickerAction': 'Peer.Activity.User.ChoosingSticker'
'sendMessageChooseStickerAction': 'Peer.Activity.User.ChoosingSticker',
'sendMessageEmojiInteractionSeen': 'Peer.Activity.User.EnjoyingAnimations'
},
chat: {
'sendMessageTypingAction': 'Peer.Activity.Chat.TypingText',
@ -1651,7 +1702,8 @@ export class AppImManager { @@ -1651,7 +1702,8 @@ export class AppImManager {
'sendMessageRecordAudioAction': 'Peer.Activity.Chat.RecordingAudio',
'sendMessageRecordRoundAction': 'Peer.Activity.Chat.RecordingVideo',
'sendMessageGamePlayAction': 'Peer.Activity.Chat.PlayingGame',
'sendMessageChooseStickerAction': 'Peer.Activity.Chat.ChoosingSticker'
'sendMessageChooseStickerAction': 'Peer.Activity.Chat.ChoosingSticker',
'sendMessageEmojiInteractionSeen': 'Peer.Activity.Chat.EnjoyingAnimations'
},
multi: {
'sendMessageTypingAction': 'Peer.Activity.Chat.Multi.TypingText1',
@ -1696,9 +1748,7 @@ export class AppImManager { @@ -1696,9 +1748,7 @@ export class AppImManager {
container.classList.add('online', 'peer-typing-container');
}
if(action._ === 'sendMessageChooseStickerAction') {
container.classList.add('peer-typing-flex');
}
container.classList.toggle('peer-typing-flex', action._ === 'sendMessageChooseStickerAction' || action._ === 'sendMessageEmojiInteractionSeen');
let typingElement = container.firstElementChild as HTMLElement;
if(!typingElement) {
@ -1717,6 +1767,18 @@ export class AppImManager { @@ -1717,6 +1767,18 @@ export class AppImManager {
typings.length - 1
];
}
if(action._ === 'sendMessageEmojiInteractionSeen') {
if(args) {
args.pop();
} else {
args = [];
}
const span = htmlToSpan(RichTextProcessor.wrapEmojiText(action.emoticon));
args.push(span);
}
const descriptionElement = i18n(langPackKey, args);
descriptionElement.classList.add('peer-typing-description');

9
src/lib/appManagers/appMessagesManager.ts

@ -201,7 +201,7 @@ export class AppMessagesManager { @@ -201,7 +201,7 @@ export class AppMessagesManager {
private groupedTempId = 0;
private typings: {[peerId: PeerId]: {type: SendMessageAction['_'], timeout?: number}} = {};
private typings: {[peerId: PeerId]: {action: SendMessageAction, timeout?: number}} = {};
private middleware: ReturnType<typeof getMiddleware>;
@ -5806,13 +5806,14 @@ export class AppMessagesManager { @@ -5806,13 +5806,14 @@ export class AppMessagesManager {
});
}
public setTyping(peerId: PeerId, action: SendMessageAction): Promise<boolean> {
public setTyping(peerId: PeerId, action: SendMessageAction, force?: boolean): Promise<boolean> {
let typing = this.typings[peerId];
if(!rootScope.myId ||
!peerId ||
!this.canSendToPeer(peerId) ||
peerId === rootScope.myId ||
typing?.type === action._
// (!force && deepEqual(typing?.action, action))
(!force && typing?.action?._ === action._)
) {
return Promise.resolve(false);
}
@ -5822,7 +5823,7 @@ export class AppMessagesManager { @@ -5822,7 +5823,7 @@ export class AppMessagesManager {
}
typing = this.typings[peerId] = {
type: action._
action
};
return apiManager.invokeApi('messages.setTyping', {

3
src/lib/appManagers/appNotificationsManager.ts

@ -28,6 +28,7 @@ import appPeersManager from "./appPeersManager"; @@ -28,6 +28,7 @@ import appPeersManager from "./appPeersManager";
import appRuntimeManager from "./appRuntimeManager";
import appStateManager from "./appStateManager";
import appUsersManager from "./appUsersManager";
import IS_VIBRATE_SUPPORTED from "../../environment/vibrateSupport";
type MyNotification = Notification & {
hidden?: boolean,
@ -60,7 +61,7 @@ export class AppNotificationsManager { @@ -60,7 +61,7 @@ export class AppNotificationsManager {
private notificationIndex = 0;
private notificationsCount = 0;
private soundsPlayed: {[tag: string]: number} = {};
private vibrateSupport = !!navigator.vibrate;
private vibrateSupport = IS_VIBRATE_SUPPORTED;
private nextSoundAt: number;
private prevSoundVolume: number;
private peerSettings = {

28
src/lib/appManagers/appStickersManager.ts

@ -22,6 +22,13 @@ import assumeType from '../../helpers/assumeType'; @@ -22,6 +22,13 @@ import assumeType from '../../helpers/assumeType';
const CACHE_TIME = 3600e3;
const EMOJI_SET_LOCAL_ID = 'emoji';
const EMOJI_ANIMATIONS_SET_LOCAL_ID = 'emojiAnimations';
const LOCAL_IDS_SET = new Set([
EMOJI_SET_LOCAL_ID,
EMOJI_ANIMATIONS_SET_LOCAL_ID
]);
export type MyStickerSetInput = {
id: StickerSet.stickerSet['id'],
access_hash?: StickerSet.stickerSet['access_hash']
@ -104,11 +111,13 @@ export class AppStickersManager { @@ -104,11 +111,13 @@ export class AppStickersManager {
return this.getStickerSetPromises[id] = new Promise(async(resolve) => {
if(!params.overwrite) {
// const perf = performance.now();
const cachedSet = await this.storage.get(id);
if(cachedSet && cachedSet.documents?.length && ((Date.now() - cachedSet.refreshTime) < CACHE_TIME || params.useCache)) {
this.saveStickers(cachedSet.documents);
resolve(cachedSet);
delete this.getStickerSetPromises[id];
// console.log('get sticker set from cache time', id, performance.now() - perf);
return;
}
}
@ -132,7 +141,12 @@ export class AppStickersManager { @@ -132,7 +141,12 @@ export class AppStickersManager {
}
public getAnimatedEmojiStickerSet() {
return this.getStickerSet({id: 'emoji'}, {saveById: true});
return Promise.all([
this.getStickerSet({id: EMOJI_SET_LOCAL_ID}, {saveById: true}),
this.getStickerSet({id: EMOJI_ANIMATIONS_SET_LOCAL_ID}, {saveById: true})
]).then(([emoji, animations]) => {
return {emoji, animations};
});
}
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {
@ -150,8 +164,8 @@ export class AppStickersManager { @@ -150,8 +164,8 @@ export class AppStickersManager {
return res;
}
public getAnimatedEmojiSticker(emoji: string) {
const stickerSet = this.storage.getFromCache('emoji');
public getAnimatedEmojiSticker(emoji: string, isAnimation?: boolean) {
const stickerSet = this.storage.getFromCache(isAnimation ? EMOJI_ANIMATIONS_SET_LOCAL_ID : EMOJI_SET_LOCAL_ID);
if(!stickerSet || !stickerSet.documents) return undefined;
emoji = emoji.replace(/\ufe0f/g, '').replace(/🏻|🏼|🏽|🏾|🏿/g, '');
@ -207,7 +221,7 @@ export class AppStickersManager { @@ -207,7 +221,7 @@ export class AppStickersManager {
this.saveStickers(res.documents);
//console.log('stickers wrote', this.stickerSets);
const needSave = stickerSet.set.installed_date || id === 'emoji';
const needSave = stickerSet.set.installed_date || LOCAL_IDS_SET.has(id as any);
stickerSet.refreshTime = Date.now();
this.storage.set({[id]: stickerSet}, !needSave);
}
@ -247,10 +261,14 @@ export class AppStickersManager { @@ -247,10 +261,14 @@ export class AppStickersManager {
} */
public getStickerSetInput(set: MyStickerSetInput): InputStickerSet {
if(set.id === 'emoji') {
if(set.id === EMOJI_SET_LOCAL_ID) {
return {
_: 'inputStickerSetAnimatedEmoji'
};
} else if(set.id === EMOJI_ANIMATIONS_SET_LOCAL_ID) {
return {
_: 'inputStickerSetAnimatedEmojiAnimations'
};
} else if(!set.access_hash) {
return {
_: 'inputStickerSetShortName',

11
src/lib/rlottie/rlottiePlayer.ts

@ -132,8 +132,8 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -132,8 +132,8 @@ export default class RLottiePlayer extends EventListenerBase<{
private color: RLottieColor;
private inverseColor: RLottieColor;
private minFrame: number;
private maxFrame: number;
public minFrame: number;
public maxFrame: number;
//private playedTimes = 0;
@ -294,7 +294,7 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -294,7 +294,7 @@ export default class RLottiePlayer extends EventListenerBase<{
}
private resetCurrentFrame() {
return this.curFrame = this.initFrame || (this.direction === 1 ? this.minFrame : this.maxFrame);
return this.curFrame = this.initFrame ?? (this.direction === 1 ? this.minFrame : this.maxFrame);
}
public stop(renderFirstFrame = true) {
@ -655,6 +655,11 @@ export default class RLottiePlayer extends EventListenerBase<{ @@ -655,6 +655,11 @@ export default class RLottiePlayer extends EventListenerBase<{
};
this.addEventListener('enterFrame', this.frameListener);
// ! fix autoplaying since there will be no animationIntersector for it,
if(this.group === 'none' && this.autoplay) {
this.play();
}
}, {once: true});
}
}

23
src/lib/storage.ts

@ -41,7 +41,8 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -41,7 +41,8 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
//private cache: Partial<{[key: string]: Storage[typeof key]}> = {};
private cache: Partial<Storage> = {};
private useStorage = true;
private useStorage: boolean;
private savingFreezed: boolean;
private getPromises: Map<keyof Storage, CancellablePromise<Storage[keyof Storage]>> = new Map();
private getThrottled: () => void;
@ -59,8 +60,12 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -59,8 +60,12 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
if(AppStorage.STORAGES.length) {
this.useStorage = AppStorage.STORAGES[0].useStorage;
} else {
this.useStorage = true;
}
this.savingFreezed = false;
AppStorage.STORAGES.push(this);
this.saveThrottled = throttle(async() => {
@ -140,6 +145,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -140,6 +145,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
this.getThrottled = throttle(async() => {
const keys = Array.from(this.getPromises.keys());
// const perf = performance.now();
this.storage.get(keys as string[]).then(values => {
for(let i = 0, length = keys.length; i < length; ++i) {
const key = keys[i];
@ -150,6 +156,8 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -150,6 +156,8 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
this.getPromises.delete(key);
}
}
// console.log('[AS]: get time', keys, performance.now() - perf);
}, (error) => {
if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(error)) {
this.useStorage = false;
@ -214,6 +222,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -214,6 +222,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
public set(obj: Partial<Storage>, onlyLocal = false) {
//console.log('storageSetValue', obj, callback, arguments);
const canUseStorage = this.useStorage && !onlyLocal && !this.savingFreezed;
for(const key in obj) {
if(obj.hasOwnProperty(key)) {
const value = obj[key];
@ -233,7 +242,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -233,7 +242,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
value = stringify(value);
console.log('LocalStorage set: stringify time by own stringify:', performance.now() - perf); */
if(this.useStorage && !onlyLocal) {
if(canUseStorage) {
this.keysToSet.add(key);
this.keysToDelete.delete(key);
this.saveThrottled();
@ -241,7 +250,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -241,7 +250,7 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
}
}
return this.useStorage ? this.saveDeferred : Promise.resolve();
return canUseStorage ? this.saveDeferred : Promise.resolve();
}
public delete(key: keyof Storage, saveLocal = false) {
@ -291,6 +300,14 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D @@ -291,6 +300,14 @@ export default class AppStorage<Storage extends Record<string, any>, T extends D
})).catch(noop);
}
public static freezeSaving<T extends Database<any>>(callback: () => any, names: T['stores'][number]['name'][]) {
this.STORAGES.forEach(storage => storage.savingFreezed = true);
try {
callback();
} catch(err) {}
this.STORAGES.forEach(storage => storage.savingFreezed = false);
}
/* public deleteDatabase() {
return IDBStorage.deleteDatabase().catch(noop);
} */

56
src/lib/storages/dialogs.ts

@ -31,6 +31,8 @@ import { MyDialogFilter } from "./filters"; @@ -31,6 +31,8 @@ import { MyDialogFilter } from "./filters";
import { NULL_PEER_ID } from "../mtproto/mtproto_config";
import { NoneToVoidFunction } from "../../types";
import ctx from "../../environment/ctx";
import AppStorage from "../storage";
import type DATABASE_STATE from "../../config/databases/state";
export type FolderDialog = {
dialog: Dialog,
@ -159,37 +161,41 @@ export default class DialogsStorage { @@ -159,37 +161,41 @@ export default class DialogsStorage {
const dialogs = appStateManager.storagesResults.dialogs;
if(dialogs.length) {
for(let i = 0, length = dialogs.length; i < length; ++i) {
const dialog = dialogs[i];
if(dialog) {
// if(dialog.peerId !== SERVICE_PEER_ID) {
dialog.top_message = this.appMessagesIdsManager.getServerMessageId(dialog.top_message); // * fix outgoing message to avoid copying dialog
// }
if(dialog.topMessage) {
this.appMessagesManager.saveMessages([dialog.topMessage]);
}
for(let i = 0; i <= 10; ++i) {
// @ts-ignore
delete dialog[`index_${i}`];
}
this.saveDialog(dialog, undefined, true);
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
if(message.deleted) {
this.appMessagesManager.reloadConversation(dialog.peerId);
}
}
}
AppStorage.freezeSaving<typeof DATABASE_STATE>(this.setDialogsFromState.bind(this, dialogs), ['chats', 'dialogs', 'messages', 'users']);
}
this.allDialogsLoaded = state.allDialogsLoaded || {};
});
}
private setDialogsFromState(dialogs: Dialog[]) {
for(let i = 0, length = dialogs.length; i < length; ++i) {
const dialog = dialogs[i];
if(dialog) {
// if(dialog.peerId !== SERVICE_PEER_ID) {
dialog.top_message = this.appMessagesIdsManager.getServerMessageId(dialog.top_message); // * fix outgoing message to avoid copying dialog
// }
if(dialog.topMessage) {
this.appMessagesManager.saveMessages([dialog.topMessage]);
}
for(let i = 0; i <= 10; ++i) {
// @ts-ignore
delete dialog[`index_${i}`];
}
this.saveDialog(dialog, undefined, true);
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
if(message.deleted) {
this.appMessagesManager.reloadConversation(dialog.peerId);
}
}
}
}
public isDialogsLoaded(folderId: number) {
return !!this.allDialogsLoaded[folderId];
}

29
src/scss/partials/_emojiAnimation.scss

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
.emoji-animation {
position: absolute;
pointer-events: none;
// @include sidebar-transform(true);
&.is-in {
.rlottie {
transform: scaleX(-1);
}
}
&-container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
overflow: hidden;
z-index: 3;
}
}

1
src/scss/style.scss

@ -305,6 +305,7 @@ $chat-input-inner-padding-handhelds: .25rem; @@ -305,6 +305,7 @@ $chat-input-inner-padding-handhelds: .25rem;
@import "partials/replyKeyboard";
@import "partials/peopleNearby";
@import "partials/spoiler";
@import "partials/emojiAnimation";
@import "partials/popups/popup";
@import "partials/popups/editAvatar";

5
src/types.d.ts vendored

@ -111,3 +111,8 @@ export namespace AuthState { @@ -111,3 +111,8 @@ export namespace AuthState {
_: 'authStateSignedIn'
};
}
export type SendMessageEmojiInteractionData = {
a: {t: number, i: 1}[],
v: 1
};

Loading…
Cancel
Save