Notifications almost finished
Maybe fix updates Removed jsbn dependency
After Width: | Height: | Size: 2.0 MiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -8,7 +8,7 @@ import rootScope from "../lib/rootScope";
|
|||
import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom";
|
||||
import Scrollable from "./scrollable";
|
||||
import { FocusDirection } from "../helpers/fastSmoothScroll";
|
||||
import CheckboxField from "./checkbox";
|
||||
import CheckboxField from "./checkboxField";
|
||||
|
||||
type PeerType = 'contacts' | 'dialogs';
|
||||
|
||||
|
@ -346,7 +346,7 @@ export default class AppSelectPeers {
|
|||
|
||||
if(this.multiSelect) {
|
||||
const selected = this.selected.has(peerId);
|
||||
const checkboxField = CheckboxField();
|
||||
const checkboxField = new CheckboxField();
|
||||
|
||||
if(selected) {
|
||||
dom.listEl.classList.add('active');
|
||||
|
|
|
@ -6,7 +6,7 @@ import { isTouchSupported } from "../../helpers/touchSupport";
|
|||
import { blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
|
||||
import Button from "../button";
|
||||
import ButtonIcon from "../buttonIcon";
|
||||
import CheckboxField from "../checkbox";
|
||||
import CheckboxField from "../checkboxField";
|
||||
import PopupDeleteMessages from "../popups/deleteMessages";
|
||||
import PopupForward from "../popups/forward";
|
||||
import { toast } from "../toast";
|
||||
|
@ -161,7 +161,7 @@ export default class ChatSelection {
|
|||
if(show) {
|
||||
if(hasCheckbox) return;
|
||||
|
||||
const checkboxField = CheckboxField({
|
||||
const checkboxField = new CheckboxField({
|
||||
name: bubble.dataset.mid,
|
||||
round: true
|
||||
});
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
import appStateManager from "../lib/appManagers/appStateManager";
|
||||
import { getDeepProperty } from "../helpers/object";
|
||||
|
||||
const CheckboxField = (options: {
|
||||
text?: string,
|
||||
name?: string,
|
||||
round?: boolean,
|
||||
stateKey?: string,
|
||||
disabled?: boolean
|
||||
} = {}) => {
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('checkbox-field');
|
||||
|
||||
if(options.round) {
|
||||
label.classList.add('checkbox-field-round');
|
||||
}
|
||||
|
||||
if(options.disabled) {
|
||||
label.classList.add('checkbox-disabled');
|
||||
}
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
if(options.name) {
|
||||
input.id = 'input-' + name;
|
||||
}
|
||||
|
||||
if(options.stateKey) {
|
||||
appStateManager.getState().then(state => {
|
||||
input.checked = getDeepProperty(state, options.stateKey);
|
||||
});
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
appStateManager.setByKey(options.stateKey, input.checked);
|
||||
});
|
||||
}
|
||||
|
||||
let span: HTMLSpanElement;
|
||||
if(options.text) {
|
||||
span = document.createElement('span');
|
||||
span.classList.add('checkbox-caption');
|
||||
|
||||
if(options.text) {
|
||||
span.innerText = options.text;
|
||||
}
|
||||
} else {
|
||||
label.classList.add('checkbox-without-caption');
|
||||
}
|
||||
|
||||
const box = document.createElement('div');
|
||||
box.classList.add('checkbox-box');
|
||||
|
||||
const checkSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
checkSvg.classList.add('checkbox-box-check');
|
||||
checkSvg.setAttributeNS(null, 'viewBox', '0 0 24 24');
|
||||
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
|
||||
use.setAttributeNS(null, 'href', '#check');
|
||||
use.setAttributeNS(null, 'x', '-1');
|
||||
checkSvg.append(use);
|
||||
|
||||
const bg = document.createElement('div');
|
||||
bg.classList.add('checkbox-box-background');
|
||||
|
||||
const border = document.createElement('div');
|
||||
border.classList.add('checkbox-box-border');
|
||||
|
||||
box.append(border, bg, checkSvg);
|
||||
|
||||
label.append(input, box);
|
||||
|
||||
if(span) {
|
||||
label.append(span);
|
||||
}
|
||||
|
||||
return {label, input, span};
|
||||
};
|
||||
|
||||
export default CheckboxField;
|
|
@ -0,0 +1,100 @@
|
|||
import appStateManager from "../lib/appManagers/appStateManager";
|
||||
import { getDeepProperty } from "../helpers/object";
|
||||
|
||||
export default class CheckboxField {
|
||||
public input: HTMLInputElement;
|
||||
public label: HTMLLabelElement;
|
||||
public span: HTMLSpanElement;
|
||||
|
||||
constructor(options: {
|
||||
text?: string,
|
||||
name?: string,
|
||||
round?: boolean,
|
||||
stateKey?: string,
|
||||
disabled?: boolean,
|
||||
checked?: boolean,
|
||||
} = {}) {
|
||||
const label = this.label = document.createElement('label');
|
||||
label.classList.add('checkbox-field');
|
||||
|
||||
if(options.round) {
|
||||
label.classList.add('checkbox-field-round');
|
||||
}
|
||||
|
||||
if(options.disabled) {
|
||||
label.classList.add('checkbox-disabled');
|
||||
}
|
||||
|
||||
const input = this.input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
if(options.name) {
|
||||
input.id = 'input-' + name;
|
||||
}
|
||||
|
||||
if(options.checked) {
|
||||
input.checked = true;
|
||||
}
|
||||
|
||||
if(options.stateKey) {
|
||||
appStateManager.getState().then(state => {
|
||||
this.value = getDeepProperty(state, options.stateKey);
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
appStateManager.setByKey(options.stateKey, input.checked);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let span: HTMLSpanElement;
|
||||
if(options.text) {
|
||||
span = this.span = document.createElement('span');
|
||||
span.classList.add('checkbox-caption');
|
||||
|
||||
if(options.text) {
|
||||
span.innerText = options.text;
|
||||
}
|
||||
} else {
|
||||
label.classList.add('checkbox-without-caption');
|
||||
}
|
||||
|
||||
const box = document.createElement('div');
|
||||
box.classList.add('checkbox-box');
|
||||
|
||||
const checkSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
checkSvg.classList.add('checkbox-box-check');
|
||||
checkSvg.setAttributeNS(null, 'viewBox', '0 0 24 24');
|
||||
const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
|
||||
use.setAttributeNS(null, 'href', '#check');
|
||||
use.setAttributeNS(null, 'x', '-1');
|
||||
checkSvg.append(use);
|
||||
|
||||
const bg = document.createElement('div');
|
||||
bg.classList.add('checkbox-box-background');
|
||||
|
||||
const border = document.createElement('div');
|
||||
border.classList.add('checkbox-box-border');
|
||||
|
||||
box.append(border, bg, checkSvg);
|
||||
|
||||
label.append(input, box);
|
||||
|
||||
if(span) {
|
||||
label.append(span);
|
||||
}
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.input.checked;
|
||||
}
|
||||
|
||||
set value(value: boolean) {
|
||||
this.setValueSilently(value);
|
||||
|
||||
const event = new Event('change', {bubbles: true, cancelable: true});
|
||||
this.input.dispatchEvent(event);
|
||||
}
|
||||
|
||||
public setValueSilently(value: boolean) {
|
||||
this.input.checked = value;
|
||||
}
|
||||
}
|
|
@ -206,4 +206,4 @@ class InputField {
|
|||
}
|
||||
}
|
||||
|
||||
export default InputField;
|
||||
export default InputField;
|
||||
|
|
|
@ -23,7 +23,7 @@ const map: Map<HTMLElement, {
|
|||
}> = new Map();
|
||||
|
||||
const testQueue: Set<HTMLElement> = new Set();
|
||||
const fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif';
|
||||
export const fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif';
|
||||
const fontSize = '16px';
|
||||
let timeoutId: number;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { Poll } from "../../lib/appManagers/appPollsManager";
|
|||
import type Chat from "../chat/chat";
|
||||
import PopupElement from ".";
|
||||
import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../../helpers/dom";
|
||||
import CheckboxField from "../checkbox";
|
||||
import CheckboxField from "../checkboxField";
|
||||
import InputField from "../inputField";
|
||||
import RadioField from "../radioField";
|
||||
import Scrollable from "../scrollable";
|
||||
|
@ -20,7 +20,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
private scrollable: Scrollable;
|
||||
private tempId = 0;
|
||||
|
||||
private anonymousCheckboxField: ReturnType<typeof CheckboxField>;
|
||||
private anonymousCheckboxField: CheckboxField;
|
||||
private multipleCheckboxField: PopupCreatePoll['anonymousCheckboxField'];
|
||||
private quizCheckboxField: PopupCreatePoll['anonymousCheckboxField'];
|
||||
|
||||
|
@ -77,7 +77,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
settingsCaption.innerText = 'Settings';
|
||||
|
||||
if(!this.chat.appPeersManager.isBroadcast(this.chat.peerId)) {
|
||||
this.anonymousCheckboxField = CheckboxField({
|
||||
this.anonymousCheckboxField = new CheckboxField({
|
||||
text: 'Anonymous Voting',
|
||||
name: 'anonymous'
|
||||
});
|
||||
|
@ -85,11 +85,11 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
dd.append(this.anonymousCheckboxField.label);
|
||||
}
|
||||
|
||||
this.multipleCheckboxField = CheckboxField({
|
||||
this.multipleCheckboxField = new CheckboxField({
|
||||
text: 'Multiple Answers',
|
||||
name: 'multiple'
|
||||
});
|
||||
this.quizCheckboxField = CheckboxField({
|
||||
this.quizCheckboxField = new CheckboxField({
|
||||
text: 'Quiz Mode',
|
||||
name: 'quiz'
|
||||
});
|
||||
|
@ -303,7 +303,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
});
|
||||
questionField.input.addEventListener('input', this.onInput);
|
||||
|
||||
const radioField = RadioField('', 'question');
|
||||
const radioField = new RadioField('', 'question');
|
||||
radioField.main.append(questionField.container);
|
||||
questionField.input.addEventListener('click', cancelEvent);
|
||||
radioField.label.classList.add('hidden-widget');
|
||||
|
|
|
@ -6,7 +6,7 @@ import PopupElement from ".";
|
|||
import Scrollable from "../scrollable";
|
||||
import { toast } from "../toast";
|
||||
import { prepareAlbum, wrapDocument } from "../wrappers";
|
||||
import CheckboxField from "../checkbox";
|
||||
import CheckboxField from "../checkboxField";
|
||||
import SendContextMenu from "../chat/sendContextMenu";
|
||||
import { createPosterForVideo, createPosterFromVideo, onVideoLoad } from "../../helpers/files";
|
||||
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||
|
@ -28,7 +28,7 @@ const MAX_LENGTH_CAPTION = 1024;
|
|||
export default class PopupNewMedia extends PopupElement {
|
||||
private input: HTMLElement;
|
||||
private mediaContainer: HTMLElement;
|
||||
private groupCheckboxField: { label: HTMLLabelElement; input: HTMLInputElement; span: HTMLSpanElement; };
|
||||
private groupCheckboxField: CheckboxField;
|
||||
private wasInputValue = '';
|
||||
|
||||
private willAttach: Partial<{
|
||||
|
@ -89,7 +89,7 @@ export default class PopupNewMedia extends PopupElement {
|
|||
this.container.append(scrollable.container);
|
||||
|
||||
if(files.length > 1) {
|
||||
this.groupCheckboxField = CheckboxField({
|
||||
this.groupCheckboxField = new CheckboxField({
|
||||
text: 'Group items',
|
||||
name: 'group-items'
|
||||
});
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class PrivacySection {
|
|||
|
||||
const random = randomLong();
|
||||
r.forEach(({type, text}) => {
|
||||
this.radioRows.set(type, new Row({radioField: RadioField(text, random, '' + type)}));
|
||||
this.radioRows.set(type, new Row({radioField: new RadioField(text, random, '' + type)}));
|
||||
});
|
||||
|
||||
const form = RadioFormFromRows([...this.radioRows.values()], this.onRadioChange);
|
||||
|
|
|
@ -1,48 +1,50 @@
|
|||
import appStateManager from "../lib/appManagers/appStateManager";
|
||||
import { getDeepProperty } from "../helpers/object";
|
||||
|
||||
const RadioField = (text: string, name: string, value?: string, stateKey?: string) => {
|
||||
const label = document.createElement('label');
|
||||
label.classList.add('radio-field');
|
||||
export default class RadioField {
|
||||
public input: HTMLInputElement;
|
||||
public label: HTMLLabelElement;
|
||||
public main: HTMLElement;
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'radio';
|
||||
/* input.id = */input.name = 'input-radio-' + name;
|
||||
|
||||
if(value) {
|
||||
input.value = value;
|
||||
|
||||
if(stateKey) {
|
||||
appStateManager.getState().then(state => {
|
||||
input.checked = getDeepProperty(state, stateKey) === value;
|
||||
});
|
||||
constructor(text: string, name: string, value?: string, stateKey?: string) {
|
||||
const label = this.label = document.createElement('label');
|
||||
label.classList.add('radio-field');
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
appStateManager.setByKey(stateKey, value);
|
||||
});
|
||||
const input = this.input = document.createElement('input');
|
||||
input.type = 'radio';
|
||||
/* input.id = */input.name = 'input-radio-' + name;
|
||||
|
||||
if(value) {
|
||||
input.value = value;
|
||||
|
||||
if(stateKey) {
|
||||
appStateManager.getState().then(state => {
|
||||
input.checked = getDeepProperty(state, stateKey) === value;
|
||||
});
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
appStateManager.setByKey(stateKey, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const main = document.createElement('div');
|
||||
main.classList.add('radio-field-main');
|
||||
|
||||
if(text) {
|
||||
main.innerHTML = text;
|
||||
/* const caption = document.createElement('div');
|
||||
caption.classList.add('radio-field-main-caption');
|
||||
caption.innerHTML = text;
|
||||
|
||||
if(subtitle) {
|
||||
label.classList.add('radio-field-with-subtitle');
|
||||
caption.insertAdjacentHTML('beforeend', `<div class="radio-field-main-subtitle">${subtitle}</div>`);
|
||||
|
||||
const main = this.main = document.createElement('div');
|
||||
main.classList.add('radio-field-main');
|
||||
|
||||
if(text) {
|
||||
main.innerHTML = text;
|
||||
/* const caption = document.createElement('div');
|
||||
caption.classList.add('radio-field-main-caption');
|
||||
caption.innerHTML = text;
|
||||
|
||||
if(subtitle) {
|
||||
label.classList.add('radio-field-with-subtitle');
|
||||
caption.insertAdjacentHTML('beforeend', `<div class="radio-field-main-subtitle">${subtitle}</div>`);
|
||||
}
|
||||
|
||||
main.append(caption); */
|
||||
}
|
||||
|
||||
main.append(caption); */
|
||||
|
||||
label.append(input, main);
|
||||
}
|
||||
|
||||
label.append(input, main);
|
||||
|
||||
return {label, input, main};
|
||||
};
|
||||
|
||||
export default RadioField;
|
|
@ -1,4 +1,4 @@
|
|||
import CheckboxField from "./checkbox";
|
||||
import CheckboxField from "./checkboxField";
|
||||
import RadioField from "./radioField";
|
||||
import { ripple } from "./ripple";
|
||||
import { SliderSuperTab } from "./slider";
|
||||
|
@ -9,8 +9,8 @@ export default class Row {
|
|||
public title: HTMLDivElement;
|
||||
public subtitle: HTMLElement;
|
||||
|
||||
public checkboxField: ReturnType<typeof CheckboxField>;
|
||||
public radioField: ReturnType<typeof RadioField>;
|
||||
public checkboxField: CheckboxField;
|
||||
public radioField: RadioField;
|
||||
|
||||
public freezed = false;
|
||||
|
||||
|
@ -44,6 +44,10 @@ export default class Row {
|
|||
if(options.checkboxField) {
|
||||
this.checkboxField = options.checkboxField;
|
||||
this.container.append(this.checkboxField.label);
|
||||
|
||||
this.checkboxField.input.addEventListener('change', () => {
|
||||
this.subtitle.innerHTML = this.checkboxField.input.checked ? 'Enabled' : 'Disabled';
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if(options.title) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import appStateManager from "../../../lib/appManagers/appStateManager";
|
|||
import apiManager from "../../../lib/mtproto/mtprotoworker";
|
||||
import rootScope from "../../../lib/rootScope";
|
||||
import Button from "../../button";
|
||||
import CheckboxField from "../../checkbox";
|
||||
import CheckboxField from "../../checkboxField";
|
||||
import ProgressivePreloader from "../../preloader";
|
||||
import { SliderSuperTab } from "../../slider";
|
||||
import { wrapPhoto } from "../../wrappers";
|
||||
|
@ -28,7 +28,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
|
|||
const uploadButton = Button('btn-primary btn-transparent', {icon: 'cameraadd', text: 'Upload Wallpaper', disabled: true});
|
||||
const colorButton = Button('btn-primary btn-transparent', {icon: 'colorize', text: 'Set a Color', disabled: true});
|
||||
|
||||
const blurCheckboxField = CheckboxField({
|
||||
const blurCheckboxField = new CheckboxField({
|
||||
text: 'Blur Wallpaper Image',
|
||||
name: 'blur',
|
||||
stateKey: 'settings.background.blur'
|
||||
|
|
|
@ -2,7 +2,7 @@ import { SliderSuperTab } from "../../slider"
|
|||
import { generateSection } from "..";
|
||||
import RangeSelector from "../../rangeSelector";
|
||||
import Button from "../../button";
|
||||
import CheckboxField from "../../checkbox";
|
||||
import CheckboxField from "../../checkboxField";
|
||||
import RadioField from "../../radioField";
|
||||
import appStateManager from "../../../lib/appManagers/appStateManager";
|
||||
import rootScope from "../../../lib/rootScope";
|
||||
|
@ -73,7 +73,7 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
|
|||
new AppBackgroundTab(this.slider).open();
|
||||
});
|
||||
|
||||
const animationsCheckboxField = CheckboxField({
|
||||
const animationsCheckboxField = new CheckboxField({
|
||||
text: 'Enable Animations',
|
||||
name: 'animations',
|
||||
stateKey: 'settings.animationsEnabled'
|
||||
|
@ -88,12 +88,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
|
|||
const form = document.createElement('form');
|
||||
|
||||
const enterRow = new Row({
|
||||
radioField: RadioField('Send by Enter', 'send-shortcut', 'enter', 'settings.sendShortcut'),
|
||||
radioField: new RadioField('Send by Enter', 'send-shortcut', 'enter', 'settings.sendShortcut'),
|
||||
subtitle: 'New line by Shift + Enter',
|
||||
});
|
||||
|
||||
const ctrlEnterRow = new Row({
|
||||
radioField: RadioField(`Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`, 'send-shortcut', 'ctrlEnter', 'settings.sendShortcut'),
|
||||
radioField: new RadioField(`Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`, 'send-shortcut', 'ctrlEnter', 'settings.sendShortcut'),
|
||||
subtitle: 'New line by Enter',
|
||||
});
|
||||
|
||||
|
@ -105,22 +105,22 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
|
|||
const container = section('Auto-Download Media');
|
||||
container.classList.add('sidebar-left-section-disabled');
|
||||
|
||||
const contactsCheckboxField = CheckboxField({
|
||||
const contactsCheckboxField = new CheckboxField({
|
||||
text: 'Contacts',
|
||||
name: 'contacts',
|
||||
stateKey: 'settings.autoDownload.contacts'
|
||||
});
|
||||
const privateCheckboxField = CheckboxField({
|
||||
const privateCheckboxField = new CheckboxField({
|
||||
text: 'Private Chats',
|
||||
name: 'private',
|
||||
stateKey: 'settings.autoDownload.private'
|
||||
});
|
||||
const groupsCheckboxField = CheckboxField({
|
||||
const groupsCheckboxField = new CheckboxField({
|
||||
text: 'Group Chats',
|
||||
name: 'groups',
|
||||
stateKey: 'settings.autoDownload.groups'
|
||||
});
|
||||
const channelsCheckboxField = CheckboxField({
|
||||
const channelsCheckboxField = new CheckboxField({
|
||||
text: 'Channels',
|
||||
name: 'channels',
|
||||
stateKey: 'settings.autoDownload.channels'
|
||||
|
@ -133,12 +133,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
|
|||
const container = section('Auto-Play Media');
|
||||
container.classList.add('sidebar-left-section-disabled');
|
||||
|
||||
const gifsCheckboxField = CheckboxField({
|
||||
const gifsCheckboxField = new CheckboxField({
|
||||
text: 'GIFs',
|
||||
name: 'gifs',
|
||||
stateKey: 'settings.autoPlay.gifs'
|
||||
});
|
||||
const videosCheckboxField = CheckboxField({
|
||||
const videosCheckboxField = new CheckboxField({
|
||||
text: 'Videos',
|
||||
name: 'videos',
|
||||
stateKey: 'settings.autoPlay.videos'
|
||||
|
@ -150,12 +150,12 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
|
|||
{
|
||||
const container = section('Stickers');
|
||||
|
||||
const suggestCheckboxField = CheckboxField({
|
||||
const suggestCheckboxField = new CheckboxField({
|
||||
text: 'Suggest Stickers by Emoji',
|
||||
name: 'suggest',
|
||||
stateKey: 'settings.stickers.suggest'
|
||||
});
|
||||
const loopCheckboxField = CheckboxField({
|
||||
const loopCheckboxField = new CheckboxField({
|
||||
text: 'Loop Animated Stickers',
|
||||
name: 'loop',
|
||||
stateKey: 'settings.stickers.loop'
|
||||
|
|
|
@ -7,7 +7,7 @@ import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters";
|
|||
import rootScope from "../../../lib/rootScope";
|
||||
import { copy } from "../../../helpers/object";
|
||||
import ButtonIcon from "../../buttonIcon";
|
||||
import CheckboxField from "../../checkbox";
|
||||
import CheckboxField from "../../checkboxField";
|
||||
import Button from "../../button";
|
||||
import AppEditFolderTab from "./editFolder";
|
||||
|
||||
|
@ -94,7 +94,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
|
|||
}
|
||||
|
||||
checkbox(selected?: boolean) {
|
||||
const checkboxField = CheckboxField({
|
||||
const checkboxField = new CheckboxField({
|
||||
round: true
|
||||
});
|
||||
if(selected) {
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
import { SettingSection } from "..";
|
||||
import Row from "../../row";
|
||||
import CheckboxField from "../../checkboxField";
|
||||
import { InputNotifyPeer, PeerNotifySettings, Update } from "../../../layer";
|
||||
import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager";
|
||||
import { SliderSuperTabEventable } from "../../sliderTab";
|
||||
import { copy } from "../../../helpers/object";
|
||||
import rootScope from "../../../lib/rootScope";
|
||||
import { convertKeyToInputKey } from "../../../helpers/string";
|
||||
|
||||
type InputNotifyKey = Exclude<InputNotifyPeer['_'], 'inputNotifyPeer'>;
|
||||
|
||||
export default class AppNotificationsTab extends SliderSuperTabEventable {
|
||||
protected init() {
|
||||
this.container.classList.add('notifications-container');
|
||||
this.title.innerText = 'Notifications';
|
||||
|
||||
const NotifySection = (options: {
|
||||
name: string,
|
||||
typeText: string,
|
||||
inputKey: InputNotifyKey,
|
||||
}) => {
|
||||
const section = new SettingSection({
|
||||
name: options.name
|
||||
});
|
||||
|
||||
const enabledRow = new Row({
|
||||
checkboxField: new CheckboxField({text: options.typeText, checked: true}),
|
||||
subtitle: 'Loading...',
|
||||
});
|
||||
|
||||
const previewEnabledRow = new Row({
|
||||
checkboxField: new CheckboxField({text: 'Message preview', checked: true}),
|
||||
subtitle: 'Loading...',
|
||||
});
|
||||
|
||||
section.content.append(enabledRow.container, previewEnabledRow.container);
|
||||
|
||||
this.scrollable.append(section.container);
|
||||
|
||||
const inputNotifyPeer = {_: options.inputKey};
|
||||
appNotificationsManager.getNotifySettings(inputNotifyPeer).then((notifySettings) => {
|
||||
const applySettings = () => {
|
||||
const muted = appNotificationsManager.isMuted(notifySettings);
|
||||
enabledRow.checkboxField.value = !muted;
|
||||
previewEnabledRow.checkboxField.value = notifySettings.show_previews;
|
||||
|
||||
return muted;
|
||||
};
|
||||
|
||||
applySettings();
|
||||
|
||||
this.eventListener.addListener('destroy', () => {
|
||||
const mute = !enabledRow.checkboxField.value;
|
||||
const showPreviews = previewEnabledRow.checkboxField.value;
|
||||
|
||||
if(mute === appNotificationsManager.isMuted(notifySettings) && showPreviews === notifySettings.show_previews) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputSettings: any = copy(notifySettings);
|
||||
inputSettings._ = 'inputPeerNotifySettings';
|
||||
inputSettings.mute_until = mute ? 2147483647 : 0;
|
||||
inputSettings.show_previews = showPreviews;
|
||||
|
||||
appNotificationsManager.updateNotifySettings(inputNotifyPeer, inputSettings);
|
||||
}, true);
|
||||
|
||||
this.listenerSetter.add(rootScope, 'notify_settings', (update: Update.updateNotifySettings) => {
|
||||
const inputKey = convertKeyToInputKey(update.peer._) as any;
|
||||
if(options.inputKey === inputKey) {
|
||||
notifySettings = update.notify_settings;
|
||||
applySettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
NotifySection({
|
||||
name: 'Private Chats',
|
||||
typeText: 'Notifications for private chats',
|
||||
inputKey: 'inputNotifyUsers'
|
||||
});
|
||||
|
||||
NotifySection({
|
||||
name: 'Groups',
|
||||
typeText: 'Notifications for groups',
|
||||
inputKey: 'inputNotifyChats'
|
||||
});
|
||||
|
||||
NotifySection({
|
||||
name: 'Channels',
|
||||
typeText: 'Notifications for channels',
|
||||
inputKey: 'inputNotifyBroadcasts'
|
||||
});
|
||||
|
||||
{
|
||||
const section = new SettingSection({
|
||||
name: 'Other'
|
||||
});
|
||||
|
||||
const contactsSignUpRow = new Row({
|
||||
checkboxField: new CheckboxField({text: 'Contacts joined Telegram', checked: true}),
|
||||
subtitle: 'Loading...',
|
||||
});
|
||||
|
||||
const soundRow = new Row({
|
||||
checkboxField: new CheckboxField({text: 'Notification sound', checked: true, stateKey: 'settings.notifications.sound'}),
|
||||
subtitle: 'Enabled',
|
||||
});
|
||||
|
||||
section.content.append(contactsSignUpRow.container, soundRow.container);
|
||||
|
||||
this.scrollable.append(section.container);
|
||||
|
||||
appNotificationsManager.getContactSignUpNotification().then(enabled => {
|
||||
contactsSignUpRow.checkboxField.value = enabled;
|
||||
|
||||
this.eventListener.addListener('destroy', () => {
|
||||
const _enabled = contactsSignUpRow.checkboxField.value;
|
||||
if(enabled !== _enabled) {
|
||||
appNotificationsManager.setContactSignUpNotification(!_enabled);
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import apiManager from "../../../lib/mtproto/mtprotoworker";
|
|||
import AppBlockedUsersTab from "./blockedUsers";
|
||||
import appUsersManager from "../../../lib/appManagers/appUsersManager";
|
||||
import rootScope from "../../../lib/rootScope";
|
||||
import { convertKeyToInputKey } from "../../../helpers/string";
|
||||
|
||||
export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
||||
private activeSessionsRow: Row;
|
||||
|
@ -204,11 +205,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
|
|||
}
|
||||
|
||||
rootScope.on('privacy_update', (update) => {
|
||||
let key: string = update.key._;
|
||||
key = key[0].toUpperCase() + key.slice(1);
|
||||
key = 'input' + key;
|
||||
|
||||
updatePrivacyRow(key as any);
|
||||
updatePrivacyRow(convertKeyToInputKey(update.key._) as any);
|
||||
});
|
||||
|
||||
container.append(numberVisibilityRow.container, lastSeenTimeRow.container, photoVisibilityRow.container, callRow.container, linkAccountRow.container, groupChatsAddRow.container);
|
||||
|
|
|
@ -8,6 +8,7 @@ import AppPrivacyAndSecurityTab from "./privacyAndSecurity";
|
|||
import AppGeneralSettingsTab from "./generalSettings";
|
||||
import AppEditProfileTab from "./editProfile";
|
||||
import AppChatFoldersTab from "./chatFolders";
|
||||
import AppNotificationsTab from "./notifications";
|
||||
//import AppMediaViewer from "../../appMediaViewerNew";
|
||||
|
||||
export default class AppSettingsTab extends SliderSuperTab {
|
||||
|
@ -97,7 +98,7 @@ export default class AppSettingsTab extends SliderSuperTab {
|
|||
buttonsDiv.append(this.buttons.edit = Button(className, {icon: 'edit', text: 'Edit Profile'}));
|
||||
buttonsDiv.append(this.buttons.folders = Button(className, {icon: 'folder', text: 'Chat Folders'}));
|
||||
buttonsDiv.append(this.buttons.general = Button(className, {icon: 'settings', text: 'General Settings'}));
|
||||
buttonsDiv.append(this.buttons.notifications = Button(className, {icon: 'unmute', text: 'Notifications', disabled: true}));
|
||||
buttonsDiv.append(this.buttons.notifications = Button(className, {icon: 'unmute', text: 'Notifications'}));
|
||||
buttonsDiv.append(this.buttons.privacy = Button(className, {icon: 'lock', text: 'Privacy and Security'}));
|
||||
buttonsDiv.append(this.buttons.language = Button(className, {icon: 'language', text: 'Language', disabled: true}));
|
||||
|
||||
|
@ -122,6 +123,10 @@ export default class AppSettingsTab extends SliderSuperTab {
|
|||
new AppGeneralSettingsTab(this.slider as any).open();
|
||||
});
|
||||
|
||||
this.buttons.notifications.addEventListener('click', () => {
|
||||
new AppNotificationsTab(this.slider).open();
|
||||
});
|
||||
|
||||
this.buttons.privacy.addEventListener('click', () => {
|
||||
new AppPrivacyAndSecurityTab(this.slider).open();
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper.";
|
|||
import AvatarElement from "../../avatar";
|
||||
import Scrollable from "../../scrollable";
|
||||
import { SliderTab } from "../../slider";
|
||||
import CheckboxField from "../../checkbox";
|
||||
import CheckboxField from "../../checkboxField";
|
||||
import { attachClickEvent, cancelEvent } from "../../../helpers/dom";
|
||||
import appSidebarRight from "..";
|
||||
import { TransitionSlider } from "../../transition";
|
||||
|
@ -81,7 +81,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
|||
notificationsStatus: this.profileContentEl.querySelector('.profile-row-notifications > p')
|
||||
};
|
||||
|
||||
const checkboxField = CheckboxField({
|
||||
const checkboxField = new CheckboxField({
|
||||
text: 'Notifications',
|
||||
name: 'notifications'
|
||||
});
|
||||
|
|
|
@ -87,3 +87,14 @@ export const checkRTL = (s: string) => {
|
|||
};
|
||||
|
||||
//(window as any).checkRTL = checkRTL;
|
||||
|
||||
export function convertInputKeyToKey(inputKey: string) {
|
||||
const str = inputKey.replace('input', '');
|
||||
return (str[0].toLowerCase() + str.slice(1)) as string;
|
||||
}
|
||||
|
||||
export function convertKeyToInputKey(key: string) {
|
||||
key = key[0].toUpperCase() + key.slice(1);
|
||||
key = 'input' + key;
|
||||
return key;
|
||||
}
|
||||
|
|
|
@ -21,3 +21,5 @@ export const isAppleMobile = (/iPad|iPhone|iPod/.test(navigator.platform) ||
|
|||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))))/* || true */;
|
||||
|
||||
export const isMobileSafari = isSafari && isAppleMobile;
|
||||
|
||||
export const isMobile = /* screen.width && screen.width < 480 || */navigator.userAgent.search(/iOS|iPhone OS|Android|BlackBerry|BB10|Series ?[64]0|J2ME|MIDP|opera mini|opera mobi|mobi.+Gecko|Windows Phone/i) != -1;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//import apiManager from '../mtproto/apiManager';
|
||||
import { MOUNT_CLASS_TO } from '../../config/debug';
|
||||
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
|
||||
import { logger, LogLevels } from '../logger';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import rootScope from '../rootScope';
|
||||
|
@ -10,14 +10,14 @@ import appStateManager from './appStateManager';
|
|||
import appUsersManager from "./appUsersManager";
|
||||
|
||||
type UpdatesState = {
|
||||
pendingPtsUpdates: any[],
|
||||
pendingSeqUpdates?: any,
|
||||
pendingPtsUpdates: {pts: number, pts_count: number}[],
|
||||
pendingSeqUpdates?: {[seq: number]: {seq: number, date: number, updates: any[]}},
|
||||
syncPending: {
|
||||
seqAwaiting?: number,
|
||||
ptsAwaiting?: true,
|
||||
timeout: number
|
||||
},
|
||||
syncLoading: boolean,
|
||||
syncLoading: Promise<void>,
|
||||
|
||||
seq?: number,
|
||||
pts?: number,
|
||||
|
@ -32,13 +32,14 @@ export class ApiUpdatesManager {
|
|||
pendingPtsUpdates: [],
|
||||
pendingSeqUpdates: {},
|
||||
syncPending: null,
|
||||
syncLoading: true
|
||||
syncLoading: null
|
||||
};
|
||||
|
||||
public channelStates: {[channelId: number]: UpdatesState} = {};
|
||||
private attached = false;
|
||||
|
||||
private log = logger('UPDATES', LogLevels.error | LogLevels.log | LogLevels.warn | LogLevels.debug);
|
||||
private debug = DEBUG;
|
||||
|
||||
constructor() {
|
||||
// * false for test purposes
|
||||
|
@ -53,31 +54,33 @@ export class ApiUpdatesManager {
|
|||
}
|
||||
|
||||
public popPendingSeqUpdate() {
|
||||
const nextSeq = this.updatesState.seq + 1;
|
||||
const pendingUpdatesData = this.updatesState.pendingSeqUpdates[nextSeq];
|
||||
const state = this.updatesState;
|
||||
const nextSeq = state.seq + 1;
|
||||
const pendingUpdatesData = state.pendingSeqUpdates[nextSeq];
|
||||
if(!pendingUpdatesData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const updates = pendingUpdatesData.updates;
|
||||
for(let i = 0, length = updates.length; i < length; i++) {
|
||||
for(let i = 0, length = updates.length; i < length; ++i) {
|
||||
this.saveUpdate(updates[i]);
|
||||
}
|
||||
this.updatesState.seq = pendingUpdatesData.seq;
|
||||
if(pendingUpdatesData.date && this.updatesState.date < pendingUpdatesData.date) {
|
||||
this.updatesState.date = pendingUpdatesData.date;
|
||||
|
||||
state.seq = pendingUpdatesData.seq;
|
||||
if(pendingUpdatesData.date && state.date < pendingUpdatesData.date) {
|
||||
state.date = pendingUpdatesData.date;
|
||||
}
|
||||
delete this.updatesState.pendingSeqUpdates[nextSeq];
|
||||
delete state.pendingSeqUpdates[nextSeq];
|
||||
|
||||
if(!this.popPendingSeqUpdate() &&
|
||||
this.updatesState.syncPending &&
|
||||
this.updatesState.syncPending.seqAwaiting &&
|
||||
this.updatesState.seq >= this.updatesState.syncPending.seqAwaiting) {
|
||||
if(!this.updatesState.syncPending.ptsAwaiting) {
|
||||
clearTimeout(this.updatesState.syncPending.timeout);
|
||||
this.updatesState.syncPending = null;
|
||||
state.syncPending &&
|
||||
state.syncPending.seqAwaiting &&
|
||||
state.seq >= state.syncPending.seqAwaiting) {
|
||||
if(!state.syncPending.ptsAwaiting) {
|
||||
clearTimeout(state.syncPending.timeout);
|
||||
state.syncPending = null;
|
||||
} else {
|
||||
delete this.updatesState.syncPending.seqAwaiting;
|
||||
delete state.syncPending.seqAwaiting;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +92,8 @@ export class ApiUpdatesManager {
|
|||
if(!curState.pendingPtsUpdates.length) {
|
||||
return false;
|
||||
}
|
||||
curState.pendingPtsUpdates.sort((a: any, b: any) => {
|
||||
|
||||
curState.pendingPtsUpdates.sort((a, b) => {
|
||||
return a.pts - b.pts;
|
||||
});
|
||||
// this.log('pop update', channelId, curState.pendingPtsUpdates)
|
||||
|
@ -97,7 +101,7 @@ export class ApiUpdatesManager {
|
|||
let curPts = curState.pts;
|
||||
let goodPts = 0;
|
||||
let goodIndex = 0;
|
||||
for(let i = 0, length = curState.pendingPtsUpdates.length; i < length; i++) {
|
||||
for(let i = 0, length = curState.pendingPtsUpdates.length; i < length; ++i) {
|
||||
const update = curState.pendingPtsUpdates[i];
|
||||
curPts += update.pts_count;
|
||||
if(curPts >= update.pts) {
|
||||
|
@ -110,10 +114,10 @@ export class ApiUpdatesManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
this.log('pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1));
|
||||
this.debug && this.log('pop pending pts updates', goodPts, curState.pendingPtsUpdates.slice(0, goodIndex + 1));
|
||||
|
||||
curState.pts = goodPts;
|
||||
for(let i = 0; i <= goodIndex; i++) {
|
||||
for(let i = 0; i <= goodIndex; ++i) {
|
||||
const update = curState.pendingPtsUpdates[i];
|
||||
this.saveUpdate(update);
|
||||
}
|
||||
|
@ -137,18 +141,18 @@ export class ApiUpdatesManager {
|
|||
}
|
||||
}
|
||||
|
||||
public processUpdateMessage = (updateMessage: any, options: Partial<{
|
||||
public processUpdateMessage = (updateMessage: any/* , options: Partial<{
|
||||
ignoreSyncLoading: boolean
|
||||
}> = {}) => {
|
||||
}> = {} */) => {
|
||||
// return forceGetDifference()
|
||||
const processOpts = {
|
||||
date: updateMessage.date,
|
||||
seq: updateMessage.seq,
|
||||
seqStart: updateMessage.seq_start,
|
||||
ignoreSyncLoading: options.ignoreSyncLoading
|
||||
//ignoreSyncLoading: options.ignoreSyncLoading
|
||||
};
|
||||
|
||||
this.log('processUpdateMessage', updateMessage);
|
||||
this.debug && this.log('processUpdateMessage', updateMessage);
|
||||
|
||||
switch(updateMessage._) {
|
||||
case 'updatesTooLong':
|
||||
|
@ -162,7 +166,7 @@ export class ApiUpdatesManager {
|
|||
|
||||
case 'updateShortMessage':
|
||||
case 'updateShortChatMessage': {
|
||||
this.log('updateShortMessage | updateShortChatMessage', {...updateMessage});
|
||||
this.debug && this.log('updateShortMessage | updateShortChatMessage', {...updateMessage});
|
||||
const isOut = updateMessage.pFlags.out;
|
||||
const fromId = updateMessage.from_id || (isOut ? rootScope.myId : updateMessage.user_id);
|
||||
const toId = updateMessage.chat_id
|
||||
|
@ -204,37 +208,34 @@ export class ApiUpdatesManager {
|
|||
}
|
||||
};
|
||||
|
||||
public getDifference(first = false) {
|
||||
public getDifference(first = false): Promise<void> {
|
||||
// this.trace('Get full diff')
|
||||
const updatesState = this.updatesState;
|
||||
if(!updatesState.syncLoading) {
|
||||
updatesState.syncLoading = true;
|
||||
let wasSyncing = updatesState.syncLoading;
|
||||
if(!wasSyncing) {
|
||||
updatesState.pendingSeqUpdates = {};
|
||||
updatesState.pendingPtsUpdates = [];
|
||||
rootScope.broadcast('state_synchronizing');
|
||||
}
|
||||
|
||||
if(updatesState.syncPending) {
|
||||
clearTimeout(updatesState.syncPending.timeout);
|
||||
updatesState.syncPending = null;
|
||||
}
|
||||
|
||||
return apiManager.invokeApi('updates.getDifference', {
|
||||
|
||||
const promise = apiManager.invokeApi('updates.getDifference', {
|
||||
pts: updatesState.pts,
|
||||
date: updatesState.date,
|
||||
qts: -1
|
||||
}, {
|
||||
timeout: 0x7fffffff
|
||||
}).then((differenceResult) => {
|
||||
this.log('Get diff result', differenceResult);
|
||||
this.debug && this.log('Get diff result', differenceResult);
|
||||
|
||||
if(differenceResult._ === 'updates.differenceEmpty') {
|
||||
this.log('apply empty diff', differenceResult.seq);
|
||||
this.debug && this.log('apply empty diff', differenceResult.seq);
|
||||
updatesState.date = differenceResult.date;
|
||||
updatesState.seq = differenceResult.seq;
|
||||
updatesState.syncLoading = false;
|
||||
rootScope.broadcast('state_synchronized');
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// ! SORRY I'M SORRY I'M SORRY
|
||||
|
@ -284,23 +285,24 @@ export class ApiUpdatesManager {
|
|||
// this.log('apply diff', updatesState.seq, updatesState.pts)
|
||||
|
||||
if(differenceResult._ === 'updates.differenceSlice') {
|
||||
this.getDifference();
|
||||
return this.getDifference();
|
||||
} else {
|
||||
// this.log('finished get diff')
|
||||
rootScope.broadcast('state_synchronized');
|
||||
updatesState.syncLoading = false;
|
||||
this.debug && this.log('finished get diff');
|
||||
}
|
||||
}, () => {
|
||||
updatesState.syncLoading = false;
|
||||
});
|
||||
|
||||
if(!wasSyncing) {
|
||||
this.justAName(updatesState, promise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public getChannelDifference(channelId: number) {
|
||||
public getChannelDifference(channelId: number): Promise<void> {
|
||||
const channelState = this.getChannelState(channelId);
|
||||
if(!channelState.syncLoading) {
|
||||
channelState.syncLoading = true;
|
||||
const wasSyncing = channelState.syncLoading;
|
||||
if(!wasSyncing) {
|
||||
channelState.pendingPtsUpdates = [];
|
||||
rootScope.broadcast('state_synchronizing', channelId);
|
||||
}
|
||||
|
||||
if(channelState.syncPending) {
|
||||
|
@ -309,40 +311,37 @@ export class ApiUpdatesManager {
|
|||
}
|
||||
|
||||
//this.log.trace('Get channel diff', appChatsManager.getChat(channelId), channelState.pts);
|
||||
apiManager.invokeApi('updates.getChannelDifference', {
|
||||
const promise = apiManager.invokeApi('updates.getChannelDifference', {
|
||||
channel: appChatsManager.getChannelInput(channelId),
|
||||
filter: {_: 'channelMessagesFilterEmpty'},
|
||||
pts: channelState.pts,
|
||||
limit: 30
|
||||
}, {timeout: 0x7fffffff}).then((differenceResult) => {
|
||||
this.log('Get channel diff result', differenceResult)
|
||||
this.debug && this.log('Get channel diff result', differenceResult)
|
||||
channelState.pts = 'pts' in differenceResult ? differenceResult.pts : undefined;
|
||||
|
||||
if(differenceResult._ === 'updates.channelDifferenceEmpty') {
|
||||
this.log('apply channel empty diff', differenceResult);
|
||||
channelState.syncLoading = false;
|
||||
rootScope.broadcast('state_synchronized', channelId);
|
||||
return false;
|
||||
this.debug && this.log('apply channel empty diff', differenceResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if(differenceResult._ === 'updates.channelDifferenceTooLong') {
|
||||
this.log('channel diff too long', differenceResult);
|
||||
channelState.syncLoading = false;
|
||||
this.debug && this.log('channel diff too long', differenceResult);
|
||||
delete this.channelStates[channelId];
|
||||
this.saveUpdate({_: 'updateChannelReload', channel_id: channelId});
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
appUsersManager.saveApiUsers(differenceResult.users);
|
||||
appChatsManager.saveApiChats(differenceResult.chats);
|
||||
|
||||
// Should be first because of updateMessageID
|
||||
this.log('applying', differenceResult.other_updates.length, 'channel other updates');
|
||||
this.debug && this.log('applying', differenceResult.other_updates.length, 'channel other updates');
|
||||
differenceResult.other_updates.forEach((update) => {
|
||||
this.saveUpdate(update);
|
||||
});
|
||||
|
||||
this.log('applying', differenceResult.new_messages.length, 'channel new messages');
|
||||
this.debug && this.log('applying', differenceResult.new_messages.length, 'channel new messages');
|
||||
differenceResult.new_messages.forEach((apiMessage) => {
|
||||
this.saveUpdate({
|
||||
_: 'updateNewChannelMessage',
|
||||
|
@ -352,18 +351,32 @@ export class ApiUpdatesManager {
|
|||
});
|
||||
});
|
||||
|
||||
this.log('apply channel diff', channelState.pts);
|
||||
this.debug && this.log('apply channel diff', channelState.pts);
|
||||
|
||||
if(differenceResult._ === 'updates.channelDifference' &&
|
||||
!differenceResult.pFlags['final']) {
|
||||
this.getChannelDifference(channelId);
|
||||
return this.getChannelDifference(channelId);
|
||||
} else {
|
||||
this.log('finished channel get diff');
|
||||
rootScope.broadcast('state_synchronized', channelId);
|
||||
channelState.syncLoading = false;
|
||||
this.debug && this.log('finished channel get diff');
|
||||
}
|
||||
});
|
||||
|
||||
if(!wasSyncing) {
|
||||
this.justAName(channelState, promise, channelId);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private justAName(state: UpdatesState, promise: UpdatesState['syncLoading'], channelId?: number) {
|
||||
state.syncLoading = promise;
|
||||
rootScope.broadcast('state_synchronizing', channelId);
|
||||
|
||||
promise.then(() => {
|
||||
state.syncLoading = null;
|
||||
rootScope.broadcast('state_synchronized', channelId);
|
||||
}, () => {
|
||||
channelState.syncLoading = false;
|
||||
state.syncLoading = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -377,7 +390,7 @@ export class ApiUpdatesManager {
|
|||
pts,
|
||||
pendingPtsUpdates: [],
|
||||
syncPending: null,
|
||||
syncLoading: false
|
||||
syncLoading: null
|
||||
};
|
||||
|
||||
return true;
|
||||
|
@ -397,8 +410,8 @@ export class ApiUpdatesManager {
|
|||
public processUpdate(update: any, options: Partial<{
|
||||
date: number,
|
||||
seq: number,
|
||||
seqStart: number,
|
||||
ignoreSyncLoading: boolean
|
||||
seqStart: number/* ,
|
||||
ignoreSyncLoading: boolean */
|
||||
}> = {}) {
|
||||
let channelId = 0;
|
||||
switch(update._) {
|
||||
|
@ -421,7 +434,7 @@ export class ApiUpdatesManager {
|
|||
|
||||
// this.log.log('process', channelId, curState.pts, update)
|
||||
|
||||
if(curState.syncLoading && !options.ignoreSyncLoading) {
|
||||
if(curState.syncLoading/* && !options.ignoreSyncLoading */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -466,18 +479,24 @@ export class ApiUpdatesManager {
|
|||
if(update.pts) {
|
||||
const newPts = curState.pts + (update.pts_count || 0);
|
||||
if(newPts < update.pts) {
|
||||
this.log.warn('Pts hole', curState, update, channelId && appChatsManager.getChat(channelId));
|
||||
this.debug && this.log.warn('Pts hole', curState, update, channelId && appChatsManager.getChat(channelId));
|
||||
curState.pendingPtsUpdates.push(update);
|
||||
if(!curState.syncPending) {
|
||||
if(!curState.syncPending && !curState.syncLoading) {
|
||||
curState.syncPending = {
|
||||
timeout: window.setTimeout(() => {
|
||||
curState.syncPending = null;
|
||||
|
||||
if(curState.syncLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(channelId) {
|
||||
this.getChannelDifference(channelId);
|
||||
} else {
|
||||
this.getDifference();
|
||||
}
|
||||
}, SYNC_DELAY)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
curState.syncPending.ptsAwaiting = true;
|
||||
|
@ -503,7 +522,7 @@ export class ApiUpdatesManager {
|
|||
|
||||
if(seqStart !== curState.seq + 1) {
|
||||
if(seqStart > curState.seq) {
|
||||
this.log.warn('Seq hole', curState, curState.syncPending && curState.syncPending.seqAwaiting);
|
||||
this.debug && this.log.warn('Seq hole', curState, curState.syncPending && curState.syncPending.seqAwaiting);
|
||||
|
||||
if(curState.pendingSeqUpdates[seqStart] === undefined) {
|
||||
curState.pendingSeqUpdates[seqStart] = {seq, date: options.date, updates: []};
|
||||
|
@ -513,9 +532,15 @@ export class ApiUpdatesManager {
|
|||
if(!curState.syncPending) {
|
||||
curState.syncPending = {
|
||||
timeout: window.setTimeout(() => {
|
||||
curState.syncPending = null;
|
||||
|
||||
if(curState.syncLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getDifference();
|
||||
}, SYNC_DELAY)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if(!curState.syncPending.seqAwaiting ||
|
||||
|
@ -563,20 +588,23 @@ export class ApiUpdatesManager {
|
|||
|
||||
//rootScope.broadcast('state_synchronizing');
|
||||
if(!state || !state.pts || !state.date || !state.seq) {
|
||||
apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult) => {
|
||||
this.updatesState.seq = stateResult.seq;
|
||||
this.updatesState.pts = stateResult.pts;
|
||||
this.updatesState.date = stateResult.date;
|
||||
//setTimeout(() => {
|
||||
this.updatesState.syncLoading = false;
|
||||
//rootScope.broadcast('state_synchronized');
|
||||
//}, 1000);
|
||||
|
||||
// ! for testing
|
||||
// updatesState.seq = 1
|
||||
// updatesState.pts = stateResult.pts - 5000
|
||||
// updatesState.date = 1
|
||||
// getDifference()
|
||||
this.updatesState.syncLoading = new Promise((resolve) => {
|
||||
apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult) => {
|
||||
this.updatesState.seq = stateResult.seq;
|
||||
this.updatesState.pts = stateResult.pts;
|
||||
this.updatesState.date = stateResult.date;
|
||||
//setTimeout(() => {
|
||||
this.updatesState.syncLoading = null;
|
||||
resolve();
|
||||
//rootScope.broadcast('state_synchronized');
|
||||
//}, 1000);
|
||||
|
||||
// ! for testing
|
||||
// updatesState.seq = 1
|
||||
// updatesState.pts = stateResult.pts - 5000
|
||||
// updatesState.date = 1
|
||||
// getDifference()
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// ! for testing
|
||||
|
|
|
@ -326,13 +326,13 @@ export class AppChatsManager {
|
|||
return this.cachedPhotoLocations[id];
|
||||
}
|
||||
|
||||
/* public getChatString(id: number) {
|
||||
public getChatString(id: number) {
|
||||
const chat = this.getChat(id);
|
||||
if(this.isChannel(id)) {
|
||||
return (this.isMegagroup(id) ? 's' : 'c') + id + '_' + chat.access_hash;
|
||||
}
|
||||
return 'g' + id;
|
||||
} */
|
||||
}
|
||||
|
||||
public getChatMembersString(id: number) {
|
||||
const chat = this.getChat(id);
|
||||
|
|
|
@ -5,7 +5,6 @@ import { attachContextMenuListener, putPreloader } from "../../components/misc";
|
|||
import { ripple } from "../../components/ripple";
|
||||
//import Scrollable from "../../components/scrollable";
|
||||
import Scrollable, { ScrollableX, SliceSides } from "../../components/scrollable";
|
||||
import appSidebarLeft from "../../components/sidebarLeft";
|
||||
import { formatDateAccordingToToday } from "../../helpers/date";
|
||||
import { escapeRegExp } from "../../helpers/string";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
|
@ -165,8 +164,8 @@ class ConnectionStatusComponent {
|
|||
DEBUG && this.log('setState: isShown:', this.connecting || this.updating);
|
||||
};
|
||||
|
||||
//this.setStateTimeout = window.setTimeout(cb, timeout);
|
||||
cb();
|
||||
this.setStateTimeout = window.setTimeout(cb, timeout);
|
||||
//cb();
|
||||
/* if(timeout) this.setStateTimeout = window.setTimeout(cb, timeout);
|
||||
else cb(); */
|
||||
});
|
||||
|
@ -499,9 +498,19 @@ export class AppDialogsManager {
|
|||
|
||||
return this.loadDialogs();
|
||||
}).then(() => {
|
||||
appMessagesManager.getConversationsAll('', 0).finally(() => {
|
||||
appMessagesManager.getConversationsAll('', 1).then(() => {
|
||||
const allDialogsLoaded = appMessagesManager.dialogsStorage.allDialogsLoaded;
|
||||
const wasLoaded = allDialogsLoaded[0] || allDialogsLoaded[1];
|
||||
const a: Promise<any> = allDialogsLoaded[0] ? Promise.resolve() : appMessagesManager.getConversationsAll('', 0);
|
||||
const b: Promise<any> = allDialogsLoaded[1] ? Promise.resolve() : appMessagesManager.getConversationsAll('', 1);
|
||||
a.finally(() => {
|
||||
b.then(() => {
|
||||
this.accumulateArchivedUnread();
|
||||
|
||||
if(wasLoaded) {
|
||||
(apiUpdatesManager.updatesState.syncLoading || Promise.resolve()).then(() => {
|
||||
appMessagesManager.refreshConversations();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -69,7 +69,11 @@ export class AppDraftsManager {
|
|||
public getAllDrafts() {
|
||||
return this.getAllDraftPromise || (this.getAllDraftPromise = new Promise((resolve) => {
|
||||
apiManager.invokeApi('messages.getAllDrafts').then((updates) => {
|
||||
apiUpdatesManager.processUpdateMessage(updates, {ignoreSyncLoading: true});
|
||||
const p = apiUpdatesManager.updatesState.syncLoading || Promise.resolve();
|
||||
p.then(() => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -86,14 +86,17 @@ export class AppImManager {
|
|||
this.offline = rootScope.idle.isIDLE = true;
|
||||
this.updateStatus();
|
||||
clearInterval(this.updateStatusInterval);
|
||||
rootScope.broadcast('idle', true);
|
||||
|
||||
window.addEventListener('focus', () => {
|
||||
this.offline = rootScope.idle.isIDLE = false;
|
||||
this.updateStatus();
|
||||
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
|
||||
|
||||
|
||||
// в обратном порядке
|
||||
animationIntersector.checkAnimations(false);
|
||||
|
||||
rootScope.broadcast('idle', false);
|
||||
}, {once: true});
|
||||
});
|
||||
|
||||
|
@ -101,6 +104,8 @@ export class AppImManager {
|
|||
window.addEventListener(FOCUS_EVENT_NAME, () => {
|
||||
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
|
||||
this.updateStatus();
|
||||
|
||||
rootScope.broadcast('idle', false);
|
||||
}, {once: true, passive: true});
|
||||
|
||||
this.chatsContainer = document.createElement('div');
|
||||
|
|
|
@ -6,7 +6,7 @@ import { createPosterForVideo } from "../../helpers/files";
|
|||
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
|
||||
import { randomLong } from "../../helpers/random";
|
||||
import { splitStringByLength, limitSymbols } from "../../helpers/string";
|
||||
import { ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputNotifyPeer, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer";
|
||||
import { ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update } from "../../layer";
|
||||
import { InvokeApiOptions } from "../../types";
|
||||
import { langPack } from "../langPack";
|
||||
import { logger, LogLevels } from "../logger";
|
||||
|
@ -37,6 +37,7 @@ import { getFileNameByLocation } from "../../helpers/fileName";
|
|||
import appProfileManager from "./appProfileManager";
|
||||
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug";
|
||||
import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray";
|
||||
import appNotificationsManager, { NotifyOptions } from "./appNotificationsManager";
|
||||
|
||||
//console.trace('include');
|
||||
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
|
||||
|
@ -155,7 +156,14 @@ export class AppMessagesManager {
|
|||
public newMessagesToHandle: {[peerId: string]: number[]} = {};
|
||||
public newDialogsHandlePromise = 0;
|
||||
public newDialogsToHandle: {[peerId: string]: {reload: true} | Dialog} = {};
|
||||
public newUpdatesAfterReloadToHandle: any = {};
|
||||
public newUpdatesAfterReloadToHandle: {[peerId: string]: any[]} = {};
|
||||
|
||||
private notificationsHandlePromise = 0;
|
||||
private notificationsToHandle: {[peerId: string]: {
|
||||
fwdCount: number,
|
||||
fromId: number,
|
||||
topMessage?: MyMessage
|
||||
}} = {};
|
||||
|
||||
private reloadConversationsPromise: Promise<void>;
|
||||
private reloadConversationsPeers: number[] = [];
|
||||
|
@ -342,6 +350,8 @@ export class AppMessagesManager {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
appNotificationsManager.start();
|
||||
}
|
||||
|
||||
public getInputEntities(entities: MessageEntity[]) {
|
||||
|
@ -1543,6 +1553,32 @@ export class AppMessagesManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
public async refreshConversations() {
|
||||
const limit = 100, outDialogs: Dialog[] = [];
|
||||
for(let folderId = 0; folderId < 2; ++folderId) {
|
||||
let offsetDate = 0;
|
||||
for(;;) {
|
||||
const {dialogs} = await appMessagesManager.getTopMessages(limit, folderId, offsetDate);
|
||||
|
||||
if(dialogs.length) {
|
||||
outDialogs.push(...dialogs as Dialog[]);
|
||||
const dialog = dialogs[dialogs.length - 1];
|
||||
offsetDate = this.getMessageByPeer(dialog.peerId, dialog.top_message).date;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let obj: {[peerId: string]: Dialog} = {};
|
||||
outDialogs.forEach(dialog => {
|
||||
obj[dialog.peerId] = dialog;
|
||||
});
|
||||
rootScope.broadcast('dialogs_multiupdate', obj);
|
||||
|
||||
return outDialogs;
|
||||
}
|
||||
|
||||
public async getConversationsAll(query = '', folderId = 0) {
|
||||
const limit = 100, outDialogs: Dialog[] = [];
|
||||
for(; folderId < 2; ++folderId) {
|
||||
|
@ -1609,7 +1645,7 @@ export class AppMessagesManager {
|
|||
});
|
||||
}
|
||||
|
||||
return this.getTopMessages(limit, realFolderId).then(totalCount => {
|
||||
return this.getTopMessages(limit, realFolderId).then(messagesDialogs => {
|
||||
//const curDialogStorage = this.dialogsStorage[folderId];
|
||||
|
||||
offset = 0;
|
||||
|
@ -1625,7 +1661,7 @@ export class AppMessagesManager {
|
|||
|
||||
return {
|
||||
dialogs: curDialogStorage.slice(offset, offset + limit),
|
||||
count: totalCount,
|
||||
count: messagesDialogs._ === 'messages.dialogs' ? messagesDialogs.dialogs.length : messagesDialogs.count,
|
||||
isEnd: this.dialogsStorage.allDialogsLoaded[realFolderId] && (offset + limit) >= curDialogStorage.length
|
||||
};
|
||||
});
|
||||
|
@ -1645,16 +1681,19 @@ export class AppMessagesManager {
|
|||
}
|
||||
}
|
||||
|
||||
public getTopMessages(limit: number, folderId: number): Promise<number> {
|
||||
public getTopMessages(limit: number, folderId: number, offsetDate?: number) {
|
||||
const dialogs = this.dialogsStorage.getFolder(folderId);
|
||||
let offsetId = 0;
|
||||
let offsetDate = 0;
|
||||
let offsetPeerId = 0;
|
||||
let offsetIndex = 0;
|
||||
|
||||
if(this.dialogsStorage.dialogsOffsetDate[folderId]) {
|
||||
offsetDate = this.dialogsStorage.dialogsOffsetDate[folderId] + serverTimeManager.serverTimeOffset;
|
||||
offsetIndex = this.dialogsStorage.dialogsOffsetDate[folderId] * 0x10000;
|
||||
if(offsetDate === undefined) {
|
||||
offsetDate = this.dialogsStorage.getOffsetDate(folderId);
|
||||
}
|
||||
|
||||
if(offsetDate) {
|
||||
offsetIndex = offsetDate * 0x10000;
|
||||
offsetDate += serverTimeManager.serverTimeOffset;
|
||||
}
|
||||
|
||||
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
|
||||
|
@ -1747,7 +1786,7 @@ export class AppMessagesManager {
|
|||
rootScope.broadcast('dialogs_multiupdate', {});
|
||||
}
|
||||
|
||||
return count;
|
||||
return dialogsResult;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2766,6 +2805,7 @@ export class AppMessagesManager {
|
|||
(dialogsResult.dialogs as Dialog[]).forEach((dialog) => {
|
||||
const peerId = appPeersManager.getPeerId(dialog.peer);
|
||||
let topMessage = dialog.top_message;
|
||||
|
||||
const topPendingMessage = this.pendingTopMsgs[peerId];
|
||||
if(topPendingMessage) {
|
||||
if(!topMessage
|
||||
|
@ -2781,34 +2821,17 @@ export class AppMessagesManager {
|
|||
} */
|
||||
|
||||
if(topMessage || (dialog.draft && dialog.draft._ === 'draftMessage')) {
|
||||
//const wasDialogBefore = this.getDialogByPeerID(peerId)[0];
|
||||
|
||||
// here need to just replace, not FULL replace dialog! WARNING
|
||||
/* if(wasDialogBefore?.pFlags?.pinned && !dialog?.pFlags?.pinned) {
|
||||
this.log.error('here need to just replace, not FULL replace dialog! WARNING', wasDialogBefore, dialog);
|
||||
if(!dialog.pFlags) dialog.pFlags = {};
|
||||
dialog.pFlags.pinned = true;
|
||||
} */
|
||||
|
||||
this.saveConversation(dialog);
|
||||
|
||||
/* if(wasDialogBefore) {
|
||||
rootScope.$broadcast('dialog_top', dialog);
|
||||
} else { */
|
||||
//if(wasDialogBefore?.top_message !== topMessage) {
|
||||
updatedDialogs[peerId] = dialog;
|
||||
//}
|
||||
//}
|
||||
updatedDialogs[peerId] = dialog;
|
||||
} else {
|
||||
const dropped = this.dialogsStorage.dropDialog(peerId);
|
||||
if(dropped.length) {
|
||||
rootScope.broadcast('dialog_drop', {peerId: peerId, dialog: dropped[0]});
|
||||
rootScope.broadcast('dialog_drop', {peerId, dialog: dropped[0]});
|
||||
}
|
||||
}
|
||||
|
||||
if(this.newUpdatesAfterReloadToHandle[peerId] !== undefined) {
|
||||
for(const i in this.newUpdatesAfterReloadToHandle[peerId]) {
|
||||
const update = this.newUpdatesAfterReloadToHandle[peerId][i];
|
||||
for(const update of this.newUpdatesAfterReloadToHandle[peerId]) {
|
||||
this.handleUpdate(update);
|
||||
}
|
||||
|
||||
|
@ -2925,6 +2948,8 @@ export class AppMessagesManager {
|
|||
historyStorage.readMaxId = dialog.read_inbox_max_id;
|
||||
historyStorage.readOutboxMaxId = dialog.read_outbox_max_id;
|
||||
|
||||
appNotificationsManager.savePeerSettings(peerId, dialog.notify_settings)
|
||||
|
||||
if(channelId && dialog.pts) {
|
||||
apiUpdatesManager.addChannelState(channelId, dialog.pts);
|
||||
}
|
||||
|
@ -3534,6 +3559,19 @@ export class AppMessagesManager {
|
|||
});
|
||||
}
|
||||
|
||||
if(!threadId && historyStorage && historyStorage.history.length) {
|
||||
const slice = historyStorage.history.slice;
|
||||
for(const mid of slice) {
|
||||
const message = this.getMessageByPeer(peerId, mid);
|
||||
if(message && !message.pFlags.out) {
|
||||
message.pFlags.unread = false;
|
||||
appNotificationsManager.cancel('msg' + mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId));
|
||||
|
||||
if(historyStorage.readPromise) {
|
||||
return historyStorage.readPromise;
|
||||
}
|
||||
|
@ -3602,6 +3640,40 @@ export class AppMessagesManager {
|
|||
return this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: new SlicedArray()});
|
||||
}
|
||||
|
||||
private handleNotifications = () => {
|
||||
window.clearTimeout(this.notificationsHandlePromise);
|
||||
this.notificationsHandlePromise = 0;
|
||||
|
||||
//var timeout = $rootScope.idle.isIDLE && StatusManager.isOtherDeviceActive() ? 30000 : 1000;
|
||||
//const timeout = 1000;
|
||||
|
||||
for(const _peerId in this.notificationsToHandle) {
|
||||
const peerId = +_peerId;
|
||||
const notifyPeerToHandle = this.notificationsToHandle[peerId];
|
||||
|
||||
Promise.all([
|
||||
appNotificationsManager.getPeerMuted(peerId),
|
||||
appNotificationsManager.getNotifySettings(appPeersManager.getInputNotifyPeerById(peerId, true))
|
||||
]).then(([muted, peerTypeNotifySettings]) => {
|
||||
const topMessage = notifyPeerToHandle.topMessage;
|
||||
if(muted || !topMessage.pFlags.unread) {
|
||||
return;
|
||||
}
|
||||
|
||||
//setTimeout(() => {
|
||||
if(topMessage.pFlags.unread) {
|
||||
this.notifyAboutMessage(topMessage, {
|
||||
fwdCount: notifyPeerToHandle.fwdCount,
|
||||
peerTypeNotifySettings
|
||||
});
|
||||
}
|
||||
//}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
this.notificationsToHandle = {};
|
||||
};
|
||||
|
||||
public handleUpdate(update: Update) {
|
||||
/* if(DEBUG) {
|
||||
this.log.debug('handleUpdate', update._, update);
|
||||
|
@ -3703,14 +3775,40 @@ export class AppMessagesManager {
|
|||
}
|
||||
|
||||
const dialog = foundDialog[0];
|
||||
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
|
||||
if(dialog) {
|
||||
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
|
||||
this.setDialogTopMessage(message, dialog);
|
||||
if(inboxUnread) {
|
||||
dialog.unread_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(inboxUnread/* && ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE) */) {
|
||||
const notifyPeer = message.peerId;
|
||||
let notifyPeerToHandle = this.notificationsToHandle[notifyPeer];
|
||||
if(notifyPeerToHandle === undefined) {
|
||||
notifyPeerToHandle = this.notificationsToHandle[notifyPeer] = {
|
||||
fwdCount: 0,
|
||||
fromId: 0
|
||||
};
|
||||
}
|
||||
|
||||
if(notifyPeerToHandle.fromId !== message.fromId) {
|
||||
notifyPeerToHandle.fromId = message.fromId;
|
||||
notifyPeerToHandle.fwdCount = 0;
|
||||
}
|
||||
|
||||
if((message as Message.message).fwd_from) {
|
||||
notifyPeerToHandle.fwdCount++;
|
||||
}
|
||||
|
||||
notifyPeerToHandle.topMessage = message;
|
||||
|
||||
if(!this.notificationsHandlePromise) {
|
||||
this.notificationsHandlePromise = window.setTimeout(this.handleNotifications, 0);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3986,8 +4084,9 @@ export class AppMessagesManager {
|
|||
|
||||
if(!message.pFlags.out && !threadId && stillUnreadCount === undefined) {
|
||||
newUnreadCount = --foundDialog.unread_count;
|
||||
//NotificationsManager.cancel('msg' + messageId); // warning
|
||||
}
|
||||
|
||||
appNotificationsManager.cancel('msg' + messageId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4194,7 +4293,7 @@ export class AppMessagesManager {
|
|||
this.pendingTopMsgs[peerId] = messageId;
|
||||
this.handleUpdate({
|
||||
_: 'updateNewMessage',
|
||||
message: message
|
||||
message
|
||||
} as any);
|
||||
}
|
||||
|
||||
|
@ -4261,13 +4360,14 @@ export class AppMessagesManager {
|
|||
|
||||
case 'updateNotifySettings': {
|
||||
const {peer, notify_settings} = update;
|
||||
if(peer._ === 'notifyPeer') {
|
||||
const peerId = appPeersManager.getPeerId((peer as NotifyPeer.notifyPeer).peer);
|
||||
|
||||
const peerId = appPeersManager.getPeerId((peer as NotifyPeer.notifyPeer).peer);
|
||||
|
||||
const dialog = this.getDialogByPeerId(peerId)[0];
|
||||
if(dialog) {
|
||||
dialog.notify_settings = notify_settings;
|
||||
rootScope.broadcast('dialog_notify_settings', peerId);
|
||||
const dialog = this.getDialogByPeerId(peerId)[0];
|
||||
if(dialog) {
|
||||
dialog.notify_settings = notify_settings;
|
||||
rootScope.broadcast('dialog_notify_settings', peerId);
|
||||
}
|
||||
}
|
||||
|
||||
/////this.log('updateNotifySettings', peerId, notify_settings);
|
||||
|
@ -4379,17 +4479,11 @@ export class AppMessagesManager {
|
|||
}
|
||||
|
||||
public mutePeer(peerId: number) {
|
||||
let inputPeer = appPeersManager.getInputPeerById(peerId);
|
||||
let inputNotifyPeer: InputNotifyPeer.inputNotifyPeer = {
|
||||
_: 'inputNotifyPeer',
|
||||
peer: inputPeer
|
||||
};
|
||||
|
||||
let settings: InputPeerNotifySettings = {
|
||||
const settings: InputPeerNotifySettings = {
|
||||
_: 'inputPeerNotifySettings'
|
||||
};
|
||||
|
||||
let dialog = appMessagesManager.getDialogByPeerId(peerId)[0];
|
||||
const dialog = appMessagesManager.getDialogByPeerId(peerId)[0];
|
||||
let muted = true;
|
||||
if(dialog && dialog.notify_settings) {
|
||||
muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
|
||||
|
@ -4398,25 +4492,11 @@ export class AppMessagesManager {
|
|||
if(!muted) {
|
||||
settings.mute_until = 2147483647;
|
||||
}
|
||||
|
||||
apiManager.invokeApi('account.updateNotifySettings', {
|
||||
peer: inputNotifyPeer,
|
||||
settings: settings
|
||||
}).then(bool => {
|
||||
if(bool) {
|
||||
this.handleUpdate({
|
||||
_: 'updateNotifySettings',
|
||||
peer: {
|
||||
_: 'notifyPeer',
|
||||
peer: appPeersManager.getOutputPeer(peerId)
|
||||
},
|
||||
notify_settings: { // ! WOW, IT WORKS !
|
||||
...settings,
|
||||
_: 'peerNotifySettings',
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return appNotificationsManager.updateNotifySettings({
|
||||
_: 'inputNotifyPeer',
|
||||
peer: appPeersManager.getInputPeerById(peerId)
|
||||
}, settings);
|
||||
}
|
||||
|
||||
public canWriteToPeer(peerId: number) {
|
||||
|
@ -4530,6 +4610,226 @@ export class AppMessagesManager {
|
|||
});
|
||||
}
|
||||
|
||||
private notifyAboutMessage(message: MyMessage, options: Partial<{
|
||||
fwdCount: number,
|
||||
peerTypeNotifySettings: PeerNotifySettings
|
||||
}> = {}) {
|
||||
const peerId = this.getMessagePeer(message);
|
||||
let peerString: string;
|
||||
const notification: NotifyOptions = {};
|
||||
var notificationMessage: string,
|
||||
notificationPhoto: any;
|
||||
|
||||
const _ = (str: string) => str;
|
||||
|
||||
const localSettings = appNotificationsManager.getLocalSettings();
|
||||
if(options.peerTypeNotifySettings.show_previews) {
|
||||
if(message._ === 'message') {
|
||||
if(message.fwd_from && options.fwdCount) {
|
||||
notificationMessage = 'Forwarded ' + options.fwdCount + ' messages';//fwdMessagesPluralize(options.fwd_count);
|
||||
} else if(message.message) {
|
||||
if(localSettings.nopreview) {
|
||||
notificationMessage = _('conversation_message_sent');
|
||||
} else {
|
||||
notificationMessage = RichTextProcessor.wrapPlainText(message.message);
|
||||
}
|
||||
} else if(message.media) {
|
||||
var captionEmoji: string;
|
||||
switch (message.media._) {
|
||||
case 'messageMediaPhoto':
|
||||
notificationMessage = _('conversation_media_photo_raw');
|
||||
captionEmoji = '🖼';
|
||||
break;
|
||||
case 'messageMediaDocument':
|
||||
if(message.media.document._ === 'documentEmpty') break;
|
||||
switch(message.media.document.type) {
|
||||
case 'gif':
|
||||
notificationMessage = _('conversation_media_gif_raw');
|
||||
captionEmoji = '🎬';
|
||||
break;
|
||||
case 'sticker':
|
||||
notificationMessage = _('conversation_media_sticker');
|
||||
var stickerEmoji = message.media.document.stickerEmojiRaw;
|
||||
if(stickerEmoji !== undefined) {
|
||||
notificationMessage = RichTextProcessor.wrapPlainText(stickerEmoji) + ' ' + notificationMessage;
|
||||
}
|
||||
break;
|
||||
case 'video':
|
||||
notificationMessage = _('conversation_media_video_raw');
|
||||
captionEmoji = '📹';
|
||||
break;
|
||||
case 'round':
|
||||
notificationMessage = _('conversation_media_round_raw');
|
||||
captionEmoji = '📹';
|
||||
break;
|
||||
case 'voice':
|
||||
case 'audio':
|
||||
notificationMessage = _('conversation_media_audio_raw');
|
||||
break;
|
||||
default:
|
||||
if(message.media.document.file_name) {
|
||||
notificationMessage = RichTextProcessor.wrapPlainText('📎 ' + message.media.document.file_name);
|
||||
} else {
|
||||
notificationMessage = _('conversation_media_document_raw');
|
||||
captionEmoji = '📎';
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'messageMediaGeo':
|
||||
case 'messageMediaVenue':
|
||||
notificationMessage = _('conversation_media_location_raw');
|
||||
captionEmoji = '📍';
|
||||
break;
|
||||
case 'messageMediaContact':
|
||||
notificationMessage = _('conversation_media_contact_raw');
|
||||
break;
|
||||
case 'messageMediaGame':
|
||||
notificationMessage = RichTextProcessor.wrapPlainText('🎮 ' + message.media.game.title);
|
||||
break;
|
||||
case 'messageMediaUnsupported':
|
||||
notificationMessage = _('conversation_media_unsupported_raw');
|
||||
break;
|
||||
default:
|
||||
notificationMessage = _('conversation_media_attachment_raw');
|
||||
break;
|
||||
}
|
||||
|
||||
if(captionEmoji !== undefined && (message.media as any).caption) {
|
||||
notificationMessage = RichTextProcessor.wrapPlainText(captionEmoji + ' ' + (message.media as any).caption);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch(message.action._) {
|
||||
case 'messageActionChatCreate':
|
||||
notificationMessage = _('conversation_group_created_raw')
|
||||
break
|
||||
case 'messageActionChatEditTitle':
|
||||
notificationMessage = _('conversation_group_renamed_raw')
|
||||
break
|
||||
case 'messageActionChatEditPhoto':
|
||||
notificationMessage = _('conversation_group_photo_updated_raw')
|
||||
break
|
||||
case 'messageActionChatDeletePhoto':
|
||||
notificationMessage = _('conversation_group_photo_removed_raw')
|
||||
break
|
||||
case 'messageActionChatAddUser':
|
||||
// @ts-ignore
|
||||
case 'messageActionChatAddUsers':
|
||||
notificationMessage = _('conversation_invited_user_message_raw')
|
||||
break
|
||||
/* case 'messageActionChatReturn':
|
||||
notificationMessage = _('conversation_returned_to_group_raw')
|
||||
break
|
||||
case 'messageActionChatJoined':
|
||||
notificationMessage = _('conversation_joined_group_raw')
|
||||
break */
|
||||
case 'messageActionChatDeleteUser':
|
||||
notificationMessage = _('conversation_kicked_user_message_raw')
|
||||
break
|
||||
/* case 'messageActionChatLeave':
|
||||
notificationMessage = _('conversation_left_group_raw')
|
||||
break */
|
||||
case 'messageActionChatJoinedByLink':
|
||||
notificationMessage = _('conversation_joined_by_link_raw')
|
||||
break
|
||||
case 'messageActionChannelCreate':
|
||||
notificationMessage = _('conversation_created_channel_raw')
|
||||
break
|
||||
/* case 'messageActionChannelEditTitle':
|
||||
notificationMessage = _('conversation_changed_channel_name_raw')
|
||||
break
|
||||
case 'messageActionChannelEditPhoto':
|
||||
notificationMessage = _('conversation_changed_channel_photo_raw')
|
||||
break
|
||||
case 'messageActionChannelDeletePhoto':
|
||||
notificationMessage = _('conversation_removed_channel_photo_raw')
|
||||
break */
|
||||
case 'messageActionPinMessage':
|
||||
notificationMessage = _('conversation_pinned_message_raw')
|
||||
break
|
||||
/* case 'messageActionGameScore':
|
||||
notificationMessage = gameScorePluralize(message.action.score)
|
||||
break */
|
||||
|
||||
case 'messageActionPhoneCall':
|
||||
switch((message.action as any).type) {
|
||||
case 'out_missed':
|
||||
notificationMessage = _('message_service_phonecall_canceled_raw')
|
||||
break
|
||||
case 'in_missed':
|
||||
notificationMessage = _('message_service_phonecall_missed_raw')
|
||||
break
|
||||
case 'out_ok':
|
||||
notificationMessage = _('message_service_phonecall_outgoing_raw')
|
||||
break
|
||||
case 'in_ok':
|
||||
notificationMessage = _('message_service_phonecall_incoming_raw')
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
notificationMessage = 'New notification';
|
||||
}
|
||||
|
||||
if(peerId > 0) {
|
||||
const fromUser = appUsersManager.getUser(message.fromId);
|
||||
const fromPhoto = appUsersManager.getUserPhoto(message.fromId);
|
||||
|
||||
notification.title = (fromUser.first_name || '') +
|
||||
(fromUser.first_name && fromUser.last_name ? ' ' : '') +
|
||||
(fromUser.last_name || '');
|
||||
if(!notification.title) {
|
||||
notification.title = fromUser.phone || _('conversation_unknown_user_raw');
|
||||
}
|
||||
|
||||
notificationPhoto = fromPhoto;
|
||||
|
||||
peerString = appUsersManager.getUserString(peerId);
|
||||
} else {
|
||||
notification.title = appChatsManager.getChat(-peerId).title || _('conversation_unknown_chat_raw');
|
||||
|
||||
if(message.fromId) {
|
||||
var fromUser = appUsersManager.getUser(message.fromId);
|
||||
notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) +
|
||||
' @ ' +
|
||||
notification.title;
|
||||
}
|
||||
|
||||
notificationPhoto = appChatsManager.getChatPhoto(-peerId);
|
||||
|
||||
peerString = appChatsManager.getChatString(-peerId);
|
||||
}
|
||||
|
||||
notification.title = RichTextProcessor.wrapPlainText(notification.title);
|
||||
|
||||
notification.onclick = () => {
|
||||
/* rootScope.broadcast('history_focus', {
|
||||
peerString: peerString,
|
||||
messageID: message.flags & 16 ? message.mid : 0
|
||||
}); */
|
||||
};
|
||||
|
||||
notification.message = notificationMessage;
|
||||
notification.key = 'msg' + message.mid;
|
||||
notification.tag = peerString;
|
||||
notification.silent = true;//message.pFlags.silent || false;
|
||||
|
||||
/* if(notificationPhoto.location && !notificationPhoto.location.empty) {
|
||||
apiManager.downloadSmallFile(notificationPhoto.location, notificationPhoto.size).then(function (blob) {
|
||||
if (message.pFlags.unread) {
|
||||
notification.image = blob
|
||||
NotificationsManager.notify(notification)
|
||||
}
|
||||
})
|
||||
} else { */
|
||||
appNotificationsManager.notify(notification);
|
||||
//}
|
||||
}
|
||||
|
||||
public getScheduledMessagesStorage(peerId: number) {
|
||||
return this.scheduledMessagesStorage[peerId] ?? (this.scheduledMessagesStorage[peerId] = this.createMessageStorage());
|
||||
}
|
||||
|
@ -4915,6 +5215,7 @@ export class AppMessagesManager {
|
|||
|
||||
if(!message.pFlags.out && !message.pFlags.is_outgoing && message.pFlags.unread) {
|
||||
history.unread++;
|
||||
appNotificationsManager.cancel('msg' + mid);
|
||||
}
|
||||
history.count++;
|
||||
history.msgs[mid] = true;
|
||||
|
|
|
@ -0,0 +1,675 @@
|
|||
import { fontFamily } from "../../components/middleEllipsis";
|
||||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
|
||||
import { tsNow } from "../../helpers/date";
|
||||
import { copy, deepEqual } from "../../helpers/object";
|
||||
import { convertInputKeyToKey } from "../../helpers/string";
|
||||
import { isMobile } from "../../helpers/userAgent";
|
||||
import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySettings, Update } from "../../layer";
|
||||
import Config from "../config";
|
||||
import apiManager from "../mtproto/mtprotoworker";
|
||||
import rootScope from "../rootScope";
|
||||
import sessionStorage from "../sessionStorage";
|
||||
import apiUpdatesManager from "./apiUpdatesManager";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
import appPeersManager from "./appPeersManager";
|
||||
import appStateManager from "./appStateManager";
|
||||
import appUsersManager from "./appUsersManager";
|
||||
|
||||
type MyNotification = Notification & {
|
||||
hidden?: boolean,
|
||||
show?: () => void,
|
||||
};
|
||||
|
||||
export type NotifyOptions = Partial<{
|
||||
tag: string;
|
||||
image: string;
|
||||
key: string;
|
||||
title: string;
|
||||
message: string;
|
||||
silent: boolean;
|
||||
onclick: () => void;
|
||||
}>;
|
||||
|
||||
export class AppNotificationsManager {
|
||||
private notificationsUiSupport: boolean;
|
||||
private notificationsShown: {[key: string]: MyNotification} = {};
|
||||
private notificationIndex = 0;
|
||||
private notificationsCount = 0;
|
||||
private soundsPlayed: {[tag: string]: number} = {};
|
||||
private vibrateSupport = !!navigator.vibrate;
|
||||
private nextSoundAt: number;
|
||||
private prevSoundVolume: number;
|
||||
private peerSettings = {
|
||||
notifyPeer: {} as {[peerId: number]: Promise<PeerNotifySettings>},
|
||||
notifyUsers: null as Promise<PeerNotifySettings>,
|
||||
notifyChats: null as Promise<PeerNotifySettings>,
|
||||
notifyBroadcasts: null as Promise<PeerNotifySettings>
|
||||
};
|
||||
private exceptions: {[peerId: string]: PeerNotifySettings} = {};
|
||||
private notifyContactsSignUp: Promise<boolean>;
|
||||
private faviconEl: HTMLLinkElement = document.head.querySelector('link[rel="icon"]');
|
||||
private langNotificationsPluralize = 'notifications';//_.pluralize('page_title_pluralize_notifications');
|
||||
|
||||
private titleBackup = document.title;
|
||||
private titleChanged = false;
|
||||
private titleInterval: number;
|
||||
private prevFavicon: string;
|
||||
private stopped = false;
|
||||
|
||||
private settings: Partial<{
|
||||
nodesktop: boolean,
|
||||
volume: number,
|
||||
novibrate: boolean,
|
||||
nopreview: boolean,
|
||||
nopush: boolean,
|
||||
nosound: boolean,
|
||||
}> = {};
|
||||
|
||||
private registeredDevice: any;
|
||||
private pushInited = false;
|
||||
|
||||
private topMessagesDeferred: CancellablePromise<void>;
|
||||
|
||||
private notifySoundEl: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
// @ts-ignore
|
||||
navigator.vibrate = navigator.vibrate || navigator.mozVibrate || navigator.webkitVibrate;
|
||||
|
||||
this.notificationsUiSupport = ('Notification' in window) || ('mozNotification' in navigator);
|
||||
|
||||
this.topMessagesDeferred = deferredPromise<void>();
|
||||
|
||||
this.notifySoundEl = document.createElement('div');
|
||||
this.notifySoundEl.id = 'notify-sound';
|
||||
document.body.append(this.notifySoundEl);
|
||||
|
||||
/* rootScope.on('idle.deactivated', (newVal) => {
|
||||
if(newVal) {
|
||||
stop();
|
||||
}
|
||||
});*/
|
||||
|
||||
rootScope.on('idle', (newVal) => {
|
||||
if(this.stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!newVal) {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
this.toggleToggler();
|
||||
});
|
||||
|
||||
rootScope.on('apiUpdate', (update) => {
|
||||
// console.log('on apiUpdate', update)
|
||||
switch(update._) {
|
||||
case 'updateNotifySettings': {
|
||||
this.savePeerSettings(update.peer._ === 'notifyPeer' ? appPeersManager.getPeerId(update.peer.peer) : update.peer._, update.notify_settings);
|
||||
rootScope.broadcast('notify_settings', update);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* rootScope.on('push_init', (tokenData) => {
|
||||
this.pushInited = true
|
||||
if(!this.settings.nodesktop && !this.settings.nopush) {
|
||||
if(tokenData) {
|
||||
this.registerDevice(tokenData);
|
||||
} else {
|
||||
WebPushApiManager.subscribe();
|
||||
}
|
||||
} else {
|
||||
this.unregisterDevice(tokenData);
|
||||
}
|
||||
});
|
||||
rootScope.on('push_subscribe', (tokenData) => {
|
||||
this.registerDevice(tokenData);
|
||||
});
|
||||
rootScope.on('push_unsubscribe', (tokenData) => {
|
||||
this.unregisterDevice(tokenData);
|
||||
}); */
|
||||
|
||||
rootScope.addListener('dialogs_multiupdate', () => {
|
||||
//unregisterTopMsgs()
|
||||
this.topMessagesDeferred.resolve();
|
||||
}, true);
|
||||
|
||||
/* rootScope.on('push_notification_click', (notificationData) => {
|
||||
if(notificationData.action === 'push_settings') {
|
||||
this.topMessagesDeferred.then(() => {
|
||||
$modal.open({
|
||||
templateUrl: templateUrl('settings_modal'),
|
||||
controller: 'SettingsModalController',
|
||||
windowClass: 'settings_modal_window mobile_modal',
|
||||
backdrop: 'single'
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if(notificationData.action === 'mute1d') {
|
||||
apiManager.invokeApi('account.updateDeviceLocked', {
|
||||
period: 86400
|
||||
}).then(() => {
|
||||
// var toastData = toaster.pop({
|
||||
// type: 'info',
|
||||
// body: _('push_action_mute1d_success'),
|
||||
// bodyOutputType: 'trustedHtml',
|
||||
// clickHandler: () => {
|
||||
// toaster.clear(toastData)
|
||||
// },
|
||||
// showCloseButton: false
|
||||
// })
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = notificationData.custom && notificationData.custom.peerId;
|
||||
console.log('click', notificationData, peerId);
|
||||
if(peerId) {
|
||||
this.topMessagesDeferred.then(() => {
|
||||
if(notificationData.custom.channel_id &&
|
||||
!appChatsManager.hasChat(notificationData.custom.channel_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(peerId > 0 && !appUsersManager.hasUser(peerId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// rootScope.broadcast('history_focus', {
|
||||
// peerString: appPeersManager.getPeerString(peerId)
|
||||
// });
|
||||
});
|
||||
}
|
||||
}); */
|
||||
}
|
||||
|
||||
private toggleToggler(enable = rootScope.idle.isIDLE) {
|
||||
if(isMobile) return;
|
||||
|
||||
const resetTitle = () => {
|
||||
this.titleChanged = false;
|
||||
document.title = this.titleBackup;
|
||||
this.setFavicon();
|
||||
};
|
||||
|
||||
window.clearInterval(this.titleInterval);
|
||||
this.titleInterval = 0;
|
||||
|
||||
if(!enable) {
|
||||
resetTitle();
|
||||
} else {
|
||||
this.titleInterval = window.setInterval(() => {
|
||||
if(!this.notificationsCount) {
|
||||
this.toggleToggler(false);
|
||||
} else if(this.titleChanged) {
|
||||
resetTitle();
|
||||
} else {
|
||||
this.titleChanged = true;
|
||||
document.title = this.notificationsCount + ' ' + this.langNotificationsPluralize;
|
||||
//this.setFavicon('assets/img/favicon_unread.ico');
|
||||
|
||||
// fetch('assets/img/favicon.ico')
|
||||
// .then(res => res.blob())
|
||||
// .then(blob => {
|
||||
// const img = document.createElement('img');
|
||||
// img.src = URL.createObjectURL(blob);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 32 * window.devicePixelRatio;
|
||||
canvas.height = canvas.width;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvas.width / 2, canvas.height / 2, canvas.width / 2, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = '#5b8af1';
|
||||
ctx.fill();
|
||||
|
||||
let fontSize = 24;
|
||||
let str = '' + this.notificationsCount;
|
||||
if(this.notificationsCount < 10) {
|
||||
fontSize = 22;
|
||||
} else if(this.notificationsCount < 100) {
|
||||
fontSize = 20;
|
||||
} else {
|
||||
str = '99+';
|
||||
fontSize = 18;
|
||||
}
|
||||
|
||||
ctx.font = `700 ${fontSize}px ${fontFamily}`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillText('' + this.notificationsCount, canvas.width / 2, canvas.height * .5625);
|
||||
|
||||
/* const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); */
|
||||
|
||||
this.setFavicon(canvas.toDataURL());
|
||||
// });
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public updateLocalSettings() {
|
||||
Promise.all(['notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush'].map(k => sessionStorage.get(k as any)))
|
||||
.then((updSettings) => {
|
||||
this.settings.nodesktop = updSettings[0];
|
||||
this.settings.volume = updSettings[1] === undefined ? 0.5 : updSettings[1];
|
||||
this.settings.novibrate = updSettings[2];
|
||||
this.settings.nopreview = updSettings[3];
|
||||
this.settings.nopush = updSettings[4];
|
||||
|
||||
/* if(this.pushInited) {
|
||||
const needPush = !this.settings.nopush && !this.settings.nodesktop && WebPushApiManager.isAvailable || false;
|
||||
const hasPush = this.registeredDevice !== false;
|
||||
if(needPush !== hasPush) {
|
||||
if(needPush) {
|
||||
WebPushApiManager.subscribe();
|
||||
} else {
|
||||
WebPushApiManager.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebPushApiManager.setSettings(this.settings); */
|
||||
});
|
||||
|
||||
appStateManager.getState().then(state => {
|
||||
this.settings.nosound = !state.settings.notifications.sound;
|
||||
});
|
||||
}
|
||||
|
||||
public getLocalSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
public getNotifySettings(peer: InputNotifyPeer): Promise<PeerNotifySettings> {
|
||||
let key: any = convertInputKeyToKey(peer._);
|
||||
let obj: any = this.peerSettings[key as NotifyPeer['_']];
|
||||
|
||||
if(peer._ === 'inputNotifyPeer') {
|
||||
key = appPeersManager.getPeerId(peer.peer);
|
||||
obj = obj[key];
|
||||
}
|
||||
|
||||
if(obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
return (obj || this.peerSettings)[key] = apiManager.invokeApi('account.getNotifySettings', {peer})/* .then(settings => {
|
||||
return settings;
|
||||
}) */;
|
||||
}
|
||||
|
||||
public updateNotifySettings(peer: InputNotifyPeer, settings: InputPeerNotifySettings) {
|
||||
//this.savePeerSettings(peerId, settings);
|
||||
|
||||
/* const inputSettings: InputPeerNotifySettings = copy(settings) as any;
|
||||
inputSettings._ = 'inputPeerNotifySettings'; */
|
||||
|
||||
return apiManager.invokeApi('account.updateNotifySettings', {
|
||||
peer,
|
||||
settings
|
||||
}).then(value => {
|
||||
if(value) {
|
||||
apiUpdatesManager.processUpdateMessage({
|
||||
_: 'updateShort',
|
||||
update: {
|
||||
_: 'updateNotifySettings',
|
||||
peer: {
|
||||
...peer,
|
||||
_: convertInputKeyToKey(peer._)
|
||||
},
|
||||
notify_settings: { // ! WOW, IT WORKS !
|
||||
...settings,
|
||||
_: 'peerNotifySettings',
|
||||
}
|
||||
} as Update.updateNotifySettings
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getNotifyExceptions() {
|
||||
apiManager.invokeApi('account.getNotifyExceptions', {compare_sound: true})
|
||||
.then((updates) => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
}
|
||||
|
||||
public getContactSignUpNotification() {
|
||||
if(this.notifyContactsSignUp) return this.notifyContactsSignUp;
|
||||
return this.notifyContactsSignUp = apiManager.invokeApi('account.getContactSignUpNotification');
|
||||
}
|
||||
|
||||
public setContactSignUpNotification(silent: boolean) {
|
||||
apiManager.invokeApi('account.setContactSignUpNotification', {silent})
|
||||
.then(value => {
|
||||
this.notifyContactsSignUp = Promise.resolve(!silent);
|
||||
});
|
||||
}
|
||||
|
||||
private setFavicon(href: string = 'assets/img/favicon.ico') {
|
||||
if(this.prevFavicon === href) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = this.faviconEl.cloneNode() as HTMLLinkElement;
|
||||
link.href = href;
|
||||
this.faviconEl.parentNode.replaceChild(link, this.faviconEl);
|
||||
this.faviconEl = link;
|
||||
|
||||
this.prevFavicon = href;
|
||||
}
|
||||
|
||||
public savePeerSettings(key: number | NotifyPeer['_'], settings: PeerNotifySettings) {
|
||||
const p = Promise.resolve(settings);
|
||||
let obj: any;
|
||||
if(typeof(key) === 'number') {
|
||||
obj = this.peerSettings['notifyPeer'];
|
||||
}
|
||||
|
||||
(obj || this.peerSettings)[key] = p;
|
||||
|
||||
//rootScope.broadcast('notify_settings', {peerId: peerId});
|
||||
}
|
||||
|
||||
public isMuted(peerNotifySettings: PeerNotifySettings) {
|
||||
return peerNotifySettings._ === 'peerNotifySettings' &&
|
||||
(peerNotifySettings.mute_until * 1000) > tsNow();
|
||||
}
|
||||
|
||||
public getPeerMuted(peerId: number) {
|
||||
return this.getNotifySettings({_: 'inputNotifyPeer', peer: appPeersManager.getInputPeerById(peerId)})
|
||||
.then((peerNotifySettings) => this.isMuted(peerNotifySettings));
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.updateLocalSettings();
|
||||
//rootScope.on('settings_changed', this.updateNotifySettings);
|
||||
//WebPushApiManager.start();
|
||||
|
||||
if(!this.notificationsUiSupport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if('Notification' in window && Notification.permission !== 'granted' && Notification.permission !== 'denied') {
|
||||
window.addEventListener('click', this.requestPermission);
|
||||
}
|
||||
|
||||
try {
|
||||
if('onbeforeunload' in window) {
|
||||
window.addEventListener('beforeunload', this.clear);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
private stop() {
|
||||
this.clear();
|
||||
window.clearInterval(this.titleInterval);
|
||||
this.titleInterval = 0;
|
||||
this.setFavicon();
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
private requestPermission = () => {
|
||||
Notification.requestPermission();
|
||||
window.removeEventListener('click', this.requestPermission);
|
||||
};
|
||||
|
||||
public notify(data: NotifyOptions) {
|
||||
console.log('notify', data, rootScope.idle.isIDLE, this.notificationsUiSupport, this.stopped);
|
||||
if(this.stopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FFOS Notification blob src bug workaround
|
||||
/* if(Config.Navigator.ffos && !Config.Navigator.ffos2p) {
|
||||
data.image = 'https://telegram.org/img/t_logo.png'
|
||||
}
|
||||
else if (data.image && !angular.isString(data.image)) {
|
||||
if (Config.Navigator.ffos2p) {
|
||||
FileManager.getDataUrl(data.image, 'image/jpeg').then(function (url) {
|
||||
data.image = url
|
||||
notify(data)
|
||||
})
|
||||
return false
|
||||
} else {
|
||||
data.image = FileManager.getUrl(data.image, 'image/jpeg')
|
||||
}
|
||||
}
|
||||
else if (!data.image) */ {
|
||||
data.image = 'assets/img/logo.svg';
|
||||
}
|
||||
// console.log('notify image', data.image)
|
||||
|
||||
this.notificationsCount++;
|
||||
if(!this.titleInterval) {
|
||||
this.toggleToggler();
|
||||
}
|
||||
|
||||
const now = tsNow();
|
||||
if(this.settings.volume > 0 && !this.settings.nosound/* &&
|
||||
(
|
||||
!data.tag ||
|
||||
!this.soundsPlayed[data.tag] ||
|
||||
now > this.soundsPlayed[data.tag] + 60000
|
||||
) */
|
||||
) {
|
||||
this.testSound(this.settings.volume);
|
||||
this.soundsPlayed[data.tag] = now;
|
||||
}
|
||||
|
||||
if(!this.notificationsUiSupport ||
|
||||
'Notification' in window && Notification.permission !== 'granted') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.settings.nodesktop) {
|
||||
if(this.vibrateSupport && !this.settings.novibrate) {
|
||||
navigator.vibrate([200, 100, 200]);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = ++this.notificationIndex;
|
||||
const key = data.key || 'k' + idx;
|
||||
let notification: MyNotification;
|
||||
|
||||
if('Notification' in window) {
|
||||
try {
|
||||
if(data.tag) {
|
||||
for(let i in this.notificationsShown) {
|
||||
const notification = this.notificationsShown[i];
|
||||
if(notification &&
|
||||
notification.tag === data.tag) {
|
||||
notification.hidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification = new Notification(data.title, {
|
||||
icon: data.image || '',
|
||||
body: data.message || '',
|
||||
tag: data.tag || '',
|
||||
silent: data.silent || false
|
||||
});
|
||||
} catch(e) {
|
||||
this.notificationsUiSupport = false;
|
||||
//WebPushApiManager.setLocalNotificationsDisabled();
|
||||
return
|
||||
}
|
||||
} /* else if('mozNotification' in navigator) {
|
||||
notification = navigator.mozNotification.createNotification(data.title, data.message || '', data.image || '')
|
||||
} else if(notificationsMsSiteMode) {
|
||||
window.external.msSiteModeClearIconOverlay()
|
||||
window.external.msSiteModeSetIconOverlay('img/icons/icon16.png', data.title)
|
||||
window.external.msSiteModeActivate()
|
||||
notification = {
|
||||
index: idx
|
||||
}
|
||||
} */ else {
|
||||
return;
|
||||
}
|
||||
|
||||
notification.onclick = () => {
|
||||
notification.close();
|
||||
//AppRuntimeManager.focus();
|
||||
this.clear();
|
||||
if(data.onclick) {
|
||||
data.onclick();
|
||||
}
|
||||
};
|
||||
|
||||
notification.onclose = () => {
|
||||
if(!notification.hidden) {
|
||||
delete this.notificationsShown[key];
|
||||
this.clear();
|
||||
}
|
||||
};
|
||||
|
||||
if(notification.show) {
|
||||
notification.show();
|
||||
}
|
||||
this.notificationsShown[key] = notification;
|
||||
|
||||
if(!isMobile) {
|
||||
setTimeout(() => {
|
||||
this.hide(key);
|
||||
}, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
public testSound(volume: number) {
|
||||
const now = tsNow();
|
||||
if(this.nextSoundAt && now < this.nextSoundAt && this.prevSoundVolume === volume) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextSoundAt = now + 1000;
|
||||
this.prevSoundVolume = volume;
|
||||
const filename = 'assets/audio/notification.mp3';
|
||||
const audio = document.createElement('audio');
|
||||
audio.autoplay = true;
|
||||
audio.setAttribute('mozaudiochannel', 'notification');
|
||||
audio.volume = volume;
|
||||
audio.innerHTML = `
|
||||
<source src="${filename}" type="audio/mpeg" />
|
||||
<embed hidden="true" autostart="true" loop="false" volume="${volume * 100}" src="${filename}" />
|
||||
`;
|
||||
this.notifySoundEl.append(audio);
|
||||
|
||||
audio.addEventListener('ended', () => {
|
||||
audio.remove();
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
public cancel(key: string) {
|
||||
const notification = this.notificationsShown[key];
|
||||
if(notification) {
|
||||
if(this.notificationsCount > 0) {
|
||||
this.notificationsCount--;
|
||||
}
|
||||
|
||||
try {
|
||||
if(notification.close) {
|
||||
notification.hidden = true;
|
||||
notification.close();
|
||||
}/* else if(notificationsMsSiteMode &&
|
||||
notification.index === notificationIndex) {
|
||||
window.external.msSiteModeClearIconOverlay()
|
||||
} */
|
||||
} catch (e) {}
|
||||
|
||||
delete this.notificationsShown[key];
|
||||
}
|
||||
}
|
||||
|
||||
private hide(key: string) {
|
||||
const notification = this.notificationsShown[key];
|
||||
if(notification) {
|
||||
try {
|
||||
if(notification.close) {
|
||||
notification.hidden = true;
|
||||
notification.close();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
public soundReset(tag: string) {
|
||||
delete this.soundsPlayed[tag];
|
||||
}
|
||||
|
||||
public clear() {
|
||||
/* if(notificationsMsSiteMode) {
|
||||
window.external.msSiteModeClearIconOverlay()
|
||||
} else { */
|
||||
for(let i in this.notificationsShown) {
|
||||
const notification = this.notificationsShown[i];
|
||||
try {
|
||||
if(notification.close) {
|
||||
notification.close();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
/* } */
|
||||
this.notificationsShown = {};
|
||||
this.notificationsCount = 0;
|
||||
|
||||
//WebPushApiManager.hidePushNotifications();
|
||||
}
|
||||
|
||||
private registerDevice(tokenData: any) {
|
||||
if(this.registeredDevice &&
|
||||
deepEqual(this.registeredDevice, tokenData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
apiManager.invokeApi('account.registerDevice', {
|
||||
token_type: tokenData.tokenType,
|
||||
token: tokenData.tokenValue,
|
||||
other_uids: [],
|
||||
app_sandbox: false,
|
||||
secret: new Uint8Array()
|
||||
}).then(() => {
|
||||
this.registeredDevice = tokenData;
|
||||
}, (error) => {
|
||||
error.handled = true;
|
||||
})
|
||||
}
|
||||
|
||||
private unregisterDevice(tokenData: any) {
|
||||
if(!this.registeredDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
apiManager.invokeApi('account.unregisterDevice', {
|
||||
token_type: tokenData.tokenType,
|
||||
token: tokenData.tokenValue,
|
||||
other_uids: []
|
||||
}).then(() => {
|
||||
this.registeredDevice = false
|
||||
}, (error) => {
|
||||
error.handled = true
|
||||
})
|
||||
}
|
||||
|
||||
public getVibrateSupport() {
|
||||
return this.vibrateSupport
|
||||
}
|
||||
}
|
||||
|
||||
const appNotificationsManager = new AppNotificationsManager();
|
||||
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appNotificationsManager = appNotificationsManager);
|
||||
export default appNotificationsManager;
|
|
@ -1,6 +1,6 @@
|
|||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||
import { isObject } from "../../helpers/object";
|
||||
import { DialogPeer, InputDialogPeer, InputPeer, Peer, Update } from "../../layer";
|
||||
import { DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update } from "../../layer";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import rootScope from "../rootScope";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
|
@ -105,12 +105,12 @@ export class AppPeersManager {
|
|||
return {_: 'peerChat', chat_id: chatId};
|
||||
}
|
||||
|
||||
/* public getPeerString(peerId: number) {
|
||||
public getPeerString(peerId: number) {
|
||||
if(peerId > 0) {
|
||||
return appUsersManager.getUserString(peerId);
|
||||
}
|
||||
return appChatsManager.getChatString(-peerId);
|
||||
} */
|
||||
}
|
||||
|
||||
public getPeerUsername(peerId: number): string {
|
||||
if(peerId > 0) {
|
||||
|
@ -163,6 +163,13 @@ export class AppPeersManager {
|
|||
return (peerId > 0) && appUsersManager.isBot(peerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check notification exceptions by type too
|
||||
*/
|
||||
public isPeerMuted(peerId: number) {
|
||||
|
||||
}
|
||||
|
||||
/* public getInputPeer(peerString: string): InputPeer {
|
||||
var firstChar = peerString.charAt(0);
|
||||
var peerParams = peerString.substr(1).split('_');
|
||||
|
@ -195,6 +202,25 @@ export class AppPeersManager {
|
|||
}
|
||||
} */
|
||||
|
||||
public getInputNotifyPeerById(peerId: number, ignorePeerId = false): InputNotifyPeer {
|
||||
if(ignorePeerId) {
|
||||
if(peerId > 0) {
|
||||
return {_: 'inputNotifyUsers'};
|
||||
} else {
|
||||
if(appPeersManager.isBroadcast(peerId)) {
|
||||
return {_: 'inputNotifyBroadcasts'};
|
||||
} else {
|
||||
return {_: 'inputNotifyChats'};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
_: 'inputNotifyPeer',
|
||||
peer: this.getInputPeerById(peerId)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public getInputPeerById(peerId: number): InputPeer {
|
||||
if(!peerId) {
|
||||
return {_: 'inputPeerEmpty'};
|
||||
|
|
|
@ -5,6 +5,7 @@ import appChatsManager from "./appChatsManager";
|
|||
import appUsersManager from "./appUsersManager";
|
||||
import apiUpdatesManager from "./apiUpdatesManager";
|
||||
import rootScope from "../rootScope";
|
||||
import { convertInputKeyToKey } from "../../helpers/string";
|
||||
|
||||
export enum PrivacyType {
|
||||
Everybody = 2,
|
||||
|
@ -31,11 +32,6 @@ export class AppPrivacyManager {
|
|||
});
|
||||
}
|
||||
|
||||
public convertInputKeyToKey(inputKey: string) {
|
||||
let str = inputKey.replace('input', '');
|
||||
return (str[0].toLowerCase() + str.slice(1)) as string;
|
||||
}
|
||||
|
||||
public setPrivacy(inputKey: InputPrivacyKey['_'], rules: InputPrivacyRule[]) {
|
||||
return apiManager.invokeApi('account.setPrivacy', {
|
||||
key: {
|
||||
|
@ -51,12 +47,12 @@ export class AppPrivacyManager {
|
|||
update: {
|
||||
_: 'updatePrivacy',
|
||||
key: {
|
||||
_: this.convertInputKeyToKey(inputKey)
|
||||
_: convertInputKeyToKey(inputKey)
|
||||
},
|
||||
rules: rules.map(inputRule => {
|
||||
const rule: PrivacyRule = {} as any;
|
||||
Object.assign(rule, inputRule);
|
||||
rule._ = this.convertInputKeyToKey(rule._) as any;
|
||||
rule._ = convertInputKeyToKey(rule._) as any;
|
||||
return rule;
|
||||
})
|
||||
} as Update.updatePrivacy
|
||||
|
@ -69,7 +65,7 @@ export class AppPrivacyManager {
|
|||
}
|
||||
|
||||
public getPrivacy(inputKey: InputPrivacyKey['_']) {
|
||||
const privacyKey: PrivacyKey['_'] = this.convertInputKeyToKey(inputKey) as any;
|
||||
const privacyKey: PrivacyKey['_'] = convertInputKeyToKey(inputKey) as any;
|
||||
const rules = this.privacy[privacyKey];
|
||||
if(rules) {
|
||||
return Promise.resolve(rules);
|
||||
|
|
|
@ -63,6 +63,9 @@ export type State = Partial<{
|
|||
highlightningColor?: string,
|
||||
color?: string,
|
||||
slug?: string,
|
||||
},
|
||||
notifications: {
|
||||
sound: boolean
|
||||
}
|
||||
},
|
||||
drafts: AppDraftsManager['drafts']
|
||||
|
@ -109,6 +112,9 @@ export const STATE_INIT: State = {
|
|||
type: 'image',
|
||||
blur: false,
|
||||
slug: 'ByxGo2lrMFAIAAAAmkJxZabh8eM', // * new blurred camomile
|
||||
},
|
||||
notifications: {
|
||||
sound: true
|
||||
}
|
||||
},
|
||||
drafts: {}
|
||||
|
|
|
@ -469,10 +469,10 @@ export class AppUsersManager {
|
|||
return this.cachedPhotoLocations[id];
|
||||
}
|
||||
|
||||
/* public getUserString(id: number) {
|
||||
public getUserString(id: number) {
|
||||
const user = this.getUser(id);
|
||||
return 'u' + id + (user.access_hash ? '_' + user.access_hash : '');
|
||||
} */
|
||||
}
|
||||
|
||||
public getUserInput(id: number): InputUser {
|
||||
const user = this.getUser(id);
|
||||
|
|
|
@ -9,10 +9,7 @@ import {str2bigInt, bpe, equalsInt, greater,
|
|||
copy_, eGCD_, add_, rightShift_, sub_, copyInt_, isZero,
|
||||
divide_, one, bigInt2str, powMod, bigInt2bytes} from '../../vendor/leemon';//from 'leemon';
|
||||
|
||||
// @ts-ignore
|
||||
import {BigInteger} from 'jsbn';
|
||||
|
||||
import { addPadding, bytesFromBigInt } from '../mtproto/bin_utils';
|
||||
import { addPadding } from '../mtproto/bin_utils';
|
||||
import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex } from '../../helpers/bytes';
|
||||
import { nextRandomInt } from '../../helpers/random';
|
||||
|
||||
|
@ -148,21 +145,20 @@ export async function hash_pbkdf2(/* hasher: 'string', */buffer: any, salt: any
|
|||
return bits;
|
||||
}
|
||||
|
||||
export function pqPrimeFactorization(pqBytes: any) {
|
||||
var what = new BigInteger(pqBytes);
|
||||
var result: any = false;
|
||||
export function pqPrimeFactorization(pqBytes: number[]) {
|
||||
let result: ReturnType<typeof pqPrimeLeemon>;
|
||||
|
||||
//console.log(dT(), 'PQ start', pqBytes, what.toString(16), what.bitLength())
|
||||
//console.log('PQ start', pqBytes, bytesToHex(pqBytes));
|
||||
|
||||
try {
|
||||
//console.time('PQ leemon');
|
||||
result = pqPrimeLeemon(str2bigInt(what.toString(16), 16, Math.ceil(64 / bpe) + 1));
|
||||
result = pqPrimeLeemon(str2bigInt(bytesToHex(pqBytes), 16, Math.ceil(64 / bpe) + 1));
|
||||
//console.timeEnd('PQ leemon');
|
||||
} catch (e) {
|
||||
console.error('Pq leemon Exception', e);
|
||||
}
|
||||
|
||||
//console.log(dT(), 'PQ finish');
|
||||
//console.log('PQ finish', result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -253,11 +249,11 @@ export function bytesModPow(x: any, y: any, m: any) {
|
|||
var resBigInt = powMod(xBigInt, yBigInt, mBigInt);
|
||||
|
||||
return bytesFromHex(bigInt2str(resBigInt, 16));
|
||||
} catch (e) {
|
||||
} catch(e) {
|
||||
console.error('mod pow error', e);
|
||||
}
|
||||
|
||||
return bytesFromBigInt(new BigInteger(x).modPow(new BigInteger(y), new BigInteger(m)), 256);
|
||||
//return bytesFromBigInt(new BigInteger(x).modPow(new BigInteger(y), new BigInteger(m)), 256);
|
||||
}
|
||||
|
||||
export function gzipUncompress(bytes: ArrayBuffer, toString: true): string;
|
||||
|
|
|
@ -3,15 +3,12 @@ import dcConfigurator from "./dcConfigurator";
|
|||
import rsaKeysManager from "./rsaKeysManager";
|
||||
import timeManager from "./timeManager";
|
||||
|
||||
// @ts-ignore
|
||||
import { BigInteger } from "jsbn";
|
||||
|
||||
import CryptoWorker from "../crypto/cryptoworker";
|
||||
|
||||
import { logger, LogLevels } from "../logger";
|
||||
import { bytesCmp, bytesToHex, bytesFromHex, bytesXor } from "../../helpers/bytes";
|
||||
import DEBUG from "../../config/debug";
|
||||
//import { bigInt2str, greater, int2bigInt, one, powMod, str2bigInt, sub } from "../../vendor/leemon";
|
||||
import { cmp, int2bigInt, one, pow, str2bigInt, sub } from "../../vendor/leemon";
|
||||
|
||||
/* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse();
|
||||
let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse();
|
||||
|
@ -401,21 +398,21 @@ export class Authorizer {
|
|||
this.log('dhPrime cmp OK');
|
||||
}
|
||||
|
||||
var gABigInt = new BigInteger(bytesToHex(gA), 16);
|
||||
//const _gABigInt = str2bigInt(bytesToHex(gA), 16);
|
||||
var dhPrimeBigInt = new BigInteger(dhPrimeHex, 16);
|
||||
//const _dhPrimeBigInt = str2bigInt(dhPrimeHex, 16);
|
||||
//var gABigInt = new BigInteger(bytesToHex(gA), 16);
|
||||
const _gABigInt = str2bigInt(bytesToHex(gA), 16);
|
||||
//var dhPrimeBigInt = new BigInteger(dhPrimeHex, 16);
|
||||
const _dhPrimeBigInt = str2bigInt(dhPrimeHex, 16);
|
||||
|
||||
//this.log('gABigInt.compareTo(BigInteger.ONE) <= 0', gABigInt.compareTo(BigInteger.ONE), BigInteger.ONE.compareTo(BigInteger.ONE), greater(_gABigInt, one));
|
||||
if(gABigInt.compareTo(BigInteger.ONE) <= 0) {
|
||||
//if(!greater(_gABigInt, one)) {
|
||||
//if(gABigInt.compareTo(BigInteger.ONE) <= 0) {
|
||||
if(cmp(_gABigInt, one) <= 0) {
|
||||
throw new Error('[MT] DH params are not verified: gA <= 1');
|
||||
}
|
||||
|
||||
/* this.log('gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0', gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)),
|
||||
greater(gABigInt, sub(_dhPrimeBigInt, one))); */
|
||||
if(gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0) {
|
||||
//if(greater(gABigInt, sub(_dhPrimeBigInt, one))) {
|
||||
//if(gABigInt.compareTo(dhPrimeBigInt.subtract(BigInteger.ONE)) >= 0) {
|
||||
if(cmp(_gABigInt, sub(_dhPrimeBigInt, one)) >= 0) {
|
||||
throw new Error('[MT] DH params are not verified: gA >= dhPrime - 1');
|
||||
}
|
||||
|
||||
|
@ -424,19 +421,25 @@ export class Authorizer {
|
|||
}
|
||||
|
||||
|
||||
var two = new BigInteger(/* null */'');
|
||||
two.fromInt(2);
|
||||
//const _two = int2bigInt(2, 10, 0);
|
||||
//var two = new BigInteger(/* null */'');
|
||||
//two.fromInt(2);
|
||||
const _two = int2bigInt(2, 32, 0);
|
||||
//this.log('_two:', bigInt2str(_two, 16), two.toString(16));
|
||||
var twoPow = two.pow(2048 - 64);
|
||||
//const _twoPow = powMod(_two, int2bigInt(2048 - 64, 10, 0), null);
|
||||
let perf = performance.now();
|
||||
//var twoPow = two.pow(2048 - 64);
|
||||
//console.log('jsbn pow', performance.now() - perf);
|
||||
perf = performance.now();
|
||||
const _twoPow = pow(_two, 2048 - 64);
|
||||
//console.log('leemon pow', performance.now() - perf);
|
||||
//this.log('twoPow:', twoPow.toString(16), bigInt2str(_twoPow, 16));
|
||||
|
||||
// this.log('gABigInt.compareTo(twoPow) < 0');
|
||||
if(gABigInt.compareTo(twoPow) < 0) {
|
||||
// this.log('gABigInt.compareTo(twoPow) < 0');
|
||||
//if(gABigInt.compareTo(twoPow) < 0) {
|
||||
if(cmp(_gABigInt, _twoPow) < 0) {
|
||||
throw new Error('[MT] DH params are not verified: gA < 2^{2048-64}');
|
||||
}
|
||||
if(gABigInt.compareTo(dhPrimeBigInt.subtract(twoPow)) >= 0) {
|
||||
//if(gABigInt.compareTo(dhPrimeBigInt.subtract(twoPow)) >= 0) {
|
||||
if(cmp(_gABigInt, sub(_dhPrimeBigInt, _twoPow)) >= 0) {
|
||||
throw new Error('[MT] DH params are not verified: gA > dhPrime - 2^{2048-64}');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
//import {str2bigInt, divInt_, int2bigInt, bigInt2str, bigInt2bytes} from '../vendor/leemon';
|
||||
|
||||
// @ts-ignore
|
||||
import {BigInteger, SecureRandom} from 'jsbn';
|
||||
import { bufferConcat, bufferConcats } from '../../helpers/bytes';
|
||||
import { bufferConcats } from '../../helpers/bytes';
|
||||
import { add_, bigInt2str, cmp, leftShift_, str2bigInt } from '../../vendor/leemon';
|
||||
import { nextRandomInt } from '../../helpers/random';
|
||||
|
||||
/// #if !MTPROTO_WORKER
|
||||
// @ts-ignore
|
||||
|
@ -22,13 +20,13 @@ export function isObject(object: any) {
|
|||
return typeof(object) === 'object' && object !== null;
|
||||
}
|
||||
|
||||
export function bigint(num: number) {
|
||||
/* export function bigint(num: number) {
|
||||
return new BigInteger(num.toString(16), 16);
|
||||
}
|
||||
} */
|
||||
|
||||
export function bigStringInt(strNum: string) {
|
||||
/* export function bigStringInt(strNum: string) {
|
||||
return new BigInteger(strNum, 10);
|
||||
}
|
||||
} */
|
||||
|
||||
/* export function base64ToBlob(base64str: string, mimeType: string) {
|
||||
var sliceSize = 1024;
|
||||
|
@ -62,7 +60,7 @@ export function dataUrlToBlob(url: string) {
|
|||
return blob;
|
||||
} */
|
||||
|
||||
export function bytesFromBigInt(bigInt: BigInteger, len?: number) {
|
||||
/* export function bytesFromBigInt(bigInt: BigInteger, len?: number) {
|
||||
var bytes = bigInt.toByteArray();
|
||||
|
||||
if(len && bytes.length < len) {
|
||||
|
@ -82,10 +80,36 @@ export function bytesFromBigInt(bigInt: BigInteger, len?: number) {
|
|||
}
|
||||
|
||||
return bytes;
|
||||
} */
|
||||
|
||||
export function longFromInts(high: number, low: number): string {
|
||||
//let perf = performance.now();
|
||||
//let str = bigint(high).shiftLeft(32).add(bigint(low)).toString(10);
|
||||
//console.log('longFromInts jsbn', performance.now() - perf);
|
||||
|
||||
//perf = performance.now();
|
||||
const bigInt = str2bigInt(high.toString(16), 16, 32);//int2bigInt(high, 64, 64);
|
||||
//console.log('longFromInts construct high', bigint(high).toString(10), bigInt2str(bigInt, 10));
|
||||
leftShift_(bigInt, 32);
|
||||
//console.log('longFromInts shiftLeft', bigint(high).shiftLeft(32).toString(10), bigInt2str(bigInt, 10));
|
||||
add_(bigInt, str2bigInt(low.toString(16), 16, 32));
|
||||
const _str = bigInt2str(bigInt, 10);
|
||||
|
||||
//console.log('longFromInts leemon', performance.now() - perf);
|
||||
|
||||
//console.log('longFromInts', high, low, str, _str, str === _str);
|
||||
|
||||
return _str;
|
||||
}
|
||||
|
||||
export function longFromInts(high: number, low: number) {
|
||||
return bigint(high).shiftLeft(32).add(bigint(low)).toString(10);
|
||||
export function sortLongsArray(arr: string[]) {
|
||||
return arr.map(long => {
|
||||
return str2bigInt(long, 10);
|
||||
}).sort((a, b) => {
|
||||
return cmp(a, b);
|
||||
}).map(bigInt => {
|
||||
return bigInt2str(bigInt, 10);
|
||||
});
|
||||
}
|
||||
|
||||
export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, full = false, prepend = false) {
|
||||
|
@ -99,7 +123,9 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
|
|||
padding[i] = 0;
|
||||
}
|
||||
} else {
|
||||
(new SecureRandom()).nextBytes(padding);
|
||||
for(let i = 0; i < padding.length; ++i) {
|
||||
padding[i] = nextRandomInt(255);
|
||||
}
|
||||
}
|
||||
|
||||
if(bytes instanceof ArrayBuffer) {
|
||||
|
@ -112,4 +138,4 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
|
|||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
|
|||
|
||||
private isSWRegistered = true;
|
||||
|
||||
private debug = DEBUG;
|
||||
private debug = DEBUG && false;
|
||||
|
||||
private sockets: Map<number, Socket> = new Map();
|
||||
|
||||
|
@ -140,7 +140,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
|
|||
}
|
||||
|
||||
private onWorkerMessage = (e: MessageEvent) => {
|
||||
this.log('got message from worker:', e.data);
|
||||
//this.log('got message from worker:', e.data);
|
||||
|
||||
const task = e.data;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {isObject} from './bin_utils';
|
||||
import {bigStringInt} from './bin_utils';
|
||||
import {isObject, sortLongsArray} from './bin_utils';
|
||||
import {TLDeserialization, TLSerialization} from './tl_utils';
|
||||
import CryptoWorker from '../crypto/cryptoworker';
|
||||
import sessionStorage from '../sessionStorage';
|
||||
|
@ -21,6 +20,7 @@ import HTTP from './transports/http';
|
|||
/// #endif
|
||||
|
||||
import type TcpObfuscated from './transports/tcpObfuscated';
|
||||
import { bigInt2str, cmp, rightShift_, str2bigInt } from '../../vendor/leemon';
|
||||
|
||||
//console.error('networker included!', new Error().stack);
|
||||
|
||||
|
@ -793,11 +793,16 @@ export default class MTPNetworker {
|
|||
|
||||
const currentTime = Date.now();
|
||||
let messagesByteLen = 0;
|
||||
|
||||
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
|
||||
let hasApiCall = false;
|
||||
let hasHttpWait = false;
|
||||
/// #endif
|
||||
|
||||
let lengthOverflow = false;
|
||||
|
||||
for(const messageId in this.pendingMessages) {
|
||||
const keys = sortLongsArray(Object.keys(this.pendingMessages));
|
||||
for(const messageId of keys) {
|
||||
const value = this.pendingMessages[messageId];
|
||||
|
||||
if(!value || value <= currentTime) {
|
||||
|
@ -808,25 +813,26 @@ export default class MTPNetworker {
|
|||
} */
|
||||
|
||||
const messageByteLength = message.body.length + 32;
|
||||
if(!message.notContentRelated && lengthOverflow) {
|
||||
continue; // maybe break here
|
||||
}
|
||||
|
||||
if(!message.notContentRelated &&
|
||||
messagesByteLen &&
|
||||
messagesByteLen + messageByteLength > 655360) { // 640 Kb
|
||||
if((messagesByteLen + messageByteLength) > 655360) { // 640 Kb
|
||||
this.log.warn('lengthOverflow', message, messages);
|
||||
lengthOverflow = true;
|
||||
continue; // maybe break here
|
||||
|
||||
if(outMessage) { // if it is a first message
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
messages.push(message);
|
||||
messagesByteLen += messageByteLength;
|
||||
|
||||
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
|
||||
if(message.isAPI) {
|
||||
hasApiCall = true;
|
||||
} else if(message.longPoll) {
|
||||
hasHttpWait = true;
|
||||
}
|
||||
/// #endif
|
||||
|
||||
outMessage = message;
|
||||
} else {
|
||||
|
@ -1426,7 +1432,10 @@ export default class MTPNetworker {
|
|||
this.log.error('Bad msg notification', message);
|
||||
|
||||
if(message.error_code === 16 || message.error_code === 17) {
|
||||
const changedOffset = timeManager.applyServerTime(bigStringInt(messageId).shiftRight(32).toString(10));
|
||||
//const changedOffset = timeManager.applyServerTime(bigStringInt(messageId).shiftRight(32).toString(10));
|
||||
const bigInt = str2bigInt(messageId, 10);
|
||||
rightShift_(bigInt, 32);
|
||||
const changedOffset = timeManager.applyServerTime(+bigInt2str(bigInt, 10));
|
||||
if(message.error_code === 17 || changedOffset) {
|
||||
this.log('Update session');
|
||||
this.updateSession();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { TLSerialization } from "./tl_utils";
|
||||
import { bigStringInt } from "./bin_utils";
|
||||
import CryptoWorker from '../crypto/cryptoworker';
|
||||
import { bytesFromArrayBuffer, bytesFromHex, bytesToHex } from "../../helpers/bytes";
|
||||
import { bigInt2str, str2bigInt } from "../../vendor/leemon";
|
||||
|
||||
export class RSAKeysManager {
|
||||
|
||||
|
@ -106,7 +106,7 @@ export class RSAKeysManager {
|
|||
let fingerprintBytes = bytesFromArrayBuffer(hash).slice(-8);
|
||||
fingerprintBytes.reverse();
|
||||
|
||||
this.publicKeysParsed[bytesToHex(fingerprintBytes)] = {
|
||||
this.publicKeysParsed[bytesToHex(fingerprintBytes).toLowerCase()] = {
|
||||
modulus: keyParsed.modulus,
|
||||
exponent: keyParsed.exponent
|
||||
};
|
||||
|
@ -125,7 +125,8 @@ export class RSAKeysManager {
|
|||
|
||||
var fingerprintHex, foundKey, i;
|
||||
for(i = 0; i < fingerprints.length; i++) {
|
||||
fingerprintHex = bigStringInt(fingerprints[i]).toString(16);
|
||||
//fingerprintHex = bigStringInt(fingerprints[i]).toString(16);
|
||||
fingerprintHex = bigInt2str(str2bigInt(fingerprints[i], 10), 16).toLowerCase();
|
||||
|
||||
if(fingerprintHex.length < 16) {
|
||||
fingerprintHex = new Array(16 - fingerprintHex.length).fill('0').join('') + fingerprintHex;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { bytesToHex } from '../../helpers/bytes';
|
||||
import { bigint, bigStringInt, isObject } from './bin_utils';
|
||||
import { isObject, longFromInts } from './bin_utils';
|
||||
import { MOUNT_CLASS_TO } from '../../config/debug';
|
||||
import { str2bigInt, dup, divide_, bigInt2str } from '../../vendor/leemon';
|
||||
import Schema, { MTProtoConstructor } from './schema';
|
||||
|
||||
/// #if MTPROTO_WORKER
|
||||
// @ts-ignore
|
||||
import { gzipUncompress } from '../crypto/crypto_utils';
|
||||
import Schema, { MTProtoConstructor } from './schema';
|
||||
|
||||
/// #endif
|
||||
|
||||
const boolFalse = +Schema.API.constructors.find(c => c.predicate === 'boolFalse').id >>> 0;
|
||||
|
@ -138,10 +139,29 @@ class TLSerialization {
|
|||
if(typeof sLong !== 'string') {
|
||||
sLong = sLong ? sLong.toString() : '0';
|
||||
}
|
||||
const divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000));
|
||||
|
||||
const R = 0x100000000;
|
||||
//const divRem = bigStringInt(sLong).divideAndRemainder(bigint(R));
|
||||
|
||||
const a = str2bigInt(sLong, 10, 64);
|
||||
const q = dup(a);
|
||||
const r = dup(a);
|
||||
divide_(a, str2bigInt((R).toString(16), 16, 64), q, r);
|
||||
//divInt_(a, R);
|
||||
|
||||
const high = +bigInt2str(q, 10);
|
||||
let low = +bigInt2str(r, 10);
|
||||
|
||||
if(high < low) {
|
||||
low -= R;
|
||||
}
|
||||
|
||||
//console.log('storeLong', sLong, divRem[0].intValue(), divRem[1].intValue(), high, low);
|
||||
|
||||
this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]');
|
||||
this.writeInt(divRem[0].intValue(), (field || '') + ':long[high]');
|
||||
//this.writeInt(divRem[1].intValue(), (field || '') + ':long[low]');
|
||||
//this.writeInt(divRem[0].intValue(), (field || '') + ':long[high]');
|
||||
this.writeInt(low, (field || '') + ':long[low]');
|
||||
this.writeInt(high, (field || '') + ':long[high]');
|
||||
}
|
||||
|
||||
public storeDouble(f: any, field?: string) {
|
||||
|
@ -480,7 +500,8 @@ class TLDeserialization {
|
|||
const iLow = this.readInt((field || '') + ':long[low]');
|
||||
const iHigh = this.readInt((field || '') + ':long[high]');
|
||||
|
||||
const longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString();
|
||||
//const longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString();
|
||||
const longDec = longFromInts(iHigh, iLow);
|
||||
|
||||
return longDec;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// @ts-ignore
|
||||
//import {SecureRandom} from 'jsbn';
|
||||
import { bytesToHex, bytesFromHex, bufferConcats } from '../helpers/bytes';
|
||||
import { nextRandomInt } from '../helpers/random';
|
||||
|
||||
|
|
|
@ -89,18 +89,22 @@ type BroadcastEvents = {
|
|||
'im_mount': void,
|
||||
'im_tab_change': number,
|
||||
|
||||
'idle': boolean,
|
||||
|
||||
'overlay_toggle': boolean,
|
||||
|
||||
'background_change': void,
|
||||
|
||||
'privacy_update': Update.updatePrivacy
|
||||
'privacy_update': Update.updatePrivacy,
|
||||
|
||||
'notify_settings': Update.updateNotifySettings
|
||||
};
|
||||
|
||||
class RootScope extends EventListenerBase<any> {
|
||||
private _overlayIsActive: boolean = false;
|
||||
public myId = 0;
|
||||
public idle = {
|
||||
isIDLE: false
|
||||
isIDLE: true
|
||||
};
|
||||
public connectionStatus: {[name: string]: ConnectionStatusChange} = {};
|
||||
public settings: State['settings'];
|
||||
|
|
|
@ -9,16 +9,27 @@ export default class DialogsStorage {
|
|||
public dialogs: {[peerId: string]: Dialog} = {};
|
||||
public byFolders: {[folderId: number]: Dialog[]} = {};
|
||||
|
||||
public allDialogsLoaded: {[folder_id: number]: boolean} = {};
|
||||
public dialogsOffsetDate: {[folder_id: number]: number} = {};
|
||||
public pinnedOrders: {[folder_id: number]: number[]} = {
|
||||
0: [],
|
||||
1: []
|
||||
};
|
||||
public dialogsNum = 0;
|
||||
public allDialogsLoaded: {[folder_id: number]: boolean};
|
||||
private dialogsOffsetDate: {[folder_id: number]: number};
|
||||
public pinnedOrders: {[folder_id: number]: number[]};
|
||||
private dialogsNum: number;
|
||||
|
||||
constructor(private appMessagesManager: AppMessagesManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private serverTimeManager: ServerTimeManager) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.allDialogsLoaded = {};
|
||||
this.dialogsOffsetDate = {};
|
||||
this.pinnedOrders = {
|
||||
0: [],
|
||||
1: []
|
||||
};
|
||||
this.dialogsNum = 0;
|
||||
}
|
||||
|
||||
public getOffsetDate(folderId: number) {
|
||||
return this.dialogsOffsetDate[folderId] || 0;
|
||||
}
|
||||
|
||||
public getFolder(id: number) {
|
||||
|
@ -181,4 +192,4 @@ export default class DialogsStorage {
|
|||
|
||||
return foundDialog;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,4 +259,4 @@ export default class FiltersStorage {
|
|||
filter.orderIndex = this.orderIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import Page from "./page";
|
|||
import pageAuthCode from "./pageAuthCode";
|
||||
import pageSignQR from './pageSignQR';
|
||||
import InputField from "../components/inputField";
|
||||
import CheckboxField from "../components/checkbox";
|
||||
import CheckboxField from "../components/checkboxField";
|
||||
import Button from "../components/button";
|
||||
import { isAppleMobile } from "../helpers/userAgent";
|
||||
import fastSmoothScroll from "../helpers/fastSmoothScroll";
|
||||
|
@ -299,7 +299,7 @@ let onFirstMount = () => {
|
|||
this.removeAttribute('readonly'); // fix autocomplete
|
||||
});*/
|
||||
|
||||
const signedCheckboxField = CheckboxField({
|
||||
const signedCheckboxField = new CheckboxField({
|
||||
text: 'Keep me signed in',
|
||||
name: 'keepSession'
|
||||
});
|
||||
|
|
|
@ -1040,6 +1040,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.notifications-container {
|
||||
.sidebar-left-section {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.range-setting-selector {
|
||||
padding: 1rem .875rem;
|
||||
|
||||
|
|
|
@ -1140,10 +1140,17 @@ middle-ellipsis-element {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.radio-field {
|
||||
&-main {
|
||||
padding-left: 3.375rem;
|
||||
margin-left: -3.375rem;
|
||||
.radio-field-main, .checkbox-field {
|
||||
padding-left: 3.375rem;
|
||||
margin-left: -3.375rem;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
margin-right: 0;
|
||||
height: auto;
|
||||
|
||||
.checkbox-caption {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -494,6 +494,20 @@ export function powMod(x: number[], y: number[], n: number[]): number[] {
|
|||
return trim(ans, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple pow with no optimizations (in 40x times slower than jsbn's pow)
|
||||
* @param x bigInt
|
||||
* @param e
|
||||
*/
|
||||
export function pow(x: number[], e: number) {
|
||||
let ans = dup(x);
|
||||
e -= 1;
|
||||
for(let i = 0; i < e; ++i) {
|
||||
ans = mult(ans, x);
|
||||
}
|
||||
return trim(ans, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* return (x-y) for bigInts x and y
|
||||
*
|
||||
|
@ -1482,6 +1496,11 @@ export function bigInt2str(x: number[], base: number): string {
|
|||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a bigInt into bytes
|
||||
* @param x bigInt
|
||||
* @param littleEndian byte order by default
|
||||
*/
|
||||
export function bigInt2bytes(x: number[], littleEndian = true) {
|
||||
if(s6.length !== x.length) s6 = dup(x);
|
||||
else copy_(s6, x);
|
||||
|
@ -1504,6 +1523,27 @@ export function bigInt2bytes(x: number[], littleEndian = true) {
|
|||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two bigInts and return -1 if x is less, 0 if equals, 1 if greater
|
||||
* @param x bigInt
|
||||
* @param y bigInt
|
||||
*/
|
||||
export function cmp(x: number[], y: number[]) {
|
||||
return greater(x, y) ? 1 : (equals(x, y) ? 0 : -1);
|
||||
}
|
||||
|
||||
/* Object.assign(self, {
|
||||
cmp,
|
||||
str2bigInt,
|
||||
int2bigInt,
|
||||
bigInt2str,
|
||||
one,
|
||||
divide_,
|
||||
divInt_,
|
||||
dup,
|
||||
negative
|
||||
}); */
|
||||
|
||||
/**
|
||||
* Returns a duplicate of bigInt x
|
||||
*
|
||||
|
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 259 KiB |
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 272 KiB After Width: | Height: | Size: 272 KiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 373 KiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |