Compare commits

...

17 Commits

Author SHA1 Message Date
John Preston
9c57e31256 Version 3.6: Fix build with GCC. 2022-03-11 17:09:18 +04:00
John Preston
0c03070109 Fix build for macOS. 2022-03-11 16:39:18 +04:00
John Preston
b40f6577a6 Version 3.6.
- Active and recently finished downloads pop up
in bar in the bottom left corner, like they do in browsers.
- View recently downloaded files in Settings > Advanced > Downloads.
- Get an alert before closing the app if you have unfinished downloads.
- Share a direct t.me link to your phone number
that instantly opens a chat with you.
- Use the full number in international format,
like t.me/+123456789
- Manage Live Streams in your channels using external software
like OBS Studio or XSplit Broadcaster.
- Choose "Stream With..." when staring a video chat or live stream,
then copy your Stream Key and paste it into your streaming software.
2022-03-11 15:54:55 +04:00
John Preston
c7398c631d Attempt to fix building with GCC. 2022-03-11 15:54:55 +04:00
John Preston
95f5f28906 Add search to the Downloads section. 2022-03-11 15:14:07 +04:00
John Preston
5be72e8ce2 Just compute scheduled ids instead of a lookup. 2022-03-11 11:25:30 +04:00
John Preston
1833fac094 Make scheduled message ids determenistic.
Fixes #6686.
2022-03-11 11:05:02 +04:00
John Preston
1bc438ed01 Support "Delete all files" menu in Downloads section. 2022-03-11 09:56:29 +04:00
John Preston
32d09f189b Use rpl::empty instead of rpl::empty_value(). 2022-03-11 09:56:29 +04:00
Ilya Fedin
437fe4ba82 Set path to mozjpeg in snap for tdesktop 2022-03-10 17:11:41 +04:00
John Preston
e7b437980e Update submodules. 2022-03-10 14:20:01 +04:00
John Preston
a46329f796 Update "trade" folder icon. 2022-03-09 17:55:20 +04:00
John Preston
6805259f74 Don't recompress some JPEGs when sending as photos.
If JPEG is saved in progressive mode and has bpp <= 4
and max(width, height) <= 1280 then we send original bytes.
2022-03-09 17:37:51 +04:00
John Preston
e84ebc2a5c Fix download bar appearing after account switch. 2022-03-09 16:02:27 +04:00
John Preston
602e7a7164 Warn on quit if downloading files. 2022-03-09 15:36:14 +04:00
John Preston
6dd720b76e Don't show downloads bar while message is visible. 2022-03-09 14:52:44 +04:00
John Preston
f1064e2d2f Fix crash in downloads bar. 2022-03-09 08:53:15 +04:00
75 changed files with 754 additions and 251 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1845,6 +1845,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_downloads_view_in_chat" = "View in chat";
"lng_downloads_view_in_section" = "View in downloads";
"lng_downloads_delete_sure_one" = "Do you want to delete this file?";
"lng_downloads_delete_sure_all" = "Do you want to delete all files?";
"lng_downloads_delete_sure#one" = "Do you want to delete {count} file?";
"lng_downloads_delete_sure#other" = "Do you want to delete {count} files?";
"lng_downloads_delete_in_cloud_one" = "It will be deleted from your disk, but will remain accessible in the cloud.";
@@ -1953,6 +1954,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_box_leave" = "Leave";
"lng_upload_sure_stop" = "Are you sure you want to stop uploading your files?\n\nIf you do, you'll need to start over.";
"lng_download_sure_stop" = "Are you sure you want to stop downloading your files?\n\nIf you do, you'll need to start over.";
"lng_upload_show_file" = "Show file";
"lng_about_version" = "version {version}";

View File

