Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Telegram/SourceFiles/dialogs/dialogs_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,11 @@ Widget::Widget(

_search->customUpDown(true);

// The bars above the list and the folder tabs are created only once they
// are needed, long after the list and the buttons below it, so the Tab
// chain has to follow the column instead of the creation order.
setVisualTabOrder(true);

updateJumpToDateVisibility(true);
updateSearchFromVisibility(true);
setupSupportMode();
Expand Down
66 changes: 64 additions & 2 deletions Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ For license and copyright information please follow this link:
*/
#include "ui/widgets/chat_filters_tabs_slider.h"

#include "lang/lang_keys.h"
#include "ui/effects/ripple_animation.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/side_bar_button.h"
#include "styles/style_dialogs.h"
#include "styles/style_widgets.h"
Expand Down Expand Up @@ -169,15 +171,18 @@ void ChatsFiltersTabs::setUnreadCount(int index, int unreadCount, bool mute) {
.muted = mute,
});
update();
accessibilityChildNameChanged(index);
}
} else if (!unreadCount) {
_unreadCounts.erase(it);
update();
accessibilityChildNameChanged(index);
} else if (it->second.count != unreadCount || it->second.muted != mute) {
it->second.count = unreadCount;
it->second.muted = mute;
it->second.cache = cacheUnreadCount(unreadCount, mute);
update();
accessibilityChildNameChanged(index);
}
updateSectionsContentWidths();
}
Expand Down Expand Up @@ -420,6 +425,24 @@ void ChatsFiltersTabs::mouseReleaseEvent(QMouseEvent *e) {
}

void ChatsFiltersTabs::contextMenuEvent(QContextMenuEvent *e) {
if (e->reason() == QContextMenuEvent::Keyboard) {
// A menu asked for from the keyboard comes with the center of an
// empty input method rect instead of a position over a tab, so the
// lookup below would land outside of them all - take the tab the
// browse position is on, or the active one when it is on none.
const auto browsed = accessibilityBrowsedSection();
const auto index = (browsed >= 0) ? browsed : activeSection();
if (!_lockedFrom || index < _lockedFrom) {
_contextMenuRequested.fire({
.index = index,
.position = ContextMenuPosition(
this,
e,
accessibilityChildRect(index)),
});
}
return;
}
const auto pos = e->pos();
if (pos.x() >= _lockedFromX) {
return;
Expand All @@ -435,10 +458,49 @@ void ChatsFiltersTabs::contextMenuEvent(QContextMenuEvent *e) {
index++;
return true;
});
_contextMenuRequested.fire_copy(index);
_contextMenuRequested.fire({
.index = index,
.position = QCursor::pos(),
});
}

QString ChatsFiltersTabs::accessibilityChildName(int index) const {
const auto title = Ui::SettingsSlider::accessibilityChildName(index);
if (_lockedFrom > 0 && index >= _lockedFrom) {
// Surface a locked folder's premium-gated status, which the visual
// lock glyph alone can't convey to a screen reader.
return tr::lng_sr_folder_locked(tr::now, lt_text, title);
}
const auto it = _unreadCounts.find(index);
return (it != _unreadCounts.end())
? tr::lng_filter_unread_chats(
tr::now,
lt_count,
it->second.count,
lt_text,
title)
: title;
}

QString ChatsFiltersTabs::accessibilityChildDescription(int index) const {
return (_lockedFrom > 0 && index >= _lockedFrom)
? tr::lng_sr_folder_locked_about(tr::now)
: QString();
}

void ChatsFiltersTabs::activateSectionByAccessibility(int index) {
// Keyboard or screen reader activation of a locked (premium) folder
// behaves like a click on it: show the premium promo instead of
// switching to a folder the user can't use.
if (_lockedFrom > 0 && index >= _lockedFrom) {
_lockedClicked.fire({});
} else {
Ui::SettingsSlider::activateSectionByAccessibility(index);
}
}

