Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63919422e0 | ||
|
|
d6e6c51639 | ||
|
|
95a24d6aa1 | ||
|
|
fa28b0b405 | ||
|
|
42fcf4ceb2 | ||
|
|
3743dd0161 | ||
|
|
dab859ea29 | ||
|
|
a92394a81f | ||
|
|
7a57174ab1 | ||
|
|
52bacb3cde | ||
|
|
8ad9770118 | ||
|
|
aef45b3a1d | ||
|
|
87af865604 | ||
|
|
4efeaacf5c |
@@ -1331,7 +1331,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_invite_qr_title" = "Invite by QR Code";
|
||||
"lng_group_invite_qr_about" = "Everyone on Telegram can scan this code to join your group.";
|
||||
"lng_group_invite_qr_copied" = "QR Code copied to clipboard.";
|
||||
"lng_group_invite_request_approve" = "Approve everyone who joins";
|
||||
"lng_group_invite_request_approve" = "Request admin approval";
|
||||
"lng_group_invite_about_approve" = "New users will be able to join the group only after having been approved by the admins.";
|
||||
"lng_group_invite_about_no_approve" = "New users will be able to join the group without being approved by the admins.";
|
||||
"lng_group_invite_about_approve_channel" = "New users will be able to join the channel only after having been approved by the admins.";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.1.13.0" />
|
||||
Version="3.2.0.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,1,13,0
|
||||
PRODUCTVERSION 3,1,13,0
|
||||
FILEVERSION 3,2,0,0
|
||||
PRODUCTVERSION 3,2,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "3.1.13.0"
|
||||
VALUE "FileVersion", "3.2.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.1.13.0"
|
||||
VALUE "ProductVersion", "3.2.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,1,13,0
|
||||
PRODUCTVERSION 3,1,13,0
|
||||
FILEVERSION 3,2,0,0
|
||||
PRODUCTVERSION 3,2,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -53,10 +53,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "3.1.13.0"
|
||||
VALUE "FileVersion", "3.2.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.1.13.0"
|
||||
VALUE "ProductVersion", "3.2.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -98,6 +98,7 @@ confirmInviteAbout: FlatLabel(boxLabel) {
|
||||
confirmInviteStatus: FlatLabel(confirmInviteAbout) {
|
||||
textFg: windowSubTextFg;
|
||||
style: boxLabelStyle;
|
||||
maxHeight: 0px;
|
||||
}
|
||||
confirmInviteAboutPadding: margins(36px, 4px, 36px, 10px);
|
||||
confirmInviteAboutRequestsPadding: margins(36px, 9px, 36px, 15px);
|
||||
@@ -373,7 +374,7 @@ aboutVersionLink: LinkButton(defaultLinkButton) {
|
||||
aboutTextTop: 34px;
|
||||
aboutSkip: 14px;
|
||||
aboutLabel: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 330px;
|
||||
minWidth: 300px;
|
||||
align: align(topleft);
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
lineHeight: 22px;
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
@@ -1211,9 +1212,10 @@ void EditLink(
|
||||
}
|
||||
};
|
||||
const auto isGroup = !peer->isBroadcast();
|
||||
const auto isPublic = peer->isChannel() && peer->asChannel()->isPublic();
|
||||
*box = Ui::show(
|
||||
(creating
|
||||
? Box(Ui::CreateInviteLinkBox, isGroup, done)
|
||||
? Box(Ui::CreateInviteLinkBox, isGroup, isPublic, done)
|
||||
: Box(
|
||||
Ui::EditInviteLinkBox,
|
||||
Fields{
|
||||
@@ -1223,6 +1225,7 @@ void EditLink(
|
||||
.usageLimit = data.usageLimit,
|
||||
.requestApproval = data.requestApproval,
|
||||
.isGroup = isGroup,
|
||||
.isPublic = isPublic,
|
||||
},
|
||||
done)),
|
||||
Ui::LayerOption::KeepOther);
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "chat_helpers/send_context_menu.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "core/application.h"
|
||||
@@ -35,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/unixtime.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_toggling_media.h"
|
||||
#include "api/api_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "styles/style_layers.h"
|
||||
@@ -87,6 +90,7 @@ protected:
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
|
||||
@@ -113,6 +117,8 @@ private:
|
||||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
|
||||
void send(not_null<DocumentData*> sticker, Api::SendOptions options);
|
||||
|
||||
not_null<Lottie::MultiPlayer*> getLottiePlayer();
|
||||
|
||||
void showPreview();
|
||||
@@ -144,6 +150,8 @@ private:
|
||||
base::Timer _previewTimer;
|
||||
int _previewShown = -1;
|
||||
|
||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||
|
||||
rpl::event_stream<uint64> _setInstalled;
|
||||
rpl::event_stream<uint64> _setArchived;
|
||||
rpl::event_stream<> _updateControls;
|
||||
@@ -577,10 +585,14 @@ void StickerSetBox::Inner::installDone(
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mousePressEvent(QMouseEvent *e) {
|
||||
int index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index >= 0 && index < _pack.size()) {
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.callOnce(QApplication::startDragTime());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::mouseMoveEvent(QMouseEvent *e) {
|
||||
@@ -605,18 +617,62 @@ void StickerSetBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_previewShown = -1;
|
||||
return;
|
||||
}
|
||||
if (_previewTimer.isActive()) {
|
||||
_previewTimer.cancel();
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index >= 0 && index < _pack.size() && !isMasksSet()) {
|
||||
const auto sticker = _pack[index];
|
||||
Ui::PostponeCall(crl::guard(_controller, [=] {
|
||||
if (_controller->content()->sendExistingDocument(sticker)) {
|
||||
Ui::hideSettingsAndLayer();
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (!_previewTimer.isActive()) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.cancel();
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0 || index >= _pack.size() || isMasksSet()) {
|
||||
return;
|
||||
}
|
||||
send(_pack[index], Api::SendOptions());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::send(
|
||||
not_null<DocumentData*> sticker,
|
||||
Api::SendOptions options) {
|
||||
const auto controller = _controller;
|
||||
Ui::PostponeCall(controller, [=] {
|
||||
if (controller->content()->sendExistingDocument(sticker, options)) {
|
||||
Ui::hideSettingsAndLayer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const auto index = stickerFromGlobalPos(e->globalPos());
|
||||
if (index < 0 || index >= _pack.size()) {
|
||||
return;
|
||||
}
|
||||
const auto type = _controller->content()->sendMenuType();
|
||||
if (type == SendMenu::Type::Disabled) {
|
||||
return;
|
||||
}
|
||||
_previewTimer.cancel();
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(this);
|
||||
|
||||
const auto document = _pack[index];
|
||||
const auto sendSelected = [=](Api::SendOptions options) {
|
||||
send(document, options);
|
||||
};
|
||||
SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
type,
|
||||
SendMenu::DefaultSilentCallback(sendSelected),
|
||||
SendMenu::DefaultScheduleCallback(this, type, sendSelected));
|
||||
|
||||
const auto toggleFavedSticker = [=] {
|
||||
Api::ToggleFavedSticker(
|
||||
document,
|
||||
Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
|
||||
};
|
||||
_menu->addAction(
|
||||
(document->owner().stickers().isFaved(document)
|
||||
? tr::lng_faved_stickers_remove
|
||||
: tr::lng_faved_stickers_add)(tr::now),
|
||||
toggleFavedSticker);
|
||||
|
||||
_menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void StickerSetBox::Inner::updateSelected() {
|
||||
|
||||
@@ -101,7 +101,7 @@ public:
|
||||
[[nodiscard]] not_null<UserData*> user() const {
|
||||
return _user;
|
||||
}
|
||||
[[nodiscard]] uint64 id() const {
|
||||
[[nodiscard]] CallId id() const {
|
||||
return _id;
|
||||
}
|
||||
[[nodiscard]] bool isIncomingWaiting() const;
|
||||
@@ -278,7 +278,7 @@ private:
|
||||
bytes::vector _randomPower;
|
||||
MTP::AuthKey::Data _authKey;
|
||||
|
||||
uint64 _id = 0;
|
||||
CallId _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint64 _keyFingerprint = 0;
|
||||
|
||||
|
||||
@@ -141,12 +141,7 @@ Application::Application(not_null<Launcher*> launcher)
|
||||
, _langpack(std::make_unique<Lang::Instance>())
|
||||
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
|
||||
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
|
||||
, _logo(Window::LoadLogo())
|
||||
, _logoNoMargin(Window::LoadLogoNoMargin())
|
||||
, _autoLockTimer([=] { checkAutoLock(); }) {
|
||||
Expects(!_logo.isNull());
|
||||
Expects(!_logoNoMargin.isNull());
|
||||
|
||||
Ui::Integration::Set(&_private->uiIntegration);
|
||||
|
||||
passcodeLockChanges(
|
||||
|
||||
@@ -145,12 +145,6 @@ public:
|
||||
bool hideMediaView();
|
||||
|
||||
[[nodiscard]] QPoint getPointForCallPanelCenter() const;
|
||||
[[nodiscard]] QImage logo() const {
|
||||
return _logo;
|
||||
}
|
||||
[[nodiscard]] QImage logoNoMargin() const {
|
||||
return _logoNoMargin;
|
||||
}
|
||||
|
||||
void startSettingsAndBackground();
|
||||
[[nodiscard]] Settings &settings() {
|
||||
@@ -354,9 +348,6 @@ private:
|
||||
Media::Player::FloatDelegate *_defaultFloatPlayerDelegate = nullptr;
|
||||
Media::Player::FloatDelegate *_replacementFloatPlayerDelegate = nullptr;
|
||||
|
||||
const QImage _logo;
|
||||
const QImage _logoNoMargin;
|
||||
|
||||
rpl::variable<bool> _passcodeLock;
|
||||
bool _screenIsLocked = false;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
|
||||
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
|
||||
constexpr auto AppName = "Telegram Desktop"_cs;
|
||||
constexpr auto AppFile = "Telegram"_cs;
|
||||
constexpr auto AppVersion = 3001013;
|
||||
constexpr auto AppVersionStr = "3.1.13";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 3002000;
|
||||
constexpr auto AppVersionStr = "3.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/main_window.h" // Window::LogoNoMargin.
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text/text_options.h"
|
||||
@@ -302,7 +303,7 @@ Image *PeerData::currentUserpic(
|
||||
_userpicEmpty = nullptr;
|
||||
} else if (isNotificationsUser()) {
|
||||
static auto result = Image(
|
||||
Core::App().logoNoMargin().scaledToWidth(
|
||||
Window::LogoNoMargin().scaledToWidth(
|
||||
kUserpicSize,
|
||||
Qt::SmoothTransformation));
|
||||
return &result;
|
||||
|
||||
@@ -89,22 +89,27 @@ bool SponsoredMessages::append(not_null<History*> history) {
|
||||
HistoryMessageMarkupData());
|
||||
entryIt->item.reset(std::move(local));
|
||||
|
||||
// Since sponsored posts are only created on demand for display,
|
||||
// we can send a request to view immediately.
|
||||
view(entryIt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SponsoredMessages::canHaveFor(not_null<History*> history) const {
|
||||
return history->isChannel();
|
||||
}
|
||||
|
||||
void SponsoredMessages::request(not_null<History*> history) {
|
||||
if (!canHaveFor(history)) {
|
||||
return;
|
||||
}
|
||||
auto &request = _requests[history];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto channel = history->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_GetSponsoredMessages(
|
||||
_session->data().channel(history->channelId())->inputChannel
|
||||
)).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
channel->inputChannel)
|
||||
).done([=](const MTPmessages_sponsoredMessages &result) {
|
||||
parse(history, result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_requests.remove(history);
|
||||
@@ -171,21 +176,44 @@ void SponsoredMessages::clearItems(not_null<History*> history) {
|
||||
list.showedAll = false;
|
||||
}
|
||||
|
||||
void SponsoredMessages::view(const std::vector<Entry>::iterator entryIt) {
|
||||
const auto randomId = entryIt->sponsored.randomId;
|
||||
const SponsoredMessages::Entry *SponsoredMessages::find(
|
||||
const FullMsgId &fullId) const {
|
||||
if (!fullId.channel) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto history = _session->data().history(
|
||||
peerFromChannel(fullId.channel));
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto &list = it->second;
|
||||
const auto entryIt = ranges::find_if(list.entries, [&](const Entry &e) {
|
||||
return e.item->fullId() == fullId;
|
||||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &*entryIt;
|
||||
}
|
||||
|
||||
void SponsoredMessages::view(const FullMsgId &fullId) {
|
||||
const auto entryPtr = find(fullId);
|
||||
if (!entryPtr) {
|
||||
return;
|
||||
}
|
||||
const auto randomId = entryPtr->sponsored.randomId;
|
||||
auto &request = _viewRequests[randomId];
|
||||
if (request.requestId || TooEarlyForRequest(request.lastReceived)) {
|
||||
return;
|
||||
}
|
||||
const auto history = entryIt->sponsored.history;
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
const auto channel = entryPtr->item->history()->peer->asChannel();
|
||||
Assert(channel != nullptr);
|
||||
request.requestId = _session->api().request(
|
||||
MTPchannels_ViewSponsoredMessage(
|
||||
_session->data().channel(history->channelId())->inputChannel,
|
||||
MTP_bytes(randomId)
|
||||
)).done([=] {
|
||||
channel->inputChannel,
|
||||
MTP_bytes(randomId))
|
||||
).done([=] {
|
||||
auto &request = _viewRequests[randomId];
|
||||
request.lastReceived = crl::now();
|
||||
request.requestId = 0;
|
||||
@@ -195,20 +223,11 @@ void SponsoredMessages::view(const std::vector<Entry>::iterator entryIt) {
|
||||
}
|
||||
|
||||
MsgId SponsoredMessages::channelPost(const FullMsgId &fullId) const {
|
||||
const auto history = _session->data().history(
|
||||
peerFromChannel(fullId.channel));
|
||||
const auto it = _data.find(history);
|
||||
if (it == end(_data)) {
|
||||
const auto entryPtr = find(fullId);
|
||||
if (!entryPtr) {
|
||||
return ShowAtUnreadMsgId;
|
||||
}
|
||||
auto &list = it->second;
|
||||
const auto entryIt = ranges::find_if(list.entries, [&](const Entry &e) {
|
||||
return e.item->fullId() == fullId;
|
||||
});
|
||||
if (entryIt == end(list.entries)) {
|
||||
return ShowAtUnreadMsgId;
|
||||
}
|
||||
const auto msgId = entryIt->sponsored.msgId;
|
||||
const auto msgId = entryPtr->sponsored.msgId;
|
||||
return msgId ? msgId : ShowAtUnreadMsgId;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,11 +36,14 @@ public:
|
||||
SponsoredMessages &operator=(const SponsoredMessages &other) = delete;
|
||||
~SponsoredMessages();
|
||||
|
||||
[[nodiscard]] bool canHaveFor(not_null<History*> history) const;
|
||||
void request(not_null<History*> history);
|
||||
[[nodiscard]] bool append(not_null<History*> history);
|
||||
void clearItems(not_null<History*> history);
|
||||
[[nodiscard]] MsgId channelPost(const FullMsgId &fullId) const;
|
||||
|
||||
void view(const FullMsgId &fullId);
|
||||
|
||||
private:
|
||||
using OwnedItem = std::unique_ptr<HistoryItem, HistoryItem::Destroyer>;
|
||||
struct Entry {
|
||||
@@ -65,7 +68,7 @@ private:
|
||||
const MTPSponsoredMessage &message);
|
||||
void clearOldRequests();
|
||||
|
||||
void view(const std::vector<Entry>::iterator entryIt);
|
||||
const Entry *find(const FullMsgId &fullId) const;
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
|
||||
@@ -2674,10 +2674,6 @@ QString History::topPromotionMessage() const {
|
||||
return _topPromotedMessage;
|
||||
}
|
||||
|
||||
bool History::canHaveSponsoredMessages() const {
|
||||
return isChannel();
|
||||
}
|
||||
|
||||
bool History::clearUnreadOnClientSide() const {
|
||||
if (!session().supportMode()) {
|
||||
return false;
|
||||
|
||||
@@ -282,8 +282,6 @@ public:
|
||||
[[nodiscard]] bool topPromotionAboutShown() const;
|
||||
void markTopPromotionAboutShown();
|
||||
|
||||
[[nodiscard]] bool canHaveSponsoredMessages() const;
|
||||
|
||||
MsgId minMsgId() const;
|
||||
MsgId maxMsgId() const;
|
||||
MsgId msgIdForRead() const;
|
||||
|
||||
@@ -72,6 +72,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/stickers/data_stickers.h"
|
||||
#include "data/data_sponsored_messages.h"
|
||||
#include "facades.h"
|
||||
#include "app.h"
|
||||
#include "styles/style_chat.h"
|
||||
@@ -678,15 +679,19 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
seltoy - mtop);
|
||||
view->draw(p, context);
|
||||
|
||||
if (item->hasViews()) {
|
||||
session().api().views().scheduleIncrement(item);
|
||||
}
|
||||
if (item->isUnreadMention() && !item->isUnreadMedia()) {
|
||||
readMentions.insert(item);
|
||||
_widget->enqueueMessageHighlight(view);
|
||||
const auto height = view->height();
|
||||
const auto middle = top + height / 2;
|
||||
if (_visibleAreaBottom >= middle
|
||||
&& _visibleAreaTop <= middle) {
|
||||
if (item->hasViews()) {
|
||||
session().api().views().scheduleIncrement(item);
|
||||
}
|
||||
if (item->isUnreadMention() && !item->isUnreadMedia()) {
|
||||
readMentions.insert(item);
|
||||
_widget->enqueueMessageHighlight(view);
|
||||
}
|
||||
}
|
||||
|
||||
const auto height = view->height();
|
||||
top += height;
|
||||
context.translate(0, -height);
|
||||
p.translate(0, height);
|
||||
@@ -735,6 +740,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||
if (!item->out() && item->unread()) {
|
||||
readTill = item;
|
||||
}
|
||||
if (item->isSponsored()) {
|
||||
session().data().sponsoredMessages().view(
|
||||
item->fullId());
|
||||
}
|
||||
}
|
||||
if (_visibleAreaBottom >= middle
|
||||
&& _visibleAreaTop <= middle) {
|
||||
|
||||
@@ -2189,11 +2189,9 @@ void HistoryWidget::showHistory(
|
||||
showAboutTopPromotion();
|
||||
|
||||
{
|
||||
const auto hasSponsored = _history->canHaveSponsoredMessages();
|
||||
_scroll->setTrackingContent(hasSponsored);
|
||||
if (hasSponsored) {
|
||||
session().data().sponsoredMessages().request(_history);
|
||||
}
|
||||
auto &sponsored = session().data().sponsoredMessages();
|
||||
sponsored.request(_history);
|
||||
_scroll->setTrackingContent(sponsored.canHaveFor(_history));
|
||||
}
|
||||
} else {
|
||||
_chooseForReport = nullptr;
|
||||
|
||||
@@ -265,6 +265,7 @@ public:
|
||||
void confirmDeleteSelected();
|
||||
void clearSelected();
|
||||
|
||||
[[nodiscard]] SendMenu::Type sendMenuType() const;
|
||||
bool sendExistingDocument(
|
||||
not_null<DocumentData*> document,
|
||||
Api::SendOptions options);
|
||||
@@ -374,7 +375,6 @@ private:
|
||||
void sendWithModifiers(Qt::KeyboardModifiers modifiers);
|
||||
void sendSilent();
|
||||
void sendScheduled();
|
||||
[[nodiscard]] SendMenu::Type sendMenuType() const;
|
||||
[[nodiscard]] SendMenu::Type sendButtonMenuType() const;
|
||||
void handlePendingHistoryUpdate();
|
||||
void fullInfoUpdated();
|
||||
|
||||
@@ -1067,8 +1067,18 @@ void MainWidget::inlineResultLoadFailed(FileLoader *loader, bool started) {
|
||||
//Ui::repaintInlineItem();
|
||||
}
|
||||
|
||||
SendMenu::Type MainWidget::sendMenuType() const {
|
||||
return _history->sendMenuType();
|
||||
}
|
||||
|
||||
bool MainWidget::sendExistingDocument(not_null<DocumentData*> document) {
|
||||
return _history->sendExistingDocument(document, Api::SendOptions());
|
||||
return sendExistingDocument(document, Api::SendOptions());
|
||||
}
|
||||
|
||||
bool MainWidget::sendExistingDocument(
|
||||
not_null<DocumentData*> document,
|
||||
Api::SendOptions options) {
|
||||
return _history->sendExistingDocument(document, options);
|
||||
}
|
||||
|
||||
void MainWidget::dialogsCancelled() {
|
||||
|
||||
@@ -29,8 +29,13 @@ class Error;
|
||||
|
||||
namespace Api {
|
||||
struct SendAction;
|
||||
struct SendOptions;
|
||||
} // namespace Api
|
||||
|
||||
namespace SendMenu {
|
||||
enum class Type;
|
||||
} // namespace SendMenu
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
@@ -150,7 +155,11 @@ public:
|
||||
QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms);
|
||||
void checkMainSectionToLayer();
|
||||
|
||||
bool sendExistingDocument(not_null<DocumentData*> sticker);
|
||||
[[nodiscard]] SendMenu::Type sendMenuType() const;
|
||||
bool sendExistingDocument(not_null<DocumentData*> document);
|
||||
bool sendExistingDocument(
|
||||
not_null<DocumentData*> document,
|
||||
Api::SendOptions options);
|
||||
|
||||
bool isActive() const;
|
||||
[[nodiscard]] bool doWeMarkAsRead() const;
|
||||
|
||||
@@ -81,17 +81,6 @@ void FeedLangTestingKey(int key) {
|
||||
|
||||
MainWindow::MainWindow(not_null<Window::Controller*> controller)
|
||||
: Platform::MainWindow(controller) {
|
||||
|
||||
auto logo = Core::App().logo();
|
||||
icon16 = logo.scaledToWidth(16, Qt::SmoothTransformation);
|
||||
icon32 = logo.scaledToWidth(32, Qt::SmoothTransformation);
|
||||
icon64 = logo.scaledToWidth(64, Qt::SmoothTransformation);
|
||||
|
||||
auto logoNoMargin = Core::App().logoNoMargin();
|
||||
iconbig16 = logoNoMargin.scaledToWidth(16, Qt::SmoothTransformation);
|
||||
iconbig32 = logoNoMargin.scaledToWidth(32, Qt::SmoothTransformation);
|
||||
iconbig64 = logoNoMargin.scaledToWidth(64, Qt::SmoothTransformation);
|
||||
|
||||
resize(st::windowDefaultWidth, st::windowDefaultHeight);
|
||||
|
||||
setLocale(QLocale(QLocale::English, QLocale::UnitedStates));
|
||||
@@ -842,122 +831,6 @@ void MainWindow::updateControlsGeometry() {
|
||||
if (_main) _main->checkMainSectionToLayer();
|
||||
}
|
||||
|
||||
void MainWindow::placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) {
|
||||
QPainter p(&img);
|
||||
|
||||
QString cnt = (count < 100) ? QString("%1").arg(count) : QString("..%1").arg(count % 10, 1, 10, QChar('0'));
|
||||
int32 cntSize = cnt.size();
|
||||
|
||||
p.setBrush(bg->b);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
int32 fontSize;
|
||||
if (size == 16) {
|
||||
fontSize = 8;
|
||||
} else if (size == 32) {
|
||||
fontSize = (cntSize < 2) ? 12 : 12;
|
||||
} else {
|
||||
fontSize = (cntSize < 2) ? 22 : 22;
|
||||
}
|
||||
style::font f = { fontSize, 0, 0 };
|
||||
int32 w = f->width(cnt), d, r;
|
||||
if (size == 16) {
|
||||
d = (cntSize < 2) ? 2 : 1;
|
||||
r = (cntSize < 2) ? 4 : 3;
|
||||
} else if (size == 32) {
|
||||
d = (cntSize < 2) ? 5 : 2;
|
||||
r = (cntSize < 2) ? 8 : 7;
|
||||
} else {
|
||||
d = (cntSize < 2) ? 9 : 4;
|
||||
r = (cntSize < 2) ? 16 : 14;
|
||||
}
|
||||
p.drawRoundedRect(QRect(shift.x() + size - w - d * 2, shift.y() + size - f->height, w + d * 2, f->height), r, r);
|
||||
p.setFont(f->f);
|
||||
|
||||
p.setPen(color->p);
|
||||
|
||||
p.drawText(shift.x() + size - w - d, shift.y() + size - f->height + f->ascent, cnt);
|
||||
|
||||
}
|
||||
|
||||
QImage MainWindow::iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) {
|
||||
bool layer = false;
|
||||
if (size < 0) {
|
||||
size = -size;
|
||||
layer = true;
|
||||
}
|
||||
if (layer) {
|
||||
if (size != 16 && size != 20 && size != 24) size = 32;
|
||||
|
||||
// platform/linux/main_window_linux depends on count used the same
|
||||
// way for all the same (count % 1000) values.
|
||||
QString cnt = (count < 1000) ? QString("%1").arg(count) : QString("..%1").arg(count % 100, 2, 10, QChar('0'));
|
||||
QImage result(size, size, QImage::Format_ARGB32);
|
||||
int32 cntSize = cnt.size();
|
||||
result.fill(Qt::transparent);
|
||||
{
|
||||
QPainter p(&result);
|
||||
p.setBrush(bg);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
int32 fontSize;
|
||||
if (size == 16) {
|
||||
fontSize = (cntSize < 2) ? 11 : ((cntSize < 3) ? 11 : 8);
|
||||
} else if (size == 20) {
|
||||
fontSize = (cntSize < 2) ? 14 : ((cntSize < 3) ? 13 : 10);
|
||||
} else if (size == 24) {
|
||||
fontSize = (cntSize < 2) ? 17 : ((cntSize < 3) ? 16 : 12);
|
||||
} else {
|
||||
fontSize = (cntSize < 2) ? 22 : ((cntSize < 3) ? 20 : 16);
|
||||
}
|
||||
style::font f = { fontSize, 0, 0 };
|
||||
int32 w = f->width(cnt), d, r;
|
||||
if (size == 16) {
|
||||
d = (cntSize < 2) ? 5 : ((cntSize < 3) ? 2 : 1);
|
||||
r = (cntSize < 2) ? 8 : ((cntSize < 3) ? 7 : 3);
|
||||
} else if (size == 20) {
|
||||
d = (cntSize < 2) ? 6 : ((cntSize < 3) ? 2 : 1);
|
||||
r = (cntSize < 2) ? 10 : ((cntSize < 3) ? 9 : 5);
|
||||
} else if (size == 24) {
|
||||
d = (cntSize < 2) ? 7 : ((cntSize < 3) ? 3 : 1);
|
||||
r = (cntSize < 2) ? 12 : ((cntSize < 3) ? 11 : 6);
|
||||
} else {
|
||||
d = (cntSize < 2) ? 9 : ((cntSize < 3) ? 4 : 2);
|
||||
r = (cntSize < 2) ? 16 : ((cntSize < 3) ? 14 : 8);
|
||||
}
|
||||
p.drawRoundedRect(QRect(size - w - d * 2, size - f->height, w + d * 2, f->height), r, r);
|
||||
p.setFont(f);
|
||||
|
||||
p.setPen(fg);
|
||||
|
||||
p.drawText(size - w - d, size - f->height + f->ascent, cnt);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
if (size != 16 && size != 32) size = 64;
|
||||
}
|
||||
|
||||
QImage img(smallIcon ? ((size == 16) ? iconbig16 : (size == 32 ? iconbig32 : iconbig64)) : ((size == 16) ? icon16 : (size == 32 ? icon32 : icon64)));
|
||||
if (const auto controller = sessionController()) {
|
||||
if (controller->session().supportMode()) {
|
||||
Window::ConvertIconToBlack(img);
|
||||
}
|
||||
}
|
||||
if (!count) return img;
|
||||
|
||||
if (smallIcon) {
|
||||
placeSmallCounter(img, size, count, bg, QPoint(), fg);
|
||||
} else {
|
||||
QPainter p(&img);
|
||||
p.drawPixmap(
|
||||
size / 2,
|
||||
size / 2,
|
||||
Ui::PixmapFromImage(
|
||||
iconWithCounter(-size / 2, count, bg, fg, false)));
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
void MainWindow::sendPaths() {
|
||||
if (controller().locked()) {
|
||||
return;
|
||||
|
||||
@@ -71,9 +71,6 @@ public:
|
||||
|
||||
void sendPaths();
|
||||
|
||||
QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) override;
|
||||
void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) override;
|
||||
|
||||
bool contentOverlapped(const QRect &globalRect);
|
||||
bool contentOverlapped(QWidget *w, QPaintEvent *e) {
|
||||
return contentOverlapped(QRect(w->mapToGlobal(e->rect().topLeft()), e->rect().size()));
|
||||
@@ -148,8 +145,6 @@ private:
|
||||
|
||||
std::unique_ptr<Media::SystemMediaControlsManager> _mediaControlsManager;
|
||||
|
||||
QImage icon16, icon32, icon64, iconbig16, iconbig32, iconbig64;
|
||||
|
||||
crl::time _lastTrayClickTime = 0;
|
||||
QPoint _lastMousePosition;
|
||||
bool _activeForTrayIconAction = true;
|
||||
|
||||
@@ -305,7 +305,7 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentImageBack = Core::App().logo();
|
||||
currentImageBack = Window::Logo();
|
||||
}
|
||||
|
||||
if (dprSize(currentImageBack) != desiredSize) {
|
||||
@@ -324,20 +324,19 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
: st::trayCounterBg;
|
||||
const auto &fg = st::trayCounterFg;
|
||||
if (iconSize >= 22) {
|
||||
auto layerSize = -16;
|
||||
if (iconSize >= 48) {
|
||||
layerSize = -32;
|
||||
} else if (iconSize >= 36) {
|
||||
layerSize = -24;
|
||||
} else if (iconSize >= 32) {
|
||||
layerSize = -20;
|
||||
}
|
||||
const auto layer = App::wnd()->iconWithCounter(
|
||||
layerSize,
|
||||
counter,
|
||||
bg,
|
||||
fg,
|
||||
false);
|
||||
const auto layerSize = (iconSize >= 48)
|
||||
? 32
|
||||
: (iconSize >= 36)
|
||||
? 24
|
||||
: (iconSize >= 32)
|
||||
? 20
|
||||
: 16;
|
||||
const auto layer = Window::GenerateCounterLayer({
|
||||
.size = layerSize,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
|
||||
QPainter p(&iconImage);
|
||||
p.drawImage(
|
||||
@@ -345,13 +344,12 @@ QIcon TrayIconGen(int counter, bool muted) {
|
||||
iconImage.height() - layer.height() - 1,
|
||||
layer);
|
||||
} else {
|
||||
App::wnd()->placeSmallCounter(
|
||||
iconImage,
|
||||
16,
|
||||
counter,
|
||||
bg,
|
||||
QPoint(),
|
||||
fg);
|
||||
iconImage = Window::WithSmallCounter(std::move(iconImage), {
|
||||
.size = 16,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,6 @@ class MainWindow : public Window::MainWindow {
|
||||
public:
|
||||
explicit MainWindow(not_null<Window::Controller*> controller);
|
||||
|
||||
virtual QImage iconWithCounter(
|
||||
int size,
|
||||
int count,
|
||||
style::color bg,
|
||||
style::color fg,
|
||||
bool smallIcon) = 0;
|
||||
|
||||
void psShowTrayMenu();
|
||||
|
||||
bool trayAvailable() {
|
||||
@@ -54,14 +47,6 @@ protected:
|
||||
void psTrayMenuUpdated();
|
||||
void psSetupTrayIcon();
|
||||
|
||||
virtual void placeSmallCounter(
|
||||
QImage &img,
|
||||
int size,
|
||||
int count,
|
||||
style::color bg,
|
||||
const QPoint &shift,
|
||||
style::color color) = 0;
|
||||
|
||||
private:
|
||||
class Private;
|
||||
friend class Private;
|
||||
|
||||
@@ -25,8 +25,6 @@ public:
|
||||
|
||||
bool psFilterNativeEvent(void *event);
|
||||
|
||||
virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0;
|
||||
|
||||
int getCustomTitleHeight() const {
|
||||
return _customTitleHeight;
|
||||
}
|
||||
@@ -77,7 +75,6 @@ protected:
|
||||
|
||||
void psTrayMenuUpdated();
|
||||
void psSetupTrayIcon();
|
||||
virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0;
|
||||
|
||||
void closeWithoutDestroy() override;
|
||||
void createGlobalMenu() override;
|
||||
|
||||
@@ -13,7 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/win/windows_dlls.h"
|
||||
#include "platform/win/windows_event_filter.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "mainwindow.h"
|
||||
#include "main/main_session.h"
|
||||
#include "base/crc32hash.h"
|
||||
#include "base/platform/win/base_windows_wrl.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
@@ -59,47 +61,77 @@ constexpr auto kKeepActiveForTrayIcon = crl::time(500);
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
HICON createHIconFromQIcon(const QIcon &icon, int xSize, int ySize) {
|
||||
[[nodiscard]] HICON NativeIcon(const QIcon &icon, QSize size) {
|
||||
if (!icon.isNull()) {
|
||||
const QPixmap pm = icon.pixmap(icon.actualSize(QSize(xSize, ySize)));
|
||||
if (!pm.isNull()) {
|
||||
return qt_pixmapToWinHICON(pm);
|
||||
const auto pixmap = icon.pixmap(icon.actualSize(size));
|
||||
if (!pixmap.isNull()) {
|
||||
return qt_pixmapToWinHICON(pixmap);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HWND createTaskbarHider() {
|
||||
HINSTANCE appinst = (HINSTANCE)GetModuleHandle(0);
|
||||
HWND hWnd = 0;
|
||||
[[nodiscard]] QImage IconWithCounter(
|
||||
Window::CounterLayerArgs &&args,
|
||||
Main::Session *session,
|
||||
bool smallIcon) {
|
||||
static constexpr auto kCount = 3;
|
||||
static auto ScaledLogo = std::array<QImage, kCount>();
|
||||
static auto ScaledLogoNoMargin = std::array<QImage, kCount>();
|
||||
|
||||
QString cn = QString("TelegramTaskbarHider");
|
||||
LPCWSTR _cn = (LPCWSTR)cn.utf16();
|
||||
WNDCLASSEX wc;
|
||||
struct Dimensions {
|
||||
int index = 0;
|
||||
int size = 0;
|
||||
};
|
||||
const auto d = [&]() -> Dimensions {
|
||||
switch (args.size) {
|
||||
case 16:
|
||||
return {
|
||||
.index = 0,
|
||||
.size = 16,
|
||||
};
|
||||
case 32:
|
||||
return {
|
||||
.index = 1,
|
||||
.size = 32,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
.index = 2,
|
||||
.size = 64,
|
||||
};
|
||||
}
|
||||
}();
|
||||
Assert(d.index < kCount);
|
||||
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = 0;
|
||||
wc.lpfnWndProc = DefWindowProc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = appinst;
|
||||
wc.hIcon = 0;
|
||||
wc.hCursor = 0;
|
||||
wc.hbrBackground = 0;
|
||||
wc.lpszMenuName = NULL;
|
||||
wc.lpszClassName = _cn;
|
||||
wc.hIconSm = 0;
|
||||
if (!RegisterClassEx(&wc)) {
|
||||
DEBUG_LOG(("Application Error: could not register taskbar hider window class, error: %1").arg(GetLastError()));
|
||||
return hWnd;
|
||||
auto &scaled = smallIcon ? ScaledLogoNoMargin : ScaledLogo;
|
||||
auto result = [&] {
|
||||
auto &image = scaled[d.index];
|
||||
if (image.isNull()) {
|
||||
image = (smallIcon
|
||||
? Window::LogoNoMargin()
|
||||
: Window::Logo()).scaledToWidth(
|
||||
d.size,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
return image;
|
||||
}();
|
||||
if (session && session->supportMode()) {
|
||||
Window::ConvertIconToBlack(result);
|
||||
}
|
||||
|
||||
hWnd = CreateWindowEx(WS_EX_TOOLWINDOW, _cn, 0, WS_POPUP, 0, 0, 0, 0, 0, 0, appinst, 0);
|
||||
if (!hWnd) {
|
||||
DEBUG_LOG(("Application Error: could not create taskbar hider window class, error: %1").arg(GetLastError()));
|
||||
return hWnd;
|
||||
if (!args.count) {
|
||||
return result;
|
||||
} else if (smallIcon) {
|
||||
return Window::WithSmallCounter(std::move(result), std::move(args));
|
||||
}
|
||||
return hWnd;
|
||||
QPainter p(&result);
|
||||
const auto half = d.size / 2;
|
||||
args.size = half;
|
||||
p.drawPixmap(
|
||||
half,
|
||||
half,
|
||||
Ui::PixmapFromImage(Window::GenerateCounterLayer(std::move(args))));
|
||||
return result;
|
||||
}
|
||||
|
||||
ComPtr<ITaskbarList3> taskbarList;
|
||||
@@ -202,7 +234,8 @@ void MainWindow::psSetupTrayIcon() {
|
||||
if (!trayIcon) {
|
||||
trayIcon = new QSystemTrayIcon(this);
|
||||
|
||||
auto icon = QIcon(Ui::PixmapFromImage(Core::App().logoNoMargin()));
|
||||
const auto icon = QIcon(Ui::PixmapFromImage(
|
||||
QImage(Window::LogoNoMargin())));
|
||||
|
||||
trayIcon->setIcon(icon);
|
||||
connect(
|
||||
@@ -327,46 +360,70 @@ void MainWindow::unreadCounterChangedHook() {
|
||||
void MainWindow::updateIconCounters() {
|
||||
const auto counter = Core::App().unreadBadge();
|
||||
const auto muted = Core::App().unreadBadgeMuted();
|
||||
const auto controller = sessionController();
|
||||
const auto session = controller ? &controller->session() : nullptr;
|
||||
|
||||
auto iconSizeSmall = QSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
|
||||
auto iconSizeBig = QSize(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
|
||||
const auto iconSizeSmall = QSize(
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
GetSystemMetrics(SM_CYSMICON));
|
||||
const auto iconSizeBig = QSize(
|
||||
GetSystemMetrics(SM_CXICON),
|
||||
GetSystemMetrics(SM_CYICON));
|
||||
|
||||
auto &bg = (muted ? st::trayCounterBgMute : st::trayCounterBg);
|
||||
auto &fg = st::trayCounterFg;
|
||||
auto iconSmallPixmap16 = Ui::PixmapFromImage(
|
||||
iconWithCounter(16, counter, bg, fg, true));
|
||||
auto iconSmallPixmap32 = Ui::PixmapFromImage(
|
||||
iconWithCounter(32, counter, bg, fg, true));
|
||||
const auto &bg = muted ? st::trayCounterBgMute : st::trayCounterBg;
|
||||
const auto &fg = st::trayCounterFg;
|
||||
const auto counterArgs = [&](int size, int counter) {
|
||||
return Window::CounterLayerArgs{
|
||||
.size = size,
|
||||
.count = counter,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
};
|
||||
};
|
||||
const auto iconWithCounter = [&](int size, int counter, bool smallIcon) {
|
||||
return Ui::PixmapFromImage(IconWithCounter(
|
||||
counterArgs(size, counter),
|
||||
session,
|
||||
smallIcon));
|
||||
};
|
||||
|
||||
auto iconSmallPixmap16 = iconWithCounter(16, counter, true);
|
||||
auto iconSmallPixmap32 = iconWithCounter(32, counter, true);
|
||||
QIcon iconSmall, iconBig;
|
||||
iconSmall.addPixmap(iconSmallPixmap16);
|
||||
iconSmall.addPixmap(iconSmallPixmap32);
|
||||
iconBig.addPixmap(Ui::PixmapFromImage(
|
||||
iconWithCounter(32, taskbarList.Get() ? 0 : counter, bg, fg, false)));
|
||||
iconBig.addPixmap(Ui::PixmapFromImage(
|
||||
iconWithCounter(64, taskbarList.Get() ? 0 : counter, bg, fg, false)));
|
||||
const auto bigCounter = taskbarList.Get() ? 0 : counter;
|
||||
iconBig.addPixmap(iconWithCounter(32, bigCounter, false));
|
||||
iconBig.addPixmap(iconWithCounter(64, bigCounter, false));
|
||||
if (trayIcon) {
|
||||
// Force Qt to use right icon size, not the larger one.
|
||||
QIcon forTrayIcon;
|
||||
forTrayIcon.addPixmap(iconSizeSmall.width() >= 20 ? iconSmallPixmap32 : iconSmallPixmap16);
|
||||
forTrayIcon.addPixmap(iconSizeSmall.width() >= 20
|
||||
? iconSmallPixmap32
|
||||
: iconSmallPixmap16);
|
||||
trayIcon->setIcon(forTrayIcon);
|
||||
}
|
||||
|
||||
psDestroyIcons();
|
||||
ps_iconSmall = createHIconFromQIcon(iconSmall, iconSizeSmall.width(), iconSizeSmall.height());
|
||||
ps_iconBig = createHIconFromQIcon(iconBig, iconSizeBig.width(), iconSizeBig.height());
|
||||
SendMessage(ps_hWnd, WM_SETICON, 0, (LPARAM)ps_iconSmall);
|
||||
SendMessage(ps_hWnd, WM_SETICON, 1, (LPARAM)(ps_iconBig ? ps_iconBig : ps_iconSmall));
|
||||
if (taskbarList.Get()) {
|
||||
ps_iconSmall = NativeIcon(iconSmall, iconSizeSmall);
|
||||
ps_iconBig = NativeIcon(iconBig, iconSizeBig);
|
||||
SendMessage(ps_hWnd, WM_SETICON, ICON_SMALL, (LPARAM)ps_iconSmall);
|
||||
SendMessage(ps_hWnd, WM_SETICON, ICON_BIG, (LPARAM)(ps_iconBig ? ps_iconBig : ps_iconSmall));
|
||||
if (taskbarList) {
|
||||
if (counter > 0) {
|
||||
const auto pixmap = [&](int size) {
|
||||
return Ui::PixmapFromImage(Window::GenerateCounterLayer(
|
||||
counterArgs(size, counter)));
|
||||
};
|
||||
QIcon iconOverlay;
|
||||
iconOverlay.addPixmap(Ui::PixmapFromImage(
|
||||
iconWithCounter(-16, counter, bg, fg, false)));
|
||||
iconOverlay.addPixmap(Ui::PixmapFromImage(
|
||||
iconWithCounter(-32, counter, bg, fg, false)));
|
||||
ps_iconOverlay = createHIconFromQIcon(iconOverlay, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
|
||||
iconOverlay.addPixmap(pixmap(16));
|
||||
iconOverlay.addPixmap(pixmap(32));
|
||||
ps_iconOverlay = NativeIcon(iconOverlay, iconSizeSmall);
|
||||
}
|
||||
auto description = (counter > 0) ? tr::lng_unread_bar(tr::now, lt_count, counter) : QString();
|
||||
taskbarList->SetOverlayIcon(ps_hWnd, ps_iconOverlay, description.toStdWString().c_str());
|
||||
const auto description = (counter > 0)
|
||||
? tr::lng_unread_bar(tr::now, lt_count, counter).toStdWString()
|
||||
: std::wstring();
|
||||
taskbarList->SetOverlayIcon(ps_hWnd, ps_iconOverlay, description.c_str());
|
||||
}
|
||||
SetWindowPos(ps_hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ public:
|
||||
|
||||
void psRefreshTaskbarIcon();
|
||||
|
||||
virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0;
|
||||
|
||||
[[nodiscard]] static uint32 TaskbarCreatedMsgId();
|
||||
static void TaskbarCreated();
|
||||
|
||||
@@ -59,7 +57,6 @@ protected:
|
||||
|
||||
void psTrayMenuUpdated();
|
||||
void psSetupTrayIcon();
|
||||
virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0;
|
||||
|
||||
void showTrayTooltip() override;
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ void NotificationsCount::prepareNotificationSampleSmall() {
|
||||
void NotificationsCount::prepareNotificationSampleUserpic() {
|
||||
if (_notificationSampleUserpic.isNull()) {
|
||||
_notificationSampleUserpic = Ui::PixmapFromImage(
|
||||
Core::App().logoNoMargin().scaled(
|
||||
Window::LogoNoMargin().scaled(
|
||||
st::notifyPhotoSize * cIntRetinaFactor(),
|
||||
st::notifyPhotoSize * cIntRetinaFactor(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
|
||||
@@ -57,6 +57,7 @@ void EditInviteLinkBox(
|
||||
|
||||
const auto link = data.link;
|
||||
const auto isGroup = data.isGroup;
|
||||
const auto isPublic = data.isPublic;
|
||||
box->setTitle(link.isEmpty()
|
||||
? tr::lng_group_invite_new_title()
|
||||
: tr::lng_group_invite_edit_title());
|
||||
@@ -64,14 +65,14 @@ void EditInviteLinkBox(
|
||||
const auto container = box->verticalLayout();
|
||||
const auto addTitle = [&](
|
||||
not_null<VerticalLayout*> container,
|
||||
rpl::producer<QString> text,
|
||||
style::margins margins = style::margins()) {
|
||||
rpl::producer<QString> text) {
|
||||
container->add(
|
||||
object_ptr<FlatLabel>(
|
||||
container,
|
||||
std::move(text),
|
||||
st::settingsSubsectionTitle),
|
||||
st::settingsSubsectionTitlePadding + margins);
|
||||
(st::settingsSubsectionTitlePadding
|
||||
+ style::margins(0, st::settingsSectionSkip, 0, 0)));
|
||||
};
|
||||
const auto addDivider = [&](
|
||||
not_null<VerticalLayout*> container,
|
||||
@@ -105,25 +106,43 @@ void EditInviteLinkBox(
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.expireValue = expire,
|
||||
.usageValue = usage,
|
||||
.requestApproval = data.requestApproval,
|
||||
.requestApproval = (data.requestApproval && !isPublic),
|
||||
});
|
||||
|
||||
const auto requestApproval = container->add(
|
||||
object_ptr<SettingsButton>(
|
||||
const auto requestApproval = isPublic
|
||||
? nullptr
|
||||
: container->add(
|
||||
object_ptr<SettingsButton>(
|
||||
container,
|
||||
tr::lng_group_invite_request_approve(),
|
||||
st::settingsButton),
|
||||
style::margins{ 0, 0, 0, st::settingsSectionSkip });
|
||||
if (requestApproval) {
|
||||
requestApproval->toggleOn(state->requestApproval.value());
|
||||
state->requestApproval = requestApproval->toggledValue();
|
||||
addDivider(container, rpl::conditional(
|
||||
state->requestApproval.value(),
|
||||
(isGroup
|
||||
? tr::lng_group_invite_about_approve()
|
||||
: tr::lng_group_invite_about_approve_channel()),
|
||||
(isGroup
|
||||
? tr::lng_group_invite_about_no_approve()
|
||||
: tr::lng_group_invite_about_no_approve_channel())));
|
||||
}
|
||||
|
||||
const auto labelField = container->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
tr::lng_group_invite_request_approve(),
|
||||
st::settingsButton),
|
||||
style::margins{ 0, 0, 0, st::settingsSectionSkip });
|
||||
requestApproval->toggleOn(state->requestApproval.value());
|
||||
state->requestApproval = requestApproval->toggledValue();
|
||||
addDivider(container, rpl::conditional(
|
||||
state->requestApproval.value(),
|
||||
(isGroup
|
||||
? tr::lng_group_invite_about_approve()
|
||||
: tr::lng_group_invite_about_approve_channel()),
|
||||
(isGroup
|
||||
? tr::lng_group_invite_about_no_approve()
|
||||
: tr::lng_group_invite_about_no_approve_channel())));
|
||||
st::defaultInputField,
|
||||
tr::lng_group_invite_label_header(),
|
||||
data.label),
|
||||
style::margins(
|
||||
st::settingsSubsectionTitlePadding.left(),
|
||||
st::settingsSectionSkip,
|
||||
st::settingsSubsectionTitlePadding.right(),
|
||||
st::settingsSectionSkip * 2));
|
||||
labelField->setMaxLength(kMaxLabelLength);
|
||||
addDivider(container, tr::lng_group_invite_label_about());
|
||||
|
||||
addTitle(container, tr::lng_group_invite_expire_title());
|
||||
const auto expiresWrap = container->add(
|
||||
@@ -138,10 +157,7 @@ void EditInviteLinkBox(
|
||||
container,
|
||||
object_ptr<VerticalLayout>(container)));
|
||||
const auto usagesInner = usagesSlide->entity();
|
||||
addTitle(
|
||||
usagesInner,
|
||||
tr::lng_group_invite_usage_title(),
|
||||
style::margins(0, st::settingsSectionSkip, 0, 0));
|
||||
addTitle(usagesInner, tr::lng_group_invite_usage_title());
|
||||
const auto usagesWrap = usagesInner->add(
|
||||
object_ptr<VerticalLayout>(usagesInner),
|
||||
style::margins(0, 0, 0, st::settingsSectionSkip));
|
||||
@@ -297,20 +313,6 @@ void EditInviteLinkBox(
|
||||
|
||||
regenerate();
|
||||
|
||||
const auto labelField = container->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
st::defaultInputField,
|
||||
tr::lng_group_invite_label_header(),
|
||||
data.label),
|
||||
style::margins(
|
||||
st::settingsSubsectionTitlePadding.left(),
|
||||
st::settingsSectionSkip,
|
||||
st::settingsSubsectionTitlePadding.right(),
|
||||
st::settingsSectionSkip * 2));
|
||||
labelField->setMaxLength(kMaxLabelLength);
|
||||
addDivider(container, tr::lng_group_invite_label_about());
|
||||
|
||||
usagesSlide->toggleOn(state->requestApproval.value() | rpl::map(!_1));
|
||||
usagesSlide->finishAnimating();
|
||||
|
||||
@@ -334,6 +336,7 @@ void EditInviteLinkBox(
|
||||
.usageLimit = usageLimit,
|
||||
.requestApproval = state->requestApproval.current(),
|
||||
.isGroup = isGroup,
|
||||
.isPublic = isPublic,
|
||||
});
|
||||
});
|
||||
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
||||
@@ -342,10 +345,11 @@ void EditInviteLinkBox(
|
||||
void CreateInviteLinkBox(
|
||||
not_null<GenericBox*> box,
|
||||
bool isGroup,
|
||||
bool isPublic,
|
||||
Fn<void(InviteLinkFields)> done) {
|
||||
EditInviteLinkBox(
|
||||
box,
|
||||
InviteLinkFields{ .isGroup = isGroup },
|
||||
InviteLinkFields{ .isGroup = isGroup, .isPublic = isPublic },
|
||||
std::move(done));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ struct InviteLinkFields {
|
||||
int usageLimit = 0;
|
||||
bool requestApproval = false;
|
||||
bool isGroup = false;
|
||||
bool isPublic = false;
|
||||
};
|
||||
|
||||
void EditInviteLinkBox(
|
||||
@@ -28,6 +29,7 @@ void EditInviteLinkBox(
|
||||
void CreateInviteLinkBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
bool isGroup,
|
||||
bool isPublic,
|
||||
Fn<void(InviteLinkFields)> done);
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
@@ -49,12 +49,14 @@ constexpr auto kSaveWindowPositionTimeout = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
QImage LoadLogo() {
|
||||
return QImage(qsl(":/gui/art/logo_256.png"));
|
||||
const QImage &Logo() {
|
||||
static const auto result = QImage(u":/gui/art/logo_256.png"_q);
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage LoadLogoNoMargin() {
|
||||
return QImage(qsl(":/gui/art/logo_256_no_margin.png"));
|
||||
const QImage &LogoNoMargin() {
|
||||
static const auto result = QImage(u":/gui/art/logo_256_no_margin.png"_q);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConvertIconToBlack(QImage &image) {
|
||||
@@ -105,7 +107,7 @@ void ConvertIconToBlack(QImage &image) {
|
||||
}
|
||||
|
||||
QIcon CreateOfficialIcon(Main::Session *session) {
|
||||
auto image = Core::IsAppLaunched() ? Core::App().logo() : LoadLogo();
|
||||
auto image = Logo();
|
||||
if (session && session->supportMode()) {
|
||||
ConvertIconToBlack(image);
|
||||
}
|
||||
@@ -156,6 +158,144 @@ QIcon CreateIcon(Main::Session *session) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage GenerateCounterLayer(CounterLayerArgs &&args) {
|
||||
// platform/linux/main_window_linux depends on count used the same
|
||||
// way for all the same (count % 1000) values.
|
||||
const auto count = args.count.value();
|
||||
const auto text = (count < 1000)
|
||||
? QString::number(count)
|
||||
: u"..%1"_q.arg(count % 100, 2, 10, QChar('0'));
|
||||
const auto textSize = text.size();
|
||||
|
||||
struct Dimensions {
|
||||
int size = 0;
|
||||
int font = 0;
|
||||
int delta = 0;
|
||||
int radius = 0;
|
||||
};
|
||||
const auto d = [&]() -> Dimensions {
|
||||
switch (args.size.value()) {
|
||||
case 16:
|
||||
return {
|
||||
.size = 16,
|
||||
.font = ((textSize < 2) ? 11 : (textSize < 3) ? 11 : 8),
|
||||
.delta = ((textSize < 2) ? 5 : (textSize < 3) ? 2 : 1),
|
||||
.radius = ((textSize < 2) ? 8 : (textSize < 3) ? 7 : 3),
|
||||
};
|
||||
case 20:
|
||||
return {
|
||||
.size = 20,
|
||||
.font = ((textSize < 2) ? 14 : (textSize < 3) ? 13 : 10),
|
||||
.delta = ((textSize < 2) ? 6 : (textSize < 3) ? 2 : 1),
|
||||
.radius = ((textSize < 2) ? 10 : (textSize < 3) ? 9 : 5),
|
||||
};
|
||||
case 24:
|
||||
return {
|
||||
.size = 24,
|
||||
.font = ((textSize < 2) ? 17 : (textSize < 3) ? 16 : 12),
|
||||
.delta = ((textSize < 2) ? 7 : (textSize < 3) ? 3 : 1),
|
||||
.radius = ((textSize < 2) ? 12 : (textSize < 3) ? 11 : 6),
|
||||
};
|
||||
default:
|
||||
return {
|
||||
.size = 32,
|
||||
.font = ((textSize < 2) ? 22 : (textSize < 3) ? 20 : 16),
|
||||
.delta = ((textSize < 2) ? 9 : (textSize < 3) ? 4 : 2),
|
||||
.radius = ((textSize < 2) ? 16 : (textSize < 3) ? 14 : 8),
|
||||
};
|
||||
}
|
||||
}();
|
||||
|
||||
auto result = QImage(d.size, d.size, QImage::Format_ARGB32);
|
||||
result.fill(Qt::transparent);
|
||||
|
||||
auto p = QPainter(&result);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto f = style::font{ d.font, 0, 0 };
|
||||
const auto w = f->width(text);
|
||||
|
||||
p.setBrush(args.bg.value());
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(
|
||||
QRect(
|
||||
d.size - w - d.delta * 2,
|
||||
d.size - f->height,
|
||||
w + d.delta * 2,
|
||||
f->height),
|
||||
d.radius,
|
||||
d.radius);
|
||||
|
||||
p.setFont(f);
|
||||
p.setPen(args.fg.value());
|
||||
p.drawText(d.size - w - d.delta, d.size - f->height + f->ascent, text);
|
||||
p.end();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QImage WithSmallCounter(QImage image, CounterLayerArgs &&args) {
|
||||
const auto count = args.count.value();
|
||||
const auto text = (count < 100)
|
||||
? QString::number(count)
|
||||
: QString("..%1").arg(count % 10, 1, 10, QChar('0'));
|
||||
const auto textSize = text.size();
|
||||
|
||||
struct Dimensions {
|
||||
int size = 0;
|
||||
int font = 0;
|
||||
int delta = 0;
|
||||
int radius = 0;
|
||||
};
|
||||
const auto d = [&]() -> Dimensions {
|
||||
switch (args.size.value()) {
|
||||
case 16:
|
||||
return {
|
||||
.size = 16,
|
||||
.font = 8,
|
||||
.delta = ((textSize < 2) ? 2 : 1),
|
||||
.radius = ((textSize < 2) ? 4 : 3),
|
||||
};
|
||||
case 32:
|
||||
return {
|
||||
.size = 32,
|
||||
.font = 12,
|
||||
.delta = ((textSize < 2) ? 5 : 2),
|
||||
.radius = ((textSize < 2) ? 8 : 7),
|
||||
};
|
||||
default:
|
||||
return {
|
||||
.size = 64,
|
||||
.font = 22,
|
||||
.delta = ((textSize < 2) ? 9 : 4),
|
||||
.radius = ((textSize < 2) ? 16 : 14),
|
||||
};
|
||||
}
|
||||
}();
|
||||
|
||||
auto p = QPainter(&image);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto f = style::font{ d.font, 0, 0 };
|
||||
const auto w = f->width(text);
|
||||
|
||||
p.setBrush(args.bg.value());
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawRoundedRect(
|
||||
QRect(
|
||||
d.size - w - d.delta * 2,
|
||||
d.size - f->height,
|
||||
w + d.delta * 2,
|
||||
f->height),
|
||||
d.radius,
|
||||
d.radius);
|
||||
|
||||
p.setFont(f);
|
||||
p.setPen(args.fg.value());
|
||||
p.drawText(d.size - w - d.delta, d.size - f->height + f->ascent, text);
|
||||
p.end();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(not_null<Controller*> controller)
|
||||
: _controller(controller)
|
||||
, _positionUpdatedTimer([=] { savePosition(); })
|
||||
|
||||
@@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/timer.h"
|
||||
#include "base/object_ptr.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "base/required.h"
|
||||
|
||||
#include <QtWidgets/QSystemTrayIcon>
|
||||
|
||||
@@ -35,11 +36,21 @@ class SessionController;
|
||||
class TitleWidget;
|
||||
struct TermsLock;
|
||||
|
||||
QImage LoadLogo();
|
||||
QImage LoadLogoNoMargin();
|
||||
QIcon CreateIcon(Main::Session *session = nullptr);
|
||||
[[nodiscard]] const QImage &Logo();
|
||||
[[nodiscard]] const QImage &LogoNoMargin();
|
||||
[[nodiscard]] QIcon CreateIcon(Main::Session *session = nullptr);
|
||||
void ConvertIconToBlack(QImage &image);
|
||||
|
||||
struct CounterLayerArgs {
|
||||
base::required<int> size = 16;
|
||||
base::required<int> count = 1;
|
||||
base::required<style::color> bg;
|
||||
base::required<style::color> fg;
|
||||
};
|
||||
|
||||
[[nodiscard]] QImage GenerateCounterLayer(CounterLayerArgs &&args);
|
||||
[[nodiscard]] QImage WithSmallCounter(QImage image, CounterLayerArgs &&args);
|
||||
|
||||
class MainWindow : public Ui::RpWindow {
|
||||
public:
|
||||
explicit MainWindow(not_null<Controller*> controller);
|
||||
|
||||
@@ -87,7 +87,7 @@ Manager::QueuedNotification::QueuedNotification(
|
||||
QPixmap Manager::hiddenUserpicPlaceholder() const {
|
||||
if (_hiddenUserpicPlaceholder.isNull()) {
|
||||
_hiddenUserpicPlaceholder = Ui::PixmapFromImage(
|
||||
Core::App().logoNoMargin().scaled(
|
||||
LogoNoMargin().scaled(
|
||||
st::notifyPhotoSize,
|
||||
st::notifyPhotoSize,
|
||||
Qt::IgnoreAspectRatio,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "window/notifications_utilities.h"
|
||||
|
||||
#include "window/main_window.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "base/random.h"
|
||||
#include "core/application.h"
|
||||
@@ -79,7 +80,7 @@ QString CachedUserpics::get(
|
||||
peer->saveUserpic(view, v.path, st::notifyMacPhotoSize);
|
||||
}
|
||||
} else {
|
||||
Core::App().logoNoMargin().save(v.path, "PNG");
|
||||
LogoNoMargin().save(v.path, "PNG");
|
||||
}
|
||||
i = _images.insert(key, v);
|
||||
_someSavedFlag = true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 3001013
|
||||
AppVersionStrMajor 3.1
|
||||
AppVersionStrSmall 3.1.13
|
||||
AppVersionStr 3.1.13
|
||||
BetaChannel 1
|
||||
AppVersion 3002000
|
||||
AppVersionStrMajor 3.2
|
||||
AppVersionStrSmall 3.2
|
||||
AppVersionStr 3.2.0
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 3.1.13.beta
|
||||
AppVersionOriginal 3.2
|
||||
|
||||
Submodule Telegram/lib_base updated: d4021e77e9...58e38e5a0a
Submodule Telegram/lib_ui updated: 53e39e37eb...4907cf35d1
@@ -1,3 +1,10 @@
|
||||
3.2 (03.11.21)
|
||||
|
||||
- Create special invite links that require admins to approve users before they become members.
|
||||
- Admins can view the applicants' profiles and bios by tapping the Join Requests bar at the top of the chat.
|
||||
- Add internal labels to your chat's Invite Links to keep them organized.
|
||||
- More Interactive Emoji - :ghost:, :dislike:, :face_vomiting:, :joy:, :money_with_wings: or :jack_o_lantern:
|
||||
|
||||
3.1.13 beta (02.11.21)
|
||||
|
||||
- Fix requests to groups / channels processing.
|
||||
|
||||
Reference in New Issue
Block a user