Browse Source

Calls improvements

master
Eduard Kuzmenko 3 years ago
parent
commit
c4827d50f6
  1. 5
      src/components/avatar.ts
  2. 1
      src/components/call/description.ts
  3. 42
      src/components/call/index.ts
  4. 4
      src/components/chat/patternRenderer.ts
  5. 2
      src/components/chat/reactions.ts
  6. 16
      src/components/peerTitle.ts
  7. 20
      src/components/popups/index.ts
  8. 3
      src/components/sidebarLeft/tabs/dataAndStorage.ts
  9. 25
      src/components/topbarCall.ts
  10. 5
      src/helpers/eventListenerBase.ts
  11. 4
      src/index.ts
  12. 4
      src/lib/appManagers/appAvatarsManager.ts
  13. 24
      src/lib/appManagers/appCallsManager.ts
  14. 45
      src/lib/appManagers/appImManager.ts
  15. 7
      src/lib/appManagers/appPeersManager.ts
  16. 2
      src/lib/appManagers/appReactionsManager.ts
  17. 29
      src/lib/calls/callInstance.ts
  18. 8
      src/lib/calls/callInstanceBase.ts
  19. 2
      src/lib/calls/groupCallInstance.ts
  20. 2
      src/lib/richtextprocessor.ts
  21. 5
      src/lib/rootScope.ts
  22. 16
      src/scss/partials/_avatar.scss
  23. 3
      src/scss/partials/_chatBubble.scss
  24. 36
      src/scss/partials/_chatTopbar.scss
  25. 7
      src/scss/partials/_fonts.scss
  26. 50
      src/scss/partials/popups/_call.scss

5
src/components/avatar.ts