rpl::producer<int> ChatsFiltersTabs::contextMenuRequested() const {
auto ChatsFiltersTabs::contextMenuRequested() const
-> rpl::producer<ChatsFiltersTabMenuRequest> {
return _contextMenuRequested.events();
}

Expand Down
14 changes: 12 additions & 2 deletions Telegram/SourceFiles/ui/widgets/chat_filters_tabs_slider.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ class SettingsSlider;

class ChatsFiltersTabsReorder;

struct ChatsFiltersTabMenuRequest {
int index = 0;
QPoint position; // Global coordinates for showing the menu.
};

class ChatsFiltersTabs final : public Ui::SettingsSlider {
public:
ChatsFiltersTabs(
Expand All @@ -45,7 +50,8 @@ class ChatsFiltersTabs final : public Ui::SettingsSlider {
return _lockedFrom;
}

[[nodiscard]] rpl::producer<int> contextMenuRequested() const;
[[nodiscard]] auto contextMenuRequested() const
-> rpl::producer<ChatsFiltersTabMenuRequest>;
[[nodiscard]] rpl::producer<> lockedClicked() const;

void setHorizontalShift(int index, int shift);
Expand All @@ -58,6 +64,9 @@ class ChatsFiltersTabs final : public Ui::SettingsSlider {

void stopAnimation();

QString accessibilityChildName(int index) const override;
QString accessibilityChildDescription(int index) const override;

protected:
struct ShiftedSection {
not_null<Ui::DiscreteSlider::Section*> section;
Expand All @@ -71,6 +80,7 @@ class ChatsFiltersTabs final : public Ui::SettingsSlider {
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void activateSectionByAccessibility(int index) override;

std::vector<ShiftedSection> _sections;

Expand Down Expand Up @@ -110,7 +120,7 @@ class ChatsFiltersTabs final : public Ui::SettingsSlider {
int _reordering = 0;

rpl::lifetime _paletteLifetime;
rpl::event_stream<int> _contextMenuRequested;
rpl::event_stream<ChatsFiltersTabMenuRequest> _contextMenuRequested;
rpl::event_stream<> _lockedClicked;

};
Expand Down
32 changes: 27 additions & 5 deletions Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ void ShowMenu(
not_null<Ui::RpWidget*> parent,
not_null<Window::SessionController*> controller,
not_null<State*> state,
int index) {
int index,
QPoint position) {
const auto session = &controller->session();

auto id = FilterId(0);
Expand Down Expand Up @@ -133,14 +134,15 @@ void ShowMenu(
state->menu = nullptr;
return;
}
state->menu->popup(QCursor::pos());
state->menu->popup(position);
}

void ShowFiltersListMenu(
not_null<Ui::RpWidget*> parent,
not_null<Main::Session*> session,
not_null<State*> state,
int active,
QPoint position,
Fn<void(int)> changeActive) {
const auto &list = session->data().chatsFilters().list();

Expand Down Expand Up @@ -195,7 +197,7 @@ void ShowFiltersListMenu(
state->menu = nullptr;
return;
}
state->menu->popup(QCursor::pos());
state->menu->popup(position);
}

} // namespace
Expand Down Expand Up @@ -230,6 +232,11 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
trackActiveFilterAndUnreadAndReorder
? st::dialogsSearchTabs
: st::chatsFiltersTabs));
slider->setAccessibleName(tr::lng_filters_title(tr::now));
// Switching a folder reloads the whole chat list, and a locked premium
// folder opens an upsell on activation - so the arrows only browse the
// folder tabs, committing on Enter / Space.
slider->setAccessibilityActivateOnBrowse(false);
const auto state = wrap->lifetime().make_state<State>();
const auto reassignUnreadValue = [=] {
state->reorderLifetime.destroy();
Expand Down Expand Up @@ -364,6 +371,14 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
}
};

slider->accessibilitySectionBrowsed(
) | rpl::on_next([=](int index) {
// Browsing with a screen reader moves between sections without
// activating them, so nothing else scrolls the strip; keep the
// browsed section visible.
scrollToIndex(index, anim::type::normal);
}, slider->lifetime());

const auto applyFilter = [=](const Data::ChatFilter &filter) {
if (slider->reordering()) {
return;
Expand Down Expand Up @@ -492,15 +507,22 @@ not_null<Ui::RpWidget*> AddChatFiltersTabsStrip(
}
applyFilter(filter);
}, state->rebuildLifetime);
slider->contextMenuRequested() | rpl::on_next([=](int index) {
slider->contextMenuRequested() | rpl::on_next([=](
Ui::ChatsFiltersTabMenuRequest request) {
if (trackActiveFilterAndUnreadAndReorder) {
ShowMenu(wrap, controller, state, index);
ShowMenu(
wrap,
controller,
state,
request.index,
request.position);
} else {
ShowFiltersListMenu(
wrap,
session,
state,
slider->activeSection(),
request.position,
[=](int i) { slider->setActiveSection(i); });
}
}, state->rebuildLifetime);
Expand Down
Loading