@@ -10,7 +10,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="3.5.6.0" />
Version="3.6.0.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 3,5,6,0
PRODUCTVERSION 3,5,6,0
FILEVERSION 3,6,0,0
PRODUCTVERSION 3,6,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "3.5.6.0"
VALUE "FileVersion", "3.6.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.5.6.0"
VALUE "ProductVersion", "3.6.0.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,5,6,0
PRODUCTVERSION 3,5,6,0
FILEVERSION 3,6,0,0
PRODUCTVERSION 3,6,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram FZ-LLC"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "3.5.6.0"
VALUE "FileVersion", "3.6.0.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2022"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "3.5.6.0"
VALUE "ProductVersion", "3.6.0.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -309,7 +309,7 @@ void EditCaptionBox::setupShadows() {
void EditCaptionBox::setupControls() {
auto hintLabelToggleOn = _previewRebuilds.events_starting_with(
rpl::empty_value()
{}
) | rpl::map([=] {
return _controller->session().settings().photoEditorHintShown()
? _isPhoto
@@ -334,9 +334,7 @@ void EditCaptionBox::setupControls() {
st::defaultBoxCheckbox),
st::editMediaCheckboxMargins)
)->toggleOn(
_previewRebuilds.events_starting_with(
rpl::empty_value()
) | rpl::map([=] {
_previewRebuilds.events_starting_with({}) | rpl::map([=] {
return _isPhoto
&& CanBeCompressed(_albumType)
&& !_preparedList.files.empty();

View File

@@ -269,9 +269,7 @@ void EditPrivacyBox::setupContent() {
};
const auto addExceptionLink = [=](Exception exception) {
const auto update = Ui::CreateChild<rpl::event_stream<>>(content);
auto label = update->events_starting_with(
rpl::empty_value()
) | rpl::map([=] {
auto label = update->events_starting_with({}) | rpl::map([=] {
return Settings::ExceptionUsersCount(exceptions(exception));
}) | rpl::map([](int count) {
return count

View File

@@ -237,7 +237,7 @@ object_ptr<Ui::RpWidget> CreateFingerprintAndSignalBars(
QImage::Format_ARGB32_Premultiplied);
background->setDevicePixelRatio(cRetinaFactor());
rpl::merge(
rpl::single(rpl::empty_value()),
rpl::single(rpl::empty),
Ui::Emoji::Updated(),
style::PaletteChanged()
) | rpl::start_with_next([=] {

View File

@@ -165,9 +165,9 @@ void ScheduleGroupCallBox(
using namespace rpl::mappers;
*duration = rpl::combine(
rpl::single(
rpl::empty_value()
) | rpl::then(base::timer_each(kLabelRefreshInterval)),
rpl::single(rpl::empty) | rpl::then(
base::timer_each(kLabelRefreshInterval)
),
std::move(descriptor.values) | rpl::filter(_1 != 0),
_2
) | rpl::map([](TimeId date) {

View File

@@ -728,7 +728,7 @@ void Panel::setupScheduledLabels(rpl::producer<TimeId> date) {
date
) | rpl::map([=](TimeId date) {
_countdownData = std::make_shared<Ui::GroupCallScheduledLeft>(date);
return rpl::empty_value();
return rpl::empty;
}) | rpl::start_spawning(lifetime());
_countdown = Ui::CreateGradientLabel(widget(), rpl::duplicate(
@@ -1742,9 +1742,7 @@ void Panel::setupControlsBackgroundNarrow() {
const auto full = lifetime.make_state<QImage>(
QSize(1, height * factor),
QImage::Format_ARGB32_Premultiplied);
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
style::PaletteChanged()
) | rpl::start_with_next([=] {
full->fill(Qt::transparent);

View File

@@ -364,9 +364,7 @@ void Row::setupContent(const Set &set) {
) | rpl::map([=](Loader *loader) {
return (loader && loader->id() == _id)
? loader->state()
: rpl::single(
rpl::empty_value()
) | rpl::then(
: rpl::single(rpl::empty) | rpl::then(
Updated()
) | rpl::map([=] {
return ComputeState(_id);

View File

@@ -661,6 +661,10 @@ void Application::logoutWithChecks(Main::Account *account) {
_exportManager->stopWithConfirmation(retry);
} else if (account->session().uploadsInProgress()) {
account->session().uploadsStopWithConfirmation(retry);
} else if (_downloadManager->loadingInProgress(&account->session())) {
_downloadManager->loadingStopWithConfirmation(
retry,
&account->session());
} else {
logout(account);
}
@@ -801,8 +805,18 @@ bool Application::uploadPreventsQuit() {
return false;
}
bool Application::downloadPreventsQuit() {
if (_downloadManager->loadingInProgress()) {
_downloadManager->loadingStopWithConfirmation([=] { Quit(); });
return true;
}
return false;
}
bool Application::preventsQuit(QuitReason reason) {
if (exportPreventsQuit() || uploadPreventsQuit()) {
if (exportPreventsQuit()
|| uploadPreventsQuit()
|| downloadPreventsQuit()) {
return true;
} else if (const auto window = activeWindow()) {
if (window->widget()->isActive()) {

View File

@@ -255,6 +255,7 @@ public:
not_null<Main::Account*> account,
const TextWithEntities &explanation);
[[nodiscard]] bool uploadPreventsQuit();
[[nodiscard]] bool downloadPreventsQuit();
void checkLocalTime();
void lockByPasscode();
void unlockPasscode();

View File

@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs;
constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs;
constexpr auto AppName = "Telegram Desktop"_cs;
constexpr auto AppFile = "Telegram"_cs;
constexpr auto AppVersion = 3005006;
constexpr auto AppVersionStr = "3.5.6";
constexpr auto AppBetaVersion = true;
constexpr auto AppVersion = 3006000;
constexpr auto AppVersionStr = "3.6";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;

View File

@@ -27,8 +27,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/mime_type.h"
#include "ui/controls/download_bar.h"
#include "ui/text/format_song_document_name.h"
#include "ui/layers/generic_box.h"
#include "storage/serialize_common.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "apiwrap.h"
#include "styles/style_layers.h"
namespace Data {
namespace {
@@ -59,8 +63,19 @@ constexpr auto ByDocument = [](const auto &entry) {
return 0;
}
struct DocumentDescriptor {
uint64 sessionUniqueId = 0;
DocumentId documentId = 0;
FullMsgId itemId;
};
} // namespace
struct DownloadManager::DeleteFilesDescriptor {
base::flat_set<not_null<Main::Session*>> sessions;
base::flat_map<QString, DocumentDescriptor> files;
};
DownloadManager::DownloadManager()
: _clearLoadingTimer([=] { clearLoading(); }) {
}
@@ -105,6 +120,26 @@ void DownloadManager::trackSession(not_null<Main::Session*> session) {
}, data.lifetime);
}
void DownloadManager::itemVisibilitiesUpdated(
not_null<Main::Session*> session) {
const auto i = _sessions.find(session);
if (i == end(_sessions)
|| i->second.downloading.empty()
|| !i->second.downloading.front().hiddenByView) {
return;
}
for (const auto &id : i->second.downloading) {
if (!id.done
&& !session->data().queryItemVisibility(id.object.item)) {
for (auto &id : i->second.downloading) {
id.hiddenByView = false;
}
_loadingListChanges.fire({});
return;
}
}
}
int64 DownloadManager::computeNextStartDate() {
const auto now = base::unixtime::now();
if (_lastStartedBase != now) {
@@ -140,11 +175,15 @@ void DownloadManager::addLoading(DownloadObject object) {
return;
}
const auto shownExists = !data.downloading.empty()
&& !data.downloading.front().hiddenByView;
data.downloading.push_back({
.object = object,
.started = computeNextStartDate(),
.path = path,
.total = size,
.hiddenByView = (!shownExists
&& item->history()->owner().queryItemVisibility(item)),
});
_loading.emplace(item);
_loadingDocuments.emplace(object.document);
@@ -281,13 +320,7 @@ void DownloadManager::clearIfFinished() {
}
void DownloadManager::deleteFiles(const std::vector<GlobalMsgId> &ids) {
struct DocumentDescriptor {
uint64 sessionUniqueId = 0;
DocumentId documentId = 0;
FullMsgId itemId;
};
auto sessions = base::flat_set<not_null<Main::Session*>>();
auto files = base::flat_map<QString, DocumentDescriptor>();
auto descriptor = DeleteFilesDescriptor();
for (const auto &id : ids) {
if (const auto item = MessageByGlobalId(id)) {
const auto session = &item->history()->session();
@@ -307,7 +340,7 @@ void DownloadManager::deleteFiles(const std::vector<GlobalMsgId> &ids) {
const auto k = ranges::find(data.downloaded, item, ByItem);
if (k != end(data.downloaded)) {
const auto document = k->object->document;
files.emplace(k->path, DocumentDescriptor{
descriptor.files.emplace(k->path, DocumentDescriptor{
.sessionUniqueId = id.sessionUniqueId,
.documentId = document ? document->id : DocumentId(),
.itemId = id.itemId,
@@ -320,14 +353,54 @@ void DownloadManager::deleteFiles(const std::vector<GlobalMsgId> &ids) {
data.downloaded.erase(k);
_loadedRemoved.fire_copy(item);
sessions.emplace(session);
descriptor.sessions.emplace(session);
}
}
}
for (const auto &session : sessions) {
finishFilesDelete(std::move(descriptor));
}
void DownloadManager::deleteAll() {
auto descriptor = DeleteFilesDescriptor();
for (auto &[session, data] : _sessions) {
if (!data.downloaded.empty()) {
descriptor.sessions.emplace(session);
} else if (data.downloading.empty()) {
continue;
}
const auto sessionUniqueId = session->uniqueId();
while (!data.downloading.empty()) {
cancel(data, data.downloading.end() - 1);
}
for (auto &id : base::take(data.downloaded)) {
const auto object = id.object.get();
const auto document = object ? object->document : nullptr;
descriptor.files.emplace(id.path, DocumentDescriptor{
.sessionUniqueId = sessionUniqueId,
.documentId = document ? document->id : DocumentId(),
.itemId = id.itemId,
});
if (document) {
_generatedDocuments.remove(document);
}
if (const auto item = object ? object->item.get() : nullptr) {
_loaded.remove(item);
_generated.remove(item);
_loadedRemoved.fire_copy(item);
}
}
}
for (const auto &session : descriptor.sessions) {
writePostponed(session);
}
crl::async([files = std::move(files)] {
finishFilesDelete(std::move(descriptor));
}
void DownloadManager::finishFilesDelete(DeleteFilesDescriptor &&descriptor) {
for (const auto &session : descriptor.sessions) {
writePostponed(session);
}
crl::async([files = std::move(descriptor.files)]{
for (const auto &file : files) {
QFile(file.first).remove();
crl::on_main([descriptor = file.second] {
@@ -347,6 +420,19 @@ void DownloadManager::deleteFiles(const std::vector<GlobalMsgId> &ids) {
});
}
bool DownloadManager::loadedHasNonCloudFile() const {
for (const auto &[session, data] : _sessions) {
for (const auto &id : data.downloaded) {
if (const auto object = id.object.get()) {
if (!object->item->isHistoryEntry()) {
return true;
}
}
}
}
return false;
}
auto DownloadManager::loadingList() const
-> ranges::any_view<const DownloadingId*, ranges::category::input> {
return ranges::views::all(
@@ -373,6 +459,93 @@ auto DownloadManager::loadingProgressValue() const
return _loadingProgress.value();
}
bool DownloadManager::loadingInProgress(Main::Session *onlyInSession) const {
return lookupLoadingItem(onlyInSession) != nullptr;
}
HistoryItem *DownloadManager::lookupLoadingItem(
Main::Session *onlyInSession) const {
constexpr auto find = [](const SessionData &data) {
constexpr auto proj = &DownloadingId::done;
const auto i = ranges::find(data.downloading, false, proj);
return (i != end(data.downloading)) ? i->object.item.get() : nullptr;
};
if (onlyInSession) {
const auto i = _sessions.find(onlyInSession);
return (i != end(_sessions)) ? find(i->second) : nullptr;
} else {
for (const auto &[session, data] : _sessions) {
if (const auto result = find(data)) {
return result;
}
}
}
return nullptr;
}
void DownloadManager::loadingStopWithConfirmation(
Fn<void()> callback,
Main::Session *onlyInSession) {
const auto window = Core::App().primaryWindow();
const auto item = lookupLoadingItem(onlyInSession);
if (!window || !item) {
return;
}
const auto weak = base::make_weak(&item->history()->session());
const auto id = item->fullId();
auto box = Box([=](not_null<Ui::GenericBox*> box) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_download_sure_stop(),
st::boxLabel),
st::boxPadding + QMargins(0, 0, 0, st::boxPadding.bottom()));
box->setStyle(st::defaultBox);
box->addButton(tr::lng_selected_upload_stop(), [=] {
box->closeBox();
if (!onlyInSession || weak.get()) {
loadingStop(onlyInSession);
}
if (callback) {
callback();
}
}, st::attentionBoxButton);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->addLeftButton(tr::lng_upload_show_file(), [=] {
box->closeBox();
if (const auto strong = weak.get()) {
if (const auto item = strong->data().message(id)) {
if (const auto window = strong->tryResolveWindow()) {
window->showPeerHistoryAtItem(item);
}
}
}
});
});
window->show(std::move(box));
window->activate();
}
void DownloadManager::loadingStop(Main::Session *onlyInSession) {
const auto stopInSession = [&](SessionData &data) {
while (!data.downloading.empty()) {
cancel(data, data.downloading.end() - 1);
}
};
if (onlyInSession) {
const auto i = _sessions.find(onlyInSession);
if (i != end(_sessions)) {
stopInSession(i->second);
}
} else {
for (auto &[session, data] : _sessions) {
stopInSession(data);
}
}
}
void DownloadManager::clearLoading() {
Expects(_loading.empty());
@@ -401,9 +574,17 @@ auto DownloadManager::loadedList()
}) | ranges::views::join;
}
rpl::producer<> DownloadManager::loadedResolveDone() const {
using namespace rpl::mappers;
return _loadedResolveDone.value() | rpl::filter(_1) | rpl::to_empty;
}
void DownloadManager::resolve(
not_null<Main::Session*> session,
SessionData &data) {
const auto guard = gsl::finally([&] {
checkFullResolveDone();
});
if (data.resolveSentTotal >= data.resolveNeeded
|| data.resolveSentTotal >= kMaxResolvePerAttempt) {
return;
@@ -508,6 +689,19 @@ void DownloadManager::resolveRequestsFinished(
});
}
void DownloadManager::checkFullResolveDone() {
if (_loadedResolveDone.current()) {
return;
}
for (const auto &[session, data] : _sessions) {
if (data.resolveSentTotal < data.resolveNeeded
|| data.resolveSentRequests > 0) {
return;
}
}
_loadedResolveDone = true;
}
void DownloadManager::generateEntry(
not_null<Main::Session*> session,
DownloadedId &id) {
@@ -908,6 +1102,9 @@ rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent() {
auto content = Ui::DownloadBarContent();
auto single = (const Data::DownloadObject*) nullptr;
for (const auto id : manager.loadingList()) {
if (id->hiddenByView) {
break;
}
if (!single) {
single = &id->object;
}
@@ -927,7 +1124,9 @@ rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent() {
state->media = thumbnailed
? thumbnailed->createMediaView()
: nullptr;
state->media->thumbnailWanted(single->item->fullId());
if (const auto raw = state->media.get()) {
raw->thumbnailWanted(single->item->fullId());
}
state->thumbnail = QImage();
resolveThumbnail();
}

View File

@@ -71,6 +71,7 @@ struct DownloadingId {
QString path;
int ready = 0;
int total = 0;
bool hiddenByView = false;
bool done = false;
};
@@ -80,6 +81,7 @@ public:
~DownloadManager();
void trackSession(not_null<Main::Session*> session);
void itemVisibilitiesUpdated(not_null<Main::Session*> session);
[[nodiscard]] DownloadDate computeNextStartDate();
@@ -91,6 +93,8 @@ public:
void clearIfFinished();
void deleteFiles(const std::vector<GlobalMsgId> &ids);
void deleteAll();
[[nodiscard]] bool loadedHasNonCloudFile() const;
[[nodiscard]] auto loadingList() const
-> ranges::any_view<const DownloadingId*, ranges::category::input>;
@@ -99,14 +103,22 @@ public:
[[nodiscard]] auto loadingProgressValue() const
-> rpl::producer<DownloadProgress>;
[[nodiscard]] bool loadingInProgress(
Main::Session *onlyInSession = nullptr) const;
void loadingStopWithConfirmation(
Fn<void()> callback,
Main::Session *onlyInSession = nullptr);
[[nodiscard]] auto loadedList()
-> ranges::any_view<const DownloadedId*, ranges::category::input>;
[[nodiscard]] auto loadedAdded() const
-> rpl::producer<not_null<const DownloadedId*>>;
[[nodiscard]] auto loadedRemoved() const
-> rpl::producer<not_null<const HistoryItem*>>;
[[nodiscard]] rpl::producer<> loadedResolveDone() const;
private:
struct DeleteFilesDescriptor;
struct SessionData {
std::vector<DownloadedId> downloaded;
std::vector<DownloadingId> downloading;
@@ -144,6 +156,8 @@ private:
void resolveRequestsFinished(
not_null<Main::Session*> session,
SessionData &data);
void checkFullResolveDone();
[[nodiscard]] not_null<HistoryItem*> regenerateItem(
const DownloadObject &previous);
[[nodiscard]] not_null<HistoryItem*> generateFakeItem(
@@ -154,6 +168,11 @@ private:
PhotoData *photo);
void generateEntry(not_null<Main::Session*> session, DownloadedId &id);
[[nodiscard]] HistoryItem *lookupLoadingItem(
Main::Session *onlyInSession) const;
void loadingStop(Main::Session *onlyInSession);
void finishFilesDelete(DeleteFilesDescriptor &&descriptor);
void writePostponed(not_null<Main::Session*> session);
[[nodiscard]] Fn<std::optional<QByteArray>()> serializator(
not_null<Main::Session*> session) const;
@@ -176,6 +195,7 @@ private:
rpl::event_stream<not_null<const DownloadedId*>> _loadedAdded;
rpl::event_stream<not_null<const HistoryItem*>> _loadedRemoved;
rpl::variable<bool> _loadedResolveDone;
base::Timer _clearLoadingTimer;

View File

@@ -113,12 +113,9 @@ HistoryItemsList::const_iterator Groups::findPositionForItem(
const HistoryItemsList &group,
not_null<HistoryItem*> item) {
const auto last = end(group);
if (!item->isRegular()) {
return last;
}
const auto itemId = item->id;
for (auto result = begin(group); result != last; ++result) {
if ((*result)->isRegular() && (*result)->id > itemId) {
if ((*result)->id > itemId) {
return result;
}
}

View File

@@ -76,6 +76,7 @@ Q_DECLARE_METATYPE(MsgId);
constexpr auto StartClientMsgId = MsgId(0x01 - (1LL << 58));
constexpr auto EndClientMsgId = MsgId(-(1LL << 57));
constexpr auto ServerMaxMsgId = MsgId(1LL << 56);
constexpr auto ScheduledMsgIdsRange = (1LL << 32);
constexpr auto ShowAtUnreadMsgId = MsgId(0);
constexpr auto SpecialMsgIdShift = EndClientMsgId.bare;

View File

@@ -23,6 +23,19 @@ namespace {
constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
[[nodiscard]] MsgId RemoteToLocalMsgId(MsgId id) {
Expects(IsServerMsgId(id));
return ServerMaxMsgId + id + 1;
}
[[nodiscard]] MsgId LocalToRemoteMsgId(MsgId id) {
Expects(id > ServerMaxMsgId);
Expects(id < ServerMaxMsgId + ScheduledMsgIdsRange);
return (id - ServerMaxMsgId - 1);
}
[[nodiscard]] bool TooEarlyForRequest(crl::time received) {
return (received > 0) && (received + kRequestTimeLimit > crl::now());
}
@@ -32,7 +45,7 @@ constexpr auto kRequestTimeLimit = 60 * crl::time(1000);
&& (item->date() > base::unixtime::now());
}
MTPMessage PrepareMessage(const MTPMessage &message) {
[[nodiscard]] MTPMessage PrepareMessage(const MTPMessage &message) {
return message.match([&](const MTPDmessageEmpty &data) {
return MTP_messageEmpty(
data.vflags(),
@@ -112,15 +125,16 @@ void ScheduledMessages::clearOldRequests() {
}
}
MsgId ScheduledMessages::lookupId(not_null<HistoryItem*> item) const {
Expects(item->isScheduled());
MsgId ScheduledMessages::localMessageId(MsgId remoteId) const {
return RemoteToLocalMsgId(remoteId);
}
const auto i = _data.find(item->history());
Assert(i != end(_data));
const auto &list = i->second;
const auto j = list.idByItem.find(item);
Assert(j != end(list.idByItem));
return j->second;
MsgId ScheduledMessages::lookupId(not_null<const HistoryItem*> item) const {
Expects(item->isScheduled());
Expects(!item->isSending());
Expects(!item->hasFailed());
return LocalToRemoteMsgId(item->id);
}
HistoryItem *ScheduledMessages::lookupItem(PeerId peer, MsgId msg) const {
@@ -314,13 +328,11 @@ void ScheduledMessages::apply(
Assert(i != end(_data));
auto &list = i->second;
const auto j = list.itemById.find(id);
if (j != end(list.itemById)) {
if (j != end(list.itemById) || !IsServerMsgId(id)) {
local->destroy();
} else {
Assert(!list.itemById.contains(local->id));
Assert(!list.idByItem.contains(local));
local->setRealId(local->history()->nextNonHistoryEntryId());
list.idByItem.emplace(local, id);
local->setRealId(localMessageId(id));
list.itemById.emplace(id, local);
}
}
@@ -466,8 +478,12 @@ HistoryItem *ScheduledMessages::append(
return existing;
}
if (!IsServerMsgId(id)) {
LOG(("API Error: Bad id in scheduled messages: %1.").arg(id));
return nullptr;
}
const auto item = _session->data().addNewMessage(
history->nextNonHistoryEntryId(),
localMessageId(id),
PrepareMessage(message),
MessageFlags(), // localFlags
NewMessageType::Existing);
@@ -477,7 +493,6 @@ HistoryItem *ScheduledMessages::append(
}
list.items.emplace_back(item);
list.itemById.emplace(id, item);
list.idByItem.emplace(item, id);
return item;
}
@@ -524,10 +539,7 @@ void ScheduledMessages::remove(not_null<const HistoryItem*> item) {
auto &list = i->second;
if (!item->isSending() && !item->hasFailed()) {
const auto j = list.idByItem.find(item);
Assert(j != end(list.idByItem));
list.itemById.remove(j->second);
list.idByItem.erase(j);
list.itemById.remove(lookupId(item));
}
const auto k = ranges::find(list.items, item, &OwnedItem::get);
Assert(k != list.items.end());
@@ -550,8 +562,7 @@ uint64 ScheduledMessages::countListHash(const List &list) const {
return !item->isSending() && !item->hasFailed();
}) | ranges::views::reverse;
for (const auto &item : serverside) {
const auto j = list.idByItem.find(item.get());
HashUpdate(hash, j->second.bare);
HashUpdate(hash, lookupId(item.get()).bare);
if (const auto edited = item->Get<HistoryMessageEdited>()) {
HashUpdate(hash, edited->date);
} else {

View File

@@ -28,7 +28,7 @@ public:
ScheduledMessages &operator=(const ScheduledMessages &other) = delete;
~ScheduledMessages();
[[nodiscard]] MsgId lookupId(not_null<HistoryItem*> item) const;
[[nodiscard]] MsgId lookupId(not_null<const HistoryItem*> item) const;
[[nodiscard]] HistoryItem *lookupItem(PeerId peer, MsgId msg) const;
[[nodiscard]] HistoryItem *lookupItem(FullMsgId itemId) const;
[[nodiscard]] int count(not_null<History*> history) const;
@@ -57,7 +57,6 @@ private:
struct List {
std::vector<OwnedItem> items;
base::flat_map<MsgId, not_null<HistoryItem*>> itemById;
base::flat_map<not_null<HistoryItem*>, MsgId> idByItem;
};
struct Request {
mtpRequestId requestId = 0;
@@ -82,6 +81,8 @@ private:
[[nodiscard]] uint64 countListHash(const List &list) const;
void clearOldRequests();
[[nodiscard]] MsgId localMessageId(MsgId remoteId) const;
const not_null<Main::Session*> _session;
base::Timer _clearTimer;

View File

@@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_file_origin.h"
#include "data/data_download_manager.h"
#include "data/data_photo.h"
#include "data/data_document.h"
#include "data/data_web_page.h"
@@ -1373,6 +1374,24 @@ void Session::changeMessageId(PeerId peerId, MsgId wasId, MsgId nowId) {
Ensures(ok);
}
bool Session::queryItemVisibility(not_null<HistoryItem*> item) const {
auto result = false;
_itemVisibilityQueries.fire({ item, &result });
return result;
}
[[nodiscard]] auto Session::itemVisibilityQueries() const
-> rpl::producer<Session::ItemVisibilityQuery> {
return _itemVisibilityQueries.events();
}
void Session::itemVisibilitiesUpdated() {
// This could be rewritten in a more generic form, like:
// rpl::producer<> itemVisibilitiesUpdates()
// if someone else requires those methods, using fast for now.
Core::App().downloadManager().itemVisibilitiesUpdated(_session);
}
void Session::notifyItemIdChange(IdChange event) {
const auto item = event.item;
changeMessageId(item->history()->peer->id, event.oldId, item->id);

View File

@@ -227,9 +227,10 @@ public:
not_null<HistoryItem*> item;
not_null<bool*> isVisible;
};
[[nodiscard]] base::Observable<ItemVisibilityQuery> &queryItemVisibility() {
return _queryItemVisibility;
}
[[nodiscard]] bool queryItemVisibility(not_null<HistoryItem*> item) const;
[[nodiscard]] rpl::producer<ItemVisibilityQuery> itemVisibilityQueries() const;
void itemVisibilitiesUpdated();
struct IdChange {
not_null<HistoryItem*> item;
MsgId oldId = 0;
@@ -839,7 +840,7 @@ private:
rpl::event_stream<Data::Folder*> _chatsListChanged;
rpl::event_stream<not_null<UserData*>> _userIsBotChanges;
rpl::event_stream<not_null<PeerData*>> _botCommandsChanges;
base::Observable<ItemVisibilityQuery> _queryItemVisibility;
rpl::event_stream<ItemVisibilityQuery> _itemVisibilityQueries;
rpl::event_stream<IdChange> _itemIdChanges;
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanges;
rpl::event_stream<not_null<const ViewElement*>> _viewLayoutChanges;
@@ -992,7 +993,7 @@ private:
std::unique_ptr<SponsoredMessages> _sponsoredMessages;
const std::unique_ptr<Reactions> _reactions;
MsgId _nonHistoryEntryId = ServerMaxMsgId;
MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange;
rpl::lifetime _lifetime;

View File

@@ -211,9 +211,7 @@ rpl::producer<SparseIdsMergedSlice> SharedScheduledMediaViewer(
const auto history = session->data().history(key.mergedKey.peerId);
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
session->data().scheduledMessages().updates(history)
) | rpl::map([=] {
const auto list = session->data().scheduledMessages().list(history);

View File

@@ -286,7 +286,7 @@ Widget::Widget(
if (!Core::UpdaterDisabled()) {
Core::UpdateChecker checker;
rpl::merge(
rpl::single(rpl::empty_value()),
rpl::single(rpl::empty),
checker.isLatest(),
checker.failed(),
checker.ready()
@@ -303,9 +303,7 @@ Widget::Widget(
_cancelSearch->setClickedCallback([this] { onCancelSearch(); });
_jumpToDate->entity()->setClickedCallback([this] { showCalendar(); });
_chooseFromUser->entity()->setClickedCallback([this] { showSearchFrom(); });
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
session().domain().local().localPasscodeChanged()
) | rpl::start_with_next([=] {
updateLockUnlockVisibility();
@@ -519,9 +517,7 @@ void Widget::setupSupportMode() {
void Widget::setupMainMenuToggle() {
_mainMenuToggle->setClickedCallback([=] { showMainMenu(); });
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
controller()->filtersMenuChanged()
) | rpl::start_with_next([=] {
const auto filtersHidden = !controller()->filtersWidth();

View File

@@ -352,9 +352,9 @@ PhotoEditorControls::PhotoEditorControls(
}, _stickersButton->lifetime());
}
rpl::single(
rpl::empty_value()
) | rpl::skip(modifications.flipped ? 0 : 1) | rpl::then(
rpl::single(rpl::empty) | rpl::skip(
modifications.flipped ? 0 : 1
) | rpl::then(
_flipButton->clicks() | rpl::to_empty
) | rpl::start_with_next([=] {
_flipped = !_flipped;

View File

@@ -300,20 +300,24 @@ InnerWidget::InnerWidget(
}
}
}, lifetime());
subscribe(session().data().queryItemVisibility(), [=](
session().data().itemVisibilityQueries(
) | rpl::filter([=](
const Data::Session::ItemVisibilityQuery &query) {
return (_history == query.item->history())
&& query.item->isAdminLogEntry()
&& isVisible();
}) | rpl::start_with_next([=](
const Data::Session::ItemVisibilityQuery &query) {
if (_history != query.item->history()
|| !query.item->isAdminLogEntry()
|| !isVisible()) {
return;
}
if (const auto view = viewForItem(query.item)) {
auto top = itemTop(view);
if (top >= 0 && top + view->height() > _visibleTop && top < _visibleBottom) {
if (top >= 0
&& top + view->height() > _visibleTop
&& top < _visibleBottom) {
*query.isVisible = true;
}
}
});
}, lifetime());
updateEmptyText();
requestAdmins();
@@ -355,6 +359,7 @@ void InnerWidget::visibleTopBottomUpdated(
scrollDateHideByTimer();
}
_controller->floatPlayerAreaUpdated();
session().data().itemVisibilitiesUpdated();
}
void InnerWidget::updateVisibleTopItem() {

View File

@@ -760,13 +760,15 @@ HistoryWidget::HistoryWidget(
updateNotifyControls();
}, lifetime());
subscribe(session().data().queryItemVisibility(), [=](
session().data().itemVisibilityQueries(
) | rpl::filter([=](
const Data::Session::ItemVisibilityQuery &query) {
return !_a_show.animating()
&& (_history == query.item->history())
&& (query.item->mainView() != nullptr)
&& isVisible();
}) | rpl::start_with_next([=](
const Data::Session::ItemVisibilityQuery &query) {
if (_a_show.animating()
|| _history != query.item->history()
|| !query.item->mainView() || !isVisible()) {
return;
}
if (const auto view = query.item->mainView()) {
auto top = _list->itemTop(view);
if (top >= 0) {
@@ -777,7 +779,8 @@ HistoryWidget::HistoryWidget(
}
}
}
});
}, lifetime());
_topBar->membersShowAreaActive(
) | rpl::start_with_next([=](bool active) {
setMembersShowAreaActive(active);
@@ -2301,6 +2304,7 @@ void HistoryWidget::showHistory(
}
update();
controller()->floatPlayerAreaUpdated();
session().data().itemVisibilitiesUpdated();
crl::on_main(this, [=] { controller()->widget()->setInnerFocus(); });
}
@@ -3332,6 +3336,7 @@ void HistoryWidget::visibleAreaUpdated() {
const auto scrollBottom = scrollTop + _scroll->height();
_list->visibleAreaUpdated(scrollTop, scrollBottom);
controller()->floatPlayerAreaUpdated();
session().data().itemVisibilitiesUpdated();
}
}
@@ -7642,6 +7647,8 @@ HistoryWidget::~HistoryWidget() {
saveFieldToHistoryLocalDraft();
session().api().saveDraftToCloudDelayed(_history);
setHistory(nullptr);
session().data().itemVisibilitiesUpdated();
}
setTabbedPanel(nullptr);
}

View File

@@ -331,7 +331,9 @@ ListWidget::ListWidget(
itemRemoved(item);
}, lifetime());
subscribe(session().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) {
session().data().itemVisibilityQueries(
) | rpl::start_with_next([=](
const Data::Session::ItemVisibilityQuery &query) {
if (const auto view = viewForItem(query.item)) {
const auto top = itemTop(view);
if (top >= 0
@@ -340,7 +342,7 @@ ListWidget::ListWidget(
*query.isVisible = true;
}
}
});
}, lifetime());
using ChosenReaction = Reactions::Manager::Chosen;
_reactionsManager->chosen(
@@ -794,6 +796,7 @@ void ListWidget::visibleTopBottomUpdated(
scrollDateHideByTimer();
}
_controller->floatPlayerAreaUpdated();
session().data().itemVisibilitiesUpdated();
_applyUpdatedScrollState.call();
}

View File

@@ -1600,9 +1600,7 @@ void SetupManagerList(
not_null<Main::Session*> session,
rpl::producer<std::optional<base::flat_set<QString>>> filter) {
const auto reactions = &session->data().reactions();
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
reactions->updates()
) | rpl::start_with_next([=] {
manager->applyList(

View File

@@ -1096,9 +1096,7 @@ rpl::producer<Data::MessagesSlice> ScheduledWidget::listSource(
int limitBefore,
int limitAfter) {
const auto data = &controller()->session().data();
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
data->scheduledMessages().updates(_history)
) | rpl::map([=] {
return data->scheduledMessages().list(_history);

View File

@@ -127,7 +127,7 @@ object_ptr<Media::ListWidget> InnerWidget::setupList() {
result->lifetime());
_selectedLists.fire(result->selectedListValue());
_listTops.fire(result->topValue());
_controller->mediaSourceQueryValue(
_controller->searchQueryValue(
) | rpl::start_with_next([this](const QString &query) {
_empty->setSearchQuery(query);
}, result->lifetime());

View File

@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/media/info_media_widget.h"
#include "info/media/info_media_list_section.h"
#include "info/info_controller.h"
#include "ui/text/format_song_document_name.h"
#include "data/data_download_manager.h"
#include "data/data_document.h"
#include "data/data_media_types.h"
@@ -70,7 +71,11 @@ bool Provider::isPossiblyMyItem(not_null<const HistoryItem*> item) {
}
std::optional<int> Provider::fullCount() {
return _fullCount;
return _queryWords.empty()
? _fullCount
: (_foundCount || _fullCount.has_value())
? _foundCount
: std::optional<int>();
}
void Provider::restart() {
@@ -84,14 +89,34 @@ void Provider::checkPreload(
bool preloadBottom) {
}
void Provider::refreshViewer() {
if (_fullCount) {
void Provider::setSearchQuery(QString query) {
if (_query == query) {
return;
}
_query = query;
auto words = TextUtilities::PrepareSearchWords(_query);
if (!_started || _queryWords == words) {
return;
}
_queryWords = std::move(words);
if (searchMode()) {
_foundCount = 0;
for (auto &element : _elements) {
if ((element.found = computeIsFound(element))) {
++_foundCount;
}
}
}
_refreshed.fire({});
}
void Provider::refreshViewer() {
if (_started) {
return;
}
_started = true;
auto &manager = Core::App().downloadManager();
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
manager.loadingListChanges() | rpl::to_empty
) | rpl::start_with_next([=, &manager] {
auto copy = _downloading;
@@ -100,7 +125,7 @@ void Provider::refreshViewer() {
const auto item = id->object.item;
if (!copy.remove(item) && !_downloaded.contains(item)) {
_downloading.emplace(item);
_elements.push_back({
addElementNow({
.item = item,
.started = id->started,
.path = id->path,
@@ -140,6 +165,13 @@ void Provider::refreshViewer() {
}
}, _lifetime);
manager.loadedResolveDone(
) | rpl::start_with_next([=] {
if (!_fullCount.has_value()) {
_fullCount = 0;
}
}, _lifetime);
performAdd();
performRefresh();
}
@@ -174,21 +206,37 @@ void Provider::performAdd() {
for (auto &element : base::take(_addPostponed)) {
_downloaded.emplace(element.item);
if (!_downloading.remove(element.item)) {
_elements.push_back(std::move(element));
addElementNow(std::move(element));
}
}
refreshPostponed(true);
}
void Provider::addElementNow(Element &&element) {
_elements.push_back(std::move(element));
auto &added = _elements.back();
fillSearchIndex(added);
added.found = searchMode() && computeIsFound(added);
if (added.found) {
++_foundCount;
}
}
void Provider::remove(not_null<const HistoryItem*> item) {
_addPostponed.erase(
ranges::remove(_addPostponed, item, &Element::item),
end(_addPostponed));
_downloading.remove(item);
_downloaded.remove(item);
_elements.erase(
ranges::remove(_elements, item, &Element::item),
end(_elements));
const auto proj = [&](const Element &element) {
if (element.item != item) {
return false;
} else if (element.found && searchMode()) {
--_foundCount;
}
return true;
};
_elements.erase(ranges::remove_if(_elements, proj), end(_elements));
if (const auto i = _layouts.find(item); i != end(_layouts)) {
_layoutRemoved.fire(i->second.item.get());
_layouts.erase(i);
@@ -213,7 +261,9 @@ void Provider::performRefresh() {
return;
}
_postponedRefresh = false;
_fullCount = _elements.size();
if (!_elements.empty() || _fullCount.has_value()) {
_fullCount = _elements.size();
}
if (base::take(_postponedRefreshSort)) {
ranges::sort(_elements, ranges::less(), &Element::started);
}
@@ -244,10 +294,14 @@ rpl::producer<> Provider::refreshed() {
std::vector<ListSection> Provider::fillSections(
not_null<Overview::Layout::Delegate*> delegate) {
markLayoutsStale();
const auto search = searchMode();
if (!search) {
markLayoutsStale();
}
const auto guard = gsl::finally([&] { clearStaleLayouts(); });
if (_elements.empty()) {
if (_elements.empty() || (search && !_foundCount)) {
return {};
}
@@ -256,7 +310,9 @@ std::vector<ListSection> Provider::fillSections(
ListSection(Type::File, sectionDelegate()));
auto &section = result.back();
for (const auto &element : ranges::views::reverse(_elements)) {
if (auto layout = getLayout(element, delegate)) {
if (search && !element.found) {
continue;
} else if (auto layout = getLayout(element, delegate)) {
section.addItem(layout);
}
}
@@ -308,6 +364,47 @@ bool Provider::isAfter(
return false;
}
bool Provider::searchMode() const {
return !_queryWords.empty();
}
void Provider::fillSearchIndex(Element &element) {
auto strings = QStringList(QFileInfo(element.path).fileName());
if (const auto media = element.item->media()) {
if (const auto document = media->document()) {
strings.append(document->filename());
strings.append(Ui::Text::FormatDownloadsName(document).text);
}
}
element.words = TextUtilities::PrepareSearchWords(strings.join(' '));
element.letters.clear();
for (const auto &word : element.words) {
element.letters.emplace(word.front());
}
}
bool Provider::computeIsFound(const Element &element) const {
Expects(!_queryWords.empty());
const auto has = [&](const QString &queryWord) {
if (!element.letters.contains(queryWord.front())) {
return false;
}
for (const auto &word : element.words) {
if (word.startsWith(queryWord)) {
return true;
}
}
return false;
};
for (const auto &queryWord : _queryWords) {
if (!has(queryWord)) {
return false;
}
}
return true;
}
void Provider::itemRemoved(not_null<const HistoryItem*> item) {
remove(item);
}
@@ -385,9 +482,13 @@ void Provider::applyDragSelection(
selected.clear();
return;
}
const auto search = !_queryWords.isEmpty();
auto chosen = base::flat_set<not_null<const HistoryItem*>>();
chosen.reserve(till - from);
for (auto i = from; i != till; ++i) {
if (search && !i->found) {
continue;
}
const auto item = i->item;
chosen.emplace(item);
ChangeItemSelection(

View File

@@ -44,6 +44,8 @@ public:
void refreshViewer() override;
rpl::producer<> refreshed() override;
void setSearchQuery(QString query) override;
std::vector<Media::ListSection> fillSections(
not_null<Overview::Layout::Delegate*> delegate) override;
rpl::producer<not_null<Media::BaseLayout*>> layoutRemoved() override;
@@ -86,6 +88,10 @@ private:
not_null<HistoryItem*> item;
int64 started = 0; // unixtime * 1000
QString path;
QStringList words;
base::flat_set<QChar> letters;
bool found = false;
};
bool sectionHasFloatingHeader() override;
@@ -94,6 +100,10 @@ private:
not_null<const Media::BaseLayout*> item,
not_null<const Media::BaseLayout*> previous) override;
[[nodiscard]] bool searchMode() const;
void fillSearchIndex(Element &element);
[[nodiscard]] bool computeIsFound(const Element &element) const;
void itemRemoved(not_null<const HistoryItem*> item);
void markLayoutsStale();
void clearStaleLayouts();
@@ -102,6 +112,7 @@ private:
void addPostponed(not_null<const Data::DownloadedId*> entry);
void performRefresh();
void performAdd();
void addElementNow(Element &&element);
void remove(not_null<const HistoryItem*> item);
void trackItemSession(not_null<const HistoryItem*> item);
@@ -127,9 +138,14 @@ private:
rpl::event_stream<not_null<Media::BaseLayout*>> _layoutRemoved;
rpl::event_stream<> _refreshed;
QString _query;
QStringList _queryWords;
int _foundCount = 0;
base::flat_map<not_null<Main::Session*>, rpl::lifetime> _trackedSessions;
bool _postponedRefreshSort = false;
bool _postponedRefresh = false;
bool _started = false;
rpl::lifetime _lifetime;

View File

@@ -108,6 +108,10 @@ rpl::producer<QString> AbstractController::mediaSourceQueryValue() const {
return rpl::single(QString());
}
rpl::producer<QString> AbstractController::searchQueryValue() const {
return rpl::single(QString());
}
AbstractController::AbstractController(
not_null<Window::SessionController*> parent)
: SessionNavigation(&parent->session())
@@ -221,8 +225,8 @@ void Controller::updateSearchControllers(
: Section::MediaType::kCount;
const auto hasMediaSearch = isMedia
&& SharedMediaAllowSearch(mediaType);
const auto hasCommonGroupsSearch
= (type == Type::CommonGroups);
const auto hasCommonGroupsSearch = (type == Type::CommonGroups);
const auto hasDownloadsSearch = (type == Type::Downloads);
const auto hasMembersSearch = (type == Type::Members)
|| (type == Type::Profile);
const auto searchQuery = memento->searchFieldQuery();
@@ -236,7 +240,10 @@ void Controller::updateSearchControllers(
} else {
_searchController = nullptr;
}
if (hasMediaSearch || hasCommonGroupsSearch || hasMembersSearch) {
if (hasMediaSearch
|| hasCommonGroupsSearch
|| hasDownloadsSearch
|| hasMembersSearch) {
_searchFieldController
= std::make_unique<Ui::SearchFieldController>(
searchQuery);
@@ -300,9 +307,11 @@ rpl::producer<bool> Controller::searchEnabledByContent() const {
}
rpl::producer<QString> Controller::mediaSourceQueryValue() const {
return _searchController
? _searchController->currentQueryValue()
: rpl::single(QString()); // #TODO downloads search
return _searchController->currentQueryValue();
}
rpl::producer<QString> Controller::searchQueryValue() const {
return searchFieldController()->queryValue();
}
rpl::producer<SparseIdsMergedSlice> Controller::mediaSource(

View File

@@ -141,6 +141,7 @@ public:
int limitBefore,
int limitAfter) const;
virtual rpl::producer<QString> mediaSourceQueryValue() const;
virtual rpl::producer<QString> searchQueryValue() const;
void showSection(
std::shared_ptr<Window::SectionMemento> memento,
@@ -198,6 +199,7 @@ public:
int limitBefore,
int limitAfter) const override;
rpl::producer<QString> mediaSourceQueryValue() const override;
rpl::producer<QString> searchQueryValue() const override;
bool takeSearchStartsFocused() {
return base::take(_searchStartsFocused);
}

View File

@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "main/main_session.h"
#include "mtproto/mtproto_config.h"
#include "data/data_download_manager.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_user.h"
@@ -397,6 +398,29 @@ void WrapWidget::createTopBar() {
&& (section.settingsType() == Section::SettingsType::Main
|| section.settingsType() == Section::SettingsType::Chat)) {
addTopBarMenuButton();
} else if (section.type() == Section::Type::Downloads) {
auto &manager = Core::App().downloadManager();
rpl::merge(
rpl::single(false),
manager.loadingListChanges() | rpl::map_to(false),
manager.loadedAdded() | rpl::map_to(true),
manager.loadedRemoved() | rpl::map_to(false)
) | rpl::start_with_next([=, &manager](bool definitelyHas) {
const auto has = [&] {
for ([[maybe_unused]] const auto id : manager.loadingList()) {
return true;
}
for ([[maybe_unused]] const auto id : manager.loadedList()) {
return true;
}
return false;
};
if (!definitelyHas && !has()) {
_topBarMenuToggle = nullptr;
} else if (!_topBarMenuToggle) {
addTopBarMenuButton();
}
}, _topBar->lifetime());
}
_topBar->lower();
@@ -495,7 +519,12 @@ void WrapWidget::showTopBarMenu() {
const style::icon *icon) {
return _topBarMenu->addAction(text, std::move(callback), icon);
};
if (const auto peer = key().peer()) {
if (key().isDownloads()) {
addAction(
tr::lng_context_delete_all_files(tr::now),
[=] { deleteAllDownloads(); },
&st::menuIconDelete);
} else if (const auto peer = key().peer()) {
Window::FillDialogsEntryMenu(
_controller->parentController(),
Dialogs::EntryState{
@@ -525,6 +554,24 @@ void WrapWidget::showTopBarMenu() {
_topBarMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
}
void WrapWidget::deleteAllDownloads() {
auto &manager = Core::App().downloadManager();
const auto phrase = tr::lng_downloads_delete_sure_all(tr::now);
const auto added = manager.loadedHasNonCloudFile()
? QString()
: tr::lng_downloads_delete_in_cloud(tr::now);
const auto deleteSure = [=, &manager](Fn<void()> close) {
Ui::PostponeCall(this, close);
manager.deleteAll();
};
_controller->parentController()->show(Ui::MakeConfirmBox({
.text = phrase + (added.isEmpty() ? QString() : "\n\n" + added),
.confirmed = deleteSure,
.confirmText = tr::lng_box_delete(tr::now),
.confirmStyle = &st::attentionBoxButton,
}));
}
bool WrapWidget::requireTopBarSearch() const {
if (!_controller->searchFieldController()) {
return false;

View File

@@ -197,6 +197,7 @@ private:
void addTopBarMenuButton();
void addProfileCallsButton();
void showTopBarMenu();
void deleteAllDownloads();
rpl::variable<Wrap> _wrap;
std::unique_ptr<Controller> _controller;

View File

@@ -158,6 +158,8 @@ public:
not_null<const HistoryItem*> item,
not_null<DocumentData*> document) = 0;
virtual void setSearchQuery(QString query) = 0;
[[nodiscard]] virtual int64 scrollTopStatePosition(
not_null<HistoryItem*> item) = 0;
[[nodiscard]] virtual HistoryItem *scrollTopStateItem(

View File

@@ -162,6 +162,11 @@ void ListWidget::start() {
if (_controller->isDownloads()) {
_provider->refreshViewer();
_controller->searchQueryValue(
) | rpl::start_with_next([this](QString &&query) {
_provider->setSearchQuery(std::move(query));
}, lifetime());
} else {
trackSession(&session());
@@ -169,6 +174,23 @@ void ListWidget::start() {
) | rpl::start_with_next([this] {
restart();
}, lifetime());
if (_provider->type() == Type::File) {
// For downloads manager.
session().data().itemVisibilityQueries(
) | rpl::filter([=](
const Data::Session::ItemVisibilityQuery &query) {
return _provider->isPossiblyMyItem(query.item)
&& isVisible();
}) | rpl::start_with_next([=](
const Data::Session::ItemVisibilityQuery &query) {
if (const auto found = findItemByItem(query.item)) {
if (itemVisible(found->layout)) {
*query.isVisible = true;
}
}
}, lifetime());
}
}
setupSelectRestriction();
@@ -566,6 +588,8 @@ void ListWidget::visibleTopBottomUpdated(
_dateBadge->check.call();
}
}
session().data().itemVisibilitiesUpdated();
}
void ListWidget::updateDateBadgeFor(int top) {

View File

@@ -341,6 +341,10 @@ bool Provider::isAfter(
return (GetUniversalId(a) < GetUniversalId(b));
}
void Provider::setSearchQuery(QString query) {
Unexpected("Media::Provider::setSearchQuery.");
}
SparseIdsMergedSlice::Key Provider::sliceKey(
UniversalMsgId universalId) const {
using Key = SparseIdsMergedSlice::Key;

View File

@@ -46,6 +46,8 @@ public:
not_null<const HistoryItem*> a,
not_null<const HistoryItem*> b) override;
void setSearchQuery(QString query) override;
ListItemSelectionData computeSelectionData(
not_null<const HistoryItem*> item,
TextSelection selection) override;

View File

@@ -405,9 +405,7 @@ rpl::producer<bool> CanAddMemberValue(not_null<PeerData*> peer) {
rpl::producer<int> FullReactionsCountValue(
not_null<Main::Session*> session) {
const auto reactions = &session->data().reactions();
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
reactions->updates()
) | rpl::map([=] {
return int(reactions->list(Data::Reactions::Type::Active).size());

View File

@@ -75,9 +75,7 @@ namespace {
) | rpl::map([](const QByteArray &code) {
return Qr::Encode(code, Qr::Redundancy::Quartile);
});
auto palettes = rpl::single(
rpl::empty_value()
) | rpl::then(
auto palettes = rpl::single(rpl::empty) | rpl::then(
style::PaletteChanged()
);
auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());

View File

@@ -149,7 +149,7 @@ Widget::Widget(
Core::UpdateChecker checker;
checker.start();
rpl::merge(
rpl::single(rpl::empty_value()),
rpl::single(rpl::empty),
checker.isLatest(),
checker.failed(),
checker.ready()

View File

@@ -479,9 +479,7 @@ void MainWidget::floatPlayerEnumerateSections(Fn<void(
}
bool MainWidget::floatPlayerIsVisible(not_null<HistoryItem*> item) {
auto isVisible = false;
session().data().queryItemVisibility().notify({ item, &isVisible }, true);
return isVisible;
return session().data().queryItemVisibility(item);
}
void MainWidget::floatPlayerClosed(FullMsgId itemId) {

View File

@@ -688,9 +688,7 @@ TimeId CalculateOnlineTill(not_null<PeerData*> peer) {
updateUserpics();
};
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
_session->data().pinnedDialogsOrderUpdated()
) | rpl::start_with_next(updatePinnedChats, _lifetime);

View File

@@ -443,9 +443,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
AddSubsectionTitle(aboutRows, tr::lng_filters_recommended());
const auto suggested = lifetime.make_state<rpl::variable<int>>();
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
session->data().chatsFilters().suggestedUpdated()
) | rpl::map([=] {
return session->data().chatsFilters().suggestedFilters();

View File

@@ -301,9 +301,7 @@ void SetupSections(
}
};
slided->toggleOn(
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
account->appConfig().refreshed()
) | rpl::map(
enabled

View File

@@ -229,9 +229,7 @@ void SetupLocalPasscode(
AddSkip(container);
AddSubsectionTitle(container, tr::lng_settings_passcode_title());
auto has = rpl::single(
rpl::empty_value()
) | rpl::then(
auto has = rpl::single(rpl::empty) | rpl::then(
controller->session().domain().local().localPasscodeChanged()
) | rpl::map([=] {
return controller->session().domain().local().hasLocalPasscode();
@@ -272,7 +270,7 @@ void SetupLocalPasscode(
? tr::lng_passcode_autolock_away
: tr::lng_passcode_autolock_inactive;
auto value = autoLockBoxClosing->events_starting_with(
rpl::empty_value()
{}
) | rpl::map([] {
const auto autolock = Core::App().settings().autoLock();
const auto hours = autolock / 3600;
@@ -471,9 +469,7 @@ void SetupCloudPassword(
) | rpl::filter([](TimeId time) {
return time != 0;
}) | rpl::map([](TimeId time) {
return rpl::single(
rpl::empty_value()
) | rpl::then(base::timer_each(
return rpl::single(rpl::empty) | rpl::then(base::timer_each(
999
)) | rpl::map([=] {
const auto now = base::unixtime::now();

View File

@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "boxes/send_files_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/image/image_prepare.h"
#include "lang/lang_keys.h"
#include "storage/file_download.h"
#include "storage/storage_media_prepare.h"
@@ -42,6 +43,7 @@ namespace {
constexpr auto kThumbnailQuality = 87;
constexpr auto kThumbnailSize = 320;
constexpr auto kPhotoUploadPartSize = 32 * 1024;
constexpr auto kRecompressAfterBpp = 4;
using Ui::ValidateThumbDimensions;
@@ -53,7 +55,7 @@ struct PreparedFileThumbnail {
MTPPhotoSize mtpSize = MTP_photoSizeEmpty(MTP_string());
};
PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
[[nodiscard]] PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
const auto width = original.width();
const auto height = original.height();
if (!ValidateThumbDimensions(width, height)) {
@@ -87,7 +89,9 @@ PreparedFileThumbnail PrepareFileThumbnail(QImage &&original) {
return result;
}
bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
[[nodiscard]] bool FileThumbnailUploadRequired(
const QString &filemime,
int32 filesize) {
constexpr auto kThumbnailUploadBySize = 5 * 1024 * 1024;
const auto kThumbnailKnownMimes = {
"image/jpeg",
@@ -101,7 +105,7 @@ bool FileThumbnailUploadRequired(const QString &filemime, int32 filesize) {
== end(kThumbnailKnownMimes));
}
PreparedFileThumbnail FinalizeFileThumbnail(
[[nodiscard]] PreparedFileThumbnail FinalizeFileThumbnail(
PreparedFileThumbnail &&prepared,
const QString &filemime,
int32 filesize,
@@ -115,7 +119,7 @@ PreparedFileThumbnail FinalizeFileThumbnail(
return std::move(prepared);
}
auto FindAlbumItem(
[[nodiscard]] auto FindAlbumItem(
std::vector<SendingAlbum::Item> &items,
not_null<HistoryItem*> item) {
const auto result = ranges::find(
@@ -127,7 +131,7 @@ auto FindAlbumItem(
return result;
}
MTPInputSingleMedia PrepareAlbumItemMedia(
[[nodiscard]] MTPInputSingleMedia PrepareAlbumItemMedia(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
uint64 randomId) {
@@ -149,7 +153,7 @@ MTPInputSingleMedia PrepareAlbumItemMedia(
sentEntities);
}
std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
[[nodiscard]] std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
not_null<const Ui::PreparedFileInformation::Image*> info) {
const auto allItems = info->modifications.paint->items();
@@ -162,6 +166,33 @@ std::vector<not_null<DocumentData*>> ExtractStickersFromScene(
}) | ranges::to_vector;
}
[[nodiscard]] QByteArray ComputePhotoJpegBytes(
QImage &full,
const QByteArray &bytes,
const QByteArray &format) {
if (!bytes.isEmpty()
&& (bytes.size()
<= full.width() * full.height() * kRecompressAfterBpp / 8)
&& (format == u"jpeg"_q)
&& Images::IsProgressiveJpeg(bytes)) {
return bytes;
}
// We have an example of dark .png image that when being sent without
// removing its color space is displayed fine on tdesktop, but with
// a light gray background on mobile apps.
full.setColorSpace(QColorSpace());
auto result = QByteArray();
QBuffer buffer(&result);
QImageWriter writer(&buffer, "JPEG");
writer.setQuality(87);
writer.setProgressiveScanWrite(true);
writer.write(full);
buffer.close();
return result;
}
} // namespace
SendMediaPrepare::SendMediaPrepare(
@@ -663,15 +694,23 @@ bool FileLoadTask::CheckForImage(
return Images::Read({
.path = filepath,
.content = content,
.returnContent = true,
});
}();
return FillImageInformation(std::move(read.image), read.animated, result);
return FillImageInformation(
std::move(read.image),
read.animated,
result,
std::move(read.content),
std::move(read.format));
}
bool FileLoadTask::FillImageInformation(
QImage &&image,
bool animated,
std::unique_ptr<Ui::PreparedFileInformation> &result) {
std::unique_ptr<Ui::PreparedFileInformation> &result,
QByteArray content,
QByteArray format) {
Expects(result != nullptr);
if (image.isNull()) {
@@ -679,6 +718,8 @@ bool FileLoadTask::FillImageInformation(
}
auto media = Ui::PreparedFileInformation::Image();
media.data = std::move(image);
media.bytes = std::move(content);
media.format = std::move(format);
media.animated = animated;
result->media = media;
return true;
@@ -703,6 +744,8 @@ void FileLoadTask::process(Args &&args) {
auto isSticker = false;
auto fullimage = QImage();
auto fullimagebytes = QByteArray();
auto fullimageformat = QByteArray();
auto info = _filepath.isEmpty() ? QFileInfo() : QFileInfo(_filepath);
if (info.exists()) {
if (info.isDir()) {
@@ -724,8 +767,12 @@ void FileLoadTask::process(Args &&args) {
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
&_information->media)) {
fullimage = base::take(image->data);
if (!Core::IsMimeSticker(filemime)) {
fullimagebytes = base::take(image->bytes);
fullimageformat = base::take(image->format);
if (!Core::IsMimeSticker(filemime)
&& fullimageformat != u"jpeg"_q) {
fullimage = Images::Opaque(std::move(fullimage));
fullimagebytes = fullimageformat = QByteArray();
}
isAnimation = image->animated;
}
@@ -739,12 +786,16 @@ void FileLoadTask::process(Args &&args) {
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
&_information->media)) {
fullimage = base::take(image->data);
fullimagebytes = base::take(image->bytes);
fullimageformat = base::take(image->format);
}
}
const auto mimeType = Core::MimeTypeForData(_content);
filemime = mimeType.name();
if (!Core::IsMimeSticker(filemime)) {
if (!Core::IsMimeSticker(filemime)
&& fullimageformat != u"jpeg"_q) {
fullimage = Images::Opaque(std::move(fullimage));
fullimagebytes = fullimageformat = QByteArray();
}
if (filemime == "image/jpeg") {
filename = filedialogDefaultName(qsl("photo"), qsl(".jpg"), QString(), true);
@@ -764,6 +815,8 @@ void FileLoadTask::process(Args &&args) {
if (auto image = std::get_if<Ui::PreparedFileInformation::Image>(
&_information->media)) {
fullimage = base::take(image->data);
fullimagebytes = base::take(image->bytes);
fullimageformat = base::take(image->format);
}
}
if (!fullimage.isNull() && fullimage.width() > 0) {
@@ -786,6 +839,7 @@ void FileLoadTask::process(Args &&args) {
filesize = _content.size();
}
fullimage = Images::Opaque(std::move(fullimage));
fullimagebytes = fullimageformat = QByteArray();
}
}
_result->filesize = (int32)qMin(filesize, qint64(INT_MAX));
@@ -878,18 +932,14 @@ void FileLoadTask::process(Args &&args) {
} else if (filemime.startsWith(u"image/"_q)
&& _type != SendMediaType::File) {
auto medium = (w > 320 || h > 320) ? fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
auto full = (w > 1280 || h > 1280) ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
{
// We have an example of dark .png image that when being sent without
// removing its color space is displayed fine on tdesktop, but with
// a light gray background on mobile apps.
full.setColorSpace(QColorSpace());
QBuffer buffer(&filedata);
QImageWriter writer(&buffer, "JPEG");
writer.setQuality(87);
writer.setProgressiveScanWrite(true);
writer.write(full);
const auto downscaled = (w > 1280 || h > 1280);
auto full = downscaled ? fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation) : fullimage;
if (downscaled) {
fullimagebytes = fullimageformat = QByteArray();
}
filedata = ComputePhotoJpegBytes(full, fullimagebytes, fullimageformat);
photoThumbs.emplace('m', PreparedPhotoThumb{ .image = medium });
photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));

View File

@@ -263,7 +263,9 @@ public:
static bool FillImageInformation(
QImage &&image,
bool animated,
std::unique_ptr<Ui::PreparedFileInformation> &result);
std::unique_ptr<Ui::PreparedFileInformation> &result,
QByteArray content = {},
QByteArray format = {});
FileLoadTask(
not_null<Main::Session*> session,

View File

@@ -47,9 +47,7 @@ ItemSingleFilePreview::ItemSingleFilePreview(
_documentMedia = document->createMediaView();
_documentMedia->thumbnailWanted(item->fullId());
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
document->session().downloaderTaskFinished()
) | rpl::start_with_next([=] {
if (_documentMedia->thumbnail()) {

View File

@@ -82,9 +82,7 @@ ItemSingleMediaPreview::ItemSingleMediaPreview(
}
};
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
session->downloaderTaskFinished()
) | rpl::start_with_next([=] {
const auto computed = computeThumbInfo();

View File

@@ -20,6 +20,8 @@ class SendFilesWay;
struct PreparedFileInformation {
struct Image {
QImage data;
QByteArray bytes;
QByteArray format;
bool animated = false;
Editor::PhotoModifications modifications;
};

View File

@@ -181,7 +181,7 @@ void SetupSendAsButton(
const auto channel = peer->asMegagroup();
auto updates = rpl::single(
rpl::empty_value()
rpl::empty
) | rpl::then(channel->session().sendAsPeers().updated(
) | rpl::filter(
_1 == channel

View File

@@ -231,9 +231,7 @@ void GroupCallBar::setupInner() {
static_cast<QMouseEvent*>(event.get())->pos());
});
}) | rpl::flatten_latest(
) | rpl::map([] {
return rpl::empty_value();
}) | rpl::start_to_stream(_barClicks, _inner->lifetime());
) | rpl::to_empty | rpl::start_to_stream(_barClicks, _inner->lifetime());
_wrap.geometryValue(
) | rpl::start_with_next([=](QRect rect) {

View File

@@ -135,13 +135,15 @@ void PinnedBar::createControls() {
static_cast<QMouseEvent*>(event.get())->pos());
});
}) | rpl::flatten_latest(
) | rpl::map([] {
return rpl::empty_value();
}) | rpl::start_to_stream(_barClicks, _bar->widget()->lifetime());
) | rpl::to_empty | rpl::start_to_stream(
_barClicks,
_bar->widget()->lifetime());
_bar->widget()->move(0, 0);
_bar->widget()->show();
_wrap.entity()->resize(_wrap.entity()->width(), _bar->widget()->height());
_wrap.entity()->resize(
_wrap.entity()->width(),
_bar->widget()->height());
_wrap.geometryValue(
) | rpl::start_with_next([=](QRect rect) {

View File

@@ -124,9 +124,7 @@ void RequestsBar::setupInner() {
static_cast<QMouseEvent*>(event.get())->pos());
});
}) | rpl::flatten_latest(
) | rpl::map([] {
return rpl::empty_value();
}) | rpl::start_to_stream(_barClicks, _inner->lifetime());
) | rpl::to_empty | rpl::start_to_stream(_barClicks, _inner->lifetime());
_wrap.geometryValue(
) | rpl::start_with_next([=](QRect rect) {

View File

@@ -74,9 +74,7 @@ AbstractSectionWidget::AbstractSectionWidget(
peerForBackground
) | rpl::map([=](PeerData *peer) -> rpl::producer<> {
if (!peer) {
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
controller->defaultChatTheme()->repaintBackgroundRequests()
);
}
@@ -84,9 +82,7 @@ AbstractSectionWidget::AbstractSectionWidget(
controller,
peer
) | rpl::map([](const std::shared_ptr<Ui::ChatTheme> &theme) {
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
theme->repaintBackgroundRequests()
);
}) | rpl::flatten_latest();

View File

@@ -1371,9 +1371,7 @@ rpl::producer<bool> IsNightModeValue() {
return update.type == BackgroundUpdate::Type::ApplyingTheme;
}) | rpl::to_empty;
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
std::move(changes)
) | rpl::map([=] {
return IsNightMode();
@@ -1455,9 +1453,7 @@ bool LoadFromContent(
}
rpl::producer<bool> IsThemeDarkValue() {
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
style::PaletteChanged()
) | rpl::map([] {
return (st::dialogsBg->c.valueF() < kDarkValueThreshold);

View File

@@ -336,9 +336,7 @@ void CloudList::setup() {
object.cloud.id ? object.cloud.id : kFakeCloudThemeId));
});
auto cloudListChanges = rpl::single(
rpl::empty_value()
) | rpl::then(
auto cloudListChanges = rpl::single(rpl::empty) | rpl::then(
_window->session().data().cloudThemes().updated()
);

View File

@@ -222,7 +222,7 @@ ConnectionState::ConnectionState(
if (!Core::UpdaterDisabled()) {
Core::UpdateChecker checker;
rpl::merge(
rpl::single(rpl::empty_value()),
rpl::single(rpl::empty),
checker.ready()
) | rpl::start_with_next([=] {
refreshState();

View File

@@ -34,7 +34,7 @@ namespace {
[[nodiscard]] rpl::producer<Dialogs::UnreadState> MainListUnreadState(
not_null<Dialogs::MainList*> list) {
return rpl::single(rpl::empty_value()) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
list->unreadStateChanges() | rpl::to_empty
) | rpl::map([=] {
return list->unreadState();
@@ -103,9 +103,7 @@ void FiltersMenu::setup() {
}, _outer.lifetime());
const auto filters = &_session->session().data().chatsFilters();
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
filters->changed()
) | rpl::start_with_next([=] {
refresh();

View File

@@ -240,9 +240,7 @@ void AddUnreadBadge(
const auto state = raw->lifetime().make_state<State>(raw);
if (!active) {
AddUnreadBadge(raw, rpl::single(
rpl::empty_value()
) | rpl::then(
AddUnreadBadge(raw, rpl::single(rpl::empty) | rpl::then(
session->data().unreadBadgeChanges()
) | rpl::map([=] {
auto &owner = session->data();
@@ -380,9 +378,7 @@ protected:
MainMenu::ToggleAccountsButton::ToggleAccountsButton(QWidget *parent)
: AbstractButton(parent) {
rpl::single(
rpl::empty_value()
) | rpl::then(
rpl::single(rpl::empty) | rpl::then(
Core::App().unreadBadgeChanges()
) | rpl::start_with_next([=] {
_unreadBadgeStale = true;
@@ -743,9 +739,7 @@ void MainMenu::setupArchive() {
return folder && (folder->id() == Data::Folder::kId);
}) | rpl::take(1);
AddUnreadBadge(button, rpl::single(
rpl::empty_value()
) | rpl::then(std::move(
AddUnreadBadge(button, rpl::single(rpl::empty) | rpl::then(std::move(
folderValue
) | rpl::map([=](not_null<Data::Folder*> folder) {
return folder->owner().chatsList(folder)->unreadStateChanges();
@@ -789,10 +783,9 @@ void MainMenu::setupAccounts() {
_addAccount = setupAddAccount(inner);
inner->add(object_ptr<Ui::FixedHeightWidget>(inner, st::mainMenuSkip));
rpl::single(
rpl::empty_value()
) | rpl::then(Core::App().domain().accountsChanges(
)) | rpl::start_with_next([=] {
rpl::single(rpl::empty) | rpl::then(
Core::App().domain().accountsChanges()
) | rpl::start_with_next([=] {
const auto &list = Core::App().domain().accounts();
const auto exists = [&](not_null<Main::Account*> account) {
for (const auto &[index, existing] : list) {
@@ -1206,12 +1199,9 @@ OthersUnreadState OtherAccountsUnreadStateCurrent() {
}
rpl::producer<OthersUnreadState> OtherAccountsUnreadState() {
return rpl::single(
rpl::empty_value()
) | rpl::then(
return rpl::single(rpl::empty) | rpl::then(
Core::App().unreadBadgeChanges()
) | rpl::map(OtherAccountsUnreadStateCurrent);
}
} // namespace Window

View File

@@ -107,16 +107,26 @@ set "DeployPath=%ReleasePath%\deploy\%AppVersionStrMajor%\%AppVersionStrFull%"
set "SignPath=%HomePath%\..\..\DesktopPrivate\Sign.bat"
set "BinaryName=Telegram"
set "DropboxSymbolsPath=Y:\Telegram\symbols"
set "DropboxSymbolsPathFallback=%HomePath%\..\..\Dropbox\Telegram\symbols"
set "FinalReleasePath=Z:\Projects\backup\tdesktop"
set "FinalReleasePathFallback=%HomePath%\..\..\Projects\backup\tdesktop"
if not exist %DropboxSymbolsPath% (
echo Dropbox path %DropboxSymbolsPath% not found!
exit /b 1
if exist %DropboxSymbolsPathFallback% (
set "DropboxSymbolsPath=%DropboxSymbolsPathFallback%"
) else (
echo Dropbox path %DropboxSymbolsPath% not found!
exit /b 1
)
)
if not exist %FinalReleasePath% (
echo Release path %FinalReleasePath% not found!
exit /b 1
if exist %FinalReleasePathFallback% (
set "FinalReleasePath=%FinalReleasePathFallback%"
) else (
echo Release path %FinalReleasePath% not found!
exit /b 1
)
)
if %BuildUWP% neq 0 (

View File

@@ -1,7 +1,7 @@
AppVersion 3005006
AppVersionStrMajor 3.5
AppVersionStrSmall 3.5.6
AppVersionStr 3.5.6
BetaChannel 1
AppVersion 3006000
AppVersionStrMajor 3.6
AppVersionStrSmall 3.6
AppVersionStr 3.6.0
BetaChannel 0
AlphaVersion 0
AppVersionOriginal 3.5.6.beta
AppVersionOriginal 3.6

View File

@@ -1,3 +1,13 @@
3.6 (11.03.22)
- Active and recently finished downloads pop up in bar in the bottom left corner, like they do in browsers.
- View recently downloaded files in Settings > Advanced > Downloads.
- Get an alert before closing the app if you have unfinished downloads.
- Share a direct t.me link to your phone number that instantly opens a chat with you.
- Use the full number in international format, like t.me/+123456789
- Manage Live Streams in your channels using external software like OBS Studio or XSplit Broadcaster.
- Choose "Stream With..." when staring a video chat or live stream - then copy your Stream Key and paste it into your streaming software.
3.5.6 beta (08.03.22)
- Show viewers count in RTMP streams.

2
cmake

Submodule cmake updated: 3aa9ec0ed1...425a0dc566

View File

@@ -125,6 +125,8 @@ parts:
- -GNinja
- -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr
- -DJPEG_LIBRARY_RELEASE=$SNAPCRAFT_STAGE/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so
- -DJPEG_INCLUDE_DIR=$SNAPCRAFT_STAGE/usr/include
- -DTDESKTOP_API_ID=611335
- -DTDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c
- -DDESKTOP_APP_USE_PACKAGED_LAZY=ON