Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21b10cebe0 | ||
|
|
50435f7783 | ||
|
|
1b789de4f4 | ||
|
|
a50310f0c1 | ||
|
|
eb02a7861a | ||
|
|
8759ca4577 | ||
|
|
d5c6d9a231 | ||
|
|
63f179e93e | ||
|
|
cfcc1b1ce7 | ||
|
|
da1945d0ca | ||
|
|
12252ef1aa | ||
|
|
1eef94e8d9 | ||
|
|
0984e631fa | ||
|
|
ec064a904d | ||
|
|
b47692e920 | ||
|
|
132f127f3f | ||
|
|
5c44b851fe | ||
|
|
2f5bed2899 | ||
|
|
cf76933352 | ||
|
|
eaa4c5e5b1 | ||
|
|
a4b5b6e370 | ||
|
|
c1be1ca4ae | ||
|
|
b2df781b76 | ||
|
|
38815c1ca8 | ||
|
|
2ec92f541c | ||
|
|
7ce8b42216 | ||
|
|
17511749de | ||
|
|
4f6c7657bf | ||
|
|
54085c70a4 | ||
|
|
e6c4b96c54 | ||
|
|
b75221737a | ||
|
|
c336d725ea | ||
|
|
d0fcc40d25 | ||
|
|
422bfd973b | ||
|
|
d4db679ce8 | ||
|
|
2c7d8858c0 | ||
|
|
155bbed3f4 | ||
|
|
b1517c68fb | ||
|
|
d206ba7e1d | ||
|
|
af100c2d13 | ||
|
|
1f25777929 | ||
|
|
a566405598 | ||
|
|
b02967a44e | ||
|
|
e0135e509d | ||
|
|
8274fddcbc | ||
|
|
82c45871c7 | ||
|
|
2164caaab7 | ||
|
|
f4b162cbaf | ||
|
|
4bc4584868 | ||
|
|
890a126423 | ||
|
|
42cc24e167 |
15
.github/workflows/win.yml
vendored
@@ -259,12 +259,17 @@ jobs:
|
||||
- name: Opus.
|
||||
if: steps.cache-opus.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone %GIT%/telegramdesktop/opus.git
|
||||
git clone -b v1.3.1 %GIT%/xiph/opus.git
|
||||
cd opus
|
||||
git checkout tdesktop
|
||||
cd win32\VS2015
|
||||
msbuild -m opus.sln /property:Configuration=Debug /property:Platform="Win32"
|
||||
msbuild -m opus.sln /property:Configuration=Release /property:Platform="Win32"
|
||||
git cherry-pick 927de8453c
|
||||
cmake -B out . ^
|
||||
-A Win32 ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LibrariesPath%/local/opus ^
|
||||
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
|
||||
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
|
||||
cmake --build out --config Debug
|
||||
cmake --build out --config Release
|
||||
cmake --install out --config Release
|
||||
|
||||
- name: Rnnoise.
|
||||
run: |
|
||||
|
||||
@@ -43,6 +43,8 @@ include(cmake/generate_appdata_changelog.cmake)
|
||||
if (WIN32)
|
||||
include(cmake/generate_midl.cmake)
|
||||
generate_midl(Telegram ${src_loc}/platform/win/windows_quiethours.idl)
|
||||
|
||||
nuget_add_winrt(Telegram)
|
||||
endif()
|
||||
|
||||
set_target_properties(Telegram PROPERTIES AUTOMOC ON AUTORCC ON)
|
||||
|
||||
BIN
Telegram/Resources/day-custom-base.tdesktop-theme
Normal file
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 453 B |
|
Before Width: | Height: | Size: 637 B After Width: | Height: | Size: 973 B |
|
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 651 B After Width: | Height: | Size: 710 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1618,7 +1618,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_user_action_upload_file" = "{user} is sending a file";
|
||||
"lng_send_action_choose_sticker" = "choosing a sticker";
|
||||
"lng_user_action_choose_sticker" = "{user} is choosing a sticker";
|
||||
"lng_user_action_watching_animations" = "watching {emoji} animations";
|
||||
"lng_user_action_watching_animations" = "watching {emoji}";
|
||||
"lng_unread_bar#one" = "{count} unread message";
|
||||
"lng_unread_bar#other" = "{count} unread messages";
|
||||
"lng_unread_bar_some" = "Unread messages";
|
||||
@@ -2239,7 +2239,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_group_call_recording_start_checkbox" = "Also record video";
|
||||
"lng_group_call_recording_start_audio_subtitle" = "This chat will be recorded into an audio file";
|
||||
"lng_group_call_recording_start_video_subtitle" = "Choose video orientation";
|
||||
"lng_group_call_is_recorded" = "Voice chat is being recorded.";
|
||||
"lng_group_call_is_recorded" = "The audio stream is being recorded.";
|
||||
"lng_group_call_is_recorded_video" = "The video stream is being recorded.";
|
||||
"lng_group_call_is_recorded_channel" = "Live stream is being recorded.";
|
||||
"lng_group_call_can_speak_here" = "You can now speak.";
|
||||
"lng_group_call_can_speak" = "You can now speak in {chat}.";
|
||||
|
||||
BIN
Telegram/Resources/night-custom-base.tdesktop-theme
Normal file
@@ -60,6 +60,8 @@
|
||||
<file alias="day-blue.tdesktop-theme">../../day-blue.tdesktop-theme</file>
|
||||
<file alias="night.tdesktop-theme">../../night.tdesktop-theme</file>
|
||||
<file alias="night-green.tdesktop-theme">../../night-green.tdesktop-theme</file>
|
||||
<file alias="day-custom-base.tdesktop-theme">../../day-custom-base.tdesktop-theme</file>
|
||||
<file alias="night-custom-base.tdesktop-theme">../../night-custom-base.tdesktop-theme</file>
|
||||
<file alias="icons/calls/hands.lottie">../../icons/calls/hands.lottie</file>
|
||||
<file alias="icons/calls/voice.lottie">../../icons/calls/voice.lottie</file>
|
||||
<file alias="recording/info_audio.svg">../../art/recording/recording_info_audio.svg</file>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="3.0.5.0" />
|
||||
Version="3.1.3.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,0,5,0
|
||||
PRODUCTVERSION 3,0,5,0
|
||||
FILEVERSION 3,1,3,0
|
||||
PRODUCTVERSION 3,1,3,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.0.5.0"
|
||||
VALUE "FileVersion", "3.1.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.5.0"
|
||||
VALUE "ProductVersion", "3.1.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3,0,5,0
|
||||
PRODUCTVERSION 3,0,5,0
|
||||
FILEVERSION 3,1,3,0
|
||||
PRODUCTVERSION 3,1,3,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.0.5.0"
|
||||
VALUE "FileVersion", "3.1.3.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2021"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "3.0.5.0"
|
||||
VALUE "ProductVersion", "3.1.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -549,9 +549,6 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
block.takeWidget(),
|
||||
QMargins(0, _inner->count() ? st::sendMediaRowSkip : 0, 0, 0));
|
||||
|
||||
const auto preventDelete =
|
||||
widget->lifetime().make_state<rpl::event_stream<int>>();
|
||||
|
||||
block.itemDeleteRequest(
|
||||
) | rpl::filter([=] {
|
||||
return !_removingIndex;
|
||||
@@ -562,9 +559,9 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
if (index < 0 || index >= _list.files.size()) {
|
||||
return;
|
||||
}
|
||||
// Prevent item delete if it is the only one.
|
||||
// Just close the box if it is the only one.
|
||||
if (_list.files.size() == 1) {
|
||||
preventDelete->fire_copy(0);
|
||||
closeBox();
|
||||
return;
|
||||
}
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
@@ -572,9 +569,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
});
|
||||
}, widget->lifetime());
|
||||
|
||||
rpl::merge(
|
||||
block.itemReplaceRequest(),
|
||||
preventDelete->events()
|
||||
block.itemReplaceRequest(
|
||||
) | rpl::start_with_next([=](int index) {
|
||||
const auto replace = [=](Ui::PreparedList list) {
|
||||
if (list.files.empty()) {
|
||||
|
||||
@@ -1000,6 +1000,8 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
|
||||
_recordingMark->setClickedCallback([=] {
|
||||
showToast({ (livestream
|
||||
? tr::lng_group_call_is_recorded_channel
|
||||
: real->recordVideo()
|
||||
? tr::lng_group_call_is_recorded_video
|
||||
: tr::lng_group_call_is_recorded)(tr::now) });
|
||||
});
|
||||
const auto animate = [=] {
|
||||
|
||||
@@ -117,7 +117,8 @@ void EmojiInteractions::startOutgoing(
|
||||
if (!IsServerMsgId(item->id) || !item->history()->peer->isUser()) {
|
||||
return;
|
||||
}
|
||||
const auto emoji = chooseInteractionEmoji(item);
|
||||
const auto emoticon = item->originalText().text;
|
||||
const auto emoji = chooseInteractionEmoji(emoticon);
|
||||
if (!emoji) {
|
||||
return;
|
||||
}
|
||||
@@ -145,6 +146,7 @@ void EmojiInteractions::startOutgoing(
|
||||
media->checkStickerLarge();
|
||||
const auto now = crl::now();
|
||||
animations.push_back({
|
||||
.emoticon = emoticon,
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
@@ -195,6 +197,7 @@ void EmojiInteractions::startIncoming(
|
||||
const auto media = document->createMediaView();
|
||||
media->checkStickerLarge();
|
||||
animations.push_back({
|
||||
.emoticon = emoticon,
|
||||
.emoji = emoji,
|
||||
.document = document,
|
||||
.media = media,
|
||||
@@ -219,7 +222,7 @@ void EmojiInteractions::seenOutgoing(
|
||||
if (const auto j = i->second.find(emoji); j != end(i->second)) {
|
||||
const auto last = j->second.lastDoneReceivedAt;
|
||||
if (!last || last + kAcceptSeenSinceRequest > crl::now()) {
|
||||
_seen.fire({ peer, emoji });
|
||||
_seen.fire({ peer, emoticon });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +266,7 @@ auto EmojiInteractions::checkAnimations(
|
||||
} else if (!lastStartedAt || lastStartedAt + kMinDelay <= now) {
|
||||
animation.startedAt = now;
|
||||
_playRequests.fire({
|
||||
animation.emoji->text(),
|
||||
animation.emoticon,
|
||||
item,
|
||||
animation.media,
|
||||
animation.scheduledAt,
|
||||
@@ -316,7 +319,7 @@ void EmojiInteractions::sendAccumulatedOutgoing(
|
||||
peer->input,
|
||||
MTPint(), // top_msg_id
|
||||
MTP_sendMessageEmojiInteraction(
|
||||
MTP_string(emoji->text()),
|
||||
MTP_string(from->emoticon),
|
||||
MTP_int(item->id),
|
||||
MTP_dataJSON(MTP_bytes(ToJson(bunch))))
|
||||
)).done([=](const MTPBool &result, mtpRequestId requestId) {
|
||||
|
||||
@@ -28,7 +28,7 @@ class Element;
|
||||
namespace ChatHelpers {
|
||||
|
||||
struct EmojiInteractionPlayRequest {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
not_null<HistoryItem*> item;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time shouldHaveStartedAt = 0;
|
||||
@@ -45,7 +45,7 @@ struct EmojiInteractionsBunch {
|
||||
|
||||
struct EmojiInteractionSeen {
|
||||
not_null<PeerData*> peer;
|
||||
not_null<EmojiPtr> emoji;
|
||||
QString emoticon;
|
||||
};
|
||||
|
||||
class EmojiInteractions final {
|
||||
@@ -78,6 +78,7 @@ public:
|
||||
|
||||
private:
|
||||
struct Animation {
|
||||
QString emoticon;
|
||||
not_null<EmojiPtr> emoji;
|
||||
not_null<DocumentData*> document;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
|
||||
@@ -60,12 +60,11 @@ auto LottieFromDocument(
|
||||
Method &&method,
|
||||
not_null<Data::DocumentMedia*> media,
|
||||
uint8 keyShift,
|
||||
QSize box,
|
||||
int cacheAreaLimit) {
|
||||
QSize box) {
|
||||
const auto document = media->owner();
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
if (box.width() * box.height() > cacheAreaLimit) {
|
||||
if (box.width() * box.height() > kDontCacheLottieAfterArea) {
|
||||
// Don't use frame caching for large stickers.
|
||||
return method(
|
||||
Lottie::ReadContent(data, filepath),
|
||||
@@ -114,12 +113,9 @@ std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
replacements,
|
||||
std::move(renderer));
|
||||
};
|
||||
const auto limit = (sizeTag == StickerLottieSize::EmojiInteraction)
|
||||
? (3 * kDontCacheLottieAfterArea)
|
||||
: kDontCacheLottieAfterArea;
|
||||
const auto tag = replacements ? replacements->tag : uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0) | (uint8(sizeTag) & 0x0F);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box, limit);
|
||||
return LottieFromDocument(method, media, uint8(keyShift), box);
|
||||
}
|
||||
|
||||
not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
@@ -130,8 +126,7 @@ not_null<Lottie::Animation*> LottieAnimationFromDocument(
|
||||
const auto method = [&](auto &&...args) {
|
||||
return player->append(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
const auto limit = kDontCacheLottieAfterArea;
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box, limit);
|
||||
return LottieFromDocument(method, media, uint8(sizeTag), box);
|
||||
}
|
||||
|
||||
bool HasLottieThumbnail(
|
||||
|
||||
@@ -45,6 +45,9 @@ enum class StickerLottieSize : uchar {
|
||||
SetsListThumbnail,
|
||||
InlineResults,
|
||||
EmojiInteraction,
|
||||
EmojiInteractionReserved1,
|
||||
EmojiInteractionReserved2,
|
||||
EmojiInteractionReserved3,
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> LottiePlayerFromDocument(
|
||||
|
||||
@@ -58,6 +58,16 @@ std::map<int, const char*> BetaLogs() {
|
||||
{
|
||||
3000005,
|
||||
"- Add support for Emoji 13.1."
|
||||
},
|
||||
{
|
||||
3001002,
|
||||
"- Control video in fullscreen mode using arrows and numbers.\n"
|
||||
|
||||
"- Open locations in browser if default Bing Maps is not installed.\n"
|
||||
|
||||
"- Reconnect without timeout when network availability changes.\n"
|
||||
|
||||
"- Crash fixes."
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/themes/window_theme_editor_box.h" // GenerateSlug.
|
||||
#include "settings/settings_common.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -469,6 +470,100 @@ bool ShowInviteLink(
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExportTestChatTheme(
|
||||
not_null<Main::Session*> session,
|
||||
not_null<const Data::CloudTheme*> theme) {
|
||||
if (!theme->paper
|
||||
|| !theme->paper->isPattern()
|
||||
|| theme->paper->backgroundColors().empty()
|
||||
|| !theme->accentColor
|
||||
|| !theme->paper->hasShareUrl()) {
|
||||
Ui::Toast::Show("Something went wrong :(");
|
||||
return;
|
||||
}
|
||||
const auto &bg = theme->paper->backgroundColors();
|
||||
const auto url = theme->paper->shareUrl(session);
|
||||
const auto from = url.indexOf("bg/");
|
||||
const auto till = url.indexOf("?");
|
||||
if (from < 0 || till <= from) {
|
||||
Ui::Toast::Show("Bad WallPaper link: " + url);
|
||||
return;
|
||||
}
|
||||
|
||||
using Flag = MTPaccount_CreateTheme::Flag;
|
||||
using Setting = MTPDinputThemeSettings::Flag;
|
||||
using Paper = MTPDwallPaperSettings::Flag;
|
||||
const auto color = [](const QColor &color) {
|
||||
const auto red = color.red();
|
||||
const auto green = color.green();
|
||||
const auto blue = color.blue();
|
||||
return int(((uint32(red) & 0xFFU) << 16)
|
||||
| ((uint32(green) & 0xFFU) << 8)
|
||||
| (uint32(blue) & 0xFFU));
|
||||
};
|
||||
const auto colors = [&](const std::vector<QColor> &colors) {
|
||||
auto result = QVector<MTPint>();
|
||||
result.reserve(colors.size());
|
||||
for (const auto &single : colors) {
|
||||
result.push_back(MTP_int(color(single)));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto slug = url.mid(from + 3, till - from - 3);
|
||||
const auto flags = Flag::f_settings;
|
||||
const auto settings = Setting::f_wallpaper
|
||||
| Setting::f_wallpaper_settings
|
||||
| (theme->outgoingAccentColor
|
||||
? Setting::f_outbox_accent_color
|
||||
: Setting(0))
|
||||
| (!theme->outgoingMessagesColors.empty()
|
||||
? Setting::f_message_colors
|
||||
: Setting(0));
|
||||
const auto papers = Paper::f_background_color
|
||||
| Paper::f_intensity
|
||||
| (bg.size() > 1
|
||||
? Paper::f_second_background_color
|
||||
: Paper(0))
|
||||
| (bg.size() > 2
|
||||
? Paper::f_third_background_color
|
||||
: Paper(0))
|
||||
| (bg.size() > 3
|
||||
? Paper::f_fourth_background_color
|
||||
: Paper(0));
|
||||
session->api().request(MTPaccount_CreateTheme(
|
||||
MTP_flags(flags),
|
||||
MTP_string(Window::Theme::GenerateSlug()),
|
||||
MTP_string(theme->title + " Desktop"),
|
||||
MTPInputDocument(),
|
||||
MTP_inputThemeSettings(
|
||||
MTP_flags(settings),
|
||||
(theme->basedOnDark
|
||||
? MTP_baseThemeTinted()
|
||||
: MTP_baseThemeClassic()),
|
||||
MTP_int(color(theme->accentColor.value_or(Qt::black))),
|
||||
MTP_int(color(theme->outgoingAccentColor.value_or(
|
||||
Qt::black))),
|
||||
MTP_vector<MTPint>(colors(
|
||||
theme->outgoingMessagesColors)),
|
||||
MTP_inputWallPaperSlug(MTP_string(slug)),
|
||||
MTP_wallPaperSettings(
|
||||
MTP_flags(papers),
|
||||
MTP_int(color(bg[0])),
|
||||
MTP_int(color(bg.size() > 1 ? bg[1] : Qt::black)),
|
||||
MTP_int(color(bg.size() > 2 ? bg[2] : Qt::black)),
|
||||
MTP_int(color(bg.size() > 3 ? bg[3] : Qt::black)),
|
||||
MTP_int(theme->paper->patternIntensity()),
|
||||
MTP_int(0)))
|
||||
)).done([=](const MTPTheme &result) {
|
||||
const auto slug = Data::CloudTheme::Parse(session, result, true).slug;
|
||||
QGuiApplication::clipboard()->setText(
|
||||
session->createInternalLinkFull("addtheme/" + slug));
|
||||
Ui::Toast::Show(tr::lng_background_link_copied(tr::now));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
Ui::Toast::Show("Error: " + error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool ResolveTestChatTheme(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
@@ -485,6 +580,9 @@ bool ResolveTestChatTheme(
|
||||
history->peer->themeEmoji(),
|
||||
params);
|
||||
if (theme) {
|
||||
if (!params["export"].isEmpty()) {
|
||||
ExportTestChatTheme(&controller->session(), &*theme);
|
||||
}
|
||||
[[maybe_unused]] auto value = controller->cachedChatThemeValue(
|
||||
*theme);
|
||||
}
|
||||
|
||||
@@ -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 = 3000005;
|
||||
constexpr auto AppVersionStr = "3.0.5";
|
||||
constexpr auto AppVersion = 3001003;
|
||||
constexpr auto AppVersionStr = "3.1.3";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -433,7 +433,7 @@ void CloudThemes::SetTestingColors(bool testing) {
|
||||
IsTestingColors = testing;
|
||||
}
|
||||
|
||||
QString CloudThemes::PrepareTestingLink(const CloudTheme &theme) {
|
||||
QString CloudThemes::prepareTestingLink(const CloudTheme &theme) const {
|
||||
const auto hex = [](int value) {
|
||||
return QChar((value < 10) ? ('0' + value) : ('a' + (value - 10)));
|
||||
};
|
||||
@@ -460,6 +460,16 @@ QString CloudThemes::PrepareTestingLink(const CloudTheme &theme) {
|
||||
if (theme.paper && !theme.paper->backgroundColors().empty()) {
|
||||
arguments.push_back("bg=" + colors(theme.paper->backgroundColors()));
|
||||
}
|
||||
if (theme.paper/* && theme.paper->hasShareUrl()*/) {
|
||||
arguments.push_back("intensity="
|
||||
+ QString::number(theme.paper->patternIntensity()));
|
||||
//const auto url = theme.paper->shareUrl(_session);
|
||||
//const auto from = url.indexOf("bg/");
|
||||
//const auto till = url.indexOf("?");
|
||||
//if (from > 0 && till > from) {
|
||||
// arguments.push_back("slug=" + url.mid(from + 3, till - from - 3));
|
||||
//}
|
||||
}
|
||||
if (theme.outgoingAccentColor) {
|
||||
arguments.push_back("out_accent" + color(*theme.outgoingAccentColor));
|
||||
}
|
||||
@@ -525,7 +535,11 @@ std::optional<CloudTheme> CloudThemes::updateThemeFromLink(
|
||||
const auto bg = colors(params["bg"]);
|
||||
applyTo.paper = (applyTo.paper && !bg.empty())
|
||||
? std::make_optional(applyTo.paper->withBackgroundColors(bg))
|
||||
: std::nullopt;
|
||||
: applyTo.paper;
|
||||
applyTo.paper = (applyTo.paper && params["intensity"].toInt())
|
||||
? std::make_optional(
|
||||
applyTo.paper->withPatternIntensity(params["intensity"].toInt()))
|
||||
: applyTo.paper;
|
||||
applyTo.outgoingAccentColor = color(params["out_accent"]);
|
||||
applyTo.outgoingMessagesColors = colors(params["out_bg"]);
|
||||
_chatThemesUpdates.fire({});
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
|
||||
[[nodiscard]] static bool TestingColors();
|
||||
static void SetTestingColors(bool testing);
|
||||
[[nodiscard]] static QString PrepareTestingLink(const CloudTheme &theme);
|
||||
[[nodiscard]] QString prepareTestingLink(const CloudTheme &theme) const;
|
||||
[[nodiscard]] std::optional<CloudTheme> updateThemeFromLink(
|
||||
const QString &emoji,
|
||||
const QMap<QString, QString> ¶ms);
|
||||
|
||||
@@ -341,6 +341,9 @@ enum class MessageFlag : uint32 {
|
||||
|
||||
// Fake message for some UI element.
|
||||
FakeHistoryItem = (1U << 27),
|
||||
|
||||
// Contact sign-up message, notification should be skipped for Silent.
|
||||
IsContactSignUp = (1U << 28),
|
||||
};
|
||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||
using MessageFlags = base::flags<MessageFlag>;
|
||||
|
||||
@@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Editor {
|
||||
|
||||
QImage ImageModified(QImage image, const PhotoModifications &mods) {
|
||||
Expects(!image.isNull());
|
||||
|
||||
if (!mods) {
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,8 @@ HistoryInner::HistoryInner(
|
||||
using PlayRequest = ChatHelpers::EmojiInteractionPlayRequest;
|
||||
_controller->emojiInteractions().playRequests(
|
||||
) | rpl::filter([=](const PlayRequest &request) {
|
||||
return (request.item->history() == _history);
|
||||
return (request.item->history() == _history)
|
||||
&& _controller->widget()->isActive();
|
||||
}) | rpl::start_with_next([=](PlayRequest &&request) {
|
||||
if (const auto view = request.item->mainView()) {
|
||||
_emojiInteractions->play(std::move(request), view);
|
||||
|
||||
@@ -459,6 +459,17 @@ bool HistoryItem::isScheduled() const {
|
||||
&& (_flags & MessageFlag::IsOrWasScheduled);
|
||||
}
|
||||
|
||||
bool HistoryItem::skipNotification() const {
|
||||
if (isSilent() && (_flags & MessageFlag::IsContactSignUp)) {
|
||||
return true;
|
||||
} else if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||
if (forwarded->imported) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryItem::destroy() {
|
||||
_history->destroyMessage(this);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ public:
|
||||
[[nodiscard]] bool isAdminLogEntry() const;
|
||||
[[nodiscard]] bool isFromScheduled() const;
|
||||
[[nodiscard]] bool isScheduled() const;
|
||||
[[nodiscard]] bool skipNotification() const;
|
||||
|
||||
void addLogEntryOriginal(
|
||||
WebPageId localId,
|
||||
|
||||
@@ -554,6 +554,8 @@ void HistoryService::applyAction(const MTPMessageAction &action) {
|
||||
_flags |= MessageFlag::IsGroupEssential;
|
||||
}, [&](const MTPDmessageActionChannelMigrateFrom &) {
|
||||
_flags |= MessageFlag::IsGroupEssential;
|
||||
}, [&](const MTPDmessageActionContactSignUp &) {
|
||||
_flags |= MessageFlag::IsContactSignUp;
|
||||
}, [](const auto &) {
|
||||
});
|
||||
}
|
||||
|
||||
@@ -688,7 +688,8 @@ HistoryWidget::HistoryWidget(
|
||||
auto text = QStringList();
|
||||
const auto push = [&](QString label, const auto &theme) {
|
||||
using namespace Data;
|
||||
const auto l = CloudThemes::PrepareTestingLink(theme);
|
||||
const auto &themes = _peer->owner().cloudThemes();
|
||||
const auto l = themes.prepareTestingLink(theme);
|
||||
if (!l.isEmpty()) {
|
||||
text.push_back(label + ": " + l);
|
||||
}
|
||||
@@ -1410,15 +1411,16 @@ bool HistoryWidget::updateStickersByEmoji() {
|
||||
}
|
||||
|
||||
void HistoryWidget::fieldChanged() {
|
||||
const auto typing = (_history
|
||||
&& !_inlineBot
|
||||
&& !_editMsgId
|
||||
&& (_textUpdateEvents & TextUpdateEvent::SendTyping));
|
||||
const auto updateTyping = (_textUpdateEvents & TextUpdateEvent::SendTyping);
|
||||
|
||||
InvokeQueued(this, [=] {
|
||||
updateInlineBotQuery();
|
||||
const auto choosingSticker = updateStickersByEmoji();
|
||||
if (!choosingSticker && typing) {
|
||||
if (_history
|
||||
&& !_inlineBot
|
||||
&& !_editMsgId
|
||||
&& !choosingSticker
|
||||
&& updateTyping) {
|
||||
session().sendProgressManager().update(
|
||||
_history,
|
||||
Api::SendProgressType::Typing);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSizeMultiplier = 3;
|
||||
|
||||
constexpr auto kCachesCount = 4;
|
||||
constexpr auto kMaxPlays = 5;
|
||||
constexpr auto kMaxPlaysWithSmallDelay = 3;
|
||||
constexpr auto kSmallDelay = crl::time(200);
|
||||
@@ -59,16 +59,19 @@ EmojiInteractions::~EmojiInteractions() = default;
|
||||
void EmojiInteractions::play(
|
||||
ChatHelpers::EmojiInteractionPlayRequest request,
|
||||
not_null<Element*> view) {
|
||||
if (_plays.empty()) {
|
||||
if (!view->media()) {
|
||||
// Large emoji may be disabled.
|
||||
return;
|
||||
} else if (_plays.empty()) {
|
||||
play(
|
||||
std::move(request.emoji),
|
||||
std::move(request.emoticon),
|
||||
view,
|
||||
std::move(request.media),
|
||||
request.incoming);
|
||||
} else {
|
||||
const auto now = crl::now();
|
||||
_delayed.push_back({
|
||||
request.emoji,
|
||||
request.emoticon,
|
||||
view,
|
||||
std::move(request.media),
|
||||
now,
|
||||
@@ -79,7 +82,7 @@ void EmojiInteractions::play(
|
||||
}
|
||||
|
||||
void EmojiInteractions::play(
|
||||
QString emoji,
|
||||
QString emoticon,
|
||||
not_null<Element*> view,
|
||||
std::shared_ptr<Data::DocumentMedia> media,
|
||||
bool incoming) {
|
||||
@@ -90,12 +93,9 @@ void EmojiInteractions::play(
|
||||
|| _visibleTop == _visibleBottom) {
|
||||
return;
|
||||
}
|
||||
auto lottie = ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::EmojiInteraction,
|
||||
_emojiSize * kSizeMultiplier * style::DevicePixelRatio(),
|
||||
Lottie::Quality::High);
|
||||
|
||||
auto lottie = preparePlayer(media.get());
|
||||
|
||||
const auto shift = GenerateRandomShift(_emojiSize);
|
||||
lottie->updates(
|
||||
) | rpl::start_with_next([=](Lottie::Update update) {
|
||||
@@ -114,13 +114,61 @@ void EmojiInteractions::play(
|
||||
.shift = shift,
|
||||
});
|
||||
if (incoming) {
|
||||
_playStarted.fire(std::move(emoji));
|
||||
_playStarted.fire(std::move(emoticon));
|
||||
}
|
||||
if (const auto media = view->media()) {
|
||||
media->stickerClearLoopPlayed();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Lottie::SinglePlayer> EmojiInteractions::preparePlayer(
|
||||
not_null<Data::DocumentMedia*> media) {
|
||||
// Shortened copy from stickers_lottie module.
|
||||
const auto document = media->owner();
|
||||
const auto baseKey = document->bigFileBaseCacheKey();
|
||||
const auto tag = uint8(0);
|
||||
const auto keyShift = ((tag << 4) & 0xF0)
|
||||
| (uint8(ChatHelpers::StickerLottieSize::EmojiInteraction) & 0x0F);
|
||||
const auto key = Storage::Cache::Key{
|
||||
baseKey.high,
|
||||
baseKey.low + keyShift
|
||||
};
|
||||
const auto get = [=](int i, FnMut<void(QByteArray &&cached)> handler) {
|
||||
document->owner().cacheBigFile().get(
|
||||
{ key.high, key.low + i },
|
||||
std::move(handler));
|
||||
};
|
||||
const auto weak = base::make_weak(&document->session());
|
||||
const auto put = [=](int i, QByteArray &&cached) {
|
||||
crl::on_main(weak, [=, data = std::move(cached)]() mutable {
|
||||
weak->data().cacheBigFile().put(
|
||||
{ key.high, key.low + i },
|
||||
std::move(data));
|
||||
});
|
||||
};
|
||||
const auto data = media->bytes();
|
||||
const auto filepath = document->filepath();
|
||||
const auto request = Lottie::FrameRequest{
|
||||
_emojiSize * kSizeMultiplier * style::DevicePixelRatio(),
|
||||
};
|
||||
auto &weakProvider = _sharedProviders[document];
|
||||
auto shared = [&] {
|
||||
if (const auto result = weakProvider.lock()) {
|
||||
return result;
|
||||
}
|
||||
const auto result = Lottie::SinglePlayer::SharedProvider(
|
||||
kCachesCount,
|
||||
get,
|
||||
put,
|
||||
Lottie::ReadContent(data, filepath),
|
||||
request,
|
||||
Lottie::Quality::High);
|
||||
weakProvider = result;
|
||||
return result;
|
||||
}();
|
||||
return std::make_unique<Lottie::SinglePlayer>(std::move(shared), request);
|
||||
}
|
||||
|
||||
void EmojiInteractions::visibleAreaUpdated(
|
||||
int visibleTop,
|
||||
int visibleBottom) {
|
||||
@@ -209,8 +257,11 @@ void EmojiInteractions::checkDelayed() {
|
||||
}
|
||||
auto good = std::move(*i);
|
||||
_delayed.erase(begin(_delayed), i + 1);
|
||||
const auto incoming = good.incoming;
|
||||
play(std::move(good.emoji), good.view, std::move(good.media), incoming);
|
||||
play(
|
||||
std::move(good.emoticon),
|
||||
good.view,
|
||||
std::move(good.media),
|
||||
good.incoming);
|
||||
}
|
||||
|
||||
rpl::producer<QRect> EmojiInteractions::updateRequests() const {
|
||||
|
||||
@@ -17,6 +17,7 @@ struct EmojiInteractionPlayRequest;
|
||||
|
||||
namespace Lottie {
|
||||
class SinglePlayer;
|
||||
class FrameProvider;
|
||||
} // namespace Lottie
|
||||
|
||||
namespace Main {
|
||||
@@ -52,7 +53,7 @@ private:
|
||||
bool finished = false;
|
||||
};
|
||||
struct Delayed {
|
||||
QString emoji;
|
||||
QString emoticon;
|
||||
not_null<Element*> view;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
crl::time shouldHaveStartedAt = 0;
|
||||
@@ -62,12 +63,15 @@ private:
|
||||
[[nodiscard]] QRect computeRect(not_null<Element*> view) const;
|
||||
|
||||
void play(
|
||||
QString emoji,
|
||||
QString emoticon,
|
||||
not_null<Element*> view,
|
||||
std::shared_ptr<Data::DocumentMedia> media,
|
||||
bool incoming);
|
||||
void checkDelayed();
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Lottie::SinglePlayer> preparePlayer(
|
||||
not_null<Data::DocumentMedia*> media);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
int _visibleTop = 0;
|
||||
@@ -78,6 +82,9 @@ private:
|
||||
std::vector<Delayed> _delayed;
|
||||
rpl::event_stream<QRect> _updateRequests;
|
||||
rpl::event_stream<QString> _playStarted;
|
||||
base::flat_map<
|
||||
not_null<DocumentData*>,
|
||||
std::weak_ptr<Lottie::FrameProvider>> _sharedProviders;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
||||
@@ -1421,6 +1421,7 @@ QPixmap RepliesWidget::grabForShowAnimation(const Window::SectionSlideParams &pa
|
||||
_composeControls->showForGrab();
|
||||
auto result = Ui::GrabWidget(this);
|
||||
if (params.withTopBarShadow) _topBarShadow->show();
|
||||
_rootView->hide();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1537,7 +1538,7 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
|
||||
? (areComments
|
||||
? tr::lng_comments_header
|
||||
: tr::lng_replies_header)(
|
||||
lt_count,
|
||||
lt_count_decimal,
|
||||
rpl::single(count) | tr::to_count())
|
||||
: (areComments
|
||||
? tr::lng_comments_header_none
|
||||
@@ -1706,6 +1707,7 @@ void RepliesWidget::showAnimatedHook(
|
||||
void RepliesWidget::showFinishedHook() {
|
||||
_topBar->setAnimatingMode(false);
|
||||
_composeControls->showFinished();
|
||||
_rootView->show();
|
||||
|
||||
// We should setup the drag area only after
|
||||
// the section animation is finished,
|
||||
|
||||
@@ -667,7 +667,7 @@ void TopBarWidget::setActiveChat(
|
||||
) | rpl::filter([=](const InteractionSeen &seen) {
|
||||
return (seen.peer == history->peer);
|
||||
}) | rpl::start_with_next([=](const InteractionSeen &seen) {
|
||||
handleEmojiInteractionSeen(seen.emoji->text());
|
||||
handleEmojiInteractionSeen(seen.emoticon);
|
||||
}, lifetime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1207,31 +1207,67 @@ void Poll::paintFilling(
|
||||
|
||||
top += st::historyPollAnswerPadding.top();
|
||||
|
||||
PainterHighQualityEnabler hq(p);
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto thickness = st::historyPollFillingHeight;
|
||||
const auto max = awidth - st::historyPollFillingRight;
|
||||
const auto size = anim::interpolate(st::historyPollFillingMin, max, filling);
|
||||
const auto radius = st::historyPollFillingRadius;
|
||||
const auto ftop = bottom - st::historyPollFillingBottom - thickness;
|
||||
|
||||
if (chosen && !correct) {
|
||||
p.setBrush(st->boxTextFgError());
|
||||
} else if (chosen && correct && _poll->quiz() && !context.outbg) {
|
||||
p.setBrush(st->boxTextFgGood());
|
||||
} else {
|
||||
p.setBrush(stm->msgWaveformActive);
|
||||
}
|
||||
enum class Style {
|
||||
Incorrect,
|
||||
Correct,
|
||||
Default,
|
||||
};
|
||||
const auto style = [&] {
|
||||
if (chosen && !correct) {
|
||||
return Style::Incorrect;
|
||||
} else if (chosen && correct && _poll->quiz() && !context.outbg) {
|
||||
return Style::Correct;
|
||||
} else {
|
||||
return Style::Default;
|
||||
}
|
||||
}();
|
||||
auto barleft = aleft;
|
||||
auto barwidth = size;
|
||||
const auto &color = (style == Style::Incorrect)
|
||||
? st->boxTextFgError()
|
||||
: (style == Style::Correct)
|
||||
? st->boxTextFgGood()
|
||||
: stm->msgFileBg;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(color);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
if (chosen || correct) {
|
||||
const auto &icon = (chosen && !correct)
|
||||
const auto &icon = (style == Style::Incorrect)
|
||||
? st->historyPollChoiceWrong()
|
||||
: st->historyPollChoiceRight();
|
||||
: (style == Style::Correct)
|
||||
? st->historyPollChoiceRight()
|
||||
: stm->historyPollChoiceRight;
|
||||
const auto cleft = aleft - st::historyPollPercentSkip - icon.width();
|
||||
const auto ctop = ftop - (icon.height() - thickness) / 2;
|
||||
p.drawEllipse(cleft, ctop, icon.width(), icon.height());
|
||||
icon.paint(p, cleft, ctop, width);
|
||||
|
||||
const auto paintContent = [&](Painter &p) {
|
||||
icon.paint(p, cleft, ctop, width);
|
||||
};
|
||||
if (style == Style::Default && usesBubblePattern(context)) {
|
||||
const auto add = st::lineWidth * 2;
|
||||
const auto target = QRect(
|
||||
cleft,
|
||||
ctop,
|
||||
icon.width(),
|
||||
icon.height()
|
||||
).marginsAdded({ add, add, add, add });
|
||||
Ui::PaintPatternBubblePart(
|
||||
p,
|
||||
context.viewport,
|
||||
context.bubblesPattern->pixmap,
|
||||
target,
|
||||
paintContent,
|
||||
_fillingIconCache);
|
||||
} else {
|
||||
paintContent(p);
|
||||
}
|
||||
//barleft += icon.width() - radius;
|
||||
//barwidth -= icon.width() - radius;
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ private:
|
||||
Ui::Animations::Simple _wrongAnswerAnimation;
|
||||
mutable QPoint _lastLinkPoint;
|
||||
mutable QImage _userpicCircleCache;
|
||||
mutable QImage _fillingIconCache;
|
||||
|
||||
mutable std::unique_ptr<CloseInformation> _close;
|
||||
|
||||
|
||||
@@ -779,7 +779,7 @@ void MainWindow::toggleDisplayNotifyFromTray() {
|
||||
settings.setRememberedFlashBounceNotifyFromTray(false);
|
||||
}
|
||||
}
|
||||
account().session().saveSettings();
|
||||
Core::App().saveSettingsDelayed();
|
||||
using Change = Window::Notifications::ChangeType;
|
||||
auto ¬ifications = Core::App().notifications();
|
||||
notifications.notifySettingsChanged(Change::DesktopEnabled);
|
||||
|
||||
@@ -94,6 +94,7 @@ constexpr auto kPreloadCount = 3;
|
||||
constexpr auto kMaxZoomLevel = 7; // x8
|
||||
constexpr auto kZoomToScreenLevel = 1024;
|
||||
constexpr auto kOverlayLoaderPriority = 2;
|
||||
constexpr auto kSeekTimeMs = 5 * crl::time(1000);
|
||||
|
||||
// macOS OpenGL renderer fails to render larger texture
|
||||
// even though it reports that max texture size is 16384.
|
||||
@@ -3002,6 +3003,23 @@ void OverlayWidget::playbackPauseResume() {
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::seekRelativeTime(crl::time time) {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
const auto newTime = std::clamp(
|
||||
_streamed->instance.info().video.state.position + time,
|
||||
crl::time(0),
|
||||
_streamed->instance.info().video.state.duration);
|
||||
restartAtSeekPosition(newTime);
|
||||
}
|
||||
|
||||
void OverlayWidget::restartAtProgress(float64 progress) {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
restartAtSeekPosition(_streamed->instance.info().video.state.duration
|
||||
* std::clamp(progress, 0., 1.));
|
||||
}
|
||||
|
||||
void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
@@ -3737,7 +3755,21 @@ void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
} else if (_fullScreenVideo) {
|
||||
if (key == Qt::Key_Escape) {
|
||||
playbackToggleFullScreen();
|
||||
} else if (key == Qt::Key_0) {
|
||||
activateControls();
|
||||
restartAtSeekPosition(0);
|
||||
} else if (key >= Qt::Key_1 && key <= Qt::Key_9) {
|
||||
activateControls();
|
||||
const auto index = int(key - Qt::Key_0);
|
||||
restartAtProgress(index / 10.0);
|
||||
} else if (key == Qt::Key_Left) {
|
||||
activateControls();
|
||||
seekRelativeTime(-kSeekTimeMs);
|
||||
} else if (key == Qt::Key_Right) {
|
||||
activateControls();
|
||||
seekRelativeTime(kSeekTimeMs);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4598,7 +4630,7 @@ void OverlayWidget::clearBeforeHide() {
|
||||
_groupThumbs = nullptr;
|
||||
_groupThumbsRect = QRect();
|
||||
for (const auto child : _widget->children()) {
|
||||
if (child->isWidgetType()) {
|
||||
if (child->isWidgetType() && _hideWorkaround.get() != child) {
|
||||
static_cast<QWidget*>(child)->hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,8 @@ private:
|
||||
void setZoomLevel(int newZoom, bool force = false);
|
||||
|
||||
void updatePlaybackState();
|
||||
void seekRelativeTime(crl::time time);
|
||||
void restartAtProgress(float64 progress);
|
||||
void restartAtSeekPosition(crl::time position);
|
||||
|
||||
void refreshClipControllerGeometry();
|
||||
|
||||
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "platform/win/windows_app_user_model_id.h"
|
||||
#include "platform/win/windows_dlls.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -28,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
|
||||
#include <Shobjidl.h>
|
||||
#include <ShObjIdl_core.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <roapi.h>
|
||||
@@ -546,5 +548,29 @@ void psSendToMenu(bool send, bool silent) {
|
||||
}
|
||||
|
||||
bool psLaunchMaps(const Data::LocationPoint &point) {
|
||||
return QDesktopServices::openUrl(qsl("bingmaps:?lvl=16&collection=point.%1_%2_Point").arg(point.latAsString()).arg(point.lonAsString()));
|
||||
const auto aar = base::WinRT::TryCreateInstance<
|
||||
IApplicationAssociationRegistration
|
||||
>(CLSID_ApplicationAssociationRegistration);
|
||||
if (!aar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto handler = (LPWSTR)nullptr;
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (handler) {
|
||||
::CoTaskMemFree(handler);
|
||||
}
|
||||
});
|
||||
const auto result = aar->QueryCurrentDefault(
|
||||
L"bingmaps",
|
||||
AT_URLPROTOCOL,
|
||||
AL_EFFECTIVE,
|
||||
&handler);
|
||||
if (FAILED(result) || !handler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto url = u"bingmaps:?lvl=16&collection=point.%1_%2_Point"_q;
|
||||
return QDesktopServices::openUrl(
|
||||
url.arg(point.latAsString()).arg(point.lonAsString()));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/chat/attach/attach_extensions.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
#include <QtCore/QSemaphore>
|
||||
#include <QtCore/QMimeData>
|
||||
@@ -40,6 +41,8 @@ bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
|
||||
bool ValidPhotoForAlbum(
|
||||
const Image &image,
|
||||
const QString &mime) {
|
||||
Expects(!image.data.isNull());
|
||||
|
||||
if (image.animated
|
||||
|| Core::IsMimeSticker(mime)
|
||||
|| (!mime.isEmpty() && !mime.startsWith(u"image/"))) {
|
||||
@@ -228,6 +231,8 @@ PreparedList PrepareMediaFromImage(
|
||||
QImage &&image,
|
||||
QByteArray &&content,
|
||||
int previewWidth) {
|
||||
Expects(!image.isNull());
|
||||
|
||||
auto result = PreparedList();
|
||||
auto file = PreparedFile(QString());
|
||||
file.content = content;
|
||||
@@ -288,6 +293,7 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
using Song = PreparedFileInformation::Song;
|
||||
if (const auto image = std::get_if<Image>(
|
||||
&file.information->media)) {
|
||||
Assert(!image->data.isNull());
|
||||
if (ValidPhotoForAlbum(*image, file.information->filemime)) {
|
||||
UpdateImageDetails(file, previewWidth);
|
||||
file.type = PreparedFile::Type::Photo;
|
||||
@@ -317,14 +323,29 @@ void UpdateImageDetails(PreparedFile &file, int previewWidth) {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
const auto &preview = image->modifications
|
||||
Assert(!image->data.isNull());
|
||||
auto preview = image->modifications
|
||||
? Editor::ImageModified(image->data, image->modifications)
|
||||
: image->data;
|
||||
Assert(!preview.isNull());
|
||||
file.shownDimensions = PrepareShownDimensions(preview);
|
||||
file.preview = Images::prepareOpaque(preview.scaledToWidth(
|
||||
std::min(previewWidth, style::ConvertScale(preview.width()))
|
||||
* cIntRetinaFactor(),
|
||||
Qt::SmoothTransformation));
|
||||
const auto toWidth = std::min(
|
||||
previewWidth,
|
||||
style::ConvertScale(preview.width())
|
||||
) * cIntRetinaFactor();
|
||||
const auto scaled = preview.scaledToWidth(
|
||||
toWidth,
|
||||
Qt::SmoothTransformation);
|
||||
if (scaled.isNull()) {
|
||||
CrashReports::SetAnnotation("Info", QString("%1x%2:%3*%4->%5;%6x%7"
|
||||
).arg(preview.width()).arg(preview.height()
|
||||
).arg(previewWidth).arg(cIntRetinaFactor()
|
||||
).arg(toWidth
|
||||
).arg(scaled.width()).arg(scaled.height()));
|
||||
Unexpected("Scaled is null.");
|
||||
}
|
||||
Assert(!scaled.isNull());
|
||||
file.preview = Images::prepareOpaque(scaled);
|
||||
Assert(!file.preview.isNull());
|
||||
file.preview.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
@@ -69,16 +69,16 @@ historyResizeWidth: 6px;
|
||||
historyPaddingBottom: 8px;
|
||||
|
||||
historyToDownPosition: point(12px, 10px);
|
||||
historyToDownAbove: icon {{ "history_down_arrow", historyToDownFg, point(17px, 23px) }};
|
||||
historyToDownAboveOver: icon {{ "history_down_arrow", historyToDownFgOver, point(17px, 23px) }};
|
||||
historyToDownAbove: icon {{ "history_down_arrow", historyToDownFg }};
|
||||
historyToDownAboveOver: icon {{ "history_down_arrow", historyToDownFgOver }};
|
||||
historyToDownPaddingTop: 10px;
|
||||
historyToDownBelow: icon {
|
||||
{ "history_down_shadow", historyToDownShadow },
|
||||
{ "history_down_circle", historyToDownBg, point(4px, 4px) },
|
||||
{ "history_down_circle", historyToDownBg },
|
||||
};
|
||||
historyToDownBelowOver: icon {
|
||||
{ "history_down_shadow", historyToDownShadow },
|
||||
{ "history_down_circle", historyToDownBgOver, point(4px, 4px) },
|
||||
{ "history_down_circle", historyToDownBgOver },
|
||||
};
|
||||
historyToDown: TwoIconButton {
|
||||
width: 52px;
|
||||
@@ -102,8 +102,8 @@ historyToDownBadgeSize: 22px;
|
||||
historyToDownShownAfter: 480px;
|
||||
historyToDownDuration: 150;
|
||||
|
||||
dialogsToUpAbove: icon {{ "history_down_arrow-flip_vertical", historyToDownFg, point(17px, 20px) }};
|
||||
dialogsToUpAboveOver: icon {{ "history_down_arrow-flip_vertical", historyToDownFgOver, point(17px, 20px) }};
|
||||
dialogsToUpAbove: icon {{ "history_down_arrow-flip_vertical", historyToDownFg, point(0px, 1px) }};
|
||||
dialogsToUpAboveOver: icon {{ "history_down_arrow-flip_vertical", historyToDownFgOver, point(0px, 1px) }};
|
||||
|
||||
dialogsToUp: TwoIconButton(historyToDown) {
|
||||
iconAbove: dialogsToUpAbove;
|
||||
@@ -741,6 +741,10 @@ historyPollBottomButtonSkip: 15px;
|
||||
historyPollBottomButtonTop: 4px;
|
||||
historyPollChoiceRight: icon {{ "poll_choice_right", activeButtonFg }};
|
||||
historyPollChoiceWrong: icon {{ "poll_choice_wrong", activeButtonFg }};
|
||||
historyPollOutChoiceRight: icon {{ "poll_choice_right", historyFileOutIconFg }};
|
||||
historyPollOutChoiceRightSelected: icon {{ "poll_choice_right", historyFileOutIconFgSelected }};
|
||||
historyPollInChoiceRight: icon {{ "poll_choice_right", historyFileInIconFg }};
|
||||
historyPollInChoiceRightSelected: icon {{ "poll_choice_right", historyFileInIconFgSelected }};
|
||||
historyPollOutChosen: icon {{ "poll_select_check", historyFileOutIconFg }};
|
||||
historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgSelected }};
|
||||
historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }};
|
||||
|
||||
@@ -391,6 +391,12 @@ ChatStyle::ChatStyle() {
|
||||
st::historyPollInChosenSelected,
|
||||
st::historyPollOutChosen,
|
||||
st::historyPollOutChosenSelected);
|
||||
make(
|
||||
&MessageStyle::historyPollChoiceRight,
|
||||
st::historyPollInChoiceRight,
|
||||
st::historyPollInChoiceRightSelected,
|
||||
st::historyPollOutChoiceRight,
|
||||
st::historyPollOutChoiceRightSelected);
|
||||
make(
|
||||
&MessageImageStyle::msgDateImgBg,
|
||||
st::msgDateImgBg,
|
||||
@@ -473,6 +479,12 @@ void ChatStyle::assignPalette(not_null<const style::palette*> palette) {
|
||||
_msgSelectOverlayCornersSmall = {};
|
||||
_msgSelectOverlayCornersLarge = {};
|
||||
|
||||
for (auto &stm : _messageStyles) {
|
||||
const auto same = (stm.textPalette.linkFg->c == stm.historyTextFg->c);
|
||||
stm.textPalette.linkAlwaysActive = same ? 1 : 0;
|
||||
stm.semiboldPalette.linkAlwaysActive = same ? 1 : 0;
|
||||
}
|
||||
|
||||
_paletteChanged.fire({});
|
||||
}
|
||||
|
||||
@@ -598,6 +610,7 @@ void ChatStyle::make(style::icon &my, const style::icon &original) const {
|
||||
void ChatStyle::make(
|
||||
style::TextPalette &my,
|
||||
const style::TextPalette &original) const {
|
||||
my.linkAlwaysActive = original.linkAlwaysActive;
|
||||
make(my.linkFg, original.linkFg);
|
||||
make(my.monoFg, original.monoFg);
|
||||
make(my.selectBg, original.selectBg);
|
||||
|
||||
@@ -69,6 +69,7 @@ struct MessageStyle {
|
||||
style::icon historyQuizTimer = { Qt::Uninitialized };
|
||||
style::icon historyQuizExplain = { Qt::Uninitialized };
|
||||
style::icon historyPollChosen = { Qt::Uninitialized };
|
||||
style::icon historyPollChoiceRight = { Qt::Uninitialized };
|
||||
};
|
||||
|
||||
struct MessageImageStyle {
|
||||
|
||||
@@ -265,8 +265,6 @@ void ChatTheme::adjustPalette(const ChatThemeDescriptor &descriptor) {
|
||||
adjust(p.msgOutReplyBarColor(), by);
|
||||
adjust(p.msgWaveformOutActive(), by);
|
||||
adjust(p.msgWaveformOutInactive(), by);
|
||||
//adjust(p.historyTextOutFg(), by); // windowFg
|
||||
//adjust(p.historyFileNameOutFg(), by); // historyTextOutFg
|
||||
adjust(p.historyFileOutRadialFg(), by); // historyFileOutIconFg
|
||||
adjust(p.mediaOutFg(), by);
|
||||
|
||||
@@ -291,6 +289,40 @@ void ChatTheme::adjustPalette(const ChatThemeDescriptor &descriptor) {
|
||||
adjust(p.historyOutIconFg(), colorizer);
|
||||
adjust(p.historySendingOutIconFg(), colorizer);
|
||||
adjust(p.historyCallArrowOutFg(), colorizer);
|
||||
|
||||
if (!descriptor.basedOnDark) {
|
||||
adjust(p.msgOutBgSelected(), by);
|
||||
adjust(p.msgOutShadowSelected(), by);
|
||||
adjust(p.msgOutServiceFgSelected(), by);
|
||||
adjust(p.msgOutDateFgSelected(), by);
|
||||
adjust(p.msgFileThumbLinkOutFgSelected(), by);
|
||||
adjust(p.msgFileOutBgSelected(), by);
|
||||
adjust(p.msgOutReplyBarSelColor(), by);
|
||||
adjust(p.msgWaveformOutActiveSelected(), by);
|
||||
adjust(p.msgWaveformOutInactiveSelected(), by);
|
||||
adjust(p.historyFileOutRadialFgSelected(), by);
|
||||
adjust(p.mediaOutFgSelected(), by);
|
||||
|
||||
adjust(p.historyLinkOutFgSelected(), by);
|
||||
adjust(p.msgOutMonoFgSelected(), by);
|
||||
adjust(p.historyOutIconFgSelected(), by);
|
||||
// adjust(p.historySendingOutIconFgSelected(), by);
|
||||
adjust(p.historyCallArrowOutFgSelected(), by);
|
||||
adjust(p.historyFileOutIconFgSelected(), by); // msgOutBg
|
||||
|
||||
adjust(p.msgOutServiceFgSelected(), colorizer);
|
||||
adjust(p.msgOutDateFgSelected(), colorizer);
|
||||
adjust(p.msgFileThumbLinkOutFgSelected(), colorizer);
|
||||
adjust(p.msgFileOutBgSelected(), colorizer);
|
||||
adjust(p.msgOutReplyBarSelColor(), colorizer);
|
||||
adjust(p.msgWaveformOutActiveSelected(), colorizer);
|
||||
adjust(p.msgWaveformOutInactiveSelected(), colorizer);
|
||||
adjust(p.mediaOutFgSelected(), colorizer);
|
||||
adjust(p.historyLinkOutFgSelected(), colorizer);
|
||||
adjust(p.historyOutIconFgSelected(), colorizer);
|
||||
//adjust(p.historySendingOutIconFgSelected(), colorizer);
|
||||
adjust(p.historyCallArrowOutFgSelected(), colorizer);
|
||||
}
|
||||
}
|
||||
auto outBgColors = descriptor.bubblesData.colors;
|
||||
if (outBgColors.empty()) {
|
||||
|
||||
@@ -98,25 +98,21 @@ System::SkipState System::skipNotification(
|
||||
not_null<HistoryItem*> item) const {
|
||||
const auto history = item->history();
|
||||
const auto notifyBy = item->specialNotificationPeer();
|
||||
if (App::quitting() || !history->currentNotification()) {
|
||||
if (App::quitting()
|
||||
|| !history->currentNotification()
|
||||
|| item->skipNotification()) {
|
||||
return { SkipState::Skip };
|
||||
} else if (!Core::App().settings().notifyFromAll()
|
||||
&& &history->session().account() != &Core::App().domain().active()) {
|
||||
return { SkipState::Skip };
|
||||
}
|
||||
const auto scheduled = item->out() && item->isFromScheduled();
|
||||
|
||||
if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
|
||||
if (forwarded->imported) {
|
||||
return { SkipState::Skip };
|
||||
}
|
||||
}
|
||||
|
||||
history->owner().requestNotifySettings(history->peer);
|
||||
if (notifyBy) {
|
||||
history->owner().requestNotifySettings(notifyBy);
|
||||
}
|
||||
|
||||
const auto scheduled = item->out() && item->isFromScheduled();
|
||||
if (history->owner().notifyMuteUnknown(history->peer)) {
|
||||
return { SkipState::Unknown, item->isSilent() };
|
||||
} else if (!history->owner().notifyIsMuted(history->peer)) {
|
||||
|
||||
@@ -25,6 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Window {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDarkValueThreshold = 0.5;
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> PeerThemeEmojiValue(
|
||||
not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
@@ -49,9 +51,17 @@ namespace {
|
||||
[[nodiscard]] auto MaybeCloudThemeValueFromPeer(
|
||||
not_null<PeerData*> peer)
|
||||
-> rpl::producer<std::optional<Data::CloudTheme>> {
|
||||
auto isThemeDarkValue = rpl::single(
|
||||
rpl::empty_value()
|
||||
) | rpl::then(
|
||||
style::PaletteChanged()
|
||||
) | rpl::map([] {
|
||||
return (st::dialogsBg->c.valueF() < kDarkValueThreshold);
|
||||
}) | rpl::distinct_until_changed();
|
||||
|
||||
return rpl::combine(
|
||||
MaybeChatThemeDataValueFromPeer(peer),
|
||||
Theme::IsNightModeValue()
|
||||
std::move(isThemeDarkValue)
|
||||
) | rpl::map([](std::optional<Data::ChatTheme> theme, bool night) {
|
||||
return !theme
|
||||
? std::nullopt
|
||||
|
||||
@@ -330,26 +330,6 @@ bool CopyColorsToPalette(
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString GenerateSlug() {
|
||||
const auto letters = uint8('Z' + 1 - 'A');
|
||||
const auto digits = uint8('9' + 1 - '0');
|
||||
const auto values = uint8(2 * letters + digits);
|
||||
|
||||
auto result = QString();
|
||||
result.reserve(kRandomSlugSize);
|
||||
for (auto i = 0; i != kRandomSlugSize; ++i) {
|
||||
const auto value = base::RandomValue<uint8>() % values;
|
||||
if (value < letters) {
|
||||
result.append(char('A' + value));
|
||||
} else if (value < 2 * letters) {
|
||||
result.append(char('a' + (value - letters)));
|
||||
} else {
|
||||
result.append(char('0' + (value - 2 * letters)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray PackTheme(const ParsedTheme &parsed) {
|
||||
zlib::FileToWrite zip;
|
||||
|
||||
@@ -1018,5 +998,25 @@ ParsedTheme ParseTheme(
|
||||
return result();
|
||||
}
|
||||
|
||||
[[nodiscard]] QString GenerateSlug() {
|
||||
const auto letters = uint8('Z' + 1 - 'A');
|
||||
const auto digits = uint8('9' + 1 - '0');
|
||||
const auto values = uint8(2 * letters + digits);
|
||||
|
||||
auto result = QString();
|
||||
result.reserve(kRandomSlugSize);
|
||||
for (auto i = 0; i != kRandomSlugSize; ++i) {
|
||||
const auto value = base::RandomValue<uint8>() % values;
|
||||
if (value < letters) {
|
||||
result.append(char('A' + value));
|
||||
} else if (value < 2 * letters) {
|
||||
result.append(char('a' + (value - letters)));
|
||||
} else {
|
||||
result.append(char('0' + (value - 2 * letters)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Theme
|
||||
} // namespace Window
|
||||
|
||||
@@ -54,5 +54,7 @@ void SaveThemeBox(
|
||||
bool onlyPalette = false,
|
||||
bool parseCurrent = true);
|
||||
|
||||
[[nodiscard]] QString GenerateSlug();
|
||||
|
||||
} // namespace Theme
|
||||
} // namespace Window
|
||||
|
||||
@@ -75,7 +75,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Window {
|
||||
namespace {
|
||||
|
||||
constexpr auto kCustomThemesInMemory = 5;
|
||||
constexpr auto kMaxChatEntryHistorySize = 50;
|
||||
constexpr auto kDayBaseFile = ":/gui/day-custom-base.tdesktop-theme"_cs;
|
||||
constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
|
||||
|
||||
[[nodiscard]] Fn<void(style::palette&)> PreparePaletteCallback(
|
||||
bool dark,
|
||||
@@ -92,21 +95,16 @@ constexpr auto kMaxChatEntryHistorySize = 50;
|
||||
? ColorizerFrom(*i, *accent)
|
||||
: style::colorizer();
|
||||
|
||||
if (dark) {
|
||||
auto instance = Instance();
|
||||
const auto loaded = LoadFromFile(
|
||||
i->path,
|
||||
&instance,
|
||||
nullptr,
|
||||
nullptr,
|
||||
colorizer);
|
||||
Assert(loaded);
|
||||
|
||||
palette.finalize();
|
||||
palette = instance.palette;
|
||||
} else {
|
||||
palette.finalize(colorizer);
|
||||
}
|
||||
auto instance = Instance();
|
||||
const auto loaded = LoadFromFile(
|
||||
(dark ? kNightBaseFile : kDayBaseFile).utf16(),
|
||||
&instance,
|
||||
nullptr,
|
||||
nullptr,
|
||||
colorizer);
|
||||
Assert(loaded);
|
||||
palette.finalize();
|
||||
palette = instance.palette;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -508,9 +506,10 @@ void SessionNavigation::showPollResults(
|
||||
}
|
||||
|
||||
struct SessionController::CachedTheme {
|
||||
std::shared_ptr<Ui::ChatTheme> theme;
|
||||
std::weak_ptr<Ui::ChatTheme> theme;
|
||||
std::shared_ptr<Data::DocumentMedia> media;
|
||||
Data::WallPaper paper;
|
||||
bool caching = false;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
@@ -1396,10 +1395,13 @@ auto SessionController::cachedChatThemeValue(
|
||||
return rpl::single(_defaultChatTheme);
|
||||
}
|
||||
const auto i = _customChatThemes.find(key);
|
||||
if (i != end(_customChatThemes) && i->second.theme) {
|
||||
return rpl::single(i->second.theme);
|
||||
if (i != end(_customChatThemes)) {
|
||||
if (auto strong = i->second.theme.lock()) {
|
||||
pushToLastUsed(strong);
|
||||
return rpl::single(std::move(strong));
|
||||
}
|
||||
}
|
||||
if (i == end(_customChatThemes)) {
|
||||
if (i == end(_customChatThemes) || !i->second.caching) {
|
||||
cacheChatTheme(data);
|
||||
}
|
||||
const auto limit = Data::CloudThemes::TestingColors() ? (1 << 20) : 1;
|
||||
@@ -1408,10 +1410,27 @@ auto SessionController::cachedChatThemeValue(
|
||||
_defaultChatTheme
|
||||
) | rpl::then(_cachedThemesStream.events(
|
||||
) | rpl::filter([=](const std::shared_ptr<Ui::ChatTheme> &theme) {
|
||||
return (theme->key() == key);
|
||||
if (theme->key() != key) {
|
||||
return false;
|
||||
}
|
||||
pushToLastUsed(theme);
|
||||
return true;
|
||||
}) | rpl::take(limit));
|
||||
}
|
||||
|
||||
void SessionController::pushToLastUsed(
|
||||
const std::shared_ptr<Ui::ChatTheme> &theme) {
|
||||
const auto i = ranges::find(_lastUsedCustomChatThemes, theme);
|
||||
if (i == end(_lastUsedCustomChatThemes)) {
|
||||
if (_lastUsedCustomChatThemes.size() >= kCustomThemesInMemory) {
|
||||
_lastUsedCustomChatThemes.pop_back();
|
||||
}
|
||||
_lastUsedCustomChatThemes.push_front(theme);
|
||||
} else if (i != begin(_lastUsedCustomChatThemes)) {
|
||||
std::rotate(begin(_lastUsedCustomChatThemes), i, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionController::setChatStyleTheme(
|
||||
const std::shared_ptr<Ui::ChatTheme> &theme) {
|
||||
if (_chatStyleTheme.lock() == theme) {
|
||||
@@ -1450,9 +1469,22 @@ void SessionController::cacheChatTheme(const Data::CloudTheme &data) {
|
||||
const auto document = data.paper->document();
|
||||
const auto media = document ? document->createMediaView() : nullptr;
|
||||
data.paper->loadDocument();
|
||||
auto &theme = _customChatThemes.emplace(
|
||||
key,
|
||||
CachedTheme{ .media = media, .paper = *data.paper }).first->second;
|
||||
auto &theme = [&]() -> CachedTheme& {
|
||||
const auto i = _customChatThemes.find(key);
|
||||
if (i != end(_customChatThemes)) {
|
||||
i->second.media = media;
|
||||
i->second.paper = *data.paper;
|
||||
i->second.caching = true;
|
||||
return i->second;
|
||||
}
|
||||
return _customChatThemes.emplace(
|
||||
key,
|
||||
CachedTheme{
|
||||
.media = media,
|
||||
.paper = *data.paper,
|
||||
.caching = true,
|
||||
}).first->second;
|
||||
}();
|
||||
auto descriptor = Ui::ChatThemeDescriptor{
|
||||
.id = key,
|
||||
.preparePalette = PreparePaletteCallback(
|
||||
@@ -1488,6 +1520,7 @@ void SessionController::cacheChatThemeDone(
|
||||
if (i == end(_customChatThemes)) {
|
||||
return;
|
||||
}
|
||||
i->second.caching = false;
|
||||
i->second.theme = result;
|
||||
if (i->second.media) {
|
||||
if (i->second.media->loaded(true)) {
|
||||
@@ -1513,10 +1546,11 @@ void SessionController::updateCustomThemeBackground(CachedTheme &theme) {
|
||||
theme.lifetime.destroy();
|
||||
theme.media = nullptr;
|
||||
});
|
||||
if (!theme.media || !theme.theme || !theme.media->loaded(true)) {
|
||||
const auto strong = theme.theme.lock();
|
||||
if (!theme.media || !strong || !theme.media->loaded(true)) {
|
||||
return;
|
||||
}
|
||||
const auto key = theme.theme->key();
|
||||
const auto key = strong->key();
|
||||
const auto weak = base::make_weak(this);
|
||||
crl::async([=, data = backgroundData(theme, false)] {
|
||||
crl::on_main(weak, [
|
||||
@@ -1525,7 +1559,9 @@ void SessionController::updateCustomThemeBackground(CachedTheme &theme) {
|
||||
]() mutable {
|
||||
const auto i = _customChatThemes.find(key);
|
||||
if (i != end(_customChatThemes)) {
|
||||
i->second.theme->updateBackgroundImageFrom(std::move(result));
|
||||
if (const auto strong = i->second.theme.lock()) {
|
||||
strong->updateBackgroundImageFrom(std::move(result));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -464,6 +464,7 @@ private:
|
||||
[[nodiscard]] Ui::ChatThemeBackgroundData backgroundData(
|
||||
CachedTheme &theme,
|
||||
bool generateGradient = true) const;
|
||||
void pushToLastUsed(const std::shared_ptr<Ui::ChatTheme> &theme);
|
||||
|
||||
const not_null<Controller*> _window;
|
||||
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
|
||||
@@ -498,6 +499,7 @@ private:
|
||||
rpl::event_stream<std::shared_ptr<Ui::ChatTheme>> _cachedThemesStream;
|
||||
const std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
||||
std::weak_ptr<Ui::ChatTheme> _chatStyleTheme;
|
||||
std::deque<std::shared_ptr<Ui::ChatTheme>> _lastUsedCustomChatThemes;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ ENV LibrariesPath /usr/src/Libraries
|
||||
WORKDIR $LibrariesPath
|
||||
|
||||
FROM builder AS patches
|
||||
RUN git clone $GIT/desktop-app/patches.git && cd patches && git checkout 01779db1de
|
||||
RUN git clone $GIT/desktop-app/patches.git && cd patches && git checkout 9d2a07ba8b
|
||||
|
||||
FROM builder AS extra-cmake-modules
|
||||
|
||||
@@ -646,7 +646,11 @@ WORKDIR glibmm
|
||||
RUN git apply ../patches/glibmm.patch
|
||||
ENV ACLOCAL_PATH="/usr/local/share/aclocal"
|
||||
RUN NOCONFIGURE=1 ./autogen.sh
|
||||
RUN ./configure --enable-maintainer-mode --enable-static --disable-documentation
|
||||
RUN CC=\"gcc -flto\" CXX=\"g++ -flto\" AR=gcc-ar RANLIB=gcc-ranlib ./configure \
|
||||
--enable-maintainer-mode \
|
||||
--enable-static \
|
||||
--disable-documentation
|
||||
|
||||
RUN make -j$(nproc)
|
||||
RUN make DESTDIR="$LibrariesPath/glibmm-cache" install
|
||||
|
||||
|
||||
7
Telegram/build/prepare/linux.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
set -e
|
||||
FullExecPath=$PWD
|
||||
pushd `dirname $0` > /dev/null
|
||||
FullScriptPath=`pwd`
|
||||
popd > /dev/null
|
||||
|
||||
./build/docker/centos_env/prepare.sh
|
||||
@@ -246,23 +246,26 @@ def winFailOnEach(command):
|
||||
result = result + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%\r\n'
|
||||
return result
|
||||
|
||||
def run(command):
|
||||
def printCommands(commands):
|
||||
print('---------------------------------COMMANDS-LIST----------------------------------')
|
||||
print(command, end='')
|
||||
print(commands, end='')
|
||||
print('--------------------------------------------------------------------------------')
|
||||
|
||||
def run(commands):
|
||||
printCommands(commands)
|
||||
if win:
|
||||
if os.path.exists("command.bat"):
|
||||
os.remove("command.bat")
|
||||
with open("command.bat", 'w') as file:
|
||||
file.write('@echo OFF\r\n' + winFailOnEach(command))
|
||||
file.write('@echo OFF\r\n' + winFailOnEach(commands))
|
||||
result = subprocess.run("command.bat", shell=True, env=modifiedEnv).returncode == 0
|
||||
if result and os.path.exists("command.bat"):
|
||||
os.remove("command.bat")
|
||||
return result
|
||||
elif re.search(r'\%', command):
|
||||
error('Bad command: ' + command)
|
||||
elif re.search(r'\%', commands):
|
||||
error('Bad command: ' + commands)
|
||||
else:
|
||||
return subprocess.run("set -e\n" + command, shell=True, env=modifiedEnv).returncode == 0
|
||||
return subprocess.run("set -e\n" + commands, shell=True, env=modifiedEnv).returncode == 0
|
||||
|
||||
# Thanks https://stackoverflow.com/a/510364
|
||||
class _Getch:
|
||||
@@ -328,6 +331,7 @@ def runStages():
|
||||
prefix = '[' + str(index) + '/' + str(count) + '](' + stage['location'] + '/' + stage['name'] + version + ')'
|
||||
print(prefix + ': ', end = '', flush=True)
|
||||
stage['key'] = computeCacheKey(stage)
|
||||
commands = removeDir(stage['name']) + '\n' + stage['commands']
|
||||
checkResult = 'Forced' if len(onlyStages) > 0 else checkCacheKey(stage)
|
||||
if checkResult == 'Good':
|
||||
print('SKIPPING')
|
||||
@@ -340,11 +344,15 @@ def runStages():
|
||||
if rebuildStale:
|
||||
checkResult == 'Rebuild'
|
||||
else:
|
||||
print('(r)ebuild, rebuild (a)ll, (s)kip, (q)uit?: ', end='', flush=True)
|
||||
print('(r)ebuild, rebuild (a)ll, (s)kip, (p)rint, (q)uit?: ', end='', flush=True)
|
||||
while True:
|
||||
ch = 'r' if rebuildStale else getch()
|
||||
if ch == 'q':
|
||||
finish(0)
|
||||
elif ch == 'p':
|
||||
printCommands(commands)
|
||||
checkResult = 'Printed'
|
||||
break
|
||||
elif ch == 's':
|
||||
checkResult = 'Skip'
|
||||
break
|
||||
@@ -355,13 +363,14 @@ def runStages():
|
||||
checkResult = 'Rebuild'
|
||||
rebuildStale = True
|
||||
break
|
||||
if checkResult == 'Printed':
|
||||
continue
|
||||
if checkResult == 'Skip':
|
||||
print('SKIPPING')
|
||||
continue
|
||||
clearCacheKey(stage)
|
||||
print('BUILDING:')
|
||||
os.chdir(stage['directory'])
|
||||
commands = removeDir(stage['name']) + '\n' + stage['commands']
|
||||
if not run(commands):
|
||||
print(prefix + ': FAILED')
|
||||
finish(1)
|
||||
@@ -370,7 +379,7 @@ def runStages():
|
||||
stage('patches', """
|
||||
git clone https://github.com/desktop-app/patches.git
|
||||
cd patches
|
||||
git checkout 1a1d9e6d2c
|
||||
git checkout 97eee9f4e5
|
||||
""")
|
||||
|
||||
stage('depot_tools', """
|
||||
@@ -387,6 +396,7 @@ depends:patches/gyp.diff
|
||||
git apply $LIBS_DIR/patches/gyp.diff
|
||||
mac:
|
||||
python3 -m pip install git+https://github.com/nodejs/gyp-next@v0.10.0
|
||||
mkdir gyp
|
||||
""", 'ThirdParty')
|
||||
|
||||
stage('yasm', """
|
||||
@@ -489,13 +499,19 @@ mac:
|
||||
""")
|
||||
|
||||
stage('opus', """
|
||||
git clone -b td-v1.3.1 https://github.com/telegramdesktop/opus.git
|
||||
git clone -b v1.3.1 https://github.com/xiph/opus.git
|
||||
cd opus
|
||||
git cherry-pick 927de8453c
|
||||
win:
|
||||
cd win32\\VS2015
|
||||
msbuild opus.sln /property:Configuration=Debug /property:Platform="%WIN32X64%"
|
||||
cmake -B out . ^
|
||||
-A %WIN32X64% ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LIBS_DIR%/local/opus ^
|
||||
-DCMAKE_C_FLAGS_DEBUG="/MTd /Zi /Ob0 /Od /RTC1" ^
|
||||
-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"
|
||||
cmake --build out --config Debug
|
||||
release:
|
||||
msbuild opus.sln /property:Configuration=Release /property:Platform="%WIN32X64%"
|
||||
cmake --build out --config Release
|
||||
cmake --install out --config Release
|
||||
mac:
|
||||
./autogen.sh
|
||||
CFLAGS="$MIN_VER $UNGUARDED" CPPFLAGS="$MIN_VER $UNGUARDED" LDFLAGS="$MIN_VER" ./configure --prefix=$USED_PREFIX
|
||||
@@ -759,7 +775,7 @@ stage('tg_angle', """
|
||||
win:
|
||||
git clone https://github.com/desktop-app/tg_angle.git
|
||||
cd tg_angle
|
||||
git checkout ec51cc6
|
||||
git checkout 0bb011f9e4
|
||||
mkdir out
|
||||
cd out
|
||||
mkdir Debug
|
||||
@@ -793,10 +809,11 @@ win:
|
||||
for /r %%i in (..\\..\\patches\\qtbase_5_15_2\\*) do git apply %%i
|
||||
cd ..
|
||||
|
||||
SET CONFIGURATIONS=-debug-and-release
|
||||
release:
|
||||
SET CONFIGURATIONS=-debug
|
||||
release:
|
||||
SET CONFIGURATIONS=-debug-and-release
|
||||
win:
|
||||
""" + removeDir("\"%LIBS_DIR%\\Qt-5.15.2\"") + """
|
||||
SET ANGLE_DIR=%LIBS_DIR%\\tg_angle
|
||||
SET ANGLE_LIBS_DIR=%ANGLE_DIR%\\out
|
||||
SET MOZJPEG_DIR=%LIBS_DIR%\\mozjpeg
|
||||
@@ -837,9 +854,9 @@ mac:
|
||||
find ../../patches/qtbase_5_15_2 -type f -print0 | sort -z | xargs -0 git apply
|
||||
cd ..
|
||||
|
||||
CONFIGURATIONS=-debug-and-release
|
||||
release:
|
||||
CONFIGURATIONS=-debug
|
||||
release:
|
||||
CONFIGURATIONS=-debug-and-release
|
||||
mac:
|
||||
./configure -prefix "$USED_PREFIX/Qt-5.15.2" \
|
||||
$CONFIGURATIONS \
|
||||
|
||||
@@ -34,7 +34,8 @@ for arg in sys.argv:
|
||||
versionMajor = match.group(1)
|
||||
versionMinor = match.group(2)
|
||||
versionPatch = match.group(4) if match.group(4) else '0'
|
||||
if len(match.group(5)) > 0:
|
||||
versionAlphaBeta = match.group(5) if match.group(5) else ''
|
||||
if len(versionAlphaBeta) > 0:
|
||||
if match.group(6) == 'beta':
|
||||
versionBeta = True
|
||||
else:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
AppVersion 3000005
|
||||
AppVersionStrMajor 3.0
|
||||
AppVersionStrSmall 3.0.5
|
||||
AppVersionStr 3.0.5
|
||||
AppVersion 3001003
|
||||
AppVersionStrMajor 3.1
|
||||
AppVersionStrSmall 3.1.3
|
||||
AppVersionStr 3.1.3
|
||||
BetaChannel 1
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 3.0.5.beta
|
||||
AppVersionOriginal 3.1.3.beta
|
||||
|
||||
@@ -1,3 +1,32 @@
|
||||
3.1.3 beta (27.09.21)
|
||||
|
||||
- Fix illegal instruction crash in opus encoder.
|
||||
|
||||
3.1.2 beta (26.09.21)
|
||||
|
||||
- Control video in fullscreen mode using arrows and numbers.
|
||||
- Open locations in browser if default Bing Maps is not installed.
|
||||
- Reconnect without timeout when network availability changes.
|
||||
- Crash fixes.
|
||||
|
||||
3.1.1 (24.09.21)
|
||||
|
||||
- Crash fixes.
|
||||
|
||||
3.1 (19.09.21)
|
||||
|
||||
- Some animated emoji now have extra effects.
|
||||
- Send :fireworks: :tada:, :balloon:, :like:, :poop: or :heart: to any private chat, then click on the animated emoji to launch the effect.
|
||||
- If your chat partner also has the chat open, you will both see the effects.
|
||||
- See the "Watching" status when your chat partner is enjoying emoji effects with you.
|
||||
- More interactive emoji coming soon.
|
||||
- Right click one of your outgoing messages in small groups to see who recently viewed it.
|
||||
- To protect privacy, read receipts are only stored for 7 days after the message was sent.
|
||||
- Record video and audio from live broadcasts in your group or channel.
|
||||
- Admins can start recording from the '...' menu.
|
||||
- Choose between recording in portrait or landscape orientation.
|
||||
- Finished recordings are sent to the admin's Saved Messages and can be easily shared.
|
||||
|
||||
3.0.5 beta (17.09.21)
|
||||
|
||||
- Add support for Emoji 13.1.
|
||||
|
||||
2
cmake
@@ -42,7 +42,16 @@ apps:
|
||||
slots:
|
||||
- tdesktop-mpris
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
command-chain:
|
||||
- bin/hooks-configure-desktop
|
||||
plugs:
|
||||
- desktop
|
||||
|
||||
plugs:
|
||||
desktop:
|
||||
mount-host-font-cache: false
|
||||
# Support for common GTK themes
|
||||
# https://forum.snapcraft.io/t/how-to-use-the-system-gtk-theme-via-the-gtk-common-themes-snap/6235
|
||||
gsettings:
|
||||
@@ -134,7 +143,7 @@ parts:
|
||||
|
||||
snapcraftctl set-version "$version"
|
||||
|
||||
sed -i 's|^Icon=.*|Icon=${SNAP}/meta/gui/icon.png|g' lib/xdg/telegramdesktop.desktop
|
||||
sed -i 's|^Icon=telegram$|Icon=${SNAP}/meta/gui/icon.png|g' lib/xdg/telegramdesktop.desktop
|
||||
override-build: |
|
||||
snapcraftctl build
|
||||
rm -rf "$SNAPCRAFT_PART_INSTALL/usr/share/icons"
|
||||
|
||||