FIx unneeded webPage refresh

Fix locking scroll due to tab swipe on iOS
This commit is contained in:
morethanwords 2021-10-22 22:31:54 +04:00
parent 667fc3c9c6
commit 304bec801f
51 changed files with 179 additions and 88 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -133,7 +133,7 @@ export default class ChatBubbles {
private loadedTopTimes = 0;
private loadedBottomTimes = 0;
private messagesQueuePromise: Promise<void> = null;
public messagesQueuePromise: Promise<void> = null;
private messagesQueue: {message: any, bubble: HTMLElement, reverse: boolean, promises: Promise<void>[]}[] = [];
private messagesQueueOnRender: () => void = null;
private messagesQueueOnRenderAdditional: () => void = null;
@ -1409,6 +1409,10 @@ export default class ChatBubbles {
if(msgId > 0 && msgId <= maxId) {
const bubble = this.bubbles[msgId];
if(bubble) {
if(bubble.classList.contains('is-sending')) {
continue;
}
bubble.classList.remove('is-sent', 'is-sending'); // is-sending can be when there are bulk of updates (e.g. sending command to Stickers bot)
bubble.classList.add('is-read');
}
@ -1533,7 +1537,25 @@ export default class ChatBubbles {
}
}
return this.scrollable.scrollIntoViewNew(element, position, 4, undefined, forceDirection, forceDuration);
return this.scrollable.scrollIntoViewNew(
element,
position,
4,
undefined,
forceDirection,
forceDuration,
'y',
({rect}) => {
let height = windowSize.windowH;
height -= this.chat.topbar.container.getBoundingClientRect().height;
height -= 78;
return height;
const rowsWrapperHeight = this.chat.input.rowsWrapper.getBoundingClientRect().height;
const diff = rowsWrapperHeight - 54;
return rect.height + diff;
}
);
}
public scrollToBubbleEnd(bubble = this.getLastBubble()) {

View File

@ -82,7 +82,6 @@ import PopupPeer from '../popups/peer';
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
import appMediaPlaybackController from '../appMediaPlaybackController';
import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config';
import replaceContent from '../../helpers/dom/replaceContent';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -776,6 +775,14 @@ export default class ChatInput {
draft = this.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId);
if(!draft) {
if(force) { // this situation can only happen when sending message with clearDraft
((this.chat.bubbles.messagesQueuePromise || Promise.resolve()) as Promise<any>).then(() => {
fastRaf(() => {
this.onMessageSent();
});
});
}
return false;
}
}
@ -921,6 +928,7 @@ export default class ChatInput {
private attachMessageInputListeners() {
this.listenerSetter.add(this.messageInput)('keydown', (e: KeyboardEvent) => {
if(isSendShortcutPressed(e)) {
cancelEvent(e);
this.sendMessage();
} else if(e.ctrlKey || e.metaKey) {
this.handleMarkdownShortcut(e);
@ -1758,6 +1766,8 @@ export default class ChatInput {
entities,
noWebPage: this.noWebPage
});
this.onMessageSent();
} else {
new PopupDeleteMessages(this.chat.peerId, [this.editMsgId], this.chat.type);
@ -1774,6 +1784,9 @@ export default class ChatInput {
silent: this.sendSilent,
clearDraft: true
});
this.onMessageSent(false, false);
// this.onMessageSent();
}
// * wait for sendText set messageId for invokeAfterMsg
@ -1792,7 +1805,7 @@ export default class ChatInput {
}, 0);
}
this.onMessageSent();
// this.onMessageSent();
}
public sendMessageWithDocument(document: MyDocument | string, force = false, clearDraft = false) {
@ -1934,12 +1947,12 @@ export default class ChatInput {
if(message._ === 'messageEmpty') { // load missing replying message
peerTitleEl = i18n('Loading');
this.chat.appMessagesManager.wrapSingleMessage(this.chat.peerId, mid).then(() => {
this.chat.appMessagesManager.wrapSingleMessage(this.chat.peerId, mid).then((_message) => {
if(this.replyToMsgId !== mid) {
return;
}
message = this.chat.getMessage(mid);
message = _message;
if(message._ === 'messageEmpty') {
this.clearHelper('reply');
} else {

View File

@ -93,7 +93,7 @@ class InputField {
public validate: () => boolean;
//public onLengthChange: (length: number, isOverflow: boolean) => void;
protected wasInputFakeClientHeight: number;
// protected wasInputFakeClientHeight: number;
// protected showScrollDebounced: () => void;
constructor(public options: InputFieldOptions = {}) {
@ -147,7 +147,7 @@ class InputField {
if(options.animate) {
input.classList.add('scrollable', 'scrollable-y');
this.wasInputFakeClientHeight = 0;
// this.wasInputFakeClientHeight = 0;
// this.showScrollDebounced = debounce(() => this.input.classList.remove('no-scrollbar'), 150, false, true);
this.inputFake = document.createElement('div');
this.inputFake.setAttribute('contenteditable', 'true');
@ -237,14 +237,21 @@ class InputField {
}
public onFakeInput() {
const {scrollHeight, clientHeight} = this.inputFake;
const {scrollHeight: newHeight/* , clientHeight */} = this.inputFake;
/* if(this.wasInputFakeClientHeight && this.wasInputFakeClientHeight !== clientHeight) {
this.input.classList.add('no-scrollbar'); // ! в сафари может вообще не появиться скролл после анимации, так как ему нужен полный reflow блока с overflow.
this.showScrollDebounced();
} */
this.wasInputFakeClientHeight = clientHeight;
this.input.style.height = scrollHeight ? scrollHeight + 'px' : '';
const TRANSITION_DURATION_FACTOR = 50;
const currentHeight = +this.input.style.height.replace('px', '');
const transitionDuration = Math.round(
TRANSITION_DURATION_FACTOR * Math.log(Math.abs(newHeight - currentHeight)),
);
// this.wasInputFakeClientHeight = clientHeight;
this.input.style.transitionDuration = `${transitionDuration}ms`;
this.input.style.height = newHeight ? newHeight + 'px' : '';
}
get value() {

View File

@ -6,7 +6,7 @@
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
import { logger, LogTypes } from "../lib/logger";
import fastSmoothScroll, { FocusDirection } from "../helpers/fastSmoothScroll";
import fastSmoothScroll, { FocusDirection, ScrollGetNormalSizeCallback } from "../helpers/fastSmoothScroll";
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
import { cancelEvent } from "../helpers/dom/cancelEvent";
/*
@ -106,10 +106,11 @@ export class ScrollableBase {
maxDistance?: number,
forceDirection?: FocusDirection,
forceDuration?: number,
axis?: 'x' | 'y'
axis?: 'x' | 'y',
getNormalSize?: ScrollGetNormalSizeCallback
) {
//return Promise.resolve();
return fastSmoothScroll(this.container, element, position, margin, maxDistance, forceDirection, forceDuration, axis);
return fastSmoothScroll(this.container, element, position, margin, maxDistance, forceDirection, forceDuration, axis, getNormalSize);
}
}

View File

@ -5,7 +5,6 @@
*/
import SwipeHandler, { SwipeHandlerOptions } from "../../components/swipeHandler";
import { IS_APPLE_MOBILE, IS_SAFARI } from "../../environment/userAgent";
import { cancelEvent } from "./cancelEvent";
import findUpClassName from "./findUpClassName";
import isSwipingBackSafari from "./isSwipingBackSafari";
@ -45,6 +44,6 @@ export default function handleHorizontalSwipe(options: SwipeHandlerHorizontalOpt
cancelY = false;
options.onReset && options.onReset();
},
cancelEvent: true
cancelEvent: false // cannot use cancelEvent on Safari iOS because scroll will be canceled too
});
}

View File

@ -12,9 +12,10 @@ import { animateSingle, cancelAnimationByKey } from './animation';
import rootScope from '../lib/rootScope';
import isInDOM from './dom/isInDOM';
const MAX_DISTANCE = 1500;
const MIN_JS_DURATION = 250;
const MAX_JS_DURATION = 600;
const LONG_TRANSITION_MAX_DISTANCE = 1500;
const SHORT_TRANSITION_MAX_DISTANCE = 500;
export enum FocusDirection {
Up,
@ -22,15 +23,18 @@ export enum FocusDirection {
Static,
};
export type ScrollGetNormalSizeCallback = (options: {rect: DOMRect}) => number;
export default function fastSmoothScroll(
container: HTMLElement,
element: HTMLElement,
position: ScrollLogicalPosition,
margin = 0,
maxDistance = MAX_DISTANCE,
maxDistance = LONG_TRANSITION_MAX_DISTANCE,
forceDirection?: FocusDirection,
forceDuration?: number,
axis: 'x' | 'y' = 'y'
axis: 'x' | 'y' = 'y',
getNormalSize?: ScrollGetNormalSizeCallback
) {
//return;
@ -40,7 +44,7 @@ export default function fastSmoothScroll(
if(forceDirection === FocusDirection.Static) {
forceDuration = 0;
return scrollWithJs(container, element, position, margin, forceDuration, axis);
return scrollWithJs(container, element, position, margin, forceDuration, axis, getNormalSize);
/* return Promise.resolve();
element.scrollIntoView({ block: position });
@ -82,9 +86,9 @@ export default function fastSmoothScroll(
} */
}
const promise = new Promise((resolve) => {
const promise = new Promise<void>((resolve) => {
fastRaf(() => {
scrollWithJs(container, element, position, margin, forceDuration, axis)
scrollWithJs(container, element, position, margin, forceDuration, axis, getNormalSize)
.then(resolve);
});
});
@ -93,7 +97,13 @@ export default function fastSmoothScroll(
}
function scrollWithJs(
container: HTMLElement, element: HTMLElement, position: ScrollLogicalPosition, margin = 0, forceDuration?: number, axis: 'x' | 'y' = 'y'
container: HTMLElement,
element: HTMLElement,
position: ScrollLogicalPosition,
margin = 0,
forceDuration?: number,
axis: 'x' | 'y' = 'y',
getNormalSize?: ScrollGetNormalSizeCallback
) {
if(!isInDOM(element)) {
cancelAnimationByKey(container);
@ -115,7 +125,7 @@ function scrollWithJs(
const elementPosition = elementRect[rectStartKey] - containerRect[rectStartKey];
const elementSize = element[scrollSizeKey]; // margin is exclusive in DOMRect
const containerSize = containerRect[sizeKey];
const containerSize = getNormalSize ? getNormalSize({rect: containerRect}) : containerRect[sizeKey];
const scrollPosition = container[scrollPositionKey];
const scrollSize = container[scrollSizeKey];
@ -177,8 +187,9 @@ function scrollWithJs(
}
const target = container[scrollPositionKey] + path;
const absPath = Math.abs(path);
const duration = forceDuration ?? (
MIN_JS_DURATION + (Math.abs(path) / MAX_DISTANCE) * (MAX_JS_DURATION - MIN_JS_DURATION)
MIN_JS_DURATION + (absPath / LONG_TRANSITION_MAX_DISTANCE) * (MAX_JS_DURATION - MIN_JS_DURATION)
);
const startAt = Date.now();
@ -222,6 +233,7 @@ function scrollWithJs(
//transformable.style.minHeight = `${transformableHeight}px`;
*/
const transition = absPath < SHORT_TRANSITION_MAX_DISTANCE ? shortTransition : longTransition;
const tick = () => {
const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1;
@ -258,6 +270,10 @@ function scrollWithJs(
return animateSingle(tick, container);
}
function transition(t: number) {
function longTransition(t: number) {
return 1 - ((1 - t) ** 5);
}
function shortTransition(t: number) {
return 1 - ((1 - t) ** 3.5);
}

4
src/layer.d.ts vendored
View File

@ -816,7 +816,9 @@ export namespace Message {
flags?: number,
id: number,
peer_id?: Peer,
deleted?: boolean
deleted?: boolean,
mid?: number,
pFlags?: {}
};
export type message = {

View File

@ -251,7 +251,7 @@ export class AppDraftsManager {
if(threadId) {
this.syncDraft(peerId, threadId);
} else {
this.saveDraft(peerId, threadId, null, {notify: true/* , force: true */});
this.saveDraft(peerId, threadId, null, {notify: true, force: true});
}
}

View File

@ -501,16 +501,22 @@ export class AppImManager {
return;
}
if(chat.input.messageInput &&
if(
chat?.input?.messageInput &&
e.target !== chat.input.messageInput &&
target.tagName !== 'INPUT' &&
!target.hasAttribute('contenteditable') &&
!IS_TOUCH_SUPPORTED &&
(!mediaSizes.isMobile || this.tabId === 1) &&
!this.chat.selection.isSelecting &&
!this.chat.input.recording) {
!chat.selection.isSelecting &&
!chat.input.recording
) {
chat.input.messageInput.focus();
placeCaretAtEnd(chat.input.messageInput);
// clone and dispatch same event to new input. it is needed for sending message if input was blurred
const newEvent = new KeyboardEvent(e.type, e);
chat.input.messageInput.dispatchEvent(newEvent);
}
};

View File

@ -1073,7 +1073,7 @@ export class AppMessagesManager {
const messages = files.map((file, idx) => {
const details = options.sendFileDetails[idx];
const o: any = {
const o: Parameters<AppMessagesManager['sendFile']>[2] = {
isGroupedItem: true,
isMedia: options.isMedia,
scheduleDate: options.scheduleDate,
@ -1094,7 +1094,9 @@ export class AppMessagesManager {
});
if(options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
setTimeout(() => {
appDraftsManager.clearDraft(peerId, options.threadId);
}, 0);
}
// * test pending
@ -1407,10 +1409,6 @@ export class AppMessagesManager {
}, 0);
}
if(!options.isGroupedItem && options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
}
this.pendingByRandomId[message.random_id] = {
peerId,
tempId: messageId,
@ -1419,9 +1417,13 @@ export class AppMessagesManager {
};
if(!options.isGroupedItem && message.send) {
setTimeout(message.send, 0);
//setTimeout(message.send, 4000);
//setTimeout(message.send, 7000);
setTimeout(() => {
if(options.clearDraft) {
appDraftsManager.clearDraft(peerId, options.threadId);
}
message.send();
}, 0);
}
}
@ -1966,15 +1968,20 @@ export class AppMessagesManager {
return promise;
}
public getMessageFromStorage(storage: MessagesStorage, mid: number) {
return storage && storage.get(mid) || {
public generateEmptyMessage(mid: number): Message.messageEmpty {
return {
_: 'messageEmpty',
id: mid,
id: appMessagesIdsManager.getServerMessageId(mid),
mid,
deleted: true,
pFlags: {}
};
}
public getMessageFromStorage(storage: MessagesStorage, mid: number) {
return storage && storage.get(mid) || this.generateEmptyMessage(mid);
}
private createMessageStorage() {
const storage: MessagesStorage = new Map();
@ -5455,7 +5462,6 @@ export class AppMessagesManager {
for(const [peerId, map] of this.needSingleMessages) {
const mids = [...map.keys()];
const promises = [...map.values()];
const msgIds: InputMessage[] = mids.map((mid) => {
return {
_: 'inputMessageID',
@ -5483,8 +5489,17 @@ export class AppMessagesManager {
this.saveMessages(getMessagesResult.messages);
for(let i = 0; i < getMessagesResult.messages.length; ++i) {
const promise = promises[i];
const message = getMessagesResult.messages[i];
const mid = appMessagesIdsManager.generateMessageId(message.id);
const promise = map.get(mid);
promise.resolve(getMessagesResult.messages[i]);
map.delete(mid);
}
if(map.size) {
for(const [mid, promise] of map) {
promise.resolve(this.generateEmptyMessage(mid));
}
}
}).finally(() => {
rootScope.dispatchEvent('messages_downloaded', {peerId, mids});
@ -5497,7 +5512,7 @@ export class AppMessagesManager {
Promise.all(requestPromises).finally(() => {
this.fetchSingleMessagesPromise = null;
if(Object.keys(this.needSingleMessages).length) this.fetchSingleMessages();
if(this.needSingleMessages.size) this.fetchSingleMessages();
resolve();
});
}, 0);

View File

@ -43,6 +43,13 @@ export class AppWebPagesManager {
if(apiWebPage._ === 'webPageNotModified') return;
const {id} = apiWebPage;
const oldWebPage = this.webpages[id];
if(oldWebPage &&
oldWebPage._ === apiWebPage._ &&
(oldWebPage as WebPage.webPage).hash === (oldWebPage as WebPage.webPage).hash) {
return oldWebPage;
}
if(apiWebPage._ === 'webPage') {
if(apiWebPage.photo?._ === 'photo') {
apiWebPage.photo = appPhotosManager.savePhoto(apiWebPage.photo, mediaContext);
@ -97,10 +104,10 @@ export class AppWebPagesManager {
pendingSet.add(messageKey);
}
if(this.webpages[id] === undefined) {
if(oldWebPage === undefined) {
this.webpages[id] = apiWebPage;
} else {
safeReplaceObject(this.webpages[id], apiWebPage);
safeReplaceObject(oldWebPage, apiWebPage);
}
if(!messageKey && pendingSet !== undefined) {

View File

@ -99,7 +99,9 @@
}, {
"predicate": "messageEmpty",
"params": [
{"name": "deleted", "type": "boolean"}
{"name": "deleted", "type": "boolean"},
{"name": "mid", "type": "number"},
{"name": "pFlags", "type": "{}"}
]
}, {
"predicate": "userFull",

View File

@ -917,7 +917,8 @@ $chat-helper-size: 36px;
pointer-events: none;
@include animation-level(2) {
transition: height var(--layer-transition), opacity var(--layer-transition);
// transition: height var(--layer-transition), opacity var(--layer-transition);
transition: height .15s ease-out, opacity .15s ease-out;
}
@include respond-to(esg-bottom-new) {