Merge tag 'v4.16.6' into dev

# Conflicts:
#	Telegram/Resources/winrc/Telegram.rc
#	Telegram/Resources/winrc/Updater.rc
#	Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
#	Telegram/SourceFiles/core/version.h
#	Telegram/SourceFiles/info/profile/info_profile_actions.cpp
#	Telegram/SourceFiles/mtproto/facade.h
#	Telegram/SourceFiles/storage/file_upload.cpp
#	Telegram/SourceFiles/storage/file_upload.h
#	Telegram/SourceFiles/window/window_main_menu.cpp
#	Telegram/lib_ui
#	snap/snapcraft.yaml
This commit is contained in:
AlexeyZavar 2024-04-12 14:34:30 +03:00
commit 6a02bd66a9
233 changed files with 2525 additions and 21320 deletions

View file

@ -52,7 +52,7 @@ jobs:
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION" - "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
env: env:
UPLOAD_ARTIFACT: "false" UPLOAD_ARTIFACT: "true"
steps: steps:
- name: Get repository name. - name: Get repository name.
@ -115,8 +115,8 @@ jobs:
if: env.UPLOAD_ARTIFACT == 'true' if: env.UPLOAD_ARTIFACT == 'true'
run: | run: |
cd $REPO_NAME/out/Debug cd $REPO_NAME/out/Debug
mkdir artifact sudo mkdir artifact
mv {Telegram,Updater} artifact/ sudo mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@master - uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true' if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact. name: Upload artifact.

View file

@ -47,7 +47,7 @@ jobs:
defines: defines:
- "" - ""
env: env:
UPLOAD_ARTIFACT: "false" UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false" ONLY_CACHE: "false"
PREPARE_PATH: "Telegram/build/prepare/prepare.py" PREPARE_PATH: "Telegram/build/prepare/prepare.py"

View file

@ -50,7 +50,7 @@ jobs:
env: env:
GIT: "https://github.com" GIT: "https://github.com"
OPENALDIR: "/usr/local/opt/openal-soft" OPENALDIR: "/usr/local/opt/openal-soft"
UPLOAD_ARTIFACT: "false" UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false" ONLY_CACHE: "false"
MANUAL_CACHING: "1" MANUAL_CACHING: "1"
AUTO_CACHING: "1" AUTO_CACHING: "1"

View file

@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
env: env:
UPLOAD_ARTIFACT: "false" UPLOAD_ARTIFACT: "true"
steps: steps:
- name: Clone. - name: Clone.

View file

@ -50,7 +50,7 @@ jobs:
generator: ["", "Ninja Multi-Config"] generator: ["", "Ninja Multi-Config"]
env: env:
UPLOAD_ARTIFACT: "false" UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false" ONLY_CACHE: "false"
PREPARE_PATH: "Telegram/build/prepare/prepare.py" PREPARE_PATH: "Telegram/build/prepare/prepare.py"

3
.gitmodules vendored
View file

@ -76,6 +76,9 @@
[submodule "Telegram/lib_webview"] [submodule "Telegram/lib_webview"]
path = Telegram/lib_webview path = Telegram/lib_webview
url = https://github.com/desktop-app/lib_webview.git url = https://github.com/desktop-app/lib_webview.git
[submodule "Telegram/ThirdParty/jemalloc"]
path = Telegram/ThirdParty/jemalloc
url = https://github.com/jemalloc/jemalloc
[submodule "Telegram/ThirdParty/dispatch"] [submodule "Telegram/ThirdParty/dispatch"]
path = Telegram/ThirdParty/dispatch path = Telegram/ThirdParty/dispatch
url = https://github.com/apple/swift-corelibs-libdispatch url = https://github.com/apple/swift-corelibs-libdispatch

View file

@ -1574,6 +1574,8 @@ PRIVATE
window/window_lock_widgets.h window/window_lock_widgets.h
window/window_main_menu.cpp window/window_main_menu.cpp
window/window_main_menu.h window/window_main_menu.h
window/window_main_menu_helpers.cpp
window/window_main_menu_helpers.h
window/window_media_preview.cpp window/window_media_preview.cpp
window/window_media_preview.h window/window_media_preview.h
window/window_peer_menu.cpp window/window_peer_menu.cpp

Binary file not shown.

Binary file not shown.

View file

@ -19,6 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_set_status" = "Set Emoji Status"; "lng_menu_set_status" = "Set Emoji Status";
"lng_menu_change_status" = "Change Emoji Status"; "lng_menu_change_status" = "Change Emoji Status";
"lng_menu_my_stories" = "My Stories"; "lng_menu_my_stories" = "My Stories";
"lng_menu_my_groups" = "My Groups";
"lng_menu_my_channels" = "My Channels";
"lng_disable_notifications_from_tray" = "Disable notifications"; "lng_disable_notifications_from_tray" = "Disable notifications";
"lng_enable_notifications_from_tray" = "Enable notifications"; "lng_enable_notifications_from_tray" = "Enable notifications";
@ -469,6 +471,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bio_placeholder" = "Bio"; "lng_bio_placeholder" = "Bio";
"lng_collectible_username_title" = "{username} is a collectible username that belongs to";
"lng_collectible_username_info" = "This username was bought on **Fragment** on {date} for {price}";
"lng_collectible_username_copy" = "Copy Link";
"lng_collectible_phone_title" = "{phone} is a collectible phone number that belongs to";
"lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}";
"lng_collectible_phone_copy" = "Copy Phone Number";
"lng_collectible_learn_more" = "Learn More";
"lng_settings_section_info" = "My info"; "lng_settings_section_info" = "My info";
"lng_settings_section_notify" = "Notifications"; "lng_settings_section_notify" = "Notifications";
@ -1058,6 +1068,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_faq_button" = "Go to FAQ"; "lng_settings_faq_button" = "Go to FAQ";
"lng_settings_ask_ok" = "Ask a Volunteer"; "lng_settings_ask_ok" = "Ask a Volunteer";
"lng_settings_faq" = "Telegram FAQ"; "lng_settings_faq" = "Telegram FAQ";
"lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
"lng_settings_features" = "Telegram Features"; "lng_settings_features" = "Telegram Features";
"lng_settings_logout" = "Log Out"; "lng_settings_logout" = "Log Out";
"lng_sure_logout" = "Are you sure you want to log out?"; "lng_sure_logout" = "Are you sure you want to log out?";
@ -5040,6 +5051,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_learn_coin_title" = "What is {emoji} TON?"; "lng_channel_earn_learn_coin_title" = "What is {emoji} TON?";
"lng_channel_earn_learn_coin_about" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}"; "lng_channel_earn_learn_coin_about" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
"lng_channel_earn_learn_close" = "Got it"; "lng_channel_earn_learn_close" = "Got it";
"lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
"lng_channel_earn_chart_top_hours" = "Ad impressions"; "lng_channel_earn_chart_top_hours" = "Ad impressions";
"lng_channel_earn_chart_revenue" = "Ad revenue"; "lng_channel_earn_chart_revenue" = "Ad revenue";
"lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON"; "lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON";

View file

@ -22,5 +22,7 @@
<file alias="hours.tgs">../../animations/hours.tgs</file> <file alias="hours.tgs">../../animations/hours.tgs</file>
<file alias="phone.tgs">../../animations/phone.tgs</file> <file alias="phone.tgs">../../animations/phone.tgs</file>
<file alias="chat_link.tgs">../../animations/chat_link.tgs</file> <file alias="chat_link.tgs">../../animations/chat_link.tgs</file>
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop" <Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE" ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A" Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="4.16.0.0" /> Version="4.16.6.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View file

@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,16,0,0 FILEVERSION 4,16,6,0
PRODUCTVERSION 4,16,0,0 PRODUCTVERSION 4,16,6,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -62,10 +62,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop" VALUE "FileDescription", "AyuGram Desktop"
VALUE "FileVersion", "4.16.0.0" VALUE "FileVersion", "4.16.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.16.0.0" VALUE "ProductVersion", "4.16.6.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 4,16,0,0 FILEVERSION 4,16,6,0
PRODUCTVERSION 4,16,0,0 PRODUCTVERSION 4,16,6,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -53,10 +53,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Radolyn Labs" VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater" VALUE "FileDescription", "AyuGram Desktop Updater"
VALUE "FileVersion", "4.16.0.0" VALUE "FileVersion", "4.16.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024" VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop" VALUE "ProductName", "AyuGram Desktop"
VALUE "ProductVersion", "4.16.0.0" VALUE "ProductVersion", "4.16.6.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View file

@ -34,7 +34,7 @@ namespace {
constexpr auto kSharedMediaLimit = 100; constexpr auto kSharedMediaLimit = 100;
[[nodiscard]] SendMediaReady PreparePeerPhoto( [[nodiscard]] std::shared_ptr<FilePrepareResult> PreparePeerPhoto(
MTP::DcId dcId, MTP::DcId dcId,
PeerId peerId, PeerId peerId,
QImage &&image) { QImage &&image) {
@ -80,24 +80,17 @@ constexpr auto kSharedMediaLimit = 100;
MTPVector<MTPVideoSize>(), MTPVector<MTPVideoSize>(),
MTP_int(dcId)); MTP_int(dcId));
QString file, filename; auto result = MakePreparedFile({
int64 filesize = 0; .id = id,
QByteArray data; .type = SendMediaType::Photo,
});
return SendMediaReady( result->type = SendMediaType::Photo;
SendMediaType::Photo, result->setFileData(jpeg);
file, result->thumbId = id;
filename, result->thumbname = "thumb.jpg";
filesize, result->photo = photo;
data, result->photoThumbs = photoThumbs;
id, return result;
id,
u"jpg"_q,
peerId,
photo,
photoThumbs,
MTP_documentEmpty(MTP_long(0)),
jpeg);
} }
[[nodiscard]] std::optional<MTPVideoSize> PrepareMtpMarkup( [[nodiscard]] std::optional<MTPVideoSize> PrepareMtpMarkup(
@ -239,7 +232,7 @@ void PeerPhoto::upload(
_api.instance().mainDcId(), _api.instance().mainDcId(),
peer->id, peer->id,
base::take(photo.image)); base::take(photo.image));
_session->uploader().uploadMedia(fakeId, ready); _session->uploader().upload(fakeId, ready);
} }
} }

View file

