Improve reply options edit design.

This commit is contained in:
John Preston 2023-10-25 11:00:22 +04:00
parent b463c76eca
commit 1409d38ac3
26 changed files with 219 additions and 86 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -2530,6 +2530,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_attached_stickers" = "Attached Stickers";
"lng_context_to_msg" = "Go To Message";
"lng_context_reply_msg" = "Reply";
"lng_context_quote_and_reply" = "Quote & Reply";
"lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now";
@ -2634,6 +2635,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_inline_switch_choose" = "Choose conversation...";
"lng_inline_switch_cant" = "Sorry, no way to write here :(";
"lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat";
"lng_reply_show_in_chat" = "Show in Chat";
"lng_reply_remove" = "Do Not Reply";
"lng_reply_about_quote" = "You can select specific part to quote.";
"lng_reply_options_header" = "Reply to Message";
"lng_reply_options_quote" = "Update Quote";
"lng_reply_header_short" = "Reply";
"lng_reply_quote_selected" = "Quote Selected";
"lng_link_options_header" = "Link Preview Settings";
"lng_link_header_short" = "Link";
"lng_link_move_up" = "Move Up";
"lng_link_move_down" = "Move Down";
"lng_link_shrink_photo" = "Shrink Photo";
"lng_link_enlarge_photo" = "Enlarge Photo";
"lng_link_remove" = "Do Not Preview";
"lng_link_about_choose" = "Click on a link to generate its preview.";
"lng_share_cant" = "Sorry, no way to share here :(";
"lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :(";
"lng_reply_cant_forward" = "Sorry, you can't reply to a message that was sent before the group was upgraded to a supergroup. Do you wish to forward it and add your comment?";

View File

@ -70,22 +70,8 @@ ChatFilter ChatFilter::FromTL(
| (data.is_exclude_read() ? Flag::NoRead : Flag(0))
| (data.is_exclude_archived() ? Flag::NoArchived : Flag(0));
auto &&to_histories = ranges::views::transform([&](
const MTPInputPeer &data) {
const auto peer = data.match([&](const MTPDinputPeerUser &data) {
const auto user = owner->user(data.vuser_id().v);
user->setAccessHash(data.vaccess_hash().v);
return (PeerData*)user;
}, [&](const MTPDinputPeerChat &data) {
return (PeerData*)owner->chat(data.vchat_id().v);
}, [&](const MTPDinputPeerChannel &data) {
const auto channel = owner->channel(data.vchannel_id().v);
channel->setAccessHash(data.vaccess_hash().v);
return (PeerData*)channel;
}, [&](const MTPDinputPeerSelf &data) {
return (PeerData*)owner->session().user();
}, [&](const auto &data) {
return (PeerData*)nullptr;
});
const MTPInputPeer &input) {
const auto peer = Data::PeerFromInputMTP(owner, input);
return peer ? owner->history(peer).get() : nullptr;
}) | ranges::views::filter([](History *history) {
return history != nullptr;

View File

@ -81,12 +81,10 @@ void ApplyPeerCloudDraft(
session,
draft.ventities().value_or_empty()))
};
const auto reply = draft.vreply_to()
? ReplyFieldsFromMTP(history, *draft.vreply_to())
: ReplyFields();
const auto replyPeerId = reply.externalPeerId
? reply.externalPeerId
: peerId;
auto replyTo = draft.vreply_to()
? ReplyToFromMTP(history, *draft.vreply_to())
: FullReplyTo();
replyTo.topicRootId = topicRootId;
auto webpage = WebPageDraft{
.invert = draft.is_invert_media(),
.removed = draft.is_no_webpage(),
@ -106,14 +104,7 @@ void ApplyPeerCloudDraft(
}
auto cloudDraft = std::make_unique<Draft>(
textWithTags,
FullReplyTo{
.messageId = FullMsgId(replyPeerId, reply.messageId),
.quote = reply.quote,
.storyId = (reply.storyId
? FullStoryId{ replyPeerId, reply.storyId }
: FullStoryId()),
.topicRootId = topicRootId,
},
replyTo,
MessageCursor(Ui::kQFixedMax, Ui::kQFixedMax, Ui::kQFixedMax),
std::move(webpage));
cloudDraft->date = date;

View File

@ -126,6 +126,40 @@ AllowedReactions Parse(const MTPChatReactions &value) {
});
}
PeerData *PeerFromInputMTP(
not_null<Session*> owner,
const MTPInputPeer &input) {
return input.match([&](const MTPDinputPeerUser &data) {
const auto user = owner->user(data.vuser_id().v);
user->setAccessHash(data.vaccess_hash().v);
return (PeerData*)user;
}, [&](const MTPDinputPeerChat &data) {
return (PeerData*)owner->chat(data.vchat_id().v);
}, [&](const MTPDinputPeerChannel &data) {
const auto channel = owner->channel(data.vchannel_id().v);
channel->setAccessHash(data.vaccess_hash().v);
return (PeerData*)channel;
}, [&](const MTPDinputPeerSelf &data) {
return (PeerData*)owner->session().user();
}, [&](const auto &data) {
return (PeerData*)nullptr;
});
}
UserData *UserFromInputMTP(
not_null<Session*> owner,
const MTPInputUser &input) {
return input.match([&](const MTPDinputUser &data) {
const auto user = owner->user(data.vuser_id().v);
user->setAccessHash(data.vaccess_hash().v);
return user.get();
}, [&](const MTPDinputUserSelf &data) {
return owner->session().user().get();
}, [](const auto &data) {
return (UserData*)nullptr;
});
}
} // namespace Data
PeerClickHandler::PeerClickHandler(not_null<PeerData*> peer)

