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

View file

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

View file

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

View file

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

View file

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

3
.gitmodules vendored
View file

@ -76,6 +76,9 @@
[submodule "Telegram/lib_webview"]
path = Telegram/lib_webview
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"]
path = Telegram/ThirdParty/dispatch
url = https://github.com/apple/swift-corelibs-libdispatch

View file

@ -1574,6 +1574,8 @@ PRIVATE
window/window_lock_widgets.h
window/window_main_menu.cpp
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.h
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_change_status" = "Change Emoji Status";
"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_enable_notifications_from_tray" = "Enable notifications";
@ -469,6 +471,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"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_notify" = "Notifications";
@ -1058,6 +1068,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_faq_button" = "Go to FAQ";
"lng_settings_ask_ok" = "Ask a Volunteer";
"lng_settings_faq" = "Telegram FAQ";
"lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
"lng_settings_features" = "Telegram Features";
"lng_settings_logout" = "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_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_coin_link" = "https://telegram.org/blog/monetization-for-channels";
"lng_channel_earn_chart_top_hours" = "Ad impressions";
"lng_channel_earn_chart_revenue" = "Ad revenue";
"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="phone.tgs">../../animations/phone.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>
</RCC>

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@ namespace {
constexpr auto kSharedMediaLimit = 100;
[[nodiscard]] SendMediaReady PreparePeerPhoto(
[[nodiscard]] std::shared_ptr<FilePrepareResult> PreparePeerPhoto(
MTP::DcId dcId,
PeerId peerId,
QImage &&image) {
@ -80,24 +80,17 @@ constexpr auto kSharedMediaLimit = 100;
MTPVector<MTPVideoSize>(),
MTP_int(dcId));
QString file, filename;
int64 filesize = 0;
QByteArray data;
return SendMediaReady(
SendMediaType::Photo,
file,
filename,
filesize,
data,
id,
id,
u"jpg"_q,
peerId,
photo,
photoThumbs,
MTP_documentEmpty(MTP_long(0)),
jpeg);
auto result = MakePreparedFile({
.id = id,
.type = SendMediaType::Photo,
});
result->type = SendMediaType::Photo;
result->setFileData(jpeg);
result->thumbId = id;
result->thumbname = "thumb.jpg";
result->photo = photo;
result->photoThumbs = photoThumbs;
return result;
}
[[nodiscard]] std::optional<MTPVideoSize> PrepareMtpMarkup(
@ -239,7 +232,7 @@ void PeerPhoto::upload(
_api.instance().mainDcId(),
peer->id,
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 {
SendMediaReady PrepareRingtoneDocument(
std::shared_ptr<FilePrepareResult> PrepareRingtoneDocument(
MTP::DcId dcId,
const QString &filename,
const QString &filemime,
const QByteArray &content) {
const auto id = base::RandomValue<DocumentId>();
auto attributes = QVector<MTPDocumentAttribute>(
1,
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_long(id),
MTP_long(0),
@ -45,21 +54,7 @@ SendMediaReady PrepareRingtoneDocument(
MTPVector<MTPVideoSize>(),
MTP_int(dcId),
MTP_vector<MTPDocumentAttribute>(std::move(attributes)));
return SendMediaReady(
SendMediaType::File,
QString(), // filepath
filename,
content.size(),
content,
id,
0,
QString(),
PeerId(),
MTP_photoEmpty(MTP_long(0)),
PreparedPhotoThumbs(),
document,
QByteArray());
return result;
}
} // namespace
@ -102,7 +97,7 @@ void Ringtones::upload(
_uploads.erase(already);
}
_uploads.emplace(fakeId, uploadedData);
_session->uploader().uploadMedia(fakeId, ready);
_session->uploader().upload(fakeId, ready);
}
void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) {

View file

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

View file

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

View file

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

View file

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

View file

@ -1044,3 +1044,33 @@ inviteForbiddenTitle: FlatLabel(boxTitle) {
inviteForbiddenTitlePadding: margins(32px, 4px, 32px, 0px);
inviteForbiddenLockBg: dialogsUnreadBgMuted;
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>;
private:
[[nodiscard]] std::unique_ptr<PeerListRow> createRow() const;
const not_null<Main::Session*> _session;
bool _premiums = false;
rpl::event_stream<> _selectionChanged;
rpl::event_stream<RowSelectionChange> _rowSelectionChanges;
@ -209,8 +206,7 @@ bool PremiumsRow::useForumLikeUserpic() const {
TypesController::TypesController(
not_null<Main::Session*> session,
bool premiums)
: _session(session)
, _premiums(premiums) {
: _session(session) {
}
Main::Session &TypesController::session() const {
@ -331,6 +327,9 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
_deselectOption = [=](PeerListRowId itemId) {
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
if (itemId == kPremiumsRowId) {
_selected.premiums = false;
}
_typesDelegate->peerListSetRowChecked(row, false);
}
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -587,11 +587,9 @@ void ChooseSourceProcess::setupGeometryWithParent(
not_null<QWidget*> parent) {
_window->createWinId();
const auto parentScreen = [&] {
if (!::Platform::IsWayland()) {
if (const auto screen = QGuiApplication::screenAt(
if (const auto screen = QGuiApplication::screenAt(
parent->geometry().center())) {
return screen;
}
return 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_channel.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_peer_values.h"
#include "data/stickers/data_stickers.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_menu_icons.h"
// AyuGram includes
#include "data/data_file_origin.h"
#include "qapplication.h"
#include <QtWidgets/QApplication>
namespace ChatHelpers {
namespace {
@ -62,6 +60,7 @@ namespace {
constexpr auto kCollapsedRows = 3;
constexpr auto kAppearDuration = 0.3;
constexpr auto kCustomSearchLimit = 256;
constexpr auto kColorPickerDelay = crl::time(500);
using Core::RecentEmojiId;
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() {
return SearchEmoji(_searchQuery, _searchEmoji);
}
@ -1125,7 +1133,7 @@ void EmojiListWidget::fillRecentMenu(
const auto addAction = Ui::Menu::CreateAddActionCallback(menu);
const auto over = OverEmoji{ section, index };
const auto emoji = lookupOverEmoji(&over);
const auto custom = lookupCustomEmoji(index, section);
const auto custom = lookupCustomEmoji(&over);
if (custom && custom->sticker()) {
const auto sticker = custom->sticker();
const auto emoji = sticker->alt;
@ -1498,6 +1506,11 @@ bool EmojiListWidget::checkPickerHide() {
return false;
}
DocumentData *EmojiListWidget::lookupCustomEmoji(
const OverEmoji *over) const {
return over ? lookupCustomEmoji(over->index, over->section) : nullptr;
}
DocumentData *EmojiListWidget::lookupCustomEmoji(
int index,
int section) const {
@ -1600,12 +1613,14 @@ void EmojiListWidget::mousePressEvent(QMouseEvent *e) {
if (!Core::App().settings().hasChosenEmojiVariant(emoji)) {
showPicker();
} 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) {
@ -1613,11 +1628,6 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed;
setPressed(v::null);
if (_previewShown) {
_previewShown = false;
return;
}
_lastMousePos = e->globalPos();
if (!_picker->isHidden()) {
if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) {
@ -1640,7 +1650,10 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
_picker->hide();
}
if (v::is_null(_selected) || _selected != pressed) {
if (_previewShown) {
_previewShown = false;
return;
} else if (v::is_null(_selected) || _selected != pressed) {
return;
}
@ -1659,7 +1672,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
return;
}
selectEmoji(lookupChosen(emoji, over));
} else if (const auto custom = lookupCustomEmoji(index, section)) {
} else if (const auto custom = lookupCustomEmoji(over)) {
selectCustom(lookupChosen(custom, over));
}
} else if (const auto set = std::get_if<OverSet>(&pressed)) {
@ -2492,8 +2505,10 @@ bool EmojiListWidget::eventHook(QEvent *e) {
}
void EmojiListWidget::updateSelected() {
if ((!v::is_null(_pressed) || !v::is_null(_pickerSelected)) && !_previewShown) {
return;
if (!v::is_null(_pressed) || !v::is_null(_pickerSelected)) {
if (!_previewShown) {
return;
}
}
auto newSelected = OverState{ v::null };
@ -2542,7 +2557,7 @@ void EmojiListWidget::setSelected(OverState newSelected) {
const auto hasSelection = !v::is_null(_selected);
if (hasSelection && Core::App().settings().suggestEmoji()) {
Ui::Tooltip::Show(350, this);
Ui::Tooltip::Show(1000, this);
}
setCursor(hasSelection ? style::cur_pointer : style::cur_default);
@ -2552,34 +2567,16 @@ void EmojiListWidget::setSelected(OverState newSelected) {
} else {
_picker->showAnimated();
}
}
if (_previewShown && _pressed != _selected) {
} else if (_previewShown && _pressed != _selected) {
if (const auto over = std::get_if<OverEmoji>(&_selected)) {
_pressed = _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);
if (const auto custom = lookupCustomEmoji(over)) {
_pressed = _selected;
_show->showMediaPreview(custom->stickerSetOrigin(), custom);
}
}
}
}
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) {
if (auto button = std::get_if<OverButton>(&_pressed)) {
Assert(hasColorButton(button->section)

View file

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

View file

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

View file

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

View file

@ -567,6 +567,7 @@ bool ResolveUsernameOrPhone(
.phone = phone,
.messageId = post,
.storyId = storyId,
.text = params.value(u"text"_q),
.repliesInfo = commentId
? Window::RepliesByLinkInfo{
Window::CommentId{ commentId }
@ -901,6 +902,34 @@ bool ShowEditPersonalChannel(
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(
not_null<Window::SessionController*> controller,
not_null<const Data::CloudTheme*> theme) {
@ -1311,6 +1340,14 @@ const std::vector<LocalUrlHandler> &InternalUrlHandlers() {
u"^edit_personal_channel$"_q,
ShowEditPersonalChannel,
},
{
u"^collectible_phone/([\\+0-9\\-\\s]+)@([0-9]+)$"_q,
ShowCollectiblePhone,
},
{
u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
ShowCollectibleUsername,
},
};
return Result;
}

View file

@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QSessionManager>
#include <QtGui/QScreen>
#include <QtGui/qpa/qplatformscreen.h>
#include <ksandbox.h>
namespace Core {
namespace {
@ -518,10 +517,8 @@ void Sandbox::refreshGlobalProxy() {
|| proxy.type == MTP::ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
} else if ((!Core::IsAppLaunched()
|| Core::App().settings().proxy().isSystem())
// this works stable only in sandboxed environment where it works through portal
&& (!Platform::IsLinux() || KSandbox::isInside() || cDebugMode())) {
} else if (!Core::IsAppLaunched()
|| Core::App().settings().proxy().isSystem()) {
QNetworkProxyFactory::setUseSystemConfiguration(true);
} else {
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/data_session.h"
#include "data/data_sponsored_messages.h"
#include "iv/iv_instance.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/basic_click_handlers.h"
#include "ui/emoji_config.h"
@ -241,6 +242,13 @@ bool UiIntegration::handleUrlClick(
} else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
Core::App().openInternalUrl(local, context);
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)) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1123,13 +1123,14 @@ rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent() {
state->thumbnail = Images::Prepare(embed->original(), 0, {
.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();
};
const auto resolveThumbnail = [=] {

View file

@ -940,7 +940,7 @@ const QString &PeerData::shortName() const {
return _name;
}
QString PeerData::userName() const {
QString PeerData::username() const {
if (const auto user = asUser()) {
return user->username();
} else if (const auto channel = asChannel()) {
@ -949,6 +949,34 @@ QString PeerData::userName() const {
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) {
index %= Ui::kColorIndexCount;
if (_colorIndexCloud && _colorIndex == index) {

View file

@ -279,7 +279,11 @@ public:
[[nodiscard]] const QString &name() const;
[[nodiscard]] const QString &shortName() 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 {
return _nameWords;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,6 +27,7 @@ public:
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector<QString> &usernames() const;
[[nodiscard]] bool isEditable(const QString &username) const;
private:
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);
p.setFont(st::dialogsTextFont);
QString username = peer->userName();
QString username = peer->username();
if (!context.active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) {
auto first = '@' + username.mid(0, _peerSearchQuery.size());
auto second = username.mid(_peerSearchQuery.size());
@ -4021,7 +4021,7 @@ void InnerWidget::setupShortcuts() {
const auto history = thread->owningHistory();
const auto isArchived = history->folder()
&& (history->folder()->id() == Data::Folder::kId);
Window::ToggleHistoryArchived(
_controller->uiShow(),
history,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -62,11 +62,19 @@ namespace HistoryView {
ScheduledMemento::ScheduledMemento(not_null<History*> history)
: _history(history)
, _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)
: _history(forumTopic->owningHistory())
, _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(
@ -1162,9 +1170,7 @@ Context ScheduledWidget::listContext() {
}
bool ScheduledWidget::listScrollTo(int top, bool syntetic) {
top = (top == ScrollMax && syntetic)
? 0
: std::clamp(top, 0, _scroll->scrollTopMax());
top = std::clamp(top, 0, _scroll->scrollTopMax());
if (_scroll->scrollTop() == top) {
updateInnerVisibleArea();
return false;

View file

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

View file

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

View file

@ -77,7 +77,7 @@ void ShowMenu(not_null<Ui::GenericBox*> box, const QString &text) {
[[nodiscard]] ClickHandlerPtr LearnMoreCurrencyLink(
not_null<Window::SessionController*> controller,
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;
const auto resolver = box->lifetime().make_state<Resolver>(
@ -217,7 +217,18 @@ void AddRecipient(not_null<Ui::GenericBox*> box, const TextWithEntities &t) {
}
#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 QColor &c) {
const auto s = Size(label.style.font->ascent);
@ -234,17 +245,6 @@ void AddRecipient(not_null<Ui::GenericBox*> box, const TextWithEntities &t) {
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(
QWidget *parent,
not_null<Controller*> controller,
@ -372,6 +372,7 @@ void InnerWidget::fill() {
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg);
p.drawEllipse(rect);

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h"
#include "apiwrap.h"
#include "info/profile/info_profile_phone_menu.h"
#include "info/profile/info_profile_badge.h"
#include "core/application.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::Usernames)
) | rpl::map([=] {
return peer->userName();
return peer->username();
});
}
@ -136,14 +137,19 @@ rpl::producer<TextWithEntities> PhoneOrHiddenValue(not_null<UserData*> user) {
PlainUsernameValue(user),
PlainAboutValue(user),
tr::lng_info_mobile_hidden()
) | rpl::map([](
) | rpl::map([user](
const TextWithEntities &phone,
const QString &username,
const QString &about,
const QString &hidden) {
return (phone.text.isEmpty() && username.isEmpty() && about.isEmpty())
? Ui::Text::WithEntities(hidden)
: phone;
if (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) {
return Ui::Text::WithEntities(hidden);
} 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();
}
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(
not_null<PeerData*> peer) {
const auto map = [=](const std::vector<QString> &usernames) {
return ranges::views::all(
usernames
) | ranges::views::transform([&](const QString &u) {
return Ui::Text::Link(
u,
peer->session().createInternalLinkFull(u));
return Ui::Text::Link(u, UsernameUrl(peer, u));
}) | ranges::to_vector;
};
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
? PlainPrimaryUsernameValue(peer)
: PlainUsernameValue(peer) | rpl::type_erased()
) | rpl::map([=](QString &&username) {
return username.isEmpty()
? QString()
: peer->session().createInternalLinkFull(username);
return LinkWithUrl{
.text = (username.isEmpty()
? 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);
[[nodiscard]] rpl::producer<std::vector<TextWithEntities>> UsernamesValue(
not_null<PeerData*> peer);
[[nodiscard]] QString UsernameUrl(
not_null<PeerData*> peer,
const QString &username);
[[nodiscard]] TextWithEntities AboutWithEntities(
not_null<PeerData*> peer,
const QString &value);
[[nodiscard]] rpl::producer<TextWithEntities> AboutValue(
not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<QString> LinkValue(
struct LinkWithUrl {
QString text;
QString url;
};
[[nodiscard]] rpl::producer<LinkWithUrl> LinkValue(
not_null<PeerData*> peer,
bool primary = false);
[[nodiscard]] rpl::producer<const ChannelLocation*> LocationValue(
not_null<ChannelData*> channel);
[[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_media.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "main/main_session.h"
#include "main/main_domain.h"
#include "storage/storage_domain.h"
#include "info/profile/info_profile_values.h"
#include "iv/iv_instance.h"
#include "ui/boxes/confirm_box.h"
#include "ui/chat/attach/attach_bot_webview.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) {
if (!_context
|| _context->fromSwitch

View file

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

View file

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

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/file_utilities.h"
#include "core/shortcuts.h"
#include "core/click_handler_types.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_cloud_file.h"
@ -63,7 +64,7 @@ class Shown final : public base::has_weak_ptr {
public:
Shown(
not_null<Delegate*> delegate,
std::shared_ptr<Main::SessionShow> show,
not_null<Main::Session*> session,
not_null<Data*> data,
QString hash);
@ -169,12 +170,11 @@ private:
Shown::Shown(
not_null<Delegate*> delegate,
std::shared_ptr<Main::SessionShow> show,
not_null<Main::Session*> session,
not_null<Data*> data,
QString hash)
: _delegate(delegate)
, _session(&show->session())
, _show(show) {
, _session(session) {
prepare(data, hash);
}
@ -818,7 +818,13 @@ void Instance::show(
std::shared_ptr<Main::SessionShow> show,
not_null<Data*> data,
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([&] {
if (data->partial()) {
requestFull(session, data->id());
@ -828,10 +834,13 @@ void Instance::show(
_shown->moveTo(data, hash);
return;
}
_shown = std::make_unique<Shown>(_delegate, show, data, hash);
_shown = std::make_unique<Shown>(_delegate, session, data, hash);
_shownSession = session;
_shown->events() | rpl::start_with_next([=](Controller::Event event) {
using Type = Controller::Event::Type;
const auto lower = event.url.toLower();
const auto urlChecked = lower.startsWith("http://")
|| lower.startsWith("https://");
switch (event.type) {
case Type::Close:
_shown = nullptr;
@ -846,7 +855,9 @@ void Instance::show(
processJoinChannel(event.context);
break;
case Type::OpenLinkExternal:
File::OpenUrl(event.url);
if (urlChecked) {
File::OpenUrl(event.url);
}
closeAll();
break;
case Type::OpenMedia:
@ -885,6 +896,9 @@ void Instance::show(
break;
case Type::OpenPage:
case Type::OpenLink:
if (!urlChecked) {
break;
}
_shownSession->api().request(MTPmessages_GetWebPage(
MTP_string(event.url),
MTP_int(0)
@ -896,7 +910,7 @@ void Instance::show(
if (page && page->iv) {
const auto parts = event.url.split('#');
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 {
UrlClickHandler::Open(event.url);
}
@ -921,20 +935,104 @@ void Instance::show(
}
}, _shown->lifetime());
if (!_tracking.contains(session)) {
_tracking.emplace(session);
session->lifetime().add([=] {
_tracking.remove(session);
_joining.remove(session);
_fullRequested.remove(session);
if (_shownSession == session) {
_shownSession = nullptr;
}
if (_shown && _shown->showingFrom(session)) {
_shown = nullptr;
}
});
trackSession(session);
}
void Instance::trackSession(not_null<Main::Session*> session) {
if (!_tracking.emplace(session).second) {
return;
}
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(
@ -1028,4 +1126,17 @@ void Instance::closeAll() {
_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

View file

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

View file

@ -32,11 +32,13 @@ Domain::Domain(const QString &dataName)
: _dataName(dataName)
, _local(std::make_unique<Storage::Domain>(this, dataName)) {
_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.
Core::App().startSettingsAndBackground();
Core::App().notifications().createManager();
crl::on_main(this, [=] {
Core::App().notifications().createManager();
});
}, _lifetime);
_active.changes(
@ -51,7 +53,7 @@ Domain::Domain(const QString &dataName)
: rpl::never<Data::PeerUpdate>();
}) | rpl::flatten_latest(
) | rpl::start_with_next([](const Data::PeerUpdate &update) {
CrashReports::SetAnnotation("Username", update.peer->userName());
CrashReports::SetAnnotation("Username", update.peer->username());
}, _lifetime);
}

View file

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

View file

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

View file

@ -933,6 +933,13 @@ void Controller::show(
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) {
const auto id = story ? story->fullId() : FullStoryId();
const auto session = story ? &story->session() : nullptr;

View file

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

View file

@ -431,7 +431,7 @@ void ReplyArea::chooseAttach(
}
const auto filter = (overrideSendImagesAsPhotos == true)
? FileDialog::ImagesOrAllFilter()
? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter();
const auto weak = make_weak(&_shownPeerGuard);
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 });
if (of) {
using namespace Data;
_controller->show(*of, { StoriesContextSingle() });
_controller->jumpTo(*of, { StoriesContextSingle() });
} else {
_controller->uiShow()->show(PrepareShortInfoBox(peer));
}

View file

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

View file

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

View file

@ -40,10 +40,6 @@ constexpr ShiftedDcId groupCallStreamDcId(DcId dcId) {
return ShiftDcId(dcId, kGroupCallStreamDcShift);
}
constexpr auto kUploadSessionsCount = 2;
constexpr auto kUploadSessionsCountMax = 8;
namespace details {
constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
@ -94,7 +90,6 @@ inline DcId getTemporaryIdFromRealDcId(ShiftedDcId shiftedDcId) {
namespace details {
constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
static_assert(kUploadSessionsCountMax < kMaxMediaDcCount, "Too large MTPUploadSessionsCount!");
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
// uploading always to the main dc so BareDcId(result) == 0
inline ShiftedDcId uploadDcId(int index) {
Expects(index >= 0 && index < kUploadSessionsCountMax);
Expects(index >= 0 && index < kMaxMediaDcCount);
return details::uploadDcId(0, index);
};
constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) {
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) {

View file

@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
#include "calls/calls_instance.h"
#include "main/main_account.h" // Account::configUpdated.
#include "apiwrap.h"
#include "core/application.h"
#include "core/core_settings.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.
constexpr auto kAckSendWaiting = 10 * crl::time(1000);
constexpr auto kCutContainerOnSize = 16 * 1024;
auto SyncTimeRequestDuration = kFastRequestDuration;
using namespace details;
@ -696,7 +698,8 @@ void SessionPrivate::tryToSend() {
initSize = initSizeInInts * sizeof(mtpPrime);
}
bool needAnyResponse = false;
auto needAnyResponse = false;
auto someSkipped = false;
SerializedRequest toSendRequest;
{
QWriteLocker locker1(_sessionData->toSendMutex());
@ -711,15 +714,33 @@ void SessionPrivate::tryToSend() {
locker1.unlock();
}
uint32 toSendCount = toSend.size();
if (pingRequest) ++toSendCount;
if (ackRequest) ++toSendCount;
if (resendRequest) ++toSendCount;
if (stateRequest) ++toSendCount;
if (httpWaitRequest) ++toSendCount;
if (bindDcKeyRequest) ++toSendCount;
auto totalSending = int(toSend.size());
auto sendingFrom = begin(toSend);
auto sendingTill = end(toSend);
auto combinedLength = 0;
for (auto i = sendingFrom; i != sendingTill; ++i) {
combinedLength += i->second->size();
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
}
@ -735,11 +756,11 @@ void SessionPrivate::tryToSend() {
? httpWaitRequest
: bindDcKeyRequest
? bindDcKeyRequest
: toSend.begin()->second;
if (toSendCount == 1 && !first->forceSendInContainer) {
: sendingRange.begin()->second;
if (totalSending == 1 && !first->forceSendInContainer) {
toSendRequest = first;
if (sendAll) {
toSend.clear();
toSend.erase(sendingFrom, sendingTill);
locker1.unlock();
}
@ -808,7 +829,7 @@ void SessionPrivate::tryToSend() {
if (stateRequest) containerSize += stateRequest.messageSize();
if (httpWaitRequest) containerSize += httpWaitRequest.messageSize();
if (bindDcKeyRequest) containerSize += bindDcKeyRequest.messageSize();
for (const auto &[requestId, request] : toSend) {
for (const auto &[requestId, request] : sendingRange) {
containerSize += request.messageSize();
if (needsLayer && request->needsLayer) {
containerSize += initSizeInInts;
@ -825,9 +846,9 @@ void SessionPrivate::tryToSend() {
// prepare container + each in invoke after
toSendRequest = SerializedRequest::Prepare(
containerSize,
containerSize + 3 * toSend.size());
containerSize + 3 * sendingCount);
toSendRequest->push_back(mtpc_msg_container);
toSendRequest->push_back(toSendCount);
toSendRequest->push_back(totalSending);
// check for a valid container
auto bigMsgId = base::unixtime::mtproto_msg_id();
@ -839,7 +860,7 @@ void SessionPrivate::tryToSend() {
// prepare sent container
auto sentIdsWrap = SentContainer();
sentIdsWrap.sent = crl::now();
sentIdsWrap.messages.reserve(toSendCount);
sentIdsWrap.messages.reserve(totalSending);
if (bindDcKeyRequest) {
_bindMsgId = placeToContainer(
@ -848,6 +869,7 @@ void SessionPrivate::tryToSend() {
false,
bindDcKeyRequest);
_bindMessageSent = crl::now();
sentIdsWrap.messages.push_back(_bindMsgId);
needAnyResponse = true;
}
if (pingRequest) {
@ -856,10 +878,11 @@ void SessionPrivate::tryToSend() {
bigMsgId,
forceNewMsgId,
pingRequest);
sentIdsWrap.messages.push_back(_pingMsgId);
needAnyResponse = true;
}
for (auto &[requestId, request] : toSend) {
for (auto &[requestId, request] : sendingRange) {
const auto msgId = prepareToSend(
request,
bigMsgId,
@ -904,7 +927,7 @@ void SessionPrivate::tryToSend() {
memcpy(toSendRequest->data() + from, request->constData() + 4, len * sizeof(mtpPrime));
}
}
toSend.clear();
toSend.erase(sendingFrom, sendingTill);
if (stateRequest) {
const auto msgId = placeToContainer(
@ -951,6 +974,11 @@ void SessionPrivate::tryToSend() {
}
}
sendSecureRequest(std::move(toSendRequest), needAnyResponse);
if (someSkipped) {
InvokeQueued(this, [=] {
tryToSend();
});
}
}
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));
}
}
if (isUploadDcId(_shiftedDcId)) {
remain *= kUploadSessionsCount;
}
_waitForReceivedTimer.callOnce(remain);
}
if (!_firstSentAt) {
@ -1577,13 +1602,22 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
correctUnixtimeWithBadLocal(info.serverTime);
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));
return HandleResult::ResetSession;
}
} else { // fatal (except 48, but it must not get here)
const auto badMsgId = mtpMsgId(data.vbad_msg_id().v);
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: "
"fatal bad message notification received, "
"msgId %1, error_code %2, requestId: %3"
@ -1628,7 +1662,9 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
}
_sessionSalt = data.vnew_server_salt().v;
correctUnixtimeWithBadLocal(info.serverTime);
// Don't force time update here.
base::unixtime::update(info.serverTime);
if (_bindMsgId) {
LOG(("Message Info: bad_server_salt received while binding temp key, restarting."));
@ -2056,7 +2092,7 @@ void SessionPrivate::correctUnixtimeByFastRequest(
locker.unlock();
SyncTimeRequestDuration = duration;
base::unixtime::update(serverTime, true);
base::unixtime::update(serverTime);
return;
}
}

View file

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

View file

@ -37,6 +37,7 @@ namespace Notifications {
namespace {
using namespace gi::repository;
namespace GObject = gi::repository::GObject;
constexpr auto kService = "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)
std::optional<bool> IsDarkMode() {
const auto result = base::Platform::XDP::ReadSetting(
auto result = base::Platform::XDP::ReadSetting(
"org.freedesktop.appearance",
"color-scheme");

View file

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

View file

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

View file

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

View file

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

View file

@ -1428,7 +1428,7 @@ void ShortcutMessages::chooseAttach(
_choosingAttach = false;
const auto filter = (overrideSendImagesAsPhotos == true)
? FileDialog::ImagesOrAllFilter()
? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
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_chat.h"
#include "settings/settings_codes.h"
#include "ui/basic_click_handlers.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/cached_round_corners.h"
@ -104,7 +104,14 @@ object_ptr<Ui::RpWidget> CreateIntroSettings(
Ui::AddDivider(result);
Ui::AddSkip(result);
SetupFaq(result, false);
AddButtonWithIcon(
result,
tr::lng_settings_faq(),
st::settingsButtonNoIcon
)->addClickHandler([] {
OpenFaq(nullptr);
});
return result;
}

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_main.h"
#include "core/application.h"
#include "core/click_handler_types.h"
#include "settings/settings_business.h"
#include "settings/settings_codes.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/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
#include "ui/wrap/vertical_layout.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/labels.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/buttons.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.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_cloud_themes.h"
#include "data/data_chat_filters.h"
#include "data/data_peer_values.h" // Data::AmPremiumValue
#include "lang/lang_cloud_manager.h"
#include "lang/lang_keys.h"
#include "lang/lang_instance.h"
#include "storage/localstorage.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 "window/window_controller.h"
#include "window/window_session_controller.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "base/call_delayed.h"
#include "base/unixtime.h"
#include "base/platform/base_platform_info.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
@ -229,7 +220,7 @@ void Cover::initViewers() {
}, lifetime());
_username->overrideLinkClickHandler([=] {
const auto username = _user->userName();
const auto username = _user->username();
if (username.isEmpty()) {
_controller->show(Box(UsernamesBox, _user));
} 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(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
Ui::AddDivider(container);
Ui::AddSkip(container);
SetupFaq(container);
AddButtonWithIcon(
container,
tr::lng_settings_faq(),
st::settingsButton,
{ &st::menuIconFaq }
)->addClickHandler([=] {
OpenFaq(controller);
});
AddButtonWithIcon(
container,
@ -688,7 +673,10 @@ void SetupHelp(
auto box = Ui::MakeConfirmBox({
.text = tr::lng_settings_ask_sure(),
.confirmed = sure,
.cancelled = OpenFaq,
.cancelled = [=](Fn<void()> close) {
OpenFaq(controller);
close();
},
.confirmText = tr::lng_settings_ask_ok(),
.cancelText = tr::lng_settings_faq_button(),
.strictCancel = true,
@ -767,4 +755,12 @@ void Main::setupContent(not_null<Window::SessionController*> controller) {
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

View file

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

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