Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c57e31256 | ||
|
|
0c03070109 | ||
|
|
b40f6577a6 | ||
|
|
c7398c631d | ||
|
|
95f5f28906 | ||
|
|
5be72e8ce2 | ||
|
|
1833fac094 | ||
|
|
1bc438ed01 | ||
|
|
32d09f189b | ||
|
|
437fe4ba82 | ||
|
|
e7b437980e | ||
|
|
a46329f796 | ||
|
|
6805259f74 | ||
|
|
e84ebc2a5c | ||
|
|
602e7a7164 | ||
|
|
6dd720b76e | ||
|
|
f1064e2d2f |
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 |
@@ -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}";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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([=] {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 §ion = 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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -197,6 +197,7 @@ private:
|
||||
void addTopBarMenuButton();
|
||||
void addProfileCallsButton();
|
||||
void showTopBarMenu();
|
||||
void deleteAllDownloads();
|
||||
|
||||
rpl::variable<Wrap> _wrap;
|
||||
std::unique_ptr<Controller> _controller;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -20,6 +20,8 @@ class SendFilesWay;
|
||||
struct PreparedFileInformation {
|
||||
struct Image {
|
||||
QImage data;
|
||||
QByteArray bytes;
|
||||
QByteArray format;
|
||||
bool animated = false;
|
||||
Editor::PhotoModifications modifications;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
Submodule Telegram/lib_base updated: 3827b6186e...03698385aa
Submodule Telegram/lib_rpl updated: 94a42b775a...6ee3948b61
Submodule Telegram/lib_ui updated: dd3cc5000e...c7826b8dff
Submodule Telegram/lib_waylandshells updated: 928501605f...8f98b8f1c1
@@ -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
2
cmake
Submodule cmake updated: 3aa9ec0ed1...425a0dc566
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user