View File

@ -116,6 +116,12 @@ bool operator<(const AllowedReactions &a, const AllowedReactions &b);
bool operator==(const AllowedReactions &a, const AllowedReactions &b);
[[nodiscard]] AllowedReactions Parse(const MTPChatReactions &value);
[[nodiscard]] PeerData *PeerFromInputMTP(
not_null<Session*> owner,
const MTPInputPeer &input);
[[nodiscard]] UserData *UserFromInputMTP(
not_null<Session*> owner,
const MTPInputUser &input);
} // namespace Data

View File

@ -2216,35 +2216,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
return;
}
const auto itemId = item->fullId();
const auto canSendReply = [&] {
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}();
const auto canReply = canSendReply || [&] {
const auto peer = item->history()->peer;
if (const auto chat = peer->asChat()) {
return !chat->isForbidden();
} else if (const auto channel = peer->asChannel()) {
return !channel->isForbidden();
}
return true;
}();
if (canReply) {
const auto quote = selectedQuote(item);
_menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
if (canSendReply) {
_widget->replyToMessage({ itemId, quote });
} else {
HistoryView::Controls::ShowReplyToChatBox(
controller->uiShow(),
{ itemId, quote });
}
}, &st::menuIconReply);
}
const auto repliesCount = item->repliesCount();
const auto withReplies = (repliesCount > 0);
const auto topicRootId = item->history()->isForum()
@ -2408,6 +2379,45 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
};
const auto addReplyAction = [&](HistoryItem *item) {
if (!item) {
return;
}
const auto canSendReply = [&] {
const auto peer = item->history()->peer;
const auto topic = item->topic();
return topic
? Data::CanSendAnything(topic)
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}();
const auto canReply = canSendReply || [&] {
const auto peer = item->history()->peer;
if (const auto chat = peer->asChat()) {
return !chat->isForbidden();
} else if (const auto channel = peer->asChannel()) {
return !channel->isForbidden();
}
return true;
}();
if (canReply) {
const auto itemId = item->fullId();
const auto quote = selectedQuote(item);
const auto text = quote.empty()
? tr::lng_context_reply_msg(tr::now)
: tr::lng_context_quote_and_reply(tr::now);
_menu->addAction(text, [=] {
if (canSendReply) {
_widget->replyToMessage({ itemId, quote });
} else {
HistoryView::Controls::ShowReplyToChatBox(
controller->uiShow(),
{ itemId, quote });
}
}, &st::menuIconReply);
}
};
const auto lnkPhoto = link
? reinterpret_cast<PhotoData*>(
link->property(kPhotoLinkMediaProperty).toULongLong())
@ -2419,6 +2429,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
if (lnkPhoto || lnkDocument) {
const auto item = _dragStateItem;
const auto itemId = item ? item->fullId() : FullMsgId();
addReplyAction(item);
if (isUponSelected > 0) {
const auto selectedText = getSelectedText();
if (!hasCopyRestrictionForSelected()
@ -2529,6 +2541,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
: QString();
if (isUponSelected > 0) {
addReplyAction(item);
const auto selectedText = getSelectedText();
if (!hasCopyRestrictionForSelected() && !selectedText.empty()) {
_menu->addAction(
@ -2551,6 +2564,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
addItemActions(item, item);
} else {
addReplyAction(item);
addItemActions(item, albumPartItem);
if (item && !isUponSelected) {
const auto media = (view ? view->media() : nullptr);

View File

@ -435,6 +435,42 @@ ReplyFields ReplyFieldsFromMTP(
});
}
FullReplyTo ReplyToFromMTP(
not_null<History*> history,
const MTPInputReplyTo &reply) {
return reply.match([&](const MTPDinputReplyToMessage &data) {
auto result = FullReplyTo{
.messageId = { history->peer->id, data.vreply_to_msg_id().v },
};
if (const auto peer = data.vreply_to_peer_id()) {
const auto parsed = Data::PeerFromInputMTP(
&history->owner(),
*peer);
if (!parsed) {
return FullReplyTo();
}
result.messageId.peer = parsed->id;
}
result.topicRootId = data.vtop_msg_id().value_or_empty();
result.quote = TextWithEntities{
qs(data.vquote_text().value_or_empty()),
Api::EntitiesFromMTP(
&history->session(),
data.vquote_entities().value_or_empty()),
};
return result;
}, [&](const MTPDinputReplyToStory &data) {
if (const auto parsed = Data::UserFromInputMTP(
&history->owner(),
data.vuser_id())) {
return FullReplyTo{
.storyId = { parsed->id, data.vstory_id().v },
};
}
return FullReplyTo();
});
}
HistoryMessageReply::HistoryMessageReply() = default;
HistoryMessageReply &HistoryMessageReply::operator=(

View File

@ -245,6 +245,10 @@ struct ReplyFields {
not_null<History*> history,
const MTPMessageReplyHeader &reply);
[[nodiscard]] FullReplyTo ReplyToFromMTP(
not_null<History*> history,
const MTPInputReplyTo &reply);
struct HistoryMessageReply
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
HistoryMessageReply();

View File

@ -6237,7 +6237,11 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
}
} else if (const auto reply = replyTo()) {
const auto done = [=](FullReplyTo replyTo) {
replyToMessage(replyTo);
if (replyTo) {
replyToMessage(replyTo);
} else {
cancelReply();
}
};
const auto highlight = [=] {
controller()->showPeerHistory(

View File

@ -626,7 +626,11 @@ void FieldHeader::init() {
const auto history = _history;
const auto topicRootId = _topicRootId;
const auto done = [=](FullReplyTo replyTo) {
replyToMessage(replyTo);
if (replyTo) {
replyToMessage(replyTo);
} else {
_replyCancelled.fire({});
}
};
const auto clearOldReplyTo = [=, id = reply.messageId] {
ClearDraftReplyTo(history, topicRootId, id);

View File

@ -118,9 +118,7 @@ private:
bool selectionStartAfterSymbol = false;
};
const auto preview = box->addRow(
object_ptr<Ui::RpWidget>(box),
QMargins(0, 0, 0, st::settingsThemesTopSkip));
const auto preview = box->addRow(object_ptr<Ui::RpWidget>(box), {});
const auto state = preview->lifetime().make_state<State>();
state->theme = DefaultThemeOn(preview->lifetime());
@ -246,14 +244,17 @@ private:
preview->resize(width, height);
}, preview->lifetime());
preview->paintRequest() | rpl::start_with_next([=](QRect clip) {
box->setAttribute(Qt::WA_OpaquePaintEvent, false);
box->paintRequest() | rpl::start_with_next([=](QRect clip) {
Window::SectionWidget::PaintBackground(
state->theme.get(),
preview,
preview->window()->height(),
box,
box->window()->height(),
0,
clip);
}, box->lifetime());
preview->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = Painter(preview);
auto hq = PainterHighQualityEnabler(p);
p.translate(state->position);
@ -366,7 +367,7 @@ void ShowReplyToChatBox(
private:
void prepareViewHook() override {
delegate()->peerListSetTitle(rpl::single(u"Reply in..."_q));
delegate()->peerListSetTitle(tr::lng_reply_in_another_title());
}
rpl::event_stream<Chosen> _singleChosen;
@ -382,7 +383,10 @@ void ShowReplyToChatBox(
const auto state = [&] {
auto controller = std::make_unique<Controller>(session);
const auto controllerRaw = controller.get();
auto box = Box<PeerListBox>(std::move(controller), nullptr);
auto box = Box<PeerListBox>(std::move(controller), [=](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
});
const auto boxRaw = box.data();
show->show(std::move(box));
auto state = State{ boxRaw, controllerRaw };
@ -440,28 +444,31 @@ void EditReplyOptions(
show->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setWidth(st::boxWideWidth);
struct State {
rpl::variable<TextWithEntities> quote;
const auto bottom = box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box));
const auto addSkip = [&] {
const auto skip = bottom->add(object_ptr<Ui::FixedHeightWidget>(
bottom,
st::settingsPrivacySkipTop));
skip->paintRequest() | rpl::start_with_next([=](QRect clip) {
QPainter(skip).fillRect(clip, st::boxBg);
}, skip->lifetime());
};
const auto state = box->lifetime().make_state<State>();
state->quote = AddQuoteTracker(box, show, item, reply.quote);
box->setTitle(reply.quote.empty()
? rpl::single(u"Reply to Message"_q)
: rpl::single(u"Update Quote"_q));
addSkip();
Settings::AddButton(
box->verticalLayout(),
rpl::single(u"Reply in another chat"_q),
bottom,
tr::lng_reply_in_another_chat(),
st::settingsButton,
{ &st::menuIconReply }
{ &st::menuIconReplace }
)->setClickedCallback([=] {
ShowReplyToChatBox(show, reply, clearOldDraft);
});
Settings::AddButton(
box->verticalLayout(),
rpl::single(u"Show message"_q),
bottom,
tr::lng_reply_show_in_chat(),
st::settingsButton,
{ &st::menuIconShowInChat }
)->setClickedCallback(highlight);
@ -475,15 +482,38 @@ void EditReplyOptions(
};
Settings::AddButton(
box->verticalLayout(),
rpl::single(u"Remove reply"_q),
bottom,
tr::lng_reply_remove(),
st::settingsAttentionButtonWithIcon,
{ &st::menuIconDeleteAttention }
)->setClickedCallback([=] {
finish({});
});
box->addButton(rpl::single(u"Apply"_q), [=] {
if (!item->originalText().empty()) {
addSkip();
Settings::AddDividerText(
bottom,
tr::lng_reply_about_quote());
}
struct State {
rpl::variable<TextWithEntities> quote;
};
const auto state = box->lifetime().make_state<State>();
state->quote = AddQuoteTracker(box, show, item, reply.quote);
box->setTitle(reply.quote.empty()
? tr::lng_reply_options_header()
: tr::lng_reply_options_quote());
auto save = state->quote.value(
) | rpl::map([=](const TextWithEntities &quote) {
return quote.empty()
? tr::lng_settings_save()
: tr::lng_reply_quote_selected();
}) | rpl::flatten_latest();
box->addButton(std::move(save), [=] {
auto result = reply;
result.quote = state->quote.current();
finish(result);

View File

@ -761,7 +761,7 @@ contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<
contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
draftMessage#db074fa8 flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?MessageReplyHeader message:string entities:flags.3?Vector<MessageEntity> media:flags.5?MessageMedia date:int = DraftMessage;
draftMessage#3fccf7ef flags:# no_webpage:flags.1?true invert_media:flags.6?true reply_to:flags.4?InputReplyTo message:string entities:flags.3?Vector<MessageEntity> media:flags.5?InputMedia date:int = DraftMessage;
messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;

View File

@ -140,6 +140,11 @@ menuIconPremium: icon {{ "menu/premium", menuIconColor }};
menuIconIpAddress: icon {{ "menu/ip_address", menuIconColor }};
menuIconAddress: icon {{ "menu/payment_address", menuIconColor }};
menuIconShowAll: icon {{ "menu/all_media", menuIconColor }};
menuIconReplace: icon {{ "chat/input_replace", menuIconColor }};
menuIconAbove: icon {{ "menu/link_above", menuIconColor }};
menuIconBelow: icon {{ "menu/link_below", menuIconColor }};
menuIconEnlarge: icon {{ "menu/link_enlarge", menuIconColor }};
menuIconShrink: icon {{ "menu/link_shrink", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);

@ -1 +1 @@
Subproject commit 71d24af3a840d15a6cc0a3a8a1e9cbe77a604739
Subproject commit c36559a6797f02d8a56a414ac91f9c6fd08b5270