Allow to send scheduled messages instantly.

This commit is contained in:
John Preston 2019-08-09 18:58:58 +01:00
parent 956bb876f6
commit 694f771131
17 changed files with 256 additions and 51 deletions

View File

@ -1388,6 +1388,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_reply_msg" = "Reply";
"lng_context_edit_msg" = "Edit";
"lng_context_forward_msg" = "Forward Message";
"lng_context_send_now_msg" = "Send now";
"lng_context_delete_msg" = "Delete Message";
"lng_context_select_msg" = "Select Message";
"lng_context_report_msg" = "Report Message";
@ -1397,6 +1398,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_context_copy_selected" = "Copy Selected Text";
"lng_context_copy_selected_items" = "Copy Selected as Text";
"lng_context_forward_selected" = "Forward Selected";
"lng_context_send_now_selected" = "Send selected now";
"lng_context_delete_selected" = "Delete Selected";
"lng_context_clear_selection" = "Clear Selection";
"lng_send_image_empty" = "Could not send an empty file: {name}";
@ -1474,6 +1476,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_selected_clear" = "Cancel";
"lng_selected_delete" = "Delete";
"lng_selected_forward" = "Forward";
"lng_selected_send_now" = "Send now";
"lng_selected_cancel_sure_this" = "Cancel uploading?";
"lng_selected_upload_stop" = "Stop";
"lng_selected_delete_sure_this" = "Do you want to delete this message?";

View File

