Display export progress.

This commit is contained in:
John Preston 2018-06-19 19:31:30 +01:00
parent 5f01751660
commit 4115d3d13d
13 changed files with 544 additions and 78 deletions

View File

@ -571,6 +571,7 @@ void ApiWrap::finishUserpicsSlice() {
} }
bool ApiWrap::loadUserpicProgress(FileProgress progress) { bool ApiWrap::loadUserpicProgress(FileProgress progress) {
Expects(_fileProcess != nullptr);
Expects(_userpicsProcess != nullptr); Expects(_userpicsProcess != nullptr);
Expects(_userpicsProcess->slice.has_value()); Expects(_userpicsProcess->slice.has_value());
Expects((_userpicsProcess->fileIndex >= 0) Expects((_userpicsProcess->fileIndex >= 0)
@ -578,6 +579,7 @@ bool ApiWrap::loadUserpicProgress(FileProgress progress) {
< _userpicsProcess->slice->list.size())); < _userpicsProcess->slice->list.size()));
return _userpicsProcess->fileProgress(DownloadProgress{ return _userpicsProcess->fileProgress(DownloadProgress{
_fileProcess->relativePath,
_userpicsProcess->fileIndex, _userpicsProcess->fileIndex,
progress.ready, progress.ready,
progress.total }); progress.total });
@ -886,12 +888,14 @@ void ApiWrap::finishMessagesSlice() {
} }
bool ApiWrap::loadMessageFileProgress(FileProgress progress) { bool ApiWrap::loadMessageFileProgress(FileProgress progress) {
Expects(_fileProcess != nullptr);
Expects(_chatProcess != nullptr); Expects(_chatProcess != nullptr);
Expects(_chatProcess->slice.has_value()); Expects(_chatProcess->slice.has_value());
Expects((_chatProcess->fileIndex >= 0) Expects((_chatProcess->fileIndex >= 0)
&& (_chatProcess->fileIndex < _chatProcess->slice->list.size())); && (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
return _chatProcess->fileProgress(DownloadProgress{ return _chatProcess->fileProgress(DownloadProgress{
_fileProcess->relativePath,
_chatProcess->fileIndex, _chatProcess->fileIndex,
progress.ready, progress.ready,
progress.total }); progress.total });
@ -996,7 +1000,6 @@ void ApiWrap::loadFile(
_fileProcess->progress = std::move(progress); _fileProcess->progress = std::move(progress);
_fileProcess->done = std::move(done); _fileProcess->done = std::move(done);
if (_fileProcess->progress) { if (_fileProcess->progress) {
const auto progress = FileProgress{ const auto progress = FileProgress{
_fileProcess->file.size(), _fileProcess->file.size(),

View File

@ -57,6 +57,7 @@ public:
void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done); void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
struct DownloadProgress { struct DownloadProgress {
QString path;
int itemIndex = 0; int itemIndex = 0;
int ready = 0; int ready = 0;
int total = 0; int total = 0;

View File

@ -68,11 +68,17 @@ private:
ProcessingState stateLeftChannelsList(int processed) const; ProcessingState stateLeftChannelsList(int processed) const;
ProcessingState stateDialogsList(int processed) const; ProcessingState stateDialogsList(int processed) const;
ProcessingState statePersonalInfo() const; ProcessingState statePersonalInfo() const;
ProcessingState stateUserpics(DownloadProgress progress) const; ProcessingState stateUserpics(const DownloadProgress &progress) const;
ProcessingState stateContacts() const; ProcessingState stateContacts() const;
ProcessingState stateSessions() const; ProcessingState stateSessions() const;
ProcessingState stateLeftChannels(DownloadProgress progress) const; ProcessingState stateLeftChannels(
ProcessingState stateDialogs(DownloadProgress progress) const; 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; int substepsInStep(Step step) const;
@ -96,7 +102,10 @@ private:
// rpl::variable<State> fails to compile in MSVC :( // rpl::variable<State> fails to compile in MSVC :(
State _state; State _state;
rpl::event_stream<State> _stateChanges; rpl::event_stream<State> _stateChanges;
std::shared_ptr<const std::vector<int>> _substepsInStep; std::vector<int> _substepsInStep;
int _substepsTotal = 0;
mutable int _substepsPassed = 0;
mutable Step _lastProcessingStep = Step::Initializing;
std::unique_ptr<Output::AbstractWriter> _writer; std::unique_ptr<Output::AbstractWriter> _writer;
std::vector<Step> _steps; std::vector<Step> _steps;
@ -292,7 +301,7 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
push(Step::PersonalInfo, 1); push(Step::PersonalInfo, 1);
} }
if (_settings.types & Settings::Type::Userpics) { if (_settings.types & Settings::Type::Userpics) {
push(Step::Userpics, info.userpicsCount); push(Step::Userpics, 1);
} }
if (_settings.types & Settings::Type::Contacts) { if (_settings.types & Settings::Type::Contacts) {
push(Step::Contacts, 1); push(Step::Contacts, 1);
@ -306,8 +315,8 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::AnyChatsMask) { if (_settings.types & Settings::Type::AnyChatsMask) {
push(Step::Dialogs, info.dialogsCount); push(Step::Dialogs, info.dialogsCount);
} }
_substepsInStep = std::make_shared<const std::vector<int>>( _substepsInStep = std::move(result);
std::move(result)); _substepsTotal = ranges::accumulate(_substepsInStep, 0);
} }
void Controller::exportNext() { void Controller::exportNext() {
@ -512,10 +521,17 @@ template <typename Callback>
ProcessingState Controller::prepareState( ProcessingState Controller::prepareState(
Step step, Step step,
Callback &&callback) const { Callback &&callback) const {
if (step != _lastProcessingStep) {
_substepsPassed += substepsInStep(_lastProcessingStep);
_lastProcessingStep = step;
}
auto result = ProcessingState(); auto result = ProcessingState();
callback(result); callback(result);
result.step = step; result.step = step;
result.substepsInStep = _substepsInStep; result.substepsPassed = _substepsPassed;
result.substepsNow = substepsInStep(_lastProcessingStep);
result.substepsTotal = _substepsTotal;
return result; return result;
} }
@ -524,10 +540,12 @@ ProcessingState Controller::stateInitializing() const {
} }
ProcessingState Controller::stateLeftChannelsList(int processed) const { ProcessingState Controller::stateLeftChannelsList(int processed) const {
const auto step = Step::LeftChannelsList; return prepareState(Step::LeftChannelsList, [&](
return prepareState(step, [&](ProcessingState &result) { ProcessingState &result) {
result.entityIndex = processed; 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; const auto step = Step::DialogsList;
return prepareState(step, [&](ProcessingState &result) { return prepareState(step, [&](ProcessingState &result) {
result.entityIndex = processed; result.entityIndex = processed;
result.entityCount = std::max(processed, substepsInStep(step)); result.entityCount = std::max(
processed,
substepsInStep(Step::Dialogs));
}); });
} }
ProcessingState Controller::statePersonalInfo() const { ProcessingState Controller::statePersonalInfo() const {
return prepareState(Step::PersonalInfo); return prepareState(Step::PersonalInfo);
} }
ProcessingState Controller::stateUserpics(DownloadProgress progress) const { ProcessingState Controller::stateUserpics(
const DownloadProgress &progress) const {
return prepareState(Step::Userpics, [&](ProcessingState &result) { return prepareState(Step::Userpics, [&](ProcessingState &result) {
result.entityIndex = _userpicsWritten + progress.itemIndex; result.entityIndex = _userpicsWritten + progress.itemIndex;
result.entityCount = std::max(_userpicsCount, result.entityIndex); 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.bytesLoaded = progress.ready;
result.bytesCount = progress.total; result.bytesCount = progress.total;
}); });
@ -560,35 +586,59 @@ ProcessingState Controller::stateSessions() const {
} }
ProcessingState Controller::stateLeftChannels( ProcessingState Controller::stateLeftChannels(
DownloadProgress progress) const { const DownloadProgress & progress) const {
const auto step = Step::LeftChannels; const auto step = Step::LeftChannels;
return prepareState(step, [&](ProcessingState &result) { return prepareState(step, [&](ProcessingState &result) {
result.entityIndex = _leftChannelIndex; fillMessagesState(
result.entityCount = _leftChannelsInfo.list.size(); result,
result.itemIndex = _messagesWritten + progress.itemIndex; _leftChannelsInfo,
result.itemCount = std::max(_messagesCount, result.entityIndex); _leftChannelIndex,
result.bytesLoaded = progress.ready; progress);
result.bytesCount = progress.total;
}); });
} }
ProcessingState Controller::stateDialogs(DownloadProgress progress) const { ProcessingState Controller::stateDialogs(
const DownloadProgress &progress) const {
const auto step = Step::Dialogs; const auto step = Step::Dialogs;
return prepareState(step, [&](ProcessingState &result) { return prepareState(step, [&](ProcessingState &result) {
result.entityIndex = _dialogIndex; fillMessagesState(
result.entityCount = _dialogsInfo.list.size(); result,
result.itemIndex = _messagesWritten + progress.itemIndex; _dialogsInfo,
result.itemCount = std::max(_messagesCount, result.entityIndex); _dialogIndex,
result.bytesLoaded = progress.ready; progress);
result.bytesCount = progress.total;
}); });
} }
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 { int Controller::substepsInStep(Step step) const {
Expects(_substepsInStep != 0); Expects(_substepsInStep.size() > static_cast<int>(step));
Expects(_substepsInStep->size() > static_cast<int>(step));
return (*_substepsInStep)[static_cast<int>(step)]; return _substepsInStep[static_cast<int>(step)];
} }
void Controller::setFinishedState() { void Controller::setFinishedState() {

View File

@ -37,8 +37,8 @@ struct ProcessingState {
LeftChannels, LeftChannels,
Dialogs, Dialogs,
}; };
enum class Item { enum class FileType {
Other, None,
Photo, Photo,
Video, Video,
VoiceMessage, VoiceMessage,
@ -50,21 +50,21 @@ struct ProcessingState {
Step step = Step::Initializing; Step step = Step::Initializing;
std::shared_ptr<const std::vector<int>> substepsInStep; int substepsPassed = 0;
int substepsNow = 0;
int substepsTotal = 0;
QString entityName;
int entityIndex = 0; int entityIndex = 0;
int entityCount = 1; int entityCount = 1;
QString entityName;
int itemIndex = 0; int itemIndex = 0;
int itemCount = 0; int itemCount = 0;
Item itemType = Item::Other;
QString itemName;
QString itemId;
FileType bytesType = FileType::None;
QString bytesName;
int bytesLoaded = 0; int bytesLoaded = 0;
int bytesCount = 0; int bytesCount = 0;
QString objectId;
}; };

View File

@ -42,3 +42,23 @@ exportErrorLabel: FlatLabel(boxLabel) {
align: align(top); align: align(top);
textFg: boxTextFgError; 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;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/view/export_view_content.h" #include "export/view/export_view_content.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "layout.h"
namespace Export { namespace Export {
namespace View { namespace View {
@ -23,26 +24,80 @@ Content ContentFromState(const ProcessingState &state) {
float64 progress) { float64 progress) {
result.rows.push_back({ id, label, info, 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<int>(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) { switch (state.step) {
case Step::Initializing: case Step::Initializing:
pushMain(lang(lng_export_state_initializing));
break;
case Step::LeftChannelsList: case Step::LeftChannelsList:
case Step::DialogsList: case Step::DialogsList:
pushMain(lang(lng_export_state_chats_list));
break;
case Step::PersonalInfo: case Step::PersonalInfo:
pushMain(lang(lng_export_option_info));
break;
case Step::Userpics: 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: case Step::Contacts:
pushMain(lang(lng_export_option_contacts));
break;
case Step::Sessions: case Step::Sessions:
pushMain(lang(lng_export_option_sessions));
break;
case Step::LeftChannels: case Step::LeftChannels:
case Step::Dialogs: case Step::Dialogs:
push("init", lang(lng_export_state_initializing), QString(), 0.); pushMain(lang(lng_export_state_chats));
if (state.entityCount > 0) {
push("entity", QString(), QString::number(state.entityIndex) + '/' + QString::number(state.entityCount), 0.);
}
if (state.itemCount > 0) { if (state.itemCount > 0) {
push("item", QString(), QString::number(state.itemIndex) + '/' + QString::number(state.itemCount), 0.); push(
} "chat" + QString::number(state.entityIndex),
if (state.bytesCount > 0) { (state.entityName.isEmpty()
push("bytes", QString(), QString::number(state.bytesLoaded) + '/' + QString::number(state.bytesCount), 0.); ? 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; break;
default: Unexpected("Step in ContentFromState."); default: Unexpected("Step in ContentFromState.");
} }

View File

@ -27,14 +27,14 @@ struct Content {
Content ContentFromState(const ProcessingState &state); Content ContentFromState(const ProcessingState &state);
inline auto ContentFromState(rpl::producer<State> state) { inline auto ContentFromState(rpl::producer<State> state) {
return std::move( return rpl::single(Content()) | rpl::then(std::move(
state state
) | rpl::filter([](const State &state) { ) | rpl::filter([](const State &state) {
return state.template is<ProcessingState>(); return state.template is<ProcessingState>();
}) | rpl::map([](const State &state) { }) | rpl::map([](const State &state) {
return ContentFromState( return ContentFromState(
state.template get_unchecked<ProcessingState>()); state.template get_unchecked<ProcessingState>());
}); }));
} }
} // namespace View } // namespace View

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "platform/platform_specific.h" #include "platform/platform_specific.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
@ -24,6 +25,8 @@ DoneWidget::DoneWidget(QWidget *parent)
} }
void DoneWidget::setupContent() { void DoneWidget::setupContent() {
initFooter();
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this); const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto label = content->add( const auto label = content->add(
@ -47,5 +50,38 @@ rpl::producer<> DoneWidget::showClicks() const {
return _showClicks.events(); 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<Ui::FixedHeightWidget>(
this,
buttonsHeight);
sizeValue(
) | rpl::start_with_next([=](QSize size) {
buttons->resizeToWidth(size.width());
buttons->moveToLeft(0, size.height() - buttons->height());
}, lifetime());
_close = Ui::CreateChild<Ui::RoundButton>(
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 View
} // namespace Export } // namespace Export

View File

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
namespace Ui {
class RoundButton;
} // namespace Ui
namespace Export { namespace Export {
namespace View { namespace View {
@ -17,12 +21,16 @@ public:
DoneWidget(QWidget *parent); DoneWidget(QWidget *parent);
rpl::producer<> showClicks() const; rpl::producer<> showClicks() const;
rpl::producer<> closeClicks() const;
private: private:
void initFooter();
void setupContent(); void setupContent();
rpl::event_stream<> _showClicks; rpl::event_stream<> _showClicks;
QPointer<Ui::RoundButton> _close;
}; };
} // namespace View } // namespace View

View File

@ -46,9 +46,7 @@ void PanelController::showSettings() {
settings->startClicks( settings->startClicks(
) | rpl::start_with_next([=](const Settings &settings) { ) | rpl::start_with_next([=](const Settings &settings) {
_panel->showInner(base::make_unique_q<ProgressWidget>( showProgress();
_panel.get(),
ContentFromState(_process->state())));
_process->startExport(settings); _process->startExport(settings);
}, settings->lifetime()); }, settings->lifetime());
@ -88,6 +86,36 @@ void PanelController::showError(const QString &text) {
_panel->showInner(std::move(container)); _panel->showInner(std::move(container));
} }
void PanelController::showProgress() {
auto progress = base::make_unique_q<ProgressWidget>(
_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<DoneWidget>(_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 { rpl::producer<> PanelController::closed() const {
return _panelCloseEvents.events( return _panelCloseEvents.events(
) | rpl::flatten_latest( ) | rpl::flatten_latest(
@ -106,17 +134,7 @@ void PanelController::updateState(State &&state) {
} else if (const auto error = base::get_if<OutputErrorState>(&_state)) { } else if (const auto error = base::get_if<OutputErrorState>(&_state)) {
showError(*error); showError(*error);
} else if (const auto finished = base::get_if<FinishedState>(&_state)) { } else if (const auto finished = base::get_if<FinishedState>(&_state)) {
const auto path = finished->path; showDone(finished->path);
auto done = base::make_unique_q<DoneWidget>(_panel.get());
done->showClicks(
) | rpl::start_with_next([=] {
File::ShowInFolder(path);
_panel->hideGetDuration();
}, done->lifetime());
_panel->showInner(std::move(done));
} }
} }

View File

@ -31,6 +31,8 @@ private:
void createPanel(); void createPanel();
void updateState(State &&state); void updateState(State &&state);
void showSettings(); void showSettings();
void showProgress();
void showDone(const QString &path);
void showError(const ApiErrorState &error); void showError(const ApiErrorState &error);
void showError(const OutputErrorState &error); void showError(const OutputErrorState &error);
void showError(const QString &text); void showError(const QString &text);

View File

@ -8,36 +8,298 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/view/export_view_progress.h" #include "export/view/export_view_progress.h"
#include "ui/widgets/labels.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_boxes.h"
#include "styles/style_export.h"
namespace Export { namespace Export {
namespace View { 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<Ui::FadeWrap<Ui::FlatLabel>> label;
base::unique_qptr<Ui::FadeWrap<Ui::FlatLabel>> 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<Ui::FlatLabel> label);
void removeOldInstance(QPointer<Ui::FlatLabel> 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<Instance> _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<Ui::FadeWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
this,
_data.label,
Ui::FlatLabel::InitType::Simple,
st::exportProgressLabel));
_current.info = base::make_unique_q<Ui::FadeWrap<Ui::FlatLabel>>(
this,
object_ptr<Ui::FlatLabel>(
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<Ui::FlatLabel> 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<Ui::FlatLabel> 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( ProgressWidget::ProgressWidget(
QWidget *parent, QWidget *parent,
rpl::producer<Content> content) rpl::producer<Content> content)
: RpWidget(parent) { : RpWidget(parent)
const auto label = Ui::CreateChild<Ui::FlatLabel>(this, st::boxLabel); , _body(this) {
sizeValue( initFooter();
) | rpl::start_with_next([=](QSize size) {
label->setGeometry(QRect(QPoint(), size)); widthValue(
}, label->lifetime()); ) | rpl::start_with_next([=](int width) {
_body->resizeToWidth(width);
_body->moveToLeft(0, 0);
}, _body->lifetime());
std::move( std::move(
content content
) | rpl::start_with_next([=](Content &&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)); updateState(std::move(content));
}, lifetime()); }, lifetime());
} }
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<Ui::FixedHeightWidget>(
this,
buttonsHeight);
sizeValue(
) | rpl::start_with_next([=](QSize size) {
buttons->resizeToWidth(size.width());
buttons->moveToLeft(0, size.height() - buttons->height());
}, lifetime());
_cancel = Ui::CreateChild<Ui::RoundButton>(
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) { void ProgressWidget::updateState(Content &&content) {
_content = std::move(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<Row>(this, std::move(row)),
st::exportProgressRowPadding));
}
++index;
}
for (const auto count = _rows.size(); index != count; ++index) {
_rows[index]->updateData(Content::Row());
}
} }
ProgressWidget::~ProgressWidget() = default; ProgressWidget::~ProgressWidget() = default;

View File

@ -10,6 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "export/view/export_view_content.h" #include "export/view/export_view_content.h"
namespace Ui {
class VerticalLayout;
class RoundButton;
} // namespace Ui
namespace Export { namespace Export {
namespace View { namespace View {
@ -19,15 +24,21 @@ public:
QWidget *parent, QWidget *parent,
rpl::producer<Content> content); rpl::producer<Content> content);
rpl::producer<> cancelClicks() const;
~ProgressWidget(); ~ProgressWidget();
private: private:
void initFooter();
void updateState(Content &&content); void updateState(Content &&content);
Content _content; Content _content;
class Row; class Row;
std::vector<base::unique_qptr<Row>> _rows; object_ptr<Ui::VerticalLayout> _body;
std::vector<not_null<Row*>> _rows;
QPointer<Ui::RoundButton> _cancel;
}; };