From 4115d3d13d8a69e2a43246bcce612f010c80fb6d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jun 2018 19:31:30 +0100 Subject: [PATCH] Display export progress. --- .../SourceFiles/export/export_api_wrap.cpp | 5 +- Telegram/SourceFiles/export/export_api_wrap.h | 1 + .../SourceFiles/export/export_controller.cpp | 110 +++++-- .../SourceFiles/export/export_controller.h | 16 +- Telegram/SourceFiles/export/view/export.style | 20 ++ .../export/view/export_view_content.cpp | 71 ++++- .../export/view/export_view_content.h | 4 +- .../export/view/export_view_done.cpp | 36 +++ .../export/view/export_view_done.h | 8 + .../view/export_view_panel_controller.cpp | 46 ++- .../view/export_view_panel_controller.h | 2 + .../export/view/export_view_progress.cpp | 288 +++++++++++++++++- .../export/view/export_view_progress.h | 13 +- 13 files changed, 543 insertions(+), 77 deletions(-) diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index aa6d060b1d28a..01ea2e4421e83 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -571,6 +571,7 @@ void ApiWrap::finishUserpicsSlice() { } bool ApiWrap::loadUserpicProgress(FileProgress progress) { + Expects(_fileProcess != nullptr); Expects(_userpicsProcess != nullptr); Expects(_userpicsProcess->slice.has_value()); Expects((_userpicsProcess->fileIndex >= 0) @@ -578,6 +579,7 @@ bool ApiWrap::loadUserpicProgress(FileProgress progress) { < _userpicsProcess->slice->list.size())); return _userpicsProcess->fileProgress(DownloadProgress{ + _fileProcess->relativePath, _userpicsProcess->fileIndex, progress.ready, progress.total }); @@ -886,12 +888,14 @@ void ApiWrap::finishMessagesSlice() { } bool ApiWrap::loadMessageFileProgress(FileProgress progress) { + Expects(_fileProcess != nullptr); Expects(_chatProcess != nullptr); Expects(_chatProcess->slice.has_value()); Expects((_chatProcess->fileIndex >= 0) && (_chatProcess->fileIndex < _chatProcess->slice->list.size())); return _chatProcess->fileProgress(DownloadProgress{ + _fileProcess->relativePath, _chatProcess->fileIndex, progress.ready, progress.total }); @@ -996,7 +1000,6 @@ void ApiWrap::loadFile( _fileProcess->progress = std::move(progress); _fileProcess->done = std::move(done); - if (_fileProcess->progress) { const auto progress = FileProgress{ _fileProcess->file.size(), diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index 0475507175fc9..ddfb28faf8705 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -57,6 +57,7 @@ class ApiWrap { void requestPersonalInfo(FnMut done); struct DownloadProgress { + QString path; int itemIndex = 0; int ready = 0; int total = 0; diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index ae37dbff948b5..f5159063d7243 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -68,11 +68,17 @@ class Controller { ProcessingState stateLeftChannelsList(int processed) const; ProcessingState stateDialogsList(int processed) const; ProcessingState statePersonalInfo() const; - ProcessingState stateUserpics(DownloadProgress progress) const; + ProcessingState stateUserpics(const DownloadProgress &progress) const; ProcessingState stateContacts() const; ProcessingState stateSessions() const; - ProcessingState stateLeftChannels(DownloadProgress progress) const; - ProcessingState stateDialogs(DownloadProgress progress) const; + ProcessingState stateLeftChannels( + const DownloadProgress &progress) const; + ProcessingState stateDialogs(const DownloadProgress &progress) const; + void fillMessagesState( + ProcessingState &result, + const Data::DialogsInfo &info, + int index, + const DownloadProgress &progress) const; int substepsInStep(Step step) const; @@ -96,7 +102,10 @@ class Controller { // rpl::variable fails to compile in MSVC :( State _state; rpl::event_stream _stateChanges; - std::shared_ptr> _substepsInStep; + std::vector _substepsInStep; + int _substepsTotal = 0; + mutable int _substepsPassed = 0; + mutable Step _lastProcessingStep = Step::Initializing; std::unique_ptr _writer; std::vector _steps; @@ -292,7 +301,7 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) { push(Step::PersonalInfo, 1); } if (_settings.types & Settings::Type::Userpics) { - push(Step::Userpics, info.userpicsCount); + push(Step::Userpics, 1); } if (_settings.types & Settings::Type::Contacts) { push(Step::Contacts, 1); @@ -306,8 +315,8 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) { if (_settings.types & Settings::Type::AnyChatsMask) { push(Step::Dialogs, info.dialogsCount); } - _substepsInStep = std::make_shared>( - std::move(result)); + _substepsInStep = std::move(result); + _substepsTotal = ranges::accumulate(_substepsInStep, 0); } void Controller::exportNext() { @@ -512,10 +521,17 @@ template ProcessingState Controller::prepareState( Step step, Callback &&callback) const { + if (step != _lastProcessingStep) { + _substepsPassed += substepsInStep(_lastProcessingStep); + _lastProcessingStep = step; + } + auto result = ProcessingState(); callback(result); result.step = step; - result.substepsInStep = _substepsInStep; + result.substepsPassed = _substepsPassed; + result.substepsNow = substepsInStep(_lastProcessingStep); + result.substepsTotal = _substepsTotal; return result; } @@ -524,10 +540,12 @@ ProcessingState Controller::stateInitializing() const { } ProcessingState Controller::stateLeftChannelsList(int processed) const { - const auto step = Step::LeftChannelsList; - return prepareState(step, [&](ProcessingState &result) { + return prepareState(Step::LeftChannelsList, [&]( + ProcessingState &result) { result.entityIndex = processed; - result.entityCount = std::max(processed, substepsInStep(step)); + result.entityCount = std::max( + processed, + substepsInStep(Step::LeftChannels)); }); } @@ -535,17 +553,25 @@ ProcessingState Controller::stateDialogsList(int processed) const { const auto step = Step::DialogsList; return prepareState(step, [&](ProcessingState &result) { result.entityIndex = processed; - result.entityCount = std::max(processed, substepsInStep(step)); + result.entityCount = std::max( + processed, + substepsInStep(Step::Dialogs)); }); } ProcessingState Controller::statePersonalInfo() const { return prepareState(Step::PersonalInfo); } -ProcessingState Controller::stateUserpics(DownloadProgress progress) const { +ProcessingState Controller::stateUserpics( + const DownloadProgress &progress) const { return prepareState(Step::Userpics, [&](ProcessingState &result) { result.entityIndex = _userpicsWritten + progress.itemIndex; result.entityCount = std::max(_userpicsCount, result.entityIndex); + result.bytesType = ProcessingState::FileType::Photo; + if (!progress.path.isEmpty()) { + const auto last = progress.path.lastIndexOf('/'); + result.bytesName = progress.path.mid(last + 1); + } result.bytesLoaded = progress.ready; result.bytesCount = progress.total; }); @@ -560,35 +586,59 @@ ProcessingState Controller::stateSessions() const { } ProcessingState Controller::stateLeftChannels( - DownloadProgress progress) const { + const DownloadProgress & progress) const { const auto step = Step::LeftChannels; return prepareState(step, [&](ProcessingState &result) { - result.entityIndex = _leftChannelIndex; - result.entityCount = _leftChannelsInfo.list.size(); - result.itemIndex = _messagesWritten + progress.itemIndex; - result.itemCount = std::max(_messagesCount, result.entityIndex); - result.bytesLoaded = progress.ready; - result.bytesCount = progress.total; + fillMessagesState( + result, + _leftChannelsInfo, + _leftChannelIndex, + progress); }); } -ProcessingState Controller::stateDialogs(DownloadProgress progress) const { +ProcessingState Controller::stateDialogs( + const DownloadProgress &progress) const { const auto step = Step::Dialogs; return prepareState(step, [&](ProcessingState &result) { - result.entityIndex = _dialogIndex; - result.entityCount = _dialogsInfo.list.size(); - result.itemIndex = _messagesWritten + progress.itemIndex; - result.itemCount = std::max(_messagesCount, result.entityIndex); - result.bytesLoaded = progress.ready; - result.bytesCount = progress.total; + fillMessagesState( + result, + _dialogsInfo, + _dialogIndex, + progress); }); } +void Controller::fillMessagesState( + ProcessingState &result, + const Data::DialogsInfo &info, + int index, + const DownloadProgress &progress) const { + const auto &dialog = info.list[index]; + auto count = 0; + for (const auto &dialog : info.list) { + if (dialog.name.isEmpty()) { + ++count; + } + } + result.entityIndex = index; + result.entityCount = info.list.size(); + result.entityName = dialog.name; + result.itemIndex = _messagesWritten + progress.itemIndex; + result.itemCount = std::max(_messagesCount, result.entityIndex); + result.bytesType = ProcessingState::FileType::File; // TODO + if (!progress.path.isEmpty()) { + const auto last = progress.path.lastIndexOf('/'); + result.bytesName = progress.path.mid(last + 1); + } + result.bytesLoaded = progress.ready; + result.bytesCount = progress.total; +} + int Controller::substepsInStep(Step step) const { - Expects(_substepsInStep != 0); - Expects(_substepsInStep->size() > static_cast(step)); + Expects(_substepsInStep.size() > static_cast(step)); - return (*_substepsInStep)[static_cast(step)]; + return _substepsInStep[static_cast(step)]; } void Controller::setFinishedState() { diff --git a/Telegram/SourceFiles/export/export_controller.h b/Telegram/SourceFiles/export/export_controller.h index debde2181cbcc..4b97ef7d8e4c6 100644 --- a/Telegram/SourceFiles/export/export_controller.h +++ b/Telegram/SourceFiles/export/export_controller.h @@ -37,8 +37,8 @@ struct ProcessingState { LeftChannels, Dialogs, }; - enum class Item { - Other, + enum class FileType { + None, Photo, Video, VoiceMessage, @@ -50,21 +50,21 @@ struct ProcessingState { Step step = Step::Initializing; - std::shared_ptr> substepsInStep; + int substepsPassed = 0; + int substepsNow = 0; + int substepsTotal = 0; + QString entityName; int entityIndex = 0; int entityCount = 1; - QString entityName; int itemIndex = 0; int itemCount = 0; - Item itemType = Item::Other; - QString itemName; - QString itemId; + FileType bytesType = FileType::None; + QString bytesName; int bytesLoaded = 0; int bytesCount = 0; - QString objectId; }; diff --git a/Telegram/SourceFiles/export/view/export.style b/Telegram/SourceFiles/export/view/export.style index 08f71ff64cc42..3981476278c67 100644 --- a/Telegram/SourceFiles/export/view/export.style +++ b/Telegram/SourceFiles/export/view/export.style @@ -42,3 +42,23 @@ exportErrorLabel: FlatLabel(boxLabel) { align: align(top); textFg: boxTextFgError; } + +exportProgressDuration: 200; +exportProgressRowHeight: 30px; +exportProgressRowPadding: margins(22px, 10px, 22px, 20px); +exportProgressLabel: FlatLabel(boxLabel) { + textFg: windowBoldFg; + maxHeight: 20px; + style: TextStyle(defaultTextStyle) { + font: font(14px semibold); + linkFont: font(14px semibold); + linkFontOver: font(14px semibold); + } +} +exportProgressInfoLabel: FlatLabel(boxLabel) { + textFg: windowSubTextFg; + maxHeight: 20px; +} +exportProgressWidth: 3px; +exportProgressFg: mediaPlayerActiveFg; +exportProgressBg: mediaPlayerInactiveFg; diff --git a/Telegram/SourceFiles/export/view/export_view_content.cpp b/Telegram/SourceFiles/export/view/export_view_content.cpp index 8122e9ca4ea8c..6c34e1d232d13 100644 --- a/Telegram/SourceFiles/export/view/export_view_content.cpp +++ b/Telegram/SourceFiles/export/view/export_view_content.cpp @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #include "export/view/export_view_content.h" #include "lang/lang_keys.h" +#include "layout.h" namespace Export { namespace View { @@ -23,26 +24,80 @@ Content ContentFromState(const ProcessingState &state) { float64 progress) { result.rows.push_back({ id, label, info, progress }); }; + const auto pushMain = [&](const QString &label) { + const auto info = (state.entityCount > 0) + ? (QString::number(state.entityIndex) + + " / " + + QString::number(state.entityCount)) + : QString(); + if (!state.substepsTotal) { + push("main", label, info, 0.); + return; + } + const auto substepsTotal = state.substepsTotal; + const auto step = static_cast(state.step); + const auto done = state.substepsPassed; + const auto add = state.substepsNow; + const auto doneProgress = done / float64(substepsTotal); + const auto addProgress = (state.entityCount > 0) + ? ((float64(add) * state.entityIndex) + / (float64(substepsTotal) * state.entityCount)) + : 0.; + push("main", label, info, doneProgress + addProgress); + }; + const auto pushBytes = [&](const QString &id, const QString &label) { + if (!state.bytesCount) { + return; + } + const auto progress = state.bytesLoaded / float64(state.bytesCount); + const auto info = formatDownloadText( + state.bytesLoaded, + state.bytesCount); + push(id, label, info, progress); + }; switch (state.step) { case Step::Initializing: + pushMain(lang(lng_export_state_initializing)); + break; case Step::LeftChannelsList: case Step::DialogsList: + pushMain(lang(lng_export_state_chats_list)); + break; case Step::PersonalInfo: + pushMain(lang(lng_export_option_info)); + break; case Step::Userpics: + pushMain(lang(lng_export_state_userpics)); + pushBytes( + "userpic" + QString::number(state.entityIndex), + "Photo_" + QString::number(state.entityIndex + 1) + ".jpg"); + break; case Step::Contacts: + pushMain(lang(lng_export_option_contacts)); + break; case Step::Sessions: + pushMain(lang(lng_export_option_sessions)); + break; case Step::LeftChannels: case Step::Dialogs: - push("init", lang(lng_export_state_initializing), QString(), 0.); - if (state.entityCount > 0) { - push("entity", QString(), QString::number(state.entityIndex) + '/' + QString::number(state.entityCount), 0.); - } + pushMain(lang(lng_export_state_chats)); if (state.itemCount > 0) { - push("item", QString(), QString::number(state.itemIndex) + '/' + QString::number(state.itemCount), 0.); - } - if (state.bytesCount > 0) { - push("bytes", QString(), QString::number(state.bytesLoaded) + '/' + QString::number(state.bytesCount), 0.); + push( + "chat" + QString::number(state.entityIndex), + (state.entityName.isEmpty() + ? lang(lng_deleted) + : state.entityName), + (QString::number(state.itemIndex) + + " / " + + QString::number(state.itemCount)), + state.itemIndex / float64(state.itemCount)); } + pushBytes( + ("file" + + QString::number(state.entityIndex) + + '_' + + QString::number(state.itemIndex)), + state.bytesName); break; default: Unexpected("Step in ContentFromState."); } diff --git a/Telegram/SourceFiles/export/view/export_view_content.h b/Telegram/SourceFiles/export/view/export_view_content.h index 66aa7007c2bd2..f0c5e230e47b7 100644 --- a/Telegram/SourceFiles/export/view/export_view_content.h +++ b/Telegram/SourceFiles/export/view/export_view_content.h @@ -27,14 +27,14 @@ struct Content { Content ContentFromState(const ProcessingState &state); inline auto ContentFromState(rpl::producer state) { - return std::move( + return rpl::single(Content()) | rpl::then(std::move( state ) | rpl::filter([](const State &state) { return state.template is(); }) | rpl::map([](const State &state) { return ContentFromState( state.template get_unchecked()); - }); + })); } } // namespace View diff --git a/Telegram/SourceFiles/export/view/export_view_done.cpp b/Telegram/SourceFiles/export/view/export_view_done.cpp index 33f6882a00a6e..9d889b539e89f 100644 --- a/Telegram/SourceFiles/export/view/export_view_done.cpp +++ b/Telegram/SourceFiles/export/view/export_view_done.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #include "lang/lang_keys.h" #include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" #include "ui/wrap/vertical_layout.h" #include "platform/platform_specific.h" #include "styles/style_widgets.h" @@ -24,6 +25,8 @@ DoneWidget::DoneWidget(QWidget *parent) } void DoneWidget::setupContent() { + initFooter(); + const auto content = Ui::CreateChild(this); const auto label = content->add( @@ -47,5 +50,38 @@ rpl::producer<> DoneWidget::showClicks() const { return _showClicks.events(); } +rpl::producer<> DoneWidget::closeClicks() const { + return _close->clicks(); +} + +void DoneWidget::initFooter() { + const auto buttonsPadding = st::boxButtonPadding; + const auto buttonsHeight = buttonsPadding.top() + + st::defaultBoxButton.height + + buttonsPadding.bottom(); + const auto buttons = Ui::CreateChild( + this, + buttonsHeight); + + sizeValue( + ) | rpl::start_with_next([=](QSize size) { + buttons->resizeToWidth(size.width()); + buttons->moveToLeft(0, size.height() - buttons->height()); + }, lifetime()); + + _close = Ui::CreateChild( + buttons, + langFactory(lng_close), + st::defaultBoxButton); + _close->show(); + + buttons->widthValue( + ) | rpl::start_with_next([=] { + const auto right = st::boxButtonPadding.right(); + const auto top = st::boxButtonPadding.top(); + _close->moveToRight(right, top); + }, _close->lifetime()); +} + } // namespace View } // namespace Export diff --git a/Telegram/SourceFiles/export/view/export_view_done.h b/Telegram/SourceFiles/export/view/export_view_done.h index 0aaed85d1b371..2a171aff2123c 100644 --- a/Telegram/SourceFiles/export/view/export_view_done.h +++ b/Telegram/SourceFiles/export/view/export_view_done.h @@ -9,6 +9,10 @@ For license and copyright information please follow this link: #include "ui/rp_widget.h" +namespace Ui { +class RoundButton; +} // namespace Ui + namespace Export { namespace View { @@ -17,12 +21,16 @@ class DoneWidget : public Ui::RpWidget { DoneWidget(QWidget *parent); rpl::producer<> showClicks() const; + rpl::producer<> closeClicks() const; private: + void initFooter(); void setupContent(); rpl::event_stream<> _showClicks; + QPointer _close; + }; } // namespace View diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp index e5d499a9d0f0a..4da0b6fc30a01 100644 --- a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp +++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp @@ -46,9 +46,7 @@ void PanelController::showSettings() { settings->startClicks( ) | rpl::start_with_next([=](const Settings &settings) { - _panel->showInner(base::make_unique_q( - _panel.get(), - ContentFromState(_process->state()))); + showProgress(); _process->startExport(settings); }, settings->lifetime()); @@ -88,6 +86,36 @@ void PanelController::showError(const QString &text) { _panel->showInner(std::move(container)); } +void PanelController::showProgress() { + auto progress = base::make_unique_q( + _panel.get(), + ContentFromState(_process->state())); + + progress->cancelClicks( + ) | rpl::start_with_next([=] { + _panel->hideGetDuration(); + }, progress->lifetime()); + + _panel->showInner(std::move(progress)); +} + +void PanelController::showDone(const QString &path) { + auto done = base::make_unique_q(_panel.get()); + + done->showClicks( + ) | rpl::start_with_next([=] { + File::ShowInFolder(path); + _panel->hideGetDuration(); + }, done->lifetime()); + + done->closeClicks( + ) | rpl::start_with_next([=] { + _panel->hideGetDuration(); + }, done->lifetime()); + + _panel->showInner(std::move(done)); +} + rpl::producer<> PanelController::closed() const { return _panelCloseEvents.events( ) | rpl::flatten_latest( @@ -106,17 +134,7 @@ void PanelController::updateState(State &&state) { } else if (const auto error = base::get_if(&_state)) { showError(*error); } else if (const auto finished = base::get_if(&_state)) { - const auto path = finished->path; - - auto done = base::make_unique_q(_panel.get()); - - done->showClicks( - ) | rpl::start_with_next([=] { - File::ShowInFolder(path); - _panel->hideGetDuration(); - }, done->lifetime()); - - _panel->showInner(std::move(done)); + showDone(finished->path); } } diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.h b/Telegram/SourceFiles/export/view/export_view_panel_controller.h index 0a9149c33b7b6..35f68afb75bae 100644 --- a/Telegram/SourceFiles/export/view/export_view_panel_controller.h +++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.h @@ -31,6 +31,8 @@ class PanelController { void createPanel(); void updateState(State &&state); void showSettings(); + void showProgress(); + void showDone(const QString &path); void showError(const ApiErrorState &error); void showError(const OutputErrorState &error); void showError(const QString &text); diff --git a/Telegram/SourceFiles/export/view/export_view_progress.cpp b/Telegram/SourceFiles/export/view/export_view_progress.cpp index 71d2034b41152..e43fa650b7b1b 100644 --- a/Telegram/SourceFiles/export/view/export_view_progress.cpp +++ b/Telegram/SourceFiles/export/view/export_view_progress.cpp @@ -8,36 +8,298 @@ For license and copyright information please follow this link: #include "export/view/export_view_progress.h" #include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "lang/lang_keys.h" #include "styles/style_boxes.h" +#include "styles/style_export.h" namespace Export { namespace View { +class ProgressWidget::Row : public Ui::RpWidget { +public: + Row(QWidget *parent, Content::Row &&data); + + void updateData(Content::Row &&data); + +protected: + int resizeGetHeight(int newWidth) override; + + void paintEvent(QPaintEvent *e) override; + +private: + struct Instance { + base::unique_qptr> label; + base::unique_qptr> info; + + float64 value = 0.; + Animation progress; + + bool hiding = true; + Animation opacity; + }; + + void fillCurrentInstance(); + void hideCurrentInstance(); + void setInstanceProgress(Instance &instance, float64 progress); + void toggleInstance(Instance &data, bool shown); + void instanceOpacityCallback(QPointer label); + void removeOldInstance(QPointer label); + void paintInstance(Painter &p, const Instance &data); + + void updateControlsGeometry(int newWidth); + void updateInstanceGeometry(const Instance &instance, int newWidth); + + Content::Row _data; + Instance _current; + std::vector _old; + +}; + +ProgressWidget::Row::Row(QWidget *parent, Content::Row &&data) +: RpWidget(parent) +, _data(std::move(data)) { + fillCurrentInstance(); +} + +void ProgressWidget::Row::updateData(Content::Row &&data) { + const auto wasId = _data.id; + const auto nowId = data.id; + _data = std::move(data); + if (nowId.isEmpty()) { + hideCurrentInstance(); + } else if (wasId.isEmpty()) { + fillCurrentInstance(); + } else { + _current.label->entity()->setText(_data.label); + _current.info->entity()->setText(_data.info); + setInstanceProgress(_current, _data.progress); + if (nowId != wasId) { + _current.progress.finish(); + } + } + updateControlsGeometry(width()); + update(); +} + +void ProgressWidget::Row::fillCurrentInstance() { + _current.label = base::make_unique_q>( + this, + object_ptr( + this, + _data.label, + Ui::FlatLabel::InitType::Simple, + st::exportProgressLabel)); + _current.info = base::make_unique_q>( + this, + object_ptr( + this, + _data.info, + Ui::FlatLabel::InitType::Simple, + st::exportProgressInfoLabel)); + _current.label->hide(anim::type::instant); + _current.info->hide(anim::type::instant); + + setInstanceProgress(_current, _data.progress); + toggleInstance(_current, true); + if (_data.id == "main") { + _current.opacity.finish(); + _current.label->finishAnimating(); + _current.info->finishAnimating(); + } +} + +void ProgressWidget::Row::hideCurrentInstance() { + if (!_current.label) { + return; + } + setInstanceProgress(_current, 1.); + toggleInstance(_current, false); + _old.push_back(std::move(_current)); +} + +void ProgressWidget::Row::setInstanceProgress( + Instance &instance, + float64 progress) { + if (_current.value < progress) { + _current.progress.start( + [=] { update(); }, + _current.value, + progress, + st::exportProgressDuration, + anim::sineInOut); + } else if (_current.value > progress) { + _current.progress.finish(); + } + _current.value = progress; +} + +void ProgressWidget::Row::toggleInstance(Instance &instance, bool shown) { + Expects(instance.label != nullptr); + + if (instance.hiding != shown) { + return; + } + const auto label = make_weak(instance.label->entity()); + instance.opacity.start( + [=] { instanceOpacityCallback(label); }, + shown ? 0. : 1., + shown ? 1. : 0., + st::exportProgressDuration); + instance.hiding = !shown; + _current.label->toggle(shown, anim::type::normal); + _current.info->toggle(shown, anim::type::normal); +} + +void ProgressWidget::Row::instanceOpacityCallback( + QPointer label) { + update(); + const auto i = ranges::find(_old, label, [](const Instance &instance) { + return make_weak(instance.label->entity()); + }); + if (i != end(_old) && i->hiding && !i->opacity.animating()) { + crl::on_main(this, [=] { + removeOldInstance(label); + }); + } +} + +void ProgressWidget::Row::removeOldInstance(QPointer label) { + const auto i = ranges::find(_old, label, [](const Instance &instance) { + return make_weak(instance.label->entity()); + }); + if (i != end(_old)) { + _old.erase(i); + } +} + +int ProgressWidget::Row::resizeGetHeight(int newWidth) { + updateControlsGeometry(newWidth); + return st::exportProgressRowHeight; +} + +void ProgressWidget::Row::paintEvent(QPaintEvent *e) { + Painter p(this); + + const auto thickness = st::exportProgressWidth; + const auto top = height() - thickness; + p.fillRect(0, top, width(), thickness, st::shadowFg); + + for (const auto &instance : _old) { + paintInstance(p, instance); + } + paintInstance(p, _current); +} + +void ProgressWidget::Row::paintInstance(Painter &p, const Instance &data) { + const auto opacity = data.opacity.current(data.hiding ? 0. : 1.); + + if (!opacity) { + return; + } + p.setOpacity(opacity); + + const auto thickness = st::exportProgressWidth; + const auto top = height() - thickness; + const auto till = qRound(data.progress.current(data.value) * width()); + if (till > 0) { + p.fillRect(0, top, till, thickness, st::exportProgressFg); + } + if (till < width()) { + const auto left = width() - till; + p.fillRect(till, top, left, thickness, st::exportProgressBg); + } +} + +void ProgressWidget::Row::updateControlsGeometry(int newWidth) { + updateInstanceGeometry(_current, newWidth); + for (const auto &instance : _old) { + updateInstanceGeometry(instance, newWidth); + } +} + +void ProgressWidget::Row::updateInstanceGeometry( + const Instance &instance, + int newWidth) { + if (!instance.label) { + return; + } + instance.info->resizeToNaturalWidth(newWidth); + instance.label->resizeToWidth(newWidth - instance.info->width()); + instance.info->moveToRight(0, 0, newWidth); + instance.label->moveToLeft(0, 0, newWidth); +} + ProgressWidget::ProgressWidget( QWidget *parent, rpl::producer content) -: RpWidget(parent) { - const auto label = Ui::CreateChild(this, st::boxLabel); - sizeValue( - ) | rpl::start_with_next([=](QSize size) { - label->setGeometry(QRect(QPoint(), size)); - }, label->lifetime()); +: RpWidget(parent) +, _body(this) { + initFooter(); + + widthValue( + ) | rpl::start_with_next([=](int width) { + _body->resizeToWidth(width); + _body->moveToLeft(0, 0); + }, _body->lifetime()); + std::move( content ) | rpl::start_with_next([=](Content &&content) { - auto text = QString(); - for (const auto &row : content.rows) { - text += row.id + ' ' + row.info + ' ' + row.label + '\n'; - } - label->setText(text); updateState(std::move(content)); }, lifetime()); } -void ProgressWidget::updateState(Content &&content) { - _content = std::move(content); +rpl::producer<> ProgressWidget::cancelClicks() const { + return _cancel->clicks(); +} +void ProgressWidget::initFooter() { + const auto buttonsPadding = st::boxButtonPadding; + const auto buttonsHeight = buttonsPadding.top() + + st::defaultBoxButton.height + + buttonsPadding.bottom(); + const auto buttons = Ui::CreateChild( + this, + buttonsHeight); + sizeValue( + ) | rpl::start_with_next([=](QSize size) { + buttons->resizeToWidth(size.width()); + buttons->moveToLeft(0, size.height() - buttons->height()); + }, lifetime()); + + _cancel = Ui::CreateChild( + buttons, + langFactory(lng_cancel), + st::defaultBoxButton); + _cancel->show(); + + buttons->widthValue( + ) | rpl::start_with_next([=] { + const auto right = st::boxButtonPadding.right(); + const auto top = st::boxButtonPadding.top(); + _cancel->moveToRight(right, top); + }, _cancel->lifetime()); +} + +void ProgressWidget::updateState(Content &&content) { + auto index = 0; + for (auto &row : content.rows) { + if (index < _rows.size()) { + _rows[index]->updateData(std::move(row)); + } else { + _rows.push_back(_body->add( + object_ptr(this, std::move(row)), + st::exportProgressRowPadding)); + } + ++index; + } + for (const auto count = _rows.size(); index != count; ++index) { + _rows[index]->updateData(Content::Row()); + } } ProgressWidget::~ProgressWidget() = default; diff --git a/Telegram/SourceFiles/export/view/export_view_progress.h b/Telegram/SourceFiles/export/view/export_view_progress.h index c78692bdd9206..252f46238dbbe 100644 --- a/Telegram/SourceFiles/export/view/export_view_progress.h +++ b/Telegram/SourceFiles/export/view/export_view_progress.h @@ -10,6 +10,11 @@ For license and copyright information please follow this link: #include "ui/rp_widget.h" #include "export/view/export_view_content.h" +namespace Ui { +class VerticalLayout; +class RoundButton; +} // namespace Ui + namespace Export { namespace View { @@ -19,15 +24,21 @@ class ProgressWidget : public Ui::RpWidget { QWidget *parent, rpl::producer content); + rpl::producer<> cancelClicks() const; + ~ProgressWidget(); private: + void initFooter(); void updateState(Content &&content); Content _content; class Row; - std::vector> _rows; + object_ptr _body; + std::vector> _rows; + + QPointer _cancel; };