mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 14:38:15 +00:00
Implement folder link add / join design.
This commit is contained in:
@@ -15,11 +15,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/controls/filter_link_header.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toasts/common_toasts.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_filter_icons.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
@@ -31,19 +34,6 @@ enum class ToggleAction {
|
||||
Removing,
|
||||
};
|
||||
|
||||
enum class HeaderType {
|
||||
AddingFilter,
|
||||
AddingChats,
|
||||
AllAdded,
|
||||
Removing,
|
||||
};
|
||||
|
||||
struct HeaderDescriptor {
|
||||
base::required<HeaderType> type;
|
||||
base::required<QString> title;
|
||||
int badge = 0;
|
||||
};
|
||||
|
||||
class ToggleChatsController final
|
||||
: public PeerListController
|
||||
, public base::has_weak_ptr {
|
||||
@@ -63,11 +53,14 @@ public:
|
||||
[[nodiscard]] auto selectedValue() const
|
||||
-> rpl::producer<base::flat_set<not_null<PeerData*>>>;
|
||||
|
||||
void setAddedTopHeight(int addedTopHeight);
|
||||
|
||||
private:
|
||||
void setupAboveWidget();
|
||||
void setupBelowWidget();
|
||||
|
||||
const not_null<Window::SessionController*> _window;
|
||||
Ui::RpWidget *_addedTopWidget = nullptr;
|
||||
|
||||
ToggleAction _action = ToggleAction::Adding;
|
||||
QString _slug;
|
||||
@@ -82,44 +75,42 @@ private:
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] rpl::producer<QString> TitleText(HeaderType type) {
|
||||
[[nodiscard]] tr::phrase<> TitleText(Ui::FilterLinkHeaderType type) {
|
||||
using Type = Ui::FilterLinkHeaderType;
|
||||
switch (type) {
|
||||
case HeaderType::AddingFilter:
|
||||
return tr::lng_filters_by_link_title();
|
||||
case HeaderType::AddingChats:
|
||||
return tr::lng_filters_by_link_more();
|
||||
case HeaderType::AllAdded:
|
||||
return tr::lng_filters_by_link_already();
|
||||
case HeaderType::Removing:
|
||||
return tr::lng_filters_by_link_remove();
|
||||
case Type::AddingFilter: return tr::lng_filters_by_link_title;
|
||||
case Type::AddingChats: return tr::lng_filters_by_link_more;
|
||||
case Type::AllAdded: return tr::lng_filters_by_link_already;
|
||||
case Type::Removing: return tr::lng_filters_by_link_remove;
|
||||
}
|
||||
Unexpected("HeaderType in TitleText.");
|
||||
Unexpected("Ui::FilterLinkHeaderType in TitleText.");
|
||||
}
|
||||
|
||||
void FillHeader(
|
||||
not_null<Ui::VerticalLayout*> container,
|
||||
HeaderDescriptor descriptor) {
|
||||
const auto phrase = (descriptor.type == HeaderType::AddingFilter)
|
||||
[[nodiscard]] TextWithEntities AboutText(
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title) {
|
||||
using Type = Ui::FilterLinkHeaderType;
|
||||
const auto phrase = (type == Type::AddingFilter)
|
||||
? tr::lng_filters_by_link_sure
|
||||
: (descriptor.type == HeaderType::AddingChats)
|
||||
: (type == Type::AddingChats)
|
||||
? tr::lng_filters_by_link_more_sure
|
||||
: (descriptor.type == HeaderType::AllAdded)
|
||||
: (type == Type::AllAdded)
|
||||
? tr::lng_filters_by_link_already_about
|
||||
: tr::lng_filters_by_link_remove_sure;
|
||||
auto boldTitle = Ui::Text::Bold(descriptor.title);
|
||||
auto description = (descriptor.type == HeaderType::AddingFilter)
|
||||
auto boldTitle = Ui::Text::Bold(title);
|
||||
return (type == Type::AddingFilter)
|
||||
? tr::lng_filters_by_link_sure(
|
||||
tr::now,
|
||||
lt_folder,
|
||||
std::move(boldTitle),
|
||||
Ui::Text::WithEntities)
|
||||
: (descriptor.type == HeaderType::AddingChats)
|
||||
: (type == Type::AddingChats)
|
||||
? tr::lng_filters_by_link_more_sure(
|
||||
tr::now,
|
||||
lt_folder,
|
||||
std::move(boldTitle),
|
||||
Ui::Text::WithEntities)
|
||||
: (descriptor.type == HeaderType::AllAdded)
|
||||
: (type == Type::AllAdded)
|
||||
? tr::lng_filters_by_link_already_about(
|
||||
tr::now,
|
||||
lt_folder,
|
||||
@@ -130,35 +121,92 @@ void FillHeader(
|
||||
lt_folder,
|
||||
std::move(boldTitle),
|
||||
Ui::Text::WithEntities);
|
||||
container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
phrase(
|
||||
lt_folder,
|
||||
rpl::single(Ui::Text::Bold(descriptor.title)),
|
||||
Ui::Text::WithEntities),
|
||||
st::boxDividerLabel),
|
||||
st::boxRowPadding);
|
||||
}
|
||||
|
||||
void InitFilterLinkHeader(
|
||||
not_null<PeerListBox*> box,
|
||||
Fn<void(int)> setAddedTopHeight,
|
||||
Ui::FilterLinkHeaderType type,
|
||||
const QString &title,
|
||||
rpl::producer<int> count) {
|
||||
auto header = Ui::MakeFilterLinkHeader(box, {
|
||||
.type = type,
|
||||
.title = TitleText(type)(tr::now),
|
||||
.about = AboutText(type, title),
|
||||
.folderTitle = title,
|
||||
.folderIcon = &st::foldersCustomActive,
|
||||
.badge = (type == Ui::FilterLinkHeaderType::AddingChats
|
||||
? std::move(count)
|
||||
: rpl::single(0)),
|
||||
});
|
||||
const auto widget = header.widget;
|
||||
widget->resizeToWidth(st::boxWideWidth);
|
||||
Ui::SendPendingMoveResizeEvents(widget);
|
||||
|
||||
const auto min = widget->minimumHeight(), max = widget->maximumHeight();
|
||||
widget->resize(st::boxWideWidth, max);
|
||||
|
||||
box->setAddedTopScrollSkip(max);
|
||||
std::move(
|
||||
header.wheelEvents
|
||||
) | rpl::start_with_next([=](not_null<QWheelEvent*> e) {
|
||||
box->sendScrollViewportEvent(e);
|
||||
}, widget->lifetime());
|
||||
|
||||
struct State {
|
||||
bool processing = false;
|
||||
int addedTopHeight = 0;
|
||||
};
|
||||
const auto state = widget->lifetime().make_state<State>();
|
||||
|
||||
box->scrolls(
|
||||
) | rpl::filter([=] {
|
||||
return !state->processing;
|
||||
}) | rpl::start_with_next([=] {
|
||||
state->processing = true;
|
||||
const auto guard = gsl::finally([&] { state->processing = false; });
|
||||
|
||||
const auto top = box->scrollTop();
|
||||
const auto height = box->scrollHeight();
|
||||
const auto headerHeight = std::max(max - top, min);
|
||||
const auto addedTopHeight = max - headerHeight;
|
||||
widget->resize(widget->width(), headerHeight);
|
||||
if (state->addedTopHeight < addedTopHeight) {
|
||||
setAddedTopHeight(addedTopHeight);
|
||||
box->setAddedTopScrollSkip(headerHeight);
|
||||
} else {
|
||||
box->setAddedTopScrollSkip(headerHeight);
|
||||
setAddedTopHeight(addedTopHeight);
|
||||
}
|
||||
state->addedTopHeight = addedTopHeight;
|
||||
box->peerListRefreshRows();
|
||||
}, widget->lifetime());
|
||||
|
||||
box->setNoContentMargin(true);
|
||||
}
|
||||
|
||||
void ImportInvite(
|
||||
base::weak_ptr<Window::SessionController> weak,
|
||||
const QString &slug,
|
||||
const base::flat_set<not_null<PeerData*>> &peers) {
|
||||
const base::flat_set<not_null<PeerData*>> &peers,
|
||||
Fn<void()> done,
|
||||
Fn<void()> fail) {
|
||||
Expects(!peers.empty());
|
||||
|
||||
const auto peer = peers.front();
|
||||
const auto api = &peer->session().api();
|
||||
const auto callback = [=](const MTPUpdates &result) {
|
||||
api->applyUpdates(result);
|
||||
done();
|
||||
};
|
||||
const auto error = [=](const MTP::Error &error) {
|
||||
if (const auto strong = weak.get()) {
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = Window::Show(strong).toastParent(),
|
||||
.text = { error.description() },
|
||||
.text = { error.type() },
|
||||
});
|
||||
}
|
||||
fail();
|
||||
};
|
||||
auto inputs = peers | ranges::views::transform([](auto peer) {
|
||||
return MTPInputPeer(peer->input);
|
||||
@@ -182,6 +230,7 @@ ToggleChatsController::ToggleChatsController(
|
||||
, _filterId(filterId)
|
||||
, _filterTitle(title)
|
||||
, _chats(std::move(chats)) {
|
||||
setStyleOverrides(&st::filterLinkChatsList);
|
||||
}
|
||||
|
||||
void ToggleChatsController::prepare() {
|
||||
@@ -218,19 +267,14 @@ void ToggleChatsController::setupAboveWidget() {
|
||||
auto wrap = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
|
||||
const auto container = wrap.data();
|
||||
|
||||
const auto type = !_filterId
|
||||
? HeaderType::AddingFilter
|
||||
: (_action == ToggleAction::Adding)
|
||||
? HeaderType::AddingChats
|
||||
: HeaderType::Removing;
|
||||
delegate()->peerListSetTitle(TitleText(type));
|
||||
FillHeader(container, {
|
||||
.type = type,
|
||||
.title = _filterTitle,
|
||||
.badge = (type == HeaderType::AddingChats) ? int(_chats.size()) : 0,
|
||||
});
|
||||
|
||||
// lng_filters_by_link_join; // langs
|
||||
_addedTopWidget = container->add(object_ptr<Ui::RpWidget>(container));
|
||||
AddDivider(container);
|
||||
AddSubsectionTitle(
|
||||
container,
|
||||
tr::lng_filters_by_link_join(
|
||||
lt_count,
|
||||
rpl::single(float64(_chats.size()))),
|
||||
st::filterLinkSubsectionTitlePadding);
|
||||
|
||||
delegate()->peerListSetAboveWidget(std::move(wrap));
|
||||
}
|
||||
@@ -241,7 +285,7 @@ void ToggleChatsController::setupBelowWidget() {
|
||||
(QWidget*)nullptr,
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
(QWidget*)nullptr,
|
||||
tr::lng_filters_by_link_about(),
|
||||
tr::lng_filters_by_link_about(tr::now),
|
||||
st::boxDividerLabel),
|
||||
st::settingsDividerLabelPadding));
|
||||
}
|
||||
@@ -255,17 +299,10 @@ auto ToggleChatsController::selectedValue() const
|
||||
return _selected.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] void AlreadyFilterBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
const QString &title) {
|
||||
box->setTitle(TitleText(HeaderType::AllAdded));
|
||||
void ToggleChatsController::setAddedTopHeight(int addedTopHeight) {
|
||||
Expects(addedTopHeight >= 0);
|
||||
|
||||
FillHeader(box->verticalLayout(), {
|
||||
.type = HeaderType::AllAdded,
|
||||
.title = title,
|
||||
});
|
||||
|
||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||
_addedTopWidget->resize(_addedTopWidget->width(), addedTopHeight);
|
||||
}
|
||||
|
||||
void ProcessFilterInvite(
|
||||
@@ -279,15 +316,11 @@ void ProcessFilterInvite(
|
||||
return;
|
||||
}
|
||||
Core::App().hideMediaView();
|
||||
if (peers.empty()) {
|
||||
if (filterId) {
|
||||
strong->show(Box(AlreadyFilterBox, title));
|
||||
} else {
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = Window::Show(strong).toastParent(),
|
||||
.text = { tr::lng_group_invite_bad_link(tr::now) },
|
||||
});
|
||||
}
|
||||
if (peers.empty() && !filterId) {
|
||||
Ui::ShowMultilineToast({
|
||||
.parentOverride = Window::Show(strong).toastParent(),
|
||||
.text = { tr::lng_group_invite_bad_link(tr::now) },
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto controller = std::make_unique<ToggleChatsController>(
|
||||
@@ -298,42 +331,61 @@ void ProcessFilterInvite(
|
||||
title,
|
||||
std::move(peers));
|
||||
const auto raw = controller.get();
|
||||
auto initBox = [=](not_null<Ui::BoxContent*> box) {
|
||||
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||
box->setStyle(st::filterInviteBox);
|
||||
|
||||
using Type = Ui::FilterLinkHeaderType;
|
||||
const auto type = !filterId
|
||||
? Type::AddingFilter
|
||||
: Type::AddingChats;
|
||||
auto badge = raw->selectedValue(
|
||||
) | rpl::map([=](const base::flat_set<not_null<PeerData*>> &peers) {
|
||||
return int(peers.size());
|
||||
});
|
||||
InitFilterLinkHeader(box, [=](int addedTopHeight) {
|
||||
raw->setAddedTopHeight(addedTopHeight);
|
||||
}, type, title, rpl::duplicate(badge));
|
||||
|
||||
auto owned = Ui::FilterLinkProcessButton(
|
||||
box,
|
||||
type,
|
||||
title,
|
||||
std::move(badge));
|
||||
|
||||
const auto button = owned.data();
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto &padding = st::filterInviteBox.buttonPadding;
|
||||
button->resizeToWidth(width
|
||||
- padding.left()
|
||||
- padding.right());
|
||||
button->moveToLeft(padding.left(), padding.top());
|
||||
}, button->lifetime());
|
||||
|
||||
box->addButton(std::move(owned));
|
||||
|
||||
struct State {
|
||||
bool importing = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
|
||||
raw->selectedValue(
|
||||
) | rpl::start_with_next([=](
|
||||
base::flat_set<not_null<PeerData*>> &&peers) {
|
||||
const auto count = int(peers.size());
|
||||
|
||||
box->clearButtons();
|
||||
auto button = object_ptr<Ui::RoundButton>(
|
||||
box,
|
||||
rpl::single(count
|
||||
? u"Add %1 Chats"_q.arg(count)
|
||||
: u"Don't add chats"_q),
|
||||
st::defaultActiveButton);
|
||||
const auto raw = button.data();
|
||||
|
||||
box->widthValue(
|
||||
) | rpl::start_with_next([=](int width) {
|
||||
const auto &padding = st::filterInviteBox.buttonPadding;
|
||||
raw->resizeToWidth(width
|
||||
- padding.left()
|
||||
- padding.right());
|
||||
raw->moveToLeft(padding.left(), padding.top());
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->setClickedCallback([=] {
|
||||
if (!count) {
|
||||
button->setClickedCallback([=] {
|
||||
if (peers.empty()) {
|
||||
box->closeBox();
|
||||
//} else if (count + alreadyInFilter() >= ...) {
|
||||
// #TODO filters
|
||||
} else {
|
||||
ImportInvite(weak, slug, peers);
|
||||
} else if (!state->importing) {
|
||||
state->importing = true;
|
||||
ImportInvite(weak, slug, peers, crl::guard(box, [=] {
|
||||
box->closeBox();
|
||||
}), crl::guard(box, [=] {
|
||||
state->importing = false;
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
box->addButton(std::move(button));
|
||||
}, box->lifetime());
|
||||
};
|
||||
strong->show(
|
||||
|
Reference in New Issue
Block a user