diff --git a/Telegram/SourceFiles/boxes/autolock_box.cpp b/Telegram/SourceFiles/boxes/autolock_box.cpp index eb268d77e..eb5d17b83 100644 --- a/Telegram/SourceFiles/boxes/autolock_box.cpp +++ b/Telegram/SourceFiles/boxes/autolock_box.cpp @@ -21,17 +21,17 @@ void AutoLockBox::prepare() { auto options = { 60, 300, 3600, 18000 }; auto group = std::make_shared(Global::AutoLock()); - auto y = st::boxOptionListPadding.top() + st::langsButton.margin.top(); + auto y = st::boxOptionListPadding.top() + st::autolockButton.margin.top(); auto count = int(options.size()); _options.reserve(count); for (auto seconds : options) { - _options.emplace_back(this, group, seconds, (seconds % 3600) ? lng_passcode_autolock_minutes(lt_count, seconds / 60) : lng_passcode_autolock_hours(lt_count, seconds / 3600), st::langsButton); + _options.emplace_back(this, group, seconds, (seconds % 3600) ? lng_passcode_autolock_minutes(lt_count, seconds / 60) : lng_passcode_autolock_hours(lt_count, seconds / 3600), st::autolockButton); _options.back()->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y); y += _options.back()->heightNoMargins() + st::boxOptionListSkip; } group->setChangedCallback([this](int value) { durationChanged(value); }); - setDimensions(st::langsWidth, st::boxOptionListPadding.top() + count * _options.back()->heightNoMargins() + (count - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom()); + setDimensions(st::autolockWidth, st::boxOptionListPadding.top() + count * _options.back()->heightNoMargins() + (count - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom()); } void AutoLockBox::durationChanged(int seconds) { diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 828d7abdc..9293400c8 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -556,11 +556,15 @@ connectionPasswordInputField: InputField(defaultInputField) { } connectionIPv6Skip: 11px; -langsWidth: 256px; -langsButton: Checkbox(defaultBoxCheckbox) { +autolockWidth: 256px; +autolockButton: Checkbox(defaultBoxCheckbox) { width: 200px; } +langsRadio: Radio(defaultRadio) { + bg: boxBg; +} + backgroundPadding: 10px; backgroundSize: size(108px, 193px); backgroundScroll: ScrollArea(boxLayerScroll) { diff --git a/Telegram/SourceFiles/boxes/language_box.cpp b/Telegram/SourceFiles/boxes/language_box.cpp index df3444e54..2e7ada28d 100644 --- a/Telegram/SourceFiles/boxes/language_box.cpp +++ b/Telegram/SourceFiles/boxes/language_box.cpp @@ -13,10 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/labels.h" #include "ui/widgets/multi_select.h" #include "ui/widgets/scroll_area.h" +#include "ui/widgets/dropdown_menu.h" #include "ui/text/text_entity.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/effects/ripple_animation.h" +#include "ui/toast/toast.h" #include "ui/text_options.h" #include "storage/localstorage.h" #include "boxes/confirm_box.h" @@ -26,7 +28,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_instance.h" #include "lang/lang_cloud_manager.h" #include "styles/style_boxes.h" +#include "styles/style_info.h" #include "styles/style_passport.h" +#include "styles/style_chat_helpers.h" namespace { @@ -35,20 +39,26 @@ using Languages = Lang::CloudManager::Languages; class Rows : public Ui::RpWidget { public: - Rows(QWidget *parent, const Languages &data, const QString &chosen); + Rows( + QWidget *parent, + const Languages &data, + const QString &chosen, + bool areOfficial); void filter(const QString &query); int count() const; int selected() const; void setSelected(int selected); - rpl::producer selections() const; + rpl::producer hasSelection() const; void activateSelected(); rpl::producer activations() const; Ui::ScrollToRequest rowScrollRequest(int index) const; + static int DefaultRowHeight(); + protected: int resizeGetHeight(int newWidth) override; @@ -66,33 +76,84 @@ private: int top = 0; int height = 0; mutable std::unique_ptr ripple; + mutable std::unique_ptr menuToggleRipple; + bool menuToggleForceRippled = false; int titleHeight = 0; int descriptionHeight = 0; QStringList keywords; + std::unique_ptr check; + bool removed = false; }; + struct RowSelection { + int index = 0; - void updateSelected(int selected); - void updatePressed(int pressed); + inline bool operator==(const RowSelection &other) const { + return (index == other.index); + } + }; + struct MenuSelection { + int index = 0; + + inline bool operator==(const MenuSelection &other) const { + return (index == other.index); + } + }; + using Selection = base::optional_variant; + + void updateSelected(Selection selected); + void updatePressed(Selection pressed); Rows::Row &rowByIndex(int index); const Rows::Row &rowByIndex(int index) const; + Rows::Row &rowBySelection(Selection selected); + const Rows::Row &rowBySelection(Selection selected) const; + std::unique_ptr &rippleBySelection( + Selection selected); + const std::unique_ptr &rippleBySelection( + Selection selected) const; + std::unique_ptr &rippleBySelection( + not_null row, + Selection selected); + const std::unique_ptr &rippleBySelection( + not_null row, + Selection selected) const; + void addRipple(Selection selected, QPoint position); + void ensureRippleBySelection(Selection selected); + void ensureRippleBySelection(not_null row, Selection selected); + int indexFromSelection(Selection selected) const; int countAvailableWidth() const; int countAvailableWidth(int newWidth) const; + QRect menuToggleArea() const; + QRect menuToggleArea(not_null row) const; + void repaint(Selection selected); void repaint(int index); void repaint(const Row &row); - void repaintChecked(not_null row); + void repaintChecked(not_null row); void activateByIndex(int index); + void showMenu(int index); + void setForceRippled(not_null row, bool rippled); + bool canShare(not_null row) const; + bool canRemove(not_null row) const; + bool hasMenu(not_null row) const; + void share(not_null row) const; + void remove(not_null row); + void restore(not_null row); + std::vector _rows; std::vector> _filtered; - int _selected = -1; - int _pressed = -1; + Selection _selected; + Selection _pressed; QString _chosen; QStringList _query; + bool _areOfficial = false; bool _mouseSelection = false; QPoint _globalMousePosition; + base::unique_qptr _menu; + int _menuShownIndex = -1; + bool _menuOtherEntered = false; - rpl::event_stream _selections; + rpl::event_stream _hasSelection; rpl::event_stream _activations; }; @@ -121,9 +182,14 @@ private: }; -Rows::Rows(QWidget *parent, const Languages &data, const QString &chosen) +Rows::Rows( + QWidget *parent, + const Languages &data, + const QString &chosen, + bool areOfficial) : RpWidget(parent) -, _chosen(chosen) { +, _chosen(chosen) +, _areOfficial(areOfficial) { const auto descriptionOptions = TextParseOptions{ TextParseMultiline, 0, @@ -134,6 +200,10 @@ Rows::Rows(QWidget *parent, const Languages &data, const QString &chosen) for (const auto &item : data) { _rows.push_back(Row{ item }); auto &row = _rows.back(); + row.check = std::make_unique( + st::langsRadio, + (row.data.id == _chosen), + [=, row = &row] { repaint(*row); }); row.title.setText( st::semiboldTextStyle, item.nativeName, @@ -152,6 +222,22 @@ Rows::Rows(QWidget *parent, const Languages &data, const QString &chosen) void Rows::mouseMoveEvent(QMouseEvent *e) { const auto position = e->globalPos(); + if (_menu) { + const auto rect = (_menuShownIndex >= 0) + ? menuToggleArea(&rowByIndex(_menuShownIndex)) + : QRect(); + if (rect.contains(e->pos())) { + if (!_menuOtherEntered) { + _menuOtherEntered = true; + _menu->otherEnter(); + } + } else { + if (_menuOtherEntered) { + _menuOtherEntered = false; + _menu->otherLeave(); + } + } + } if (!_mouseSelection && position == _globalMousePosition) { return; } @@ -170,46 +256,238 @@ void Rows::mouseMoveEvent(QMouseEvent *e) { } return -1; }(); - updateSelected(index); + const auto row = (index >= 0) ? &rowByIndex(index) : nullptr; + const auto inMenuToggle = (index >= 0 && hasMenu(row)) + ? menuToggleArea(row).contains(e->pos()) + : false; + if (index < 0) { + updateSelected({}); + } else if (inMenuToggle) { + updateSelected(MenuSelection{ index }); + } else if (!row->removed) { + updateSelected(RowSelection{ index }); + } else { + updateSelected({}); + } } void Rows::mousePressEvent(QMouseEvent *e) { updatePressed(_selected); - if (_pressed >= 0) { - auto &row = rowByIndex(_pressed); - if (!row.ripple) { - auto mask = Ui::RippleAnimation::rectMask({ - width(), - row.height - }); - row.ripple = std::make_unique( - st::defaultRippleAnimation, - std::move(mask), - [=, row = &row] { repaintChecked(row); }); - } - row.ripple->add(e->pos() - QPoint(0, row.top)); + if (_pressed.has_value()) { + addRipple(_pressed, e->pos()); } } +QRect Rows::menuToggleArea() const { + const auto size = st::topBarSearch.width; + const auto top = (DefaultRowHeight() - size) / 2; + const auto skip = st::boxLayerScroll.width + - st::boxLayerScroll.deltax + + top; + const auto left = width() - skip - size; + return QRect(left, top, size, size); +} + +QRect Rows::menuToggleArea(not_null row) const { + return menuToggleArea().translated(0, row->top); +} + +void Rows::addRipple(Selection selected, QPoint position) { + Expects(selected.has_value()); + + ensureRippleBySelection(selected); + + const auto menu = selected.is(); + const auto &row = rowBySelection(selected); + const auto menuArea = menuToggleArea(&row); + auto &ripple = rippleBySelection(&row, selected); + const auto topleft = menu ? menuArea.topLeft() : QPoint(0, row.top); + ripple->add(position - topleft); +} + +void Rows::ensureRippleBySelection(Selection selected) { + ensureRippleBySelection(&rowBySelection(selected), selected); +} + +void Rows::ensureRippleBySelection(not_null row, Selection selected) { + auto &ripple = rippleBySelection(row, _pressed); + if (ripple) { + return; + } + const auto menu = selected.is(); + const auto menuArea = menuToggleArea(row); + auto mask = menu + ? Ui::RippleAnimation::ellipseMask(menuArea.size()) + : Ui::RippleAnimation::rectMask({ width(), row->height }); + ripple = std::make_unique( + st::defaultRippleAnimation, + std::move(mask), + [=] { repaintChecked(row); }); +} + void Rows::mouseReleaseEvent(QMouseEvent *e) { const auto pressed = _pressed; - updatePressed(-1); - if (pressed == _selected && pressed >= 0) { - activateByIndex(_selected); + updatePressed({}); + if (pressed == _selected) { + pressed.match([&](RowSelection data) { + activateByIndex(data.index); + }, [&](MenuSelection data) { + showMenu(data.index); + }, [](std::nullopt_t) {}); } } +bool Rows::canShare(not_null row) const { + return !_areOfficial && !row->data.id.startsWith('#'); +} + +bool Rows::canRemove(not_null row) const { + return !_areOfficial && !row->check->checked(); +} + +bool Rows::hasMenu(not_null row) const { + return canShare(row) || canRemove(row); +} + +void Rows::share(not_null row) const { + const auto link = qsl("https://t.me/setlanguage/") + row->data.id; + QApplication::clipboard()->setText(link); + Ui::Toast::Show(lang(lng_username_copied)); +} + +void Rows::remove(not_null row) { + row->removed = true; + Local::removeRecentLanguage(row->data.id); +} + +void Rows::restore(not_null row) { + row->removed = false; + Local::saveRecentLanguages(ranges::view::all( + _rows + ) | ranges::view::filter([](const Row &row) { + return !row.removed; + }) | ranges::view::transform([](const Row &row) { + return row.data; + }) | ranges::to_vector); +} + +void Rows::showMenu(int index) { + const auto row = &rowByIndex(index); + if (_menu || !hasMenu(row)) { + return; + } + _menu = base::make_unique_q(window()); + const auto weak = _menu.get(); + _menu->setHiddenCallback([=] { + weak->deleteLater(); + if (_menu == weak) { + setForceRippled(row, false); + _menuShownIndex = -1; + } + }); + _menu->setShowStartCallback([=] { + if (_menu == weak) { + setForceRippled(row, true); + _menuShownIndex = index; + } + }); + _menu->setHideStartCallback([=] { + if (_menu == weak) { + setForceRippled(row, false); + _menuShownIndex = -1; + } + }); + const auto addAction = [&]( + const QString &text, + Fn callback) { + return _menu->addAction(text, std::move(callback)); + }; + const auto id = row->data.id; + if (canShare(row)) { + addAction(lang(lng_proxy_edit_share), [=] { share(row); }); + } + if (canRemove(row)) { + if (row->removed) { + addAction(lang(lng_proxy_menu_restore), [=] { + restore(row); + }); + } else { + addAction(lang(lng_proxy_menu_delete), [=] { + remove(row); + }); + } + } + const auto toggle = menuToggleArea(row); + const auto parentTopLeft = window()->mapToGlobal({ 0, 0 }); + const auto buttonTopLeft = mapToGlobal(toggle.topLeft()); + const auto parent = QRect(parentTopLeft, window()->size()); + const auto button = QRect(buttonTopLeft, toggle.size()); + const auto bottom = button.y() + + st::proxyDropdownDownPosition.y() + + _menu->height() + - parent.y(); + const auto top = button.y() + + st::proxyDropdownUpPosition.y() + - _menu->height() + - parent.y(); + _menuShownIndex = index; + _menuOtherEntered = true; + if (bottom > parent.height() && top >= 0) { + const auto left = button.x() + + button.width() + + st::proxyDropdownUpPosition.x() + - _menu->width() + - parent.x(); + _menu->move(left, top); + _menu->showAnimated(Ui::PanelAnimation::Origin::BottomRight); + } else { + const auto left = button.x() + + button.width() + + st::proxyDropdownDownPosition.x() + - _menu->width() + - parent.x(); + _menu->move(left, bottom - _menu->height()); + _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); + } +} + +void Rows::setForceRippled(not_null row, bool rippled) { + if (row->menuToggleForceRippled != rippled) { + row->menuToggleForceRippled = rippled; + auto &ripple = rippleBySelection(row, MenuSelection{}); + if (row->menuToggleForceRippled) { + ensureRippleBySelection(row, MenuSelection{}); + if (ripple->empty()) { + ripple->addFading(); + } else { + ripple->lastUnstop(); + } + } else { + if (ripple) { + ripple->lastStop(); + } + } + } + repaint(*row); +} + void Rows::activateByIndex(int index) { _activations.fire_copy(rowByIndex(index).data); } void Rows::leaveEventHook(QEvent *e) { - updateSelected(-1); + updateSelected({}); + if (_menu && _menuOtherEntered) { + _menuOtherEntered = false; + _menu->otherLeave(); + } } void Rows::filter(const QString &query) { - updateSelected(-1); - updatePressed(-1); + updateSelected({}); + updatePressed({}); + _menu = nullptr; + _menuShownIndex = -1; _query = TextUtilities::PrepareSearchWords(query); @@ -254,9 +532,18 @@ int Rows::count() const { return _query.isEmpty() ? _rows.size() : _filtered.size(); } +int Rows::indexFromSelection(Selection selected) const { + return selected.match([&](RowSelection data) { + return data.index; + }, [&](MenuSelection data) { + return data.index; + }, [](std::nullopt_t) { + return -1; + }); +} + int Rows::selected() const { - const auto limit = count(); - return (_selected >= 0 && _selected < limit) ? _selected : -1; + return indexFromSelection(_selected); } void Rows::activateSelected() { @@ -273,11 +560,22 @@ rpl::producer Rows::activations() const { void Rows::setSelected(int selected) { _mouseSelection = false; const auto limit = count(); - updateSelected((selected >= 0 && selected < limit) ? selected : -1); + if (selected >= 0 && selected < limit) { + updateSelected(RowSelection{ selected }); + } else { + updateSelected({}); + } } -rpl::producer Rows::selections() const { - return _selections.events(); +rpl::producer Rows::hasSelection() const { + return _hasSelection.events(); +} + +void Rows::repaint(Selection selected) { + selected.match([](std::nullopt_t) { + }, [&](const auto &data) { + repaint(data.index); + }); } void Rows::repaint(int index) { @@ -290,23 +588,26 @@ void Rows::repaint(const Row &row) { update(0, row.top, width(), row.height); } -void Rows::repaintChecked(not_null row) { +void Rows::repaintChecked(not_null row) { const auto found = (ranges::find(_filtered, row) != end(_filtered)); if (_query.isEmpty() || found) { repaint(*row); } } -void Rows::updateSelected(int selected) { +void Rows::updateSelected(Selection selected) { + const auto changed = (_selected.has_value() != selected.has_value()); repaint(_selected); _selected = selected; repaint(_selected); - _selections.fire_copy(_selected); + if (changed) { + _hasSelection.fire(_selected.has_value()); + } } -void Rows::updatePressed(int pressed) { - if (_pressed >= 0) { - if (const auto ripple = rowByIndex(_pressed).ripple.get()) { +void Rows::updatePressed(Selection pressed) { + if (_pressed.has_value()) { + if (const auto ripple = rippleBySelection(_pressed).get()) { ripple->lastStop(); } } @@ -325,11 +626,53 @@ const Rows::Row &Rows::rowByIndex(int index) const { return _query.isEmpty() ? _rows[index] : *_filtered[index]; } +Rows::Row &Rows::rowBySelection(Selection selected) { + return rowByIndex(indexFromSelection(selected)); +} + +const Rows::Row &Rows::rowBySelection(Selection selected) const { + return rowByIndex(indexFromSelection(selected)); +} + +std::unique_ptr &Rows::rippleBySelection( + Selection selected) { + return rippleBySelection(&rowBySelection(selected), selected); +} + +const std::unique_ptr &Rows::rippleBySelection( + Selection selected) const { + return rippleBySelection(&rowBySelection(selected), selected); +} + +std::unique_ptr &Rows::rippleBySelection( + not_null row, + Selection selected) { + return selected.is() + ? row->menuToggleRipple + : row->ripple; +} + +const std::unique_ptr &Rows::rippleBySelection( + not_null row, + Selection selected) const { + return const_cast(this)->rippleBySelection( + const_cast(row.get()), + selected); +} + Ui::ScrollToRequest Rows::rowScrollRequest(int index) const { const auto &row = rowByIndex(index); return Ui::ScrollToRequest(row.top, row.top + row.height); } +int Rows::DefaultRowHeight() { + return st::passportRowPadding.top() + + st::semiboldFont->height + + st::passportRowSkip + + st::normalFont->height + + st::passportRowPadding.bottom(); +} + int Rows::resizeGetHeight(int newWidth) { const auto availableWidth = countAvailableWidth(newWidth); auto result = 0; @@ -349,10 +692,12 @@ int Rows::resizeGetHeight(int newWidth) { } int Rows::countAvailableWidth(int newWidth) const { + const auto right = width() - menuToggleArea().x(); return newWidth - st::passportRowPadding.left() - - st::passportRowPadding.right() - - st::passportRowReadyIcon.width() + - st::langsRadio.diameter + - st::passportRowPadding.left() + - right - st::passportRowIconSkip; } @@ -366,8 +711,15 @@ void Rows::paintEvent(QPaintEvent *e) { const auto ms = getms(); const auto clip = e->rect(); - const auto left = st::passportRowPadding.left(); + const auto checkLeft = st::passportRowPadding.left(); + const auto left = checkLeft + + st::langsRadio.diameter + + st::passportRowPadding.left(); const auto availableWidth = countAvailableWidth(); + const auto menu = menuToggleArea(); + const auto selectedIndex = (_menuShownIndex >= 0) + ? _menuShownIndex + : indexFromSelection(_pressed.has_value() ? _pressed : _selected); for (auto i = 0, till = count(); i != till; ++i) { const auto &row = rowByIndex(i); if (row.top + row.height <= clip.y()) { @@ -375,11 +727,12 @@ void Rows::paintEvent(QPaintEvent *e) { } else if (row.top >= clip.y() + clip.height()) { break; } + p.setOpacity(row.removed ? st::stickersRowDisabledOpacity : 1.); p.translate(0, row.top); const auto guard = gsl::finally([&] { p.translate(0, -row.top); }); - const auto selected = (_selected == i); - if (selected) { + const auto selected = (selectedIndex == i); + if (selected && !row.removed) { p.fillRect(0, 0, width(), row.height, st::windowBgOver); } @@ -390,6 +743,9 @@ void Rows::paintEvent(QPaintEvent *e) { } } + const auto checkTop = (row.height - st::defaultRadio.diameter) / 2; + row.check->paint(p, checkLeft, checkTop, width(), ms); + auto top = st::passportRowPadding.top(); p.setPen(st::passportRowTitleFg); @@ -400,13 +756,23 @@ void Rows::paintEvent(QPaintEvent *e) { row.description.drawLeft(p, left, top, availableWidth, width()); top += row.descriptionHeight + st::passportRowPadding.bottom(); - if (row.data.id == _chosen) { - const auto &icon = st::passportRowReadyIcon; - icon.paint( - p, - width() - st::passportRowPadding.right() - icon.width(), - (row.height - icon.height()) / 2, - width()); + if (hasMenu(&row)) { + p.setOpacity(1.); + if (selected && row.removed) { + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::windowBgOver); + p.drawEllipse(menu); + } + if (row.menuToggleRipple) { + row.menuToggleRipple->paint(p, menu.x(), menu.y(), width(), ms); + if (row.menuToggleRipple->empty()) { + row.menuToggleRipple.reset(); + } + } + (selected + ? st::topBarMenuToggle.iconOver + : st::topBarMenuToggle.icon).paintInCenter(p, menu); } } } @@ -435,11 +801,12 @@ void Content::setupContent( const auto main = container->add(object_ptr( container, official, - current)); + current, + true)); container->add(object_ptr( container, st::boxVerticalMargin)); - const auto additional = !unofficial.isEmpty() + const auto additional = !unofficial.empty() ? content->add(object_ptr>( content, object_ptr(content))) @@ -459,7 +826,7 @@ void Content::setupContent( st::passportFormHeaderPadding) : nullptr; const auto other = inner - ? inner->add(object_ptr(inner, unofficial, current)) + ? inner->add(object_ptr(inner, unofficial, current, false)) : nullptr; if (inner) { inner->add(object_ptr( @@ -493,9 +860,9 @@ void Content::setupContent( }, divider->lifetime()); const auto excludeSelections = [](Rows *a, Rows *b) { - a->selections( + a->hasSelection( ) | rpl::filter( - _1 >= 0 + _1 ) | rpl::start_with_next([=] { b->setSelected(-1); }, a->lifetime()); @@ -616,8 +983,8 @@ void LanguageBox::prepare() { const auto current = Lang::LanguageIdOrDefault(Lang::Current().id()); auto official = Lang::CurrentCloudManager().languageList(); - if (official.isEmpty()) { - official.push_back({ "en", "English", "English" }); + if (official.empty()) { + official.push_back({ "en", {}, {}, "English", "English" }); } ranges::stable_partition(official, [&](const Language &language) { return (language.id == current); @@ -635,12 +1002,12 @@ void LanguageBox::prepare() { ranges::remove_if( unofficial, foundInOfficial), - unofficial.end()); + end(unofficial)); ranges::stable_partition(unofficial, [&](const Language &language) { return (language.id == current); }); if (official.front().id != current - && (unofficial.isEmpty() || unofficial.front().id != current)) { + && (unofficial.empty() || unofficial.front().id != current)) { const auto name = (current == "#custom") ? "Custom lang pack" : lang(lng_language_name); @@ -659,7 +1026,7 @@ void LanguageBox::prepare() { object_ptr(this, official, unofficial), st::boxLayerScroll, select->height()); - inner->resizeToWidth(st::langsWidth); + inner->resizeToWidth(st::boxWidth); const auto max = lifetime().make_state(0); rpl::combine( @@ -668,7 +1035,7 @@ void LanguageBox::prepare() { _1 + _2 ) | rpl::start_with_next([=](int height) { accumulate_max(*max, height); - setDimensions(st::langsWidth, qMin(*max, st::boxMaxListHeight)); + setDimensions(st::boxWidth, qMin(*max, st::boxMaxListHeight)); }, inner->lifetime()); select->setSubmittedCallback([=](Qt::KeyboardModifiers) { @@ -721,12 +1088,7 @@ void LanguageBox::keyPressEvent(QKeyEvent *e) { } int LanguageBox::rowsInPage() const { - const auto rowHeight = st::passportRowPadding.top() - + st::semiboldFont->height - + st::passportRowSkip - + st::normalFont->height - + st::passportRowPadding.bottom(); - return std::max(height() / rowHeight, 1); + return std::max(height() / Rows::DefaultRowHeight(), 1); } void LanguageBox::setInnerFocus() { @@ -738,7 +1100,7 @@ not_null LanguageBox::createMultiSelect() { this, st::contactsMultiSelect, langFactory(lng_participant_filter)); - result->resizeToWidth(st::langsWidth); + result->resizeToWidth(st::boxWidth); result->moveToLeft(0, 0); return result; } @@ -747,7 +1109,7 @@ base::binary_guard LanguageBox::Show() { auto result = base::binary_guard(); const auto manager = Messenger::Instance().langCloudManager(); - if (manager->languageList().isEmpty()) { + if (manager->languageList().empty()) { auto guard = std::make_shared(); std::tie(result, *guard) = base::make_binary_guard(); auto alive = std::make_shared>( diff --git a/Telegram/SourceFiles/boxes/self_destruction_box.cpp b/Telegram/SourceFiles/boxes/self_destruction_box.cpp index 128fe05e2..628c15902 100644 --- a/Telegram/SourceFiles/boxes/self_destruction_box.cpp +++ b/Telegram/SourceFiles/boxes/self_destruction_box.cpp @@ -67,7 +67,7 @@ void SelfDestructionBox::showContent() { _ttlGroup, value, DaysLabel(value), - st::langsButton); + st::autolockButton); button->moveToLeft(st::boxPadding.left(), y); y += button->heightNoMargins() + st::boxOptionListSkip; } diff --git a/Telegram/SourceFiles/lang/lang_cloud_manager.h b/Telegram/SourceFiles/lang/lang_cloud_manager.h index 6bfb85fb2..f2c2b1cc3 100644 --- a/Telegram/SourceFiles/lang/lang_cloud_manager.h +++ b/Telegram/SourceFiles/lang/lang_cloud_manager.h @@ -32,10 +32,10 @@ class CloudManager : public base::has_weak_ptr, private MTP::Sender, private bas public: CloudManager(Instance &langpack, not_null mtproto); - using Languages = QVector; + using Languages = std::vector; void requestLanguageList(); - Languages languageList() const { + const Languages &languageList() const { return _languages; } base::Observable &languageListChanged() { diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 9753746ca..6e5d68d6b 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -4131,17 +4131,15 @@ void writeLangPack() { file.writeEncrypted(data, SettingsKey); } -void pushRecentLanguage(const Lang::Language &language) { - if (language.id.startsWith('#')) { +void saveRecentLanguages(const std::vector &list) { + if (list.empty()) { + if (_languagesKey) { + clearKey(_languagesKey, FileOption::Safe); + _languagesKey = 0; + writeSettings(); + } return; } - auto list = readRecentLanguages(); - list.erase( - ranges::remove_if( - list, - [&](const Lang::Language &v) { return (v.id == language.id); }), - list.end()); - list.insert(list.begin(), language); auto size = sizeof(qint32); for (const auto &language : list) { @@ -4171,7 +4169,33 @@ void pushRecentLanguage(const Lang::Language &language) { file.writeEncrypted(data, SettingsKey); } -QVector readRecentLanguages() { +void pushRecentLanguage(const Lang::Language &language) { + if (language.id.startsWith('#')) { + return; + } + auto list = readRecentLanguages(); + list.erase( + ranges::remove_if( + list, + [&](const Lang::Language &v) { return (v.id == language.id); }), + end(list)); + list.insert(list.begin(), language); + + saveRecentLanguages(list); +} + +void removeRecentLanguage(const QString &id) { + auto list = readRecentLanguages(); + list.erase( + ranges::remove_if( + list, + [&](const Lang::Language &v) { return (v.id == id); }), + end(list)); + + saveRecentLanguages(list); +} + +std::vector readRecentLanguages() { FileReadDescriptor languages; if (!_languagesKey || !readEncryptedFile(languages, _languagesKey, FileOption::Safe, SettingsKey)) { return {}; @@ -4181,7 +4205,7 @@ QVector readRecentLanguages() { if (count <= 0) { return {}; } - auto result = QVector(); + auto result = std::vector(); result.reserve(count); for (auto i = 0; i != count; ++i) { auto language = Lang::Language(); diff --git a/Telegram/SourceFiles/storage/localstorage.h b/Telegram/SourceFiles/storage/localstorage.h index 77a2a5039..ef56b217b 100644 --- a/Telegram/SourceFiles/storage/localstorage.h +++ b/Telegram/SourceFiles/storage/localstorage.h @@ -146,7 +146,9 @@ Window::Theme::Saved readThemeAfterSwitch(); void writeLangPack(); void pushRecentLanguage(const Lang::Language &language); -QVector readRecentLanguages(); +std::vector readRecentLanguages(); +void saveRecentLanguages(const std::vector &list); +void removeRecentLanguage(const QString &id); void writeRecentHashtagsAndBots(); void readRecentHashtagsAndBots(); diff --git a/Telegram/SourceFiles/window/themes/window_theme.cpp b/Telegram/SourceFiles/window/themes/window_theme.cpp index db89b61fe..047e94d29 100644 --- a/Telegram/SourceFiles/window/themes/window_theme.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme.cpp @@ -22,6 +22,7 @@ namespace { constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024; constexpr auto kThemeBackgroundSizeLimit = 4 * 1024 * 1024; +constexpr auto kBackgroundSizeLimit = 25 * 1024 * 1024; constexpr auto kThemeSchemeSizeLimit = 1024 * 1024; constexpr auto kMinimumTiledSize = 512; constexpr auto kNightThemeFile = str_const(":/gui/night.tdesktop-theme"); @@ -291,12 +292,20 @@ bool loadTheme(const QByteArray &content, Cached &cache, Instance *out = nullptr } if (!backgroundContent.isEmpty()) { + auto check = QBuffer(&backgroundContent); + auto reader = QImageReader(&check); + const auto size = reader.size(); + if (size.isEmpty() + || (size.width() * size.height() > kBackgroundSizeLimit)) { + LOG(("Theme Error: bad background image size in the theme file.")); + return false; + } auto background = App::readImage(backgroundContent); if (background.isNull()) { LOG(("Theme Error: could not read background image in the theme file.")); return false; } - QBuffer buffer(&cache.background); + auto buffer = QBuffer(&cache.background); if (!background.save(&buffer, "BMP")) { LOG(("Theme Error: could not write background image as a BMP to cache.")); return false;