diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 92abe09038..04b63f52ea 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1285,6 +1285,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_unpin_from_top" = "Unpin from top"; "lng_context_mark_unread" = "Mark as unread"; "lng_context_mark_read" = "Mark as read"; +"lng_context_archive_expand" = "Expand"; +"lng_context_archive_collapse" = "Collapse"; "lng_context_promote_admin" = "Promote to admin"; "lng_context_edit_permissions" = "Edit permissions"; diff --git a/Telegram/SourceFiles/auth_session.cpp b/Telegram/SourceFiles/auth_session.cpp index facbb7be2c..9da0b9efc5 100644 --- a/Telegram/SourceFiles/auth_session.cpp +++ b/Telegram/SourceFiles/auth_session.cpp @@ -93,6 +93,7 @@ QByteArray AuthSessionSettings::serialize() const { stream << qint32(_variables.exeLaunchWarning ? 1 : 0); stream << autoDownload; stream << qint32(_variables.supportAllSearchResults.current() ? 1 : 0); + stream << qint32(_variables.archiveCollapsed.current() ? 1 : 0); } return result; } @@ -129,6 +130,7 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) qint32 exeLaunchWarning = _variables.exeLaunchWarning ? 1 : 0; QByteArray autoDownload; qint32 supportAllSearchResults = _variables.supportAllSearchResults.current() ? 1 : 0; + qint32 archiveCollapsed = _variables.archiveCollapsed.current() ? 1 : 0; stream >> selectorTab; stream >> lastSeenWarningSeen; @@ -208,6 +210,9 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) if (!stream.atEnd()) { stream >> supportAllSearchResults; } + if (!stream.atEnd()) { + stream >> archiveCollapsed; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for AuthSessionSettings::constructFromSerialized()")); @@ -277,6 +282,7 @@ void AuthSessionSettings::constructFromSerialized(const QByteArray &serialized) _variables.countUnreadMessages = (countUnreadMessages == 1); _variables.exeLaunchWarning = (exeLaunchWarning == 1); _variables.supportAllSearchResults = (supportAllSearchResults == 1); + _variables.archiveCollapsed = (archiveCollapsed == 1); } void AuthSessionSettings::setSupportChatsTimeSlice(int slice) { @@ -371,8 +377,20 @@ rpl::producer AuthSessionSettings::thirdColumnWidthChanges() const { return _variables.thirdColumnWidth.changes(); } +void AuthSessionSettings::setArchiveCollapsed(bool collapsed) { + _variables.archiveCollapsed = collapsed; +} + +bool AuthSessionSettings::archiveCollapsed() const { + return _variables.archiveCollapsed.current(); +} + +rpl::producer AuthSessionSettings::archiveCollapsedChanges() const { + return _variables.archiveCollapsed.changes(); +} + AuthSession &Auth() { - auto result = Core::App().authSession(); + const auto result = Core::App().authSession(); Assert(result != nullptr); return *result; } diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h index 2a589f8ecc..4e7a5eab92 100644 --- a/Telegram/SourceFiles/auth_session.h +++ b/Telegram/SourceFiles/auth_session.h @@ -188,6 +188,10 @@ public: return _variables.autoDownload; } + void setArchiveCollapsed(bool collapsed); + bool archiveCollapsed() const; + rpl::producer archiveCollapsedChanges() const; + bool hadLegacyCallsPeerToPeerNobody() const { return _variables.hadLegacyCallsPeerToPeerNobody; } @@ -240,6 +244,7 @@ private: bool countUnreadMessages = true; bool exeLaunchWarning = true; Data::AutoDownload::Full autoDownload; + rpl::variable archiveCollapsed = false; static constexpr auto kDefaultSupportChatsLimitSlice = 7 * 24 * 60 * 60; diff --git a/Telegram/SourceFiles/data/data_folder.cpp b/Telegram/SourceFiles/data/data_folder.cpp index 22dcb98ca7..a5c6a68aa1 100644 --- a/Telegram/SourceFiles/data/data_folder.cpp +++ b/Telegram/SourceFiles/data/data_folder.cpp @@ -75,29 +75,30 @@ FolderId Folder::id() const { } void Folder::indexNameParts() { - _nameWords.clear(); - _nameFirstLetters.clear(); - auto toIndexList = QStringList(); - auto appendToIndex = [&](const QString &value) { - if (!value.isEmpty()) { - toIndexList.push_back(TextUtilities::RemoveAccents(value)); - } - }; + // We don't want archive to be filtered in the chats list. + //_nameWords.clear(); + //_nameFirstLetters.clear(); + //auto toIndexList = QStringList(); + //auto appendToIndex = [&](const QString &value) { + // if (!value.isEmpty()) { + // toIndexList.push_back(TextUtilities::RemoveAccents(value)); + // } + //}; - appendToIndex(_name); - const auto appendTranslit = !toIndexList.isEmpty() - && cRussianLetters().match(toIndexList.front()).hasMatch(); - if (appendTranslit) { - appendToIndex(translitRusEng(toIndexList.front())); - } - auto toIndex = toIndexList.join(' '); - toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); + //appendToIndex(_name); + //const auto appendTranslit = !toIndexList.isEmpty() + // && cRussianLetters().match(toIndexList.front()).hasMatch(); + //if (appendTranslit) { + // appendToIndex(translitRusEng(toIndexList.front())); + //} + //auto toIndex = toIndexList.join(' '); + //toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); - const auto namesList = TextUtilities::PrepareSearchWords(toIndex); - for (const auto &name : namesList) { - _nameWords.insert(name); - _nameFirstLetters.insert(name[0]); - } + //const auto namesList = TextUtilities::PrepareSearchWords(toIndex); + //for (const auto &name : namesList) { + // _nameWords.insert(name); + // _nameFirstLetters.insert(name[0]); + //} } void Folder::registerOne(not_null history) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 51bc3028cc..1ec0afd1c4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -74,7 +74,11 @@ int PinnedDialogsCount(not_null list) { } // namespace -struct InnerWidget::ImportantSwitch { +struct InnerWidget::CollapsedRow { + explicit CollapsedRow(Data::Folder *folder = nullptr) : folder(folder) { + } + + Data::Folder *folder = nullptr; RippleRow row; }; @@ -108,10 +112,10 @@ InnerWidget::InnerWidget( setAttribute(Qt::WA_OpaquePaintEvent, true); #endif // OS_MAC_OLD - if (Global::DialogsModeEnabled()) { - _importantSwitch = std::make_unique(); - _mode = Global::DialogsMode(); - } + _mode = Global::DialogsModeEnabled() + ? Global::DialogsMode() + : Dialogs::Mode::All; + connect(_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact())); _cancelSearchInChat->setClickedCallback([=] { cancelSearchInChat(); }); _cancelSearchInChat->hide(); @@ -180,6 +184,11 @@ InnerWidget::InnerWidget( refresh(); }, lifetime()); + session().settings().archiveCollapsedChanges( + ) | rpl::start_with_next([=] { + refreshWithCollapsedRows(); + }, lifetime()); + subscribe(Window::Theme::Background(), [=](const Window::Theme::BackgroundUpdate &data) { if (data.paletteChanged()) { Layout::clearUnreadBadgesCache(); @@ -225,7 +234,8 @@ InnerWidget::InnerWidget( updateDialogRow(previous); updateDialogRow(next); }, lifetime()); - refresh(); + + refreshWithCollapsedRows(true); setupShortcuts(); } @@ -249,17 +259,50 @@ void InnerWidget::handleChatMigration(not_null chat) { } } -bool InnerWidget::importantSwitchShown() const { - return !_openedFolder && _importantSwitch; +void InnerWidget::refreshWithCollapsedRows(bool toTop) { + const auto pressed = _collapsedPressed; + const auto selected = _collapsedSelected; + + setCollapsedPressed(-1); + _collapsedSelected = -1; + + _collapsedRows.clear(); + if (!_openedFolder && Global::DialogsModeEnabled()) { + _collapsedRows.push_back(std::make_unique()); + } + const auto list = shownDialogs(); + const auto archive = !list->empty() + ? (*list->begin())->folder() + : nullptr; + if (archive && session().settings().archiveCollapsed()) { + if (_selected && _selected->folder() == archive) { + _selected = nullptr; + } + if (_pressed && _pressed->folder() == archive) { + setPressed(nullptr); + } + _skipByCollapsedRows = 1; + _collapsedRows.push_back(std::make_unique(archive)); + } else { + _skipByCollapsedRows = 0; + } + + refresh(toTop); + + if (selected >= 0 && selected < _collapsedRows.size()) { + _collapsedSelected = selected; + } + if (pressed >= 0 && pressed < _collapsedRows.size()) { + setCollapsedPressed(pressed); + } } int InnerWidget::dialogsOffset() const { - return importantSwitchShown() - ? st::dialogsImportantBarHeight - : 0; + return _collapsedRows.size() * st::dialogsImportantBarHeight + - _skipByCollapsedRows * st::dialogsRowHeight; } -int InnerWidget::proxyPromotedCount() const { +int InnerWidget::fixedOnTopCount() const { auto result = 0; for (const auto row : *shownDialogs()) { if (row->entry()->fixedOnTopIndex()) { @@ -272,7 +315,7 @@ int InnerWidget::proxyPromotedCount() const { } int InnerWidget::pinnedOffset() const { - return dialogsOffset() + proxyPromotedCount() * st::dialogsRowHeight; + return dialogsOffset() + fixedOnTopCount() * st::dialogsRowHeight; } int InnerWidget::filteredOffset() const { @@ -309,7 +352,7 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) { clearSelection(); _openedFolder = folder; _mode = _openedFolder ? Mode::All : Global::DialogsMode(); - refresh(true); + refreshWithCollapsedRows(true); // This doesn't work, because we clear selection in leaveEvent on hide. //if (mouseSelection && lastMousePosition) { // selectByMouse(*lastMousePosition); @@ -331,14 +374,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) { auto dialogsClip = r; auto ms = crl::now(); if (_state == WidgetState::Default) { - auto rows = shownDialogs(); - if (importantSwitchShown()) { - auto selected = isPressed() ? _importantSwitchPressed : _importantSwitchSelected; - Layout::paintImportantSwitch(p, _mode, fullWidth, selected); - dialogsClip.translate(0, -st::dialogsImportantBarHeight); - p.translate(0, st::dialogsImportantBarHeight); - } - auto otherStart = rows->size() * st::dialogsRowHeight; + paintCollapsedRows(p, r); + + const auto rows = shownDialogs(); + const auto &list = rows->all(); + const auto otherStart = std::max(int(rows->size()) - _skipByCollapsedRows, 0) * st::dialogsRowHeight; const auto active = activeEntry.key; const auto selected = _menuRow.key ? _menuRow.key @@ -350,13 +390,13 @@ void InnerWidget::paintEvent(QPaintEvent *e) { ? _selected->key() : Key())); if (otherStart) { + const auto skip = dialogsOffset(); auto reorderingPinned = (_aboveIndex >= 0 && !_pinnedRows.empty()); - auto &list = rows->all(); if (reorderingPinned) { dialogsClip = dialogsClip.marginsAdded(QMargins(0, st::dialogsRowHeight, 0, st::dialogsRowHeight)); } - const auto promoted = proxyPromotedCount(); + const auto promoted = fixedOnTopCount(); const auto paintDialog = [&](not_null row) { const auto pinned = row->pos() - promoted; const auto count = _pinnedRows.size(); @@ -381,19 +421,22 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } }; - auto i = list.cfind(dialogsClip.top(), st::dialogsRowHeight); + auto i = list.cfind(dialogsClip.top() - skip, st::dialogsRowHeight); + while (i != list.cend() && (*i)->pos() < _skipByCollapsedRows) { + ++i; + } if (i != list.cend()) { auto lastPaintedPos = (*i)->pos(); // If we're reordering pinned chats we need to fill this area background first. if (reorderingPinned) { - p.fillRect(0, promoted * st::dialogsRowHeight, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg); + p.fillRect(0, (promoted - _skipByCollapsedRows) * st::dialogsRowHeight, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg); } - p.translate(0, lastPaintedPos * st::dialogsRowHeight); + p.translate(0, (lastPaintedPos - _skipByCollapsedRows) * st::dialogsRowHeight); for (auto e = list.cend(); i != e; ++i) { auto row = (*i); - if (lastPaintedPos * st::dialogsRowHeight >= dialogsClip.top() + dialogsClip.height()) { + if ((lastPaintedPos - _skipByCollapsedRows) * st::dialogsRowHeight >= dialogsClip.top() - skip + dialogsClip.height()) { break; } @@ -591,6 +634,42 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } } +void InnerWidget::paintCollapsedRows(Painter &p, QRect clip) const { + auto index = 0; + const auto rowHeight = st::dialogsImportantBarHeight; + for (const auto &row : _collapsedRows) { + const auto increment = gsl::finally([&] { + p.translate(0, rowHeight); + ++index; + }); + + const auto y = index * rowHeight; + if (!clip.intersects(QRect(0, y, width(), rowHeight))) { + continue; + } + const auto selected = (index == _collapsedSelected) + || (index == _collapsedPressed); + paintCollapsedRow(p, row.get(), selected); + } +} + +void InnerWidget::paintCollapsedRow( + Painter &p, + not_null row, + bool selected) const { + const auto text = row->folder + ? row->folder->chatListName() + : (_mode == Dialogs::Mode::Important) + ? lang(lng_dialogs_show_all_chats) + : lang(lng_dialogs_hide_muted_chats); + const auto unread = row->folder + ? row->folder->chatListUnreadCount() + : (_mode == Dialogs::Mode::Important) + ? session().data().unreadOnlyMutedBadge() + : 0; + Layout::PaintCollapsedRow(p, row->row, text, unread, width(), selected); +} + bool InnerWidget::isSearchResultActive( not_null result, const RowDescriptor &entry) const { @@ -791,8 +870,8 @@ void InnerWidget::clearIrrelevantState() { _searchedSelected = -1; setSearchedPressed(-1); } else if (_state == WidgetState::Filtered) { - _importantSwitchSelected = false; - setImportantSwitchPressed(false); + _collapsedSelected = -1; + setCollapsedPressed(-1); _selected = nullptr; setPressed(nullptr); } @@ -810,22 +889,26 @@ void InnerWidget::selectByMouse(QPoint globalPosition) { const auto mouseY = local.y(); clearIrrelevantState(); if (_state == WidgetState::Default) { - const auto switchTop = 0; - const auto switchBottom = dialogsOffset(); - const auto importantSwitchSelected = importantSwitchShown() - && (mouseY >= switchTop) - && (mouseY < switchBottom); - const auto selected = importantSwitchSelected + const auto offset = dialogsOffset(); + const auto collapsedSelected = (mouseY >= 0 + && mouseY < _collapsedRows.size() * st::dialogsImportantBarHeight) + ? (mouseY / st::dialogsImportantBarHeight) + : -1; + const auto selected = (collapsedSelected >= 0) ? nullptr - : (mouseY >= switchBottom) - ? shownDialogs()->rowAtY(mouseY - switchBottom, st::dialogsRowHeight) + : (mouseY >= offset) + ? shownDialogs()->rowAtY( + mouseY - offset, + st::dialogsRowHeight) : nullptr; - if (_selected != selected || _importantSwitchSelected != importantSwitchSelected) { + if (_selected != selected || _collapsedSelected != collapsedSelected) { updateSelectedRow(); _selected = selected; - _importantSwitchSelected = importantSwitchSelected; + _collapsedSelected = collapsedSelected; updateSelectedRow(); - setCursor((_selected || _importantSwitchSelected) ? style::cur_pointer : style::cur_default); + setCursor((_selected || _collapsedSelected) + ? style::cur_pointer + : style::cur_default); } } else if (_state == WidgetState::Filtered) { auto wasSelected = isSelected(); @@ -892,15 +975,16 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) { _pressButton = e->button(); setPressed(_selected); - setImportantSwitchPressed(_importantSwitchSelected); + setCollapsedPressed(_collapsedSelected); setHashtagPressed(_hashtagSelected); _hashtagDeletePressed = _hashtagDeleteSelected; setFilteredPressed(_filteredSelected); setPeerSearchPressed(_peerSearchSelected); setSearchedPressed(_searchedSelected); - if (_importantSwitchPressed) { - _importantSwitch->row.addRipple(e->pos(), QSize(width(), st::dialogsImportantBarHeight), [this] { - update(0, 0, width(), st::dialogsImportantBarHeight); + if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { + auto row = &_collapsedRows[_collapsedSelected]->row; + row->addRipple(e->pos(), QSize(width(), st::dialogsImportantBarHeight), [this, index = _collapsedSelected] { + update(0, (index * st::dialogsImportantBarHeight), width(), st::dialogsImportantBarHeight); }); } else if (_pressed) { auto row = _pressed; @@ -1170,8 +1254,8 @@ void InnerWidget::mousePressReleased( finishReorderPinned(); } - auto importantSwitchPressed = _importantSwitchPressed; - setImportantSwitchPressed(false); + auto collapsedPressed = _collapsedPressed; + setCollapsedPressed(-1); auto pressed = _pressed; setPressed(nullptr); auto hashtagPressed = _hashtagPressed; @@ -1189,28 +1273,27 @@ void InnerWidget::mousePressReleased( } updateSelectedRow(); if (!wasDragging && button == Qt::LeftButton) { - if (importantSwitchPressed && importantSwitchPressed == _importantSwitchSelected) { - chooseRow(); - } else if (pressed && pressed == _selected) { - chooseRow(); - } else if (hashtagPressed >= 0 && hashtagPressed == _hashtagSelected && hashtagDeletePressed == _hashtagDeleteSelected) { - chooseRow(); - } else if (filteredPressed >= 0 && filteredPressed == _filteredSelected) { - chooseRow(); - } else if (peerSearchPressed >= 0 && peerSearchPressed == _peerSearchSelected) { - chooseRow(); - } else if (searchedPressed >= 0 && searchedPressed == _searchedSelected) { + if ((collapsedPressed >= 0 && collapsedPressed == _collapsedSelected) + || (pressed && pressed == _selected) + || (hashtagPressed >= 0 + && hashtagPressed == _hashtagSelected + && hashtagDeletePressed == _hashtagDeleteSelected) + || (filteredPressed >= 0 && filteredPressed == _filteredSelected) + || (peerSearchPressed >= 0 + && peerSearchPressed == _peerSearchSelected) + || (searchedPressed >= 0 + && searchedPressed == _searchedSelected)) { chooseRow(); } } } -void InnerWidget::setImportantSwitchPressed(bool pressed) { - if (_importantSwitchPressed != pressed) { - if (_importantSwitchPressed) { - _importantSwitch->row.stopLastRipple(); +void InnerWidget::setCollapsedPressed(int pressed) { + if (_collapsedPressed != pressed) { + if (_collapsedPressed >= 0) { + _collapsedRows[_collapsedPressed]->row.stopLastRipple(); } - _importantSwitchPressed = pressed; + _collapsedPressed = pressed; } } @@ -1360,11 +1443,23 @@ void InnerWidget::removeDialog(Key key) { refresh(); } +void InnerWidget::repaintCollapsedFolderRow(not_null folder) { + for (auto i = 0, l = int(_collapsedRows.size()); i != l; ++i) { + if (_collapsedRows[i]->folder == folder) { + update(0, i * st::dialogsImportantBarHeight, width(), st::dialogsImportantBarHeight); + return; + } + } +} + void InnerWidget::repaintDialogRow( Mode list, not_null row) { if (_state == WidgetState::Default) { if (_mode == list) { + if (const auto folder = row->folder()) { + repaintCollapsedFolderRow(folder); + } auto position = row->pos(); auto top = dialogsOffset(); if (base::in_range(position, 0, _pinnedRows.size())) { @@ -1435,6 +1530,9 @@ void InnerWidget::updateDialogRow( }; if (_state == WidgetState::Default) { if (sections & UpdateRowSection::Default) { + if (const auto folder = row.key.folder()) { + repaintCollapsedFolderRow(folder); + } if (const auto dialog = shownDialogs()->getRow(row.key)) { const auto position = dialog->pos(); auto top = dialogsOffset(); @@ -1505,8 +1603,8 @@ void InnerWidget::updateSelectedRow(Key key) { update(0, top + position * st::dialogsRowHeight, width(), st::dialogsRowHeight); } else if (_selected) { update(0, dialogsOffset() + _selected->pos() * st::dialogsRowHeight, width(), st::dialogsRowHeight); - } else if (_importantSwitchSelected) { - update(0, 0, width(), st::dialogsImportantBarHeight); + } else if (_collapsedSelected >= 0) { + update(0, _collapsedSelected * st::dialogsImportantBarHeight, width(), st::dialogsImportantBarHeight); } } else if (_state == WidgetState::Filtered) { if (key) { @@ -1545,14 +1643,9 @@ void InnerWidget::dragLeft() { void InnerWidget::clearSelection() { _mouseSelection = false; _lastMousePosition = std::nullopt; - if (_importantSwitchSelected - || _selected - || _filteredSelected >= 0 - || _hashtagSelected >= 0 - || _peerSearchSelected >= 0 - || _searchedSelected >= 0) { + if (isSelected()) { updateSelectedRow(); - _importantSwitchSelected = false; + _collapsedSelected = -1; _selected = nullptr; _filteredSelected = _searchedSelected @@ -1583,6 +1676,10 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { if (_state == WidgetState::Default) { if (_selected) { return { _selected->key(), FullMsgId() }; + } else if (base::in_range(_collapsedSelected, 0, _collapsedRows.size())) { + if (const auto folder = _collapsedRows[_collapsedSelected]->folder) { + return { folder, FullMsgId() }; + } } } else if (_state == WidgetState::Filtered) { if (base::in_range(_filteredSelected, 0, _filterResults.size())) { @@ -1931,7 +2028,7 @@ void InnerWidget::peerSearchReceived( const auto [i, ok] = _filterResultsGlobal.emplace( peer, std::move(row)); - _filterResults.push_back(i->second.get()); + _filterResults.emplace_back(i->second.get()); } else { LOG(("API Error: " "user %1 was not loaded in InnerWidget::peopleReceived()" @@ -1957,7 +2054,7 @@ void InnerWidget::peerSearchReceived( } void InnerWidget::notify_historyMuteUpdated(History *history) { - if (!_importantSwitch || !history->inChatList()) { + if (!Global::DialogsModeEnabled() || !history->inChatList()) { return; } refreshDialog(history); @@ -1967,7 +2064,23 @@ Data::Folder *InnerWidget::shownFolder() const { return _openedFolder; } +bool InnerWidget::needCollapsedRowsRefresh() const { + const auto archive = !shownDialogs()->empty() + ? (*shownDialogs()->begin())->folder() + : nullptr; + const auto collapsedHasArchive = !_collapsedRows.empty() + && (_collapsedRows.back()->folder != nullptr); + const auto archiveIsCollapsed = (archive != nullptr) + && session().settings().archiveCollapsed(); + return archiveIsCollapsed + ? (!collapsedHasArchive || _skipByCollapsedRows != 1) + : (collapsedHasArchive || _skipByCollapsedRows != 0); +} + void InnerWidget::refresh(bool toTop) { + if (needCollapsedRowsRefresh()) { + return refreshWithCollapsedRows(toTop); + } auto h = 0; if (_state == WidgetState::Default) { if (shownDialogs()->empty()) { @@ -2006,8 +2119,8 @@ void InnerWidget::clearMouseSelection(bool clearSelection) { _lastMousePosition = std::nullopt; if (clearSelection) { if (_state == WidgetState::Default) { + _collapsedSelected = -1; _selected = nullptr; - _importantSwitchSelected = false; } else if (_state == WidgetState::Filtered) { _filteredSelected = _peerSearchSelected @@ -2116,41 +2229,40 @@ void InnerWidget::clearFilter() { void InnerWidget::selectSkip(int32 direction) { clearMouseSelection(); if (_state == WidgetState::Default) { - if (_importantSwitchSelected) { - if (!shownDialogs()->empty() && direction > 0) { - _selected = *shownDialogs()->cbegin(); - _importantSwitchSelected = false; + const auto list = shownDialogs(); + if (_collapsedRows.empty() && list->size() <= _skipByCollapsedRows) { + return; + } + if (_collapsedSelected < 0 && !_selected) { + if (!_collapsedRows.empty()) { + _collapsedSelected = 0; } else { - return; - } - } else if (!_selected) { - if (importantSwitchShown()) { - _importantSwitchSelected = true; - } else if (!shownDialogs()->empty() && direction > 0) { - _selected = *shownDialogs()->cbegin(); - } else { - return; - } - } else if (direction > 0) { - auto next = shownDialogs()->cfind(_selected); - if (++next != shownDialogs()->cend()) { - _selected = *next; + _selected = *(list->cbegin() + _skipByCollapsedRows); } } else { - auto prev = shownDialogs()->cfind(_selected); - if (prev != shownDialogs()->cbegin()) { - _selected = *(--prev); - } else if (importantSwitchShown()) { - _importantSwitchSelected = true; + auto cur = (_collapsedSelected >= 0) + ? _collapsedSelected + : int(_collapsedRows.size() + + (list->cfind(_selected) - list->cbegin() - _skipByCollapsedRows)); + cur = snap(cur + direction, 0, static_cast(_collapsedRows.size() + list->size() - _skipByCollapsedRows - 1)); + if (cur < _collapsedRows.size()) { + _collapsedSelected = cur; _selected = nullptr; + } else { + _collapsedSelected = -1; + _selected = *(list->cbegin() + _skipByCollapsedRows + cur - _collapsedRows.size()); } } - if (_importantSwitchSelected || _selected) { - int fromY = _importantSwitchSelected ? 0 : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight); + if (_collapsedSelected >= 0 || _selected) { + const auto fromY = (_collapsedSelected >= 0) + ? (_collapsedSelected * st::dialogsImportantBarHeight) + : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight); emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } else if (_state == WidgetState::Filtered) { - if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _searchResults.empty()) return; + if (_hashtagResults.empty() && _filterResults.empty() && _peerSearchResults.empty() && _searchResults.empty()) { + return; + } if ((_hashtagSelected < 0 || _hashtagSelected >= _hashtagResults.size()) && (_filteredSelected < 0 || _filteredSelected >= _filterResults.size()) && (_peerSearchSelected < 0 || _peerSearchSelected >= _peerSearchResults.size()) && @@ -2232,9 +2344,9 @@ void InnerWidget::selectSkipPage(int32 pixels, int32 direction) { int toSkip = pixels / int(st::dialogsRowHeight); if (_state == WidgetState::Default) { if (!_selected) { - if (direction > 0 && !shownDialogs()->empty()) { - _selected = *shownDialogs()->cbegin(); - _importantSwitchSelected = false; + if (direction > 0 && shownDialogs()->size() > _skipByCollapsedRows) { + _selected = *(shownDialogs()->cbegin() + _skipByCollapsedRows); + _collapsedSelected = -1; } else { return; } @@ -2244,16 +2356,18 @@ void InnerWidget::selectSkipPage(int32 pixels, int32 direction) { _selected = *i; } } else { - for (auto i = shownDialogs()->cfind(_selected), b = shownDialogs()->cbegin(); i != b && (toSkip--);) { + for (auto i = shownDialogs()->cfind(_selected), b = shownDialogs()->cbegin(); i != b && (*i)->pos() > _skipByCollapsedRows && (toSkip--);) { _selected = *(--i); } - if (toSkip && importantSwitchShown()) { - _importantSwitchSelected = true; + if (toSkip && !_collapsedRows.empty()) { + _collapsedSelected = std::max(int(_collapsedRows.size()) - toSkip, 0); _selected = nullptr; } } - if (_importantSwitchSelected || _selected) { - int fromY = (_importantSwitchSelected ? 0 : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight)); + if (_collapsedSelected >= 0 || _selected) { + const auto fromY = (_collapsedSelected >= 0) + ? (_collapsedSelected * st::dialogsImportantBarHeight) + : (dialogsOffset() + _selected->pos() * st::dialogsRowHeight); emit mustScrollTo(fromY, fromY + st::dialogsRowHeight); } } else { @@ -2317,12 +2431,23 @@ void InnerWidget::loadPeerPhotos() { } } -bool InnerWidget::switchImportantChats() { - if (!_importantSwitchSelected - || !importantSwitchShown() - || (_state != WidgetState::Default)) { +bool InnerWidget::chooseCollapsedRow() { + if (_state != WidgetState::Default) { + return false; + } else if ((_collapsedSelected < 0) + || (_collapsedSelected >= _collapsedRows.size())) { return false; } + const auto &row = _collapsedRows[_collapsedSelected]; + if (row->folder) { + _controller->openFolder(row->folder); + } else { + switchImportantChats(); + } + return true; +} + +void InnerWidget::switchImportantChats() { clearSelection(); if (Global::DialogsMode() == Mode::All) { Global::SetDialogsMode(Mode::Important); @@ -2331,9 +2456,8 @@ bool InnerWidget::switchImportantChats() { } _mode = Global::DialogsMode(); Local::writeUserSettings(); - refresh(); - _importantSwitchSelected = true; - return true; + refreshWithCollapsedRows(true); + _collapsedSelected = 0; } bool InnerWidget::chooseHashtag() { @@ -2404,7 +2528,7 @@ ChosenRow InnerWidget::computeChosenRow() const { } bool InnerWidget::chooseRow() { - if (switchImportantChats()) { + if (chooseCollapsedRow()) { return true; } else if (chooseHashtag()) { return true; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index 0a5a1ac7aa..15633234ee 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -146,7 +146,7 @@ protected: void contextMenuEvent(QContextMenuEvent *e) override; private: - struct ImportantSwitch; + struct CollapsedRow; struct HashtagResult; struct PeerSearchResult; @@ -161,7 +161,11 @@ private: void dialogRowReplaced(Row *oldRow, Row *newRow); - bool switchImportantChats(); + void repaintCollapsedFolderRow(not_null folder); + void refreshWithCollapsedRows(bool toTop = false); + bool needCollapsedRowsRefresh() const; + bool chooseCollapsedRow(); + void switchImportantChats(); bool chooseHashtag(); ChosenRow computeChosenRow() const; bool isSearchResultActive( @@ -173,14 +177,14 @@ private: void clearIrrelevantState(); void selectByMouse(QPoint globalPosition); void loadPeerPhotos(); - void setImportantSwitchPressed(bool pressed); + void setCollapsedPressed(int pressed); void setPressed(Row *pressed); void setHashtagPressed(int pressed); void setFilteredPressed(int pressed); void setPeerSearchPressed(int pressed); void setSearchedPressed(int pressed); bool isPressed() const { - return _importantSwitchPressed + return (_collapsedPressed >= 0) || _pressed || (_hashtagPressed >= 0) || (_filteredPressed >= 0) @@ -188,7 +192,7 @@ private: || (_searchedPressed >= 0); } bool isSelected() const { - return _importantSwitchSelected + return (_collapsedSelected >= 0) || _selected || (_hashtagSelected >= 0) || (_filteredSelected >= 0) @@ -227,15 +231,21 @@ private: UpdateRowSections sections = UpdateRowSection::All); void fillSupportSearchMenu(not_null menu); - bool importantSwitchShown() const; int dialogsOffset() const; - int proxyPromotedCount() const; + int fixedOnTopCount() const; int pinnedOffset() const; int filteredOffset() const; int peerSearchOffset() const; int searchedOffset() const; int searchInChatSkip() const; + void paintCollapsedRows( + Painter &p, + QRect clip) const; + void paintCollapsedRow( + Painter &p, + not_null row, + bool selected) const; void paintPeerSearchResult( Painter &p, not_null result, @@ -290,9 +300,10 @@ private: Data::Folder *_openedFolder = nullptr; - std::unique_ptr _importantSwitch; - bool _importantSwitchSelected = false; - bool _importantSwitchPressed = false; + std::vector> _collapsedRows; + int _collapsedSelected = -1; + int _collapsedPressed = -1; + int _skipByCollapsedRows = 0; Row *_selected = nullptr; Row *_pressed = nullptr; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index eb7ca81194..2c69a5741e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -841,26 +841,21 @@ QRect RowPainter::sendActionAnimationRect(int animationWidth, int animationHeigh return QRect(nameleft, texttop, textUpdated ? namewidth : animationWidth, animationHeight); } -void paintImportantSwitch(Painter &p, Mode current, int fullWidth, bool selected) { +void PaintCollapsedRow(Painter &p, const RippleRow &row, const QString &text, int unread, int fullWidth, bool selected) { p.fillRect(0, 0, fullWidth, st::dialogsImportantBarHeight, selected ? st::dialogsBgOver : st::dialogsBg); + row.paintRipple(p, 0, 0, fullWidth); + p.setFont(st::semiboldFont); p.setPen(st::dialogsNameFg); const auto unreadTop = (st::dialogsImportantBarHeight - st::dialogsUnreadHeight) / 2; - const auto mutedHidden = (current == Dialogs::Mode::Important); - const auto text = lang(mutedHidden - ? lng_dialogs_show_all_chats - : lng_dialogs_hide_muted_chats); const auto textBaseline = unreadTop + (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2 + st::dialogsUnreadFont->ascent; p.drawText(st::dialogsPadding.x(), textBaseline, text); - if (!mutedHidden) { - return; - } - if (const auto unread = Auth().data().unreadOnlyMutedBadge()) { + if (unread) { const auto unreadRight = fullWidth - st::dialogsPadding.x(); UnreadBadgeStyle st; st.muted = true; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.h b/Telegram/SourceFiles/dialogs/dialogs_layout.h index f9cb8eb6e6..f16cc31003 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.h @@ -11,6 +11,7 @@ namespace Dialogs { class Row; class FakeRow; +class RippleRow; namespace Layout { @@ -48,9 +49,11 @@ public: }; -void paintImportantSwitch( +void PaintCollapsedRow( Painter &p, - Mode current, + const RippleRow &row, + const QString &text, + int unread, int fullWidth, bool selected); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 89a5822031..04f084171d 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -84,6 +84,7 @@ public: void fill(); private: + void addToggleCollapse(); //bool showInfo(); //void addTogglePin(); //void addInfo(); @@ -91,10 +92,10 @@ private: //void addNotifications(); //void addUngroup(); - //not_null _controller; - //not_null _folder; - //const PeerMenuCallback &_addAction; - //PeerMenuSource _source; + not_null _controller; + not_null _folder; + const PeerMenuCallback &_addAction; + PeerMenuSource _source; }; @@ -534,54 +535,33 @@ FolderFiller::FolderFiller( not_null controller, not_null folder, const PeerMenuCallback &addAction, - PeerMenuSource source) { -//: _controller(controller) -//, _folder(folder) -//, _addAction(addAction) -//, _source(source) { + PeerMenuSource source) +: _controller(controller) +, _folder(folder) +, _addAction(addAction) +, _source(source) { } -void FolderFiller::fill() { // #TODO archive - //if (_source == PeerMenuSource::ChatsList) { - // addTogglePin(); - //} - //if (showInfo()) { - // addInfo(); - //} - //addNotifications(); - //if (_source == PeerMenuSource::ChatsList) { - // addSearch(); - //} - //addUngroup(); +void FolderFiller::fill() { + if (_source == PeerMenuSource::ChatsList) { + addToggleCollapse(); + } +} + +void FolderFiller::addToggleCollapse() { + if (_folder->id() != Data::Folder::kId) { + return; + } + const auto controller = _controller; + const auto hidden = controller->session().settings().archiveCollapsed(); + const auto text = lang(hidden + ? lng_context_archive_expand + : lng_context_archive_collapse); + _addAction(text, [=] { + controller->session().settings().setArchiveCollapsed(!hidden); + controller->session().saveSettingsDelayed(); + }); } -// -//bool FolderFiller::showInfo() { -// if (_source == PeerMenuSource::Profile) { -// return false; -// } else if (_controller->activeChatCurrent().feed() != _feed) { -// return true; -// } else if (!Adaptive::ThreeColumn()) { -// return true; -// } else if ( -// !Auth().settings().thirdSectionInfoEnabled() && -// !Auth().settings().tabbedReplacedWithInfo()) { -// return true; -// } -// return false; -//} -// -//void FolderFiller::addTogglePin() { -// const auto feed = _feed; -// const auto isPinned = feed->isPinnedDialog(); -// const auto pinText = [](bool isPinned) { -// return lang(isPinned -// ? lng_context_unpin_from_top -// : lng_context_pin_to_top); -// }; -// _addAction(pinText(isPinned), [=] { -// TogglePinnedDialog(feed); -// }); -//} // //void FolderFiller::addInfo() { // auto controller = _controller;