Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb0642f569 | ||
|
|
1cce35a5a5 | ||
|
|
aeb71e089a | ||
|
|
b962efeca3 | ||
|
|
eb6c350e72 | ||
|
|
d496d41e7e | ||
|
|
19aa4f4acc | ||
|
|
19350e3846 | ||
|
|
741b524d71 | ||
|
|
84288112fc | ||
|
|
7c537cd787 | ||
|
|
c56977cbc1 | ||
|
|
2afa2cd9ab | ||
|
|
442d0da5c1 | ||
|
|
db6bdf36af | ||
|
|
b246328dcf | ||
|
|
a27ea35edd | ||
|
|
a7c4aea9ff | ||
|
|
1ba870a655 | ||
|
|
5bc3cf56fd | ||
|
|
3c4cf2862b | ||
|
|
af69a7a01f | ||
|
|
b9f7a501f5 | ||
|
|
322a085b70 | ||
|
|
6c4dc34441 | ||
|
|
efa287b786 | ||
|
|
23e1c6128b | ||
|
|
bc71a2619a | ||
|
|
4f3510c47c | ||
|
|
2adc20f07f | ||
|
|
b6ade7ce19 | ||
|
|
cabed9587b | ||
|
|
0ce01410a1 | ||
|
|
d02819db13 | ||
|
|
46bae9ed74 | ||
|
|
693ff3398e | ||
|
|
567216f41f | ||
|
|
1ef0791bc6 | ||
|
|
ef5e39f680 | ||
|
|
27228480a8 | ||
|
|
eebe1f4c11 | ||
|
|
5d5e4cbdff | ||
|
|
7e9920b5ea | ||
|
|
297fd0f0c8 | ||
|
|
0eec470387 | ||
|
|
7a64725045 | ||
|
|
1acfe441e1 | ||
|
|
80e932a083 | ||
|
|
698d32db57 | ||
|
|
2ee7cc784f | ||
|
|
071411c8b9 | ||
|
|
43671e2b47 | ||
|
|
1666683dbb | ||
|
|
c134861cd9 | ||
|
|
a1a5ef9d39 | ||
|
|
7e98e9ecf2 | ||
|
|
cad6faa790 | ||
|
|
13ad39dfc2 | ||
|
|
aa8ca28f77 | ||
|
|
d424a8b039 | ||
|
|
9896855789 | ||
|
|
613d4932ca | ||
|
|
44f79b8331 | ||
|
|
aa1117a714 | ||
|
|
8748265b00 | ||
|
|
be8aeb0d96 | ||
|
|
599cc35e57 | ||
|
|
bd367da1bd | ||
|
|
62b50a41c8 | ||
|
|
a3caecbc07 | ||
|
|
71354d1611 |
2
LEGAL
2
LEGAL
@@ -1,7 +1,7 @@
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
Copyright (c) 2014-2022 The Telegram Desktop Authors.
|
||||
Copyright (c) 2014-2023 The Telegram Desktop Authors.
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -849,6 +849,8 @@ PRIVATE
|
||||
info/profile/info_profile_members.h
|
||||
info/profile/info_profile_members_controllers.cpp
|
||||
info/profile/info_profile_members_controllers.h
|
||||
info/profile/info_profile_phone_menu.cpp
|
||||
info/profile/info_profile_phone_menu.h
|
||||
info/profile/info_profile_text.cpp
|
||||
info/profile/info_profile_text.h
|
||||
info/profile/info_profile_values.cpp
|
||||
|
||||
BIN
Telegram/Resources/icons/fragment.png
Normal file
BIN
Telegram/Resources/icons/fragment.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 525 B |
BIN
Telegram/Resources/icons/fragment@2x.png
Normal file
BIN
Telegram/Resources/icons/fragment@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Telegram/Resources/icons/fragment@3x.png
Normal file
BIN
Telegram/Resources/icons/fragment@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -323,6 +323,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_intro_qr_step3" = "Scan this image to Log In";
|
||||
"lng_intro_qr_skip" = "Or log in using your phone number";
|
||||
|
||||
"lng_intro_fragment_title" = "Enter code";
|
||||
"lng_intro_fragment_about" = "Get the code for {phone_number} in the Anonymous Numbers section on Fragment.";
|
||||
"lng_intro_fragment_button" = "Open Fragment";
|
||||
|
||||
"lng_phone_title" = "Your Phone Number";
|
||||
"lng_phone_desc" = "Please confirm your country code and\nenter your mobile phone number.";
|
||||
"lng_phone_to_qr" = "Quick log in using QR code";
|
||||
@@ -751,6 +755,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_download_path" = "Download path";
|
||||
"lng_download_path_temp" = "Temp folder";
|
||||
"lng_download_path_default" = "Default folder";
|
||||
"lng_download_path_unset" = "Unset";
|
||||
"lng_download_path_clear" = "Clear all";
|
||||
"lng_download_path_header" = "Choose download path";
|
||||
"lng_download_path_default_radio" = "Telegram folder in system «Downloads»";
|
||||
@@ -1192,6 +1197,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_info_tab_media" = "Media";
|
||||
"lng_info_public_photo" = "public photo";
|
||||
"lng_info_mobile_label" = "Mobile";
|
||||
"lng_info_mobile_context_menu_fragment_about" = "This number is not tied to a SIM card and was acquired on {link}.";
|
||||
"lng_info_mobile_context_menu_fragment_about_link" = "Fragment";
|
||||
"lng_info_mobile_hidden" = "Hidden";
|
||||
"lng_info_username_label" = "Username";
|
||||
"lng_info_usernames_label" = "also";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.4.2.0" />
|
||||
Version="4.5.2.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 4,4,2,0
|
||||
PRODUCTVERSION 4,4,2,0
|
||||
FILEVERSION 4,5,2,0
|
||||
PRODUCTVERSION 4,5,2,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -62,10 +62,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram FZ-LLC"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "4.4.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.2.0"
|
||||
VALUE "ProductVersion", "4.5.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 4,4,2,0
|
||||
PRODUCTVERSION 4,4,2,0
|
||||
FILEVERSION 4,5,2,0
|
||||
PRODUCTVERSION 4,5,2,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", "4.4.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
|
||||
VALUE "FileVersion", "4.5.2.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "4.4.2.0"
|
||||
VALUE "ProductVersion", "4.5.2.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/confirm_phone_box.h"
|
||||
@@ -58,6 +59,10 @@ void ConfirmPhone::resolve(
|
||||
}, [&](const MTPDauth_sentCodeTypeSetUpEmailRequired &) {
|
||||
return bad("SetUpEmailRequired");
|
||||
});
|
||||
const auto fragmentUrl = data.vtype().match([](
|
||||
const MTPDauth_sentCodeTypeFragmentSms &data) {
|
||||
return qs(data.vurl());
|
||||
}, [](const auto &) { return QString(); });
|
||||
const auto phoneHash = qs(data.vphone_code_hash());
|
||||
const auto timeout = [&]() -> std::optional<int> {
|
||||
if (const auto nextType = data.vnext_type()) {
|
||||
@@ -70,8 +75,15 @@ void ConfirmPhone::resolve(
|
||||
auto box = Box<Ui::ConfirmPhoneBox>(
|
||||
phone,
|
||||
sentCodeLength,
|
||||
fragmentUrl,
|
||||
timeout);
|
||||
const auto boxWeak = Ui::MakeWeak(box.data());
|
||||
using LoginCode = rpl::event_stream<QString>;
|
||||
const auto codeHandles = box->lifetime().make_state<LoginCode>();
|
||||
controller->session().account().setHandleLoginCode([=](
|
||||
const QString &code) {
|
||||
codeHandles->fire_copy(code);
|
||||
});
|
||||
box->resendRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
_api.request(MTPauth_ResendCode(
|
||||
@@ -83,7 +95,9 @@ void ConfirmPhone::resolve(
|
||||
}
|
||||
}).send();
|
||||
}, box->lifetime());
|
||||
box->checkRequests(
|
||||
rpl::merge(
|
||||
codeHandles->events(),
|
||||
box->checkRequests()
|
||||
) | rpl::start_with_next([=](const QString &code) {
|
||||
if (_checkRequestId) {
|
||||
return;
|
||||
@@ -115,6 +129,10 @@ void ConfirmPhone::resolve(
|
||||
boxWeak->showServerError(errorText);
|
||||
}).handleFloodErrors().send();
|
||||
}, box->lifetime());
|
||||
box->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
controller->session().account().setHandleLoginCode(nullptr);
|
||||
}, box->lifetime());
|
||||
|
||||
controller->show(std::move(box), Ui::LayerOption::CloseOther);
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ bool UnreadThings::trackMentions(Data::Thread *thread) const {
|
||||
|
||||
bool UnreadThings::trackReactions(Data::Thread *thread) const {
|
||||
const auto peer = thread ? thread->peer().get() : nullptr;
|
||||
return peer && (peer->isChat() || peer->isMegagroup());
|
||||
return peer && (peer->isUser() || peer->isChat() || peer->isMegagroup());
|
||||
}
|
||||
|
||||
void UnreadThings::preloadEnough(Data::Thread *thread) {
|
||||
|
||||
@@ -2137,7 +2137,6 @@ void ApiWrap::saveDraftsToCloud() {
|
||||
if (const auto cloudDraft = history->cloudDraft(topicRootId)) {
|
||||
if (cloudDraft->saveRequestId == requestId) {
|
||||
history->clearCloudDraft(topicRootId);
|
||||
history->applyCloudDraft(topicRootId);
|
||||
}
|
||||
}
|
||||
const auto i = _draftsSaveRequestIds.find(weak);
|
||||
|
||||
@@ -467,6 +467,7 @@ void GroupInfoBox::prepare() {
|
||||
&_navigation->parentController()->window(),
|
||||
Ui::UserpicButton::Role::ChoosePhoto,
|
||||
st::defaultUserpicButton);
|
||||
_photo->showCustomOnChosen();
|
||||
_title.create(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
@@ -640,10 +641,11 @@ void GroupInfoBox::createGroup(
|
||||
MTP_int(_ttlPeriod)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
auto image = _photo->takeResultImage();
|
||||
const auto period = _ttlPeriod;
|
||||
const auto navigation = _navigation;
|
||||
|
||||
getDelegate()->hideLayer(); // Destroys 'this'.
|
||||
ChatCreateDone(navigation, std::move(image), _ttlPeriod, result);
|
||||
ChatCreateDone(navigation, std::move(image), period, result);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto &type = error.type();
|
||||
_creationRequestId = 0;
|
||||
|
||||
@@ -293,6 +293,10 @@ membersAbout: FlatLabel(defaultFlatLabel) {
|
||||
style: boxLabelStyle;
|
||||
}
|
||||
|
||||
fragmentBoxButton: RoundButton(introFragmentButton) {
|
||||
width: 256px;
|
||||
}
|
||||
|
||||
passcodeHeaderFont: font(19px);
|
||||
passcodeHeaderHeight: 80px;
|
||||
passcodeInput: InputField(introPhone) {
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/change_phone_box.h"
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/sent_code_field.h"
|
||||
@@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "boxes/phone_banned_box.h"
|
||||
#include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
@@ -67,6 +69,10 @@ void CreateErrorLabel(
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int ErrorSkip() {
|
||||
return st::boxLittleSkip + st::changePhoneError.style.font->height;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Settings {
|
||||
@@ -109,6 +115,7 @@ public:
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &phone,
|
||||
const QString &hash,
|
||||
const QString &openUrl,
|
||||
int codeLength,
|
||||
int callTimeout);
|
||||
|
||||
@@ -120,7 +127,7 @@ protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
void submit();
|
||||
void submit(const QString &code);
|
||||
void sendCall();
|
||||
void updateCall();
|
||||
void sendCodeFail(const MTP::Error &error);
|
||||
@@ -128,18 +135,20 @@ private:
|
||||
void hideError() {
|
||||
showError(QString());
|
||||
}
|
||||
int countHeight();
|
||||
[[nodiscard]] int countHeight() const;
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
MTP::Sender _api;
|
||||
|
||||
QString _phone;
|
||||
QString _hash;
|
||||
QString _openUrl;
|
||||
int _codeLength = 0;
|
||||
int _callTimeout = 0;
|
||||
object_ptr<Ui::SentCodeField> _code = { nullptr };
|
||||
object_ptr<Ui::FadeWrap<Ui::FlatLabel>> _error = { nullptr };
|
||||
object_ptr<Ui::FlatLabel> _callLabel = { nullptr };
|
||||
object_ptr<Ui::RoundButton> _fragment = { nullptr };
|
||||
mtpRequestId _requestId = 0;
|
||||
Ui::SentCodeCall _call;
|
||||
|
||||
@@ -174,11 +183,9 @@ void ChangePhone::EnterPhone::prepare() {
|
||||
this,
|
||||
tr::lng_change_phone_new_description(tr::now),
|
||||
st::changePhoneLabel);
|
||||
const auto errorSkip = st::boxLittleSkip
|
||||
+ st::changePhoneError.style.font->height;
|
||||
description->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
_phone->y() + _phone->height() + errorSkip + st::boxLittleSkip);
|
||||
_phone->y() + _phone->height() + ErrorSkip() + st::boxLittleSkip);
|
||||
|
||||
setDimensions(
|
||||
st::boxWidth,
|
||||
@@ -221,6 +228,7 @@ void ChangePhone::EnterPhone::sendPhoneDone(
|
||||
return false;
|
||||
};
|
||||
auto codeLength = 0;
|
||||
auto codeByFragmentUrl = QString();
|
||||
const auto hasLength = data.vtype().match([&](
|
||||
const MTPDauth_sentCodeTypeApp &typeData) {
|
||||
LOG(("Error: should not be in-app code!"));
|
||||
@@ -231,6 +239,7 @@ void ChangePhone::EnterPhone::sendPhoneDone(
|
||||
return true;
|
||||
}, [&](const MTPDauth_sentCodeTypeFragmentSms &typeData) {
|
||||
codeLength = typeData.vlength().v;
|
||||
codeByFragmentUrl = qs(typeData.vurl());
|
||||
return true;
|
||||
}, [&](const MTPDauth_sentCodeTypeCall &typeData) {
|
||||
codeLength = typeData.vlength().v;
|
||||
@@ -263,6 +272,7 @@ void ChangePhone::EnterPhone::sendPhoneDone(
|
||||
_controller,
|
||||
phoneNumber,
|
||||
phoneCodeHash,
|
||||
codeByFragmentUrl,
|
||||
codeLength,
|
||||
callTimeout),
|
||||
Ui::LayerOption::KeepOther);
|
||||
@@ -307,18 +317,21 @@ ChangePhone::EnterCode::EnterCode(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const QString &phone,
|
||||
const QString &hash,
|
||||
const QString &openUrl,
|
||||
int codeLength,
|
||||
int callTimeout)
|
||||
: _controller(controller)
|
||||
, _api(&controller->session().mtp())
|
||||
, _phone(phone)
|
||||
, _hash(hash)
|
||||
, _openUrl(openUrl)
|
||||
, _codeLength(codeLength)
|
||||
, _callTimeout(callTimeout)
|
||||
, _call([this] { sendCall(); }, [this] { updateCall(); }) {
|
||||
}
|
||||
|
||||
void ChangePhone::EnterCode::prepare() {
|
||||
const auto width = st::boxWidth;
|
||||
setTitle(tr::lng_change_phone_title());
|
||||
|
||||
const auto descriptionText = tr::lng_change_phone_code_description(
|
||||
@@ -332,44 +345,69 @@ void ChangePhone::EnterCode::prepare() {
|
||||
st::changePhoneLabel);
|
||||
description->moveToLeft(st::boxPadding.left(), 0);
|
||||
|
||||
const auto submitInput = [=] { submit(_code->getDigitsOnly()); };
|
||||
|
||||
const auto phoneValue = QString();
|
||||
_code.create(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
tr::lng_change_phone_code_title(),
|
||||
phoneValue);
|
||||
_code->setAutoSubmit(_codeLength, [=] { submit(); });
|
||||
_code->setAutoSubmit(_codeLength, submitInput);
|
||||
_code->setChangedCallback([=] { hideError(); });
|
||||
|
||||
_code->resize(st::boxWidth - 2 * st::boxPadding.left(), _code->height());
|
||||
_code->resize(width - 2 * st::boxPadding.left(), _code->height());
|
||||
_code->moveToLeft(st::boxPadding.left(), description->bottomNoMargins());
|
||||
connect(_code, &Ui::InputField::submitted, [=] { submit(); });
|
||||
connect(_code, &Ui::InputField::submitted, submitInput);
|
||||
|
||||
setDimensions(st::boxWidth, countHeight());
|
||||
if (!_openUrl.isEmpty()) {
|
||||
_fragment.create(
|
||||
this,
|
||||
tr::lng_intro_fragment_button(),
|
||||
st::fragmentBoxButton);
|
||||
_fragment->setClickedCallback([=] { File::OpenUrl(_openUrl); });
|
||||
_fragment->setTextTransform(
|
||||
Ui::RoundButton::TextTransform::NoTransform);
|
||||
const auto codeBottom = _code->y() + _code->height();
|
||||
_fragment->setFullWidth(_code->width());
|
||||
_fragment->moveToLeft(
|
||||
(width - _fragment->width()) / 2,
|
||||
codeBottom + ErrorSkip() + st::boxLittleSkip);
|
||||
}
|
||||
|
||||
_controller->session().account().setHandleLoginCode([=](QString code) {
|
||||
submit(code);
|
||||
});
|
||||
boxClosing(
|
||||
) | rpl::start_with_next([controller = _controller] {
|
||||
controller->session().account().setHandleLoginCode(nullptr);
|
||||
}, lifetime());
|
||||
|
||||
setDimensions(width, countHeight());
|
||||
|
||||
if (_callTimeout > 0) {
|
||||
_call.setStatus({ Ui::SentCodeCall::State::Waiting, _callTimeout });
|
||||
updateCall();
|
||||
}
|
||||
|
||||
addButton(tr::lng_change_phone_new_submit(), [=] { submit(); });
|
||||
addButton(tr::lng_change_phone_new_submit(), submitInput);
|
||||
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
||||
}
|
||||
|
||||
int ChangePhone::EnterCode::countHeight() {
|
||||
const auto errorSkip = st::boxLittleSkip
|
||||
+ st::changePhoneError.style.font->height;
|
||||
return _code->bottomNoMargins() + errorSkip + 3 * st::boxLittleSkip;
|
||||
int ChangePhone::EnterCode::countHeight() const {
|
||||
return _code->bottomNoMargins()
|
||||
+ ErrorSkip()
|
||||
+ 3 * st::boxLittleSkip
|
||||
+ (_fragment ? _fragment->height() : 0);
|
||||
}
|
||||
|
||||
void ChangePhone::EnterCode::submit() {
|
||||
void ChangePhone::EnterCode::submit(const QString &code) {
|
||||
if (_requestId) {
|
||||
return;
|
||||
}
|
||||
hideError();
|
||||
|
||||
const auto session = &_controller->session();
|
||||
const auto code = _code->getDigitsOnly();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_requestId = session->api().request(MTPaccount_ChangePhone(
|
||||
MTP_string(_phone),
|
||||
|
||||
@@ -25,7 +25,14 @@ DownloadPathBox::DownloadPathBox(
|
||||
, _path(Core::App().settings().downloadPath())
|
||||
, _pathBookmark(Core::App().settings().downloadPathBookmark())
|
||||
, _group(std::make_shared<Ui::RadioenumGroup<Directory>>(typeFromPath(_path)))
|
||||
, _default(this, _group, Directory::Downloads, tr::lng_download_path_default_radio(tr::now), st::defaultBoxCheckbox)
|
||||
, _default(Core::App().canReadDefaultDownloadPath(true)
|
||||
? object_ptr<Ui::Radioenum<Directory>>(
|
||||
this,
|
||||
_group,
|
||||
Directory::Downloads,
|
||||
tr::lng_download_path_default_radio(tr::now),
|
||||
st::defaultBoxCheckbox)
|
||||
: nullptr)
|
||||
, _temp(this, _group, Directory::Temp, tr::lng_download_path_temp_radio(tr::now), st::defaultBoxCheckbox)
|
||||
, _dir(this, _group, Directory::Custom, tr::lng_download_path_dir_radio(tr::now), st::defaultBoxCheckbox)
|
||||
, _pathLink(this, QString(), st::boxLinkButton) {
|
||||
@@ -50,7 +57,7 @@ void DownloadPathBox::updateControlsVisibility() {
|
||||
auto custom = (_group->value() == Directory::Custom);
|
||||
_pathLink->setVisible(custom);
|
||||
|
||||
auto newHeight = st::boxOptionListPadding.top() + _default->getMargins().top() + _default->heightNoMargins() + st::boxOptionListSkip + _temp->heightNoMargins() + st::boxOptionListSkip + _dir->heightNoMargins();
|
||||
auto newHeight = st::boxOptionListPadding.top() + (_default ? _default->getMargins().top() + _default->heightNoMargins() : 0) + st::boxOptionListSkip + _temp->heightNoMargins() + st::boxOptionListSkip + _dir->heightNoMargins();
|
||||
if (custom) {
|
||||
newHeight += st::downloadPathSkip + _pathLink->height();
|
||||
}
|
||||
@@ -62,8 +69,10 @@ void DownloadPathBox::updateControlsVisibility() {
|
||||
void DownloadPathBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_default->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top() + _default->getMargins().top());
|
||||
_temp->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _default->bottomNoMargins() + st::boxOptionListSkip);
|
||||
if (_default) {
|
||||
_default->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top() + _default->getMargins().top());
|
||||
}
|
||||
_temp->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), (_default ? _default->bottomNoMargins() : 0) + st::boxOptionListSkip);
|
||||
_dir->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _temp->bottomNoMargins() + st::boxOptionListSkip);
|
||||
auto inputx = st::boxPadding.left() + st::boxOptionListPadding.left() + st::defaultCheck.diameter + st::defaultBoxCheckbox.textPosition.x();
|
||||
auto inputy = _dir->bottomNoMargins() + st::downloadPathSkip;
|
||||
|
||||
@@ -451,7 +451,8 @@ void EditCaptionBox::setupPhotoEditorEventHandler() {
|
||||
&file.information->media);
|
||||
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(file, previewWidth);
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
Storage::UpdateImageDetails(file, previewWidth, sideLimit);
|
||||
rebuildPreview();
|
||||
};
|
||||
const auto fileImage = std::make_shared<Image>(*large);
|
||||
|
||||
@@ -477,6 +477,7 @@ object_ptr<Ui::RpWidget> Controller::createPhotoEdit() {
|
||||
st::defaultUserpicButton),
|
||||
st::editPeerPhotoMargins);
|
||||
_controls.photo = photoWrap->entity();
|
||||
_controls.photo->showCustomOnChosen();
|
||||
|
||||
return photoWrap;
|
||||
}
|
||||
|
||||
@@ -348,8 +348,9 @@ void SendFilesBox::enqueueNextPrepare() {
|
||||
_list.filesToProcess.pop_front();
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_preparing = true;
|
||||
crl::async([weak, file = std::move(file)]() mutable {
|
||||
Storage::PrepareDetails(file, st::sendMediaPreviewSize);
|
||||
const auto sideLimit = PhotoSideLimit(); // Get on main thread.
|
||||
crl::async([weak, sideLimit, file = std::move(file)]() mutable {
|
||||
Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit);
|
||||
crl::on_main([weak, file = std::move(file)]() mutable {
|
||||
if (weak) {
|
||||
weak->addPreparedAsyncFile(std::move(file));
|
||||
|
||||
@@ -595,11 +595,9 @@ void Application::saveSettings() {
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
bool Application::canSaveFileWithoutAskingForPath() const {
|
||||
if (Core::App().settings().askDownloadPath()) {
|
||||
return false;
|
||||
} else if (KSandbox::isInside()
|
||||
&& Core::App().settings().downloadPath().isEmpty()) {
|
||||
bool Application::canReadDefaultDownloadPath(bool always) const {
|
||||
if (KSandbox::isInside()
|
||||
&& (always || Core::App().settings().downloadPath().isEmpty())) {
|
||||
const auto path = QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation);
|
||||
return base::CanReadDirectory(path);
|
||||
@@ -607,6 +605,11 @@ bool Application::canSaveFileWithoutAskingForPath() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::canSaveFileWithoutAskingForPath() const {
|
||||
return !Core::App().settings().askDownloadPath()
|
||||
&& canReadDefaultDownloadPath();
|
||||
}
|
||||
|
||||
MTP::Config &Application::fallbackProductionConfig() const {
|
||||
if (!_fallbackProductionConfig) {
|
||||
_fallbackProductionConfig = std::make_unique<MTP::Config>(
|
||||
|
||||
@@ -178,6 +178,8 @@ public:
|
||||
[[nodiscard]] Settings &settings();
|
||||
void saveSettingsDelayed(crl::time delay = kDefaultSaveDelay);
|
||||
void saveSettings();
|
||||
|
||||
[[nodiscard]] bool canReadDefaultDownloadPath(bool always = false) const;
|
||||
[[nodiscard]] bool canSaveFileWithoutAskingForPath() const;
|
||||
|
||||
// Fallback config and proxy.
|
||||
|
||||
@@ -84,6 +84,14 @@ std::map<int, const char*> BetaLogs() {
|
||||
"- Set a public photo for those who are restricted to see "
|
||||
"your profile photo in the Privacy Settings.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
},
|
||||
{
|
||||
4004003,
|
||||
"- Support for anonymous numbers from the Fragment.com platform.\n"
|
||||
|
||||
"- Fix a crash in own profile photo updating.\n"
|
||||
|
||||
"- Bug fixes and other minor improvements.\n"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,8 +24,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
#include <ksandbox.h>
|
||||
|
||||
bool filedialogGetSaveFile(
|
||||
QPointer<QWidget> parent,
|
||||
QString &file,
|
||||
@@ -173,15 +171,12 @@ QString DefaultDownloadPathFolder(not_null<Main::Session*> session) {
|
||||
}
|
||||
|
||||
QString DefaultDownloadPath(not_null<Main::Session*> session) {
|
||||
const auto standardLocation = QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation);
|
||||
const auto realDefaultPath = standardLocation
|
||||
const auto realDefaultPath = QStandardPaths::writableLocation(
|
||||
QStandardPaths::DownloadLocation)
|
||||
+ '/'
|
||||
+ DefaultDownloadPathFolder(session)
|
||||
+ '/';
|
||||
if (KSandbox::isInside()
|
||||
&& Core::App().settings().downloadPath().isEmpty()
|
||||
&& !base::CanReadDirectory(standardLocation)) {
|
||||
if (!Core::App().canReadDefaultDownloadPath()) {
|
||||
QStringList files;
|
||||
QByteArray remoteContent;
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
|
||||
@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "settings/settings_chat.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "mainwidget.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "inline_bots/bot_attach_web_view.h"
|
||||
@@ -792,6 +793,25 @@ bool ResolvePremiumOffer(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolveLoginCode(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
const auto loginCode = match->captured(2);
|
||||
if (loginCode.isEmpty()) {
|
||||
return false;
|
||||
};
|
||||
(controller
|
||||
? controller->session().account()
|
||||
: Core::App().activeAccount()).handleLoginCode(loginCode);
|
||||
if (controller) {
|
||||
controller->window().activate();
|
||||
} else if (const auto window = Core::App().activeWindow()) {
|
||||
window->activate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
@@ -864,6 +884,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
u"premium_offer/?(\\?.+)?(#|$)"_q,
|
||||
ResolvePremiumOffer,
|
||||
},
|
||||
{
|
||||
u"^login/?(\\?code=([0-9]+))(&|$)"_q,
|
||||
ResolveLoginCode
|
||||
},
|
||||
{
|
||||
u"^([^\\?]+)(\\?|#|$)"_q,
|
||||
HandleUnknown
|
||||
|
||||
@@ -370,6 +370,9 @@ void Manager::fillDefaults() {
|
||||
set(u"ctrl+3"_q, Command::ChatPinned3);
|
||||
set(u"ctrl+4"_q, Command::ChatPinned4);
|
||||
set(u"ctrl+5"_q, Command::ChatPinned5);
|
||||
set(u"ctrl+6"_q, Command::ChatPinned6);
|
||||
set(u"ctrl+7"_q, Command::ChatPinned7);
|
||||
set(u"ctrl+8"_q, Command::ChatPinned8);
|
||||
|
||||
auto &&folders = ranges::views::zip(
|
||||
kShowFolder,
|
||||
|
||||
@@ -34,6 +34,9 @@ enum class Command {
|
||||
ChatPinned3,
|
||||
ChatPinned4,
|
||||
ChatPinned5,
|
||||
ChatPinned6,
|
||||
ChatPinned7,
|
||||
ChatPinned8,
|
||||
|
||||
ShowAllChats,
|
||||
ShowFolder1,
|
||||
|
||||
@@ -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 = 4004002;
|
||||
constexpr auto AppVersionStr = "4.4.2";
|
||||
constexpr auto AppBetaVersion = true;
|
||||
constexpr auto AppVersion = 4005002;
|
||||
constexpr auto AppVersionStr = "4.5.2";
|
||||
constexpr auto AppBetaVersion = false;
|
||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||
|
||||
@@ -356,24 +356,55 @@ FormatResult CountriesInstance::format(FormatArgs args) {
|
||||
const auto codeSize = int(bestCallingCodePtr->callingCode.size());
|
||||
|
||||
if (args.onlyGroups && args.incomplete) {
|
||||
auto groups = args.skipCode
|
||||
auto initialGroups = args.skipCode
|
||||
? QVector<int>()
|
||||
: QVector<int>{ codeSize };
|
||||
auto groupSize = 0;
|
||||
auto initialGroupsSize = 0;
|
||||
if (bestCallingCodePtr->patterns.empty()) {
|
||||
return FormatResult{ .groups = std::move(groups) };
|
||||
return FormatResult{ .groups = std::move(initialGroups) };
|
||||
}
|
||||
for (const auto &c : bestCallingCodePtr->patterns.front()) {
|
||||
if (c == ' ') {
|
||||
groups.push_back(base::take(groupSize));
|
||||
} else {
|
||||
groupSize++;
|
||||
auto bestGroups = initialGroups;
|
||||
auto bestGroupsSize = initialGroupsSize;
|
||||
auto bestPatternMaxMatches = -1;
|
||||
for (const auto &pattern : bestCallingCodePtr->patterns) {
|
||||
auto groups = initialGroups;
|
||||
auto groupSize = initialGroupsSize;
|
||||
auto lastSpacesCount = 0;
|
||||
auto maxMatchedDigits = 0;
|
||||
auto isNotBestPattern = false;
|
||||
for (auto i = 0; i < pattern.size(); i++) {
|
||||
const auto c = pattern.at(i);
|
||||
if (c.isDigit()) {
|
||||
const auto n = (i - lastSpacesCount) + codeSize;
|
||||
if (n < phoneNumber.size()) {
|
||||
if (phoneNumber.at(n) == c) {
|
||||
maxMatchedDigits++;
|
||||
} else {
|
||||
isNotBestPattern = true;
|
||||
}
|
||||
} else {
|
||||
isNotBestPattern = true;
|
||||
}
|
||||
}
|
||||
if (c.isSpace()) {
|
||||
groups.push_back(base::take(groupSize));
|
||||
lastSpacesCount++;
|
||||
} else {
|
||||
groupSize++;
|
||||
}
|
||||
}
|
||||
if (maxMatchedDigits > bestPatternMaxMatches) {
|
||||
bestPatternMaxMatches = isNotBestPattern
|
||||
? -1
|
||||
: maxMatchedDigits;
|
||||
bestGroups = std::move(groups);
|
||||
bestGroupsSize = groupSize;
|
||||
}
|
||||
}
|
||||
if (groupSize) {
|
||||
groups.push_back(base::take(groupSize));
|
||||
if (bestGroupsSize) {
|
||||
bestGroups.push_back(base::take(bestGroupsSize));
|
||||
}
|
||||
return FormatResult{ .groups = std::move(groups) };
|
||||
return FormatResult{ .groups = std::move(bestGroups) };
|
||||
}
|
||||
|
||||
const auto formattedPart = phoneNumber.mid(codeSize);
|
||||
|
||||
@@ -1198,26 +1198,29 @@ bool DocumentData::isStickerSetInstalled() const {
|
||||
|
||||
Image *DocumentData::getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
if (!hasThumbnail()) {
|
||||
return nullptr;
|
||||
} else if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
return _replyPreview->image(origin, context);
|
||||
return _replyPreview->image(origin, context, spoiler);
|
||||
}
|
||||
|
||||
Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||
return getReplyPreview(item->fullId(), item->history()->peer);
|
||||
const auto media = item->media();
|
||||
const auto spoiler = media && media->hasSpoiler();
|
||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||
}
|
||||
|
||||
bool DocumentData::replyPreviewLoaded() const {
|
||||
bool DocumentData::replyPreviewLoaded(bool spoiler) const {
|
||||
if (!hasThumbnail()) {
|
||||
return true;
|
||||
} else if (!_replyPreview) {
|
||||
return false;
|
||||
}
|
||||
return _replyPreview->loaded();
|
||||
return _replyPreview->loaded(spoiler);
|
||||
}
|
||||
|
||||
StickerData *DocumentData::sticker() const {
|
||||
|
||||
@@ -142,9 +142,10 @@ public:
|
||||
|
||||
[[nodiscard]] Image *getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool replyPreviewLoaded() const;
|
||||
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
|
||||
|
||||
[[nodiscard]] StickerData *sticker() const;
|
||||
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const;
|
||||
|
||||
@@ -618,7 +618,7 @@ Image *MediaPhoto::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaPhoto::replyPreviewLoaded() const {
|
||||
return _photo->replyPreviewLoaded();
|
||||
return _photo->replyPreviewLoaded(_spoiler);
|
||||
}
|
||||
|
||||
TextWithEntities MediaPhoto::notificationText() const {
|
||||
@@ -854,7 +854,7 @@ Image *MediaFile::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaFile::replyPreviewLoaded() const {
|
||||
return _document->replyPreviewLoaded();
|
||||
return _document->replyPreviewLoaded(_spoiler);
|
||||
}
|
||||
|
||||
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||
@@ -1479,10 +1479,11 @@ Image *MediaWebPage::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaWebPage::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = MediaWebPage::document()) {
|
||||
return document->replyPreviewLoaded();
|
||||
return document->replyPreviewLoaded(spoiler);
|
||||
} else if (const auto photo = MediaWebPage::photo()) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1552,10 +1553,11 @@ Image *MediaGame::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaGame::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto document = _game->document) {
|
||||
return document->replyPreviewLoaded();
|
||||
return document->replyPreviewLoaded(spoiler);
|
||||
} else if (const auto photo = _game->photo) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1675,8 +1677,9 @@ Image *MediaInvoice::replyPreview() const {
|
||||
}
|
||||
|
||||
bool MediaInvoice::replyPreviewLoaded() const {
|
||||
const auto spoiler = false;
|
||||
if (const auto photo = _invoice.photo) {
|
||||
return photo->replyPreviewLoaded();
|
||||
return photo->replyPreviewLoaded(spoiler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPhotoSideLimit = 1280;
|
||||
constexpr auto kPhotoSideLimit = 2560;
|
||||
|
||||
using Data::PhotoMedia;
|
||||
using Data::PhotoSize;
|
||||
@@ -209,22 +209,25 @@ bool PhotoData::uploading() const {
|
||||
|
||||
Image *PhotoData::getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
if (!_replyPreview) {
|
||||
_replyPreview = std::make_unique<Data::ReplyPreview>(this);
|
||||
}
|
||||
return _replyPreview->image(origin, context);
|
||||
return _replyPreview->image(origin, context, spoiler);
|
||||
}
|
||||
|
||||
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
|
||||
return getReplyPreview(item->fullId(), item->history()->peer);
|
||||
const auto media = item->media();
|
||||
const auto spoiler = media && media->hasSpoiler();
|
||||
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
|
||||
}
|
||||
|
||||
bool PhotoData::replyPreviewLoaded() const {
|
||||
bool PhotoData::replyPreviewLoaded(bool spoiler) const {
|
||||
if (!_replyPreview) {
|
||||
return false;
|
||||
}
|
||||
return _replyPreview->loaded();
|
||||
return _replyPreview->loaded(spoiler);
|
||||
}
|
||||
|
||||
void PhotoData::setRemoteLocation(
|
||||
|
||||
@@ -66,9 +66,10 @@ public:
|
||||
|
||||
[[nodiscard]] Image *getReplyPreview(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] bool replyPreviewLoaded() const;
|
||||
[[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
|
||||
|
||||
void setRemoteLocation(
|
||||
int32 dc,
|
||||
|
||||
@@ -101,6 +101,14 @@ void PhotoMedia::set(
|
||||
QImage image,
|
||||
QByteArray bytes) {
|
||||
const auto index = PhotoSizeIndex(size);
|
||||
const auto limit = PhotoData::SideLimit();
|
||||
if (image.width() > limit || image.height() > limit) {
|
||||
image = image.scaled(
|
||||
limit,
|
||||
limit,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
_images[index] = PhotoImage{
|
||||
.data = std::make_unique<Image>(std::move(image)),
|
||||
.bytes = std::move(bytes),
|
||||
|
||||
@@ -27,7 +27,11 @@ ReplyPreview::ReplyPreview(not_null<PhotoData*> photo)
|
||||
|
||||
ReplyPreview::~ReplyPreview() = default;
|
||||
|
||||
void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
void ReplyPreview::prepare(
|
||||
not_null<Image*> image,
|
||||
Images::Options options,
|
||||
bool spoiler) {
|
||||
using namespace Images;
|
||||
if (image->isNull()) {
|
||||
return;
|
||||
}
|
||||
@@ -41,24 +45,34 @@ void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
|
||||
: QSize(
|
||||
st::msgReplyBarSize.height(),
|
||||
h * st::msgReplyBarSize.height() / w);
|
||||
thumbSize *= cIntRetinaFactor();
|
||||
options |= Images::Option::TransparentBackground;
|
||||
thumbSize *= style::DevicePixelRatio();
|
||||
options |= Option::TransparentBackground;
|
||||
auto outerSize = st::msgReplyBarSize.height();
|
||||
auto bitmap = image->pixNoCache(
|
||||
thumbSize,
|
||||
{ .options = options, .outer = { outerSize, outerSize } });
|
||||
_image = std::make_unique<Image>(bitmap.toImage());
|
||||
_good = ((options & Images::Option::Blur) == 0);
|
||||
auto original = spoiler
|
||||
? image->original().scaled(
|
||||
{ 40, 40 },
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
: image->original();
|
||||
auto prepared = Prepare(std::move(original), thumbSize, {
|
||||
.options = options | (spoiler ? Option::Blur : Option()),
|
||||
.outer = { outerSize, outerSize },
|
||||
});
|
||||
(spoiler ? _spoilered : _regular) = std::make_unique<Image>(
|
||||
std::move(prepared));
|
||||
_good = spoiler || ((options & Option::Blur) == 0);
|
||||
}
|
||||
|
||||
Image *ReplyPreview::image(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context) {
|
||||
if (_checked) {
|
||||
return _image.get();
|
||||
}
|
||||
if (_document) {
|
||||
if (!_image || (!_good && _document->hasThumbnail())) {
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler) {
|
||||
auto &image = spoiler ? _spoilered : _regular;
|
||||
auto &checked = spoiler ? _checkedSpoilered : _checkedRegular;
|
||||
if (checked) {
|
||||
return image.get();
|
||||
} else if (_document) {
|
||||
if (!image || (!_good && _document->hasThumbnail())) {
|
||||
if (!_documentMedia) {
|
||||
_documentMedia = _document->createMediaView();
|
||||
_documentMedia->thumbnailWanted(origin);
|
||||
@@ -67,51 +81,66 @@ Image *ReplyPreview::image(
|
||||
const auto option = _document->isVideoMessage()
|
||||
? Images::Option::RoundCircle
|
||||
: Images::Option::None;
|
||||
if (thumbnail) {
|
||||
if (spoiler) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option, true);
|
||||
} else if (thumbnail) {
|
||||
prepare(thumbnail, option, true);
|
||||
}
|
||||
} else if (thumbnail) {
|
||||
prepare(thumbnail, option);
|
||||
} else if (!_image) {
|
||||
} else if (!image) {
|
||||
if (const auto image = _documentMedia->thumbnailInline()) {
|
||||
prepare(image, option | Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good || !_document->hasThumbnail()) {
|
||||
_checked = true;
|
||||
checked = true;
|
||||
_documentMedia = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Assert(_photo != nullptr);
|
||||
if (!_image || !_good) {
|
||||
if (!image || !_good) {
|
||||
const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes();
|
||||
if (!_photoMedia) {
|
||||
_photoMedia = _photo->createMediaView();
|
||||
}
|
||||
using Size = PhotoSize;
|
||||
const auto loadThumbnail = inlineThumbnailBytes.isEmpty()
|
||||
|| _photoMedia->autoLoadThumbnailAllowed(context);
|
||||
|| (!spoiler
|
||||
&& _photoMedia->autoLoadThumbnailAllowed(context));
|
||||
if (loadThumbnail) {
|
||||
_photoMedia->wanted(PhotoSize::Small, origin);
|
||||
_photoMedia->wanted(Size::Small, origin);
|
||||
}
|
||||
if (const auto small = _photoMedia->image(PhotoSize::Small)) {
|
||||
prepare(small, Images::Option(0));
|
||||
} else if (const auto large = _photoMedia->image(
|
||||
PhotoSize::Large)) {
|
||||
prepare(large, Images::Option(0));
|
||||
} else if (!_image) {
|
||||
if (spoiler) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, {}, true);
|
||||
} else if (const auto small = _photoMedia->image(Size::Small)) {
|
||||
prepare(small, {}, true);
|
||||
} else if (const auto large = _photoMedia->image(Size::Large)) {
|
||||
prepare(large, {}, true);
|
||||
}
|
||||
} else if (const auto small = _photoMedia->image(Size::Small)) {
|
||||
prepare(small, {});
|
||||
} else if (const auto large = _photoMedia->image(Size::Large)) {
|
||||
prepare(large, {});
|
||||
} else if (!image) {
|
||||
if (const auto blurred = _photoMedia->thumbnailInline()) {
|
||||
prepare(blurred, Images::Option::Blur);
|
||||
}
|
||||
}
|
||||
if (_good) {
|
||||
_checked = true;
|
||||
checked = true;
|
||||
_photoMedia = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _image.get();
|
||||
return image.get();
|
||||
}
|
||||
|
||||
bool ReplyPreview::loaded() const {
|
||||
return _checked;
|
||||
bool ReplyPreview::loaded(bool spoiler) const {
|
||||
return spoiler ? _checkedSpoilered : _checkedRegular;
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
||||
@@ -25,19 +25,25 @@ public:
|
||||
|
||||
[[nodiscard]] Image *image(
|
||||
Data::FileOrigin origin,
|
||||
not_null<PeerData*> context);
|
||||
[[nodiscard]] bool loaded() const;
|
||||
not_null<PeerData*> context,
|
||||
bool spoiler);
|
||||
[[nodiscard]] bool loaded(bool spoiler) const;
|
||||
|
||||
private:
|
||||
void prepare(not_null<Image*> image, Images::Options options);
|
||||
void prepare(
|
||||
not_null<Image*> image,
|
||||
Images::Options options,
|
||||
bool spoiler = false);
|
||||
|
||||
std::unique_ptr<Image> _image;
|
||||
std::unique_ptr<Image> _regular;
|
||||
std::unique_ptr<Image> _spoilered;
|
||||
PhotoData *_photo = nullptr;
|
||||
DocumentData *_document = nullptr;
|
||||
std::shared_ptr<PhotoMedia> _photoMedia;
|
||||
std::shared_ptr<DocumentMedia> _documentMedia;
|
||||
bool _good = false;
|
||||
bool _checked = false;
|
||||
bool _checkedRegular = false;
|
||||
bool _checkedSpoilered = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -144,6 +144,8 @@ WebPageType ParseWebPageType(
|
||||
return WebPageType::Video;
|
||||
} else if (type == u"photo"_q) {
|
||||
return WebPageType::Photo;
|
||||
} else if (type == u"document"_q) {
|
||||
return WebPageType::Document;
|
||||
} else if (type == u"profile"_q) {
|
||||
return WebPageType::Profile;
|
||||
} else if (type == u"telegram_background"_q) {
|
||||
|
||||
@@ -26,6 +26,7 @@ enum class WebPageType {
|
||||
|
||||
Photo,
|
||||
Video,
|
||||
Document,
|
||||
|
||||
User,
|
||||
Bot,
|
||||
|
||||
@@ -926,7 +926,8 @@ void Stickers::specialSetReceived(
|
||||
LOG(("API Error: "
|
||||
"received recent attached stickers hash %1 "
|
||||
"while counted hash is %2"
|
||||
).arg(hash, counted));
|
||||
).arg(hash
|
||||
).arg(counted));
|
||||
}
|
||||
session().local().writeRecentMasks();
|
||||
} break;
|
||||
|
||||
@@ -3739,6 +3739,9 @@ void InnerWidget::setupShortcuts() {
|
||||
Command::ChatPinned3,
|
||||
Command::ChatPinned4,
|
||||
Command::ChatPinned5,
|
||||
Command::ChatPinned6,
|
||||
Command::ChatPinned7,
|
||||
Command::ChatPinned8,
|
||||
};
|
||||
auto &&pinned = ranges::views::zip(
|
||||
kPinned,
|
||||
|
||||
@@ -252,6 +252,10 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) {
|
||||
}
|
||||
}
|
||||
|
||||
Row::~Row() {
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::recountHeight(float64 narrowRatio) {
|
||||
if (const auto history = _id.history()) {
|
||||
_height = history->isForum()
|
||||
@@ -487,6 +491,11 @@ void Row::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void Row::clearRipple() {
|
||||
BasicRow::clearRipple();
|
||||
clearTopicJumpRipple();
|
||||
}
|
||||
|
||||
void Row::addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
@@ -503,10 +512,15 @@ void Row::addTopicJumpRipple(
|
||||
}
|
||||
|
||||
void Row::clearTopicJumpRipple() {
|
||||
if (_topicJumpRipple) {
|
||||
clearRipple();
|
||||
_topicJumpRipple = 0;
|
||||
if (!_topicJumpRipple) {
|
||||
return;
|
||||
}
|
||||
const auto history = this->history();
|
||||
const auto view = history ? &history->lastItemDialogsView() : nullptr;
|
||||
if (view) {
|
||||
view->clearRipple();
|
||||
}
|
||||
_topicJumpRipple = 0;
|
||||
}
|
||||
|
||||
bool Row::topicJumpRipple() const {
|
||||
|
||||
@@ -53,11 +53,11 @@ public:
|
||||
|
||||
void addRipple(QPoint origin, QSize size, Fn<void()> updateCallback);
|
||||
virtual void stopLastRipple();
|
||||
virtual void clearRipple();
|
||||
void addRippleWithMask(
|
||||
QPoint origin,
|
||||
QImage mask,
|
||||
Fn<void()> updateCallback);
|
||||
void clearRipple();
|
||||
|
||||
void paintRipple(
|
||||
QPainter &p,
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
explicit Row(std::nullptr_t) {
|
||||
}
|
||||
Row(Key key, int index, int top);
|
||||
~Row();
|
||||
|
||||
[[nodiscard]] int top() const {
|
||||
return _top;
|
||||
@@ -105,6 +106,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool lookupIsInTopicJump(int x, int y) const;
|
||||
void stopLastRipple() override;
|
||||
void clearRipple() override;
|
||||
void addTopicJumpRipple(
|
||||
QPoint origin,
|
||||
not_null<Ui::TopicJumpCache*> topicJumpCache,
|
||||
|
||||
@@ -2160,15 +2160,13 @@ bool Widget::setSearchInChat(Key chat, PeerData *from) {
|
||||
}
|
||||
}
|
||||
_searchInMigrated = nullptr;
|
||||
if (peer) {
|
||||
if (peer && !forum) {
|
||||
if (_layout != Layout::Main) {
|
||||
return false;
|
||||
} else if (const auto migrateTo = peer->migrateTo()) {
|
||||
return setSearchInChat(peer->owner().history(migrateTo), from);
|
||||
} else if (const auto migrateFrom = peer->migrateFrom()) {
|
||||
if (!forum) {
|
||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||
}
|
||||
_searchInMigrated = peer->owner().history(migrateFrom);
|
||||
}
|
||||
}
|
||||
if (searchInPeerUpdated) {
|
||||
|
||||
@@ -89,12 +89,10 @@ void PaintRowDate(
|
||||
const auto lastDate = lastTime.date();
|
||||
|
||||
const auto dt = [&] {
|
||||
const auto wasSameDay = (lastDate == nowDate);
|
||||
const auto wasRecently = qAbs(lastTime.secsTo(now)) < kRecentlyInSeconds;
|
||||
if (wasSameDay || wasRecently) {
|
||||
if ((lastDate == nowDate)
|
||||
|| (qAbs(lastTime.secsTo(now)) < kRecentlyInSeconds)) {
|
||||
return QLocale().toString(lastTime.time(), QLocale::ShortFormat);
|
||||
} else if (lastDate.year() == nowDate.year()
|
||||
&& lastDate.weekNumber() == nowDate.weekNumber()) {
|
||||
} else if (qAbs(lastDate.daysTo(nowDate)) < 7) {
|
||||
return langDayOfWeek(lastDate);
|
||||
} else {
|
||||
return QLocale().toString(lastDate, QLocale::ShortFormat);
|
||||
|
||||
@@ -227,6 +227,12 @@ void MessageView::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void MessageView::clearRipple() {
|
||||
if (_topics) {
|
||||
_topics->clearRipple();
|
||||
}
|
||||
}
|
||||
|
||||
int MessageView::countWidth() const {
|
||||
auto result = 0;
|
||||
if (!_senderCache.isEmpty()) {
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
not_null<TopicJumpCache*> topicJumpCache,
|
||||
Fn<void()> updateCallback);
|
||||
void stopLastRipple();
|
||||
void clearRipple();
|
||||
|
||||
private:
|
||||
struct LoadingContext;
|
||||
|
||||
@@ -187,6 +187,10 @@ void TopicsView::stopLastRipple() {
|
||||
}
|
||||
}
|
||||
|
||||
void TopicsView::clearRipple() {
|
||||
_ripple = nullptr;
|
||||
}
|
||||
|
||||
void TopicsView::paintRipple(
|
||||
QPainter &p,
|
||||
int x,
|
||||
|
||||
@@ -89,8 +89,8 @@ public:
|
||||
int y,
|
||||
int outerWidth,
|
||||
const QColor *colorOverride) const;
|
||||
void clearRipple();
|
||||
void stopLastRipple();
|
||||
void clearRipple();
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/boxes/confirm_box.h" // InformBox
|
||||
#include "editor/editor_layer_widget.h"
|
||||
#include "editor/photo_editor.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
#include "ui/chat/attach/attach_prepare.h"
|
||||
#include "window/window_controller.h"
|
||||
@@ -42,10 +43,11 @@ void OpenWithPreparedFile(
|
||||
return;
|
||||
}
|
||||
|
||||
const auto sideLimit = PhotoSideLimit();
|
||||
auto callback = [=, done = std::move(doneCallback)](
|
||||
const PhotoModifications &mods) {
|
||||
image->modifications = mods;
|
||||
Storage::UpdateImageDetails(*file, previewWidth);
|
||||
Storage::UpdateImageDetails(*file, previewWidth, sideLimit);
|
||||
{
|
||||
using namespace Ui;
|
||||
const auto size = file->preview.size();
|
||||
|
||||
@@ -490,8 +490,6 @@ void ApiWrap::requestSplitRanges() {
|
||||
_splits.push_back(MTP_messageRange(
|
||||
MTP_int(1),
|
||||
MTP_int(std::numeric_limits<int>::max())));
|
||||
} else {
|
||||
ranges::reverse(_splits);
|
||||
}
|
||||
_startProcess->splitIndex = useOnlyLastSplit()
|
||||
? (_splits.size() - 1)
|
||||
|
||||
@@ -1431,12 +1431,18 @@ void HistoryItem::applyEdition(const MTPDmessageService &message) {
|
||||
if (wasGrouped) {
|
||||
history()->owner().groups().unregisterMessage(this);
|
||||
}
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
reply->clearData(this);
|
||||
}
|
||||
clearDependencyMessage();
|
||||
UpdateComponents(0);
|
||||
createServiceFromMtp(message);
|
||||
applyServiceDateEdition(message);
|
||||
finishEditionToEmpty();
|
||||
} else if (isService()) {
|
||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||
reply->clearData(this);
|
||||
}
|
||||
clearDependencyMessage();
|
||||
UpdateComponents(0);
|
||||
createServiceFromMtp(message);
|
||||
|
||||
@@ -283,9 +283,10 @@ bool HistoryMessageReply::updateData(
|
||||
}
|
||||
|
||||
if (replyToMsg) {
|
||||
const auto repaint = [=] { holder->customEmojiRepaint(); };
|
||||
const auto context = Core::MarkedTextContext{
|
||||
.session = &holder->history()->session(),
|
||||
.customEmojiRepaint = [=] { holder->customEmojiRepaint(); },
|
||||
.customEmojiRepaint = repaint,
|
||||
};
|
||||
replyToText.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
@@ -312,9 +313,17 @@ bool HistoryMessageReply::updateData(
|
||||
? replyToMsg->from()->id
|
||||
: PeerId(0);
|
||||
}
|
||||
|
||||
const auto media = replyToMsg->media();
|
||||
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
|
||||
spoiler = nullptr;
|
||||
} else if (!spoiler) {
|
||||
spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
|
||||
}
|
||||
} else if (force) {
|
||||
replyToMsgId = 0;
|
||||
replyToColorKey = PeerId(0);
|
||||
spoiler = nullptr;
|
||||
}
|
||||
if (force) {
|
||||
holder->history()->owner().requestItemResize(holder);
|
||||
@@ -463,14 +472,15 @@ void HistoryMessageReply::paint(
|
||||
|
||||
if (w > st::msgReplyBarSkip) {
|
||||
if (replyToMsg) {
|
||||
auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
||||
const auto media = replyToMsg->media();
|
||||
auto hasPreview = media && media->hasReplyPreview();
|
||||
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
|
||||
hasPreview = false;
|
||||
}
|
||||
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
|
||||
if (hasPreview) {
|
||||
if (const auto image = replyToMsg->media()->replyPreview()) {
|
||||
if (const auto image = media->replyPreview()) {
|
||||
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
|
||||
const auto preview = image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
@@ -482,6 +492,16 @@ void HistoryMessageReply::paint(
|
||||
.outer = to.size(),
|
||||
});
|
||||
p.drawPixmap(to.x(), to.y(), preview);
|
||||
if (spoiler) {
|
||||
holder->clearCustomEmojiRepaint();
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
spoiler->index(
|
||||
context.now,
|
||||
context.paused)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (w > st::msgReplyBarSkip + previewSkip) {
|
||||
|
||||
@@ -246,6 +246,7 @@ struct HistoryMessageReply
|
||||
WebPageId replyToWebPageId = 0;
|
||||
ReplyToMessagePointer replyToMsg;
|
||||
std::unique_ptr<HistoryMessageVia> replyToVia;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
|
||||
ClickHandlerPtr replyToLnk;
|
||||
mutable Ui::Text::String replyToName, replyToText;
|
||||
mutable int replyToVersion = 0;
|
||||
|
||||
@@ -176,8 +176,8 @@ const char kOptionAutoScrollInactiveChat[] =
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMessagesPerPageFirst = 10;
|
||||
constexpr auto kMessagesPerPage = 10;
|
||||
constexpr auto kMessagesPerPageFirst = 30;
|
||||
constexpr auto kMessagesPerPage = 50;
|
||||
constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
|
||||
constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
|
||||
constexpr auto kSkipRepaintWhileScrollMs = 100;
|
||||
@@ -6224,7 +6224,7 @@ void HistoryWidget::checkPinnedBarState() {
|
||||
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
|
||||
_peer,
|
||||
MsgId(0), // topicRootId
|
||||
@@ -7490,20 +7490,44 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||
p.setInactive(
|
||||
controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
|
||||
p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
|
||||
|
||||
const auto media = (!drawWebPagePreview && drawMsgText)
|
||||
? drawMsgText->media()
|
||||
: nullptr;
|
||||
const auto hasPreview = media && media->hasReplyPreview();
|
||||
const auto preview = hasPreview ? media->replyPreview() : nullptr;
|
||||
const auto spoilered = preview && media->hasSpoiler();
|
||||
if (!spoilered) {
|
||||
_replySpoiler = nullptr;
|
||||
} else if (!_replySpoiler) {
|
||||
_replySpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
updateField();
|
||||
});
|
||||
}
|
||||
|
||||
if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
|
||||
const auto now = crl::now();
|
||||
const auto paused = p.inactive();
|
||||
auto replyLeft = st::historyReplySkip;
|
||||
(_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
|
||||
if (!drawWebPagePreview) {
|
||||
if (drawMsgText) {
|
||||
if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) {
|
||||
if (const auto image = drawMsgText->media()->replyPreview()) {
|
||||
if (hasPreview) {
|
||||
if (preview) {
|
||||
auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
|
||||
p.drawPixmap(to.x(), to.y(), image->pixSingle(
|
||||
image->size() / style::DevicePixelRatio(),
|
||||
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
|
||||
preview->size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = to.size(),
|
||||
}));
|
||||
if (_replySpoiler) {
|
||||
Ui::FillSpoilerRect(
|
||||
p,
|
||||
to,
|
||||
Ui::DefaultImageSpoiler().frame(
|
||||
_replySpoiler->index(now, paused)));
|
||||
}
|
||||
}
|
||||
replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
|
||||
}
|
||||
@@ -7521,8 +7545,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
|
||||
.availableWidth = width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(),
|
||||
.palette = &st::historyComposeAreaPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = crl::now(),
|
||||
.paused = p.inactive(),
|
||||
.now = now,
|
||||
.paused = paused,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -70,6 +70,7 @@ class RequestsBar;
|
||||
struct PreparedList;
|
||||
class SendFilesWay;
|
||||
class SendAsButton;
|
||||
class SpoilerAnimation;
|
||||
enum class ReportReason;
|
||||
class ChooseThemeController;
|
||||
class ContinuousScroll;
|
||||
@@ -626,6 +627,7 @@ private:
|
||||
|
||||
HistoryItem *_replyEditMsg = nullptr;
|
||||
Ui::Text::String _replyEditMsgText;
|
||||
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
|
||||
mutable base::Timer _updateEditTimeLeftDisplay;
|
||||
|
||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||
|
||||
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_forum_topic.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/chat/forward_options_box.h"
|
||||
#include "ui/effects/spoiler_mess.h"
|
||||
#include "ui/text/text_options.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/painter.h"
|
||||
@@ -306,33 +307,35 @@ void ForwardPanel::paint(
|
||||
return;
|
||||
}
|
||||
const_cast<ForwardPanel*>(this)->checkTexts();
|
||||
const auto now = crl::now();
|
||||
const auto paused = p.inactive();
|
||||
const auto firstItem = _data.items.front();
|
||||
const auto firstMedia = firstItem->media();
|
||||
const auto hasPreview = (_data.items.size() < 2)
|
||||
&& firstMedia
|
||||
&& firstMedia->hasReplyPreview();
|
||||
const auto preview = hasPreview ? firstMedia->replyPreview() : nullptr;
|
||||
const auto spoiler = preview && firstMedia->hasSpoiler();
|
||||
if (!spoiler) {
|
||||
_spoiler = nullptr;
|
||||
} else if (!_spoiler) {
|
||||
_spoiler = std::make_unique<Ui::SpoilerAnimation>(_repaint);
|
||||
}
|
||||
if (preview) {
|
||||
auto to = QRect(
|
||||
x,
|
||||
y + st::msgReplyPadding.top(),
|
||||
st::msgReplyBarSize.height(),
|
||||
st::msgReplyBarSize.height());
|
||||
if (preview->width() == preview->height()) {
|
||||
p.drawPixmap(to.x(), to.y(), preview->pix());
|
||||
} else {
|
||||
auto from = (preview->width() > preview->height())
|
||||
? QRect(
|
||||
(preview->width() - preview->height()) / 2,
|
||||
0,
|
||||
preview->height(),
|
||||
preview->height())
|
||||
: QRect(
|
||||
0,
|
||||
(preview->height() - preview->width()) / 2,
|
||||
preview->width(),
|
||||
preview->width());
|
||||
p.drawPixmap(to, preview->pix(), from);
|
||||
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
|
||||
preview->size() / style::DevicePixelRatio(),
|
||||
{
|
||||
.options = Images::Option::RoundSmall,
|
||||
.outer = to.size(),
|
||||
}));
|
||||
if (_spoiler) {
|
||||
Ui::FillSpoilerRect(p, to, Ui::DefaultImageSpoiler().frame(
|
||||
_spoiler->index(now, paused)));
|
||||
}
|
||||
const auto skip = st::msgReplyBarSize.height()
|
||||
+ st::msgReplyBarSkip
|
||||
@@ -355,8 +358,8 @@ void ForwardPanel::paint(
|
||||
.availableWidth = available,
|
||||
.palette = &st::historyComposeAreaPalette,
|
||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||
.now = crl::now(),
|
||||
.paused = p.inactive(),
|
||||
.now = now,
|
||||
.paused = paused,
|
||||
.elisionLines = 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
class Painter;
|
||||
class HistoryItem;
|
||||
|
||||
namespace Ui {
|
||||
class SpoilerAnimation;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Data {
|
||||
class Thread;
|
||||
} // namespace Data
|
||||
@@ -57,6 +61,7 @@ private:
|
||||
|
||||
rpl::event_stream<> _itemsUpdated;
|
||||
Ui::Text::String _from, _text;
|
||||
mutable std::unique_ptr<Ui::SpoilerAnimation> _spoiler;
|
||||
int _nameVersion = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -141,7 +141,7 @@ rpl::producer<Ui::GroupCallBarContent> GroupCallBarContentByCall(
|
||||
userpic.peer->loadUserpic();
|
||||
auto image = userpic.peer->generateUserpicImage(
|
||||
userpic.view,
|
||||
userpicSize);
|
||||
userpicSize * style::DevicePixelRatio());
|
||||
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
|
||||
state->current.users.push_back({
|
||||
.userpic = std::move(image),
|
||||
|
||||
@@ -3749,6 +3749,8 @@ void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
|
||||
}
|
||||
|
||||
void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
saveScrollState();
|
||||
|
||||
if (_reactionsItem.current() == item) {
|
||||
_reactionsItem = nullptr;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,9 @@ namespace {
|
||||
[[nodiscard]] Ui::MessageBarContent ContentWithPreview(
|
||||
not_null<HistoryItem*> item,
|
||||
Image *preview,
|
||||
bool spoiler,
|
||||
Fn<void()> repaint) {
|
||||
auto result = ContentWithoutPreview(item, std::move(repaint));
|
||||
auto result = ContentWithoutPreview(item, repaint);
|
||||
if (!preview) {
|
||||
static const auto kEmpty = [&] {
|
||||
const auto size = st::historyReplyHeight * cIntRetinaFactor();
|
||||
@@ -51,8 +52,10 @@ namespace {
|
||||
return result;
|
||||
}();
|
||||
result.preview = kEmpty;
|
||||
result.spoilerRepaint = nullptr;
|
||||
} else {
|
||||
result.preview = preview->original();
|
||||
result.spoilerRepaint = spoiler ? repaint : nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -90,7 +93,11 @@ namespace {
|
||||
}) | rpl::then(
|
||||
rpl::single(kFullLoaded)
|
||||
) | rpl::map([=] {
|
||||
return ContentWithPreview(item, media->replyPreview(), repaint);
|
||||
return ContentWithPreview(
|
||||
item,
|
||||
media->replyPreview(),
|
||||
media->hasSpoiler(),
|
||||
repaint);
|
||||
});
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ void RepliesWidget::setupRootView() {
|
||||
_rootView = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
_rootView->setContent(rpl::combine(
|
||||
RootViewContent(
|
||||
_history,
|
||||
@@ -1592,7 +1592,7 @@ void RepliesWidget::checkPinnedBarState() {
|
||||
_pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
|
||||
return controller()->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Any);
|
||||
});
|
||||
}, controller()->gifPauseLevelChanged());
|
||||
auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
|
||||
_history->peer,
|
||||
_rootId,
|
||||
|
||||
@@ -88,7 +88,7 @@ rpl::producer<Ui::RequestsBarContent> RequestsBarContentByPeer(
|
||||
userpic.peer->loadUserpic();
|
||||
auto image = userpic.peer->generateUserpicImage(
|
||||
userpic.view,
|
||||
userpicSize);
|
||||
userpicSize * style::DevicePixelRatio());
|
||||
userpic.uniqueKey = userpic.peer->userpicUniqueKey(userpic.view);
|
||||
state->current.users.push_back({
|
||||
.userpic = std::move(image),
|
||||
|
||||
@@ -71,8 +71,8 @@ bool DrawWebPageDataPreview(
|
||||
}
|
||||
|
||||
const auto preview = photo
|
||||
? photo->getReplyPreview(Data::FileOrigin(), context)
|
||||
: document->getReplyPreview(Data::FileOrigin(), context);
|
||||
? photo->getReplyPreview(Data::FileOrigin(), context, false)
|
||||
: document->getReplyPreview(Data::FileOrigin(), context, false);
|
||||
if (preview) {
|
||||
const auto w = preview->width();
|
||||
const auto h = preview->height();
|
||||
|
||||
@@ -166,6 +166,7 @@ QSize WebPage::countOptimalSize() {
|
||||
} else if (!_data->document
|
||||
&& _data->photo
|
||||
&& _data->type != WebPageType::Photo
|
||||
&& _data->type != WebPageType::Document
|
||||
&& _data->type != WebPageType::Video) {
|
||||
if (_data->type == WebPageType::Profile) {
|
||||
_asArticle = true;
|
||||
@@ -755,6 +756,7 @@ ClickHandlerPtr WebPage::replaceAttachLink(
|
||||
|| _data->type == WebPageType::Video) {
|
||||
return _openl;
|
||||
} else if (_data->type == WebPageType::Photo
|
||||
|| _data->type == WebPageType::Document
|
||||
|| _data->siteName == u"Twitter"_q
|
||||
|| _data->siteName == u"Facebook"_q) {
|
||||
// leave photo link
|
||||
|
||||
@@ -45,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/profile/info_profile_phone_menu.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_text.h"
|
||||
#include "support/support_helper.h"
|
||||
@@ -391,10 +392,17 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||
user->session().supportHelper().infoTextValue(user));
|
||||
}
|
||||
|
||||
addInfoOneLine(
|
||||
tr::lng_info_mobile_label(),
|
||||
PhoneOrHiddenValue(user),
|
||||
tr::lng_profile_copy_phone(tr::now));
|
||||
{
|
||||
const auto phoneLabel = addInfoOneLine(
|
||||
tr::lng_info_mobile_label(),
|
||||
PhoneOrHiddenValue(user),
|
||||
tr::lng_profile_copy_phone(tr::now)).text;
|
||||
const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) {
|
||||
phoneLabel->fillContextMenu(request);
|
||||
AddPhoneMenu(request.menu, user);
|
||||
};
|
||||
phoneLabel->setContextMenuHook(hook);
|
||||
}
|
||||
auto label = user->isBot()
|
||||
? tr::lng_info_about_label()
|
||||
: tr::lng_info_bio_label();
|
||||
|
||||
155
Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
Normal file
155
Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/profile/info_profile_phone_menu.h"
|
||||
|
||||
#include "data/data_user.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "styles/style_chat.h" // expandedMenuSeparator.
|
||||
|
||||
namespace Info {
|
||||
namespace Profile {
|
||||
namespace {
|
||||
|
||||
class TextItem final : public Ui::Menu::ItemBase {
|
||||
public:
|
||||
TextItem(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
rpl::producer<TextWithEntities> &&text);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
protected:
|
||||
int contentHeight() const override;
|
||||
|
||||
private:
|
||||
const base::unique_qptr<Ui::FlatLabel> _label;
|
||||
const not_null<QAction*> _dummyAction;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] int CountMinWidthForHeight(
|
||||
not_null<Ui::FlatLabel*> label,
|
||||
int basicWidth,
|
||||
int heightLimit) {
|
||||
const auto height = [&](int width) {
|
||||
label->resizeToWidth(width);
|
||||
return label->height();
|
||||
};
|
||||
auto widthMin = basicWidth;
|
||||
auto widthMax = label->naturalWidth();
|
||||
if (height(widthMin) <= heightLimit || height(widthMax) > heightLimit) {
|
||||
return basicWidth;
|
||||
}
|
||||
while (widthMin + 1 < widthMax) {
|
||||
const auto middle = (widthMin + widthMax) / 2;
|
||||
if (height(middle) > heightLimit) {
|
||||
widthMin = middle;
|
||||
} else {
|
||||
widthMax = middle;
|
||||
}
|
||||
}
|
||||
return widthMax;
|
||||
}
|
||||
|
||||
TextItem::TextItem(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
rpl::producer<TextWithEntities> &&text)
|
||||
: ItemBase(parent, st)
|
||||
, _label(base::make_unique_q<Ui::FlatLabel>(
|
||||
this,
|
||||
std::move(text),
|
||||
st::historyMessagesTTLLabel))
|
||||
, _dummyAction(Ui::CreateChild<QAction>(parent.get())) {
|
||||
// Try to fit the phrase in two lines.
|
||||
const auto limit = st::historyMessagesTTLLabel.style.font->height * 2;
|
||||
const auto min1 = st::historyMessagesTTLLabel.minWidth;
|
||||
const auto min2 = CountMinWidthForHeight(_label.get(), min1, limit);
|
||||
const auto added = st.itemPadding.left() + st.itemPadding.right();
|
||||
setMinWidth(std::max(min1, min2) + added);
|
||||
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
if (s.width() <= added) {
|
||||
return;
|
||||
}
|
||||
_label->resizeToWidth(s.width() - added);
|
||||
_label->moveToLeft(
|
||||
st.itemPadding.left(),
|
||||
(s.height() - _label->height()) / 2);
|
||||
}, lifetime());
|
||||
|
||||
_label->resizeToWidth(parent->width() - added);
|
||||
initResizeHook(parent->sizeValue());
|
||||
}
|
||||
|
||||
not_null<QAction*> TextItem::action() const {
|
||||
return _dummyAction;
|
||||
}
|
||||
|
||||
bool TextItem::isEnabled() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int TextItem::contentHeight() const {
|
||||
return _label->height();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddPhoneMenu(not_null<Ui::PopupMenu*> menu, not_null<UserData*> user) {
|
||||
if (user->isSelf()) {
|
||||
return;
|
||||
}
|
||||
using Strings = std::vector<QString>;
|
||||
const auto prefixes = user->session().account().appConfig().get<Strings>(
|
||||
u"fragment_prefixes"_q,
|
||||
std::vector<QString>());
|
||||
{
|
||||
const auto proj = [&phone = user->phone()](const QString &p) {
|
||||
return phone.startsWith(p);
|
||||
};
|
||||
if (ranges::none_of(prefixes, proj)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto domains = user->session().account().appConfig().get<Strings>(
|
||||
u"whitelisted_domains"_q,
|
||||
std::vector<QString>());
|
||||
const auto proj = [&, domain = u"fragment"_q](const QString &p) {
|
||||
return p.contains(domain);
|
||||
};
|
||||
const auto it = ranges::find_if(domains, proj);
|
||||
if (it == end(domains)) {
|
||||
return;
|
||||
}
|
||||
|
||||
menu->addSeparator(&st::expandedMenuSeparator);
|
||||
const auto link = Ui::Text::Link(
|
||||
tr::lng_info_mobile_context_menu_fragment_about_link(tr::now),
|
||||
*it);
|
||||
menu->addAction(base::make_unique_q<TextItem>(
|
||||
menu->menu(),
|
||||
st::reactionMenu.menu,
|
||||
tr::lng_info_mobile_context_menu_fragment_about(
|
||||
lt_link,
|
||||
rpl::single(link),
|
||||
Ui::Text::RichLangValue)));
|
||||
}
|
||||
|
||||
} // namespace Profile
|
||||
} // namespace Info
|
||||
22
Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
Normal file
22
Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class UserData;
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
namespace Profile {
|
||||
|
||||
void AddPhoneMenu(not_null<Ui::PopupMenu*> menu, not_null<UserData*> user);
|
||||
|
||||
} // namespace Profile
|
||||
} // namespace Info
|
||||
@@ -85,9 +85,17 @@ introCoverDuration: 200;
|
||||
introNextButton: RoundButton(defaultActiveButton) {
|
||||
width: 300px;
|
||||
height: 42px;
|
||||
radius: 6px;
|
||||
textTop: 11px;
|
||||
font: font(boxFontSize semibold);
|
||||
}
|
||||
introFragmentIcon: icon{{ "fragment", activeButtonFg }};
|
||||
introFragmentIconOver: icon{{ "fragment", activeButtonFgOver }};
|
||||
introFragmentButton: RoundButton(introNextButton) {
|
||||
icon: introFragmentIcon;
|
||||
iconOver: introFragmentIconOver;
|
||||
iconPosition: point(-10px, 9px);
|
||||
}
|
||||
|
||||
introStepFieldTop: 96px;
|
||||
introPhoneTop: 6px;
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "lang/lang_keys.h"
|
||||
#include "intro/intro_signup.h"
|
||||
#include "intro/intro_password_check.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/update_checker.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
@@ -99,8 +100,18 @@ CodeWidget::CodeWidget(
|
||||
|
||||
_code->setDigitsCountMax(getData()->codeLength);
|
||||
|
||||
setTitleText(rpl::single(Ui::FormatPhone(getData()->phone)));
|
||||
updateDescText();
|
||||
setTitleText(_isFragment.value(
|
||||
) | rpl::map([=](bool isFragment) {
|
||||
return !isFragment
|
||||
? rpl::single(Ui::FormatPhone(getData()->phone))
|
||||
: tr::lng_intro_fragment_title();
|
||||
}) | rpl::flatten_latest());
|
||||
|
||||
account->setHandleLoginCode([=](const QString &code) {
|
||||
_code->setText(code);
|
||||
submitCode();
|
||||
});
|
||||
}
|
||||
|
||||
void CodeWidget::refreshLang() {
|
||||
@@ -117,9 +128,18 @@ int CodeWidget::errorTop() const {
|
||||
|
||||
void CodeWidget::updateDescText() {
|
||||
const auto byTelegram = getData()->codeByTelegram;
|
||||
const auto isFragment = !getData()->codeByFragmentUrl.isEmpty();
|
||||
_isFragment = isFragment;
|
||||
setDescriptionText(
|
||||
(byTelegram ? tr::lng_code_from_telegram : tr::lng_code_desc)(
|
||||
Ui::Text::RichLangValue));
|
||||
isFragment
|
||||
? tr::lng_intro_fragment_about(
|
||||
lt_phone_number,
|
||||
rpl::single(TextWithEntities{
|
||||
.text = Ui::FormatPhone(getData()->phone)
|
||||
}),
|
||||
Ui::Text::RichLangValue)
|
||||
: (byTelegram ? tr::lng_code_from_telegram : tr::lng_code_desc)(
|
||||
Ui::Text::RichLangValue));
|
||||
if (getData()->codeByTelegram) {
|
||||
_noTelegramCode->show();
|
||||
_callTimer.cancel();
|
||||
@@ -204,6 +224,7 @@ void CodeWidget::activate() {
|
||||
|
||||
void CodeWidget::finished() {
|
||||
Step::finished();
|
||||
account().setHandleLoginCode(nullptr);
|
||||
_checkRequestTimer.cancel();
|
||||
_callTimer.cancel();
|
||||
apiClear();
|
||||
@@ -300,7 +321,7 @@ void CodeWidget::codeSubmitFail(const MTP::Error &error) {
|
||||
|
||||
void CodeWidget::codeChanged() {
|
||||
hideError();
|
||||
submit();
|
||||
submitCode();
|
||||
}
|
||||
|
||||
void CodeWidget::sendCall() {
|
||||
@@ -362,10 +383,18 @@ void CodeWidget::gotPassword(const MTPaccount_Password &result) {
|
||||
}
|
||||
|
||||
void CodeWidget::submit() {
|
||||
if (getData()->codeByFragmentUrl.isEmpty()) {
|
||||
submitCode();
|
||||
} else {
|
||||
File::OpenUrl(getData()->codeByFragmentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeWidget::submitCode() {
|
||||
const auto text = QString(
|
||||
_code->getLastText()
|
||||
).remove(
|
||||
QRegularExpression("[^\\d]")
|
||||
TextUtilities::RegExpDigitsExclude()
|
||||
).mid(0, getData()->codeLength);
|
||||
|
||||
if (_sentRequest
|
||||
@@ -393,6 +422,22 @@ void CodeWidget::submit() {
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
rpl::producer<QString> CodeWidget::nextButtonText() const {
|
||||
return _isFragment.value(
|
||||
) | rpl::map([=](bool isFragment) {
|
||||
return isFragment
|
||||
? tr::lng_intro_fragment_button()
|
||||
: Step::nextButtonText();
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
rpl::producer<const style::RoundButton*> CodeWidget::nextButtonStyle() const {
|
||||
return _isFragment.value(
|
||||
) | rpl::map([](bool isFragment) {
|
||||
return isFragment ? &st::introFragmentButton : nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void CodeWidget::noTelegramCode() {
|
||||
if (_noTelegramCodeRequestId) {
|
||||
return;
|
||||
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
void finished() override;
|
||||
void cancelled() override;
|
||||
void submit() override;
|
||||
rpl::producer<QString> nextButtonText() const override;
|
||||
rpl::producer<const style::RoundButton*> nextButtonStyle() const override;
|
||||
|
||||
void updateDescText();
|
||||
|
||||
@@ -83,6 +85,8 @@ private:
|
||||
void noTelegramCodeDone(const MTPauth_SentCode &result);
|
||||
void noTelegramCodeFail(const MTP::Error &result);
|
||||
|
||||
void submitCode();
|
||||
|
||||
void stopCheck();
|
||||
|
||||
object_ptr<Ui::LinkButton> _noTelegramCode;
|
||||
@@ -92,6 +96,8 @@ private:
|
||||
QString _sentCode;
|
||||
mtpRequestId _sentRequest = 0;
|
||||
|
||||
rpl::variable<bool> _isFragment = false;
|
||||
|
||||
base::Timer _callTimer;
|
||||
CallStatus _callStatus = CallStatus();
|
||||
int _callTimeout;
|
||||
|
||||
@@ -34,6 +34,8 @@ SignupWidget::SignupWidget(
|
||||
, _first(this, st::introName, tr::lng_signup_firstname())
|
||||
, _last(this, st::introName, tr::lng_signup_lastname())
|
||||
, _invertOrder(langFirstNameGoesSecond()) {
|
||||
_photo->showCustomOnChosen();
|
||||
|
||||
Lang::Updated(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshLang();
|
||||
|
||||
@@ -119,6 +119,10 @@ rpl::producer<QString> Step::nextButtonText() const {
|
||||
return tr::lng_intro_next();
|
||||
}
|
||||
|
||||
rpl::producer<const style::RoundButton*> Step::nextButtonStyle() const {
|
||||
return rpl::single((const style::RoundButton*)(nullptr));
|
||||
}
|
||||
|
||||
void Step::goBack() {
|
||||
if (_goCallback) {
|
||||
_goCallback(nullptr, StackAction::Back, Animate::Back);
|
||||
|
||||
@@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
namespace style {
|
||||
struct RoundButton;
|
||||
} // namespace style;
|
||||
|
||||
namespace Main {
|
||||
class Account;
|
||||
} // namespace Main;
|
||||
@@ -77,6 +81,8 @@ public:
|
||||
|
||||
virtual void submit() = 0;
|
||||
[[nodiscard]] virtual rpl::producer<QString> nextButtonText() const;
|
||||
[[nodiscard]] virtual auto nextButtonStyle() const
|
||||
-> rpl::producer<const style::RoundButton*>;
|
||||
|
||||
[[nodiscard]] int contentLeft() const;
|
||||
[[nodiscard]] int contentTop() const;
|
||||
|
||||
@@ -74,6 +74,7 @@ Widget::Widget(
|
||||
: RpWidget(parent)
|
||||
, _account(account)
|
||||
, _data(details::Data{ .controller = controller })
|
||||
, _nextStyle(&st::introNextButton)
|
||||
, _back(this, object_ptr<Ui::IconButton>(this, st::introBackButton))
|
||||
, _settings(
|
||||
this,
|
||||
@@ -83,7 +84,7 @@ Widget::Widget(
|
||||
st::defaultBoxButton))
|
||||
, _next(
|
||||
this,
|
||||
object_ptr<Ui::RoundButton>(this, nullptr, st::introNextButton))
|
||||
object_ptr<Ui::RoundButton>(this, nullptr, *_nextStyle))
|
||||
, _connecting(std::make_unique<Window::ConnectionState>(
|
||||
this,
|
||||
account,
|
||||
@@ -127,10 +128,6 @@ Widget::Widget(
|
||||
_back->entity()->setClickedCallback([=] { backRequested(); });
|
||||
_back->hide(anim::type::instant);
|
||||
|
||||
_next->entity()->setClickedCallback([=] { getStep()->submit(); });
|
||||
_next->entity()->setTextTransform(
|
||||
Ui::RoundButton::TextTransform::NoTransform);
|
||||
|
||||
if (_changeLanguage) {
|
||||
_changeLanguage->finishAnimating();
|
||||
}
|
||||
@@ -344,13 +341,35 @@ void Widget::historyMove(StackAction action, Animate animate) {
|
||||
if (_terms) {
|
||||
hideAndDestroy(std::exchange(_terms, { nullptr }));
|
||||
}
|
||||
{
|
||||
getStep()->nextButtonStyle(
|
||||
) | rpl::start_with_next([=](const style::RoundButton *st) {
|
||||
const auto nextStyle = st ? st : &st::introNextButton;
|
||||
if (_nextStyle != nextStyle) {
|
||||
_nextStyle = nextStyle;
|
||||
const auto wasShown = _next->toggled();
|
||||
_next.destroy();
|
||||
_next.create(
|
||||
this,
|
||||
object_ptr<Ui::RoundButton>(this, nullptr, *nextStyle));
|
||||
showControls();
|
||||
updateControlsGeometry();
|
||||
_next->toggle(wasShown, anim::type::instant);
|
||||
}
|
||||
}, _next->lifetime());
|
||||
}
|
||||
|
||||
getStep()->finishInit();
|
||||
getStep()->prepareShowAnimated(wasStep);
|
||||
if (wasStep->hasCover() != getStep()->hasCover()) {
|
||||
_nextTopFrom = wasStep->contentTop() + st::introNextTop;
|
||||
_controlsTopFrom = wasStep->hasCover() ? st::introCoverHeight : 0;
|
||||
_coverShownAnimation.start([this] { updateControlsGeometry(); }, 0., 1., st::introCoverDuration, wasStep->hasCover() ? anim::linear : anim::easeOutCirc);
|
||||
_coverShownAnimation.start(
|
||||
[this] { updateControlsGeometry(); },
|
||||
0.,
|
||||
1.,
|
||||
st::introCoverDuration,
|
||||
wasStep->hasCover() ? anim::linear : anim::easeOutCirc);
|
||||
}
|
||||
|
||||
_stepLifetime.destroy();
|
||||
@@ -665,6 +684,10 @@ void Widget::showControls() {
|
||||
}
|
||||
|
||||
void Widget::setupNextButton() {
|
||||
_next->entity()->setClickedCallback([=] { getStep()->submit(); });
|
||||
_next->entity()->setTextTransform(
|
||||
Ui::RoundButton::TextTransform::NoTransform);
|
||||
|
||||
_next->entity()->setText(getStep()->nextButtonText(
|
||||
) | rpl::filter([](const QString &text) {
|
||||
return !text.isEmpty();
|
||||
@@ -757,13 +780,18 @@ void Widget::resizeEvent(QResizeEvent *e) {
|
||||
}
|
||||
|
||||
void Widget::updateControlsGeometry() {
|
||||
auto shown = _coverShownAnimation.value(1.);
|
||||
const auto skip = st::introSettingsSkip;
|
||||
const auto shown = _coverShownAnimation.value(1.);
|
||||
|
||||
auto controlsTopTo = getStep()->hasCover() ? st::introCoverHeight : 0;
|
||||
auto controlsTop = anim::interpolate(_controlsTopFrom, controlsTopTo, shown);
|
||||
_settings->moveToRight(st::introSettingsSkip, controlsTop + st::introSettingsSkip);
|
||||
const auto controlsTop = anim::interpolate(
|
||||
_controlsTopFrom,
|
||||
getStep()->hasCover() ? st::introCoverHeight : 0,
|
||||
shown);
|
||||
_settings->moveToRight(skip, controlsTop + skip);
|
||||
if (_update) {
|
||||
_update->moveToRight(st::introSettingsSkip + _settings->width() + st::introSettingsSkip, _settings->y());
|
||||
_update->moveToRight(
|
||||
skip + _settings->width() + skip,
|
||||
_settings->y());
|
||||
}
|
||||
_back->moveToLeft(0, controlsTop);
|
||||
|
||||
@@ -779,13 +807,19 @@ void Widget::updateControlsGeometry() {
|
||||
? QRect(0, 0, width(), realNextTop)
|
||||
: QRect());
|
||||
if (_changeLanguage) {
|
||||
_changeLanguage->moveToLeft((width() - _changeLanguage->width()) / 2, _next->y() + _next->height() + _changeLanguage->height());
|
||||
_changeLanguage->moveToLeft(
|
||||
(width() - _changeLanguage->width()) / 2,
|
||||
_next->y() + _next->height() + _changeLanguage->height());
|
||||
}
|
||||
if (_resetAccount) {
|
||||
_resetAccount->moveToLeft((width() - _resetAccount->width()) / 2, height() - st::introResetBottom - _resetAccount->height());
|
||||
_resetAccount->moveToLeft(
|
||||
(width() - _resetAccount->width()) / 2,
|
||||
height() - st::introResetBottom - _resetAccount->height());
|
||||
}
|
||||
if (_terms) {
|
||||
_terms->moveToLeft((width() - _terms->width()) / 2, height() - st::introTermsBottom - _terms->height());
|
||||
_terms->moveToLeft(
|
||||
(width() - _terms->width()) / 2,
|
||||
height() - st::introTermsBottom - _terms->height());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ private:
|
||||
int _nextTopFrom = 0;
|
||||
int _controlsTopFrom = 0;
|
||||
|
||||
const style::RoundButton *_nextStyle = nullptr;
|
||||
|
||||
object_ptr<Ui::FadeWrap<Ui::IconButton>> _back;
|
||||
object_ptr<Ui::FadeWrap<Ui::RoundButton>> _update = { nullptr };
|
||||
object_ptr<Ui::FadeWrap<Ui::RoundButton>> _settings;
|
||||
|
||||
@@ -66,5 +66,9 @@ inline QString EmailConfirmationExpired() {
|
||||
return u"This email confirmation has expired. Please setup two-step verification once again."_q;
|
||||
}
|
||||
|
||||
inline QString AutostartEnableError() {
|
||||
return u"Could not register for autostart."_q;
|
||||
}
|
||||
|
||||
} // namespace Hard
|
||||
} // namespace Lang
|
||||
|
||||
@@ -602,6 +602,16 @@ void Account::destroyStaleAuthorizationKeys() {
|
||||
}
|
||||
}
|
||||
|
||||
void Account::setHandleLoginCode(Fn<void(QString)> callback) {
|
||||
_handleLoginCode = std::move(callback);
|
||||
}
|
||||
|
||||
void Account::handleLoginCode(const QString &code) const {
|
||||
if (_handleLoginCode) {
|
||||
_handleLoginCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
void Account::resetAuthorizationKeys() {
|
||||
Expects(_mtp != nullptr);
|
||||
|
||||
|
||||
@@ -110,6 +110,9 @@ public:
|
||||
void suggestMainDcId(MTP::DcId mainDcId);
|
||||
void destroyStaleAuthorizationKeys();
|
||||
|
||||
void setHandleLoginCode(Fn<void(QString)> callback);
|
||||
void handleLoginCode(const QString &code) const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
@@ -152,6 +155,8 @@ private:
|
||||
std::unique_ptr<Session> _session;
|
||||
rpl::variable<Session*> _sessionValue;
|
||||
|
||||
Fn<void(QString)> _handleLoginCode = nullptr;
|
||||
|
||||
UserId _sessionUserId = 0;
|
||||
QByteArray _sessionUserSerialized;
|
||||
int32 _sessionUserStreamVersion = 0;
|
||||
|
||||
@@ -198,7 +198,6 @@ QByteArray Session::validTmpPassword() const {
|
||||
|
||||
// Can be called only right before ~Session.
|
||||
void Session::finishLogout() {
|
||||
updates().updateOnline();
|
||||
unlockTerms();
|
||||
data().clear();
|
||||
data().clearLocalStorage();
|
||||
|
||||
@@ -254,6 +254,7 @@ struct OverlayWidget::PipWrap {
|
||||
|
||||
PipDelegate delegate;
|
||||
Pip wrapped;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
OverlayWidget::Streamed::Streamed(
|
||||
@@ -3334,14 +3335,14 @@ void OverlayWidget::switchToPip() {
|
||||
Expects(_document != nullptr);
|
||||
|
||||
const auto document = _document;
|
||||
const auto message = _message;
|
||||
const auto messageId = _message ? _message->fullId() : FullMsgId();
|
||||
const auto topicRootId = _topicRootId;
|
||||
const auto closeAndContinue = [=] {
|
||||
_showAsPip = false;
|
||||
show(OpenRequest(
|
||||
findWindow(false),
|
||||
document,
|
||||
message,
|
||||
document->owner().message(messageId),
|
||||
topicRootId,
|
||||
true));
|
||||
};
|
||||
@@ -3352,6 +3353,16 @@ void OverlayWidget::switchToPip() {
|
||||
_streamed->instance.shared(),
|
||||
closeAndContinue,
|
||||
[=] { _pip = nullptr; });
|
||||
|
||||
if (const auto raw = _message) {
|
||||
raw->history()->owner().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (raw == item);
|
||||
}) | rpl::start_with_next([=] {
|
||||
_pip = nullptr;
|
||||
}, _pip->lifetime);
|
||||
}
|
||||
|
||||
if (isHidden()) {
|
||||
clearBeforeHide();
|
||||
clearAfterHide();
|
||||
|
||||
@@ -65,7 +65,7 @@ QByteArray DnsUserAgent() {
|
||||
static const auto kResult = QByteArray(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/107.0.5304.110 Safari/537.36");
|
||||
"Chrome/108.0.5359.98 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -1537,6 +1537,7 @@ bool Instance::Private::onErrorDefault(
|
||||
|
||||
const auto session = getSession(qAbs(dcWithShift));
|
||||
request->needsLayer = true;
|
||||
session->setConnectionNotInited();
|
||||
session->sendPrepared(request);
|
||||
return true;
|
||||
} else if (type == u"CONNECTION_LANG_CODE_INVALID"_q) {
|
||||
|
||||
@@ -257,10 +257,14 @@ void Session::refreshOptions() {
|
||||
}
|
||||
|
||||
void Session::reInitConnection() {
|
||||
_dc->setConnectionInited(false);
|
||||
setConnectionNotInited();
|
||||
restart();
|
||||
}
|
||||
|
||||
void Session::setConnectionNotInited() {
|
||||
_dc->setConnectionInited(false);
|
||||
}
|
||||
|
||||
void Session::stop() {
|
||||
if (_killed) {
|
||||
DEBUG_LOG(("Session Error: can't stop a killed session"));
|
||||
|
||||
@@ -142,6 +142,7 @@ public:
|
||||
|
||||
void start();
|
||||
void reInitConnection();
|
||||
void setConnectionNotInited();
|
||||
|
||||
void restart();
|
||||
void refreshOptions();
|
||||
|
||||
@@ -1610,9 +1610,11 @@ Link::Link(
|
||||
}),
|
||||
parent->fullId());
|
||||
} else if (_page->photo) {
|
||||
if (_page->type == WebPageType::Profile || _page->type == WebPageType::Video) {
|
||||
if (_page->type == WebPageType::Profile
|
||||
|| _page->type == WebPageType::Video) {
|
||||
_photol = createHandler(_page->url);
|
||||
} else if (_page->type == WebPageType::Photo
|
||||
|| _page->type == WebPageType::Document
|
||||
|| _page->siteName == u"Twitter"_q
|
||||
|| _page->siteName == u"Facebook"_q) {
|
||||
_photol = std::make_shared<PhotoOpenClickHandler>(
|
||||
|
||||
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "passport/passport_panel_controller.h"
|
||||
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "passport/passport_panel_edit_document.h"
|
||||
#include "passport/passport_panel_edit_contact.h"
|
||||
@@ -485,7 +487,9 @@ EditContactScheme GetContactScheme(Scope::Type type) {
|
||||
return Ui::FormatPhone(value);
|
||||
};
|
||||
result.postprocess = [](QString value) {
|
||||
return value.replace(QRegularExpression("[^\\d]"), QString());
|
||||
return value.replace(
|
||||
TextUtilities::RegExpDigitsExclude(),
|
||||
QString());
|
||||
};
|
||||
return result;
|
||||
} break;
|
||||
@@ -1319,11 +1323,16 @@ void PanelController::processVerificationNeeded(
|
||||
});
|
||||
const auto box = [&] {
|
||||
if (type == Value::Type::Phone) {
|
||||
return show(VerifyPhoneBox(
|
||||
const auto submit = [=](const QString &code) {
|
||||
_form->verify(value, code);
|
||||
};
|
||||
const auto account = &_form->window()->session().account();
|
||||
account->setHandleLoginCode(submit);
|
||||
const auto box = show(VerifyPhoneBox(
|
||||
text,
|
||||
value->verification.codeLength,
|
||||
[=](const QString &code) { _form->verify(value, code); },
|
||||
|
||||
value->verification.fragmentUrl,
|
||||
submit,
|
||||
value->verification.call ? rpl::single(
|
||||
value->verification.call->getText()
|
||||
) | rpl::then(rpl::duplicate(
|
||||
@@ -1339,6 +1348,11 @@ void PanelController::processVerificationNeeded(
|
||||
) | rpl::map([=](not_null<const Value*> field) {
|
||||
return field->verification.error;
|
||||
}) | rpl::distinct_until_changed()));
|
||||
box->boxClosing(
|
||||
) | rpl::start_with_next([=] {
|
||||
account->setHandleLoginCode(nullptr);
|
||||
}, box->lifetime());
|
||||
return box;
|
||||
} else if (type == Value::Type::Email) {
|
||||
return show(VerifyEmailBox(
|
||||
text,
|
||||
|
||||
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "passport/passport_panel_edit_contact.h"
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
#include "passport/passport_panel_controller.h"
|
||||
#include "passport/ui/passport_details_row.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
@@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "countries/countries_instance.h" // Countries::ExtractPhoneCode.
|
||||
#include "main/main_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_passport.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
@@ -39,6 +41,7 @@ public:
|
||||
rpl::producer<QString> title,
|
||||
const QString &text,
|
||||
int codeLength,
|
||||
const QString &openUrl,
|
||||
Fn<void(QString code)> submit,
|
||||
Fn<void()> resend,
|
||||
rpl::producer<QString> call,
|
||||
@@ -54,6 +57,7 @@ private:
|
||||
void setupControls(
|
||||
const QString &text,
|
||||
int codeLength,
|
||||
const QString &openUrl,
|
||||
Fn<void(QString code)> submit,
|
||||
Fn<void()> resend,
|
||||
rpl::producer<QString> call,
|
||||
@@ -72,6 +76,7 @@ VerifyBox::VerifyBox(
|
||||
rpl::producer<QString> title,
|
||||
const QString &text,
|
||||
int codeLength,
|
||||
const QString &openUrl,
|
||||
Fn<void(QString code)> submit,
|
||||
Fn<void()> resend,
|
||||
rpl::producer<QString> call,
|
||||
@@ -81,6 +86,7 @@ VerifyBox::VerifyBox(
|
||||
setupControls(
|
||||
text,
|
||||
codeLength,
|
||||
openUrl,
|
||||
submit,
|
||||
resend,
|
||||
std::move(call),
|
||||
@@ -91,6 +97,7 @@ VerifyBox::VerifyBox(
|
||||
void VerifyBox::setupControls(
|
||||
const QString &text,
|
||||
int codeLength,
|
||||
const QString &openUrl,
|
||||
Fn<void(QString code)> submit,
|
||||
Fn<void()> resend,
|
||||
rpl::producer<QString> call,
|
||||
@@ -130,6 +137,21 @@ void VerifyBox::setupControls(
|
||||
std::move(call),
|
||||
st::boxDividerLabel),
|
||||
small);
|
||||
if (!openUrl.isEmpty()) {
|
||||
const auto button = _content->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
_content,
|
||||
tr::lng_intro_fragment_button(),
|
||||
st::fragmentBoxButton),
|
||||
small);
|
||||
_content->widthValue(
|
||||
) | rpl::start_with_next([=](int w) {
|
||||
button->setFullWidth(w - small.left() - small.right());
|
||||
}, button->lifetime());
|
||||
button->setClickedCallback([=] { ::File::OpenUrl(openUrl); });
|
||||
button->setTextTransform(
|
||||
Ui::RoundButton::TextTransform::NoTransform);
|
||||
}
|
||||
if (resend) {
|
||||
auto link = TextWithEntities{ tr::lng_cloud_password_resend(tr::now) };
|
||||
link.entities.push_back({
|
||||
@@ -144,9 +166,7 @@ void VerifyBox::setupControls(
|
||||
link
|
||||
) | rpl::then(rpl::duplicate(
|
||||
resent
|
||||
) | rpl::map([](const QString &value) {
|
||||
return TextWithEntities{ value };
|
||||
})),
|
||||
) | rpl::map(TextWithEntities::Simple)),
|
||||
st::boxDividerLabel),
|
||||
small);
|
||||
std::move(
|
||||
@@ -392,6 +412,7 @@ void PanelEditContact::save(const QString &value) {
|
||||
object_ptr<Ui::BoxContent> VerifyPhoneBox(
|
||||
const QString &phone,
|
||||
int codeLength,
|
||||
const QString &openUrl,
|
||||
Fn<void(QString code)> submit,
|
||||
rpl::producer<QString> call,
|
||||
rpl::producer<QString> error) {
|
||||
@@ -402,6 +423,7 @@ object_ptr<Ui::BoxContent> VerifyPhoneBox(
|
||||
lt_phone,
|
||||
Ui::FormatPhone(phone)),
|
||||
codeLength,
|
||||
openUrl,
|
||||
submit,
|
||||
nullptr,
|
||||
std::move(call),
|
||||
@@ -420,6 +442,7 @@ object_ptr<Ui::BoxContent> VerifyEmailBox(
|
||||
tr::lng_passport_email_title(),
|
||||
tr::lng_passport_confirm_email(tr::now, lt_email, email),
|
||||
codeLength,
|
||||
QString(),
|
||||
submit,
|
||||
resend,
|
||||
rpl::single(QString()),
|
||||
|
||||
@@ -78,6 +78,7 @@ private:
|
||||
object_ptr<Ui::BoxContent> VerifyPhoneBox(
|
||||
const QString &phone,
|
||||
int codeLength,
|
||||
const QString &openUrl,
|
||||
Fn<void(QString code)> submit,
|
||||
rpl::producer<QString> call,
|
||||
rpl::producer<QString> error);
|
||||
|
||||
@@ -80,7 +80,8 @@ std::unique_ptr<base::Platform::DBus::ServiceWatcher> CreateServiceWatcher() {
|
||||
try {
|
||||
return ranges::contains(
|
||||
base::Platform::DBus::ListActivatableNames(connection),
|
||||
Glib::ustring(std::string(kService)));
|
||||
std::string(kService),
|
||||
&Glib::ustring::raw);
|
||||
} catch (...) {
|
||||
// avoid service restart loop in sandboxed environments
|
||||
return true;
|
||||
@@ -129,9 +130,11 @@ void StartServiceAsync(Fn<void()> callback) {
|
||||
};
|
||||
|
||||
const auto errorName =
|
||||
Gio::DBus::ErrorUtils::get_remote_error(e);
|
||||
Gio::DBus::ErrorUtils::get_remote_error(e).raw();
|
||||
|
||||
if (!ranges::contains(NotSupportedErrors, errorName)) {
|
||||
if (!ranges::contains(
|
||||
NotSupportedErrors,
|
||||
errorName)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -166,7 +169,8 @@ bool GetServiceRegistered() {
|
||||
try {
|
||||
return ranges::contains(
|
||||
DBus::ListActivatableNames(connection),
|
||||
Glib::ustring(std::string(kService)));
|
||||
std::string(kService),
|
||||
&Glib::ustring::raw);
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
@@ -383,12 +387,12 @@ bool NotificationData::init(
|
||||
const QString &msg,
|
||||
Window::Notifications::Manager::DisplayOptions options) {
|
||||
if (_application) {
|
||||
_notification = Gio::Notification::create(title.toStdString());
|
||||
|
||||
_notification->set_body(
|
||||
_notification = Gio::Notification::create(
|
||||
subtitle.isEmpty()
|
||||
? msg.toStdString()
|
||||
: u"%1\n%2"_q.arg(subtitle, msg).toStdString());
|
||||
? title.toStdString()
|
||||
: subtitle.toStdString() + " (" + title.toStdString() + ')');
|
||||
|
||||
_notification->set_body(msg.toStdString());
|
||||
|
||||
_notification->set_icon(
|
||||
Gio::ThemedIcon::create(base::IconName().toStdString()));
|
||||
@@ -498,19 +502,22 @@ bool NotificationData::init(
|
||||
});
|
||||
};
|
||||
|
||||
_title = title.toStdString();
|
||||
_imageKey = GetImageKey(CurrentServerInformationValue().specVersion);
|
||||
|
||||
if (capabilities.contains(u"body-markup"_q)) {
|
||||
_title = title.toStdString();
|
||||
|
||||
_body = subtitle.isEmpty()
|
||||
? msg.toHtmlEscaped().toStdString()
|
||||
: u"<b>%1</b>\n%2"_q.arg(
|
||||
subtitle.toHtmlEscaped(),
|
||||
msg.toHtmlEscaped()).toStdString();
|
||||
} else {
|
||||
_body = subtitle.isEmpty()
|
||||
? msg.toStdString()
|
||||
: u"%1\n%2"_q.arg(subtitle, msg).toStdString();
|
||||
_title = subtitle.isEmpty()
|
||||
? title.toStdString()
|
||||
: subtitle.toStdString() + " (" + title.toStdString() + ')';
|
||||
|
||||
_body = msg.toStdString();
|
||||
}
|
||||
|
||||
if (capabilities.contains("actions")) {
|
||||
@@ -529,12 +536,13 @@ bool NotificationData::init(
|
||||
_actions.push_back(
|
||||
tr::lng_notification_reply(tr::now).toStdString());
|
||||
|
||||
_notificationRepliedSignalId = _dbusConnection->signal_subscribe(
|
||||
signalEmitted,
|
||||
std::string(kService),
|
||||
std::string(kInterface),
|
||||
"NotificationReplied",
|
||||
std::string(kObjectPath));
|
||||
_notificationRepliedSignalId =
|
||||
_dbusConnection->signal_subscribe(
|
||||
signalEmitted,
|
||||
std::string(kService),
|
||||
std::string(kInterface),
|
||||
"NotificationReplied",
|
||||
std::string(kObjectPath));
|
||||
} else {
|
||||
// icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
|
||||
_actions.push_back("mail-reply-sender");
|
||||
@@ -818,8 +826,6 @@ bool ByDefault() {
|
||||
static const auto NeededCapabilities = {
|
||||
// To show message content
|
||||
u"body"_q,
|
||||
// To make the sender name bold
|
||||
u"body-markup"_q,
|
||||
// To have buttons on notifications
|
||||
u"actions"_q,
|
||||
// To have quick reply
|
||||
@@ -892,7 +898,8 @@ void Create(Window::Notifications::System *system) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetServerInformation([=](const std::optional<ServerInformation> &result) {
|
||||
GetServerInformation([=](
|
||||
const std::optional<ServerInformation> &result) {
|
||||
CurrentServerInformation = result;
|
||||
oneReady();
|
||||
});
|
||||
|
||||
@@ -119,11 +119,13 @@ namespace {
|
||||
constexpr auto kDesktopFile = ":/misc/org.telegram.desktop.desktop"_cs;
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
void PortalAutostart(bool start, bool silent) {
|
||||
bool PortalAutostart(bool start, bool silent) {
|
||||
if (cExeName().isEmpty()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto error = false;
|
||||
|
||||
try {
|
||||
const auto connection = Gio::DBus::Connection::get_sync(
|
||||
Gio::DBus::BusType::SESSION);
|
||||
@@ -182,14 +184,18 @@ void PortalAutostart(bool start, bool silent) {
|
||||
const auto response = base::Platform::GlibVariantCast<
|
||||
uint>(parameters.get_child(0));
|
||||
|
||||
if (response && !silent) {
|
||||
LOG(("Portal Autostart Error: Request denied"));
|
||||
if (response) {
|
||||
if (!silent) {
|
||||
LOG(("Portal Autostart Error: Request denied"));
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
if (!silent) {
|
||||
LOG(("Portal Autostart Error: %1").arg(
|
||||
QString::fromStdString(e.what())));
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
|
||||
loop->quit();
|
||||
@@ -227,7 +233,10 @@ void PortalAutostart(bool start, bool silent) {
|
||||
LOG(("Portal Autostart Error: %1").arg(
|
||||
QString::fromStdString(e.what())));
|
||||
}
|
||||
error = true;
|
||||
}
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
void LaunchGApplication() {
|
||||
@@ -264,7 +273,8 @@ void LaunchGApplication() {
|
||||
|
||||
if (ranges::contains(
|
||||
activatableNames,
|
||||
"org.freedesktop.Notifications")) {
|
||||
"org.freedesktop.Notifications",
|
||||
&Glib::ustring::raw)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -619,25 +629,31 @@ bool AutostartSupported() {
|
||||
|
||||
void AutostartToggle(bool enabled, Fn<void(bool)> done) {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
const auto guard = gsl::finally([&] {
|
||||
if (done) {
|
||||
done(enabled);
|
||||
}
|
||||
});
|
||||
const auto success = [&] {
|
||||
const auto silent = !done;
|
||||
|
||||
if (KSandbox::isFlatpak()) {
|
||||
return PortalAutostart(enabled, silent);
|
||||
}
|
||||
|
||||
const auto silent = !done;
|
||||
if (KSandbox::isFlatpak()) {
|
||||
PortalAutostart(enabled, silent);
|
||||
} else {
|
||||
const auto autostart = QStandardPaths::writableLocation(
|
||||
QStandardPaths::GenericConfigLocation)
|
||||
+ u"/autostart/"_q;
|
||||
|
||||
if (enabled) {
|
||||
GenerateDesktopFile(autostart, { u"-autostart"_q }, true, silent);
|
||||
} else {
|
||||
QFile::remove(autostart + QGuiApplication::desktopFileName());
|
||||
if (!enabled) {
|
||||
return QFile::remove(
|
||||
autostart + QGuiApplication::desktopFileName());
|
||||
}
|
||||
|
||||
return GenerateDesktopFile(
|
||||
autostart,
|
||||
{ u"-autostart"_q },
|
||||
true,
|
||||
silent);
|
||||
}();
|
||||
|
||||
if (done) {
|
||||
done(enabled && success);
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
}
|
||||
|
||||
@@ -142,51 +142,78 @@ void DeleteMyModules() {
|
||||
RemoveDirectory(modules.c_str());
|
||||
}
|
||||
|
||||
void ManageAppLink(bool create, bool silent, int path_csidl, const wchar_t *args, const wchar_t *description) {
|
||||
bool ManageAppLink(
|
||||
bool create,
|
||||
bool silent,
|
||||
const GUID &folderId,
|
||||
const wchar_t *args,
|
||||
const wchar_t *description) {
|
||||
if (cExeName().isEmpty()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
WCHAR startupFolder[MAX_PATH];
|
||||
HRESULT hr = SHGetFolderPath(0, path_csidl, 0, SHGFP_TYPE_CURRENT, startupFolder);
|
||||
if (SUCCEEDED(hr)) {
|
||||
QString lnk = QString::fromWCharArray(startupFolder) + '\\' + AppFile.utf16() + u".lnk"_q;
|
||||
if (create) {
|
||||
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
|
||||
CLSID_ShellLink,
|
||||
CLSCTX_INPROC_SERVER);
|
||||
if (shellLink) {
|
||||
QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
|
||||
shellLink->SetArguments(args);
|
||||
shellLink->SetPath(exe.toStdWString().c_str());
|
||||
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
|
||||
shellLink->SetDescription(description);
|
||||
|
||||
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
|
||||
PROPVARIANT appIdPropVar;
|
||||
hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->SetValue(AppUserModelId::getKey(), appIdPropVar);
|
||||
PropVariantClear(&appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto persistFile = shellLink.try_as<IPersistFile>()) {
|
||||
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
|
||||
} else {
|
||||
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
|
||||
}
|
||||
} else {
|
||||
if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr));
|
||||
}
|
||||
} else {
|
||||
QFile::remove(lnk);
|
||||
PWSTR startupFolder;
|
||||
HRESULT hr = SHGetKnownFolderPath(
|
||||
folderId,
|
||||
KF_FLAG_CREATE,
|
||||
nullptr,
|
||||
&startupFolder);
|
||||
const auto guard = gsl::finally([&] {
|
||||
CoTaskMemFree(startupFolder);
|
||||
});
|
||||
if (!SUCCEEDED(hr)) {
|
||||
WCHAR buffer[64];
|
||||
const auto size = base::array_size(buffer) - 1;
|
||||
const auto length = StringFromGUID2(folderId, buffer, size);
|
||||
if (length > 0 && length <= size) {
|
||||
buffer[length] = 0;
|
||||
if (!silent) LOG(("App Error: could not get %1 folder: %2").arg(buffer).arg(hr));
|
||||
}
|
||||
} else {
|
||||
if (!silent) LOG(("App Error: could not get CSIDL %1 folder %2").arg(path_csidl).arg(hr));
|
||||
return false;
|
||||
}
|
||||
const auto lnk = QString::fromWCharArray(startupFolder)
|
||||
+ '\\'
|
||||
+ AppFile.utf16()
|
||||
+ u".lnk"_q;
|
||||
if (!create) {
|
||||
QFile::remove(lnk);
|
||||
return true;
|
||||
}
|
||||
const auto shellLink = base::WinRT::TryCreateInstance<IShellLink>(
|
||||
CLSID_ShellLink,
|
||||
CLSCTX_INPROC_SERVER);
|
||||
if (!shellLink) {
|
||||
if (!silent) LOG(("App Error: could not create instance of IID_IShellLink %1").arg(hr));
|
||||
return false;
|
||||
}
|
||||
QString exe = QDir::toNativeSeparators(cExeDir() + cExeName()), dir = QDir::toNativeSeparators(QDir(cWorkingDir()).absolutePath());
|
||||
shellLink->SetArguments(args);
|
||||
shellLink->SetPath(exe.toStdWString().c_str());
|
||||
shellLink->SetWorkingDirectory(dir.toStdWString().c_str());
|
||||
shellLink->SetDescription(description);
|
||||
|
||||
if (const auto propertyStore = shellLink.try_as<IPropertyStore>()) {
|
||||
PROPVARIANT appIdPropVar;
|
||||
hr = InitPropVariantFromString(AppUserModelId::getId(), &appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->SetValue(AppUserModelId::getKey(), appIdPropVar);
|
||||
PropVariantClear(&appIdPropVar);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = propertyStore->Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto persistFile = shellLink.try_as<IPersistFile>();
|
||||
if (!persistFile) {
|
||||
if (!silent) LOG(("App Error: could not create interface IID_IPersistFile %1").arg(hr));
|
||||
return false;
|
||||
}
|
||||
hr = persistFile->Save(lnk.toStdWString().c_str(), TRUE);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
if (!silent) LOG(("App Error: could not save IPersistFile to path %1").arg(lnk));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -415,9 +442,15 @@ void AutostartToggle(bool enabled, Fn<void(bool)> done) {
|
||||
done ? Fn<void(bool)>(callback) : nullptr);
|
||||
#else // OS_WIN_STORE
|
||||
const auto silent = !done;
|
||||
ManageAppLink(enabled, silent, CSIDL_STARTUP, L"-autostart", L"Telegram autorun link.\nYou can disable autorun in Telegram settings.");
|
||||
const auto success = ManageAppLink(
|
||||
enabled,
|
||||
silent,
|
||||
FOLDERID_Startup,
|
||||
L"-autostart",
|
||||
L"Telegram autorun link.\n"
|
||||
"You can disable autorun in Telegram settings.");
|
||||
if (done) {
|
||||
done(enabled);
|
||||
done(enabled && success);
|
||||
}
|
||||
#endif // OS_WIN_STORE
|
||||
}
|
||||
@@ -586,7 +619,13 @@ void NewVersionLaunched(int oldVersion) {
|
||||
} // namespace Platform
|
||||
|
||||
void psSendToMenu(bool send, bool silent) {
|
||||
ManageAppLink(send, silent, CSIDL_SENDTO, L"-sendpath", L"Telegram send to link.\nYou can disable send to menu item in Telegram settings.");
|
||||
ManageAppLink(
|
||||
send,
|
||||
silent,
|
||||
FOLDERID_SendTo,
|
||||
L"-sendpath",
|
||||
L"Telegram send to link.\n"
|
||||
"You can disable send to menu item in Telegram settings.");
|
||||
}
|
||||
|
||||
bool psLaunchMaps(const Data::LocationPoint &point) {
|
||||
|
||||
@@ -500,10 +500,17 @@ void SetupSystemIntegrationContent(
|
||||
) | rpl::filter([](bool checked) {
|
||||
return (checked != cAutoStart());
|
||||
}) | rpl::start_with_next([=](bool checked) {
|
||||
const auto weak = base::make_weak(controller);
|
||||
cSetAutoStart(checked);
|
||||
Platform::AutostartToggle(checked, crl::guard(autostart, [=](
|
||||
bool enabled) {
|
||||
autostart->setChecked(enabled);
|
||||
if (checked && !enabled && weak) {
|
||||
weak->window().showToast(
|
||||
Lang::Hard::AutostartEnableError());
|
||||
}
|
||||
Ui::PostponeCall(autostart, [=] {
|
||||
autostart->setChecked(enabled);
|
||||
});
|
||||
if (enabled || !minimized->entity()->checked()) {
|
||||
Local::writeSettings();
|
||||
} else {
|
||||
|
||||
@@ -1055,7 +1055,9 @@ void SetupDataStorage(
|
||||
auto pathtext = Core::App().settings().downloadPathValue(
|
||||
) | rpl::map([](const QString &text) {
|
||||
if (text.isEmpty()) {
|
||||
return tr::lng_download_path_default(tr::now);
|
||||
return Core::App().canReadDefaultDownloadPath(true)
|
||||
? tr::lng_download_path_default(tr::now)
|
||||
: tr::lng_download_path_unset(tr::now);
|
||||
} else if (text == FileDialog::Tmp()) {
|
||||
return tr::lng_download_path_temp(tr::now);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "storage/localimageloader.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
@@ -142,6 +143,7 @@ void SetupExperimental(
|
||||
addToggle(Ui::GL::kOptionAllowLinuxNvidiaOpenGL);
|
||||
addToggle(Ui::kOptionUseSmallMsgBubbleRadius);
|
||||
addToggle(Media::Player::kOptionDisableAutoplayNext);
|
||||
addToggle(kOptionSendLargePhotos);
|
||||
addToggle(Settings::kOptionMonoSettingsIcons);
|
||||
addToggle(Webview::kOptionWebviewDebugEnabled);
|
||||
addToggle(kOptionAutoScrollInactiveChat);
|
||||
|
||||
@@ -1163,9 +1163,10 @@ object_ptr<Ui::RpWidget> ProfilePhotoPrivacyController::setupBelowWidget(
|
||||
|
||||
_saveAdditional = [=] {
|
||||
if (removeButton->isHidden()) {
|
||||
const auto photoId = SyncUserFallbackPhotoViewer(self);
|
||||
if (const auto photo = self->owner().photo(*photoId)) {
|
||||
controller->session().api().peerPhoto().clear(photo);
|
||||
if (const auto photoId = SyncUserFallbackPhotoViewer(self)) {
|
||||
if (const auto photo = self->owner().photo(*photoId)) {
|
||||
controller->session().api().peerPhoto().clear(photo);
|
||||
}
|
||||
}
|
||||
} else if (!state->localOriginal.isNull()) {
|
||||
controller->session().api().peerPhoto().uploadFallback(
|
||||
|
||||
@@ -280,22 +280,27 @@ void SetupPrivacy(
|
||||
{ &st::settingsIconOnline, kIconLightBlue },
|
||||
Key::LastSeen,
|
||||
[=] { return std::make_unique<LastSeenPrivacyController>(session); });
|
||||
add(
|
||||
tr::lng_settings_profile_photo_privacy(),
|
||||
{ &st::settingsIconAccount, kIconRed },
|
||||
Key::ProfilePhoto,
|
||||
[] { return std::make_unique<ProfilePhotoPrivacyController>(); });
|
||||
add(
|
||||
tr::lng_settings_forwards_privacy(),
|
||||
{ &st::settingsIconForward, kIconLightOrange },
|
||||
Key::Forwards,
|
||||
[=] { return std::make_unique<ForwardsPrivacyController>(
|
||||
controller); });
|
||||
add(
|
||||
tr::lng_settings_profile_photo_privacy(),
|
||||
{ &st::settingsIconAccount, kIconRed },
|
||||
Key::ProfilePhoto,
|
||||
[] { return std::make_unique<ProfilePhotoPrivacyController>(); });
|
||||
add(
|
||||
tr::lng_settings_calls(),
|
||||
{ &st::settingsIconVideoCalls, kIconGreen },
|
||||
Key::Calls,
|
||||
[] { return std::make_unique<CallsPrivacyController>(); });
|
||||
add(
|
||||
tr::lng_settings_groups_invite(),
|
||||
{ &st::settingsIconGroup, kIconDarkBlue },
|
||||
Key::Invites,
|
||||
[] { return std::make_unique<GroupsInvitePrivacyController>(); });
|
||||
AddPremiumPrivacyButton(
|
||||
controller,
|
||||
container,
|
||||
@@ -303,16 +308,11 @@ void SetupPrivacy(
|
||||
{ &st::settingsPremiumIconVoice, kIconRed },
|
||||
Key::Voices,
|
||||
[=] { return std::make_unique<VoicesPrivacyController>(session); });
|
||||
add(
|
||||
tr::lng_settings_groups_invite(),
|
||||
{ &st::settingsIconGroup, kIconDarkBlue },
|
||||
Key::Invites,
|
||||
[] { return std::make_unique<GroupsInvitePrivacyController>(); });
|
||||
|
||||
session->api().userPrivacy().reload(Api::UserPrivacy::Key::AddedByPhone);
|
||||
|
||||
AddSkip(container, st::settingsPrivacySecurityPadding);
|
||||
AddDividerText(container, tr::lng_settings_group_privacy_about());
|
||||
AddDivider(container);
|
||||
}
|
||||
|
||||
void SetupArchiveAndMute(
|
||||
@@ -865,10 +865,10 @@ void PrivacySecurity::setupContent(
|
||||
return rpl::duplicate(updateOnTick);
|
||||
};
|
||||
|
||||
SetupPrivacy(controller, content, trigger());
|
||||
SetupSecurity(controller, content, trigger(), [=](Type type) {
|
||||
_showOther.fire_copy(type);
|
||||
});
|
||||
SetupPrivacy(controller, content, trigger());
|
||||
#if !defined OS_MAC_STORE && !defined OS_WIN_STORE
|
||||
SetupSensitiveContent(controller, content, trigger());
|
||||
#else // !OS_MAC_STORE && !OS_WIN_STORE
|
||||
|
||||
@@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_user.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "core/mime_type.h"
|
||||
#include "base/options.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/random.h"
|
||||
#include "editor/scene/scene_item_sticker.h"
|
||||
@@ -49,6 +50,13 @@ constexpr auto kRecompressAfterBpp = 4;
|
||||
|
||||
using Ui::ValidateThumbDimensions;
|
||||
|
||||
base::options::toggle SendLargePhotos({
|
||||
.id = kOptionSendLargePhotos,
|
||||
.name = "Send large photos",
|
||||
.description = "Increase the side limit on compressed images to 2560px.",
|
||||
});
|
||||
std::atomic<bool> SendLargePhotosAtomic/* = false*/;
|
||||
|
||||
struct PreparedFileThumbnail {
|
||||
uint64 id = 0;
|
||||
QString name;
|
||||
@@ -191,8 +199,22 @@ struct PreparedFileThumbnail {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PhotoSideLimit(bool large) {
|
||||
return large ? 2560 : 1280;
|
||||
}
|
||||
|
||||
[[nodiscard]] int PhotoSideLimitAtomic() {
|
||||
return PhotoSideLimit(SendLargePhotosAtomic.load());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionSendLargePhotos[] = "send-large-photos";
|
||||
|
||||
int PhotoSideLimit() {
|
||||
return PhotoSideLimit(SendLargePhotos.value());
|
||||
}
|
||||
|
||||
SendMediaPrepare::SendMediaPrepare(
|
||||
const QString &file,
|
||||
const PeerId &peer,
|
||||
@@ -518,7 +540,6 @@ void FileLoadResult::setThumbData(const QByteArray &thumbdata) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FileLoadTask::FileLoadTask(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &filepath,
|
||||
@@ -543,6 +564,8 @@ FileLoadTask::FileLoadTask(
|
||||
Expects(to.options.scheduled
|
||||
|| !to.replaceMediaOf
|
||||
|| IsServerMsgId(to.replaceMediaOf));
|
||||
|
||||
SendLargePhotosAtomic = SendLargePhotos.value();
|
||||
}
|
||||
|
||||
FileLoadTask::FileLoadTask(
|
||||
@@ -942,8 +965,9 @@ void FileLoadTask::process(Args &&args) {
|
||||
}
|
||||
auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||
|
||||
const auto downscaled = (w > 1280 || h > 1280);
|
||||
auto full = downscaled ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||
const auto limit = PhotoSideLimitAtomic();
|
||||
const auto downscaled = (w > limit || h > limit);
|
||||
auto full = downscaled ? fullimage.scaled(limit, limit, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
|
||||
if (downscaled) {
|
||||
fullimagebytes = fullimageformat = QByteArray();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ constexpr auto kFileSizeLimit = 2'000 * int64(1024 * 1024);
|
||||
// Load files up to 4'000 MB.
|
||||
constexpr auto kFileSizePremiumLimit = 4'000 * int64(1024 * 1024);
|
||||
|
||||
extern const char kOptionSendLargePhotos[];
|
||||
|
||||
[[nodiscard]] int PhotoSideLimit();
|
||||
|
||||
enum class SendMediaType {
|
||||
Photo,
|
||||
Audio,
|
||||
|
||||
@@ -47,13 +47,10 @@ bool ValidVideoForAlbum(const PreparedFileInformation::Video &video) {
|
||||
return Ui::ValidateThumbDimensions(width, height);
|
||||
}
|
||||
|
||||
QSize PrepareShownDimensions(const QImage &preview) {
|
||||
constexpr auto kMaxWidth = 1280;
|
||||
constexpr auto kMaxHeight = 1280;
|
||||
|
||||
QSize PrepareShownDimensions(const QImage &preview, int sideLimit) {
|
||||
const auto result = preview.size();
|
||||
return (result.width() > kMaxWidth || result.height() > kMaxHeight)
|
||||
? result.scaled(kMaxWidth, kMaxHeight, Qt::KeepAspectRatio)
|
||||
return (result.width() > sideLimit || result.height() > sideLimit)
|
||||
? result.scaled(sideLimit, sideLimit, Qt::KeepAspectRatio)
|
||||
: result;
|
||||
}
|
||||
|
||||
@@ -63,10 +60,11 @@ void PrepareDetailsInParallel(PreparedList &result, int previewWidth) {
|
||||
if (result.files.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto sideLimit = PhotoSideLimit(); // Get on main thread.
|
||||
QSemaphore semaphore;
|
||||
for (auto &file : result.files) {
|
||||
crl::async([=, &semaphore, &file] {
|
||||
PrepareDetails(file, previewWidth);
|
||||
PrepareDetails(file, previewWidth, sideLimit);
|
||||
semaphore.release();
|
||||
});
|
||||
}
|
||||
@@ -272,7 +270,7 @@ std::optional<PreparedList> PreparedFileFromFilesDialog(
|
||||
}
|
||||
}
|
||||
|
||||
void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
void PrepareDetails(PreparedFile &file, int previewWidth, int sideLimit) {
|
||||
if (!file.path.isEmpty()) {
|
||||
file.information = FileLoadTask::ReadMediaInformation(
|
||||
file.path,
|
||||
@@ -293,7 +291,7 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
&file.information->media)) {
|
||||
Assert(!image->data.isNull());
|
||||
if (ValidPhotoForAlbum(*image, file.information->filemime)) {
|
||||
UpdateImageDetails(file, previewWidth);
|
||||
UpdateImageDetails(file, previewWidth, sideLimit);
|
||||
file.type = PreparedFile::Type::Photo;
|
||||
} else if (image->animated) {
|
||||
file.type = PreparedFile::Type::None;
|
||||
@@ -303,7 +301,10 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
if (ValidVideoForAlbum(*video)) {
|
||||
auto blurred = Images::Blur(
|
||||
Images::Opaque(base::duplicate(video->thumbnail)));
|
||||
file.shownDimensions = PrepareShownDimensions(video->thumbnail);
|
||||
file.originalDimensions = video->thumbnail.size();
|
||||
file.shownDimensions = PrepareShownDimensions(
|
||||
video->thumbnail,
|
||||
sideLimit);
|
||||
file.preview = std::move(blurred).scaledToWidth(
|
||||
previewWidth * cIntRetinaFactor(),
|
||||
Qt::SmoothTransformation);
|
||||
@@ -316,7 +317,10 @@ void PrepareDetails(PreparedFile &file, int previewWidth) {
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateImageDetails(PreparedFile &file, int previewWidth) {
|
||||
void UpdateImageDetails(
|
||||
PreparedFile &file,
|
||||
int previewWidth,
|
||||
int sideLimit) {
|
||||
const auto image = std::get_if<Image>(&file.information->media);
|
||||
if (!image) {
|
||||
return;
|
||||
@@ -326,7 +330,8 @@ void UpdateImageDetails(PreparedFile &file, int previewWidth) {
|
||||
? Editor::ImageModified(image->data, image->modifications)
|
||||
: image->data;
|
||||
Assert(!preview.isNull());
|
||||
file.shownDimensions = PrepareShownDimensions(preview);
|
||||
file.originalDimensions = preview.size();
|
||||
file.shownDimensions = PrepareShownDimensions(preview, sideLimit);
|
||||
const auto toWidth = std::min(
|
||||
previewWidth,
|
||||
style::ConvertScale(preview.width())
|
||||
|
||||
@@ -51,8 +51,11 @@ enum class MimeDataState {
|
||||
QImage &&image,
|
||||
QByteArray &&content,
|
||||
int previewWidth);
|
||||
void PrepareDetails(Ui::PreparedFile &file, int previewWidth);
|
||||
void UpdateImageDetails(Ui::PreparedFile &file, int previewWidth);
|
||||
void PrepareDetails(Ui::PreparedFile &file, int previewWidth, int sideLimit);
|
||||
void UpdateImageDetails(
|
||||
Ui::PreparedFile &file,
|
||||
int previewWidth,
|
||||
int sideLimit);
|
||||
|
||||
bool ApplyModifications(Ui::PreparedList &list);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user