@ -24,16 +24,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api { namespace Api {
namespace { namespace {
SendMediaReady PrepareRingtoneDocument( std::shared_ptr<FilePrepareResult> PrepareRingtoneDocument(
MTP::DcId dcId, MTP::DcId dcId,
const QString &filename, const QString &filename,
const QString &filemime, const QString &filemime,
const QByteArray &content) { const QByteArray &content) {
const auto id = base::RandomValue<DocumentId>();
auto attributes = QVector<MTPDocumentAttribute>( auto attributes = QVector<MTPDocumentAttribute>(
1, 1,
MTP_documentAttributeFilename(MTP_string(filename))); MTP_documentAttributeFilename(MTP_string(filename)));
const auto id = base::RandomValue<DocumentId>();
const auto document = MTP_document( auto result = MakePreparedFile({
.id = id,
.type = SendMediaType::File,
});
result->filename = filename;
result->content = content;
result->filesize = content.size();
result->setFileData(content);
result->document = MTP_document(
MTP_flags(0), MTP_flags(0),
MTP_long(id), MTP_long(id),
MTP_long(0), MTP_long(0),
@ -45,21 +54,7 @@ SendMediaReady PrepareRingtoneDocument(
MTPVector<MTPVideoSize>(), MTPVector<MTPVideoSize>(),
MTP_int(dcId), MTP_int(dcId),
MTP_vector<MTPDocumentAttribute>(std::move(attributes))); MTP_vector<MTPDocumentAttribute>(std::move(attributes)));
return result;
return SendMediaReady(
SendMediaType::File,
QString(), // filepath
filename,
content.size(),
content,
id,
0,
QString(),
PeerId(),
MTP_photoEmpty(MTP_long(0)),
PreparedPhotoThumbs(),
document,
QByteArray());
} }
} // namespace } // namespace
@ -102,7 +97,7 @@ void Ringtones::upload(
_uploads.erase(already); _uploads.erase(already);
} }
_uploads.emplace(fakeId, uploadedData); _uploads.emplace(fakeId, uploadedData);
_session->uploader().uploadMedia(fakeId, ready); _session->uploader().upload(fakeId, ready);
} }
void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) { void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) {

View file

@ -353,7 +353,7 @@ void FillMessagePostFlags(
void SendConfirmedFile( void SendConfirmedFile(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const std::shared_ptr<FileLoadResult> &file) { const std::shared_ptr<FilePrepareResult> &file) {
const auto isEditing = (file->type != SendMediaType::Audio) const auto isEditing = (file->type != SendMediaType::Audio)
&& (file->to.replaceMediaOf != 0); && (file->to.replaceMediaOf != 0);
const auto newId = FullMsgId( const auto newId = FullMsgId(

View file

@ -14,7 +14,7 @@ class Session;
class History; class History;
class PhotoData; class PhotoData;
class DocumentData; class DocumentData;
struct FileLoadResult; struct FilePrepareResult;
namespace Api { namespace Api {
@ -40,6 +40,6 @@ void FillMessagePostFlags(
void SendConfirmedFile( void SendConfirmedFile(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const std::shared_ptr<FileLoadResult> &file); const std::shared_ptr<FilePrepareResult> &file);
} // namespace Api } // namespace Api

View file

@ -813,7 +813,7 @@ QString ApiWrap::exportDirectStoryLink(not_null<Data::Story*> story) {
const auto storyId = story->fullId(); const auto storyId = story->fullId();
const auto peer = story->peer(); const auto peer = story->peer();
const auto fallback = [&] { const auto fallback = [&] {
const auto base = peer->userName(); const auto base = peer->username();
const auto story = QString::number(storyId.story); const auto story = QString::number(storyId.story);
const auto query = base + "/s/" + story; const auto query = base + "/s/" + story;
return session().createInternalLinkFull(query); return session().createInternalLinkFull(query);

View file

@ -593,11 +593,11 @@ void BackgroundPreviewBox::uploadForPeer(bool both) {
const auto ready = Window::Theme::PrepareWallPaper( const auto ready = Window::Theme::PrepareWallPaper(
session->mainDcId(), session->mainDcId(),
_paper.localThumbnail()->original()); _paper.localThumbnail()->original());
const auto documentId = ready.id; const auto documentId = ready->id;
_uploadId = FullMsgId( _uploadId = FullMsgId(
session->userPeerId(), session->userPeerId(),
session->data().nextLocalMessageId()); session->data().nextLocalMessageId());
session->uploader().uploadMedia(_uploadId, ready); session->uploader().upload(_uploadId, ready);
if (_uploadLifetime) { if (_uploadLifetime) {
return; return;
} }

View file

@ -1044,3 +1044,33 @@ inviteForbiddenTitle: FlatLabel(boxTitle) {
inviteForbiddenTitlePadding: margins(32px, 4px, 32px, 0px); inviteForbiddenTitlePadding: margins(32px, 4px, 32px, 0px);
inviteForbiddenLockBg: dialogsUnreadBgMuted; inviteForbiddenLockBg: dialogsUnreadBgMuted;
inviteForbiddenLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadFg }}; inviteForbiddenLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadFg }};
collectibleIconDiameter: 72px;
collectibleIcon: 64px;
collectibleIconPadding: margins(24px, 32px, 24px, 12px);
collectibleHeader: FlatLabel(boxTitle) {
minWidth: 120px;
maxHeight: 0px;
align: align(top);
}
collectibleHeaderPadding: margins(24px, 16px, 24px, 12px);
collectibleOwnerPadding: margins(24px, 4px, 24px, 8px);
collectibleInfo: inviteForbiddenInfo;
collectibleInfoPadding: margins(24px, 12px, 24px, 12px);
collectibleInfoTonMargins: margins(0px, 3px, 0px, 0px);
collectibleMore: RoundButton(defaultActiveButton) {
height: 36px;
textTop: 9px;
radius: 6px;
}
collectibleMorePadding: margins(24px, 12px, 24px, 0px);
collectibleCopy: RoundButton(defaultLightButton) {
height: 36px;
textTop: 9px;
radius: 6px;
}
collectibleBox: Box(defaultBox) {
buttonPadding: margins(24px, 12px, 24px, 12px);
buttonHeight: 36px;
button: collectibleCopy;
}

View file

@ -159,10 +159,7 @@ public:
-> rpl::producer<RowSelectionChange>; -> rpl::producer<RowSelectionChange>;
private: private:
[[nodiscard]] std::unique_ptr<PeerListRow> createRow() const;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
bool _premiums = false;
rpl::event_stream<> _selectionChanged; rpl::event_stream<> _selectionChanged;
rpl::event_stream<RowSelectionChange> _rowSelectionChanges; rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
@ -209,8 +206,7 @@ bool PremiumsRow::useForumLikeUserpic() const {
TypesController::TypesController( TypesController::TypesController(
not_null<Main::Session*> session, not_null<Main::Session*> session,
bool premiums) bool premiums)
: _session(session) : _session(session) {
, _premiums(premiums) {
} }
Main::Session &TypesController::session() const { Main::Session &TypesController::session() const {
@ -331,6 +327,9 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
_deselectOption = [=](PeerListRowId itemId) { _deselectOption = [=](PeerListRowId itemId) {
if (const auto row = _typesDelegate->peerListFindRow(itemId)) { if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
if (itemId == kPremiumsRowId) {
_selected.premiums = false;
}
_typesDelegate->peerListSetRowChecked(row, false); _typesDelegate->peerListSetRowChecked(row, false);
} }
}; };

View file

@ -1039,11 +1039,9 @@ void PeerListContent::changeCheckState(
not_null<PeerListRow*> row, not_null<PeerListRow*> row,
bool checked, bool checked,
anim::type animated) { anim::type animated) {
row->setChecked( row->setChecked(checked, _st.item.checkbox, animated, [=] {
checked, updateRow(row);
_st.item.checkbox, });
animated,
[=] { updateRow(row); });
} }
void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) { void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
@ -1789,10 +1787,10 @@ crl::time PeerListContent::paintRow(
if (row->isSearchResult() if (row->isSearchResult()
&& !_mentionHighlight.isEmpty() && !_mentionHighlight.isEmpty()
&& peer && peer
&& peer->userName().startsWith( && peer->username().startsWith(
_mentionHighlight, _mentionHighlight,
Qt::CaseInsensitive)) { Qt::CaseInsensitive)) {
const auto username = peer->userName(); const auto username = peer->username();
const auto availableWidth = statusw; const auto availableWidth = statusw;
auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size()); auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size());
auto grayedPart = username.mid(_mentionHighlight.size()); auto grayedPart = username.mid(_mentionHighlight.size());

View file

@ -100,7 +100,7 @@ void Controller::prepare() {
return; return;
} }
auto row = std::make_unique<PeerListRow>(chat); auto row = std::make_unique<PeerListRow>(chat);
const auto username = chat->userName(); const auto username = chat->username();
row->setCustomStatus(!username.isEmpty() row->setCustomStatus(!username.isEmpty()
? ('@' + username) ? ('@' + username)
: (chat->isChannel() && !chat->isMegagroup()) : (chat->isChannel() && !chat->isMegagroup())

View file

@ -207,7 +207,7 @@ void ProcessFullPhoto(
| UpdateFlag::Birthday) | UpdateFlag::Birthday)
) | rpl::map([=] { ) | rpl::map([=] {
const auto user = peer->asUser(); const auto user = peer->asUser();
const auto username = peer->userName(); const auto username = peer->username();
return PeerShortInfoFields{ return PeerShortInfoFields{
.name = peer->name(), .name = peer->name(),
.phone = user ? Ui::FormatPhone(user->phone()) : QString(), .phone = user ? Ui::FormatPhone(user->phone()) : QString(),

View file

@ -322,7 +322,7 @@ void PublicsController::prepare() {
auto &owner = _navigation->session().data(); auto &owner = _navigation->session().data();
for (const auto &chat : chats) { for (const auto &chat : chats) {
if (const auto peer = owner.processChat(chat)) { if (const auto peer = owner.processChat(chat)) {
if (!peer->isChannel() || peer->userName().isEmpty()) { if (!peer->isChannel() || peer->username().isEmpty()) {
continue; continue;
} }
appendRow(peer); appendRow(peer);
@ -346,7 +346,7 @@ void PublicsController::rowRightActionClicked(not_null<PeerListRow*> row) {
const auto text = textMethod( const auto text = textMethod(
tr::now, tr::now,
lt_link, lt_link,
peer->session().createInternalLink(peer->userName()), peer->session().createInternalLink(peer->username()),
lt_group, lt_group,
peer->name()); peer->name());
const auto confirmText = tr::lng_channels_too_much_public_revoke( const auto confirmText = tr::lng_channels_too_much_public_revoke(
@ -389,7 +389,7 @@ std::unique_ptr<PeerListRow> PublicsController::createRow(
auto result = std::make_unique<PeerListRowWithLink>(peer); auto result = std::make_unique<PeerListRowWithLink>(peer);
result->setActionLink(tr::lng_channels_too_much_public_revoke(tr::now)); result->setActionLink(tr::lng_channels_too_much_public_revoke(tr::now));
result->setCustomStatus( result->setCustomStatus(
_navigation->session().createInternalLink(peer->userName())); _navigation->session().createInternalLink(peer->username()));
return result; return result;
} }

View file

@ -652,8 +652,8 @@ void StickersBox::prepare() {
} }
if (const auto featured = _featured.widget()) { if (const auto featured = _featured.widget()) {
featured->setInstallSetCallback([=](uint64 setId) { featured->setInstallSetCallback([=](uint64 setId) {
markAsInstalledCallback(setId);
installCallback(setId); installCallback(setId);
markAsInstalledCallback(setId);
}); });
featured->setRemoveSetCallback(markAsRemovedCallback); featured->setRemoveSetCallback(markAsRemovedCallback);
} }
@ -1760,8 +1760,11 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
const auto &st = installedSet const auto &st = installedSet
? st::stickersTrendingInstalled ? st::stickersTrendingInstalled
: st::stickersTrendingAdd; : st::stickersTrendingAdd;
const auto buttonTextWidth = installedSet
? _installedWidth
: _addWidth;
auto rippleMask = Ui::RippleAnimation::RoundRectMask( auto rippleMask = Ui::RippleAnimation::RoundRectMask(
QSize(_addWidth - st.width, st.height), QSize(buttonTextWidth - st.width, st.height),
st::roundRadiusLarge); st::roundRadiusLarge);
ensureRipple( ensureRipple(
st.ripple, st.ripple,
@ -1902,9 +1905,19 @@ void StickersBox::Inner::updateSelected() {
selected = selectedIndex; selected = selectedIndex;
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight); local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
const auto row = _rows[selectedIndex].get(); const auto row = _rows[selectedIndex].get();
if (!_megagroupSet && (_isInstalledTab || (_section == Section::Featured) || !row->isInstalled() || row->isArchived() || row->removed)) { if (!_megagroupSet
&& (_isInstalledTab
|| (_section == Section::Featured)
|| !row->isInstalled()
|| row->isArchived()
|| row->removed)) {
auto removeButton = (_isInstalledTab && !row->removed); auto removeButton = (_isInstalledTab && !row->removed);
auto rect = myrtlrect(relativeButtonRect(removeButton, false));
const auto installedSetButton = !_isInstalledTab
&& row->isInstalled()
&& !row->isArchived()
&& !row->removed;
auto rect = myrtlrect(relativeButtonRect(removeButton, installedSetButton));
actionSel = rect.contains(local) ? selectedIndex : -1; actionSel = rect.contains(local) ? selectedIndex : -1;
} else { } else {
actionSel = -1; actionSel = -1;
@ -1957,12 +1970,19 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_mouse = e->globalPos(); _mouse = e->globalPos();
updateSelected(); updateSelected();
if (_actionDown == _actionSel && _actionSel >= 0) { const auto down = _actionDown;
const auto callback = _rows[_actionDown]->removed setActionDown(-1);
? _installSetCallback if (down == _actionSel && _actionSel >= 0) {
: _removeSetCallback; const auto row = _rows[down].get();
const auto installedSet = row->isInstalled()
&& !row->isArchived()
&& !row->removed;
const auto callback = installedSet
? _removeSetCallback
: _installSetCallback;
if (callback) { if (callback) {
callback(_rows[_actionDown]->set->id); row->ripple.reset();
callback(row->set->id);
} }
} else if (_dragging >= 0) { } else if (_dragging >= 0) {
_rows[_dragging]->yadd.start(0.); _rows[_dragging]->yadd.start(0.);
@ -1973,7 +1993,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
} }
_dragging = _started = -1; _dragging = _started = -1;
} else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) { } else if (pressed == _selected && _actionSel < 0 && down < 0) {
const auto selectedIndex = [&] { const auto selectedIndex = [&] {
if (auto index = std::get_if<int>(&_selected)) { if (auto index = std::get_if<int>(&_selected)) {
return *index; return *index;
@ -1997,7 +2017,6 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
showSetByRow(*_megagroupSelectedSet); showSetByRow(*_megagroupSelectedSet);
} }
} }
setActionDown(-1);
} }
void StickersBox::Inner::saveGroupSet(Fn<void()> done) { void StickersBox::Inner::saveGroupSet(Fn<void()> done) {

View file

@ -612,9 +612,9 @@ void MembersRow::paintComplexStatusText(
x += skip; x += skip;
availableWidth -= skip; availableWidth -= skip;
const auto &font = st::normalFont; const auto &font = st::normalFont;
const auto useAbout = (style == MembersRowStyle::Video) const auto useAbout = !_about.isEmpty()
? false && (style != MembersRowStyle::Video)
: ((_state == State::RaisedHand && !_raisedHandStatus) && ((_state == State::RaisedHand && !_raisedHandStatus)
|| (_state != State::RaisedHand && !_speaking)); || (_state != State::RaisedHand && !_speaking));
if (!useAbout if (!useAbout
&& _state != State::Invited && _state != State::Invited

View file

@ -587,11 +587,9 @@ void ChooseSourceProcess::setupGeometryWithParent(
not_null<QWidget*> parent) { not_null<QWidget*> parent) {
_window->createWinId(); _window->createWinId();
const auto parentScreen = [&] { const auto parentScreen = [&] {
if (!::Platform::IsWayland()) { if (const auto screen = QGuiApplication::screenAt(
if (const auto screen = QGuiApplication::screenAt(
parent->geometry().center())) { parent->geometry().center())) {
return screen; return screen;
}
} }
return parent->screen(); return parent->screen();
}(); }();

View file

@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
@ -51,10 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
// AyuGram includes #include <QtWidgets/QApplication>
#include "data/data_file_origin.h"
#include "qapplication.h"
namespace ChatHelpers { namespace ChatHelpers {
namespace { namespace {
@ -62,6 +60,7 @@ namespace {
constexpr auto kCollapsedRows = 3; constexpr auto kCollapsedRows = 3;
constexpr auto kAppearDuration = 0.3; constexpr auto kAppearDuration = 0.3;
constexpr auto kCustomSearchLimit = 256; constexpr auto kCustomSearchLimit = 256;
constexpr auto kColorPickerDelay = crl::time(500);
using Core::RecentEmojiId; using Core::RecentEmojiId;
using Core::RecentEmojiDocument; using Core::RecentEmojiDocument;
@ -630,6 +629,15 @@ void EmojiListWidget::applyNextSearchQuery() {
} }
} }
void EmojiListWidget::showPreview() {
if (const auto over = std::get_if<OverEmoji>(&_pressed)) {
if (const auto custom = lookupCustomEmoji(over)) {
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
_previewShown = true;
}
}
}
std::vector<EmojiPtr> EmojiListWidget::collectPlainSearchResults() { std::vector<EmojiPtr> EmojiListWidget::collectPlainSearchResults() {
return SearchEmoji(_searchQuery, _searchEmoji); return SearchEmoji(_searchQuery, _searchEmoji);
} }
@ -1125,7 +1133,7 @@ void EmojiListWidget::fillRecentMenu(
const auto addAction = Ui::Menu::CreateAddActionCallback(menu); const auto addAction = Ui::Menu::CreateAddActionCallback(menu);
const auto over = OverEmoji{ section, index }; const auto over = OverEmoji{ section, index };
const auto emoji = lookupOverEmoji(&over); const auto emoji = lookupOverEmoji(&over);
const auto custom = lookupCustomEmoji(index, section); const auto custom = lookupCustomEmoji(&over);
if (custom && custom->sticker()) { if (custom && custom->sticker()) {
const auto sticker = custom->sticker(); const auto sticker = custom->sticker();
const auto emoji = sticker->alt; const auto emoji = sticker->alt;
@ -1498,6 +1506,11 @@ bool EmojiListWidget::checkPickerHide() {
return false; return false;
} }
DocumentData *EmojiListWidget::lookupCustomEmoji(
const OverEmoji *over) const {
return over ? lookupCustomEmoji(over->index, over->section) : nullptr;
}
DocumentData *EmojiListWidget::lookupCustomEmoji( DocumentData *EmojiListWidget::lookupCustomEmoji(
int index, int index,
int section) const { int section) const {
@ -1600,12 +1613,14 @@ void EmojiListWidget::mousePressEvent(QMouseEvent *e) {
if (!Core::App().settings().hasChosenEmojiVariant(emoji)) { if (!Core::App().settings().hasChosenEmojiVariant(emoji)) {
showPicker(); showPicker();
} else { } else {
_showPickerTimer.callOnce(500); _previewTimer.cancel();
_showPickerTimer.callOnce(kColorPickerDelay);
} }
} else if (lookupCustomEmoji(over)) {
_showPickerTimer.cancel();
_previewTimer.callOnce(QApplication::startDragTime());
} }
} }
_previewTimer.callOnce(QApplication::startDragTime());
} }
void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
@ -1613,11 +1628,6 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed; auto pressed = _pressed;
setPressed(v::null); setPressed(v::null);
if (_previewShown) {
_previewShown = false;
return;
}
_lastMousePos = e->globalPos(); _lastMousePos = e->globalPos();
if (!_picker->isHidden()) { if (!_picker->isHidden()) {
if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) { if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) {
@ -1640,7 +1650,10 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
_picker->hide(); _picker->hide();
} }
if (v::is_null(_selected) || _selected != pressed) { if (_previewShown) {
_previewShown = false;
return;
} else if (v::is_null(_selected) || _selected != pressed) {
return; return;
} }
@ -1659,7 +1672,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
return; return;
} }
selectEmoji(lookupChosen(emoji, over)); selectEmoji(lookupChosen(emoji, over));
} else if (const auto custom = lookupCustomEmoji(index, section)) { } else if (const auto custom = lookupCustomEmoji(over)) {
selectCustom(lookupChosen(custom, over)); selectCustom(lookupChosen(custom, over));
} }
} else if (const auto set = std::get_if<OverSet>(&pressed)) { } else if (const auto set = std::get_if<OverSet>(&pressed)) {
@ -2492,8 +2505,10 @@ bool EmojiListWidget::eventHook(QEvent *e) {
} }
void EmojiListWidget::updateSelected() { void EmojiListWidget::updateSelected() {
if ((!v::is_null(_pressed) || !v::is_null(_pickerSelected)) && !_previewShown) { if (!v::is_null(_pressed) || !v::is_null(_pickerSelected)) {
return; if (!_previewShown) {
return;
}
} }
auto newSelected = OverState{ v::null }; auto newSelected = OverState{ v::null };
@ -2542,7 +2557,7 @@ void EmojiListWidget::setSelected(OverState newSelected) {
const auto hasSelection = !v::is_null(_selected); const auto hasSelection = !v::is_null(_selected);
if (hasSelection && Core::App().settings().suggestEmoji()) { if (hasSelection && Core::App().settings().suggestEmoji()) {
Ui::Tooltip::Show(350, this); Ui::Tooltip::Show(1000, this);
} }
setCursor(hasSelection ? style::cur_pointer : style::cur_default); setCursor(hasSelection ? style::cur_pointer : style::cur_default);
@ -2552,34 +2567,16 @@ void EmojiListWidget::setSelected(OverState newSelected) {
} else { } else {
_picker->showAnimated(); _picker->showAnimated();
} }
} } else if (_previewShown && _pressed != _selected) {
if (_previewShown && _pressed != _selected) {
if (const auto over = std::get_if<OverEmoji>(&_selected)) { if (const auto over = std::get_if<OverEmoji>(&_selected)) {
_pressed = _selected; if (const auto custom = lookupCustomEmoji(over)) {
_pressed = _selected;
const auto section = over ? over->section : -1; _show->showMediaPreview(custom->stickerSetOrigin(), custom);
const auto index = over ? over->index : -1;
if (const auto document = lookupCustomEmoji(index, section)) {
_show->showMediaPreview(document->stickerSetOrigin(), document);
} }
} }
} }
} }
void EmojiListWidget::showPreview() {
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
const auto section = over ? over->section : -1;
const auto index = over ? over->index : -1;
if (const auto document = lookupCustomEmoji(index, section)) {
_show->showMediaPreview(document->stickerSetOrigin(), document);
_previewShown = true;
}
}
}
void EmojiListWidget::setPressed(OverState newPressed) { void EmojiListWidget::setPressed(OverState newPressed) {
if (auto button = std::get_if<OverButton>(&_pressed)) { if (auto button = std::get_if<OverButton>(&_pressed)) {
Assert(hasColorButton(button->section) Assert(hasColorButton(button->section)

View file

@ -287,6 +287,8 @@ private:
int index); int index);
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const; [[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
[[nodiscard]] DocumentData *lookupCustomEmoji(
const OverEmoji *over) const;
[[nodiscard]] DocumentData *lookupCustomEmoji( [[nodiscard]] DocumentData *lookupCustomEmoji(
int index, int index,
int section) const; int section) const;
@ -371,6 +373,8 @@ private:
DocumentId documentId, DocumentId documentId,
uint64 setId); uint64 setId);
void showPreview();
void applyNextSearchQuery(); void applyNextSearchQuery();
void showPreview(); void showPreview();
@ -442,6 +446,8 @@ private:
object_ptr<EmojiColorPicker> _picker; object_ptr<EmojiColorPicker> _picker;
base::Timer _showPickerTimer; base::Timer _showPickerTimer;
base::Timer _previewTimer;
bool _previewShown = false;
rpl::event_stream<EmojiChosen> _chosen; rpl::event_stream<EmojiChosen> _chosen;
rpl::event_stream<FileChosen> _customChosen; rpl::event_stream<FileChosen> _customChosen;

View file

@ -46,6 +46,7 @@ struct ClickHandlerContext {
bool mayShowConfirmation = false; bool mayShowConfirmation = false;
bool skipBotAutoLogin = false; bool skipBotAutoLogin = false;
bool botStartAutoSubmit = false; bool botStartAutoSubmit = false;
bool ignoreIv = false;
// Is filled from peer info. // Is filled from peer info.
PeerData *peer = nullptr; PeerData *peer = nullptr;
}; };

View file

@ -159,7 +159,7 @@ void Launch(const QString &filepath) {
void ShowInFolder(const QString &filepath) { void ShowInFolder(const QString &filepath) {
crl::on_main([=] { crl::on_main([=] {
Ui::PreventDelayedActivation(); Ui::PreventDelayedActivation();
if (Platform::IsLinux()) { if (Platform::IsX11()) {
// Hide mediaview to make other apps visible. // Hide mediaview to make other apps visible.
Core::App().hideMediaView(); Core::App().hideMediaView();
} }

View file

@ -567,6 +567,7 @@ bool ResolveUsernameOrPhone(
.phone = phone, .phone = phone,
.messageId = post, .messageId = post,
.storyId = storyId, .storyId = storyId,
.text = params.value(u"text"_q),
.repliesInfo = commentId .repliesInfo = commentId
? Window::RepliesByLinkInfo{ ? Window::RepliesByLinkInfo{
Window::CommentId{ commentId } Window::CommentId{ commentId }
@ -901,6 +902,34 @@ bool ShowEditPersonalChannel(
return true; return true;
} }
bool ShowCollectiblePhone(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto phone = match->captured(1);
const auto peerId = PeerId(match->captured(2).toULongLong());
controller->resolveCollectible(
peerId,
phone.startsWith('+') ? phone : '+' + phone);
return true;
}
bool ShowCollectibleUsername(
Window::SessionController *controller,
const Match &match,
const QVariant &context) {
if (!controller) {
return false;
}
const auto username = match->captured(1);
const auto peerId = PeerId(match->captured(2).toULongLong());
controller->resolveCollectible(peerId, username);
return true;
}
void ExportTestChatTheme( void ExportTestChatTheme(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) { not_null<const Data::CloudTheme*> theme) {
@ -1311,6 +1340,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^edit_personal_channel$"_q, u"^edit_personal_channel$"_q,
ShowEditPersonalChannel, ShowEditPersonalChannel,
}, },
{
u"^collectible_phone/([\\+0-9\\-\\s]+)@([0-9]+)$"_q,
ShowCollectiblePhone,
},
{
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
ShowCollectibleUsername,
},
}; };
return Result; return Result;
} }

View file

@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QSessionManager> #include <QtGui/QSessionManager>
#include <QtGui/QScreen> #include <QtGui/QScreen>
#include <QtGui/qpa/qplatformscreen.h> #include <QtGui/qpa/qplatformscreen.h>
#include <ksandbox.h>
namespace Core { namespace Core {
namespace { namespace {
@ -518,10 +517,8 @@ void Sandbox::refreshGlobalProxy() {
|| proxy.type == MTP::ProxyData::Type::Http) { || proxy.type == MTP::ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy( QNetworkProxy::setApplicationProxy(
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy))); MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
} else if ((!Core::IsAppLaunched() } else if (!Core::IsAppLaunched()
|| Core::App().settings().proxy().isSystem()) || Core::App().settings().proxy().isSystem()) {
// this works stable only in sandboxed environment where it works through portal
&& (!Platform::IsLinux() || KSandbox::isInside() || cDebugMode())) {
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
} else { } else {
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy); QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_sponsored_messages.h" #include "data/data_sponsored_messages.h"
#include "iv/iv_instance.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/basic_click_handlers.h" #include "ui/basic_click_handlers.h"
#include "ui/emoji_config.h" #include "ui/emoji_config.h"
@ -241,6 +242,13 @@ bool UiIntegration::handleUrlClick(
} else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) { } else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
Core::App().openInternalUrl(local, context); Core::App().openInternalUrl(local, context);
return true; return true;
} else if (Iv::PreferForUri(url)
&& !context.value<ClickHandlerContext>().ignoreIv) {
const auto my = context.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
Core::App().iv().openWithIvPreferred(controller, url, context);
return true;
}
} }
if (AyuUrlHandlers::TryHandleSpotify(url)) { if (AyuUrlHandlers::TryHandleSpotify(url)) {

View file

@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs; constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs; constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs; constexpr auto AppFile = "AyuGram"_cs;
constexpr auto AppVersion = 4016000; constexpr auto AppVersion = 4016006;
constexpr auto AppVersionStr = "4.16"; constexpr auto AppVersionStr = "4.16.6";
constexpr auto AppBetaVersion = false; constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View file

@ -125,8 +125,10 @@ void Chatbots::togglePaused(not_null<PeerData*> peer, bool paused) {
return; return;
} else if (const auto settings = peer->barSettings()) { } else if (const auto settings = peer->barSettings()) {
peer->setBarSettings(paused peer->setBarSettings(paused
? (*settings | PeerBarSetting::BusinessBotPaused) ? ((*settings | PeerBarSetting::BusinessBotPaused)
: (*settings & ~PeerBarSetting::BusinessBotPaused)); & ~PeerBarSetting::BusinessBotCanReply)
: ((*settings & ~PeerBarSetting::BusinessBotPaused)
| PeerBarSetting::BusinessBotCanReply));
} else { } else {
api->requestPeerSettings(peer); api->requestPeerSettings(peer);
} }

View file

@ -71,6 +71,13 @@ auto RecipientsFlags(const BusinessRecipients &data) {
} // namespace } // namespace
BusinessRecipients BusinessRecipients::MakeValid(BusinessRecipients value) {
if (value.included.empty()) {
value.allButExcluded = true;
}
return value;
}
MTPInputBusinessRecipients ForMessagesToMTP(const BusinessRecipients &data) { MTPInputBusinessRecipients ForMessagesToMTP(const BusinessRecipients &data) {
using Flag = MTPDinputBusinessRecipients::Flag; using Flag = MTPDinputBusinessRecipients::Flag;
const auto &chats = data.allButExcluded ? data.excluded : data.included; const auto &chats = data.allButExcluded ? data.excluded : data.included;

View file

@ -44,6 +44,9 @@ struct BusinessRecipients {
BusinessChats excluded; BusinessChats excluded;
bool allButExcluded = false; bool allButExcluded = false;
[[nodiscard]] static BusinessRecipients MakeValid(
BusinessRecipients value);
friend inline bool operator==( friend inline bool operator==(
const BusinessRecipients &a, const BusinessRecipients &a,
const BusinessRecipients &b) = default; const BusinessRecipients &b) = default;

View file

@ -127,4 +127,3 @@ rpl::producer<bool> IsBirthdayTodayValue(Birthday date) {
} }
} // namespace Data } // namespace Data

View file

@ -139,6 +139,10 @@ const std::vector<QString> &ChannelData::usernames() const {
return _username.usernames(); return _username.usernames();
} }
bool ChannelData::isUsernameEditable(QString username) const {
return _username.isEditable(username);
}
void ChannelData::setAccessHash(uint64 accessHash) { void ChannelData::setAccessHash(uint64 accessHash) {
access = accessHash; access = accessHash;
input = MTP_inputPeerChannel( input = MTP_inputPeerChannel(

View file

@ -182,6 +182,7 @@ public:
[[nodiscard]] QString username() const; [[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const; [[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector<QString> &usernames() const; [[nodiscard]] const std::vector<QString> &usernames() const;
[[nodiscard]] bool isUsernameEditable(QString username) const;
[[nodiscard]] int membersCount() const { [[nodiscard]] int membersCount() const {
return std::max(_membersCount, 1); return std::max(_membersCount, 1);

View file

@ -1123,13 +1123,14 @@ rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent() {
state->thumbnail = Images::Prepare(embed->original(), 0, { state->thumbnail = Images::Prepare(embed->original(), 0, {
.options = Images::Option::Blur, .options = Images::Option::Blur,
}); });
} else if (!state->downloadTaskLifetime) {
state->document->session().downloaderTaskFinished(
) | rpl::filter([=] {
return self(self);
}) | rpl::start_with_next(
state->push,
state->downloadTaskLifetime);
} }
state->document->session().downloaderTaskFinished(
) | rpl::filter([=] {
return self(self);
}) | rpl::start_with_next(
state->push,
state->downloadTaskLifetime);
return !state->thumbnail.isNull(); return !state->thumbnail.isNull();
}; };
const auto resolveThumbnail = [=] { const auto resolveThumbnail = [=] {

View file

@ -940,7 +940,7 @@ const QString &PeerData::shortName() const {
return _name; return _name;
} }
QString PeerData::userName() const { QString PeerData::username() const {
if (const auto user = asUser()) { if (const auto user = asUser()) {
return user->username(); return user->username();
} else if (const auto channel = asChannel()) { } else if (const auto channel = asChannel()) {
@ -949,6 +949,34 @@ QString PeerData::userName() const {
return QString(); return QString();
} }
QString PeerData::editableUsername() const {
if (const auto user = asUser()) {
return user->editableUsername();
} else if (const auto channel = asChannel()) {
return channel->editableUsername();
}
return QString();
}
const std::vector<QString> &PeerData::usernames() const {
if (const auto user = asUser()) {
return user->usernames();
} else if (const auto channel = asChannel()) {
return channel->usernames();
}
static const auto kEmpty = std::vector<QString>();
return kEmpty;
}
bool PeerData::isUsernameEditable(QString username) const {
if (const auto user = asUser()) {
return user->isUsernameEditable(username);
} else if (const auto channel = asChannel()) {
return channel->isUsernameEditable(username);
}
return false;
}
bool PeerData::changeColorIndex(uint8 index) { bool PeerData::changeColorIndex(uint8 index) {
index %= Ui::kColorIndexCount; index %= Ui::kColorIndexCount;
if (_colorIndexCloud && _colorIndex == index) { if (_colorIndexCloud && _colorIndex == index) {

View file

@ -279,7 +279,11 @@ public:
[[nodiscard]] const QString &name() const; [[nodiscard]] const QString &name() const;
[[nodiscard]] const QString &shortName() const; [[nodiscard]] const QString &shortName() const;
[[nodiscard]] const QString &topBarNameText() const; [[nodiscard]] const QString &topBarNameText() const;
[[nodiscard]] QString userName() const;
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector<QString> &usernames() const;
[[nodiscard]] bool isUsernameEditable(QString username) const;
[[nodiscard]] const base::flat_set<QString> &nameWords() const { [[nodiscard]] const base::flat_set<QString> &nameWords() const {
return _nameWords; return _nameWords;

View file

@ -1240,7 +1240,7 @@ PeerData *Session::peerByUsername(const QString &username) const {
const auto uname = username.trimmed(); const auto uname = username.trimmed();
for (const auto &[peerId, peer] : _peers) { for (const auto &[peerId, peer] : _peers) {
if (peer->isLoaded() if (peer->isLoaded()
&& !peer->userName().compare(uname, Qt::CaseInsensitive)) { && !peer->username().compare(uname, Qt::CaseInsensitive)) {
return peer.get(); return peer.get();
} }
} }

View file

@ -309,7 +309,7 @@ void SponsoredMessages::append(
? _session->data().processBotApp(peerId, *data.vapp()) ? _session->data().processBotApp(peerId, *data.vapp())
: nullptr; : nullptr;
result.botLinkInfo = Window::PeerByLinkInfo{ result.botLinkInfo = Window::PeerByLinkInfo{
.usernameOrId = user->userName(), .usernameOrId = user->username(),
.resolveType = botAppData .resolveType = botAppData
? Window::ResolveType::BotApp ? Window::ResolveType::BotApp
: data.vstart_param() : data.vstart_param()

View file

@ -450,7 +450,7 @@ bool Story::hasDirectLink() const {
if (!_privacyPublic || (!_pinned && expired())) { if (!_privacyPublic || (!_pinned && expired())) {
return false; return false;
} }
return !_peer->userName().isEmpty(); return !_peer->username().isEmpty();
} }
std::optional<QString> Story::errorTextForForward( std::optional<QString> Story::errorTextForForward(

View file

@ -486,6 +486,10 @@ const std::vector<QString> &UserData::usernames() const {
return _username.usernames(); return _username.usernames();
} }
bool UserData::isUsernameEditable(QString username) const {
return _username.isEditable(username);
}
const QString &UserData::phone() const { const QString &UserData::phone() const {
return _phone; return _phone;
} }

View file

@ -150,15 +150,11 @@ public:
// a full check by canShareThisContact() call. // a full check by canShareThisContact() call.
[[nodiscard]] bool canShareThisContactFast() const; [[nodiscard]] bool canShareThisContactFast() const;
MTPInputUser inputUser = MTP_inputUserEmpty();
QString firstName;
QString lastName;
[[nodiscard]] const QString &phone() const; [[nodiscard]] const QString &phone() const;
[[nodiscard]] QString username() const; [[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const; [[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector<QString> &usernames() const; [[nodiscard]] const std::vector<QString> &usernames() const;
QString nameOrPhone; [[nodiscard]] bool isUsernameEditable(QString username) const;
enum class ContactStatus : char { enum class ContactStatus : char {
Unknown, Unknown,
@ -186,8 +182,6 @@ public:
void setBirthday(Data::Birthday value); void setBirthday(Data::Birthday value);
void setBirthday(const tl::conditional<MTPBirthday> &value); void setBirthday(const tl::conditional<MTPBirthday> &value);
std::unique_ptr<BotInfo> botInfo;
void setUnavailableReasons( void setUnavailableReasons(
std::vector<Data::UnavailableReason> &&reasons); std::vector<Data::UnavailableReason> &&reasons);
@ -209,6 +203,14 @@ public:
[[nodiscard]] MsgId personalChannelMessageId() const; [[nodiscard]] MsgId personalChannelMessageId() const;
void setPersonalChannel(ChannelId channelId, MsgId messageId); void setPersonalChannel(ChannelId channelId, MsgId messageId);
MTPInputUser inputUser = MTP_inputUserEmpty();
QString firstName;
QString lastName;
QString nameOrPhone;
std::unique_ptr<BotInfo> botInfo;
private: private:
auto unavailableReasons() const auto unavailableReasons() const
-> const std::vector<Data::UnavailableReason> & override; -> const std::vector<Data::UnavailableReason> & override;

View file

@ -80,4 +80,10 @@ const std::vector<QString> &UsernamesInfo::usernames() const {
return _usernames; return _usernames;
} }
bool UsernamesInfo::isEditable(const QString &username) const {
return (_indexEditableUsername >= 0)
&& (_indexEditableUsername < _usernames.size())
&& (_usernames[_indexEditableUsername] == username);
}
} // namespace Data } // namespace Data

View file

@ -27,6 +27,7 @@ public:
[[nodiscard]] QString username() const; [[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const; [[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector<QString> &usernames() const; [[nodiscard]] const std::vector<QString> &usernames() const;
[[nodiscard]] bool isEditable(const QString &username) const;
private: private:
std::vector<QString> _usernames; std::vector<QString> _usernames;

View file

@ -1087,7 +1087,7 @@ void InnerWidget::paintPeerSearchResult(
QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height); QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height);
p.setFont(st::dialogsTextFont); p.setFont(st::dialogsTextFont);
QString username = peer->userName(); QString username = peer->username();
if (!context.active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) { if (!context.active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) {
auto first = '@' + username.mid(0, _peerSearchQuery.size()); auto first = '@' + username.mid(0, _peerSearchQuery.size());
auto second = username.mid(_peerSearchQuery.size()); auto second = username.mid(_peerSearchQuery.size());
@ -4021,7 +4021,7 @@ void InnerWidget::setupShortcuts() {
const auto history = thread->owningHistory(); const auto history = thread->owningHistory();
const auto isArchived = history->folder() const auto isArchived = history->folder()
&& (history->folder()->id() == Data::Folder::kId); && (history->folder()->id() == Data::Folder::kId);
Window::ToggleHistoryArchived( Window::ToggleHistoryArchived(
_controller->uiShow(), _controller->uiShow(),
history, history,

View file

@ -496,7 +496,7 @@ auto GenerateParticipantString(
data, data,
}); });
} }
const auto username = peer->userName(); const auto username = peer->username();
if (username.isEmpty()) { if (username.isEmpty()) {
return name; return name;
} }

View file

@ -4536,7 +4536,7 @@ void HistoryWidget::chooseAttach(
} }
const auto filter = (overrideSendImagesAsPhotos == true) const auto filter = (overrideSendImagesAsPhotos == true)
? FileDialog::ImagesOrAllFilter() ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter(); : FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=]( FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
@ -6284,8 +6284,7 @@ void HistoryWidget::startItemRevealAnimations() {
void HistoryWidget::startMessageSendingAnimation( void HistoryWidget::startMessageSendingAnimation(
not_null<HistoryItem*> item) { not_null<HistoryItem*> item) {
auto &sendingAnimation = controller()->sendingAnimation(); auto &sendingAnimation = controller()->sendingAnimation();
if (!sendingAnimation.hasLocalMessage(item->fullId().msg) if (!sendingAnimation.checkExpectedType(item)) {
|| !sendingAnimation.checkExpectedType(item)) {
return; return;
} }
Assert(item->mainView() != nullptr); Assert(item->mainView() != nullptr);
@ -6297,14 +6296,16 @@ void HistoryWidget::startMessageSendingAnimation(
geometryValue() | rpl::to_empty, geometryValue() | rpl::to_empty,
_scroll->geometryValue() | rpl::to_empty, _scroll->geometryValue() | rpl::to_empty,
_list->geometryValue() | rpl::to_empty _list->geometryValue() | rpl::to_empty
) | rpl::map([=] { ) | rpl::map([=]() -> std::optional<QPoint> {
const auto view = item->mainView(); const auto view = item->mainView();
const auto top = view ? _list->itemTop(view) : -1;
if (top < 0) {
return std::nullopt;
}
const auto additional = (_list->height() == _scroll->height()) const auto additional = (_list->height() == _scroll->height())
? view->height() ? view->height()
: 0; : 0;
return _list->mapToGlobal(QPoint( return _list->mapToGlobal(QPoint(0, top - additional));
0,
_list->itemTop(view) - additional));
}); });
sendingAnimation.startAnimation({ sendingAnimation.startAnimation({

View file

@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "mtproto/sender.h" #include "mtproto/sender.h"
struct FileLoadResult;
enum class SendMediaType; enum class SendMediaType;
class MessageLinksParser; class MessageLinksParser;
struct InlineBotQuery; struct InlineBotQuery;

View file

@ -1973,6 +1973,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
updateSendButtonType(); updateSendButtonType();
updateReplaceMediaButton(); updateReplaceMediaButton();
updateFieldPlaceholder();
updateControlsVisibility(); updateControlsVisibility();
updateControlsGeometry(_wrap->size()); updateControlsGeometry(_wrap->size());
}); });

View file

@ -902,15 +902,15 @@ void BusinessBotStatus::Bar::showState(State state) {
_userpic->setAttribute(Qt::WA_TransparentForMouseEvents); _userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
_userpic->show(); _userpic->show();
_name->setText(state.bot->name()); _name->setText(state.bot->name());
_status->setText(!state.canReply _status->setText(state.paused
? tr::lng_chatbot_status_views(tr::now)
: state.paused
? tr::lng_chatbot_status_paused(tr::now) ? tr::lng_chatbot_status_paused(tr::now)
: tr::lng_chatbot_status_can_reply(tr::now)); : state.canReply
? tr::lng_chatbot_status_can_reply(tr::now)
: tr::lng_chatbot_status_views(tr::now));
_togglePaused->setText(state.paused _togglePaused->setText(state.paused
? tr::lng_chatbot_button_resume() ? tr::lng_chatbot_button_resume()
: tr::lng_chatbot_button_pause()); : tr::lng_chatbot_button_pause());
_togglePaused->setVisible(state.canReply); _togglePaused->setVisible(state.canReply || state.paused);
_paused = state.paused; _paused = state.paused;
resizeToWidth(width()); resizeToWidth(width());
} }

View file

@ -1839,16 +1839,18 @@ void ListWidget::startItemRevealAnimations() {
void ListWidget::startMessageSendingAnimation( void ListWidget::startMessageSendingAnimation(
not_null<HistoryItem*> item) { not_null<HistoryItem*> item) {
auto &sendingAnimation = controller()->sendingAnimation(); auto &sendingAnimation = controller()->sendingAnimation();
if (!sendingAnimation.hasLocalMessage(item->fullId().msg) if (!sendingAnimation.checkExpectedType(item)) {
|| !sendingAnimation.checkExpectedType(item)) {
return; return;
} }
auto globalEndTopLeft = rpl::merge( auto globalEndTopLeft = rpl::merge(
session().data().newItemAdded() | rpl::to_empty, session().data().newItemAdded() | rpl::to_empty,
geometryValue() | rpl::to_empty geometryValue() | rpl::to_empty
) | rpl::map([=] { ) | rpl::map([=]() -> std::optional<QPoint> {
const auto view = viewForItem(item); const auto view = viewForItem(item);
if (!view) {
return std::nullopt;
}
const auto additional = !_visibleTop ? view->height() : 0; const auto additional = !_visibleTop ? view->height() : 0;
return mapToGlobal(QPoint(0, itemTop(view) - additional)); return mapToGlobal(QPoint(0, itemTop(view) - additional));
}); });

View file

@ -875,7 +875,7 @@ void RepliesWidget::chooseAttach(
} }
const auto filter = (overrideSendImagesAsPhotos == true) const auto filter = (overrideSendImagesAsPhotos == true)
? FileDialog::ImagesOrAllFilter() ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter(); : FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=]( FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
FileDialog::OpenResult &&result) { FileDialog::OpenResult &&result) {

View file

@ -62,11 +62,19 @@ namespace HistoryView {
ScheduledMemento::ScheduledMemento(not_null<History*> history) ScheduledMemento::ScheduledMemento(not_null<History*> history)
: _history(history) : _history(history)
, _forumTopic(nullptr) { , _forumTopic(nullptr) {
const auto list = _history->owner().scheduledMessages().list(_history);
if (!list.ids.empty()) {
_list.setScrollTopState({ .item = { .fullId = list.ids.front() } });
}
} }
ScheduledMemento::ScheduledMemento(not_null<Data::ForumTopic*> forumTopic) ScheduledMemento::ScheduledMemento(not_null<Data::ForumTopic*> forumTopic)
: _history(forumTopic->owningHistory()) : _history(forumTopic->owningHistory())
, _forumTopic(forumTopic) { , _forumTopic(forumTopic) {
const auto list = _history->owner().scheduledMessages().list(_forumTopic);
if (!list.ids.empty()) {
_list.setScrollTopState({ .item = { .fullId = list.ids.front() } });
}
} }
object_ptr<Window::SectionWidget> ScheduledMemento::createWidget( object_ptr<Window::SectionWidget> ScheduledMemento::createWidget(
@ -1162,9 +1170,7 @@ Context ScheduledWidget::listContext() {
} }
bool ScheduledWidget::listScrollTo(int top, bool syntetic) { bool ScheduledWidget::listScrollTo(int top, bool syntetic) {
top = (top == ScrollMax && syntetic) top = std::clamp(top, 0, _scroll->scrollTopMax());
? 0
: std::clamp(top, 0, _scroll->scrollTopMax());
if (_scroll->scrollTop() == top) { if (_scroll->scrollTop() == top) {
updateInnerVisibleArea(); updateInnerVisibleArea();
return false; return false;

View file

@ -280,7 +280,7 @@ private:
}; };
class ScheduledMemento : public Window::SectionMemento { class ScheduledMemento final : public Window::SectionMemento {
public: public:
ScheduledMemento(not_null<History*> history); ScheduledMemento(not_null<History*> history);
ScheduledMemento(not_null<Data::ForumTopic*> forumTopic); ScheduledMemento(not_null<Data::ForumTopic*> forumTopic);

View file

@ -12,6 +12,7 @@ namespace Info::ChannelEarn {
using EarnInt = Data::EarnInt; using EarnInt = Data::EarnInt;
constexpr auto kMinorPartLength = 9; constexpr auto kMinorPartLength = 9;
constexpr auto kMaxChoppedZero = kMinorPartLength - 2;
constexpr auto kZero = QChar('0'); constexpr auto kZero = QChar('0');
constexpr auto kDot = QChar('.'); constexpr auto kDot = QChar('.');
@ -35,7 +36,7 @@ QString MinorPart(EarnInt value) {
auto ch = end - 1; auto ch = end - 1;
auto zeroCount = 0; auto zeroCount = 0;
while (ch != begin) { while (ch != begin) {
if ((*ch) == kZero) { if (((*ch) == kZero) && (zeroCount < kMaxChoppedZero)) {
zeroCount++; zeroCount++;
} else { } else {
break; break;

View file

@ -77,7 +77,7 @@ void ShowMenu(not_null<Ui::GenericBox*> box, const QString &text) {
[[nodiscard]] ClickHandlerPtr LearnMoreCurrencyLink( [[nodiscard]] ClickHandlerPtr LearnMoreCurrencyLink(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::GenericBox*> box) { not_null<Ui::GenericBox*> box) {
const auto url = u"https://telegram.org/blog/monetization-for-channels"_q; const auto url = tr::lng_channel_earn_learn_coin_link(tr::now);
using Resolver = HistoryView::Controls::WebpageResolver; using Resolver = HistoryView::Controls::WebpageResolver;
const auto resolver = box->lifetime().make_state<Resolver>( const auto resolver = box->lifetime().make_state<Resolver>(
@ -217,7 +217,18 @@ void AddRecipient(not_null<Ui::GenericBox*> box, const TextWithEntities &t) {
} }
#endif #endif
[[nodiscard]] QImage IconCurrency( [[nodiscard]] QString FormatDate(const QDateTime &date) {
return tr::lng_group_call_starts_short_date(
tr::now,
lt_date,
langDayOfMonth(date.date()),
lt_time,
QLocale().toString(date.time(), QLocale::ShortFormat));
}
} // namespace
QImage IconCurrency(
const style::FlatLabel &label, const style::FlatLabel &label,
const QColor &c) { const QColor &c) {
const auto s = Size(label.style.font->ascent); const auto s = Size(label.style.font->ascent);
@ -234,17 +245,6 @@ void AddRecipient(not_null<Ui::GenericBox*> box, const TextWithEntities &t) {
return image; return image;
} }
[[nodiscard]] QString FormatDate(const QDateTime &date) {
return tr::lng_group_call_starts_short_date(
tr::now,
lt_date,
langDayOfMonth(date.date()),
lt_time,
QLocale().toString(date.time(), QLocale::ShortFormat));
}
} // namespace
InnerWidget::InnerWidget( InnerWidget::InnerWidget(
QWidget *parent, QWidget *parent,
not_null<Controller*> controller, not_null<Controller*> controller,
@ -372,6 +372,7 @@ void InnerWidget::fill() {
widget->paintRequest( widget->paintRequest(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
auto p = Painter(widget); auto p = Painter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg); p.setBrush(st::activeButtonBg);
p.drawEllipse(rect); p.drawEllipse(rect);

View file

@ -23,6 +23,10 @@ namespace Info::ChannelEarn {
class Memento; class Memento;
[[nodiscard]] QImage IconCurrency(
const style::FlatLabel &label,
const QColor &c);
class InnerWidget final : public Ui::VerticalLayout { class InnerWidget final : public Ui::VerticalLayout {
public: public:
struct ShowRequest final { struct ShowRequest final {

View file

@ -123,21 +123,25 @@ base::options::toggle ShowPeerIdBelowAbout({
[[nodiscard]] Fn<void(QString)> UsernamesLinkCallback( [[nodiscard]] Fn<void(QString)> UsernamesLinkCallback(
not_null<PeerData*> peer, not_null<PeerData*> peer,
std::shared_ptr<Ui::Show> show, not_null<Window::SessionController*> controller,
const QString &addToLink) { const QString &addToLink) {
const auto weak = base::make_weak(controller);
return [=](QString link) { return [=](QString link) {
auto settings = &AyuSettings::getInstance(); if (link.startsWith(u"internal:"_q)) {
if (!settings->copyUsernameAsLink) { Core::App().openInternalUrl(link,
link = '@' + link.mid(13); QVariant::fromValue(ClickHandlerContext{
} else { .sessionWindow = weak,
if (!link.startsWith(u"https://"_q)) { }));
link = peer->session().createInternalLinkFull(peer->userName()) return;
+ addToLink; } else if (!link.startsWith(u"https://"_q)) {
} link = peer->session().createInternalLinkFull(peer->username())
+ addToLink;
} }
if (!link.isEmpty()) { if (!link.isEmpty()) {
QGuiApplication::clipboard()->setText(link); QGuiApplication::clipboard()->setText(link);
show->showToast(tr::lng_username_copied(tr::now)); if (const auto window = weak.get()) {
window->showToast(tr::lng_username_copied(tr::now));
}
} }
}; };
} }
@ -1055,16 +1059,13 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
UsernameValue(user, true) | rpl::map([=](TextWithEntities u) { UsernameValue(user, true) | rpl::map([=](TextWithEntities u) {
return u.text.isEmpty() return u.text.isEmpty()
? TextWithEntities() ? TextWithEntities()
: Ui::Text::Link( : Ui::Text::Link(u, UsernameUrl(user, u.text.mid(1)));
u,
user->session().createInternalLinkFull(
u.text.mid(1)));
}), }),
QString(), QString(),
st::infoProfileLabeledUsernamePadding); st::infoProfileLabeledUsernamePadding);
const auto callback = UsernamesLinkCallback( const auto callback = UsernamesLinkCallback(
_peer, _peer,
controller->uiShow(), controller,
QString()); QString());
const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) { const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) {
if (!request.link) { if (!request.link) {
@ -1108,7 +1109,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
}, copyUsername->lifetime()); }, copyUsername->lifetime());
copyUsername->setClickedCallback([=] { copyUsername->setClickedCallback([=] {
const auto link = user->session().createInternalLinkFull( const auto link = user->session().createInternalLinkFull(
user->userName()); user->username());
if (!link.isEmpty()) { if (!link.isEmpty()) {
QGuiApplication::clipboard()->setText(link); QGuiApplication::clipboard()->setText(link);
controller->showToast(tr::lng_username_copied(tr::now)); controller->showToast(tr::lng_username_copied(tr::now));
@ -1180,14 +1181,15 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
auto linkText = LinkValue( auto linkText = LinkValue(
_peer, _peer,
true true
) | rpl::map([=](const QString &link) { ) | rpl::map([=](const LinkWithUrl &link) {
return link.isEmpty() const auto text = link.text;
return text.isEmpty()
? TextWithEntities() ? TextWithEntities()
: Ui::Text::Link( : Ui::Text::Link(
(link.startsWith(u"https://"_q) (text.startsWith(u"https://"_q)
? link.mid(u"https://"_q.size()) ? text.mid(u"https://"_q.size())
: link) + addToLink, : text) + addToLink,
link + addToLink); (addToLink.isEmpty() ? link.url : (text + addToLink)));
}); });
auto linkLine = addInfoOneLine( auto linkLine = addInfoOneLine(
(topicRootId (topicRootId
@ -1198,7 +1200,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
const auto controller = _controller->parentController(); const auto controller = _controller->parentController();
const auto linkCallback = UsernamesLinkCallback( const auto linkCallback = UsernamesLinkCallback(
_peer, _peer,
controller->uiShow(), controller,
addToLink); addToLink);
linkLine.text->overrideLinkClickHandler(linkCallback); linkLine.text->overrideLinkClickHandler(linkCallback);
linkLine.subtext->overrideLinkClickHandler(linkCallback); linkLine.subtext->overrideLinkClickHandler(linkCallback);

View file

@ -112,23 +112,22 @@ int TextItem::contentHeight() const {
} // namespace } // namespace
void AddPhoneMenu(not_null<Ui::PopupMenu*> menu, not_null<UserData*> user) { bool IsCollectiblePhone(not_null<UserData*> user) {
if (user->isSelf()) {
return;
}
using Strings = std::vector<QString>; using Strings = std::vector<QString>;
const auto prefixes = user->session().appConfig().get<Strings>( const auto prefixes = user->session().appConfig().get<Strings>(
u"fragment_prefixes"_q, u"fragment_prefixes"_q,
std::vector<QString>()); Strings{ u"888"_q });
{ const auto phone = user->phone();
const auto proj = [&phone = user->phone()](const QString &p) { const auto proj = [&](const QString &p) {
return phone.startsWith(p); return phone.startsWith(p);
}; };
if (ranges::none_of(prefixes, proj)) { return ranges::any_of(prefixes, proj);
return; }
}
} void AddPhoneMenu(not_null<Ui::PopupMenu*> menu, not_null<UserData*> user) {
if (const auto url = AppConfig::FragmentLink(&user->session())) { if (user->isSelf() || !IsCollectiblePhone(user)) {
return;
} else if (const auto url = AppConfig::FragmentLink(&user->session())) {
menu->addSeparator(&st::expandedMenuSeparator); menu->addSeparator(&st::expandedMenuSeparator);
const auto link = Ui::Text::Link( const auto link = Ui::Text::Link(
tr::lng_info_mobile_context_menu_fragment_about_link(tr::now), tr::lng_info_mobile_context_menu_fragment_about_link(tr::now),

View file

@ -16,6 +16,8 @@ class PopupMenu;
namespace Info { namespace Info {
namespace Profile { namespace Profile {
[[nodiscard]] bool IsCollectiblePhone(not_null<UserData*> user);
void AddPhoneMenu(not_null<Ui::PopupMenu*> menu, not_null<UserData*> user); void AddPhoneMenu(not_null<Ui::PopupMenu*> menu, not_null<UserData*> user);
} // namespace Profile } // namespace Profile

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h" #include "api/api_chat_participants.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "info/profile/info_profile_phone_menu.h"
#include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_badge.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
@ -55,7 +56,7 @@ auto PlainUsernameValue(not_null<PeerData*> peer) {
peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username), peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username),
peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames) peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames)
) | rpl::map([=] { ) | rpl::map([=] {
return peer->userName(); return peer->username();
}); });
} }
@ -136,14 +137,19 @@ rpl::producer<TextWithEntities> PhoneOrHiddenValue(not_null<UserData*> user) {
PlainUsernameValue(user), PlainUsernameValue(user),
PlainAboutValue(user), PlainAboutValue(user),
tr::lng_info_mobile_hidden() tr::lng_info_mobile_hidden()
) | rpl::map([]( ) | rpl::map([user](
const TextWithEntities &phone, const TextWithEntities &phone,
const QString &username, const QString &username,
const QString &about, const QString &about,
const QString &hidden) { const QString &hidden) {
return (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) if (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) {
? Ui::Text::WithEntities(hidden) return Ui::Text::WithEntities(hidden);
: phone; } else if (IsCollectiblePhone(user)) {
return Ui::Text::Link(phone, u"internal:collectible_phone/"_q
+ user->phone() + '@' + QString::number(user->id.value));
} else {
return phone;
}
}); });
} }
@ -160,15 +166,22 @@ rpl::producer<TextWithEntities> UsernameValue(
}) | Ui::Text::ToWithEntities(); }) | Ui::Text::ToWithEntities();
} }
QString UsernameUrl(not_null<PeerData*> peer, const QString &username) {
return peer->isUsernameEditable(username)
? peer->session().createInternalLinkFull(username)
: (u"internal:collectible_username/"_q
+ username
+ "@"
+ QString::number(peer->id.value));
}
rpl::producer<std::vector<TextWithEntities>> UsernamesValue( rpl::producer<std::vector<TextWithEntities>> UsernamesValue(
not_null<PeerData*> peer) { not_null<PeerData*> peer) {
const auto map = [=](const std::vector<QString> &usernames) { const auto map = [=](const std::vector<QString> &usernames) {
return ranges::views::all( return ranges::views::all(
usernames usernames
) | ranges::views::transform([&](const QString &u) { ) | ranges::views::transform([&](const QString &u) {
return Ui::Text::Link( return Ui::Text::Link(u, UsernameUrl(peer, u));
u,
peer->session().createInternalLinkFull(u));
}) | ranges::to_vector; }) | ranges::to_vector;
}; };
auto value = rpl::merge( auto value = rpl::merge(
@ -219,14 +232,19 @@ rpl::producer<TextWithEntities> AboutValue(not_null<PeerData*> peer) {
}); });
} }
rpl::producer<QString> LinkValue(not_null<PeerData*> peer, bool primary) { rpl::producer<LinkWithUrl> LinkValue(not_null<PeerData*> peer, bool primary) {
return (primary return (primary
? PlainPrimaryUsernameValue(peer) ? PlainPrimaryUsernameValue(peer)
: PlainUsernameValue(peer) | rpl::type_erased() : PlainUsernameValue(peer) | rpl::type_erased()
) | rpl::map([=](QString &&username) { ) | rpl::map([=](QString &&username) {
return username.isEmpty() return LinkWithUrl{
? QString() .text = (username.isEmpty()
: peer->session().createInternalLinkFull(username); ? QString()
: peer->session().createInternalLinkFull(username)),
.url = (username.isEmpty()
? QString()
: UsernameUrl(peer, username)),
};
}); });
} }

View file

@ -61,14 +61,23 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
bool primary = false); bool primary = false);
[[nodiscard]] rpl::producer<std::vector<TextWithEntities>> UsernamesValue( [[nodiscard]] rpl::producer<std::vector<TextWithEntities>> UsernamesValue(
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] QString UsernameUrl(
not_null<PeerData*> peer,
const QString &username);
[[nodiscard]] TextWithEntities AboutWithEntities( [[nodiscard]] TextWithEntities AboutWithEntities(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const QString &value); const QString &value);
[[nodiscard]] rpl::producer<TextWithEntities> AboutValue( [[nodiscard]] rpl::producer<TextWithEntities> AboutValue(
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<QString> LinkValue(
struct LinkWithUrl {
QString text;
QString url;
};
[[nodiscard]] rpl::producer<LinkWithUrl> LinkValue(
not_null<PeerData*> peer, not_null<PeerData*> peer,
bool primary = false); bool primary = false);
[[nodiscard]] rpl::producer<const ChannelLocation*> LocationValue( [[nodiscard]] rpl::producer<const ChannelLocation*> LocationValue(
not_null<ChannelData*> channel); not_null<ChannelData*> channel);
[[nodiscard]] rpl::producer<bool> NotificationsEnabledValue( [[nodiscard]] rpl::producer<bool> NotificationsEnabledValue(

View file

@ -17,10 +17,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_domain.h" #include "main/main_domain.h"
#include "storage/storage_domain.h" #include "storage/storage_domain.h"
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "iv/iv_instance.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/chat/attach/attach_bot_webview.h" #include "ui/chat/attach/attach_bot_webview.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
@ -653,6 +655,15 @@ void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) {
} }
} }
void AttachWebView::botOpenIvLink(QString uri) {
const auto window = _context ? _context->controller.get() : nullptr;
if (window) {
Core::App().iv().openWithIvPreferred(window, uri);
} else {
Core::App().iv().openWithIvPreferred(_session, uri);
}
}
void AttachWebView::botSendData(QByteArray data) { void AttachWebView::botSendData(QByteArray data) {
if (!_context if (!_context
|| _context->fromSwitch || _context->fromSwitch

View file

@ -165,6 +165,7 @@ private:
bool botHandleLocalUri(QString uri, bool keepOpen) override; bool botHandleLocalUri(QString uri, bool keepOpen) override;
void botHandleInvoice(QString slug) override; void botHandleInvoice(QString slug) override;
void botHandleMenuButton(Ui::BotWebView::MenuButton button) override; void botHandleMenuButton(Ui::BotWebView::MenuButton button) override;
void botOpenIvLink(QString uri) override;
void botSendData(QByteArray data) override; void botSendData(QByteArray data) override;
void botSwitchInlineQuery( void botSwitchInlineQuery(
std::vector<QString> chatTypes, std::vector<QString> chatTypes,

View file

@ -25,7 +25,7 @@ QByteArray GeoPointId(Geo point) {
const auto lon = int(point.lon * 1000000); const auto lon = int(point.lon * 1000000);
const auto combined = (std::uint64_t(std::uint32_t(lat)) << 32) const auto combined = (std::uint64_t(std::uint32_t(lat)) << 32)
| std::uint64_t(std::uint32_t(lon)); | std::uint64_t(std::uint32_t(lon));
return QByteArray::number(combined) return QByteArray::number(quint64(combined))
+ ',' + ','
+ QByteArray::number(point.access); + QByteArray::number(point.access);
} }

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h" #include "core/application.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "core/shortcuts.h" #include "core/shortcuts.h"
#include "core/click_handler_types.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_cloud_file.h" #include "data/data_cloud_file.h"
@ -63,7 +64,7 @@ class Shown final : public base::has_weak_ptr {
public: public:
Shown( Shown(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
std::shared_ptr<Main::SessionShow> show, not_null<Main::Session*> session,
not_null<Data*> data, not_null<Data*> data,
QString hash); QString hash);
@ -169,12 +170,11 @@ private:
Shown::Shown( Shown::Shown(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
std::shared_ptr<Main::SessionShow> show, not_null<Main::Session*> session,
not_null<Data*> data, not_null<Data*> data,
QString hash) QString hash)
: _delegate(delegate) : _delegate(delegate)
, _session(&show->session()) , _session(session) {
, _show(show) {
prepare(data, hash); prepare(data, hash);
} }
@ -818,7 +818,13 @@ void Instance::show(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data, not_null<Data*> data,
QString hash) { QString hash) {
const auto session = &show->session(); this->show(&show->session(), data, hash);
}
void Instance::show(
not_null<Main::Session*> session,
not_null<Data*> data,
QString hash) {
const auto guard = gsl::finally([&] { const auto guard = gsl::finally([&] {
if (data->partial()) { if (data->partial()) {
requestFull(session, data->id()); requestFull(session, data->id());
@ -828,10 +834,13 @@ void Instance::show(
_shown->moveTo(data, hash); _shown->moveTo(data, hash);
return; return;
} }
_shown = std::make_unique<Shown>(_delegate, show, data, hash); _shown = std::make_unique<Shown>(_delegate, session, data, hash);
_shownSession = session; _shownSession = session;
_shown->events() | rpl::start_with_next([=](Controller::Event event) { _shown->events() | rpl::start_with_next([=](Controller::Event event) {
using Type = Controller::Event::Type; using Type = Controller::Event::Type;
const auto lower = event.url.toLower();
const auto urlChecked = lower.startsWith("http://")
|| lower.startsWith("https://");
switch (event.type) { switch (event.type) {
case Type::Close: case Type::Close:
_shown = nullptr; _shown = nullptr;
@ -846,7 +855,9 @@ void Instance::show(
processJoinChannel(event.context); processJoinChannel(event.context);
break; break;
case Type::OpenLinkExternal: case Type::OpenLinkExternal:
File::OpenUrl(event.url); if (urlChecked) {
File::OpenUrl(event.url);
}
closeAll(); closeAll();
break; break;
case Type::OpenMedia: case Type::OpenMedia:
@ -885,6 +896,9 @@ void Instance::show(
break; break;
case Type::OpenPage: case Type::OpenPage:
case Type::OpenLink: case Type::OpenLink:
if (!urlChecked) {
break;
}
_shownSession->api().request(MTPmessages_GetWebPage( _shownSession->api().request(MTPmessages_GetWebPage(
MTP_string(event.url), MTP_string(event.url),
MTP_int(0) MTP_int(0)
@ -896,7 +910,7 @@ void Instance::show(
if (page && page->iv) { if (page && page->iv) {
const auto parts = event.url.split('#'); const auto parts = event.url.split('#');
const auto hash = (parts.size() > 1) ? parts[1] : u""_q; const auto hash = (parts.size() > 1) ? parts[1] : u""_q;
this->show(show, page->iv.get(), hash); this->show(_shownSession, page->iv.get(), hash);
} else { } else {
UrlClickHandler::Open(event.url); UrlClickHandler::Open(event.url);
} }
@ -921,20 +935,104 @@ void Instance::show(
} }
}, _shown->lifetime()); }, _shown->lifetime());
if (!_tracking.contains(session)) { trackSession(session);
_tracking.emplace(session); }
session->lifetime().add([=] {
_tracking.remove(session); void Instance::trackSession(not_null<Main::Session*> session) {
_joining.remove(session); if (!_tracking.emplace(session).second) {
_fullRequested.remove(session); return;
if (_shownSession == session) {
_shownSession = nullptr;
}
if (_shown && _shown->showingFrom(session)) {
_shown = nullptr;
}
});
} }
session->lifetime().add([=] {
_tracking.remove(session);
_joining.remove(session);
_fullRequested.remove(session);
_ivCache.remove(session);
if (_ivRequestSession == session) {
session->api().request(_ivRequestId).cancel();
_ivRequestSession = nullptr;
_ivRequestUri = QString();
_ivRequestId = 0;
}
if (_shownSession == session) {
_shownSession = nullptr;
}
if (_shown && _shown->showingFrom(session)) {
_shown = nullptr;
}
});
}
void Instance::openWithIvPreferred(
not_null<Window::SessionController*> controller,
QString uri,
QVariant context) {
auto my = context.value<ClickHandlerContext>();
my.sessionWindow = controller;
openWithIvPreferred(
&controller->session(),
uri,
QVariant::fromValue(my));
}
void Instance::openWithIvPreferred(
not_null<Main::Session*> session,
QString uri,
QVariant context) {
const auto openExternal = [=] {
auto my = context.value<ClickHandlerContext>();
my.ignoreIv = true;
UrlClickHandler::Open(uri, QVariant::fromValue(my));
};
const auto parts = uri.split('#');
if (parts.isEmpty() || parts[0].isEmpty()) {
return;
} else if (!ShowButton()) {
return openExternal();
}
trackSession(session);
const auto hash = (parts.size() > 1) ? parts[1] : u""_q;
const auto url = parts[0];
auto &cache = _ivCache[session];
if (const auto i = cache.find(url); i != end(cache)) {
const auto page = i->second;
if (page && page->iv) {
auto my = context.value<ClickHandlerContext>();
if (const auto window = my.sessionWindow.get()) {
show(window, page->iv.get(), hash);
} else {
show(session, page->iv.get(), hash);
}
} else {
openExternal();
}
return;
} else if (_ivRequestSession == session.get() && _ivRequestUri == uri) {
return;
} else if (_ivRequestId) {
_ivRequestSession->api().request(_ivRequestId).cancel();
}
const auto finish = [=](WebPageData *page) {
Expects(_ivRequestSession == session);
_ivRequestId = 0;
_ivRequestUri = QString();
_ivRequestSession = nullptr;
_ivCache[session][url] = page;
openWithIvPreferred(session, uri, context);
};
_ivRequestSession = session;
_ivRequestUri = uri;
_ivRequestId = session->api().request(MTPmessages_GetWebPage(
MTP_string(url),
MTP_int(0)
)).done([=](const MTPmessages_WebPage &result) {
const auto &data = result.data();
session->data().processUsers(data.vusers());
session->data().processChats(data.vchats());
finish(session->data().processWebpage(data.vwebpage()));
}).fail([=] {
finish(nullptr);
}).send();
} }
void Instance::requestFull( void Instance::requestFull(
@ -1028,4 +1126,17 @@ void Instance::closeAll() {
_shown = nullptr; _shown = nullptr;
} }
bool PreferForUri(const QString &uri) {
const auto url = QUrl(uri);
const auto host = url.host().toLower();
const auto path = url.path().toLower();
return (host == u"telegra.ph"_q)
|| (host == u"te.legra.ph"_q)
|| (host == u"graph.org"_q)
|| (host == u"telegram.org"_q
&& (path.startsWith(u"/faq"_q)
|| path.startsWith(u"/privacy"_q)
|| path.startsWith(u"/blog"_q)));
}
} // namespace Iv } // namespace Iv

View file

@ -36,6 +36,19 @@ public:
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data, not_null<Data*> data,
QString hash); QString hash);
void show(
not_null<Main::Session*> session,
not_null<Data*> data,
QString hash);
void openWithIvPreferred(
not_null<Window::SessionController*> controller,
QString uri,
QVariant context = {});
void openWithIvPreferred(
not_null<Main::Session*> session,
QString uri,
QVariant context = {});
[[nodiscard]] bool hasActiveWindow( [[nodiscard]] bool hasActiveWindow(
not_null<Main::Session*> session) const; not_null<Main::Session*> session) const;
@ -52,6 +65,8 @@ private:
void processJoinChannel(const QString &context); void processJoinChannel(const QString &context);
void requestFull(not_null<Main::Session*> session, const QString &id); void requestFull(not_null<Main::Session*> session, const QString &id);
void trackSession(not_null<Main::Session*> session);
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
std::unique_ptr<Shown> _shown; std::unique_ptr<Shown> _shown;
@ -64,8 +79,18 @@ private:
not_null<Main::Session*>, not_null<Main::Session*>,
base::flat_set<QString>> _fullRequested; base::flat_set<QString>> _fullRequested;
base::flat_map<
not_null<Main::Session*>,
base::flat_map<QString, WebPageData*>> _ivCache;
Main::Session *_ivRequestSession = nullptr;
QString _ivRequestUri;
mtpRequestId _ivRequestId = 0;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };
[[nodiscard]] bool PreferForUri(const QString &uri);
} // namespace Iv } // namespace Iv

View file

@ -51,7 +51,7 @@ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
return Number(base::SafeRound(value * 10000.) / 100.); return Number(base::SafeRound(value * 10000.) / 100.);
}; };
[[nodiscard]] QByteArray EscapeAttr(QByteArray value) { [[nodiscard]] QByteArray Escape(QByteArray value) {
auto result = QByteArray(); auto result = QByteArray();
result.reserve(value.size()); result.reserve(value.size());
for (const auto &ch : value) { for (const auto &ch : value) {
@ -67,8 +67,8 @@ template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
return result; return result;
} }
[[nodiscard]] QByteArray EscapeText(QByteArray value) { [[nodiscard]] QByteArray Date(TimeId date) {
return EscapeAttr(value); return Escape(langDateTimeFull(base::unixtime::parse(date)).toUtf8());
} }
class Parser final { class Parser final {
@ -391,9 +391,7 @@ QByteArray Parser::block(const MTPDpageBlockSubtitle &data) {
QByteArray Parser::block(const MTPDpageBlockAuthorDate &data) { QByteArray Parser::block(const MTPDpageBlockAuthorDate &data) {
auto inner = rich(data.vauthor()); auto inner = rich(data.vauthor());
if (const auto date = data.vpublished_date().v) { if (const auto date = data.vpublished_date().v) {
const auto parsed = base::unixtime::parse(date); inner += " \xE2\x80\xA2 " + tag("time", Date(date));
inner += " \xE2\x80\xA2 "
+ tag("time", EscapeText(langDateTimeFull(parsed).toUtf8()));
} }
return tag("address", inner); return tag("address", inner);
} }
@ -412,7 +410,7 @@ QByteArray Parser::block(const MTPDpageBlockParagraph &data) {
QByteArray Parser::block(const MTPDpageBlockPreformatted &data) { QByteArray Parser::block(const MTPDpageBlockPreformatted &data) {
auto list = Attributes(); auto list = Attributes();
const auto language = EscapeAttr(utf(data.vlanguage())); const auto language = utf(data.vlanguage());
if (!language.isEmpty()) { if (!language.isEmpty()) {
list.push_back({ "data-language", language }); list.push_back({ "data-language", language });
list.push_back({ "class", "lang-" + language }); list.push_back({ "class", "lang-" + language });
@ -430,7 +428,7 @@ QByteArray Parser::block(const MTPDpageBlockDivider &data) {
} }
QByteArray Parser::block(const MTPDpageBlockAnchor &data) { QByteArray Parser::block(const MTPDpageBlockAnchor &data) {
return tag("a", { { "name", EscapeAttr(utf(data.vname())) } }); return tag("a", { { "name", utf(data.vname()) } });
} }
QByteArray Parser::block(const MTPDpageBlockList &data) { QByteArray Parser::block(const MTPDpageBlockList &data) {
@ -507,15 +505,13 @@ QByteArray Parser::block(
}; };
auto result = tag("div", attributes, inner); auto result = tag("div", attributes, inner);
const auto href = data.vurl() const auto href = data.vurl() ? utf(*data.vurl()) : photoFullUrl(photo);
? utf(*data.vurl())
: photoFullUrl(photo);
const auto id = Number(photo.id); const auto id = Number(photo.id);
result = tag("a", { result = tag("a", {
{ "href", href }, { "href", href },
{ "oncontextmenu", data.vurl() ? QByteArray() : "return false;" }, { "oncontextmenu", data.vurl() ? QByteArray() : "return false;" },
{ "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id }, { "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id },
}, result); }, result);
if (!slideshow) { if (!slideshow) {
result += caption(data.vcaption()); result += caption(data.vcaption());
} }
@ -621,9 +617,9 @@ QByteArray Parser::block(const MTPDpageBlockEmbed &data) {
attributes.push_back({ "height", iframeHeight }); attributes.push_back({ "height", iframeHeight });
if (const auto url = data.vurl()) { if (const auto url = data.vurl()) {
if (!autosize) { if (!autosize) {
attributes.push_back({ "src", EscapeAttr(utf(url)) }); attributes.push_back({ "src", utf(url) });
} else { } else {
attributes.push_back({ "srcdoc", EscapeAttr(utf(url)) }); attributes.push_back({ "srcdoc", utf(url) });
} }
} else if (const auto html = data.vhtml()) { } else if (const auto html = data.vhtml()) {
attributes.push_back({ "src", embedUrl(html->v) }); attributes.push_back({ "src", embedUrl(html->v) });
@ -661,12 +657,10 @@ QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) {
address += tag( address += tag(
"a", "a",
{ { "rel", "author" }, { "onclick", "return false;" } }, { { "rel", "author" }, { "onclick", "return false;" } },
EscapeText(utf(data.vauthor()))); utf(data.vauthor()));
if (const auto date = data.vdate().v) { if (const auto date = data.vdate().v) {
const auto parsed = base::unixtime::parse(date); const auto parsed = base::unixtime::parse(date);
address += tag( address += tag("time", Date(date));
"time",
EscapeText(langDateTimeFull(parsed).toUtf8()));
} }
const auto inner = tag("address", address) + list(data.vblocks()); const auto inner = tag("address", address) + list(data.vblocks());
result = tag("blockquote", { { "class", "embed-post" } }, inner); result = tag("blockquote", { { "class", "embed-post" } }, inner);
@ -675,7 +669,7 @@ QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) {
const auto inner = tag("strong", utf(data.vauthor())) const auto inner = tag("strong", utf(data.vauthor()))
+ tag( + tag(
"small", "small",
tag("a", { { "href", EscapeAttr(url) } }, EscapeText(url))); tag("a", { { "href", url } }, url));
result = tag("section", { { "class", "embed-post" } }, inner); result = tag("section", { { "class", "embed-post" } }, inner);
} }
result += caption(data.vcaption()); result += caption(data.vcaption());
@ -732,7 +726,7 @@ QByteArray Parser::block(const MTPDpageBlockChannel &data) {
: "https://t.me/" + username; : "https://t.me/" + username;
result = tag( result = tag(
"a", "a",
{ { "href", EscapeAttr(link) }, { "data-context", "channel" + id } }, { { "href", link }, { "data-context", "channel" + id } },
result); result);
_result.channelIds.emplace(id); _result.channelIds.emplace(id);
return tag("section", { return tag("section", {
@ -839,23 +833,21 @@ QByteArray Parser::block(const MTPDpageRelatedArticle &data) {
inner += tag( inner += tag(
"span", "span",
{ { "class", "related-link-title" } }, { { "class", "related-link-title" } },
EscapeText(utf(*title))); utf(*title));
} }
if (description) { if (description) {
inner += tag( inner += tag(
"span", "span",
{ { "class", "related-link-desc" } }, { { "class", "related-link-desc" } },
EscapeText(utf(*description))); utf(*description));
} }
if (author || published) { if (author || published) {
const auto separator = (author && published) ? ", " : "";
const auto parsed = base::unixtime::parse(published->v);
inner += tag( inner += tag(
"span", "span",
{ { "class", "related-link-source" } }, { { "class", "related-link-source" } },
EscapeText((author ? utf(*author) : QByteArray()) ((author ? utf(*author) : QByteArray())
+ separator + ((author && published) ? ", " : QByteArray())
+ langDateTimeFull(parsed).toUtf8())); + (published ? Date(published->v) : QByteArray())));
} }
result += tag("span", { result += tag("span", {
{ "class", "related-link-content" }, { "class", "related-link-content" },
@ -912,22 +904,25 @@ QByteArray Parser::block(const MTPDpageListItemBlocks &data) {
} }
QByteArray Parser::block(const MTPDpageListOrderedItemText &data) { QByteArray Parser::block(const MTPDpageListOrderedItemText &data) {
return tag("li", { { "value", utf(data.vnum()) } }, rich(data.vtext())); return tag(
"li",
{ { "value", utf(data.vnum()) } },
rich(data.vtext()));
} }
QByteArray Parser::block(const MTPDpageListOrderedItemBlocks &data) { QByteArray Parser::block(const MTPDpageListOrderedItemBlocks &data) {
return tag( return tag(
"li", "li",
{ { "value", EscapeAttr(utf(data.vnum())) } }, { { "value", utf(data.vnum()) } },
list(data.vblocks())); list(data.vblocks()));
} }
QByteArray Parser::utf(const MTPstring &text) { QByteArray Parser::utf(const MTPstring &text) {
return text.v; return Escape(text.v);
} }
QByteArray Parser::utf(const tl::conditional<MTPstring> &text) { QByteArray Parser::utf(const tl::conditional<MTPstring> &text) {
return text ? text->v : QByteArray(); return text ? utf(*text) : QByteArray();
} }
QByteArray Parser::tag( QByteArray Parser::tag(
@ -965,7 +960,7 @@ QByteArray Parser::rich(const MTPRichText &text) {
{ "\xE2\x81\xA8", "<span dir=\"auto\">" }, { "\xE2\x81\xA8", "<span dir=\"auto\">" },
{ "\xE2\x81\xA9", "</span>" }, { "\xE2\x81\xA9", "</span>" },
}; };
auto text = EscapeText(utf(data.vtext())); auto text = utf(data.vtext());
for (const auto &[from, to] : replacements) { for (const auto &[from, to] : replacements) {
text.replace(from, to); text.replace(from, to);
} }
@ -1016,8 +1011,8 @@ QByteArray Parser::rich(const MTPRichText &text) {
}, rich(data.vtext())); }, rich(data.vtext()));
}, [&](const MTPDtextEmail &data) { }, [&](const MTPDtextEmail &data) {
return tag("a", { return tag("a", {
{ "href", "mailto:" + EscapeAttr(utf(data.vemail())) }, { "href", "mailto:" + utf(data.vemail()) },
}, rich(data.vtext())); }, rich(data.vtext()));
}, [&](const MTPDtextSubscript &data) { }, [&](const MTPDtextSubscript &data) {
return tag("sub", rich(data.vtext())); return tag("sub", rich(data.vtext()));
}, [&](const MTPDtextSuperscript &data) { }, [&](const MTPDtextSuperscript &data) {
@ -1026,11 +1021,11 @@ QByteArray Parser::rich(const MTPRichText &text) {
return tag("mark", rich(data.vtext())); return tag("mark", rich(data.vtext()));
}, [&](const MTPDtextPhone &data) { }, [&](const MTPDtextPhone &data) {
return tag("a", { return tag("a", {
{ "href", "tel:" + EscapeAttr(utf(data.vphone())) }, { "href", "tel:" + utf(data.vphone()) },
}, rich(data.vtext())); }, rich(data.vtext()));
}, [&](const MTPDtextAnchor &data) { }, [&](const MTPDtextAnchor &data) {
const auto inner = rich(data.vtext()); const auto inner = rich(data.vtext());
const auto name = EscapeAttr(utf(data.vname())); const auto name = utf(data.vname());
return inner.isEmpty() return inner.isEmpty()
? tag("a", { { "name", name } }) ? tag("a", { { "name", name } })
: tag( : tag(

View file

@ -32,11 +32,13 @@ Domain::Domain(const QString &dataName)
: _dataName(dataName) : _dataName(dataName)
, _local(std::make_unique<Storage::Domain>(this, dataName)) { , _local(std::make_unique<Storage::Domain>(this, dataName)) {
_active.changes( _active.changes(
) | rpl::take(1) | rpl::start_with_next([] { ) | rpl::take(1) | rpl::start_with_next([=] {
// In case we had a legacy passcoded app we start settings here. // In case we had a legacy passcoded app we start settings here.
Core::App().startSettingsAndBackground(); Core::App().startSettingsAndBackground();
Core::App().notifications().createManager(); crl::on_main(this, [=] {
Core::App().notifications().createManager();
});
}, _lifetime); }, _lifetime);
_active.changes( _active.changes(
@ -51,7 +53,7 @@ Domain::Domain(const QString &dataName)
: rpl::never<Data::PeerUpdate>(); : rpl::never<Data::PeerUpdate>();
}) | rpl::flatten_latest( }) | rpl::flatten_latest(
) | rpl::start_with_next([](const Data::PeerUpdate &update) { ) | rpl::start_with_next([](const Data::PeerUpdate &update) {
CrashReports::SetAnnotation("Username", update.peer->userName()); CrashReports::SetAnnotation("Username", update.peer->username());
}, _lifetime); }, _lifetime);
} }

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
#include "base/timer.h" #include "base/timer.h"
#include "base/weak_ptr.h"
namespace Storage { namespace Storage {
class Domain; class Domain;
@ -23,7 +24,7 @@ namespace Main {
class Account; class Account;
class Session; class Session;
class Domain final { class Domain final : public base::has_weak_ptr {
public: public:
struct AccountWithIndex { struct AccountWithIndex {
int index = 0; int index = 0;

View file

@ -16,7 +16,6 @@ struct HistoryMessageMarkupButton;
class MainWindow; class MainWindow;
class HistoryWidget; class HistoryWidget;
class StackItem; class StackItem;
struct FileLoadResult;
class History; class History;
class Image; class Image;

View file

@ -933,6 +933,13 @@ void Controller::show(
peer->updateFull(); peer->updateFull();
} }
void Controller::jumpTo(
not_null<Data::Story*> story,
Data::StoriesContext context) {
show(story, std::move(context));
_delegate->storiesRedisplay(story);
}
bool Controller::changeShown(Data::Story *story) { bool Controller::changeShown(Data::Story *story) {
const auto id = story ? story->fullId() : FullStoryId(); const auto id = story ? story->fullId() : FullStoryId();
const auto session = story ? &story->session() : nullptr; const auto session = story ? &story->session() : nullptr;

View file

@ -141,6 +141,7 @@ public:
-> HistoryView::Reactions::CachedIconFactory &; -> HistoryView::Reactions::CachedIconFactory &;
void show(not_null<Data::Story*> story, Data::StoriesContext context); void show(not_null<Data::Story*> story, Data::StoriesContext context);
void jumpTo(not_null<Data::Story*> story, Data::StoriesContext context);
void ready(); void ready();
void updateVideoPlayback(const Player::TrackState &state); void updateVideoPlayback(const Player::TrackState &state);

View file

@ -431,7 +431,7 @@ void ReplyArea::chooseAttach(
} }
const auto filter = (overrideSendImagesAsPhotos == true) const auto filter = (overrideSendImagesAsPhotos == true)
? FileDialog::ImagesOrAllFilter() ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter(); : FileDialog::AllOrImagesFilter();
const auto weak = make_weak(&_shownPeerGuard); const auto weak = make_weak(&_shownPeerGuard);
const auto callback = [=](FileDialog::OpenResult &&result) { const auto callback = [=](FileDialog::OpenResult &&result) {

View file

@ -184,7 +184,7 @@ RepostClickHandler RepostView::lookupHandler(QPoint position) {
const auto of = owner->stories().lookup({ peer->id, id }); const auto of = owner->stories().lookup({ peer->id, id });
if (of) { if (of) {
using namespace Data; using namespace Data;
_controller->show(*of, { StoriesContextSingle() }); _controller->jumpTo(*of, { StoriesContextSingle() });
} else { } else {
_controller->uiShow()->show(PrepareShortInfoBox(peer)); _controller->uiShow()->show(PrepareShortInfoBox(peer));
} }

View file

@ -3262,7 +3262,7 @@ not_null<QWidget*> OverlayWidget::widget() const {
void OverlayWidget::hide() { void OverlayWidget::hide() {
clearBeforeHide(); clearBeforeHide();
//applyHideWindowWorkaround(); applyHideWindowWorkaround();
_window->hide(); _window->hide();
} }

View file

@ -60,6 +60,7 @@ void AboutBox(
widget->paintRequest( widget->paintRequest(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
auto p = Painter(widget); auto p = Painter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg); p.setBrush(st::activeButtonBg);
p.drawEllipse(rect); p.drawEllipse(rect);

View file

@ -40,10 +40,6 @@ constexpr ShiftedDcId groupCallStreamDcId(DcId dcId) {
return ShiftDcId(dcId, kGroupCallStreamDcShift); return ShiftDcId(dcId, kGroupCallStreamDcShift);
} }
constexpr auto kUploadSessionsCount = 2;
constexpr auto kUploadSessionsCountMax = 8;
namespace details { namespace details {
constexpr ShiftedDcId downloadDcId(DcId dcId, int index) { constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
@ -94,7 +90,6 @@ inline DcId getTemporaryIdFromRealDcId(ShiftedDcId shiftedDcId) {
namespace details { namespace details {
constexpr ShiftedDcId uploadDcId(DcId dcId, int index) { constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
static_assert(kUploadSessionsCountMax < kMaxMediaDcCount, "Too large MTPUploadSessionsCount!");
return ShiftDcId(dcId, kBaseUploadDcShift + index); return ShiftDcId(dcId, kBaseUploadDcShift + index);
}; };
@ -103,14 +98,14 @@ constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
// send(req, callbacks, MTP::uploadDcId(index)) - for upload shifted dc id // send(req, callbacks, MTP::uploadDcId(index)) - for upload shifted dc id
// uploading always to the main dc so BareDcId(result) == 0 // uploading always to the main dc so BareDcId(result) == 0
inline ShiftedDcId uploadDcId(int index) { inline ShiftedDcId uploadDcId(int index) {
Expects(index >= 0 && index < kUploadSessionsCountMax); Expects(index >= 0 && index < kMaxMediaDcCount);
return details::uploadDcId(0, index); return details::uploadDcId(0, index);
}; };
constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) { constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) {
return (shiftedDcId >= details::uploadDcId(0, 0)) return (shiftedDcId >= details::uploadDcId(0, 0))
&& (shiftedDcId < details::uploadDcId(0, kUploadSessionsCountMax - 1) + kDcShift); && (shiftedDcId < details::uploadDcId(0, kMaxMediaDcCount - 1) + kDcShift);
} }
inline ShiftedDcId destroyKeyNextDcId(ShiftedDcId shiftedDcId) { inline ShiftedDcId destroyKeyNextDcId(ShiftedDcId shiftedDcId) {

View file

@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "calls/calls_instance.h" #include "calls/calls_instance.h"
#include "main/main_account.h" // Account::configUpdated. #include "main/main_account.h" // Account::configUpdated.
#include "apiwrap.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "lang/lang_instance.h" #include "lang/lang_instance.h"

View file

@ -60,6 +60,8 @@ constexpr auto kSendStateRequestWaiting = crl::time(1000);
// How much time to wait for some more requests, when sending msg acks. // How much time to wait for some more requests, when sending msg acks.
constexpr auto kAckSendWaiting = 10 * crl::time(1000); constexpr auto kAckSendWaiting = 10 * crl::time(1000);
constexpr auto kCutContainerOnSize = 16 * 1024;
auto SyncTimeRequestDuration = kFastRequestDuration; auto SyncTimeRequestDuration = kFastRequestDuration;
using namespace details; using namespace details;
@ -696,7 +698,8 @@ void SessionPrivate::tryToSend() {
initSize = initSizeInInts * sizeof(mtpPrime); initSize = initSizeInInts * sizeof(mtpPrime);
} }
bool needAnyResponse = false; auto needAnyResponse = false;
auto someSkipped = false;
SerializedRequest toSendRequest; SerializedRequest toSendRequest;
{ {
QWriteLocker locker1(_sessionData->toSendMutex()); QWriteLocker locker1(_sessionData->toSendMutex());
@ -711,15 +714,33 @@ void SessionPrivate::tryToSend() {
locker1.unlock(); locker1.unlock();
} }
uint32 toSendCount = toSend.size(); auto totalSending = int(toSend.size());
if (pingRequest) ++toSendCount; auto sendingFrom = begin(toSend);
if (ackRequest) ++toSendCount; auto sendingTill = end(toSend);
if (resendRequest) ++toSendCount; auto combinedLength = 0;
if (stateRequest) ++toSendCount; for (auto i = sendingFrom; i != sendingTill; ++i) {
if (httpWaitRequest) ++toSendCount; combinedLength += i->second->size();
if (bindDcKeyRequest) ++toSendCount; if (combinedLength >= kCutContainerOnSize) {
++i;
if (const auto skipping = int(sendingTill - i)) {
sendingTill = i;
totalSending -= skipping;
Assert(totalSending > 0);
someSkipped = true;
}
break;
}
}
auto sendingRange = ranges::make_subrange(sendingFrom, sendingTill);
const auto sendingCount = totalSending;
if (pingRequest) ++totalSending;
if (ackRequest) ++totalSending;
if (resendRequest) ++totalSending;
if (stateRequest) ++totalSending;
if (httpWaitRequest) ++totalSending;
if (bindDcKeyRequest) ++totalSending;
if (!toSendCount) { if (!totalSending) {
return; // nothing to send return; // nothing to send
} }
@ -735,11 +756,11 @@ void SessionPrivate::tryToSend() {
? httpWaitRequest ? httpWaitRequest
: bindDcKeyRequest : bindDcKeyRequest
? bindDcKeyRequest ? bindDcKeyRequest
: toSend.begin()->second; : sendingRange.begin()->second;
if (toSendCount == 1 && !first->forceSendInContainer) { if (totalSending == 1 && !first->forceSendInContainer) {
toSendRequest = first; toSendRequest = first;
if (sendAll) { if (sendAll) {
toSend.clear(); toSend.erase(sendingFrom, sendingTill);
locker1.unlock(); locker1.unlock();
} }
@ -808,7 +829,7 @@ void SessionPrivate::tryToSend() {
if (stateRequest) containerSize += stateRequest.messageSize(); if (stateRequest) containerSize += stateRequest.messageSize();
if (httpWaitRequest) containerSize += httpWaitRequest.messageSize(); if (httpWaitRequest) containerSize += httpWaitRequest.messageSize();
if (bindDcKeyRequest) containerSize += bindDcKeyRequest.messageSize(); if (bindDcKeyRequest) containerSize += bindDcKeyRequest.messageSize();
for (const auto &[requestId, request] : toSend) { for (const auto &[requestId, request] : sendingRange) {
containerSize += request.messageSize(); containerSize += request.messageSize();
if (needsLayer && request->needsLayer) { if (needsLayer && request->needsLayer) {
containerSize += initSizeInInts; containerSize += initSizeInInts;
@ -825,9 +846,9 @@ void SessionPrivate::tryToSend() {
// prepare container + each in invoke after // prepare container + each in invoke after
toSendRequest = SerializedRequest::Prepare( toSendRequest = SerializedRequest::Prepare(
containerSize, containerSize,
containerSize + 3 * toSend.size()); containerSize + 3 * sendingCount);
toSendRequest->push_back(mtpc_msg_container); toSendRequest->push_back(mtpc_msg_container);
toSendRequest->push_back(toSendCount); toSendRequest->push_back(totalSending);
// check for a valid container // check for a valid container
auto bigMsgId = base::unixtime::mtproto_msg_id(); auto bigMsgId = base::unixtime::mtproto_msg_id();
@ -839,7 +860,7 @@ void SessionPrivate::tryToSend() {
// prepare sent container // prepare sent container
auto sentIdsWrap = SentContainer(); auto sentIdsWrap = SentContainer();
sentIdsWrap.sent = crl::now(); sentIdsWrap.sent = crl::now();
sentIdsWrap.messages.reserve(toSendCount); sentIdsWrap.messages.reserve(totalSending);
if (bindDcKeyRequest) { if (bindDcKeyRequest) {
_bindMsgId = placeToContainer( _bindMsgId = placeToContainer(
@ -848,6 +869,7 @@ void SessionPrivate::tryToSend() {
false, false,
bindDcKeyRequest); bindDcKeyRequest);
_bindMessageSent = crl::now(); _bindMessageSent = crl::now();
sentIdsWrap.messages.push_back(_bindMsgId);
needAnyResponse = true; needAnyResponse = true;
} }
if (pingRequest) { if (pingRequest) {
@ -856,10 +878,11 @@ void SessionPrivate::tryToSend() {
bigMsgId, bigMsgId,
forceNewMsgId, forceNewMsgId,
pingRequest); pingRequest);
sentIdsWrap.messages.push_back(_pingMsgId);
needAnyResponse = true; needAnyResponse = true;
} }
for (auto &[requestId, request] : toSend) { for (auto &[requestId, request] : sendingRange) {
const auto msgId = prepareToSend( const auto msgId = prepareToSend(
request, request,
bigMsgId, bigMsgId,
@ -904,7 +927,7 @@ void SessionPrivate::tryToSend() {
memcpy(toSendRequest->data() + from, request->constData() + 4, len * sizeof(mtpPrime)); memcpy(toSendRequest->data() + from, request->constData() + 4, len * sizeof(mtpPrime));
} }
} }
toSend.clear(); toSend.erase(sendingFrom, sendingTill);
if (stateRequest) { if (stateRequest) {
const auto msgId = placeToContainer( const auto msgId = placeToContainer(
@ -951,6 +974,11 @@ void SessionPrivate::tryToSend() {
} }
} }
sendSecureRequest(std::move(toSendRequest), needAnyResponse); sendSecureRequest(std::move(toSendRequest), needAnyResponse);
if (someSkipped) {
InvokeQueued(this, [=] {
tryToSend();
});
}
} }
void SessionPrivate::retryByTimer() { void SessionPrivate::retryByTimer() {
@ -1108,9 +1136,6 @@ void SessionPrivate::onSentSome(uint64 size) {
DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain)); DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain));
} }
} }
if (isUploadDcId(_shiftedDcId)) {
remain *= kUploadSessionsCount;
}
_waitForReceivedTimer.callOnce(remain); _waitForReceivedTimer.callOnce(remain);
} }
if (!_firstSentAt) { if (!_firstSentAt) {
@ -1577,13 +1602,22 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
correctUnixtimeWithBadLocal(info.serverTime); correctUnixtimeWithBadLocal(info.serverTime);
info.badTime = false; info.badTime = false;
} }
if (_bindMsgId) {
LOG(("Message Info: bad message notification received"
" while binding temp key, restarting."));
return HandleResult::RestartConnection;
}
LOG(("Message Info: bad message notification received, msgId %1, error_code %2").arg(data.vbad_msg_id().v).arg(errorCode)); LOG(("Message Info: bad message notification received, msgId %1, error_code %2").arg(data.vbad_msg_id().v).arg(errorCode));
return HandleResult::ResetSession; return HandleResult::ResetSession;
} }
} else { // fatal (except 48, but it must not get here) } else { // fatal (except 48, but it must not get here)
const auto badMsgId = mtpMsgId(data.vbad_msg_id().v); const auto badMsgId = mtpMsgId(data.vbad_msg_id().v);
const auto requestId = wasSent(resendId); const auto requestId = wasSent(resendId);
if (requestId) { if (_bindMsgId) {
LOG(("Message Error: fatal bad message notification received"
" while binding temp key, restarting."));
return HandleResult::RestartConnection;
} else if (requestId) {
LOG(("Message Error: " LOG(("Message Error: "
"fatal bad message notification received, " "fatal bad message notification received, "
"msgId %1, error_code %2, requestId: %3" "msgId %1, error_code %2, requestId: %3"
@ -1628,7 +1662,9 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
} }
_sessionSalt = data.vnew_server_salt().v; _sessionSalt = data.vnew_server_salt().v;
correctUnixtimeWithBadLocal(info.serverTime);
// Don't force time update here.
base::unixtime::update(info.serverTime);
if (_bindMsgId) { if (_bindMsgId) {
LOG(("Message Info: bad_server_salt received while binding temp key, restarting.")); LOG(("Message Info: bad_server_salt received while binding temp key, restarting."));
@ -2056,7 +2092,7 @@ void SessionPrivate::correctUnixtimeByFastRequest(
locker.unlock(); locker.unlock();
SyncTimeRequestDuration = duration; SyncTimeRequestDuration = duration;
base::unixtime::update(serverTime, true); base::unixtime::update(serverTime);
return; return;
} }
} }

View file

@ -1581,14 +1581,10 @@ void FormController::uploadEncryptedFile(
&session(), &session(),
std::make_unique<UploadScanData>(std::move(data))); std::make_unique<UploadScanData>(std::move(data)));
auto prepared = std::make_shared<FileLoadResult>( auto prepared = MakePreparedFile({
TaskId(), .id = file.uploadData->fileId,
file.uploadData->fileId, .type = SendMediaType::Secure,
FileLoadTo(PeerId(), Api::SendOptions(), FullReplyTo(), MsgId()), });
TextWithTags(),
false,
std::shared_ptr<SendingAlbum>(nullptr));
prepared->type = SendMediaType::Secure;
prepared->content = QByteArray::fromRawData( prepared->content = QByteArray::fromRawData(
reinterpret_cast<char*>(file.uploadData->bytes.data()), reinterpret_cast<char*>(file.uploadData->bytes.data()),
file.uploadData->bytes.size()); file.uploadData->bytes.size());

View file

@ -37,6 +37,7 @@ namespace Notifications {
namespace { namespace {
using namespace gi::repository; using namespace gi::repository;
namespace GObject = gi::repository::GObject;
constexpr auto kService = "org.freedesktop.Notifications"; constexpr auto kService = "org.freedesktop.Notifications";
constexpr auto kObjectPath = "/org/freedesktop/Notifications"; constexpr auto kObjectPath = "/org/freedesktop/Notifications";

View file

@ -510,7 +510,7 @@ QString SingleInstanceLocalServerName(const QString &hash) {
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
std::optional<bool> IsDarkMode() { std::optional<bool> IsDarkMode() {
const auto result = base::Platform::XDP::ReadSetting( auto result = base::Platform::XDP::ReadSetting(
"org.freedesktop.appearance", "org.freedesktop.appearance",
"color-scheme"); "color-scheme");

View file

@ -220,7 +220,7 @@ void AwayMessage::setupContent(
_recipients = disabled _recipients = disabled
? Data::BusinessRecipients{ .allButExcluded = true } ? Data::BusinessRecipients{ .allButExcluded = true }
: current.recipients; : Data::BusinessRecipients::MakeValid(current.recipients);
auto initialSchedule = disabled ? AwaySchedule{ auto initialSchedule = disabled ? AwaySchedule{
.type = AwayScheduleType::Always, .type = AwayScheduleType::Always,
} : current.schedule; } : current.schedule;

View file

@ -160,7 +160,7 @@ private:
object_ptr<Ui::InputField>( object_ptr<Ui::InputField>(
container, container,
st::settingsChatIntroField, st::settingsChatIntroField,
tr::lng_chat_intro_enter_title(), std::move(placeholder),
current), current),
st::settingsChatIntroFieldMargins); st::settingsChatIntroFieldMargins);
field->setMaxLength(limit); field->setMaxLength(limit);

View file

@ -408,7 +408,7 @@ void Chatbots::setupContent(
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this); const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto current = controller->session().data().chatbots().current(); const auto current = controller->session().data().chatbots().current();
_recipients = current.recipients; _recipients = Data::BusinessRecipients::MakeValid(current.recipients);
_repliesAllowed = current.repliesAllowed; _repliesAllowed = current.repliesAllowed;
AddDividerTextWithLottie(content, { AddDividerTextWithLottie(content, {

View file

@ -125,7 +125,7 @@ void Greeting::setupContent(
_recipients = disabled _recipients = disabled
? Data::BusinessRecipients{ .allButExcluded = true } ? Data::BusinessRecipients{ .allButExcluded = true }
: current.recipients; : Data::BusinessRecipients::MakeValid(current.recipients);
_noActivityDays = disabled _noActivityDays = disabled
? kDefaultNoActivityDays ? kDefaultNoActivityDays
: current.noActivityDays; : current.noActivityDays;

View file

@ -1428,7 +1428,7 @@ void ShortcutMessages::chooseAttach(
_choosingAttach = false; _choosingAttach = false;
const auto filter = (overrideSendImagesAsPhotos == true) const auto filter = (overrideSendImagesAsPhotos == true)
? FileDialog::ImagesOrAllFilter() ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter(); : FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=]( FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
FileDialog::OpenResult &&result) { FileDialog::OpenResult &&result) {

View file

@ -11,10 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_main.h" #include "settings/settings_main.h"
#include "settings/settings_chat.h" #include "settings/settings_chat.h"
#include "settings/settings_codes.h" #include "settings/settings_codes.h"
#include "ui/basic_click_handlers.h"
#include "ui/wrap/fade_wrap.h" #include "ui/wrap/fade_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/cached_round_corners.h" #include "ui/cached_round_corners.h"
@ -104,7 +104,14 @@ object_ptr<Ui::RpWidget> CreateIntroSettings(
Ui::AddDivider(result); Ui::AddDivider(result);
Ui::AddSkip(result); Ui::AddSkip(result);
SetupFaq(result, false);
AddButtonWithIcon(
result,
tr::lng_settings_faq(),
st::settingsButtonNoIcon
)->addClickHandler([] {
OpenFaq(nullptr);
});
return result; return result;
} }

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_main.h" #include "settings/settings_main.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h"
#include "settings/settings_business.h" #include "settings/settings_business.h"
#include "settings/settings_codes.h" #include "settings/settings_codes.h"
#include "settings/settings_chat.h" #include "settings/settings_chat.h"
@ -26,13 +27,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/basic_click_handlers.h" #include "ui/basic_click_handlers.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h" #include "ui/controls/userpic_button.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/menu/menu_add_action_callback.h" #include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/continuous_sliders.h" #include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/buttons.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/new_badges.h" #include "ui/new_badges.h"
@ -43,9 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue
#include "lang/lang_cloud_manager.h" #include "lang/lang_cloud_manager.h"
#include "lang/lang_keys.h"
#include "lang/lang_instance.h" #include "lang/lang_instance.h"
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -62,13 +57,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
#include "window/window_controller.h" #include "window/window_controller.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "base/call_delayed.h" #include "base/call_delayed.h"
#include "base/unixtime.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "styles/style_settings.h" #include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h" #include "styles/style_info.h"
#include "styles/style_menu_icons.h" #include "styles/style_menu_icons.h"
@ -229,7 +220,7 @@ void Cover::initViewers() {
}, lifetime()); }, lifetime());
_username->overrideLinkClickHandler([=] { _username->overrideLinkClickHandler([=] {
const auto username = _user->userName(); const auto username = _user->username();
if (username.isEmpty()) { if (username.isEmpty()) {
_controller->show(Box(UsernamesBox, _user)); _controller->show(Box(UsernamesBox, _user));
} else { } else {
@ -625,26 +616,20 @@ void SetupInterfaceScale(
} }
} }
void OpenFaq() {
File::OpenUrl(telegramFaqLink());
}
void SetupFaq(not_null<Ui::VerticalLayout*> container, bool icon) {
AddButtonWithIcon(
container,
tr::lng_settings_faq(),
icon ? st::settingsButton : st::settingsButtonNoIcon,
{ icon ? &st::menuIconFaq : nullptr }
)->addClickHandler(OpenFaq);
}
void SetupHelp( void SetupHelp(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
Ui::AddDivider(container); Ui::AddDivider(container);
Ui::AddSkip(container); Ui::AddSkip(container);
SetupFaq(container); AddButtonWithIcon(
container,
tr::lng_settings_faq(),
st::settingsButton,
{ &st::menuIconFaq }
)->addClickHandler([=] {
OpenFaq(controller);
});
AddButtonWithIcon( AddButtonWithIcon(
container, container,
@ -688,7 +673,10 @@ void SetupHelp(
auto box = Ui::MakeConfirmBox({ auto box = Ui::MakeConfirmBox({
.text = tr::lng_settings_ask_sure(), .text = tr::lng_settings_ask_sure(),
.confirmed = sure, .confirmed = sure,
.cancelled = OpenFaq, .cancelled = [=](Fn<void()> close) {
OpenFaq(controller);
close();
},
.confirmText = tr::lng_settings_ask_ok(), .confirmText = tr::lng_settings_ask_ok(),
.cancelText = tr::lng_settings_faq_button(), .cancelText = tr::lng_settings_faq_button(),
.strictCancel = true, .strictCancel = true,
@ -767,4 +755,12 @@ void Main::setupContent(not_null<Window::SessionController*> controller) {
controller->session().data().cloudThemes().refresh(); controller->session().data().cloudThemes().refresh();
} }
void OpenFaq(base::weak_ptr<Window::SessionController> weak) {
UrlClickHandler::Open(
tr::lng_settings_faq_link(tr::now),
QVariant::fromValue(ClickHandlerContext{
.sessionWindow = weak,
}));
}
} // namespace Settings } // namespace Settings

View file

@ -28,9 +28,8 @@ void SetupInterfaceScale(
not_null<Window::Controller*> window, not_null<Window::Controller*> window,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
bool icon = true); bool icon = true);
void SetupFaq(
not_null<Ui::VerticalLayout*> container, void OpenFaq(base::weak_ptr<Window::SessionController> weak);
bool icon = true);
class Main : public Section<Main> { class Main : public Section<Main> {
public: public:

Some files were not shown because too many files have changed in this diff Show more