Allow schedule of files, stickers, GIFs.

This commit is contained in:
John Preston 2019-08-16 20:07:30 +03:00
parent 77ebdd3576
commit f690f93f32
8 changed files with 477 additions and 18 deletions

View File

@ -129,7 +129,7 @@ template <std::size_t... I>
inline void GenericBox::Initer<InitMethod, InitArgs...>::call(
not_null<GenericBox*> box,
std::index_sequence<I...>) {
std::invoke(method, box, std::get<I>(args)...);
std::invoke(method, box, std::get<I>(std::move(args))...);
}
template <typename InitMethod, typename ...InitArgs>

View File

@ -50,7 +50,7 @@ inline void CallDelayed(int duration, Guard &&object, Lambda &&lambda) {
}
template <typename Guard, typename Lambda>
inline auto LambdaDelayed(int duration, Guard &&object, Lambda &&lambda) {
[[nodiscard]] inline auto LambdaDelayed(int duration, Guard &&object, Lambda &&lambda) {
auto guarded = crl::guard(
std::forward<Guard>(object),
std::forward<Lambda>(lambda));

View File

@ -83,6 +83,26 @@ rpl::producer<> ComposeControls::sendRequests() const {
return _send->clicks() | rpl::map([] { return rpl::empty_value(); });
}
rpl::producer<> ComposeControls::attachRequests() const {
return _attachToggle->clicks(
) | rpl::map([] {
return rpl::empty_value();
});
}
rpl::producer<not_null<DocumentData*>> ComposeControls::fileChosen() const {
return _fileChosen.events();
}
rpl::producer<not_null<PhotoData*>> ComposeControls::photoChosen() const {
return _photoChosen.events();
}
auto ComposeControls::inlineResultChosen() const
->rpl::producer<ChatHelpers::TabbedSelector::InlineChosen> {
return _inlineResultChosen.events();
}
void ComposeControls::showStarted() {
if (_inlineResults) {
_inlineResults->hideFast();
@ -202,20 +222,13 @@ void ComposeControls::initTabbedSelector() {
}, wrap->lifetime());
selector->fileChosen(
) | rpl::start_with_next([=](not_null<DocumentData*> document) {
//sendExistingDocument(document);
}, wrap->lifetime());
) | rpl::start_to_stream(_fileChosen, wrap->lifetime());
selector->photoChosen(
) | rpl::start_with_next([=](not_null<PhotoData*> photo) {
//sendExistingPhoto(photo);
}, wrap->lifetime());
) | rpl::start_to_stream(_photoChosen, wrap->lifetime());
selector->inlineResultChosen(
) | rpl::start_with_next([=](
ChatHelpers::TabbedSelector::InlineChosen data) {
//sendInlineResult(data.result, data.bot);
}, wrap->lifetime());
) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime());
}
void ComposeControls::initSendButton() {

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unique_qptr.h"
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "chat_helpers/tabbed_selector.h"
namespace ChatHelpers {
class TabbedPanel;
@ -65,6 +66,11 @@ public:
void focus();
[[nodiscard]] rpl::producer<> cancelRequests() const;
[[nodiscard]] rpl::producer<> sendRequests() const;
[[nodiscard]] rpl::producer<> attachRequests() const;
[[nodiscard]] rpl::producer<not_null<DocumentData*>> fileChosen() const;
[[nodiscard]] rpl::producer<not_null<PhotoData*>> photoChosen() const;
[[nodiscard]] auto inlineResultChosen() const
-> rpl::producer<ChatHelpers::TabbedSelector::InlineChosen>;
void pushTabbedSelectorToThirdSection(const Window::SectionShow &params);
bool returnTabbedSelector();
@ -108,6 +114,9 @@ private:
std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
rpl::event_stream<> _cancelRequests;
rpl::event_stream<not_null<DocumentData*>> _fileChosen;
rpl::event_stream<not_null<PhotoData*>> _photoChosen;
rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
bool _recording = false;
bool _inField = false;

View File

@ -512,7 +512,7 @@ TimeId DefaultScheduleTime() {
void ScheduleBox(
not_null<GenericBox*> box,
Fn<void(Api::SendOptions)> done,
FnMut<void(Api::SendOptions)> done,
TimeId time) {
box->setTitle(tr::lng_schedule_title());
box->setWidth(st::boxWideWidth);
@ -582,6 +582,8 @@ void ScheduleBox(
}), (*calendar)->lifetime());
});
const auto shared = std::make_shared<FnMut<void(Api::SendOptions)>>(
std::move(done));
const auto save = [=] {
auto result = Api::SendOptions();
@ -602,9 +604,9 @@ void ScheduleBox(
return;
}
auto copy = done;
auto copy = shared;
box->closeBox();
copy(result);
(*copy)(result);
};
box->setFocusCallback([=] { timeInput->setFocusFast(); });

View File

@ -18,7 +18,7 @@ namespace HistoryView {
[[nodiscard]] TimeId DefaultScheduleTime();
void ScheduleBox(
not_null<GenericBox*> box,
Fn<void(Api::SendOptions)> done,
FnMut<void(Api::SendOptions)> done,
TimeId time);
} // namespace HistoryView

View File

@ -15,22 +15,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "ui/toast/toast.h"
#include "ui/special_buttons.h"
#include "api/api_common.h"
#include "api/api_sending.h"
#include "apiwrap.h"
#include "boxes/confirm_box.h"
#include "boxes/send_files_box.h"
#include "boxes/generic_box.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "core/event_filter.h"
#include "core/file_utilities.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_scheduled_messages.h"
#include "storage/storage_media_prepare.h"
#include "storage/localstorage.h"
#include "inline_bots/inline_bot_result.h"
#include "lang/lang_keys.h"
#include "styles/style_history.h"
#include "styles/style_window.h"
#include "styles/style_info.h"
#include "styles/style_boxes.h"
namespace HistoryView {
namespace {
void ShowErrorToast(const QString &text) {
auto config = Ui::Toast::Config();
config.multiline = true;
config.minWidth = st::msgMinWidth;
config.text = text;
Ui::Toast::Show(config);
}
} // namespace
object_ptr<Window::SectionWidget> ScheduledMemento::createWidget(
QWidget *parent,
@ -111,6 +131,256 @@ void ScheduledWidget::setupComposeControls() {
) | rpl::start_with_next([=] {
send();
}, lifetime());
_composeControls->attachRequests(
) | rpl::filter([=] {
return !_choosingAttach;
}) | rpl::start_with_next([=] {
_choosingAttach = true;
App::CallDelayed(
st::historyAttach.ripple.hideDuration,
this,
[=] { _choosingAttach = false; chooseAttach(); });
}, lifetime());
_composeControls->fileChosen(
) | rpl::start_with_next([=](not_null<DocumentData*> document) {
sendExistingDocument(document);
}, lifetime());
_composeControls->photoChosen(
) | rpl::start_with_next([=](not_null<PhotoData*> photo) {
sendExistingPhoto(photo);
}, lifetime());
_composeControls->inlineResultChosen(
) | rpl::start_with_next([=](
ChatHelpers::TabbedSelector::InlineChosen chosen) {
sendInlineResult(chosen.result, chosen.bot);
}, lifetime());
}
void ScheduledWidget::chooseAttach() {
if (const auto error = Data::RestrictionError(
_history->peer,
ChatRestriction::f_send_media)) {
Ui::Toast::Show(*error);
return;
}
const auto filter = FileDialog::AllFilesFilter()
+ qsl(";;Image files (*")
+ cImgExtensions().join(qsl(" *"))
+ qsl(")");
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
FileDialog::OpenResult &&result) {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
if (!result.remoteContent.isEmpty()) {
auto animated = false;
auto image = App::readImage(
result.remoteContent,
nullptr,
false,
&animated);
if (!image.isNull() && !animated) {
confirmSendingFiles(
std::move(image),
std::move(result.remoteContent),
CompressConfirm::Auto);
} else {
uploadFile(result.remoteContent, SendMediaType::File);
}
} else {
auto list = Storage::PrepareMediaList(
result.paths,
st::sendMediaPreviewSize);
if (list.allFilesForCompress || list.albumIsPossible) {
confirmSendingFiles(std::move(list), CompressConfirm::Auto);
} else if (!showSendingFilesError(list)) {
confirmSendingFiles(std::move(list), CompressConfirm::No);
}
}
}), nullptr);
}
bool ScheduledWidget::confirmSendingFiles(
Storage::PreparedList &&list,
CompressConfirm compressed,
const QString &insertTextOnCancel) {
if (showSendingFilesError(list)) {
return false;
}
const auto noCompressOption = (list.files.size() > 1)
&& !list.allFilesForCompress
&& !list.albumIsPossible;
const auto boxCompressConfirm = noCompressOption
? CompressConfirm::None
: compressed;
//const auto cursor = _field->textCursor();
//const auto position = cursor.position();
//const auto anchor = cursor.anchor();
const auto text = _composeControls->getTextWithAppliedMarkdown();//_field->getTextWithTags();
using SendLimit = SendFilesBox::SendLimit;
auto box = Box<SendFilesBox>(
controller(),
std::move(list),
text,
boxCompressConfirm,
_history->peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many);
//_field->setTextWithTags({});
box->setConfirmedCallback(crl::guard(this, [=](
Storage::PreparedList &&list,
SendFilesWay way,
TextWithTags &&caption,
Api::SendOptions options,
bool ctrlShiftEnter) {
if (showSendingFilesError(list)) {
return;
}
const auto type = (way == SendFilesWay::Files)
? SendMediaType::File
: SendMediaType::Photo;
const auto album = (way == SendFilesWay::Album)
? std::make_shared<SendingAlbum>()
: nullptr;
uploadFilesAfterConfirmation(
std::move(list),
type,
std::move(caption),
MsgId(0),//replyToId(),
options,
album);
}));
//box->setCancelledCallback(crl::guard(this, [=] {
// _field->setTextWithTags(text);
// auto cursor = _field->textCursor();
// cursor.setPosition(anchor);
// if (position != anchor) {
// cursor.setPosition(position, QTextCursor::KeepAnchor);
// }
// _field->setTextCursor(cursor);
// if (!insertTextOnCancel.isEmpty()) {
// _field->textCursor().insertText(insertTextOnCancel);
// }
//}));
//ActivateWindow(controller());
const auto shown = Ui::show(std::move(box));
shown->setCloseByOutsideClick(false);
return true;
}
bool ScheduledWidget::confirmSendingFiles(
QImage &&image,
QByteArray &&content,
CompressConfirm compressed,
const QString &insertTextOnCancel) {
if (image.isNull()) {
return false;
}
auto list = Storage::PrepareMediaFromImage(
std::move(image),
std::move(content),
st::sendMediaPreviewSize);
return confirmSendingFiles(
std::move(list),
compressed,
insertTextOnCancel);
}
void ScheduledWidget::uploadFilesAfterConfirmation(
Storage::PreparedList &&list,
SendMediaType type,
TextWithTags &&caption,
MsgId replyTo,
Api::SendOptions options,
std::shared_ptr<SendingAlbum> album) {
const auto isAlbum = (album != nullptr);
const auto compressImages = (type == SendMediaType::Photo);
if (_history->peer->slowmodeApplied()
&& ((list.files.size() > 1 && !album)
|| (!list.files.empty()
&& !caption.text.isEmpty()
&& !list.canAddCaption(isAlbum, compressImages)))) {
ShowErrorToast(tr::lng_slowmode_no_many(tr::now));
return;
}
auto callback = crl::guard(this, [
=,
list = std::move(list),
caption = std::move(caption),
// Strange thing, otherwise std::is_copy_constructible is true. O_o
msvc_bug_workaround = std::make_unique<int>()
](Api::SendOptions options) mutable {
auto action = Api::SendAction(_history);
action.replyTo = replyTo;
action.options = options;
session().api().sendFiles(
std::move(list),
type,
std::move(caption),
album,
action);
});
Ui::show(
Box(ScheduleBox, std::move(callback), DefaultScheduleTime()),
LayerOption::KeepOther);
}
void ScheduledWidget::uploadFile(
const QByteArray &fileContent,
SendMediaType type) {
const auto callback = crl::guard(this, [=](Api::SendOptions options) {
auto action = Api::SendAction(_history);
//action.replyTo = replyToId();
action.options = options;
session().api().sendFile(fileContent, type, action);
});
Ui::show(
Box(ScheduleBox, callback, DefaultScheduleTime()),
LayerOption::KeepOther);
}
bool ScheduledWidget::showSendingFilesError(
const Storage::PreparedList &list) const {
const auto text = [&] {
const auto error = Data::RestrictionError(
_history->peer,
ChatRestriction::f_send_media);
if (error) {
return *error;
}
using Error = Storage::PreparedList::Error;
switch (list.error) {
case Error::None: return QString();
case Error::EmptyFile:
case Error::Directory:
case Error::NonLocalUrl: return tr::lng_send_image_empty(
tr::now,
lt_name,
list.errorData);
case Error::TooLargeFile: return tr::lng_send_image_too_large(
tr::now,
lt_name,
list.errorData);
}
return tr::lng_forward_send_files_cant(tr::now);
}();
if (text.isEmpty()) {
return false;
}
ShowErrorToast(text);
return true;
}
void ScheduledWidget::send() {
@ -160,6 +430,123 @@ void ScheduledWidget::send(Api::SendOptions options) {
_composeControls->focus();
}
void ScheduledWidget::sendExistingDocument(
not_null<DocumentData*> document) {
const auto callback = crl::guard(this, [=](Api::SendOptions options) {
sendExistingDocument(document, options);
});
Ui::show(
Box(ScheduleBox, callback, DefaultScheduleTime()),
LayerOption::KeepOther);
}
bool ScheduledWidget::sendExistingDocument(
not_null<DocumentData*> document,
Api::SendOptions options) {
const auto error = Data::RestrictionError(
_history->peer,
ChatRestriction::f_send_stickers);
if (error) {
Ui::show(Box<InformBox>(*error), LayerOption::KeepOther);
return false;
}
auto message = Api::MessageToSend(_history);
//message.action.replyTo = replyToId();
message.action.options = options;
Api::SendExistingDocument(std::move(message), document);
//if (_fieldAutocomplete->stickersShown()) {
// clearFieldText();
// //_saveDraftText = true;
// //_saveDraftStart = crl::now();
// //onDraftSave();
// onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
//}
_composeControls->hidePanelsAnimated();
_composeControls->focus();
return true;
}
void ScheduledWidget::sendExistingPhoto(not_null<PhotoData*> photo) {
const auto callback = crl::guard(this, [=](Api::SendOptions options) {
sendExistingPhoto(photo, options);
});
Ui::show(
Box(ScheduleBox, callback, DefaultScheduleTime()),
LayerOption::KeepOther);
}
bool ScheduledWidget::sendExistingPhoto(
not_null<PhotoData*> photo,
Api::SendOptions options) {
const auto error = Data::RestrictionError(
_history->peer,
ChatRestriction::f_send_media);
if (error) {
Ui::show(Box<InformBox>(*error), LayerOption::KeepOther);
return false;
}
auto message = Api::MessageToSend(_history);
//message.action.replyTo = replyToId();
message.action.options = options;
Api::SendExistingPhoto(std::move(message), photo);
_composeControls->hidePanelsAnimated();
_composeControls->focus();
return true;
}
void ScheduledWidget::sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot) {
const auto errorText = result->getErrorOnSend(_history);
if (!errorText.isEmpty()) {
Ui::show(Box<InformBox>(errorText));
return;
}
const auto callback = crl::guard(this, [=](Api::SendOptions options) {
sendInlineResult(result, bot, options);
});
Ui::show(
Box(ScheduleBox, callback, DefaultScheduleTime()),
LayerOption::KeepOther);
}
void ScheduledWidget::sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot,
Api::SendOptions options) {
auto action = Api::SendAction(_history);
action.clearDraft = true;
//action.replyTo = replyToId();
action.options = options;
action.generateLocal = true;
session().api().sendInlineResult(bot, result, action);
_composeControls->clear();
//_saveDraftText = true;
//_saveDraftStart = crl::now();
//onDraftSave();
auto &bots = cRefRecentInlineBots();
const auto index = bots.indexOf(bot);
if (index) {
if (index > 0) {
bots.removeAt(index);
} else if (bots.size() >= RecentInlineBotsLimit) {
bots.resize(RecentInlineBotsLimit - 1);
}
bots.push_front(bot);
Local::writeRecentHashtagsAndBots();
}
_composeControls->hidePanelsAnimated();
_composeControls->focus();
}
void ScheduledWidget::setupScrollDownButton() {
_scrollDown->setClickedCallback([=] {
scrollDownClicked();
@ -470,14 +857,14 @@ void ScheduledWidget::highlightSingleNewMessage(
return;
}
auto firstDifferent = 0;
for (; firstDifferent != _lastSlice.ids.size(); ++firstDifferent) {
while (firstDifferent != _lastSlice.ids.size()) {
if (slice.ids[firstDifferent] != _lastSlice.ids[firstDifferent]) {
break;
}
++firstDifferent;
}
auto lastDifferent = slice.ids.size() - 1;
for (; lastDifferent != firstDifferent;) {
while (lastDifferent != firstDifferent) {
if (slice.ids[lastDifferent] != _lastSlice.ids[lastDifferent - 1]) {
break;
}

View File

@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_messages.h"
class History;
enum class CompressConfirm;
enum class SendMediaType;
struct SendingAlbum;
namespace Api {
struct SendOptions;
@ -22,6 +25,10 @@ namespace Notify {
struct PeerUpdate;
} // namespace Notify
namespace Storage {
struct PreparedList;
} // namespace Storage
namespace Ui {
class ScrollArea;
class PlainShadow;
@ -33,6 +40,10 @@ namespace Profile {
class BackButton;
} // namespace Profile
namespace InlineBots {
class Result;
} // namespace InlineBots
namespace HistoryView {
class Element;
@ -133,6 +144,42 @@ private:
void send();
void send(Api::SendOptions options);
void highlightSingleNewMessage(const Data::MessagesSlice &slice);
void chooseAttach();
void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles(
QImage &&image,
QByteArray &&content,
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
Storage::PreparedList &&list,
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool showSendingFilesError(const Storage::PreparedList &list) const;
void uploadFilesAfterConfirmation(
Storage::PreparedList &&list,
SendMediaType type,
TextWithTags &&caption,
MsgId replyTo,
Api::SendOptions options,
std::shared_ptr<SendingAlbum> album);
void sendExistingDocument(not_null<DocumentData*> document);
bool sendExistingDocument(
not_null<DocumentData*> document,
Api::SendOptions options);
void sendExistingPhoto(not_null<PhotoData*> photo);
bool sendExistingPhoto(
not_null<PhotoData*> photo,
Api::SendOptions options);
void sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot);
void sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot,
Api::SendOptions options);
const not_null<History*> _history;
object_ptr<Ui::ScrollArea> _scroll;
@ -151,6 +198,7 @@ private:
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
bool _choosingAttach = false;
};