Browse Source

Reactions management

Config, stickers: fix possible race conditions
master
Eduard Kuzmenko 3 years ago
parent
commit
a0384f5daf
  1. 7
      src/components/radioField.ts
  2. 23
      src/components/row.ts
  3. 23
      src/components/sidebarLeft/tabs/generalSettings.ts
  4. 65
      src/components/sidebarLeft/tabs/quickReaction.ts
  5. 100
      src/components/sidebarRight/tabs/chatReactions.ts
  6. 39
      src/components/sidebarRight/tabs/editChat.ts
  7. 32
      src/components/wrappers.ts
  8. 9
      src/helpers/callbackify.ts
  9. 82
      src/lang.ts
  10. 9
      src/lib/appManagers/appChatsManager.ts
  11. 57
      src/lib/appManagers/appReactionsManager.ts
  12. 8
      src/lib/appManagers/appStickersManager.ts
  13. 8
      src/lib/mtproto/mtprotoworker.ts
  14. 5
      src/lib/rootScope.ts

7
src/components/radioField.ts

@ -18,11 +18,16 @@ export default class RadioField {
langKey?: LangPackKey, langKey?: LangPackKey,
name: string, name: string,
value?: string, value?: string,
stateKey?: string stateKey?: string,
alignRight?: boolean
}) { }) {
const label = this.label = document.createElement('label'); const label = this.label = document.createElement('label');
label.classList.add('radio-field'); label.classList.add('radio-field');
if(options.alignRight) {
label.classList.add('radio-field-right');
}
const input = this.input = document.createElement('input'); const input = this.input = document.createElement('input');
input.type = 'radio'; input.type = 'radio';
/* input.id = */input.name = 'input-radio-' + options.name; /* input.id = */input.name = 'input-radio-' + options.name;

23
src/components/row.ts

@ -17,6 +17,7 @@ export default class Row {
public container: HTMLElement; public container: HTMLElement;
public title: HTMLDivElement; public title: HTMLDivElement;
public subtitle: HTMLElement; public subtitle: HTMLElement;
public media: HTMLElement;
public checkboxField: CheckboxField; public checkboxField: CheckboxField;
public radioField: RadioField; public radioField: RadioField;
@ -31,7 +32,7 @@ export default class Row {
radioField: Row['radioField'], radioField: Row['radioField'],
checkboxField: Row['checkboxField'], checkboxField: Row['checkboxField'],
noCheckboxSubtitle: boolean, noCheckboxSubtitle: boolean,
title: string, title: string | HTMLElement,
titleLangKey: LangPackKey, titleLangKey: LangPackKey,
titleRight: string | HTMLElement, titleRight: string | HTMLElement,
clickable: boolean | ((e: Event) => void), clickable: boolean | ((e: Event) => void),
@ -58,10 +59,10 @@ export default class Row {
let havePadding = !!options.havePadding; let havePadding = !!options.havePadding;
if(options.radioField || options.checkboxField) { if(options.radioField || options.checkboxField) {
havePadding = true;
if(options.radioField) { if(options.radioField) {
this.radioField = options.radioField; this.radioField = options.radioField;
this.container.append(this.radioField.label); this.container.append(this.radioField.label);
havePadding = true;
} }
if(options.checkboxField) { if(options.checkboxField) {
@ -72,6 +73,7 @@ export default class Row {
this.container.classList.add('row-with-toggle'); this.container.classList.add('row-with-toggle');
options.titleRight = this.checkboxField.label; options.titleRight = this.checkboxField.label;
} else { } else {
havePadding = true;
this.container.append(this.checkboxField.label); this.container.append(this.checkboxField.label);
} }
@ -100,7 +102,11 @@ export default class Row {
this.title.classList.add('row-title'); this.title.classList.add('row-title');
this.title.setAttribute('dir', 'auto'); this.title.setAttribute('dir', 'auto');
if(options.title) { if(options.title) {
if(typeof(options.title) === 'string') {
this.title.innerHTML = options.title; this.title.innerHTML = options.title;
} else {
this.title.append(options.title);
}
} else { } else {
this.title.append(i18n(options.titleLangKey)); this.title.append(i18n(options.titleLangKey));
} }
@ -154,7 +160,20 @@ export default class Row {
} }
} }
public createMedia(size?: 'small') {
this.container.classList.add('row-with-padding');
const media = this.media = document.createElement('div');
media.classList.add('row-media');
if(size) {
media.classList.add('row-media-' + size);
}
this.container.append(media);
return media;
}
} }
export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void) => { export const RadioFormFromRows = (rows: Row[], onChange: (value: string) => void) => {

23
src/components/sidebarLeft/tabs/generalSettings.ts

@ -20,13 +20,14 @@ import appStickersManager from "../../../lib/appManagers/appStickersManager";
import assumeType from "../../../helpers/assumeType"; import assumeType from "../../../helpers/assumeType";
import { MessagesAllStickers, StickerSet } from "../../../layer"; import { MessagesAllStickers, StickerSet } from "../../../layer";
import RichTextProcessor from "../../../lib/richtextprocessor"; import RichTextProcessor from "../../../lib/richtextprocessor";
import { wrapSticker, wrapStickerSetThumb } from "../../wrappers"; import { wrapStickerSetThumb, wrapStickerToRow } from "../../wrappers";
import LazyLoadQueue from "../../lazyLoadQueue"; import LazyLoadQueue from "../../lazyLoadQueue";
import PopupStickers from "../../popups/stickers"; import PopupStickers from "../../popups/stickers";
import eachMinute from "../../../helpers/eachMinute"; import eachMinute from "../../../helpers/eachMinute";
import { SliderSuperTabEventable } from "../../sliderTab"; import { SliderSuperTabEventable } from "../../sliderTab";
import IS_GEOLOCATION_SUPPORTED from "../../../environment/geolocationSupport"; import IS_GEOLOCATION_SUPPORTED from "../../../environment/geolocationSupport";
import appReactionsManager from "../../../lib/appManagers/appReactionsManager"; import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
import AppQuickReactionTab from "./quickReaction";
export class RangeSettingSelector { export class RangeSettingSelector {
public container: HTMLDivElement; public container: HTMLDivElement;
@ -287,26 +288,26 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
const container = section('Telegram.InstalledStickerPacksController'); const container = section('Telegram.InstalledStickerPacksController');
const reactionsRow = new Row({ const reactionsRow = new Row({
titleLangKey: 'Reactions', titleLangKey: 'DoubleTapSetting',
havePadding: true, havePadding: true,
clickable: () => { clickable: () => {
new AppQuickReactionTab(this.slider).open();
} }
}); });
const quickReactionMediaDiv = document.createElement('div'); const renderQuickReaction = () => {
quickReactionMediaDiv.classList.add('row-media', 'row-media-small');
appReactionsManager.getQuickReaction().then(reaction => { appReactionsManager.getQuickReaction().then(reaction => {
wrapSticker({ wrapStickerToRow({
div: quickReactionMediaDiv, row: reactionsRow,
doc: reaction.static_icon, doc: reaction.static_icon,
width: 32, size: 'small'
height: 32
}); });
}); });
};
renderQuickReaction();
reactionsRow.container.append(quickReactionMediaDiv); this.listenerSetter.add(rootScope)('quick_reaction', renderQuickReaction);
const suggestCheckboxField = new CheckboxField({ const suggestCheckboxField = new CheckboxField({
text: 'Stickers.SuggestStickers', text: 'Stickers.SuggestStickers',

65
src/components/sidebarLeft/tabs/quickReaction.ts

@ -0,0 +1,65 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { SettingSection } from "..";
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
import RadioField from "../../radioField";
import Row, { RadioFormFromRows } from "../../row";
import SliderSuperTab from "../../sliderTab";
import { wrapStickerToRow } from "../../wrappers";
export default class AppQuickReactionTab extends SliderSuperTab {
protected init() {
this.header.classList.add('with-border');
this.setTitle('DoubleTapSetting');
this.container.classList.add('quick-reaction-container');
return Promise.all([
appReactionsManager.getQuickReaction(),
appReactionsManager.getAvailableReactions()
]).then(([quickReaction, availableReactions]) => {
availableReactions = availableReactions.filter(reaction => !reaction.pFlags.inactive);
const section = new SettingSection();
const name = 'quick-reaction';
const rows = availableReactions.map((availableReaction) => {
const radioField = new RadioField({
name,
text: availableReaction.title,
value: availableReaction.reaction,
alignRight: true
});
const row = new Row({
radioField,
havePadding: true
});
radioField.main.classList.add('quick-reaction-title');
wrapStickerToRow({
row,
doc: availableReaction.static_icon,
size: 'small'
});
if(availableReaction === quickReaction) {
radioField.setValueSilently(true);
}
return row;
});
const form = RadioFormFromRows(rows, (value) => {
appReactionsManager.setDefaultReaction(value);
});
section.content.append(form);
this.scrollable.append(section.container);
});
}
}

100
src/components/sidebarRight/tabs/chatReactions.ts

@ -0,0 +1,100 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
import CheckboxField from "../../checkboxField";
import Row from "../../row";
import { SettingSection } from "../../sidebarLeft";
import { SliderSuperTabEventable } from "../../sliderTab";
import { wrapStickerToRow } from "../../wrappers";
export default class AppChatReactionsTab extends SliderSuperTabEventable {
public chatId: ChatId;
protected async init() {
this.setTitle('Reactions');
const availableReactions = await appReactionsManager.getActiveAvailableReactions();
const chatFull = await appProfileManager.getChatFull(this.chatId);
const originalReactions = chatFull.available_reactions ?? [];
const enabledReactions = new Set(originalReactions);
const toggleSection = new SettingSection({
caption: appChatsManager.isBroadcast(this.chatId) ? 'EnableReactionsChannelInfo' : 'EnableReactionsGroupInfo'
});
const toggleCheckboxField = new CheckboxField({toggle: true, checked: !!enabledReactions.size});
const toggleRow = new Row({
checkboxField: toggleCheckboxField,
titleLangKey: 'EnableReactions'
});
toggleSection.content.append(toggleRow.container);
const reactionsSection = new SettingSection({
name: 'AvailableReactions'
});
const checkboxFields = availableReactions.map(availableReaction => {
const checkboxField = new CheckboxField({
toggle: true,
checked: enabledReactions.has(availableReaction.reaction)
});
this.listenerSetter.add(checkboxField.input)('change', () => {
if(checkboxField.checked) {
enabledReactions.add(availableReaction.reaction);
if(!toggleCheckboxField.checked) {
toggleCheckboxField.setValueSilently(true);
}
} else enabledReactions.delete(availableReaction.reaction);
});
const row = new Row({
checkboxField,
title: availableReaction.title,
havePadding: true
});
wrapStickerToRow({
row,
doc: availableReaction.static_icon,
size: 'small'
});
reactionsSection.content.append(row.container);
return checkboxField;
});
this.listenerSetter.add(toggleRow.checkboxField.input)('change', () => {
if(!toggleCheckboxField.checked) {
checkboxFields.forEach(checkboxField => checkboxField.setValueSilently(false));
} else if(checkboxFields.every(checkboxField => !checkboxField.checked)) {
checkboxFields.forEach(checkboxField => checkboxField.setValueSilently(true));
}
});
this.eventListener.addEventListener('destroy', () => {
const newReactions = Array.from(enabledReactions);
if([...newReactions].sort().join() === [...originalReactions].sort().join()) {
return;
}
const chatFull = appProfileManager.getCachedFullChat(this.chatId);
if(chatFull) {
chatFull.available_reactions = newReactions;
}
appChatsManager.setChatAvailableReactions(this.chatId, newReactions);
}, {once: true});
this.scrollable.append(toggleSection.container, reactionsSection.container);
}
}

39
src/components/sidebarRight/tabs/editChat.ts

@ -21,22 +21,27 @@ import PopupDeleteDialog from "../../popups/deleteDialog";
import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import toggleDisability from "../../../helpers/dom/toggleDisability"; import toggleDisability from "../../../helpers/dom/toggleDisability";
import CheckboxField from "../../checkboxField"; import CheckboxField from "../../checkboxField";
import appReactionsManager from "../../../lib/appManagers/appReactionsManager";
import AppChatReactionsTab from "./chatReactions";
export default class AppEditChatTab extends SliderSuperTab { export default class AppEditChatTab extends SliderSuperTab {
private chatNameInputField: InputField; private chatNameInputField: InputField;
private descriptionInputField: InputField; private descriptionInputField: InputField;
private editPeer: EditPeer; private editPeer: EditPeer;
private tempId: number;
public chatId: ChatId; public chatId: ChatId;
protected async _init() { protected async _init() {
// * cleanup prev // * cleanup prev
this.listenerSetter.removeAll(); this.listenerSetter.removeAll();
this.scrollable.container.innerHTML = ''; this.scrollable.container.innerHTML = '';
this.tempId ??= 0;
const tempId = ++this.tempId;
this.container.classList.add('edit-peer-container', 'edit-group-container'); this.container.classList.add('edit-peer-container', 'edit-group-container');
this.setTitle('Edit'); this.setTitle('Edit');
const chatFull = await appProfileManager.getChatFull(this.chatId, true); let chatFull = await appProfileManager.getChatFull(this.chatId, true);
const chat: Chat.chat | Chat.channel = appChatsManager.getChat(this.chatId); const chat: Chat.chat | Chat.channel = appChatsManager.getChat(this.chatId);
const isBroadcast = appChatsManager.isBroadcast(this.chatId); const isBroadcast = appChatsManager.isBroadcast(this.chatId);
@ -53,6 +58,12 @@ export default class AppEditChatTab extends SliderSuperTab {
} }
}); });
this.listenerSetter.add(rootScope)('chat_full_update', (chatId) => {
if(this.chatId === chatId) {
chatFull = appProfileManager.getCachedFullChat(chatId) || chatFull;
}
});
const peerId = this.chatId.toPeerId(true); const peerId = this.chatId.toPeerId(true);
{ {
@ -119,6 +130,32 @@ export default class AppEditChatTab extends SliderSuperTab {
setChatTypeSubtitle(); setChatTypeSubtitle();
section.content.append(chatTypeRow.container); section.content.append(chatTypeRow.container);
const reactionsRow = new Row({
titleLangKey: 'Reactions',
icon: 'tip',
clickable: () => {
const tab = new AppChatReactionsTab(this.slider);
tab.chatId = this.chatId;
tab.open().then(() => {
if(this.tempId !== tempId) {
return;
}
this.listenerSetter.add(tab.eventListener)('destroy', setReactionsLength);
});
}
});
const availableReactions = await appReactionsManager.getAvailableReactions();
const availableReactionsLength = availableReactions.filter(availableReaction => !availableReaction.pFlags.inactive).length;
const setReactionsLength = () => {
reactionsRow.subtitle.innerHTML = chatFull.available_reactions.length + '/' + availableReactionsLength;
};
setReactionsLength();
section.content.append(reactionsRow.container);
} }
if(appChatsManager.hasRights(this.chatId, 'change_permissions') && !isBroadcast) { if(appChatsManager.hasRights(this.chatId, 'change_permissions') && !isBroadcast) {

32
src/components/wrappers.ts

@ -56,6 +56,7 @@ import appMessagesIdsManager from '../lib/appManagers/appMessagesIdsManager';
import throttle from '../helpers/schedulers/throttle'; import throttle from '../helpers/schedulers/throttle';
import { SendMessageEmojiInteractionData } from '../types'; import { SendMessageEmojiInteractionData } from '../types';
import IS_VIBRATE_SUPPORTED from '../environment/vibrateSupport'; import IS_VIBRATE_SUPPORTED from '../environment/vibrateSupport';
import Row from './row';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -1637,6 +1638,37 @@ export async function wrapStickerSetThumb({set, lazyLoadQueue, container, group,
} }
} }
export function wrapStickerToRow({doc, row, size}: {
doc: MyDocument,
row: Row,
size?: 'small' | 'large',
}) {
const previousMedia = row.media;
const media = row.createMedia('small');
if(previousMedia) {
media.classList.add('hide');
}
const loadPromises: Promise<any>[] = previousMedia ? [] : undefined;
const _size = size === 'small' ? 32 : 48;
const result = wrapSticker({
div: media,
doc: doc,
width: _size,
height: _size,
loadPromises
});
loadPromises && Promise.all(loadPromises).then(() => {
media.classList.remove('hide');
previousMedia.remove();
});
return result;
}
export function wrapLocalSticker({emoji, width, height}: { export function wrapLocalSticker({emoji, width, height}: {
doc?: MyDocument, doc?: MyDocument,
url?: string, url?: string,

9
src/helpers/callbackify.ts

@ -0,0 +1,9 @@
import {Awaited} from '../types';
export default function callbackify<T extends Awaited<any>, R extends any>(smth: T, callback: (result: Awaited<T>) => R): PromiseLike<R> | R {
if(smth instanceof Promise) {
return smth.then(callback);
} else {
return callback(smth as any);
}
}

82
src/lang.ts

@ -642,6 +642,10 @@ const lang = {
"Update": "UPDATE", "Update": "UPDATE",
"Reactions": "Reactions", "Reactions": "Reactions",
"DoubleTapSetting": "Quick Reaction", "DoubleTapSetting": "Quick Reaction",
"EnableReactions": "Enable Reactions",
"EnableReactionsChannelInfo": "Allow subscribers to react to channel posts.",
"EnableReactionsGroupInfo": "Allow members to react to group messages.",
"AvailableReactions": "Available reactions",
// * macos // * macos
"AccountSettings.Filters": "Chat Folders", "AccountSettings.Filters": "Chat Folders",
@ -833,22 +837,25 @@ const lang = {
"Emoji.Objects": "Objects", "Emoji.Objects": "Objects",
//"Emoji.Symbols": "Symbols", //"Emoji.Symbols": "Symbols",
"Emoji.Flags": "Flags", "Emoji.Flags": "Flags",
"InstalledStickers.LoopAnimated": "Loop Animated Stickers",
"LastSeen.HoursAgo": { "LastSeen.HoursAgo": {
"one_value": "last seen %d hour ago", "one_value": "last seen %d hour ago",
"other_value": "last seen %d hours ago" "other_value": "last seen %d hours ago"
}, },
"Login.Register.LastName.Placeholder": "Last Name", "Login.Register.LastName.Placeholder": "Last Name",
"Message.Context.Select": "Select",
"Message.Context.Pin": "Pin",
"Message.Context.Unpin": "Unpin",
"Message.Context.Goto": "Show Message",
"MessageContext.CopyMessageLink1": "Copy Message Link",
"Modal.Send": "Send", "Modal.Send": "Send",
"Telegram.GeneralSettingsViewController": "General Settings", "NewPoll.Anonymous": "Anonymous Voting",
"Telegram.InstalledStickerPacksController": "Stickers", "NewPoll.Explanation.Placeholder": "Add a Comment (Optional)",
"Telegram.NotificationSettingsViewController": "Notifications", "NewPoll.OptionsAddOption": "Add an Option",
"Telegram.LanguageViewController": "Language", "NewPoll.MultipleChoice": "Multiple Answers",
"Stickers.SearchAdd": "Add", "NewPoll.Quiz": "Quiz Mode",
"Stickers.SearchAdded": "Added", "Notification.Contact.Reacted": "%1$@ to your \"%2$@\"",
"Stickers.SuggestStickers": "Suggest Stickers by Emoji", // "Notification.Group.Reacted": "%1$@: %2$@ to your \"%3$@\"",
"ShareModal.Search.Placeholder": "Share to...",
"ShareModal.Search.ForwardPlaceholder": "Forward to...",
"InstalledStickers.LoopAnimated": "Loop Animated Stickers",
"Peer.Activity.User.PlayingGame": "playing a game", "Peer.Activity.User.PlayingGame": "playing a game",
"Peer.Activity.User.TypingText": "typing", "Peer.Activity.User.TypingText": "typing",
"Peer.Activity.User.SendingPhoto": "sending a photo", "Peer.Activity.User.SendingPhoto": "sending a photo",
@ -958,16 +965,15 @@ const lang = {
}, },
"RecentSessions.Error.FreshReset": "For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours.", "RecentSessions.Error.FreshReset": "For security reasons, you can't terminate older sessions from a device that you've just connected. Please use an earlier connection or wait for a few hours.",
"RequestJoin.Button": "Request to Join", "RequestJoin.Button": "Request to Join",
"Message.Context.Select": "Select", "Stickers.SearchAdd": "Add",
"Message.Context.Pin": "Pin", "Stickers.SearchAdded": "Added",
"Message.Context.Unpin": "Unpin", "Stickers.SuggestStickers": "Suggest Stickers by Emoji",
"Message.Context.Goto": "Show Message", "ShareModal.Search.Placeholder": "Share to...",
"MessageContext.CopyMessageLink1": "Copy Message Link", "ShareModal.Search.ForwardPlaceholder": "Forward to...",
"NewPoll.Anonymous": "Anonymous Voting", "Telegram.GeneralSettingsViewController": "General Settings",
"NewPoll.Explanation.Placeholder": "Add a Comment (Optional)", "Telegram.InstalledStickerPacksController": "Stickers",
"NewPoll.OptionsAddOption": "Add an Option", "Telegram.NotificationSettingsViewController": "Notifications",
"NewPoll.MultipleChoice": "Multiple Answers", "Telegram.LanguageViewController": "Language",
"NewPoll.Quiz": "Quiz Mode",
"GeneralSettings.BigEmoji": "Large Emoji", "GeneralSettings.BigEmoji": "Large Emoji",
"GeneralSettings.EmojiPrediction": "Suggest Emoji", "GeneralSettings.EmojiPrediction": "Suggest Emoji",
"GroupPermission.Delete": "Delete Exception", "GroupPermission.Delete": "Delete Exception",
@ -981,6 +987,23 @@ const lang = {
"Stickers.Recent": "Recent", "Stickers.Recent": "Recent",
//"Stickers.Favorite": "Favorite", //"Stickers.Favorite": "Favorite",
"StickerSet.DontExist": "Sorry, this sticker set doesn't seem to exist.", "StickerSet.DontExist": "Sorry, this sticker set doesn't seem to exist.",
"Text.Context.Copy.Username": "Copy Username",
"Text.Context.Copy.Hashtag": "Copy Hashtag",
"Time.TomorrowAt": "tomorrow at %@",
"TwoStepAuth.SetPasswordHelp": "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.",
"TwoStepAuth.GenericHelp": "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account.",
"TwoStepAuth.ChangePassword": "Change Password",
"TwoStepAuth.RemovePassword": "Turn Password Off",
"TwoStepAuth.SetupEmail": "Set Recovery Email",
"TwoStepAuth.ChangeEmail": "Change Recovery Email",
"TwoStepAuth.ConfirmEmailCodeDesc": "Please enter the code we've just emailed to %@.",
"TwoStepAuth.RecoveryTitle": "Email Code",
"TwoStepAuth.RecoveryCode": "Code",
"TwoStepAuth.RecoveryCodeInvalid": "Invalid code. Please try again.",
"TwoStepAuth.RecoveryCodeExpired": "Code Expired",
"TwoStepAuth.SetupHintTitle": "Password Hint",
"TwoStepAuth.SetupHintPlaceholder": "Hint",
"UsernameSettings.ChangeDescription": "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters.",
"VoiceChat.Chat.StartNew": "Video chat ended. Start a new one?", "VoiceChat.Chat.StartNew": "Video chat ended. Start a new one?",
"VoiceChat.Chat.StartNew.OK": "Start", "VoiceChat.Chat.StartNew.OK": "Start",
"VoiceChat.Chat.Ended": "Video chat ended.", "VoiceChat.Chat.Ended": "Video chat ended.",
@ -1012,24 +1035,7 @@ const lang = {
"VoiceChat.UnmuteForMe": "Unmute For Me", "VoiceChat.UnmuteForMe": "Unmute For Me",
"VoiceChat.RemovePeer.Confirm.Channel": "Do you want to remove %1$@ from the channel?", "VoiceChat.RemovePeer.Confirm.Channel": "Do you want to remove %1$@ from the channel?",
"VoiceChat.RemovePeer.Confirm": "Are you sure you want to remove %1$@ from the group?", "VoiceChat.RemovePeer.Confirm": "Are you sure you want to remove %1$@ from the group?",
"VoiceChat.RemovePeer.Confirm.OK": "Remove", "VoiceChat.RemovePeer.Confirm.OK": "Remove"
"Text.Context.Copy.Username": "Copy Username",
"Text.Context.Copy.Hashtag": "Copy Hashtag",
"Time.TomorrowAt": "tomorrow at %@",
"TwoStepAuth.SetPasswordHelp": "You can set a password that will be required when you log in on a new device in addition to the code you get in the SMS.",
"TwoStepAuth.GenericHelp": "You have enabled Two-Step verification.\nYou'll need the password you set up here to log in to your Telegram account.",
"TwoStepAuth.ChangePassword": "Change Password",
"TwoStepAuth.RemovePassword": "Turn Password Off",
"TwoStepAuth.SetupEmail": "Set Recovery Email",
"TwoStepAuth.ChangeEmail": "Change Recovery Email",
"TwoStepAuth.ConfirmEmailCodeDesc": "Please enter the code we've just emailed to %@.",
"TwoStepAuth.RecoveryTitle": "Email Code",
"TwoStepAuth.RecoveryCode": "Code",
"TwoStepAuth.RecoveryCodeInvalid": "Invalid code. Please try again.",
"TwoStepAuth.RecoveryCodeExpired": "Code Expired",
"TwoStepAuth.SetupHintTitle": "Password Hint",
"TwoStepAuth.SetupHintPlaceholder": "Hint",
"UsernameSettings.ChangeDescription": "You can choose a username on Telegram. If you do, people will be able to find you by this username and contact you without needing your phone number.\n\n\nYou can use a-z, 0-9 and underscores. Minimum length is 5 characters."
}; };
export default lang; export default lang;

9
src/lib/appManagers/appChatsManager.ts

@ -770,6 +770,15 @@ export class AppChatsManager {
apiUpdatesManager.processUpdateMessage(updates); apiUpdatesManager.processUpdateMessage(updates);
}); });
} }
public setChatAvailableReactions(id: ChatId, reactions: Array<string>) {
return apiManager.invokeApi('messages.setChatAvailableReactions', {
peer: this.getInputPeer(id),
available_reactions: reactions
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
} }
const appChatsManager = new AppChatsManager(); const appChatsManager = new AppChatsManager();

57
src/lib/appManagers/appReactionsManager.ts

@ -6,6 +6,7 @@
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import assumeType from "../../helpers/assumeType"; import assumeType from "../../helpers/assumeType";
import callbackify from "../../helpers/callbackify";
import { AvailableReaction, MessagesAvailableReactions } from "../../layer"; import { AvailableReaction, MessagesAvailableReactions } from "../../layer";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";
import { ReferenceContext } from "../mtproto/referenceDatabase"; import { ReferenceContext } from "../mtproto/referenceDatabase";
@ -37,7 +38,7 @@ export class AppReactionsManager {
} }
public getAvailableReactions() { public getAvailableReactions() {
if(this.availableReactions) return Promise.resolve(this.availableReactions); if(this.availableReactions) return this.availableReactions;
return apiManager.invokeApiSingleProcess({ return apiManager.invokeApiSingleProcess({
method: 'messages.getAvailableReactions', method: 'messages.getAvailableReactions',
processResult: (messagesAvailableReactions) => { processResult: (messagesAvailableReactions) => {
@ -62,6 +63,17 @@ export class AppReactionsManager {
}); });
} }
public getActiveAvailableReactions() {
return callbackify(this.getAvailableReactions(), (availableReactions) => {
return availableReactions.filter(availableReaction => !availableReaction.pFlags.inactive);
});
}
public isReactionActive(reaction: string) {
if(!this.availableReactions) return false;
return !!this.availableReactions.find(availableReaction => availableReaction.reaction === reaction);
}
public getQuickReaction() { public getQuickReaction() {
return Promise.all([ return Promise.all([
apiManager.getAppConfig(), apiManager.getAppConfig(),
@ -70,6 +82,49 @@ export class AppReactionsManager {
return availableReactions.find(reaction => reaction.reaction === appConfig.reactions_default); return availableReactions.find(reaction => reaction.reaction === appConfig.reactions_default);
}); });
} }
public getReactionCached(reaction: string) {
return this.availableReactions.find(availableReaction => availableReaction.reaction === reaction);
}
public getReaction(reaction: string) {
return callbackify(this.getAvailableReactions(), () => {
return this.getReactionCached(reaction);
});
}
/* public getMessagesReactions(peerId: PeerId, mids: number[]) {
return apiManager.invokeApiSingleProcess({
method: 'messages.getMessagesReactions',
params: {
id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid)),
peer: appPeersManager.getInputPeerById(peerId)
},
processResult: (updates) => {
apiUpdatesManager.processUpdateMessage(updates);
// const update = (updates as Updates.updates).updates.find(update => update._ === 'updateMessageReactions') as Update.updateMessageReactions;
// return update.reactions;
}
});
} */
public setDefaultReaction(reaction: string) {
return apiManager.invokeApi('messages.setDefaultReaction', {reaction}).then(value => {
if(value) {
const appConfig = rootScope.appConfig;
if(appConfig) {
appConfig.reactions_default = reaction;
} else { // if no config or loading it - overwrite
apiManager.getAppConfig(true);
}
rootScope.dispatchEvent('quick_reaction', reaction);
}
return value;
});
}
} }
const appReactionsManager = new AppReactionsManager(); const appReactionsManager = new AppReactionsManager();

8
src/lib/appManagers/appStickersManager.ts

@ -162,7 +162,11 @@ export class AppStickersManager {
public getAnimatedEmojiSounds(overwrite?: boolean) { public getAnimatedEmojiSounds(overwrite?: boolean) {
if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise; if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise;
return this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => { const promise = this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => {
if(this.getAnimatedEmojiSoundsPromise !== promise) {
return;
}
for(const emoji in appConfig.emojies_sounds) { for(const emoji in appConfig.emojies_sounds) {
const sound = appConfig.emojies_sounds[emoji]; const sound = appConfig.emojies_sounds[emoji];
const bytesStr = atob(fixBase64String(sound.file_reference_base64, false)); const bytesStr = atob(fixBase64String(sound.file_reference_base64, false));
@ -206,6 +210,8 @@ export class AppStickersManager {
// TEST_FILE_REFERENCE_REFRESH = false; // TEST_FILE_REFERENCE_REFRESH = false;
// } // }
}); });
return promise;
} }
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, { public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {

8
src/lib/mtproto/mtprotoworker.ts

@ -698,10 +698,16 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
public getAppConfig(overwrite?: boolean): Promise<MTAppConfig> { public getAppConfig(overwrite?: boolean): Promise<MTAppConfig> {
if(this.getAppConfigPromise && !overwrite) return this.getAppConfigPromise; if(this.getAppConfigPromise && !overwrite) return this.getAppConfigPromise;
return this.getAppConfigPromise = this.invokeApi('help.getAppConfig').then(config => { const promise = this.getAppConfigPromise = this.invokeApi('help.getAppConfig').then(config => {
if(this.getAppConfigPromise !== promise) {
return;
}
rootScope.appConfig = config; rootScope.appConfig = config;
return config; return config;
}); });
return promise;
} }
} }

5
src/lib/rootScope.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall, MethodDeclMap } from "../layer"; import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall, MethodDeclMap, MessageReactions } from "../layer";
import type { MyDocument } from "./appManagers/appDocsManager"; import type { MyDocument } from "./appManagers/appDocsManager";
import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager"; import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager";
import type { MyDialogFilter } from "./storages/filters"; import type { MyDialogFilter } from "./storages/filters";
@ -76,6 +76,7 @@ export type BroadcastEvents = {
'message_edit': {storage: MessagesStorage, peerId: PeerId, mid: number}, 'message_edit': {storage: MessagesStorage, peerId: PeerId, mid: number},
'message_views': {peerId: PeerId, mid: number, views: number}, 'message_views': {peerId: PeerId, mid: number, views: number},
'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number, message: MyMessage}, 'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number, message: MyMessage},
'message_reactions': Message.message,
'messages_pending': void, 'messages_pending': void,
'messages_read': void, 'messages_read': void,
'messages_downloaded': {peerId: PeerId, mids: number[]}, 'messages_downloaded': {peerId: PeerId, mids: number[]},
@ -156,6 +157,8 @@ export type BroadcastEvents = {
// 'group_call_video_track_added': {instance: GroupCallInstance} // 'group_call_video_track_added': {instance: GroupCallInstance}
'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */}, 'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */},
'quick_reaction': string
}; };
export class RootScope extends EventListenerBase<{ export class RootScope extends EventListenerBase<{

Loading…
Cancel
Save