@ -119,10 +119,11 @@ const seen: Set<PeerId> = new Set(); @@ -119,10 +119,11 @@ const seen: Set<PeerId> = new Set();
export default class AvatarElement extends HTMLElement {
private peerId: PeerId;
private isDialog = false;
private isDialog: boolean;
private peerTitle: string;
public loadPromises: Promise<any>[];
public lazyLoadQueue: LazyLoadQueueIntersector;
public isBig: boolean;
private addedToQueue = false;
connectedCallback() {
@ -196,7 +197,7 @@ export default class AvatarElement extends HTMLElement { @@ -196,7 +197,7 @@ export default class AvatarElement extends HTMLElement {
}
private r(onlyThumb = false) {
const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb);
const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb, this.isBig);
const promise = res ? res.loadPromise : Promise.resolve();
if(this.loadPromises) {
if(res && res.cached) {

1
src/components/call/description.ts

@ -75,6 +75,7 @@ export default class CallDescriptionElement { @@ -75,6 +75,7 @@ export default class CallDescriptionElement {
}
}
this.container.classList.toggle('has-duration', connectionState === CALL_STATE.CONNECTED);
replaceContent(this.container, element);
if(!this.container.parentElement) {

42
src/components/call/index.ts

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
*/
import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport";
import { IS_MOBILE } from "../../environment/userAgent";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
import ControlsHover from "../../helpers/dom/controlsHover";
import findUpClassName from "../../helpers/dom/findUpClassName";
@ -14,6 +15,7 @@ import MovablePanel from "../../helpers/movablePanel"; @@ -14,6 +15,7 @@ import MovablePanel from "../../helpers/movablePanel";
import safeAssign from "../../helpers/object/safeAssign";
import toggleClassName from "../../helpers/toggleClassName";
import type { AppAvatarsManager } from "../../lib/appManagers/appAvatarsManager";
import type { AppCallsManager } from "../../lib/appManagers/appCallsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import CallInstance from "../../lib/calls/callInstance";
import CALL_STATE from "../../lib/calls/callState";
@ -21,6 +23,7 @@ import I18n, { i18n } from "../../lib/langPack"; @@ -21,6 +23,7 @@ import I18n, { i18n } from "../../lib/langPack";
import RichTextProcessor from "../../lib/richtextprocessor";
import rootScope from "../../lib/rootScope";
import animationIntersector from "../animationIntersector";
import AvatarElement from "../avatar";
import ButtonIcon from "../buttonIcon";
import GroupCallMicrophoneIconMini from "../groupCall/microphoneIconMini";
import { MovableState } from "../movableElement";
@ -40,6 +43,7 @@ let previousState: MovableState = { @@ -40,6 +43,7 @@ let previousState: MovableState = {
export default class PopupCall extends PopupElement {
private instance: CallInstance;
private appCallsManager: AppCallsManager;
private appAvatarsManager: AppAvatarsManager;
private appPeersManager: AppPeersManager;
private peerId: PeerId;
@ -76,6 +80,7 @@ export default class PopupCall extends PopupElement { @@ -76,6 +80,7 @@ export default class PopupCall extends PopupElement {
private controlsHover: ControlsHover;
constructor(options: {
appCallsManager: AppCallsManager,
appAvatarsManager: AppAvatarsManager,
appPeersManager: AppPeersManager,
instance: CallInstance
@ -96,8 +101,11 @@ export default class PopupCall extends PopupElement { @@ -96,8 +101,11 @@ export default class PopupCall extends PopupElement {
avatarContainer.classList.add(className + '-avatar');
const peerId = this.peerId = this.instance.interlocutorUserId.toPeerId();
const photo = this.appPeersManager.getPeerPhoto(peerId);
this.appAvatarsManager.putAvatar(avatarContainer, peerId, photo, 'photo_big');
const avatar = new AvatarElement();
avatar.isBig = true;
avatar.setAttribute('peer', '' + peerId);
avatar.classList.add('avatar-full');
avatarContainer.append(avatar);
const title = new PeerTitle({
peerId
@ -113,22 +121,28 @@ export default class PopupCall extends PopupElement { @@ -113,22 +121,28 @@ export default class PopupCall extends PopupElement {
const emojisSubtitle = this.emojisSubtitle = document.createElement('div');
emojisSubtitle.classList.add(className + '-emojis');
container.append(avatarContainer, title, subtitle, emojisSubtitle);
container.append(avatarContainer, title, subtitle);
this.btnFullScreen = ButtonIcon('fullscreen');
this.btnExitFullScreen = ButtonIcon('smallscreen hide');
attachClickEvent(this.btnFullScreen, this.onFullScreenClick, {listenerSetter});
attachClickEvent(this.btnExitFullScreen, () => cancelFullScreen(), {listenerSetter});
addFullScreenListener(this.container, this.onFullScreenChange, listenerSetter);
this.header.prepend(this.btnExitFullScreen);
this.header.append(this.btnFullScreen);
if(!IS_MOBILE) {
this.btnFullScreen = ButtonIcon('fullscreen');
this.btnExitFullScreen = ButtonIcon('smallscreen hide');
attachClickEvent(this.btnFullScreen, this.onFullScreenClick, {listenerSetter});
attachClickEvent(this.btnExitFullScreen, () => cancelFullScreen(), {listenerSetter});
addFullScreenListener(this.container, this.onFullScreenChange, listenerSetter);
this.header.prepend(this.btnExitFullScreen);
this.header.append(this.btnFullScreen);
container.append(emojisSubtitle);
} else {
this.header.append(emojisSubtitle);
}
this.partyStates = document.createElement('div');
this.partyStates.classList.add(className + '-party-states');
this.partyMutedState = document.createElement('div');
this.partyMutedState.classList.add(className + '-party-state');
const stateText = i18n('VoipUserMicrophoneIsOff', [new PeerTitle({peerId, onlyFirstName: true}).element]);
const stateText = i18n('VoipUserMicrophoneIsOff', [new PeerTitle({peerId, onlyFirstName: true, limitSymbols: 18}).element]);
stateText.classList.add(className + '-party-state-text');
const mutedIcon = new GroupCallMicrophoneIconMini(false, true);
mutedIcon.setState(false, false);
@ -193,6 +207,10 @@ export default class PopupCall extends PopupElement { @@ -193,6 +207,10 @@ export default class PopupCall extends PopupElement {
this.updateInstance();
}
public getCallInstance() {
return this.instance;
}
private constructFirstButtons() {
const buttons = this.firstButtonsRow = document.createElement('div');
buttons.classList.add(className + '-buttons', 'is-first');
@ -260,7 +278,7 @@ export default class PopupCall extends PopupElement { @@ -260,7 +278,7 @@ export default class PopupCall extends PopupElement {
const btnAccept = this.btnAccept = this.makeButton({
text: 'Call.Accept',
icon: 'phone',
icon: 'phone_filled',
callback: () => {
this.instance.acceptCall();
},

4
src/components/chat/patternRenderer.ts

@ -5,9 +5,9 @@ @@ -5,9 +5,9 @@
*/
import { IS_SAFARI } from "../../environment/userAgent";
import { indexOfAndSplice } from "../../helpers/array";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import { deepEqual } from "../../helpers/object";
import deepEqual from "../../helpers/object/deepEqual";
type ChatBackgroundPatternRendererInitOptions = {
url: string,

2
src/components/chat/reactions.ts

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { forEachReverse } from "../../helpers/array";
import forEachReverse from "../../helpers/array/forEachReverse";
import positionElementByIndex from "../../helpers/dom/positionElementByIndex";
import { Message, ReactionCount } from "../../layer";
import appReactionsManager from "../../lib/appManagers/appReactionsManager";

16
src/components/peerTitle.ts

@ -12,13 +12,15 @@ import replaceContent from "../helpers/dom/replaceContent"; @@ -12,13 +12,15 @@ import replaceContent from "../helpers/dom/replaceContent";
import appUsersManager from "../lib/appManagers/appUsersManager";
import RichTextProcessor from "../lib/richtextprocessor";
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
import { limitSymbols } from "../helpers/string";
export type PeerTitleOptions = {
peerId?: PeerId,
fromName?: string,
plainText?: boolean,
onlyFirstName?: boolean,
dialog?: boolean
dialog?: boolean,
limitSymbols?: number
};
const weakMap: WeakMap<HTMLElement, PeerTitle> = new WeakMap();
@ -44,6 +46,7 @@ export default class PeerTitle { @@ -44,6 +46,7 @@ export default class PeerTitle {
public plainText = false;
public onlyFirstName = false;
public dialog = false;
public limitSymbols: number;
constructor(options: PeerTitleOptions) {
this.element = document.createElement('span');
@ -64,8 +67,13 @@ export default class PeerTitle { @@ -64,8 +67,13 @@ export default class PeerTitle {
}
}
if(this.fromName !== undefined) {
this.element.innerHTML = RichTextProcessor.wrapEmojiText(this.fromName);
let fromName = this.fromName;
if(fromName !== undefined) {
if(this.limitSymbols !== undefined) {
fromName = limitSymbols(fromName, this.limitSymbols, this.limitSymbols);
}
this.element.innerHTML = RichTextProcessor.wrapEmojiText(fromName);
return;
}
@ -77,7 +85,7 @@ export default class PeerTitle { @@ -77,7 +85,7 @@ export default class PeerTitle {
if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName'));
} else {
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName);
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName, this.limitSymbols);
}
} else {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Saved' : 'SavedMessages'));

20
src/components/popups/index.ts

@ -15,7 +15,7 @@ import ListenerSetter from "../../helpers/listenerSetter"; @@ -15,7 +15,7 @@ import ListenerSetter from "../../helpers/listenerSetter";
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
import isSendShortcutPressed from "../../helpers/dom/isSendShortcutPressed";
import { cancelEvent } from "../../helpers/dom/cancelEvent";
import EventListenerBase from "../../helpers/eventListenerBase";
import EventListenerBase, { EventListenerListeners } from "../../helpers/eventListenerBase";
import { addFullScreenListener, getFullScreenElement } from "../../helpers/dom/fullScreen";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
@ -52,11 +52,13 @@ const onFullScreenChange = () => { @@ -52,11 +52,13 @@ const onFullScreenChange = () => {
addFullScreenListener(DEFAULT_APPEND_TO, onFullScreenChange);
export default class PopupElement extends EventListenerBase<{
type PopupListeners = {
close: () => void,
closeAfterTimeout: () => void
}> {
private static POPUPS: PopupElement[] = [];
};
export default class PopupElement<T extends EventListenerListeners = {}> extends EventListenerBase<PopupListeners & T> {
private static POPUPS: PopupElement<any>[] = [];
protected element = document.createElement('div');
protected container = document.createElement('div');
protected header = document.createElement('div');
@ -181,7 +183,7 @@ export default class PopupElement extends EventListenerBase<{ @@ -181,7 +183,7 @@ export default class PopupElement extends EventListenerBase<{
public show() {
this.navigationItem = {
type: 'popup',
onPop: this.destroy,
onPop: () => this.destroy(),
onEscape: this.onEscape
};
@ -214,8 +216,8 @@ export default class PopupElement extends EventListenerBase<{ @@ -214,8 +216,8 @@ export default class PopupElement extends EventListenerBase<{
appNavigationController.backByItem(this.navigationItem);
};
private destroy = () => {
this.dispatchEvent('close');
protected destroy() {
this.dispatchEvent<PopupListeners>('close');
this.element.classList.add('hiding');
this.element.classList.remove('active');
this.listenerSetter.removeAll();
@ -234,14 +236,14 @@ export default class PopupElement extends EventListenerBase<{ @@ -234,14 +236,14 @@ export default class PopupElement extends EventListenerBase<{
setTimeout(() => {
this.element.remove();
this.dispatchEvent('closeAfterTimeout');
this.dispatchEvent<PopupListeners>('closeAfterTimeout');
this.cleanup();
if(!this.withoutOverlay) {
animationIntersector.checkAnimations(false);
}
}, 150);
};
}
public static reAppend() {
this.POPUPS.forEach(popup => {

3
src/components/sidebarLeft/tabs/dataAndStorage.ts

@ -9,7 +9,8 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent"; @@ -9,7 +9,8 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import replaceContent from "../../../helpers/dom/replaceContent";
import toggleDisability from "../../../helpers/dom/toggleDisability";
import formatBytes from "../../../helpers/formatBytes";
import { copy, deepEqual } from "../../../helpers/object";
import copy from "../../../helpers/object/copy";
import deepEqual from "../../../helpers/object/deepEqual";
import appStateManager, { AutoDownloadPeerTypeSettings, STATE_INIT } from "../../../lib/appManagers/appStateManager";
import { FormatterArguments, i18n, join, LangPackKey } from "../../../lib/langPack";
import rootScope from "../../../lib/rootScope";

25
src/components/topbarCall.ts

@ -29,6 +29,7 @@ import PopupCall from "./call"; @@ -29,6 +29,7 @@ import PopupCall from "./call";
import type { AppAvatarsManager } from "../lib/appManagers/appAvatarsManager";
import GroupCallMicrophoneIconMini from "./groupCall/microphoneIconMini";
import CallInstance from "../lib/calls/callInstance";
import type { AppCallsManager } from "../lib/appManagers/appCallsManager";
function convertCallStateToGroupState(state: CALL_STATE, isMuted: boolean) {
switch(state) {
@ -64,11 +65,18 @@ export default class TopbarCall { @@ -64,11 +65,18 @@ export default class TopbarCall {
private appPeersManager: AppPeersManager,
private appChatsManager: AppChatsManager,
private appAvatarsManager: AppAvatarsManager,
private appCallsManager: AppCallsManager
) {
const listenerSetter = this.listenerSetter = new ListenerSetter();
listenerSetter.add(rootScope)('call_instance', ({instance, hasCurrent}) => {
if(!hasCurrent) {
listenerSetter.add(rootScope)('call_instance', ({instance}) => {
if(!this.instance) {
this.updateInstance(instance);
}
});
listenerSetter.add(rootScope)('call_accepting', (instance) => {
if(this.instance !== instance) {
this.updateInstance(instance);
}
});
@ -121,7 +129,8 @@ export default class TopbarCall { @@ -121,7 +129,8 @@ export default class TopbarCall {
this.construct = undefined;
}
if(this.instance !== instance) {
const isChangingInstance = this.instance !== instance;
if(isChangingInstance) {
this.clearCurrentInstance();
this.instance = instance;
@ -135,6 +144,8 @@ export default class TopbarCall { @@ -135,6 +144,8 @@ export default class TopbarCall {
this.currentDescription = this.callDescription;
this.instanceListenerSetter.add(instance)('muted', this.onState);
}
this.container.classList.toggle('is-call', !(instance instanceof GroupCallInstance));
}
const isMuted = this.instance.isMuted;
@ -145,7 +156,7 @@ export default class TopbarCall { @@ -145,7 +156,7 @@ export default class TopbarCall {
weave.componentDidMount();
const isClosed = state === GROUP_CALL_STATE.CLOSED;
if(!document.body.classList.contains('is-calling') || isClosed) {
if((!document.body.classList.contains('is-calling') || isChangingInstance) || isClosed) {
if(isClosed) {
weave.setAmplitude(0);
}
@ -257,7 +268,13 @@ export default class TopbarCall { @@ -257,7 +268,13 @@ export default class TopbarCall {
appChatsManager: this.appChatsManager
}).show();
} else if(this.instance instanceof CallInstance) {
const hasPopup = PopupElement.getPopup(PopupCall) as PopupCall;
if(hasPopup && hasPopup.getCallInstance() === this.instance) {
return;
}
new PopupCall({
appCallsManager: this.appCallsManager,
appAvatarsManager: this.appAvatarsManager,
appPeersManager: this.appPeersManager,
instance: this.instance

5
src/helpers/eventListenerBase.ts

@ -50,6 +50,8 @@ import type { ArgumentTypes, SuperReturnType } from "../types"; @@ -50,6 +50,8 @@ import type { ArgumentTypes, SuperReturnType } from "../types";
// MOUNT_CLASS_TO.e = e;
export type EventListenerListeners = Record<string, Function>;
// export type EventListenerListeners = Record<string, (...args: any[]) => any>;
// export type EventListenerListeners = {[name in string]: Function};
/**
* Better not to remove listeners during setting
@ -151,7 +153,8 @@ export default class EventListenerBase<Listeners extends EventListenerListeners> @@ -151,7 +153,8 @@ export default class EventListenerBase<Listeners extends EventListenerListeners>
}
// * must be protected, but who cares
public dispatchEvent<T extends keyof Listeners>(name: T, ...args: ArgumentTypes<Listeners[T]>) {
public dispatchEvent<L extends EventListenerListeners = Listeners, T extends keyof L = keyof L>(name: T, ...args: ArgumentTypes<L[T]>) {
// @ts-ignore
this._dispatchEvent(name, false, ...args);
}

4
src/index.ts

@ -170,6 +170,10 @@ console.timeEnd('get storage1'); */ @@ -170,6 +170,10 @@ console.timeEnd('get storage1'); */
document.documentElement.classList.add('is-firefox');
}
if(userAgent.IS_MOBILE) {
document.documentElement.classList.add('is-mobile');
}
if(userAgent.IS_APPLE) {
if(userAgent.IS_SAFARI) {
document.documentElement.classList.add('is-safari');

4
src/lib/appManagers/appAvatarsManager.ts

@ -169,7 +169,7 @@ export class AppAvatarsManager { @@ -169,7 +169,7 @@ export class AppAvatarsManager {
}
// peerId === peerId || title
public putPhoto(div: HTMLElement, peerId: PeerId, isDialog = false, title = '', onlyThumb = false) {
public putPhoto(div: HTMLElement, peerId: PeerId, isDialog = false, title = '', onlyThumb = false, isBig?: boolean) {
const myId = rootScope.myId;
//console.log('loadDialogPhoto location:', location, inputPeer);
@ -213,7 +213,7 @@ export class AppAvatarsManager { @@ -213,7 +213,7 @@ export class AppAvatarsManager {
}
if(avatarAvailable/* && false */) {
const size: PeerPhotoSize = 'photo_small';
const size: PeerPhotoSize = isBig ? 'photo_big' : 'photo_small';
return this.putAvatar(div, peerId, photo, size, undefined, onlyThumb);
}
}

24
src/lib/appManagers/appCallsManager.ts

@ -11,6 +11,8 @@ @@ -11,6 +11,8 @@
import { MOUNT_CLASS_TO } from "../../config/debug";
import IS_CALL_SUPPORTED from "../../environment/callSupport";
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
import insertInDescendSortedArray from "../../helpers/array/insertInDescendSortedArray";
import AudioAssetPlayer from "../../helpers/audioAssetPlayer";
import bytesCmp from "../../helpers/bytes/bytesCmp";
import safeReplaceObject from "../../helpers/object/safeReplaceObject";
@ -39,6 +41,7 @@ export class AppCallsManager { @@ -39,6 +41,7 @@ export class AppCallsManager {
private log: ReturnType<typeof logger>;
private calls: Map<CallId, MyPhoneCall>;
private instances: Map<CallId, CallInstance>;
private sortedInstances: Array<CallInstance>;
private tempId: number;
private audioAsset: AudioAssetPlayer<CallAudioAssetName>;
@ -48,6 +51,7 @@ export class AppCallsManager { @@ -48,6 +51,7 @@ export class AppCallsManager {
this.tempId = 0;
this.calls = new Map();
this.instances = new Map();
this.sortedInstances = [];
if(!IS_CALL_SUPPORTED) {
return;
@ -139,15 +143,7 @@ export class AppCallsManager { @@ -139,15 +143,7 @@ export class AppCallsManager {
}
public get currentCall() {
let lastInstance: CallInstance;
for(const [callId, instance] of this.instances) {
lastInstance = instance;
if(instance.connectionState !== CALL_STATE.PENDING) {
break;
}
}
return lastInstance;
return this.sortedInstances[0];
}
public getCallByUserId(userId: UserId) {
@ -207,15 +203,17 @@ export class AppCallsManager { @@ -207,15 +203,17 @@ export class AppCallsManager {
...options,
});
let wasTryingToJoin = false;
call.addEventListener('state', (state) => {
const currentCall = this.currentCall;
if(state === CALL_STATE.CLOSED) {
this.instances.delete(call.id);
indexOfAndSplice(this.sortedInstances, call);
} else {
insertInDescendSortedArray(this.sortedInstances, call, 'sortIndex');
}
if(state === CALL_STATE.EXCHANGING_KEYS) {
wasTryingToJoin = true;
call.wasTryingToJoin = true;
}
const hasConnected = call.connectedAt !== undefined;
@ -227,9 +225,9 @@ export class AppCallsManager { @@ -227,9 +225,9 @@ export class AppCallsManager {
if(currentCall === call || !currentCall) {
if(state === CALL_STATE.CLOSED) {
if(!call.isOutgoing && !wasTryingToJoin) { // incoming call has been accepted on other device or ended
if(!call.isOutgoing && !call.wasTryingToJoin) { // incoming call has been accepted on other device or ended
this.audioAsset.stopSound();
} else if(wasTryingToJoin && !hasConnected) { // something has happened during the key exchanging
} else if(call.wasTryingToJoin && !hasConnected) { // something has happened during the key exchanging
this.audioAsset.playSound('voip_failed.mp3');
} else {
this.audioAsset.playSound(call.discardReason === 'phoneCallDiscardReasonBusy' ? 'call_busy.mp3' : 'call_end.mp3');

45
src/lib/appManagers/appImManager.ts

@ -87,6 +87,8 @@ import appReactionsManager from './appReactionsManager'; @@ -87,6 +87,8 @@ import appReactionsManager from './appReactionsManager';
import PopupCall from '../../components/call';
import copy from '../../helpers/object/copy';
import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort';
import type GroupCallInstance from '../calls/groupCallInstance';
import type CallInstance from '../calls/callInstance';
//console.log('appImManager included33!');
@ -341,20 +343,39 @@ export class AppImManager { @@ -341,20 +343,39 @@ export class AppImManager {
});
if(IS_CALL_SUPPORTED || IS_GROUP_CALL_SUPPORTED) {
this.topbarCall = new TopbarCall(appGroupCallsManager, appPeersManager, appChatsManager, appAvatarsManager);
this.topbarCall = new TopbarCall(appGroupCallsManager, appPeersManager, appChatsManager, appAvatarsManager, appCallsManager);
}
if(IS_CALL_SUPPORTED) {
rootScope.addEventListener('call_instance', ({instance, hasCurrent}) => {
if(hasCurrent) {
return;
}
rootScope.addEventListener('call_instance', ({instance/* , hasCurrent */}) => {
// if(hasCurrent) {
// return;
// }
new PopupCall({
const popup = new PopupCall({
appCallsManager,
appAvatarsManager,
appPeersManager,
instance
}).show();
});
instance.addEventListener('acceptCallOverride', () => {
return this.discardCurrentCall(instance.interlocutorUserId.toPeerId(), undefined, instance)
.then(() => {
rootScope.dispatchEvent('call_accepting', instance);
return true;
})
.catch(() => false);
});
popup.addEventListener('close', () => {
const currentCall = appCallsManager.currentCall;
if(currentCall && currentCall !== instance && !instance.wasTryingToJoin) {
instance.hangUp('phoneCallDiscardReasonBusy');
}
}, {once: true});
popup.show();
});
}
@ -944,9 +965,9 @@ export class AppImManager { @@ -944,9 +965,9 @@ export class AppImManager {
appCallsManager.startCallInternal(userId, type === 'video');
}
private discardCurrentCall(toPeerId: PeerId) {
if(appCallsManager.currentCall) return this.discardCallConfirmation(toPeerId);
else if(appGroupCallsManager.groupCall) return this.discardGroupCallConfirmation(toPeerId);
private discardCurrentCall(toPeerId: PeerId, ignoreGroupCall?: GroupCallInstance, ignoreCall?: CallInstance) {
if(appGroupCallsManager.groupCall && appGroupCallsManager.groupCall !== ignoreGroupCall) return this.discardGroupCallConfirmation(toPeerId);
else if(appCallsManager.currentCall && appCallsManager.currentCall !== ignoreCall) return this.discardCallConfirmation(toPeerId);
else return Promise.resolve();
}
@ -965,8 +986,8 @@ export class AppImManager { @@ -965,8 +986,8 @@ export class AppImManager {
}
});
if(appCallsManager.currentCall === currentCall) {
await currentCall.hangUp();
if(!currentCall.isClosing) {
await currentCall.hangUp('phoneCallDiscardReasonDisconnect');
}
}
}

7
src/lib/appManagers/appPeersManager.ts

@ -20,6 +20,7 @@ import I18n from '../langPack'; @@ -20,6 +20,7 @@ import I18n from '../langPack';
import { NULL_PEER_ID } from "../mtproto/mtproto_config";
import { getRestrictionReason } from "../../helpers/restrictions";
import isObject from "../../helpers/object/isObject";
import { limitSymbols } from "../../helpers/string";
// https://github.com/eelcohn/Telegram-API/wiki/Calculating-color-for-a-Telegram-user-on-IRC
/*
@ -73,7 +74,7 @@ export class AppPeersManager { @@ -73,7 +74,7 @@ export class AppPeersManager {
return false;
}
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false) {
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false, _limitSymbols?: number) {
if(!peerId) {
peerId = rootScope.myId;
}
@ -94,6 +95,10 @@ export class AppPeersManager { @@ -94,6 +95,10 @@ export class AppPeersManager {
title = title.split(' ')[0];
}
}
if(_limitSymbols !== undefined) {
title = limitSymbols(title, _limitSymbols, _limitSymbols);
}
return plainText ? title : RichTextProcessor.wrapEmojiText(title);
}

2
src/lib/appManagers/appReactionsManager.ts

@ -8,7 +8,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; @@ -8,7 +8,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug";
import assumeType from "../../helpers/assumeType";
import callbackify from "../../helpers/callbackify";
import callbackifyAll from "../../helpers/callbackifyAll";
import { copy } from "../../helpers/object";
import copy from "../../helpers/object/copy";
import { AvailableReaction, Message, MessagePeerReaction, MessagesAvailableReactions, Update, Updates } from "../../layer";
import apiManager from "../mtproto/mtprotoworker";
import { ReferenceContext } from "../mtproto/referenceDatabase";

29
src/lib/calls/callInstance.ts

@ -34,7 +34,8 @@ export default class CallInstance extends CallInstanceBase<{ @@ -34,7 +34,8 @@ export default class CallInstance extends CallInstanceBase<{
state: (state: CALL_STATE) => void,
id: (id: CallId, prevId: CallId) => void,
muted: (muted: boolean) => void,
mediaState: (mediaState: CallMediaState) => void
mediaState: (mediaState: CallMediaState) => void,
acceptCallOverride: () => Promise<boolean>,
}> {
public dh: Partial<DiffieHellmanInfo.a & DiffieHellmanInfo.b>;
public id: CallId;
@ -55,6 +56,7 @@ export default class CallInstance extends CallInstanceBase<{ @@ -55,6 +56,7 @@ export default class CallInstance extends CallInstanceBase<{
public release: () => Promise<void>;
public _connectionState: CALL_STATE;
public createdAt: number;
public connectedAt: number;
public discardReason: string;
@ -78,6 +80,9 @@ export default class CallInstance extends CallInstanceBase<{ @@ -78,6 +80,9 @@ export default class CallInstance extends CallInstanceBase<{
private wasStartingScreen: boolean;
private wasStartingVideo: boolean;
public wasTryingToJoin: boolean;
public streamManager: StreamManager;
constructor(options: {
isOutgoing: boolean,
@ -97,6 +102,7 @@ export default class CallInstance extends CallInstanceBase<{ @@ -97,6 +102,7 @@ export default class CallInstance extends CallInstanceBase<{
safeAssign(this, options);
this.createdAt = Date.now();
this.offerReceived = false;
this.offerSent = false;
this.decryptQueue = [];
@ -110,7 +116,7 @@ export default class CallInstance extends CallInstanceBase<{ @@ -110,7 +116,7 @@ export default class CallInstance extends CallInstanceBase<{
}
});
const streamManager = new StreamManager(GROUP_CALL_AMPLITUDE_ANALYSE_INTERVAL_MS);
const streamManager = this.streamManager = new StreamManager(GROUP_CALL_AMPLITUDE_ANALYSE_INTERVAL_MS);
streamManager.direction = 'sendrecv';
streamManager.types.push('screencast');
if(!this.isOutgoing) {
@ -164,6 +170,14 @@ export default class CallInstance extends CallInstanceBase<{ @@ -164,6 +170,14 @@ export default class CallInstance extends CallInstanceBase<{
}
}
get sortIndex() {
const connectionState = this.connectionState;
const state = CALL_STATE.CLOSED - connectionState + 1;
let index = state * 10000000000000;
index += 2147483647000 - (connectionState === CALL_STATE.PENDING && this.isOutgoing ? 0 : this.createdAt);
return index;
}
public getVideoElement(type: CallMediaState['type']) {
if(type === 'input') return this.elements.get('main');
else {
@ -283,10 +297,6 @@ export default class CallInstance extends CallInstanceBase<{ @@ -283,10 +297,6 @@ export default class CallInstance extends CallInstanceBase<{
return connectionState === CALL_STATE.CLOSING || connectionState === CALL_STATE.CLOSED;
}
public get streamManager(): StreamManager {
return this.connectionInstance?.streamManager;
}
public get description(): localConferenceDescription {
return this.connectionInstance?.description;
}
@ -318,6 +328,11 @@ export default class CallInstance extends CallInstanceBase<{ @@ -318,6 +328,11 @@ export default class CallInstance extends CallInstanceBase<{
}
public async acceptCall() {
const canAccept = (await Promise.all(this.dispatchResultableEvent('acceptCallOverride')))[0] ?? true;
if(this.isClosing || !canAccept) {
return;
}
// this.clearHangUpTimeout();
this.overrideConnectionState(CALL_STATE.EXCHANGING_KEYS);
@ -619,7 +634,7 @@ export default class CallInstance extends CallInstanceBase<{ @@ -619,7 +634,7 @@ export default class CallInstance extends CallInstanceBase<{
}
public async hangUp(discardReason?: PhoneCallDiscardReason['_'], discardedByOtherParty?: boolean) {
if(this.connectionState === CALL_STATE.CLOSED) {
if(this.isClosing) {
return;
}

8
src/lib/calls/callInstanceBase.ts

@ -11,6 +11,7 @@ import getAudioConstraints from "./helpers/getAudioConstraints"; @@ -11,6 +11,7 @@ import getAudioConstraints from "./helpers/getAudioConstraints";
import getScreenConstraints from "./helpers/getScreenConstraints";
import getStreamCached from "./helpers/getStreamCached";
import getVideoConstraints from "./helpers/getVideoConstraints";
import stopTrack from "./helpers/stopTrack";
import LocalConferenceDescription from "./localConferenceDescription";
import StreamManager, { StreamItem } from "./streamManager";
@ -190,6 +191,9 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners> @@ -190,6 +191,9 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners>
if(!isVideo) {
player.appendChild(element);
} else {
element.setAttribute('playsinline', 'true');
element.muted = true;
}
// audio.play();
@ -229,6 +233,10 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners> @@ -229,6 +233,10 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners>
if(description) {
streamManager.appendToConference(description);
}
} else { // if call is declined earlier than stream appears
stream.getTracks().forEach(track => {
stopTrack(track);
});
}
}
}

2
src/lib/calls/groupCallInstance.ts

@ -183,8 +183,6 @@ export default class GroupCallInstance extends CallInstanceBase<{ @@ -183,8 +183,6 @@ export default class GroupCallInstance extends CallInstanceBase<{
const clone = element.cloneNode() as typeof element;
clone.srcObject = element.srcObject;
clone.setAttribute('playsinline', 'true');
clone.muted = true;
return {video: clone, source};
}

2
src/lib/richtextprocessor.ts

@ -18,7 +18,7 @@ import { encodeEntities } from '../helpers/string'; @@ -18,7 +18,7 @@ import { encodeEntities } from '../helpers/string';
import { IS_SAFARI } from '../environment/userAgent';
import { MOUNT_CLASS_TO } from '../config/debug';
import IS_EMOJI_SUPPORTED from '../environment/emojiSupport';
import { copy } from '../helpers/object';
import copy from '../helpers/object/copy';
const EmojiHelper = {
emojiMap: (code: string) => { return code; },

5
src/lib/rootScope.ts

@ -17,7 +17,7 @@ import type { PushNotificationObject } from "./serviceWorker/push"; @@ -17,7 +17,7 @@ import type { PushNotificationObject } from "./serviceWorker/push";
import type { ConnectionStatusChange } from "./mtproto/connectionStatus";
import type { GroupCallId } from "./appManagers/appGroupCallsManager";
import type GroupCallInstance from "./calls/groupCallInstance";
// import type CallInstance from "./calls/callInstance";
import type CallInstance from "./calls/callInstance";
import type { StreamAmplitude } from "./calls/streamManager";
import type Chat from "../components/chat/chat";
import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config";
@ -158,7 +158,8 @@ export type BroadcastEvents = { @@ -158,7 +158,8 @@ export type BroadcastEvents = {
'group_call_participant': {groupCallId: GroupCallId, participant: GroupCallParticipant},
// 'group_call_video_track_added': {instance: GroupCallInstance}
'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */},
'call_instance': {hasCurrent: boolean, instance: CallInstance},
'call_accepting': CallInstance, // это костыль. используется при параллельном вызове, чтобы заменить звонок в topbarCall
'quick_reaction': string,

16
src/scss/partials/_avatar.scss

@ -232,3 +232,19 @@ avatar-element { @@ -232,3 +232,19 @@ avatar-element {
left: 0;
}
}
.avatar-full {
position: absolute;
width: 100%;
height: 100%;
border-radius: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
.avatar-photo {
width: 100% !important;
height: 100% !important;
object-fit: cover;
}
}

3
src/scss/partials/_chatBubble.scss

@ -1383,12 +1383,13 @@ $bubble-beside-button-width: 38px; @@ -1383,12 +1383,13 @@ $bubble-beside-button-width: 38px;
}
&-subtitle {
font-size: .875rem;
font-size: var(--messages-secondary-text-size);
color: var(--secondary-text-color);
display: flex;
align-items: center;
margin-top: .0625rem;
margin-left: -.1875rem;
line-height: var(--messages-secondary-line-height);
&.is-reason:before {
margin-right: .0625rem;

36
src/scss/partials/_chatTopbar.scss

@ -239,6 +239,7 @@ body.is-right-column-shown { @@ -239,6 +239,7 @@ body.is-right-column-shown {
height: 3.5rem;
max-height: 3.5rem;
flex: 1 1 auto;
max-width: 100%;
}
}
@ -360,15 +361,29 @@ body.is-right-column-shown { @@ -360,15 +361,29 @@ body.is-right-column-shown {
white-space: nowrap;
}
@media only screen and (max-width: 480px) {
.topbar-call-left,
.topbar-call-right {
width: auto;
&:not(.is-call) {
@media only screen and (max-width: 480px) {
.topbar-call-left,
.topbar-call-right {
width: auto;
}
.group-call-description {
display: none;
}
}
}
&.is-call {
@media only screen and (max-width: 480px) {
.topbar-call-left,
.topbar-call-right {
width: 6.25rem;
}
.group-call-description,
.call-description {
display: none;
.call-description:not(.has-duration) {
display: none;
}
}
}
}
@ -399,6 +414,13 @@ body.is-right-column-shown { @@ -399,6 +414,13 @@ body.is-right-column-shown {
&-center {
@include sidebar-transform(true);
@include text-overflow();
@include respond-to(medium-screens) {
// ! it flicks over the left side :(
// body.is-right-column-shown & {
padding: 0 calc(var(--right-column-width) / 2);
// }
}
}
&-right {

7
src/scss/partials/_fonts.scss

@ -37,6 +37,13 @@ @@ -37,6 +37,13 @@
-moz-osx-font-smoothing: grayscale;
}
.tgico-phone_filled {
&:before {
content: $tgico-endcall_filled;
transform: rotate(-135deg);
}
}
.tgico-check {
&:before {
content: $tgico-check;

50
src/scss/partials/popups/_call.scss

@ -24,10 +24,15 @@ @@ -24,10 +24,15 @@
color: #fff;
align-items: center;
&.is-full-screen {
&.is-full-screen,
html.is-mobile & {
border-radius: 0;
}
&.is-full-screen:not(.show-controls) {
cursor: none;
}
&.show-controls,
&.no-video {
.call-title,
@ -41,10 +46,25 @@ @@ -41,10 +46,25 @@
}
}
&.show-controls {
.call-video {
opacity: .8;
}
.call-video-blur {
opacity: .56;
}
}
.popup-header {
.btn-icon {
color: #fff;
}
.call-emojis {
transform: scale(1.3125);
margin-right: 1rem;
}
}
&-avatar {
@ -55,11 +75,10 @@ @@ -55,11 +75,10 @@
left: 0;
z-index: -1;
opacity: .7;
border-radius: inherit;
.avatar-photo {
width: 100%;
height: 100%;
object-fit: cover;
.avatar-full {
font-size: 6rem;
}
}
@ -182,6 +201,7 @@ @@ -182,6 +201,7 @@
object-fit: contain;
position: absolute;
border-radius: inherit;
opacity: 1;
&-container {
position: absolute;
@ -212,6 +232,13 @@ @@ -212,6 +232,13 @@
opacity: .7;
border-radius: inherit;
}
&,
&-blur {
@include animation-level(2) {
transition: opacity var(--transition-standard-in);
}
}
}
// html.emoji-supported & {
@ -234,6 +261,7 @@ @@ -234,6 +261,7 @@
z-index: 2;
width: 100%;
align-items: center;
padding: 0 1rem;
}
&-party-state {
@ -252,7 +280,6 @@ @@ -252,7 +280,6 @@
opacity: 0;
transform: scale(0) translateY(0);
max-width: 100%;
padding: 0 1rem;
@include animation-level(2) {
transition: opacity var(--transition-standard-in), transform var(--transition-standard-in);
@ -262,7 +289,7 @@ @@ -262,7 +289,7 @@
width: 1.875rem !important;
height: 1.875rem !important;
margin-right: .25rem;
margin-left: -.625rem;
margin-left: -.25rem;
flex: 0 0 auto;
}
@ -275,11 +302,12 @@ @@ -275,11 +302,12 @@
// opacity: 0 !important;
// }
}
}
&-text {
max-width: 100%;
@include text-overflow();
}
&-party-state-text,
&-title {
max-width: 100%;
@include text-overflow();
}
&.two-button-rows {

Loading…
Cancel
Save