Added ordering and setting of main profile media tab.

This commit is contained in:
23rd
2026-07-12 09:35:53 +03:00
parent da1270af85
commit f586e9dcd6
9 changed files with 171 additions and 13 deletions
+1
View File
@@ -1869,6 +1869,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_media_type_similar" = "Similar";
"lng_media_type_gifts" = "Gifts";
"lng_media_type_saved" = "Saved";
"lng_profile_tab_set_as_main" = "Set as Main Tab";
"lng_polls_search_none" = "No polls found";
"lng_media_type_rounds" = "Video messages";
"lng_media_zoom_in" = "Zoom In";
@@ -59,6 +59,18 @@ using SharedMediaType = Storage::SharedMediaType;
return u"media:"_q + QString::number(int(type));
}
[[nodiscard]] Data::ProfileTab MediaProfileTab(SharedMediaType type) {
switch (type) {
case SharedMediaType::Photo: return Data::ProfileTab::Media;
case SharedMediaType::File: return Data::ProfileTab::Files;
case SharedMediaType::MusicFile: return Data::ProfileTab::Music;
case SharedMediaType::RoundVoiceFile: return Data::ProfileTab::Voice;
case SharedMediaType::Link: return Data::ProfileTab::Links;
case SharedMediaType::GIF: return Data::ProfileTab::Gifs;
default: return Data::ProfileTab::None;
}
}
class MediaTabAdapter final : public MediaTabContent {
public:
MediaTabAdapter(MediaTabContext context, SharedMediaType type)
@@ -253,6 +265,7 @@ MediaTabDescriptor MakeMediaTabDescriptor(
std::move(context),
type);
},
.profileTab = MediaProfileTab(type),
};
}
@@ -190,6 +190,7 @@ MediaTabDescriptor MakeGiftsTabDescriptor(not_null<PeerData*> peer) {
std::move(context),
peer);
},
.profileTab = Data::ProfileTab::Gifts,
};
}
@@ -154,6 +154,7 @@ MediaTabDescriptor MakeStoriesTabDescriptor(not_null<PeerData*> peer) {
.factory = [](MediaTabContext context) {
return std::make_unique<StoriesTabAdapter>(std::move(context));
},
.profileTab = Data::ProfileTab::Posts,
};
}
@@ -14,6 +14,7 @@ class PeerData;
namespace Data {
class ForumTopic;
class SavedSublist;
enum class ProfileTab : uchar;
} // namespace Data
namespace Ui {
@@ -64,6 +65,7 @@ struct MediaTabDescriptor {
rpl::producer<QString> title;
rpl::producer<bool> shown;
Fn<std::unique_ptr<MediaTabContent>(MediaTabContext)> factory;
Data::ProfileTab profileTab = {};
};
} // namespace Info::Profile
@@ -8,13 +8,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/tabs/info_profile_tabs_host.h"
#include "info/profile/tabs/info_profile_tabs_strip.h"
#include "apiwrap.h"
#include "base/options.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/scroll_area.h"
#include "ui/effects/slide_animation.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "styles/style_basic.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
namespace Info::Profile {
namespace {
@@ -48,8 +58,14 @@ TabsHost::TabsHost(not_null<QWidget*> parent, Descriptor descriptor)
}
_stripTitles.assign(_tabs.size(), QString());
_tabsShown.assign(_tabs.size(), false);
refreshOrder();
wireStripTitles();
wireTabsVisibility();
wireMainTab();
_strip->contextMenuRequests(
) | rpl::on_next([this](const QString &id) {
showTabMenu(id);
}, lifetime());
_strip->activated(
) | rpl::on_next([this](const QString &id) {
_userChosenTab = true;
@@ -159,10 +175,109 @@ void TabsHost::wireTabsVisibility() {
}
}
void TabsHost::wireMainTab() {
const auto peer = _context.peer;
peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::MainProfileTab
) | rpl::on_next([this] {
if (refreshOrder()) {
syncStripTitles();
ensureActiveVisible();
}
}, lifetime());
}
bool TabsHost::refreshOrder() {
const auto main = _context.peer->mainProfileTab();
const auto i = (main == Data::ProfileTab::None)
? end(_tabs)
: ranges::find(_tabs, main, &MediaTabDescriptor::profileTab);
const auto mainTabIndex = (i != end(_tabs))
? int(i - begin(_tabs))
: -1;
if (!_order.empty() && _mainTabIndex == mainTabIndex) {
return false;
}
_mainTabIndex = mainTabIndex;
_order.resize(_tabs.size());
for (auto index = 0; index != int(_order.size()); ++index) {
_order[index] = index;
}
if (mainTabIndex > 0) {
std::rotate(
begin(_order),
begin(_order) + mainTabIndex,
begin(_order) + mainTabIndex + 1);
}
return true;
}
int TabsHost::displayPosition(int index) const {
return int(ranges::find(_order, index) - begin(_order));
}
int TabsHost::firstVisibleIndex() const {
for (const auto i : _order) {
if (_tabsShown[i]) {
return i;
}
}
return -1;
}
bool TabsHost::canSetMainTab(Data::ProfileTab tab) const {
if (tab == Data::ProfileTab::None || _context.topic || _context.sublist) {
return false;
} else if (const auto channel = _context.peer->asBroadcast()) {
return channel->canEditInformation();
}
return _context.peer->isSelf()
&& ((tab == Data::ProfileTab::Posts)
|| (tab == Data::ProfileTab::Gifts));
}
void TabsHost::showTabMenu(const QString &id) {
const auto strip = _stripWeak.get();
const auto i = ranges::find(_tabs, id, &MediaTabDescriptor::id);
if (!strip || i == end(_tabs)) {
return;
}
const auto tab = i->profileTab;
const auto index = int(i - begin(_tabs));
if (!canSetMainTab(tab) || index == firstVisibleIndex()) {
return;
}
_menu = base::make_unique_q<Ui::PopupMenu>(
strip,
st::popupMenuWithIcons);
Ui::Menu::CreateAddActionCallback(_menu)(
tr::lng_profile_tab_set_as_main(tr::now),
crl::guard(this, [=] { setMainTab(tab); }),
&st::menuIconReorder);
_menu->popup(QCursor::pos());
}
void TabsHost::setMainTab(Data::ProfileTab tab) {
const auto peer = _context.peer;
const auto sending = Data::ProfileTabToMTP(tab);
if (const auto channel = peer->asChannel()) {
peer->session().api().request(MTPchannels_SetMainProfileTab(
channel->inputChannel(),
sending
)).send();
} else {
peer->session().api().request(
MTPaccount_SetMainProfileTab(sending)
).send();
}
peer->setMainProfileTab(tab);
}
void TabsHost::syncStripTitles() {
auto stripTabs = std::vector<StripTab>();
stripTabs.reserve(_tabs.size());
for (auto i = 0; i != int(_tabs.size()); ++i) {
for (const auto i : _order) {
if (!_tabsShown[i]) {
continue;
}
@@ -184,14 +299,7 @@ void TabsHost::syncStripTitles() {
}
void TabsHost::ensureActiveVisible() {
const auto firstVisible = [&] {
for (auto i = 0; i != int(_tabs.size()); ++i) {
if (_tabsShown[i]) {
return i;
}
}
return -1;
}();
const auto firstVisible = firstVisibleIndex();
const auto activeIndex = _activeId.isEmpty()
? -1
: int(ranges::find(_tabs, _activeId, &MediaTabDescriptor::id)
@@ -246,10 +354,15 @@ Fn<void()> TabsHost::prepareSwitch(bool toNextTab) {
if (active >= int(_tabs.size())) {
return nullptr;
}
const auto position = displayPosition(active);
if (position >= int(_order.size())) {
return nullptr;
}
const auto delta = toNextTab ? 1 : -1;
for (auto i = active + delta
; i >= 0 && i < int(_tabs.size())
; i += delta) {
for (auto p = position + delta
; p >= 0 && p < int(_order.size())
; p += delta) {
const auto i = _order[p];
if (!_tabsShown[i]) {
continue;
}
@@ -380,7 +493,8 @@ void TabsHost::activateTab(const QString &id, bool animated) {
startSlideAnimation(
std::move(wasCache),
active,
previousIndex > int(it - begin(_tabs)));
displayPosition(previousIndex)
> displayPosition(int(it - begin(_tabs))));
}
previous->widget()->hide();
}
@@ -9,10 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/object_ptr.h"
#include "base/flat_map.h"
#include "base/unique_qptr.h"
#include "info/profile/tabs/info_profile_tab_content.h"
#include "ui/rp_widget.h"
namespace Ui {
class PopupMenu;
class RpWidget;
class SlideAnimation;
struct ScrollToRequest;
@@ -60,6 +62,13 @@ protected:
private:
void wireStripTitles();
void wireTabsVisibility();
void wireMainTab();
bool refreshOrder();
[[nodiscard]] int displayPosition(int index) const;
[[nodiscard]] int firstVisibleIndex() const;
[[nodiscard]] bool canSetMainTab(Data::ProfileTab tab) const;
void showTabMenu(const QString &id);
void setMainTab(Data::ProfileTab tab);
void syncStripTitles();
void ensureActiveVisible();
void pushViewportToActive();
@@ -77,9 +86,12 @@ private:
std::vector<MediaTabDescriptor> _tabs;
std::vector<QString> _stripTitles;
std::vector<bool> _tabsShown;
std::vector<int> _order;
int _mainTabIndex = -1;
TabsStrip *_strip = nullptr;
base::weak_qptr<TabsStrip> _stripWeak;
base::unique_qptr<Ui::PopupMenu> _menu;
Ui::RpWidget *_body = nullptr;
int _stripHeight = 0;
int _visibleTop = 0;
@@ -120,6 +120,10 @@ rpl::producer<QString> TabsStrip::activated() const {
return _activated.events();
}
rpl::producer<QString> TabsStrip::contextMenuRequests() const {
return _contextMenuRequests.events();
}
QRect TabsStrip::islandRect() const {
return rect() - _st.margin;
}
@@ -333,6 +337,13 @@ void TabsStrip::mousePressEvent(QMouseEvent *e) {
}
}
void TabsStrip::contextMenuEvent(QContextMenuEvent *e) {
const auto index = indexAt(e->pos());
if (index >= 0) {
_contextMenuRequests.fire_copy(_buttons[index].tab.id);
}
}
void TabsStrip::mouseReleaseEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) {
return;
@@ -35,6 +35,7 @@ public:
void setActiveTab(const QString &id);
[[nodiscard]] rpl::producer<QString> activated() const;
[[nodiscard]] rpl::producer<QString> contextMenuRequests() const;
private:
struct Button {
@@ -49,6 +50,7 @@ private:
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void paintEvent(QPaintEvent *e) override;
bool eventHook(QEvent *e) override;
@@ -69,6 +71,7 @@ private:
Ui::BoxShadow _shadow;
std::vector<Button> _buttons;
rpl::event_stream<QString> _activated;
rpl::event_stream<QString> _contextMenuRequests;
std::optional<Qt::Orientation> _locked;
int _dragx = 0;
int _pressx = 0;