@ -9,24 +9,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
uint32 HashInit() {
[[nodiscard]] inline uint32 HashInit() {
return 0;
}
template <
typename Int,
typename = std::enable_if_t<
std::is_same_v<Int, int32> || std::is_same_v<Int, uint32>>>
void HashUpdate(uint32 &already, Int value) {
already += (already * 20261) + uint32(value);
inline void HashUpdate(uint32 &already, uint32 value) {
already = (already * 20261) + uint32(value);
}
int32 HashFinalize(uint32 already) {
inline void HashUpdate(uint32 &already, int32 value) {
HashUpdate(already, uint32(value));
}
inline void HashUpdate(uint32 &already, uint64 value) {
HashUpdate(already, uint32(value >> 32));
HashUpdate(already, uint32(value & 0xFFFFFFFFULL));
}
[[nodiscard]] inline int32 HashFinalize(uint32 already) {
return int32(already & 0x7FFFFFFF);
}
template <typename IntRange>
inline int32 CountHash(IntRange &&range) {
[[nodiscard]] inline int32 CountHash(IntRange &&range) {
auto result = HashInit();
for (const auto value : range) {
HashUpdate(result, value);

View File

@ -78,13 +78,14 @@ ScheduledMessages::~ScheduledMessages() {
}
MsgId ScheduledMessages::lookupId(not_null<HistoryItem*> item) const {
if (const auto i = _data.find(item->history()); i != end(_data)) {
const auto &list = i->second.idByItem;
if (const auto j = list.find(item); j != end(list)) {
return j->second;
}
}
return MsgId(0);
Expects(item->isScheduled());
const auto i = _data.find(item->history());
Assert(i != end(_data));
const auto &list = i->second;
const auto j = list.idByItem.find(item);
Assert(j != end(list.idByItem));
return j->second;
}
int ScheduledMessages::count(not_null<History*> history) const {

View File

@ -475,6 +475,10 @@ bool HistoryItem::canPin() const {
return _history->peer->canPinMessages();
}
bool HistoryItem::allowsSendNow() const {
return false;
}
bool HistoryItem::allowsForward() const {
return false;
}

View File

@ -266,6 +266,7 @@ public:
bool isPinned() const;
bool canPin() const;
bool canStopPoll() const;
virtual bool allowsSendNow() const;
virtual bool allowsForward() const;
virtual bool allowsEdit(TimeId now) const;
bool canDelete() const;

View File

@ -742,6 +742,10 @@ bool HistoryMessage::allowsForward() const {
return !_media || _media->allowsForward();
}
bool HistoryMessage::allowsSendNow() const {
return isScheduled();
}
bool HistoryMessage::isTooOldForEdit(TimeId now) const {
const auto peer = _history->peer;
if (peer->isSelf()) {

View File

@ -108,6 +108,7 @@ public:
const MTPMessageMedia &media);
[[nodiscard]] bool allowsForward() const override;
[[nodiscard]] bool allowsSendNow() const override;
[[nodiscard]] bool allowsEdit(TimeId now) const override;
[[nodiscard]] bool uploading() const;

View File

@ -291,6 +291,95 @@ void AddForwardAction(
AddForwardMessageAction(menu, request, list);
}
bool AddSendNowSelectedAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
if (!request.overSelection || request.selectedItems.empty()) {
return false;
}
if (ranges::find_if(request.selectedItems, [](const auto &item) {
return !item.canSendNow;
}) != end(request.selectedItems)) {
return false;
}
const auto session = &request.navigation->session();
auto histories = ranges::view::all(
request.selectedItems
) | ranges::view::transform([&](const SelectedItem &item) {
return session->data().message(item.msgId);
}) | ranges::view::filter([](HistoryItem *item) {
return item != nullptr;
}) | ranges::view::transform([](not_null<HistoryItem*> item) {
return item->history();
});
if (histories.begin() == histories.end()) {
return false;
}
const auto history = *histories.begin();
menu->addAction(tr::lng_context_send_now_selected(tr::now), [=] {
const auto weak = make_weak(list);
const auto callback = [=] {
request.navigation->showBackFromStack();
};
Window::ShowSendNowMessagesBox(
request.navigation,
history,
ExtractIdsList(request.selectedItems),
callback);
});
return true;
}
bool AddSendNowMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
const auto item = request.item;
if (!request.selectedItems.empty()) {
return false;
} else if (!item || !item->allowsSendNow()) {
return false;
}
const auto owner = &item->history()->owner();
const auto asGroup = (request.pointState != PointState::GroupPart);
if (asGroup) {
if (const auto group = owner->groups().find(item)) {
if (ranges::find_if(group->items, [](auto item) {
return !item->allowsSendNow();
}) != end(group->items)) {
return false;
}
}
}
const auto itemId = item->fullId();
menu->addAction(tr::lng_context_send_now_msg(tr::now), [=] {
if (const auto item = owner->message(itemId)) {
const auto callback = [=] {
request.navigation->showBackFromStack();
};
Window::ShowSendNowMessagesBox(
request.navigation,
item->history(),
(asGroup
? owner->itemOrItsGroup(item)
: MessageIdsList{ 1, itemId }),
callback);
}
});
return true;
}
void AddSendNowAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
AddSendNowSelectedAction(menu, request, list);
AddSendNowMessageAction(menu, request, list);
}
bool AddDeleteSelectedAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
@ -426,6 +515,7 @@ void AddMessageActions(
not_null<ListWidget*> list) {
AddPostLinkAction(menu, request);
AddForwardAction(menu, request, list);
AddSendNowAction(menu, request, list);
AddDeleteAction(menu, request, list);
AddSelectionAction(menu, request, list);
}

View File

@ -669,6 +669,7 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems {
auto result = SelectedItem(itemId);
result.canDelete = selection.canDelete;
result.canForward = selection.canForward;
result.canSendNow = selection.canSendNow;
return result;
};
auto items = SelectedItems();
@ -770,6 +771,7 @@ bool ListWidget::addToSelection(
}
iterator->second.canDelete = item->canDelete();
iterator->second.canForward = item->allowsForward();
iterator->second.canSendNow = item->allowsSendNow();
return true;
}

View File

@ -46,6 +46,7 @@ struct SelectedItem {
FullMsgId msgId;
bool canDelete = false;
bool canForward = false;
bool canSendNow = false;
};
@ -78,6 +79,7 @@ public:
struct SelectionData {
bool canDelete = false;
bool canForward = false;
bool canSendNow = false;
};

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/special_buttons.h"
#include "boxes/confirm_box.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "core/event_filter.h"
#include "main/main_session.h"
#include "data/data_session.h"
@ -56,6 +57,10 @@ ScheduledWidget::ScheduledWidget(
_topBar->resizeToWidth(width());
_topBar->show();
_topBar->sendNowSelectionRequest(
) | rpl::start_with_next([=] {
confirmSendNowSelected();
}, _topBar->lifetime());
_topBar->deleteSelectionRequest(
) | rpl::start_with_next([=] {
confirmDeleteSelected();
@ -386,12 +391,12 @@ void ScheduledWidget::listSelectionChanged(SelectedItems &&items) {
HistoryView::TopBarWidget::SelectedState state;
state.count = items.size();
for (const auto item : items) {
if (item.canForward) {
++state.canForwardCount;
}
if (item.canDelete) {
++state.canDeleteCount;
}
if (item.canSendNow) {
++state.canSendNowCount;
}
}
_topBar->showSelected(state);
}
@ -411,6 +416,19 @@ ClickHandlerPtr ScheduledWidget::listDateLink(not_null<Element*> view) {
return nullptr;
}
void ScheduledWidget::confirmSendNowSelected() {
auto items = _inner->getSelectedItems();
if (items.empty()) {
return;
}
const auto navigation = controller();
Window::ShowSendNowMessagesBox(
navigation,
_history,
std::move(items),
[=] { navigation->showBackFromStack(); });
}
void ScheduledWidget::confirmDeleteSelected() {
auto items = _inner->getSelectedItems();
if (items.empty()) {

View File

@ -112,6 +112,7 @@ private:
void updateScrollDownVisibility();
void updateScrollDownPosition();
void confirmSendNowSelected();
void confirmDeleteSelected();
void clearSelected();

View File

@ -52,6 +52,7 @@ TopBarWidget::TopBarWidget(
, _controller(controller)
, _clear(this, tr::lng_selected_clear(), st::topBarClearButton)
, _forward(this, tr::lng_selected_forward(), st::defaultActiveButton)
, _sendNow(this, tr::lng_selected_send_now(), st::defaultActiveButton)
, _delete(this, tr::lng_selected_delete(), st::defaultActiveButton)
, _back(this, st::historyTopBarBack)
, _call(this, st::topBarCall)
@ -60,19 +61,21 @@ TopBarWidget::TopBarWidget(
, _menuToggle(this, st::topBarMenuToggle)
, _titlePeerText(st::windowMinWidth / 3)
, _onlineUpdater([=] { updateOnlineDisplay(); }) {
subscribe(Lang::Current().updated(), [this] { refreshLang(); });
subscribe(Lang::Current().updated(), [=] { refreshLang(); });
setAttribute(Qt::WA_OpaquePaintEvent);
_forward->setClickedCallback([this] { _forwardSelection.fire({}); });
_forward->setWidthChangedCallback([this] { updateControlsGeometry(); });
_delete->setClickedCallback([this] { _deleteSelection.fire({}); });
_delete->setWidthChangedCallback([this] { updateControlsGeometry(); });
_clear->setClickedCallback([this] { _clearSelection.fire({}); });
_call->setClickedCallback([this] { onCall(); });
_search->setClickedCallback([this] { onSearch(); });
_menuToggle->setClickedCallback([this] { showMenu(); });
_infoToggle->setClickedCallback([this] { toggleInfoSection(); });
_back->addClickHandler([this] { backClicked(); });
_forward->setClickedCallback([=] { _forwardSelection.fire({}); });
_forward->setWidthChangedCallback([=] { updateControlsGeometry(); });
_sendNow->setClickedCallback([=] { _sendNowSelection.fire({}); });
_sendNow->setWidthChangedCallback([=] { updateControlsGeometry(); });
_delete->setClickedCallback([=] { _deleteSelection.fire({}); });
_delete->setWidthChangedCallback([=] { updateControlsGeometry(); });
_clear->setClickedCallback([=] { _clearSelection.fire({}); });
_call->setClickedCallback([=] { onCall(); });
_search->setClickedCallback([=] { onSearch(); });
_menuToggle->setClickedCallback([=] { showMenu(); });
_infoToggle->setClickedCallback([=] { toggleInfoSection(); });
_back->addClickHandler([=] { backClicked(); });
rpl::combine(
_controller->activeChatValue(),
@ -522,12 +525,16 @@ void TopBarWidget::updateControlsGeometry() {
auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.value(hasSelected ? 1. : 0.));
auto otherButtonsTop = selectedButtonsTop + st::topBarHeight;
auto buttonsLeft = st::topBarActionSkip + (Adaptive::OneColumn() ? 0 : st::lineWidth);
auto buttonsWidth = _forward->contentWidth() + _delete->contentWidth() + _clear->width();
auto buttonsWidth = (_forward->isHidden() ? 0 : _forward->contentWidth())
+ (_sendNow->isHidden() ? 0 : _sendNow->contentWidth())
+ (_delete->isHidden() ? 0 : _delete->contentWidth())
+ _clear->width();
buttonsWidth += buttonsLeft + st::topBarActionSkip * 3;
auto widthLeft = qMin(width() - buttonsWidth, -2 * st::defaultActiveButton.width);
auto buttonFullWidth = qMin(-(widthLeft / 2), 0);
_forward->setFullWidth(buttonFullWidth);
_sendNow->setFullWidth(buttonFullWidth);
_delete->setFullWidth(buttonFullWidth);
selectedButtonsTop += (height() - _forward->height()) / 2;
@ -537,6 +544,11 @@ void TopBarWidget::updateControlsGeometry() {
buttonsLeft += _forward->width() + st::topBarActionSkip;
}
_sendNow->moveToLeft(buttonsLeft, selectedButtonsTop);
if (!_sendNow->isHidden()) {
buttonsLeft += _sendNow->width() + st::topBarActionSkip;
}
_delete->moveToLeft(buttonsLeft, selectedButtonsTop);
_clear->moveToRight(st::topBarActionSkip, selectedButtonsTop);
@ -595,6 +607,7 @@ void TopBarWidget::updateControlsVisibility() {
_clear->show();
_delete->setVisible(_canDelete);
_forward->setVisible(_canForward);
_sendNow->setVisible(_canSendNow);
auto backVisible = Adaptive::OneColumn()
|| (App::main() && !App::main()->stackIsEmpty())
@ -665,29 +678,34 @@ void TopBarWidget::updateMembersShowArea() {
void TopBarWidget::showSelected(SelectedState state) {
auto canDelete = (state.count > 0 && state.count == state.canDeleteCount);
auto canForward = (state.count > 0 && state.count == state.canForwardCount);
if (_selectedCount == state.count && _canDelete == canDelete && _canForward == canForward) {
auto canSendNow = (state.count > 0 && state.count == state.canSendNowCount);
if (_selectedCount == state.count && _canDelete == canDelete && _canForward == canForward && _canSendNow == canSendNow) {
return;
}
if (state.count == 0) {
// Don't change the visible buttons if the selection is cancelled.
canDelete = _canDelete;
canForward = _canForward;
canSendNow = _canSendNow;
}
auto wasSelected = (_selectedCount > 0);
_selectedCount = state.count;
if (_selectedCount > 0) {
_forward->setNumbersText(_selectedCount);
_sendNow->setNumbersText(_selectedCount);
_delete->setNumbersText(_selectedCount);
if (!wasSelected) {
_forward->finishNumbersAnimation();
_sendNow->finishNumbersAnimation();
_delete->finishNumbersAnimation();
}
}
auto hasSelected = (_selectedCount > 0);
if (_canDelete != canDelete || _canForward != canForward) {
if (_canDelete != canDelete || _canForward != canForward || _canSendNow != canSendNow) {
_canDelete = canDelete;
_canForward = canForward;
_canSendNow = canSendNow;
updateControlsVisibility();
}
if (wasSelected != hasSelected) {

View File

@ -38,6 +38,7 @@ public:
int count = 0;
int canDeleteCount = 0;
int canForwardCount = 0;
int canSendNowCount = 0;
};
enum class Section {
History,
@ -64,6 +65,9 @@ public:
rpl::producer<> forwardSelectionRequest() const {
return _forwardSelection.events();
}
rpl::producer<> sendNowSelectionRequest() const {
return _sendNowSelection.events();
}
rpl::producer<> deleteSelectionRequest() const {
return _deleteSelection.events();
}
@ -123,11 +127,12 @@ private:
int _selectedCount = 0;
bool _canDelete = false;
bool _canForward = false;
bool _canSendNow = false;
Ui::Animations::Simple _selectedShown;
object_ptr<Ui::RoundButton> _clear;
object_ptr<Ui::RoundButton> _forward, _delete;
object_ptr<Ui::RoundButton> _forward, _sendNow, _delete;
object_ptr<Ui::IconButton> _back;
object_ptr<Ui::UnreadBadge> _unreadBadge = { nullptr };
@ -153,6 +158,7 @@ private:
base::Timer _onlineUpdater;
rpl::event_stream<> _forwardSelection;
rpl::event_stream<> _sendNowSelection;
rpl::event_stream<> _deleteSelection;
rpl::event_stream<> _clearSelection;

View File

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/input_fields.h"
#include "ui/emoji_config.h"
#include "export/export_settings.h"
#include "api/api_hash.h"
#include "core/crash_reports.h"
#include "core/update_checker.h"
#include "observer_peer.h"
@ -3854,13 +3855,11 @@ void readArchivedStickers() {
}
int32 countDocumentVectorHash(const QVector<DocumentData*> vector) {
uint32 acc = 0;
for_const (auto doc, vector) {
auto docId = doc->id;
acc = (acc * 20261) + uint32(docId >> 32);
acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF);
auto result = Api::HashInit();
for (const auto document : vector) {
Api::HashUpdate(result, document->id);
}
return int32(acc & 0x7FFFFFFF);
return Api::HashFinalize(result);
}
int32 countSpecialStickerSetHash(uint64 setId) {
@ -3873,7 +3872,7 @@ int32 countSpecialStickerSetHash(uint64 setId) {
}
int32 countStickersHash(bool checkOutdatedInfo) {
uint32 acc = 0;
auto result = Api::HashInit();
bool foundOutdated = false;
auto &sets = Auth().data().stickerSets();
auto &order = Auth().data().stickerSetsOrder();
@ -3884,11 +3883,13 @@ int32 countStickersHash(bool checkOutdatedInfo) {
foundOutdated = true;
} else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special)
&& !(j->flags & MTPDstickerSet::Flag::f_archived)) {
acc = (acc * 20261) + j->hash;
Api::HashUpdate(result, j->hash);
}
}
}
return (!checkOutdatedInfo || !foundOutdated) ? int32(acc & 0x7FFFFFFF) : 0;
return (!checkOutdatedInfo || !foundOutdated)
? Api::HashFinalize(result)
: 0;
}
int32 countRecentStickersHash() {
@ -3900,19 +3901,18 @@ int32 countFavedStickersHash() {
}
int32 countFeaturedStickersHash() {
uint32 acc = 0;
auto &sets = Auth().data().stickerSets();
auto &featured = Auth().data().featuredStickerSetsOrder();
for_const (auto setId, featured) {
acc = (acc * 20261) + uint32(setId >> 32);
acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF);
auto result = Api::HashInit();
const auto &sets = Auth().data().stickerSets();
const auto &featured = Auth().data().featuredStickerSetsOrder();
for (const auto setId : featured) {
Api::HashUpdate(result, setId);
auto it = sets.constFind(setId);
if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
acc = (acc * 20261) + 1U;
Api::HashUpdate(result, 1);
}
}
return int32(acc & 0x7FFFFFFF);
return Api::HashFinalize(result);
}
int32 countSavedGifsHash() {

View File

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "observer_peer.h"
#include "history/history.h"
#include "history/history_item.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "support/support_helper.h"
@ -40,6 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_drafts.h"
#include "data/data_user.h"
#include "data/data_scheduled_messages.h"
#include "dialogs/dialogs_key.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "styles/style_boxes.h"
@ -849,6 +851,47 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox(
return weak->data();
}
QPointer<Ui::RpWidget> ShowSendNowMessagesBox(
not_null<Window::SessionNavigation*> navigation,
not_null<History*> history,
MessageIdsList &&items,
FnMut<void()> &&successCallback) {
const auto session = &navigation->session();
const auto text = "Send now?";
const auto box = std::make_shared<QPointer<BoxContent>>();
auto done = [
=,
list = std::move(items),
callback = std::move(successCallback)
]() mutable {
if (*box) {
(*box)->closeBox();
}
auto ids = QVector<MTPint>();
for (const auto &itemId : list) {
if (const auto item = session->data().message(itemId)) {
if (item->allowsSendNow()) {
ids.push_back(MTP_int(
session->data().scheduledMessages().lookupId(item)));
}
}
}
session->api().request(MTPmessages_SendScheduledMessages(
history->peer->input,
MTP_vector<MTPint>(ids)
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
}).send();
if (callback) {
callback();
}
};
*box = Ui::show(
Box<ConfirmBox>(text, tr::lng_send_button(tr::now), std::move(done)),
LayerOption::KeepOther);
return box->data();
}
void PeerMenuAddChannelMembers(
not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> channel) {

View File

@ -74,4 +74,10 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox(
MessageIdsList &&items,
FnMut<void()> &&successCallback = nullptr);
QPointer<Ui::RpWidget> ShowSendNowMessagesBox(
not_null<Window::SessionNavigation*> navigation,
not_null<History*> history,
MessageIdsList &&items,
FnMut<void()> &&successCallback = nullptr);
} // namespace Window