mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Allow editing tag names in Saved Messages.
This commit is contained in:
parent
32462fca9b
commit
d116c8fea0
13 changed files with 289 additions and 15 deletions
|
@ -2801,6 +2801,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_context_seen_reacted_all" = "Show All Reactions";
|
"lng_context_seen_reacted_all" = "Show All Reactions";
|
||||||
"lng_context_set_as_quick" = "Set as Quick";
|
"lng_context_set_as_quick" = "Set as Quick";
|
||||||
"lng_context_filter_by_tag" = "Filter by Tag";
|
"lng_context_filter_by_tag" = "Filter by Tag";
|
||||||
|
"lng_context_tag_add_name" = "Add Name";
|
||||||
|
"lng_context_tag_edit_name" = "Edit Name";
|
||||||
"lng_context_remove_tag" = "Remove Tag";
|
"lng_context_remove_tag" = "Remove Tag";
|
||||||
"lng_context_delete_from_disk" = "Delete from disk";
|
"lng_context_delete_from_disk" = "Delete from disk";
|
||||||
"lng_context_delete_all_files" = "Delete all files";
|
"lng_context_delete_all_files" = "Delete all files";
|
||||||
|
@ -2810,6 +2812,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_context_read_hidden" = "read";
|
"lng_context_read_hidden" = "read";
|
||||||
"lng_context_read_show" = "show when";
|
"lng_context_read_show" = "show when";
|
||||||
|
|
||||||
|
"lng_edit_tag_about" = "You can label your emoji tag with a text name.";
|
||||||
|
"lng_edit_tag_name" = "Name";
|
||||||
|
|
||||||
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
|
"lng_context_animated_emoji" = "This message contains emoji from **{name} pack**.";
|
||||||
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
|
"lng_context_animated_emoji_many#one" = "This message contains emoji from **{count} pack**.";
|
||||||
"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**.";
|
"lng_context_animated_emoji_many#other" = "This message contains emoji from **{count} packs**.";
|
||||||
|
|
|
@ -1307,3 +1307,22 @@ ttlMediaButton: RoundButton(defaultActiveButton) {
|
||||||
textTop: 6px;
|
textTop: 6px;
|
||||||
}
|
}
|
||||||
ttlMediaButtonBottomSkip: 14px;
|
ttlMediaButtonBottomSkip: 14px;
|
||||||
|
|
||||||
|
editTagAbout: FlatLabel(defaultFlatLabel) {
|
||||||
|
minWidth: 256px;
|
||||||
|
}
|
||||||
|
editTagField: InputField(defaultInputField) {
|
||||||
|
textBg: transparent;
|
||||||
|
textMargins: margins(24px, 10px, 32px, 0px);
|
||||||
|
|
||||||
|
placeholderFg: placeholderFg;
|
||||||
|
placeholderFgActive: placeholderFgActive;
|
||||||
|
placeholderFgError: placeholderFgActive;
|
||||||
|
placeholderMargins: margins(2px, 0px, 2px, 0px);
|
||||||
|
placeholderScale: 0.;
|
||||||
|
|
||||||
|
heightMin: 32px;
|
||||||
|
}
|
||||||
|
editTagLimit: FlatLabel(defaultFlatLabel) {
|
||||||
|
textFg: windowSubTextFg;
|
||||||
|
}
|
||||||
|
|
|
@ -356,6 +356,15 @@ const std::vector<MyTagInfo> &Reactions::myTagsInfo() const {
|
||||||
return _myTagsInfo;
|
return _myTagsInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString &Reactions::myTagTitle(const ReactionId &id) const {
|
||||||
|
const auto i = ranges::find(_myTagsInfo, id, &::Data::MyTagInfo::id);
|
||||||
|
if (i != end(_myTagsInfo)) {
|
||||||
|
return i->title;
|
||||||
|
}
|
||||||
|
static const auto kEmpty = QString();
|
||||||
|
return kEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
ReactionId Reactions::favoriteId() const {
|
ReactionId Reactions::favoriteId() const {
|
||||||
return _favoriteId;
|
return _favoriteId;
|
||||||
}
|
}
|
||||||
|
@ -415,6 +424,23 @@ void Reactions::decrementMyTag(const ReactionId &id) {
|
||||||
scheduleMyTagsUpdate();
|
scheduleMyTagsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::renameTag(const ReactionId &id, const QString &name) {
|
||||||
|
auto i = ranges::find(_myTagsInfo, id, &MyTagInfo::id);
|
||||||
|
if (i == end(_myTagsInfo) || i->title == name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i->title = name;
|
||||||
|
scheduleMyTagsUpdate();
|
||||||
|
_myTagRenamed.fire_copy(id);
|
||||||
|
|
||||||
|
using Flag = MTPmessages_UpdateSavedReactionTag::Flag;
|
||||||
|
_owner->session().api().request(MTPmessages_UpdateSavedReactionTag(
|
||||||
|
MTP_flags(name.isEmpty() ? Flag(0) : Flag::f_title),
|
||||||
|
ReactionToMTP(id),
|
||||||
|
MTP_string(name)
|
||||||
|
)).send();
|
||||||
|
}
|
||||||
|
|
||||||
void Reactions::scheduleMyTagsUpdate() {
|
void Reactions::scheduleMyTagsUpdate() {
|
||||||
_myTagsUpdateScheduled = true;
|
_myTagsUpdateScheduled = true;
|
||||||
crl::on_main(&session(), [=] {
|
crl::on_main(&session(), [=] {
|
||||||
|
@ -496,6 +522,10 @@ rpl::producer<> Reactions::tagsUpdates() const {
|
||||||
return _tagsUpdated.events();
|
return _tagsUpdated.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpl::producer<ReactionId> Reactions::myTagRenamed() const {
|
||||||
|
return _myTagRenamed.events();
|
||||||
|
}
|
||||||
|
|
||||||
void Reactions::preloadImageFor(const ReactionId &id) {
|
void Reactions::preloadImageFor(const ReactionId &id) {
|
||||||
if (_images.contains(id) || id.emoji().isEmpty()) {
|
if (_images.contains(id) || id.emoji().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -850,9 +880,21 @@ void Reactions::updateGeneric(const MTPDmessages_stickerSet &data) {
|
||||||
|
|
||||||
void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) {
|
void Reactions::updateMyTags(const MTPDmessages_savedReactionTags &data) {
|
||||||
_myTagsHash = data.vhash().v;
|
_myTagsHash = data.vhash().v;
|
||||||
_myTagsInfo = ListFromMTP(data);
|
auto list = ListFromMTP(data);
|
||||||
|
auto renamed = base::flat_set<ReactionId>();
|
||||||
|
for (const auto &info : list) {
|
||||||
|
const auto j = ranges::find(_myTagsInfo, info.id, &MyTagInfo::id);
|
||||||
|
const auto was = (j != end(_myTagsInfo)) ? j->title : QString();
|
||||||
|
if (info.title != was) {
|
||||||
|
renamed.emplace(info.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_myTagsInfo = std::move(list);
|
||||||
_myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags);
|
_myTags = resolveByInfos(_myTagsInfo, _unresolvedMyTags);
|
||||||
_myTagsUpdated.fire({});
|
_myTagsUpdated.fire({});
|
||||||
|
for (const auto &id : renamed) {
|
||||||
|
_myTagRenamed.fire_copy(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reactions::updateTags(const MTPDmessages_reactions &data) {
|
void Reactions::updateTags(const MTPDmessages_reactions &data) {
|
||||||
|
@ -1304,6 +1346,7 @@ void MessageReactions::remove(const ReactionId &id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
i->my = false;
|
i->my = false;
|
||||||
|
const auto tags = _item->reactionsAreTags();
|
||||||
const auto removed = !--i->count;
|
const auto removed = !--i->count;
|
||||||
if (removed) {
|
if (removed) {
|
||||||
_list.erase(i);
|
_list.erase(i);
|
||||||
|
@ -1321,6 +1364,9 @@ void MessageReactions::remove(const ReactionId &id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (tags) {
|
||||||
|
history->owner().reactions().decrementMyTag(id);
|
||||||
|
}
|
||||||
auto &owner = history->owner();
|
auto &owner = history->owner();
|
||||||
owner.reactions().send(_item, false);
|
owner.reactions().send(_item, false);
|
||||||
owner.notifyItemDataChange(_item);
|
owner.notifyItemDataChange(_item);
|
||||||
|
|
|
@ -93,11 +93,13 @@ public:
|
||||||
};
|
};
|
||||||
[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
|
[[nodiscard]] const std::vector<Reaction> &list(Type type) const;
|
||||||
[[nodiscard]] const std::vector<MyTagInfo> &myTagsInfo() const;
|
[[nodiscard]] const std::vector<MyTagInfo> &myTagsInfo() const;
|
||||||
|
[[nodiscard]] const QString &myTagTitle(const ReactionId &id) const;
|
||||||
[[nodiscard]] ReactionId favoriteId() const;
|
[[nodiscard]] ReactionId favoriteId() const;
|
||||||
[[nodiscard]] const Reaction *favorite() const;
|
[[nodiscard]] const Reaction *favorite() const;
|
||||||
void setFavorite(const ReactionId &id);
|
void setFavorite(const ReactionId &id);
|
||||||
void incrementMyTag(const ReactionId &id);
|
void incrementMyTag(const ReactionId &id);
|
||||||
void decrementMyTag(const ReactionId &id);
|
void decrementMyTag(const ReactionId &id);
|
||||||
|
void renameTag(const ReactionId &id, const QString &name);
|
||||||
[[nodiscard]] DocumentData *chooseGenericAnimation(
|
[[nodiscard]] DocumentData *chooseGenericAnimation(
|
||||||
not_null<DocumentData*> custom) const;
|
not_null<DocumentData*> custom) const;
|
||||||
|
|
||||||
|
@ -107,6 +109,7 @@ public:
|
||||||
[[nodiscard]] rpl::producer<> favoriteUpdates() const;
|
[[nodiscard]] rpl::producer<> favoriteUpdates() const;
|
||||||
[[nodiscard]] rpl::producer<> myTagsUpdates() const;
|
[[nodiscard]] rpl::producer<> myTagsUpdates() const;
|
||||||
[[nodiscard]] rpl::producer<> tagsUpdates() const;
|
[[nodiscard]] rpl::producer<> tagsUpdates() const;
|
||||||
|
[[nodiscard]] rpl::producer<ReactionId> myTagRenamed() const;
|
||||||
|
|
||||||
enum class ImageSize {
|
enum class ImageSize {
|
||||||
BottomInfo,
|
BottomInfo,
|
||||||
|
@ -223,6 +226,7 @@ private:
|
||||||
rpl::event_stream<> _favoriteUpdated;
|
rpl::event_stream<> _favoriteUpdated;
|
||||||
rpl::event_stream<> _myTagsUpdated;
|
rpl::event_stream<> _myTagsUpdated;
|
||||||
rpl::event_stream<> _tagsUpdated;
|
rpl::event_stream<> _tagsUpdated;
|
||||||
|
rpl::event_stream<ReactionId> _myTagRenamed;
|
||||||
|
|
||||||
// We need &i->second stay valid while inserting new items.
|
// We need &i->second stay valid while inserting new items.
|
||||||
// So we use std::map instead of base::flat_map here.
|
// So we use std::map instead of base::flat_map here.
|
||||||
|
|
|
@ -295,6 +295,16 @@ Session::Session(not_null<Main::Session*> session)
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
|
_reactions->myTagRenamed(
|
||||||
|
) | rpl::start_with_next([=](const ReactionId &id) {
|
||||||
|
const auto i = _viewsByTag.find(id);
|
||||||
|
if (i != end(_viewsByTag)) {
|
||||||
|
for (const auto &view : i->second) {
|
||||||
|
notifyItemDataChange(view->data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
Spellchecker::HighlightReady(
|
Spellchecker::HighlightReady(
|
||||||
) | rpl::start_with_next([=](uint64 processId) {
|
) | rpl::start_with_next([=](uint64 processId) {
|
||||||
highlightProcessDone(processId);
|
highlightProcessDone(processId);
|
||||||
|
@ -4608,6 +4618,28 @@ rpl::producer<not_null<PeerData*>> Session::peerDecorationsUpdated() const {
|
||||||
return _peerDecorationsUpdated.events();
|
return _peerDecorationsUpdated.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::viewTagsChanged(
|
||||||
|
not_null<ViewElement*> view,
|
||||||
|
std::vector<Data::ReactionId> &&was,
|
||||||
|
std::vector<Data::ReactionId> &&now) {
|
||||||
|
for (const auto &id : now) {
|
||||||
|
const auto i = ranges::remove(was, id);
|
||||||
|
if (i != end(was)) {
|
||||||
|
was.erase(i, end(was));
|
||||||
|
} else {
|
||||||
|
_viewsByTag[id].emplace(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &id : was) {
|
||||||
|
const auto i = _viewsByTag.find(id);
|
||||||
|
if (i != end(_viewsByTag)
|
||||||
|
&& i->second.remove(view)
|
||||||
|
&& i->second.empty()) {
|
||||||
|
_viewsByTag.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Session::clearLocalStorage() {
|
void Session::clearLocalStorage() {
|
||||||
_cache->close();
|
_cache->close();
|
||||||
_cache->clear();
|
_cache->clear();
|
||||||
|
|
|
@ -62,6 +62,7 @@ class NotifySettings;
|
||||||
class CustomEmojiManager;
|
class CustomEmojiManager;
|
||||||
class Stories;
|
class Stories;
|
||||||
class SavedMessages;
|
class SavedMessages;
|
||||||
|
struct ReactionId;
|
||||||
|
|
||||||
struct RepliesReadTillUpdate {
|
struct RepliesReadTillUpdate {
|
||||||
FullMsgId id;
|
FullMsgId id;
|
||||||
|
@ -742,6 +743,11 @@ public:
|
||||||
void applyStatsDcId(not_null<ChannelData*>, MTP::DcId);
|
void applyStatsDcId(not_null<ChannelData*>, MTP::DcId);
|
||||||
[[nodiscard]] MTP::DcId statsDcId(not_null<ChannelData*>);
|
[[nodiscard]] MTP::DcId statsDcId(not_null<ChannelData*>);
|
||||||
|
|
||||||
|
void viewTagsChanged(
|
||||||
|
not_null<ViewElement*> view,
|
||||||
|
std::vector<ReactionId> &&was,
|
||||||
|
std::vector<ReactionId> &&now);
|
||||||
|
|
||||||
void clearLocalStorage();
|
void clearLocalStorage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1005,9 +1011,14 @@ private:
|
||||||
|
|
||||||
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
base::flat_map<uint64, not_null<GroupCall*>> _groupCalls;
|
||||||
rpl::event_stream<InviteToCall> _invitesToCalls;
|
rpl::event_stream<InviteToCall> _invitesToCalls;
|
||||||
base::flat_map<uint64, base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
base::flat_map<
|
||||||
|
uint64,
|
||||||
|
base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
|
||||||
|
|
||||||
base::flat_set<not_null<ViewElement*>> _shownSpoilers;
|
base::flat_set<not_null<ViewElement*>> _shownSpoilers;
|
||||||
|
base::flat_map<
|
||||||
|
ReactionId,
|
||||||
|
base::flat_set<not_null<ViewElement*>>> _viewsByTag;
|
||||||
|
|
||||||
History *_topPromoted = nullptr;
|
History *_topPromoted = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -1430,8 +1430,10 @@ void InnerWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
});
|
});
|
||||||
} else if (_pressed) {
|
} else if (_pressed) {
|
||||||
auto row = _pressed;
|
auto row = _pressed;
|
||||||
const auto updateCallback = [this, row] {
|
const auto weak = Ui::MakeWeak(this);
|
||||||
if (!_pinnedShiftAnimation.animating()) {
|
const auto updateCallback = [weak, row] {
|
||||||
|
const auto strong = weak.data();
|
||||||
|
if (!strong || !strong->_pinnedShiftAnimation.animating()) {
|
||||||
row->entry()->updateChatListEntry();
|
row->entry()->updateChatListEntry();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -110,8 +110,12 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (const auto &reaction : list) {
|
for (const auto &reaction : list) {
|
||||||
|
if (reaction.count > 0
|
||||||
|
|| ranges::contains(_added, reaction.id)
|
||||||
|
|| ranges::contains(selected, reaction.id)) {
|
||||||
push(reaction.id, ComposeText(reaction));
|
push(reaction.id, ComposeText(reaction));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (const auto &reaction : _added) {
|
for (const auto &reaction : _added) {
|
||||||
if (!ranges::contains(_tags, reaction, &Tag::id)) {
|
if (!ranges::contains(_tags, reaction, &Tag::id)) {
|
||||||
push(reaction, QString());
|
push(reaction, QString());
|
||||||
|
@ -119,6 +123,7 @@ void SearchTags::fill(const std::vector<Data::Reaction> &list) {
|
||||||
}
|
}
|
||||||
if (_width > 0) {
|
if (_width > 0) {
|
||||||
layout();
|
layout();
|
||||||
|
_repaintRequests.fire({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "menu/menu_send.h"
|
#include "menu/menu_send.h"
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/boxes/show_or_premium_box.h"
|
#include "ui/boxes/show_or_premium_box.h"
|
||||||
|
#include "ui/widgets/fields/input_field.h"
|
||||||
|
#include "ui/power_saving.h"
|
||||||
#include "boxes/delete_messages_box.h"
|
#include "boxes/delete_messages_box.h"
|
||||||
#include "boxes/report_messages_box.h"
|
#include "boxes/report_messages_box.h"
|
||||||
#include "boxes/sticker_set_box.h"
|
#include "boxes/sticker_set_box.h"
|
||||||
|
@ -85,6 +87,7 @@ namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kRescheduleLimit = 20;
|
constexpr auto kRescheduleLimit = 20;
|
||||||
|
constexpr auto kTagNameLimit = 12;
|
||||||
|
|
||||||
bool HasEditMessageAction(
|
bool HasEditMessageAction(
|
||||||
const ContextMenuRequest &request,
|
const ContextMenuRequest &request,
|
||||||
|
@ -980,6 +983,112 @@ void AddCopyLinkAction(
|
||||||
&st::menuIconCopy);
|
&st::menuIconCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EditTagBox(
|
||||||
|
not_null<Ui::GenericBox*> box,
|
||||||
|
not_null<Window::SessionController*> controller,
|
||||||
|
const Data::ReactionId &id) {
|
||||||
|
const auto owner = &controller->session().data();
|
||||||
|
const auto title = owner->reactions().myTagTitle(id);
|
||||||
|
box->setTitle(title.isEmpty()
|
||||||
|
? tr::lng_context_tag_add_name()
|
||||||
|
: tr::lng_context_tag_edit_name());
|
||||||
|
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
tr::lng_edit_tag_about(),
|
||||||
|
st::editTagAbout));
|
||||||
|
const auto field = box->addRow(object_ptr<Ui::InputField>(
|
||||||
|
box,
|
||||||
|
st::editTagField,
|
||||||
|
tr::lng_edit_tag_name(),
|
||||||
|
title));
|
||||||
|
field->setMaxLength(kTagNameLimit * 2);
|
||||||
|
box->setFocusCallback([=] {
|
||||||
|
field->setFocusFast();
|
||||||
|
});
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
std::unique_ptr<Ui::Text::CustomEmoji> custom;
|
||||||
|
QImage image;
|
||||||
|
rpl::variable<int> length;
|
||||||
|
};
|
||||||
|
const auto state = field->lifetime().make_state<State>();
|
||||||
|
state->length = rpl::single(
|
||||||
|
int(title.size())
|
||||||
|
) | rpl::then(field->changes() | rpl::map([=] {
|
||||||
|
return int(field->getLastText().size());
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (const auto customId = id.custom()) {
|
||||||
|
state->custom = owner->customEmojiManager().create(
|
||||||
|
customId,
|
||||||
|
[=] { field->update(); });
|
||||||
|
} else {
|
||||||
|
owner->reactions().preloadImageFor(id);
|
||||||
|
}
|
||||||
|
field->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||||
|
auto p = QPainter(field);
|
||||||
|
const auto top = st::editTagField.textMargins.top();
|
||||||
|
if (const auto custom = state->custom.get()) {
|
||||||
|
const auto inactive = !field->window()->isActiveWindow();
|
||||||
|
custom->paint(p, {
|
||||||
|
.textColor = st::windowFg->c,
|
||||||
|
.now = crl::now(),
|
||||||
|
.position = QPoint(0, top),
|
||||||
|
.paused = inactive || On(PowerSaving::kEmojiChat),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (state->image.isNull()) {
|
||||||
|
state->image = owner->reactions().resolveImageFor(
|
||||||
|
id,
|
||||||
|
::Data::Reactions::ImageSize::InlineList);
|
||||||
|
}
|
||||||
|
if (!state->image.isNull()) {
|
||||||
|
const auto size = st::reactionInlineSize;
|
||||||
|
const auto skip = (size - st::reactionInlineImage) / 2;
|
||||||
|
p.drawImage(skip, top + skip, state->image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, field->lifetime());
|
||||||
|
const auto warning = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
field,
|
||||||
|
state->length.value() | rpl::map([](int count) {
|
||||||
|
return (count > kTagNameLimit / 2)
|
||||||
|
? QString::number(kTagNameLimit - count)
|
||||||
|
: QString();
|
||||||
|
}),
|
||||||
|
st::editTagLimit);
|
||||||
|
state->length.value() | rpl::map(
|
||||||
|
rpl::mappers::_1 > kTagNameLimit
|
||||||
|
) | rpl::start_with_next([=](bool exceeded) {
|
||||||
|
warning->setTextColorOverride(exceeded
|
||||||
|
? st::attentionButtonFg->c
|
||||||
|
: std::optional<QColor>());
|
||||||
|
}, warning->lifetime());
|
||||||
|
rpl::combine(
|
||||||
|
field->sizeValue(),
|
||||||
|
warning->sizeValue()
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
warning->moveToRight(0, st::editTagField.textMargins.top());
|
||||||
|
}, warning->lifetime());
|
||||||
|
warning->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
|
box->addButton(tr::lng_settings_save(), [=] {
|
||||||
|
const auto text = field->getLastText();
|
||||||
|
if (text.size() > kTagNameLimit) {
|
||||||
|
field->showError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto weak = Ui::MakeWeak(box);
|
||||||
|
controller->session().data().reactions().renameTag(id, text);
|
||||||
|
if (const auto strong = weak.data()) {
|
||||||
|
strong->closeBox();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
box->addButton(tr::lng_cancel(), [=] {
|
||||||
|
box->closeBox();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ContextMenuRequest::ContextMenuRequest(
|
ContextMenuRequest::ContextMenuRequest(
|
||||||
|
@ -1342,6 +1451,13 @@ void ShowTagMenu(
|
||||||
});
|
});
|
||||||
}, &st::menuIconFave);
|
}, &st::menuIconFave);
|
||||||
|
|
||||||
|
const auto editLabel = owner->reactions().myTagTitle(id).isEmpty()
|
||||||
|
? tr::lng_context_tag_add_name(tr::now)
|
||||||
|
: tr::lng_context_tag_edit_name(tr::now);
|
||||||
|
(*menu)->addAction(editLabel, [=] {
|
||||||
|
controller->show(Box(EditTagBox, controller, id));
|
||||||
|
}, &st::menuIconEdit);
|
||||||
|
|
||||||
const auto removeTag = [=] {
|
const auto removeTag = [=] {
|
||||||
if (const auto item = owner->message(itemId)) {
|
if (const auto item = owner->message(itemId)) {
|
||||||
const auto &list = item->reactions();
|
const auto &list = item->reactions();
|
||||||
|
|
|
@ -435,6 +435,21 @@ Message::~Message() {
|
||||||
_fromNameStatus = nullptr;
|
_fromNameStatus = nullptr;
|
||||||
checkHeavyPart();
|
checkHeavyPart();
|
||||||
}
|
}
|
||||||
|
setReactions(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Message::setReactions(std::unique_ptr<Reactions::InlineList> list) {
|
||||||
|
auto was = _reactions
|
||||||
|
? _reactions->computeTagsList()
|
||||||
|
: std::vector<Data::ReactionId>();
|
||||||
|
_reactions = std::move(list);
|
||||||
|
auto now = _reactions
|
||||||
|
? _reactions->computeTagsList()
|
||||||
|
: std::vector<Data::ReactionId>();
|
||||||
|
if (!was.empty() || !now.empty()) {
|
||||||
|
auto &owner = history()->owner();
|
||||||
|
owner.viewTagsChanged(this, std::move(was), std::move(now));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::refreshRightBadge() {
|
void Message::refreshRightBadge() {
|
||||||
|
@ -2958,7 +2973,7 @@ void Message::refreshReactions() {
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
const auto &list = item->reactions();
|
const auto &list = item->reactions();
|
||||||
if (list.empty() || embedReactionsInBottomInfo()) {
|
if (list.empty() || embedReactionsInBottomInfo()) {
|
||||||
_reactions = nullptr;
|
setReactions(nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using namespace Reactions;
|
using namespace Reactions;
|
||||||
|
@ -2988,13 +3003,19 @@ void Message::refreshReactions() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
_reactions = std::make_unique<InlineList>(
|
setReactions(std::make_unique<InlineList>(
|
||||||
&item->history()->owner().reactions(),
|
&item->history()->owner().reactions(),
|
||||||
handlerFactory,
|
handlerFactory,
|
||||||
[=] { customEmojiRepaint(); },
|
[=] { customEmojiRepaint(); },
|
||||||
std::move(reactionsData));
|
std::move(reactionsData)));
|
||||||
} else {
|
} else {
|
||||||
|
auto was = _reactions->computeTagsList();
|
||||||
_reactions->update(std::move(reactionsData), width());
|
_reactions->update(std::move(reactionsData), width());
|
||||||
|
auto now = _reactions->computeTagsList();
|
||||||
|
if (!was.empty() || !now.empty()) {
|
||||||
|
auto &owner = history()->owner();
|
||||||
|
owner.viewTagsChanged(this, std::move(was), std::move(now));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,6 +289,7 @@ private:
|
||||||
[[nodiscard]] ClickHandlerPtr psaTooltipLink() const;
|
[[nodiscard]] ClickHandlerPtr psaTooltipLink() const;
|
||||||
void psaTooltipToggled(bool shown) const;
|
void psaTooltipToggled(bool shown) const;
|
||||||
|
|
||||||
|
void setReactions(std::unique_ptr<Reactions::InlineList> list);
|
||||||
void refreshRightBadge();
|
void refreshRightBadge();
|
||||||
void refreshReactions();
|
void refreshReactions();
|
||||||
void validateFromNameText(PeerData *from) const;
|
void validateFromNameText(PeerData *from) const;
|
||||||
|
|
|
@ -88,6 +88,19 @@ void InlineList::removeSkipBlock() {
|
||||||
_skipBlock = {};
|
_skipBlock = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InlineList::areTags() const {
|
||||||
|
return _data.flags & Data::Flag::Tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ReactionId> InlineList::computeTagsList() const {
|
||||||
|
if (!areTags()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return _buttons | ranges::views::transform(
|
||||||
|
&Button::id
|
||||||
|
) | ranges::to_vector;
|
||||||
|
}
|
||||||
|
|
||||||
bool InlineList::hasCustomEmoji() const {
|
bool InlineList::hasCustomEmoji() const {
|
||||||
return _hasCustomEmoji;
|
return _hasCustomEmoji;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +132,7 @@ void InlineList::layoutButtons() {
|
||||||
) | ranges::views::transform([](const MessageReaction &reaction) {
|
) | ranges::views::transform([](const MessageReaction &reaction) {
|
||||||
return not_null{ &reaction };
|
return not_null{ &reaction };
|
||||||
}) | ranges::to_vector;
|
}) | ranges::to_vector;
|
||||||
const auto tags = _data.flags & Data::Flag::Tags;
|
const auto tags = areTags();
|
||||||
const auto &infos = _owner->myTagsInfo();
|
const auto &infos = _owner->myTagsInfo();
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
const auto &list = _owner->list(::Data::Reactions::Type::All);
|
const auto &list = _owner->list(::Data::Reactions::Type::All);
|
||||||
|
@ -148,10 +161,7 @@ void InlineList::layoutButtons() {
|
||||||
? std::move(*i)
|
? std::move(*i)
|
||||||
: prepareButtonWithId(id));
|
: prepareButtonWithId(id));
|
||||||
if (tags) {
|
if (tags) {
|
||||||
const auto i = ranges::find(infos, id, &::Data::MyTagInfo::id);
|
setButtonTag(buttons.back(), _owner->myTagTitle(id));
|
||||||
setButtonTag(
|
|
||||||
buttons.back(),
|
|
||||||
(i == end(infos)) ? QString() : i->title);
|
|
||||||
} else if (const auto j = _data.recent.find(id)
|
} else if (const auto j = _data.recent.find(id)
|
||||||
; j != end(_data.recent) && !j->second.empty()) {
|
; j != end(_data.recent) && !j->second.empty()) {
|
||||||
setButtonUserpics(buttons.back(), j->second);
|
setButtonUserpics(buttons.back(), j->second);
|
||||||
|
@ -367,7 +377,7 @@ void InlineList::paint(
|
||||||
const auto padding = st::reactionInlinePadding;
|
const auto padding = st::reactionInlinePadding;
|
||||||
const auto size = st::reactionInlineSize;
|
const auto size = st::reactionInlineSize;
|
||||||
const auto skip = (size - st::reactionInlineImage) / 2;
|
const auto skip = (size - st::reactionInlineImage) / 2;
|
||||||
const auto tags = (_data.flags & Data::Flag::Tags);
|
const auto tags = areTags();
|
||||||
const auto inbubble = (_data.flags & Data::Flag::InBubble);
|
const auto inbubble = (_data.flags & Data::Flag::InBubble);
|
||||||
const auto flipped = (_data.flags & Data::Flag::Flipped);
|
const auto flipped = (_data.flags & Data::Flag::Flipped);
|
||||||
p.setFont(tags ? st::reactionInlineTagFont : st::semiboldFont);
|
p.setFont(tags ? st::reactionInlineTagFont : st::semiboldFont);
|
||||||
|
@ -613,7 +623,7 @@ void InlineList::paintSingleBg(
|
||||||
const QColor &color,
|
const QColor &color,
|
||||||
float64 opacity) const {
|
float64 opacity) const {
|
||||||
p.setOpacity(opacity);
|
p.setOpacity(opacity);
|
||||||
if (!(_data.flags & Data::Flag::Tags)) {
|
if (!areTags()) {
|
||||||
const auto radius = fill.height() / 2.;
|
const auto radius = fill.height() / 2.;
|
||||||
p.setBrush(color);
|
p.setBrush(color);
|
||||||
p.drawRoundedRect(fill, radius, radius);
|
p.drawRoundedRect(fill, radius, radius);
|
||||||
|
|
|
@ -70,6 +70,8 @@ public:
|
||||||
void updateSkipBlock(int width, int height);
|
void updateSkipBlock(int width, int height);
|
||||||
void removeSkipBlock();
|
void removeSkipBlock();
|
||||||
|
|
||||||
|
[[nodiscard]] bool areTags() const;
|
||||||
|
[[nodiscard]] std::vector<ReactionId> computeTagsList() const;
|
||||||
[[nodiscard]] bool hasCustomEmoji() const;
|
[[nodiscard]] bool hasCustomEmoji() const;
|
||||||
void unloadCustomEmoji();
|
void unloadCustomEmoji();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue