| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "boxes/share_box.h"
- #include "api/api_premium.h"
- #include "base/random.h"
- #include "lang/lang_keys.h"
- #include "base/qthelp_url.h"
- #include "storage/storage_account.h"
- #include "ui/boxes/confirm_box.h"
- #include "apiwrap.h"
- #include "ui/widgets/chat_filters_tabs_strip.h"
- #include "ui/widgets/checkbox.h"
- #include "ui/widgets/multi_select.h"
- #include "ui/widgets/scroll_area.h"
- #include "ui/widgets/fields/input_field.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/wrap/slide_wrap.h"
- #include "ui/text/text_options.h"
- #include "ui/text/text_utilities.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "chat_helpers/message_field.h"
- #include "menu/menu_check_item.h"
- #include "menu/menu_send.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_helpers.h"
- #include "history/view/history_view_element.h"
- #include "history/view/history_view_context_menu.h" // CopyPostLink.
- #include "settings/settings_premium.h"
- #include "window/window_session_controller.h"
- #include "boxes/peer_list_controllers.h"
- #include "chat_helpers/emoji_suggestions_widget.h"
- #include "chat_helpers/share_message_phrase_factory.h"
- #include "data/business/data_shortcut_messages.h"
- #include "data/data_channel.h"
- #include "data/data_chat_filters.h"
- #include "data/data_game.h"
- #include "data/data_histories.h"
- #include "data/data_user.h"
- #include "data/data_peer_values.h"
- #include "data/data_session.h"
- #include "data/data_folder.h"
- #include "data/data_forum.h"
- #include "data/data_forum_topic.h"
- #include "data/data_changes.h"
- #include "main/main_session.h"
- #include "core/application.h"
- #include "core/core_settings.h"
- #include "styles/style_calls.h"
- #include "styles/style_layers.h"
- #include "styles/style_boxes.h"
- #include "styles/style_menu_icons.h"
- #include <QtGui/QGuiApplication>
- #include <QtGui/QClipboard>
- class ShareBox::Inner final : public Ui::RpWidget {
- public:
- Inner(
- QWidget *parent,
- const Descriptor &descriptor,
- std::shared_ptr<Ui::Show> show);
- void setPeerSelectedChangedCallback(
- Fn<void(not_null<Data::Thread*> thread, bool selected)> callback);
- void peerUnselected(not_null<PeerData*> peer);
- [[nodiscard]] std::vector<not_null<Data::Thread*>> selected() const;
- [[nodiscard]] bool hasSelected() const;
- void peopleReceived(
- const QString &query,
- const QVector<MTPPeer> &my,
- const QVector<MTPPeer> &people);
- void activateSkipRow(int direction);
- void activateSkipColumn(int direction);
- void activateSkipPage(int pageHeight, int direction);
- void updateFilter(QString filter = QString());
- [[nodiscard]] bool isFilterEmpty() const;
- void selectActive();
- rpl::producer<Ui::ScrollToRequest> scrollToRequests() const;
- rpl::producer<> searchRequests() const;
- void applyChatFilter(FilterId id);
- protected:
- void visibleTopBottomUpdated(
- int visibleTop,
- int visibleBottom) override;
- void paintEvent(QPaintEvent *e) override;
- void enterEventHook(QEnterEvent *e) override;
- void leaveEventHook(QEvent *e) override;
- void mouseMoveEvent(QMouseEvent *e) override;
- void mousePressEvent(QMouseEvent *e) override;
- void resizeEvent(QResizeEvent *e) override;
- private:
- struct Chat {
- Chat(
- not_null<History*> history,
- const style::PeerListItem &st,
- Fn<void()> updateCallback);
- not_null<History*> history;
- not_null<PeerData*> peer;
- Data::ForumTopic *topic = nullptr;
- rpl::lifetime topicLifetime;
- Ui::RoundImageCheckbox checkbox;
- Ui::Text::String name;
- Ui::Animations::Simple nameActive;
- Api::MessageMoneyRestriction restriction;
- RestrictionBadgeCache badgeCache;
- };
- void invalidateCache();
- bool showLockedError(not_null<Chat*> chat);
- void refreshRestrictedRows();
- [[nodiscard]] int displayedChatsCount() const;
- [[nodiscard]] not_null<Data::Thread*> chatThread(
- not_null<Chat*> chat) const;
- void paintChat(Painter &p, not_null<Chat*> chat, int index);
- void updateChat(not_null<PeerData*> peer);
- void updateChatName(not_null<Chat*> chat);
- void initChatRestriction(not_null<Chat*> chat);
- void repaintChat(not_null<PeerData*> peer);
- int chatIndex(not_null<PeerData*> peer) const;
- void repaintChatAtIndex(int index);
- Chat *getChatAtIndex(int index);
- void loadProfilePhotos();
- void preloadUserpic(not_null<Dialogs::Entry*> entry);
- void changeCheckState(Chat *chat);
- void chooseForumTopic(not_null<Data::Forum*> forum);
- enum class ChangeStateWay {
- Default,
- SkipCallback,
- };
- void changePeerCheckState(
- not_null<Chat*> chat,
- bool checked,
- ChangeStateWay useCallback = ChangeStateWay::Default);
- not_null<Chat*> getChat(not_null<Dialogs::Row*> row);
- void setActive(int active);
- void updateUpon(const QPoint &pos);
- void refresh();
- const Descriptor &_descriptor;
- const std::shared_ptr<Ui::Show> _show;
- const style::PeerList &_st;
- float64 _columnSkip = 0.;
- float64 _rowWidthReal = 0.;
- int _rowsLeft = 0;
- int _rowsTop = 0;
- int _rowWidth = 0;
- int _rowHeight = 0;
- int _columnCount = 4;
- int _active = -1;
- int _upon = -1;
- int _visibleTop = 0;
- std::unique_ptr<Dialogs::IndexedList> _defaultChatsIndexed;
- std::unique_ptr<Dialogs::IndexedList> _customChatsIndexed;
- not_null<Dialogs::IndexedList*> _chatsIndexed;
- QString _filter;
- std::vector<not_null<Dialogs::Row*>> _filtered;
- std::map<not_null<PeerData*>, std::unique_ptr<Chat>> _dataMap;
- base::flat_set<not_null<Data::Thread*>> _selected;
- Fn<void(not_null<Data::Thread*>, bool)> _peerSelectedChangedCallback;
- bool _searching = false;
- QString _lastQuery;
- std::vector<PeerData*> _byUsernameFiltered;
- std::vector<std::unique_ptr<Chat>> d_byUsernameFiltered;
- rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
- rpl::event_stream<> _searchRequests;
- };
- ShareBox::ShareBox(QWidget*, Descriptor &&descriptor)
- : _descriptor(std::move(descriptor))
- , _api(&_descriptor.session->mtp())
- , _select(
- this,
- (_descriptor.st.multiSelect
- ? *_descriptor.st.multiSelect
- : st::defaultMultiSelect),
- tr::lng_participant_filter())
- , _comment(
- this,
- object_ptr<Ui::InputField>(
- this,
- (_descriptor.st.comment
- ? *_descriptor.st.comment
- : st::shareComment),
- Ui::InputField::Mode::MultiLine,
- tr::lng_photos_comment()),
- st::shareCommentPadding)
- , _bottomWidget(std::move(_descriptor.bottomWidget))
- , _copyLinkText(_descriptor.copyLinkText
- ? std::move(_descriptor.copyLinkText)
- : tr::lng_share_copy_link())
- , _searchTimer([=] { searchByUsername(); }) {
- if (_bottomWidget) {
- _bottomWidget->setParent(this);
- _bottomWidget->resizeToWidth(st::boxWideWidth);
- _bottomWidget->show();
- }
- }
- void ShareBox::prepareCommentField() {
- _comment->hide(anim::type::instant);
- rpl::combine(
- heightValue(),
- _comment->heightValue(),
- (_bottomWidget
- ? _bottomWidget->heightValue()
- : (rpl::single(0) | rpl::type_erased()))
- ) | rpl::start_with_next([=](int height, int comment, int bottom) {
- _comment->moveToLeft(0, height - bottom - comment);
- if (_bottomWidget) {
- _bottomWidget->moveToLeft(0, height - bottom);
- }
- }, _comment->lifetime());
- const auto field = _comment->entity();
- field->submits(
- ) | rpl::start_with_next([=] {
- submit({});
- }, field->lifetime());
- if (const auto show = uiShow(); show->valid()) {
- InitMessageFieldHandlers({
- .session = _descriptor.session,
- .show = Main::MakeSessionShow(show, _descriptor.session),
- .field = field,
- .fieldStyle = _descriptor.st.label,
- });
- }
- field->setSubmitSettings(Core::App().settings().sendSubmitWay());
- field->changes() | rpl::start_with_next([=] {
- if (!field->getLastText().isEmpty()) {
- setCloseByOutsideClick(false);
- } else if (_inner->selected().empty()) {
- setCloseByOutsideClick(true);
- }
- }, field->lifetime());
- Ui::SendPendingMoveResizeEvents(_comment);
- if (_bottomWidget) {
- Ui::SendPendingMoveResizeEvents(_bottomWidget);
- }
- }
- void ShareBox::prepare() {
- prepareCommentField();
- _select->resizeToWidth(st::boxWideWidth);
- Ui::SendPendingMoveResizeEvents(_select);
- setTitle(_descriptor.titleOverride
- ? std::move(_descriptor.titleOverride)
- : tr::lng_share_title());
- _inner = setInnerWidget(
- object_ptr<Inner>(this, _descriptor, uiShow()),
- getTopScrollSkip(),
- getBottomScrollSkip());
- createButtons();
- setDimensions(st::boxWideWidth, st::boxMaxListHeight);
- _select->setQueryChangedCallback([=](const QString &query) {
- applyFilterUpdate(query);
- if (_chatsFilters) {
- updateScrollSkips();
- scrollToY(0);
- }
- });
- _select->setItemRemovedCallback([=](uint64 itemId) {
- if (const auto peer = _descriptor.session->data().peerLoaded(PeerId(itemId))) {
- _inner->peerUnselected(peer);
- selectedChanged();
- update();
- }
- });
- _select->setResizedCallback([=] { updateScrollSkips(); });
- _select->setSubmittedCallback([=](Qt::KeyboardModifiers modifiers) {
- if (modifiers.testFlag(Qt::ControlModifier)
- || modifiers.testFlag(Qt::MetaModifier)) {
- submit({});
- } else {
- _inner->selectActive();
- }
- });
- rpl::combine(
- _comment->heightValue(),
- (_bottomWidget
- ? _bottomWidget->heightValue()
- : rpl::single(0) | rpl::type_erased())
- ) | rpl::start_with_next([=] {
- updateScrollSkips();
- }, _comment->lifetime());
- _inner->searchRequests(
- ) | rpl::start_with_next([=] {
- needSearchByUsername();
- }, _inner->lifetime());
- _inner->scrollToRequests(
- ) | rpl::start_with_next([=](const Ui::ScrollToRequest &request) {
- scrollTo(request);
- }, _inner->lifetime());
- _inner->setPeerSelectedChangedCallback([=](
- not_null<Data::Thread*> thread,
- bool checked) {
- innerSelectedChanged(thread, checked);
- if (checked) {
- setCloseByOutsideClick(false);
- } else if (_inner->selected().empty()
- && _comment->entity()->getLastText().isEmpty()) {
- setCloseByOutsideClick(true);
- }
- });
- Ui::Emoji::SuggestionsController::Init(
- getDelegate()->outerContainer(),
- _comment->entity(),
- _descriptor.session,
- { .suggestCustomEmoji = true });
- _select->raise();
- {
- const auto chatsFilters = AddChatFiltersTabsStrip(
- this,
- _descriptor.session,
- [this](FilterId id) {
- _inner->applyChatFilter(id);
- scrollToY(0);
- },
- Window::GifPauseReason::Layer);
- chatsFilters->lower();
- chatsFilters->heightValue() | rpl::start_with_next([this](int h) {
- updateScrollSkips();
- scrollToY(0);
- }, lifetime());
- _select->heightValue() | rpl::start_with_next([=](int h) {
- chatsFilters->moveToLeft(0, h);
- }, chatsFilters->lifetime());
- _chatsFilters = chatsFilters;
- }
- }
- int ShareBox::getTopScrollSkip() const {
- return (_select->isHidden() ? 0 : _select->height())
- + ((_chatsFilters && _inner && _inner->isFilterEmpty())
- ? _chatsFilters->height()
- : 0);
- }
- int ShareBox::getBottomScrollSkip() const {
- return (_comment->isHidden() ? 0 : _comment->height())
- + (_bottomWidget ? _bottomWidget->height() : 0);
- }
- int ShareBox::contentHeight() const {
- return height() - getTopScrollSkip() - getBottomScrollSkip();
- }
- void ShareBox::updateScrollSkips() {
- setInnerTopSkip(getTopScrollSkip(), true);
- setInnerBottomSkip(getBottomScrollSkip());
- }
- bool ShareBox::searchByUsername(bool searchCache) {
- auto query = _select->getQuery();
- if (query.isEmpty()) {
- if (_peopleRequest) {
- _peopleRequest = 0;
- }
- return true;
- }
- if (!query.isEmpty()) {
- if (searchCache) {
- auto i = _peopleCache.constFind(query);
- if (i != _peopleCache.cend()) {
- _peopleQuery = query;
- _peopleRequest = 0;
- peopleDone(i.value(), 0);
- return true;
- }
- } else if (_peopleQuery != query) {
- _peopleQuery = query;
- _peopleFull = false;
- _peopleRequest = _api.request(MTPcontacts_Search(
- MTP_string(_peopleQuery),
- MTP_int(SearchPeopleLimit)
- )).done([=](const MTPcontacts_Found &result, mtpRequestId requestId) {
- peopleDone(result, requestId);
- }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
- peopleFail(error, requestId);
- }).send();
- _peopleQueries.insert(_peopleRequest, _peopleQuery);
- }
- }
- return false;
- }
- void ShareBox::needSearchByUsername() {
- if (!searchByUsername(true)) {
- _searchTimer.callOnce(AutoSearchTimeout);
- }
- }
- void ShareBox::peopleDone(
- const MTPcontacts_Found &result,
- mtpRequestId requestId) {
- Expects(result.type() == mtpc_contacts_found);
- auto query = _peopleQuery;
- auto i = _peopleQueries.find(requestId);
- if (i != _peopleQueries.cend()) {
- query = i.value();
- _peopleCache[query] = result;
- _peopleQueries.erase(i);
- }
- if (_peopleRequest == requestId) {
- switch (result.type()) {
- case mtpc_contacts_found: {
- auto &found = result.c_contacts_found();
- _descriptor.session->data().processUsers(found.vusers());
- _descriptor.session->data().processChats(found.vchats());
- _inner->peopleReceived(
- query,
- found.vmy_results().v,
- found.vresults().v);
- } break;
- }
- _peopleRequest = 0;
- }
- }
- void ShareBox::peopleFail(const MTP::Error &error, mtpRequestId requestId) {
- if (_peopleRequest == requestId) {
- _peopleRequest = 0;
- _peopleFull = true;
- }
- }
- void ShareBox::setInnerFocus() {
- if (_comment->isHidden()) {
- _select->setInnerFocus();
- } else {
- _comment->entity()->setFocusFast();
- }
- }
- void ShareBox::resizeEvent(QResizeEvent *e) {
- BoxContent::resizeEvent(e);
- _select->resizeToWidth(width());
- _select->moveToLeft(0, 0);
- updateScrollSkips();
- _inner->resizeToWidth(width());
- }
- void ShareBox::keyPressEvent(QKeyEvent *e) {
- auto focused = focusWidget();
- if (_select == focused || _select->isAncestorOf(focusWidget())) {
- if (e->key() == Qt::Key_Up) {
- _inner->activateSkipColumn(-1);
- } else if (e->key() == Qt::Key_Down) {
- _inner->activateSkipColumn(1);
- } else if (e->key() == Qt::Key_PageUp) {
- _inner->activateSkipPage(contentHeight(), -1);
- } else if (e->key() == Qt::Key_PageDown) {
- _inner->activateSkipPage(contentHeight(), 1);
- } else {
- BoxContent::keyPressEvent(e);
- }
- } else {
- BoxContent::keyPressEvent(e);
- }
- }
- SendMenu::Details ShareBox::sendMenuDetails() const {
- const auto selected = _inner->selected();
- const auto hasPaid = [&] {
- for (const auto &thread : selected) {
- if (thread->peer()->starsPerMessageChecked()) {
- return true;
- }
- }
- return false;
- }();
- const auto type = hasPaid
- ? SendMenu::Type::SilentOnly
- : ranges::all_of(
- selected | ranges::views::transform(&Data::Thread::peer),
- HistoryView::CanScheduleUntilOnline)
- ? SendMenu::Type::ScheduledToUser
- : (selected.size() == 1 && selected.front()->peer()->isSelf())
- ? SendMenu::Type::Reminder
- : SendMenu::Type::Scheduled;
- // We can't support effect here because we don't have ChatHelpers::Show.
- return { .type = type, .effectAllowed = false };
- }
- void ShareBox::showMenu(not_null<Ui::RpWidget*> parent) {
- if (_menu) {
- _menu = nullptr;
- return;
- }
- _menu.emplace(parent, st::popupMenuWithIcons);
- if (_descriptor.forwardOptions.show) {
- auto createView = [&](rpl::producer<QString> &&text, bool checked) {
- auto item = base::make_unique_q<Menu::ItemWithCheck>(
- _menu->menu(),
- st::popupMenuWithIcons.menu,
- Ui::CreateChild<QAction>(_menu->menu().get()),
- nullptr,
- nullptr);
- std::move(
- text
- ) | rpl::start_with_next([action = item->action()](QString text) {
- action->setText(text);
- }, item->lifetime());
- item->init(checked);
- const auto view = item->checkView();
- _menu->addAction(std::move(item));
- return view;
- };
- Ui::FillForwardOptions(
- std::move(createView),
- _forwardOptions,
- [=](Ui::ForwardOptions value) { _forwardOptions = value; },
- _menu->lifetime());
- _menu->addSeparator();
- }
- using namespace SendMenu;
- const auto sendAction = crl::guard(this, [=](Action action, Details) {
- if (action.type == ActionType::Send) {
- submit(action.options);
- return;
- }
- const auto st = _descriptor.st.scheduleBox
- ? *_descriptor.st.scheduleBox
- : HistoryView::ScheduleBoxStyleArgs();
- uiShow()->showBox(
- HistoryView::PrepareScheduleBox(
- this,
- nullptr, // ChatHelpers::Show for effect attachment.
- sendMenuDetails(),
- [=](Api::SendOptions options) { submit(options); },
- action.options,
- HistoryView::DefaultScheduleTime(),
- st));
- });
- _menu->setForcedVerticalOrigin(Ui::PopupMenu::VerticalOrigin::Bottom);
- const auto result = FillSendMenu(
- _menu.get(),
- nullptr, // showForEffect.
- sendMenuDetails(),
- sendAction);
- if (result == SendMenu::FillMenuResult::Prepared) {
- _menu->popupPrepared();
- } else if (_descriptor.forwardOptions.show
- && result != SendMenu::FillMenuResult::Failed) {
- _menu->popup(QCursor::pos());
- }
- }
- void ShareBox::createButtons() {
- clearButtons();
- if (_hasSelected) {
- const auto send = addButton(tr::lng_share_confirm(), [=] {
- submit({});
- });
- _forwardOptions.sendersCount
- = _descriptor.forwardOptions.sendersCount;
- _forwardOptions.captionsCount
- = _descriptor.forwardOptions.captionsCount;
- send->setAcceptBoth();
- send->clicks(
- ) | rpl::start_with_next([=](Qt::MouseButton button) {
- if (button == Qt::RightButton) {
- showMenu(send);
- }
- }, send->lifetime());
- send->setText(PaidSendButtonText(
- _starsToSend.value(),
- tr::lng_share_confirm()));
- } else if (_descriptor.copyCallback) {
- addButton(_copyLinkText.value(), [=] { copyLink(); });
- }
- addButton(tr::lng_cancel(), [=] { closeBox(); });
- }
- void ShareBox::applyFilterUpdate(const QString &query) {
- scrollToY(0);
- _inner->updateFilter(query);
- }
- void ShareBox::addPeerToMultiSelect(not_null<Data::Thread*> thread) {
- auto addItemWay = Ui::MultiSelect::AddItemWay::Default;
- const auto peer = thread->peer();
- const auto topic = thread->asTopic();
- _select->addItem(
- peer->id.value,
- (topic
- ? topic->title()
- : peer->isSelf()
- ? tr::lng_saved_short(tr::now)
- : peer->shortName()),
- st::activeButtonBg,
- (topic
- ? ForceRoundUserpicCallback(peer)
- : PaintUserpicCallback(peer, true)),
- addItemWay);
- }
- void ShareBox::innerSelectedChanged(
- not_null<Data::Thread*> thread,
- bool checked) {
- if (checked) {
- addPeerToMultiSelect(thread);
- _select->clearQuery();
- } else {
- _select->removeItem(thread->peer()->id.value);
- }
- selectedChanged();
- update();
- }
- void ShareBox::submit(Api::SendOptions options) {
- _submitLifetime.destroy();
- auto threads = _inner->selected();
- const auto weak = Ui::MakeWeak(this);
- const auto field = _comment->entity();
- auto comment = field->getTextWithAppliedMarkdown();
- const auto checkPaid = [=] {
- if (!_descriptor.countMessagesCallback) {
- return true;
- }
- const auto withPaymentApproved = crl::guard(weak, [=](int approved) {
- auto copy = options;
- copy.starsApproved = approved;
- submit(copy);
- });
- const auto messagesCount = _descriptor.countMessagesCallback(
- comment);
- const auto alreadyApproved = options.starsApproved;
- auto paid = std::vector<not_null<PeerData*>>();
- auto waiting = base::flat_set<not_null<PeerData*>>();
- auto totalStars = 0;
- for (const auto &thread : threads) {
- const auto peer = thread->peer();
- const auto details = ComputePaymentDetails(peer, messagesCount);
- if (!details) {
- waiting.emplace(peer);
- } else if (details->stars > 0) {
- totalStars += details->stars;
- paid.push_back(peer);
- }
- }
- if (!waiting.empty()) {
- _descriptor.session->changes().peerUpdates(
- Data::PeerUpdate::Flag::FullInfo
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- if (waiting.contains(update.peer)) {
- withPaymentApproved(alreadyApproved);
- }
- }, _submitLifetime);
- if (!_descriptor.session->credits().loaded()) {
- _descriptor.session->credits().loadedValue(
- ) | rpl::filter(
- rpl::mappers::_1
- ) | rpl::take(1) | rpl::start_with_next([=] {
- withPaymentApproved(alreadyApproved);
- }, _submitLifetime);
- }
- return false;
- } else if (totalStars > alreadyApproved) {
- const auto show = uiShow();
- const auto session = _descriptor.session;
- const auto sessionShow = Main::MakeSessionShow(show, session);
- const auto scheduleBoxSt = _descriptor.st.scheduleBox.get();
- ShowSendPaidConfirm(sessionShow, paid, SendPaymentDetails{
- .messages = messagesCount,
- .stars = totalStars,
- }, [=] { withPaymentApproved(totalStars); }, PaidConfirmStyles{
- .label = (scheduleBoxSt
- ? scheduleBoxSt->chooseDateTimeArgs.labelStyle
- : nullptr),
- .checkbox = _descriptor.st.checkbox,
- });
- return false;
- }
- return true;
- };
- if (const auto onstack = _descriptor.submitCallback) {
- const auto forwardOptions = (_forwardOptions.captionsCount
- && _forwardOptions.dropCaptions)
- ? Data::ForwardOptions::NoNamesAndCaptions
- : _forwardOptions.dropNames
- ? Data::ForwardOptions::NoSenderNames
- : Data::ForwardOptions::PreserveInfo;
- onstack(
- std::move(threads),
- checkPaid,
- std::move(comment),
- options,
- forwardOptions);
- }
- }
- void ShareBox::copyLink() const {
- if (const auto onstack = _descriptor.copyCallback) {
- onstack();
- }
- }
- void ShareBox::selectedChanged() {
- auto hasSelected = _inner->hasSelected();
- if (_hasSelected != hasSelected) {
- _hasSelected = hasSelected;
- createButtons();
- _comment->toggle(_hasSelected, anim::type::normal);
- _comment->resizeToWidth(st::boxWideWidth);
- }
- computeStarsCount();
- update();
- }
- void ShareBox::computeStarsCount() {
- auto perMessage = 0;
- for (const auto &thread : _inner->selected()) {
- perMessage += thread->peer()->starsPerMessageChecked();
- }
- const auto messagesCount = _descriptor.countMessagesCallback
- ? _descriptor.countMessagesCallback(_comment
- ? _comment->entity()->getTextWithTags()
- : TextWithTags())
- : 0;
- _starsToSend = perMessage * messagesCount;
- }
- void ShareBox::scrollTo(Ui::ScrollToRequest request) {
- scrollToY(request.ymin, request.ymax);
- //auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height();
- //auto from = scrollTop, to = scrollTop;
- //if (scrollTop > top) {
- // to = top;
- //} else if (scrollBottom < bottom) {
- // to = bottom - (scrollBottom - scrollTop);
- //}
- //if (from != to) {
- // _scrollAnimation.start([this]() { scrollAnimationCallback(); }, from, to, st::shareScrollDuration, anim::sineInOut);
- //}
- }
- void ShareBox::scrollAnimationCallback() {
- //auto scrollTop = qRound(_scrollAnimation.current(scrollArea()->scrollTop()));
- //scrollArea()->scrollToY(scrollTop);
- }
- ShareBox::Inner::Inner(
- QWidget *parent,
- const Descriptor &descriptor,
- std::shared_ptr<Ui::Show> show)
- : RpWidget(parent)
- , _descriptor(descriptor)
- , _show(std::move(show))
- , _st(_descriptor.st.peerList
- ? *_descriptor.st.peerList
- : st::shareBoxList)
- , _defaultChatsIndexed(
- std::make_unique<Dialogs::IndexedList>(
- Dialogs::SortMode::Add))
- , _chatsIndexed(_defaultChatsIndexed.get()) {
- _rowsTop = st::shareRowsTop;
- _rowHeight = st::shareRowHeight;
- setAttribute(Qt::WA_OpaquePaintEvent);
- if (_descriptor.moneyRestrictionError) {
- const auto session = _descriptor.session;
- rpl::merge(
- Data::AmPremiumValue(session) | rpl::to_empty,
- session->api().premium().someMessageMoneyRestrictionsResolved()
- ) | rpl::start_with_next([=] {
- refreshRestrictedRows();
- }, lifetime());
- }
- const auto self = _descriptor.session->user();
- const auto selfHistory = self->owner().history(self);
- if (_descriptor.filterCallback(selfHistory)) {
- _defaultChatsIndexed->addToEnd(selfHistory);
- }
- const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
- for (const auto &row : list->all()) {
- if (const auto history = row->history()) {
- if (!history->peer->isSelf()
- && (history->asForum()
- || _descriptor.filterCallback(history))) {
- _defaultChatsIndexed->addToEnd(history);
- }
- }
- }
- };
- addList(_descriptor.session->data().chatsList()->indexed());
- const auto id = Data::Folder::kId;
- if (const auto folder = _descriptor.session->data().folderLoaded(id)) {
- addList(folder->chatsList()->indexed());
- }
- addList(_descriptor.session->data().contactsNoChatsList());
- _filter = u"a"_q;
- updateFilter();
- _descriptor.session->changes().peerUpdates(
- Data::PeerUpdate::Flag::Photo
- ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
- updateChat(update.peer);
- }, lifetime());
- _descriptor.session->changes().realtimeNameUpdates(
- ) | rpl::start_with_next([=](const Data::NameUpdate &update) {
- _defaultChatsIndexed->peerNameChanged(
- update.peer,
- update.oldFirstLetters);
- }, lifetime());
- _descriptor.session->downloaderTaskFinished(
- ) | rpl::start_with_next([=] {
- update();
- }, lifetime());
- style::PaletteChanged(
- ) | rpl::start_with_next([=] {
- invalidateCache();
- }, lifetime());
- }
- void ShareBox::Inner::invalidateCache() {
- for (const auto &[peer, data] : _dataMap) {
- data->checkbox.invalidateCache();
- }
- }
- bool ShareBox::Inner::showLockedError(not_null<Chat*> chat) {
- if (!chat->restriction.premiumRequired) {
- return false;
- }
- ::Settings::ShowPremiumPromoToast(
- Main::MakeSessionShow(_show, _descriptor.session),
- ChatHelpers::ResolveWindowDefault(),
- _descriptor.moneyRestrictionError(chat->peer->asUser()).text,
- u"require_premium"_q);
- return true;
- }
- void ShareBox::Inner::refreshRestrictedRows() {
- auto changed = false;
- for (const auto &[peer, data] : _dataMap) {
- const auto history = data->history;
- const auto restriction = Api::ResolveMessageMoneyRestrictions(
- history->peer,
- history);
- if (data->restriction != restriction) {
- data->restriction = restriction;
- changed = true;
- }
- }
- for (const auto &data : d_byUsernameFiltered) {
- const auto history = data->history;
- const auto restriction = Api::ResolveMessageMoneyRestrictions(
- history->peer,
- history);
- if (data->restriction != restriction) {
- data->restriction = restriction;
- changed = true;
- }
- }
- if (changed) {
- update();
- }
- }
- void ShareBox::Inner::visibleTopBottomUpdated(
- int visibleTop,
- int visibleBottom) {
- _visibleTop = visibleTop;
- loadProfilePhotos();
- }
- void ShareBox::Inner::activateSkipRow(int direction) {
- activateSkipColumn(direction * _columnCount);
- }
- int ShareBox::Inner::displayedChatsCount() const {
- return _filter.isEmpty() ? _chatsIndexed->size() : (_filtered.size() + d_byUsernameFiltered.size());
- }
- void ShareBox::Inner::activateSkipColumn(int direction) {
- if (_active < 0) {
- if (direction > 0) {
- setActive(0);
- }
- return;
- }
- auto count = displayedChatsCount();
- auto active = _active + direction;
- if (active < 0) {
- active = (_active > 0) ? 0 : -1;
- }
- if (active >= count) {
- active = count - 1;
- }
- setActive(active);
- }
- void ShareBox::Inner::activateSkipPage(int pageHeight, int direction) {
- activateSkipRow(direction * (pageHeight / _rowHeight));
- }
- void ShareBox::Inner::updateChat(not_null<PeerData*> peer) {
- if (const auto i = _dataMap.find(peer); i != end(_dataMap)) {
- updateChatName(i->second.get());
- repaintChat(peer);
- }
- }
- void ShareBox::Inner::updateChatName(not_null<Chat*> chat) {
- const auto peer = chat->peer;
- const auto text = chat->topic
- ? chat->topic->title()
- : peer->isSelf()
- ? tr::lng_saved_messages(tr::now)
- : peer->isRepliesChat()
- ? tr::lng_replies_messages(tr::now)
- : peer->isVerifyCodes()
- ? tr::lng_verification_codes(tr::now)
- : peer->name();
- chat->name.setText(_st.item.nameStyle, text, Ui::NameTextOptions());
- }
- void ShareBox::Inner::initChatRestriction(not_null<Chat*> chat) {
- if (_descriptor.moneyRestrictionError) {
- const auto history = chat->history;
- const auto restriction = Api::ResolveMessageMoneyRestrictions(
- history->peer,
- history);
- if (restriction || restriction.known) {
- chat->restriction = restriction;
- }
- }
- }
- void ShareBox::Inner::repaintChatAtIndex(int index) {
- if (index < 0) return;
- auto row = index / _columnCount;
- auto column = index % _columnCount;
- update(style::rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width()));
- }
- ShareBox::Inner::Chat *ShareBox::Inner::getChatAtIndex(int index) {
- if (index < 0) {
- return nullptr;
- }
- const auto row = [=] {
- if (_filter.isEmpty()) {
- return (index < _chatsIndexed->size())
- ? (_chatsIndexed->begin() + index)->get()
- : nullptr;
- }
- return (index < _filtered.size())
- ? _filtered[index].get()
- : nullptr;
- }();
- if (row) {
- return static_cast<Chat*>(row->attached);
- }
- if (!_filter.isEmpty()) {
- index -= _filtered.size();
- if (index >= 0 && index < d_byUsernameFiltered.size()) {
- return d_byUsernameFiltered[index].get();
- }
- }
- return nullptr;
- }
- void ShareBox::Inner::repaintChat(not_null<PeerData*> peer) {
- repaintChatAtIndex(chatIndex(peer));
- }
- int ShareBox::Inner::chatIndex(not_null<PeerData*> peer) const {
- int index = 0;
- if (_filter.isEmpty()) {
- for (const auto &row : _chatsIndexed->all()) {
- if (const auto history = row->history()) {
- if (history->peer == peer) {
- return index;
- }
- }
- ++index;
- }
- } else {
- for (const auto &row : _filtered) {
- if (const auto history = row->history()) {
- if (history->peer == peer) {
- return index;
- }
- }
- ++index;
- }
- for (const auto &row : d_byUsernameFiltered) {
- if (row->peer == peer) {
- return index;
- }
- ++index;
- }
- }
- return -1;
- }
- void ShareBox::Inner::loadProfilePhotos() {
- if (!parentWidget()) {
- return;
- }
- auto yFrom = std::max(_visibleTop, 0);
- if (auto part = (yFrom % _rowHeight)) {
- yFrom -= part;
- }
- int yTo = yFrom + parentWidget()->height() * 5 * _columnCount;
- if (!yTo) {
- return;
- }
- yFrom *= _columnCount;
- yTo *= _columnCount;
- if (_filter.isEmpty()) {
- if (!_chatsIndexed->empty()) {
- const auto index = yFrom / _rowHeight;
- auto i = _chatsIndexed->begin()
- + std::min(index, _chatsIndexed->size());
- for (auto end = _chatsIndexed->cend(); i != end; ++i) {
- if (((*i)->index() * _rowHeight) >= yTo) {
- break;
- }
- preloadUserpic((*i)->entry());
- }
- }
- } else {
- const auto from = std::max(yFrom / _rowHeight, 0);
- const auto to = std::max((yTo / _rowHeight) + 1, from);
- const auto fto = std::min(to, int(_filtered.size()));
- const auto ffrom = std::min(from, fto);
- for (auto i = ffrom; i != fto; ++i) {
- preloadUserpic(_filtered[i]->entry());
- }
- const auto uto = std::min(
- to - int(_filtered.size()),
- int(d_byUsernameFiltered.size()));
- const auto ufrom = std::min(
- std::max(from - int(_filtered.size()), 0),
- uto);
- for (auto i = ufrom; i != uto; ++i) {
- preloadUserpic(d_byUsernameFiltered[i]->history);
- }
- }
- }
- void ShareBox::Inner::preloadUserpic(not_null<Dialogs::Entry*> entry) {
- entry->chatListPreloadData();
- const auto history = entry->asHistory();
- if (!_descriptor.moneyRestrictionError || !history) {
- return;
- } else if (!Api::ResolveMessageMoneyRestrictions(
- history->peer,
- history).known) {
- if (const auto user = history->peer->asUser()) {
- const auto api = &_descriptor.session->api();
- api->premium().resolveMessageMoneyRestrictions(user);
- }
- }
- }
- auto ShareBox::Inner::getChat(not_null<Dialogs::Row*> row)
- -> not_null<Chat*> {
- Expects(row->history() != nullptr);
- if (const auto data = static_cast<Chat*>(row->attached)) {
- return data;
- }
- const auto history = row->history();
- const auto peer = history->peer;
- if (const auto i = _dataMap.find(peer); i != end(_dataMap)) {
- row->attached = i->second.get();
- return i->second.get();
- }
- const auto &[i, ok] = _dataMap.emplace(
- peer,
- std::make_unique<Chat>(history, _st.item, [=] {
- repaintChat(peer);
- }));
- updateChatName(i->second.get());
- initChatRestriction(i->second.get());
- row->attached = i->second.get();
- return i->second.get();
- }
- void ShareBox::Inner::setActive(int active) {
- if (active != _active) {
- auto changeNameFg = [this](int index, float64 from, float64 to) {
- if (auto chat = getChatAtIndex(index)) {
- chat->nameActive.start([this, peer = chat->peer] {
- repaintChat(peer);
- }, from, to, st::shareActivateDuration);
- }
- };
- changeNameFg(_active, 1., 0.);
- _active = active;
- changeNameFg(_active, 0., 1.);
- }
- auto y = (_active < _columnCount) ? 0 : (_rowsTop + ((_active / _columnCount) * _rowHeight));
- _scrollToRequests.fire({ y, y + _rowHeight });
- }
- void ShareBox::Inner::paintChat(
- Painter &p,
- not_null<Chat*> chat,
- int index) {
- auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal);
- auto y = _rowsTop + (index / _columnCount) * _rowHeight;
- auto outerWidth = width();
- auto photoLeft = (_rowWidth - (_st.item.checkbox.imageRadius * 2)) / 2;
- auto photoTop = st::sharePhotoTop;
- chat->checkbox.paint(p, x + photoLeft, y + photoTop, outerWidth);
- if (chat->restriction) {
- PaintRestrictionBadge(
- p,
- &_st.item,
- chat->restriction.starsPerMessage,
- chat->badgeCache,
- x + photoLeft,
- y + photoTop,
- outerWidth,
- _st.item.checkbox.imageRadius * 2);
- }
- auto nameActive = chat->nameActive.value((index == _active) ? 1. : 0.);
- p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameActive));
- auto nameWidth = (_rowWidth - st::shareColumnSkip);
- auto nameLeft = st::shareColumnSkip / 2;
- auto nameTop = photoTop + _st.item.checkbox.imageRadius * 2 + st::shareNameTop;
- chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, outerWidth, 2, style::al_top, 0, -1, 0, true);
- }
- ShareBox::Inner::Chat::Chat(
- not_null<History*> history,
- const style::PeerListItem &st,
- Fn<void()> updateCallback)
- : history(history)
- , peer(history->peer)
- , checkbox(
- st.checkbox,
- updateCallback,
- PaintUserpicCallback(peer, true),
- [=](int size) { return peer->isForum()
- ? int(size * Ui::ForumUserpicRadiusMultiplier())
- : std::optional<int>(); })
- , name(st.checkbox.imageRadius * 2) {
- }
- void ShareBox::Inner::paintEvent(QPaintEvent *e) {
- Painter p(this);
- auto r = e->rect();
- p.setClipRect(r);
- p.fillRect(r, _st.bg);
- auto yFrom = r.y(), yTo = r.y() + r.height();
- auto rowFrom = yFrom / _rowHeight;
- auto rowTo = (yTo + _rowHeight - 1) / _rowHeight;
- auto indexFrom = rowFrom * _columnCount;
- auto indexTo = rowTo * _columnCount;
- if (_filter.isEmpty()) {
- if (!_chatsIndexed->empty()) {
- auto i = _chatsIndexed->begin()
- + std::min(indexFrom, _chatsIndexed->size());
- for (auto end = _chatsIndexed->cend(); i != end; ++i) {
- if (indexFrom >= indexTo) {
- break;
- }
- paintChat(p, getChat(*i), indexFrom);
- ++indexFrom;
- }
- } else {
- p.setFont(st::noContactsFont);
- p.setPen(_st.about.textFg);
- p.drawText(
- rect().marginsRemoved(st::boxPadding),
- tr::lng_bot_no_chats(tr::now),
- style::al_center);
- }
- } else {
- if (_filtered.empty()
- && _byUsernameFiltered.empty()
- && !_searching) {
- p.setFont(st::noContactsFont);
- p.setPen(_st.about.textFg);
- p.drawText(
- rect().marginsRemoved(st::boxPadding),
- tr::lng_bot_chats_not_found(tr::now),
- style::al_center);
- } else {
- auto filteredSize = _filtered.size();
- if (filteredSize) {
- if (indexFrom < 0) indexFrom = 0;
- while (indexFrom < indexTo) {
- if (indexFrom >= _filtered.size()) {
- break;
- }
- paintChat(p, getChat(_filtered[indexFrom]), indexFrom);
- ++indexFrom;
- }
- indexFrom -= filteredSize;
- indexTo -= filteredSize;
- }
- if (!_byUsernameFiltered.empty()) {
- if (indexFrom < 0) indexFrom = 0;
- while (indexFrom < indexTo) {
- if (indexFrom >= d_byUsernameFiltered.size()) {
- break;
- }
- paintChat(
- p,
- d_byUsernameFiltered[indexFrom].get(),
- filteredSize + indexFrom);
- ++indexFrom;
- }
- }
- }
- }
- }
- void ShareBox::Inner::enterEventHook(QEnterEvent *e) {
- setMouseTracking(true);
- }
- void ShareBox::Inner::leaveEventHook(QEvent *e) {
- setMouseTracking(false);
- }
- void ShareBox::Inner::mouseMoveEvent(QMouseEvent *e) {
- updateUpon(e->pos());
- setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default);
- }
- void ShareBox::Inner::updateUpon(const QPoint &pos) {
- auto x = pos.x(), y = pos.y();
- auto row = (y - _rowsTop) / _rowHeight;
- auto column = qFloor((x - _rowsLeft) / _rowWidthReal);
- auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2;
- auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop;
- auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip));
- auto yupon = (y >= top) && (y < top + _st.item.checkbox.imageRadius * 2 + st::shareNameTop + _st.item.nameStyle.font->height * 2);
- auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1;
- if (upon >= displayedChatsCount()) {
- upon = -1;
- }
- _upon = upon;
- }
- void ShareBox::Inner::mousePressEvent(QMouseEvent *e) {
- if (e->button() == Qt::LeftButton) {
- updateUpon(e->pos());
- changeCheckState(getChatAtIndex(_upon));
- }
- }
- void ShareBox::Inner::selectActive() {
- changeCheckState(getChatAtIndex(_active > 0 ? _active : 0));
- }
- void ShareBox::Inner::resizeEvent(QResizeEvent *e) {
- _columnSkip = (width() - _columnCount * _st.item.checkbox.imageRadius * 2) / float64(_columnCount + 1);
- _rowWidthReal = _st.item.checkbox.imageRadius * 2 + _columnSkip;
- _rowsLeft = qFloor(_columnSkip / 2);
- _rowWidth = qFloor(_rowWidthReal);
- update();
- }
- void ShareBox::Inner::changeCheckState(Chat *chat) {
- if (!chat || showLockedError(chat)) {
- return;
- } else if (!_filter.isEmpty()) {
- const auto history = chat->peer->owner().history(chat->peer);
- auto row = _chatsIndexed->getRow(history);
- if (!row) {
- row = _chatsIndexed->addToEnd(history).main;
- }
- chat = getChat(row);
- if (!chat->checkbox.checked()) {
- _chatsIndexed->moveToTop(history);
- }
- }
- const auto checked = chat->checkbox.checked();
- const auto forum = chat->peer->forum();
- if (checked || !forum) {
- changePeerCheckState(chat, !checked);
- } else {
- chooseForumTopic(chat->peer->forum());
- }
- }
- void ShareBox::Inner::chooseForumTopic(not_null<Data::Forum*> forum) {
- const auto guard = Ui::MakeWeak(this);
- const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
- auto chosen = [=](not_null<Data::ForumTopic*> topic) {
- if (const auto strong = *weak) {
- strong->closeBox();
- }
- if (!guard) {
- return;
- }
- const auto row = _chatsIndexed->getRow(topic->owningHistory());
- if (!row) {
- return;
- }
- const auto chat = getChat(row);
- Assert(!chat->topic);
- chat->topic = topic;
- chat->topic->destroyed(
- ) | rpl::start_with_next([=] {
- changePeerCheckState(chat, false);
- }, chat->topicLifetime);
- updateChatName(chat);
- changePeerCheckState(chat, true);
- };
- auto initBox = [=](not_null<PeerListBox*> box) {
- box->addButton(tr::lng_cancel(), [=] {
- box->closeBox();
- });
- forum->destroyed(
- ) | rpl::start_with_next([=] {
- box->closeBox();
- }, box->lifetime());
- };
- auto filter = [=](not_null<Data::ForumTopic*> topic) {
- return guard && _descriptor.filterCallback(topic);
- };
- auto box = Box<PeerListBox>(
- std::make_unique<ChooseTopicBoxController>(
- forum,
- std::move(chosen),
- std::move(filter)),
- std::move(initBox));
- *weak = box.data();
- _show->showBox(std::move(box));
- }
- void ShareBox::Inner::peerUnselected(not_null<PeerData*> peer) {
- if (const auto i = _dataMap.find(peer); i != end(_dataMap)) {
- changePeerCheckState(
- i->second.get(),
- false,
- ChangeStateWay::SkipCallback);
- }
- }
- void ShareBox::Inner::setPeerSelectedChangedCallback(
- Fn<void(not_null<Data::Thread*> thread, bool selected)> callback) {
- _peerSelectedChangedCallback = std::move(callback);
- }
- void ShareBox::Inner::changePeerCheckState(
- not_null<Chat*> chat,
- bool checked,
- ChangeStateWay useCallback) {
- chat->checkbox.setChecked(checked);
- const auto thread = chatThread(chat);
- if (checked) {
- _selected.emplace(thread);
- setActive(chatIndex(chat->peer));
- } else {
- _selected.remove(thread);
- if (chat->topic) {
- chat->topicLifetime.destroy();
- chat->topic = nullptr;
- updateChatName(chat);
- }
- }
- if (useCallback != ChangeStateWay::SkipCallback
- && _peerSelectedChangedCallback) {
- _peerSelectedChangedCallback(thread, checked);
- }
- }
- bool ShareBox::Inner::hasSelected() const {
- return _selected.size();
- }
- void ShareBox::Inner::updateFilter(QString filter) {
- _lastQuery = filter.toLower().trimmed();
- auto words = TextUtilities::PrepareSearchWords(_lastQuery);
- filter = words.isEmpty() ? QString() : words.join(' ');
- if (_filter != filter) {
- _filter = filter;
- _byUsernameFiltered.clear();
- d_byUsernameFiltered.clear();
- if (_filter.isEmpty()) {
- refresh();
- } else {
- _filtered = _chatsIndexed->filtered(words);
- refresh();
- _searching = true;
- _searchRequests.fire({});
- }
- setActive(-1);
- loadProfilePhotos();
- update();
- }
- }
- bool ShareBox::Inner::isFilterEmpty() const {
- return _filter.isEmpty();
- }
- rpl::producer<Ui::ScrollToRequest> ShareBox::Inner::scrollToRequests() const {
- return _scrollToRequests.events();
- }
- rpl::producer<> ShareBox::Inner::searchRequests() const {
- return _searchRequests.events();
- }
- void ShareBox::Inner::applyChatFilter(FilterId id) {
- if (!id) {
- _chatsIndexed = _defaultChatsIndexed.get();
- } else {
- _customChatsIndexed = std::make_unique<Dialogs::IndexedList>(
- Dialogs::SortMode::Add);
- _chatsIndexed = _customChatsIndexed.get();
- const auto addList = [&](not_null<Dialogs::IndexedList*> list) {
- for (const auto &row : list->all()) {
- if (const auto history = row->history()) {
- if (history->asForum()
- || _descriptor.filterCallback(history)) {
- _customChatsIndexed->addToEnd(history);
- }
- }
- }
- };
- const auto &data = _descriptor.session->data();
- addList(data.chatsFilters().chatsList(id)->indexed());
- }
- update();
- }
- void ShareBox::Inner::peopleReceived(
- const QString &query,
- const QVector<MTPPeer> &my,
- const QVector<MTPPeer> &people) {
- _lastQuery = query.toLower().trimmed();
- if (_lastQuery.at(0) == '@') {
- _lastQuery = _lastQuery.mid(1);
- }
- int32 already = _byUsernameFiltered.size();
- _byUsernameFiltered.reserve(already + my.size() + people.size());
- d_byUsernameFiltered.reserve(already + my.size() + people.size());
- const auto feedList = [&](const QVector<MTPPeer> &list) {
- for (const auto &data : list) {
- if (const auto peer = _descriptor.session->data().peerLoaded(
- peerFromMTP(data))) {
- const auto history = _descriptor.session->data().history(
- peer);
- if (!history->asForum()
- && !_descriptor.filterCallback(history)) {
- continue;
- } else if (history && _chatsIndexed->getRow(history)) {
- continue;
- } else if (base::contains(_byUsernameFiltered, peer)) {
- continue;
- }
- _byUsernameFiltered.push_back(peer);
- d_byUsernameFiltered.push_back(std::make_unique<Chat>(
- history,
- _st.item,
- [=] { repaintChat(peer); }));
- updateChatName(d_byUsernameFiltered.back().get());
- initChatRestriction(d_byUsernameFiltered.back().get());
- }
- }
- };
- feedList(my);
- feedList(people);
- _searching = false;
- refresh();
- }
- void ShareBox::Inner::refresh() {
- auto count = displayedChatsCount();
- if (count) {
- auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0);
- resize(width(), _rowsTop + rows * _rowHeight);
- } else {
- resize(width(), st::noContactsHeight);
- }
- loadProfilePhotos();
- update();
- }
- not_null<Data::Thread*> ShareBox::Inner::chatThread(
- not_null<Chat*> chat) const {
- return chat->topic
- ? (Data::Thread*)chat->topic
- : chat->peer->owner().history(chat->peer).get();
- }
- std::vector<not_null<Data::Thread*>> ShareBox::Inner::selected() const {
- auto result = std::vector<not_null<Data::Thread*>>();
- result.reserve(_dataMap.size());
- for (const auto &[peer, chat] : _dataMap) {
- if (chat->checkbox.checked()) {
- result.push_back(chatThread(chat.get()));
- }
- }
- return result;
- }
- ChatHelpers::ForwardedMessagePhraseArgs CreateForwardedMessagePhraseArgs(
- const std::vector<not_null<Data::Thread*>> &result,
- const MessageIdsList &msgIds) {
- const auto toCount = result.size();
- return {
- .toCount = result.size(),
- .singleMessage = (msgIds.size() <= 1),
- .to1 = (toCount > 0) ? result.front()->peer().get() : nullptr,
- .to2 = (toCount > 1) ? result[1]->peer().get() : nullptr,
- };
- }
- ShareBox::CountMessagesCallback ShareBox::DefaultForwardCountMessages(
- not_null<History*> history,
- MessageIdsList msgIds) {
- return [=](const TextWithTags &comment) {
- const auto items = history->owner().idsToItems(msgIds);
- return int(items.size()) + (comment.empty() ? 0 : 1);
- };
- }
- ShareBox::SubmitCallback ShareBox::DefaultForwardCallback(
- std::shared_ptr<Ui::Show> show,
- not_null<History*> history,
- MessageIdsList msgIds,
- std::optional<TimeId> videoTimestamp) {
- struct State final {
- base::flat_set<mtpRequestId> requests;
- };
- const auto state = std::make_shared<State>();
- return [=](
- std::vector<not_null<Data::Thread*>> &&result,
- Fn<bool()> checkPaid,
- TextWithTags comment,
- Api::SendOptions options,
- Data::ForwardOptions forwardOptions) {
- if (!state->requests.empty()) {
- return; // Share clicked already.
- }
- const auto items = history->owner().idsToItems(msgIds);
- const auto existingIds = history->owner().itemsToIds(items);
- if (existingIds.empty() || result.empty()) {
- return;
- }
- const auto error = GetErrorForSending(
- result,
- { .forward = &items, .text = &comment });
- if (error.error) {
- show->showBox(MakeSendErrorBox(error, result.size() > 1));
- return;
- } else if (!checkPaid()) {
- return;
- }
- using Flag = MTPmessages_ForwardMessages::Flag;
- const auto commonSendFlags = Flag(0)
- | Flag::f_with_my_score
- | (options.scheduled ? Flag::f_schedule_date : Flag(0))
- | ((forwardOptions != Data::ForwardOptions::PreserveInfo)
- ? Flag::f_drop_author
- : Flag(0))
- | ((forwardOptions == Data::ForwardOptions::NoNamesAndCaptions)
- ? Flag::f_drop_media_captions
- : Flag(0))
- | (videoTimestamp.has_value()
- ? Flag::f_video_timestamp
- : Flag(0));
- auto mtpMsgIds = QVector<MTPint>();
- mtpMsgIds.reserve(existingIds.size());
- for (const auto &fullId : existingIds) {
- mtpMsgIds.push_back(MTP_int(fullId.msg));
- }
- const auto generateRandom = [&] {
- auto result = QVector<MTPlong>(existingIds.size());
- for (auto &value : result) {
- value = base::RandomValue<MTPlong>();
- }
- return result;
- };
- auto &api = history->owner().session().api();
- auto &histories = history->owner().histories();
- const auto donePhraseArgs = CreateForwardedMessagePhraseArgs(
- result,
- msgIds);
- const auto requestType = Data::Histories::RequestType::Send;
- for (const auto thread : result) {
- if (!comment.text.isEmpty()) {
- auto message = Api::MessageToSend(
- Api::SendAction(thread, options));
- message.textWithTags = comment;
- message.action.clearDraft = false;
- api.sendMessage(std::move(message));
- }
- const auto topicRootId = thread->topicRootId();
- const auto kGeneralId = Data::ForumTopic::kGeneralId;
- const auto topMsgId = (topicRootId == kGeneralId)
- ? MsgId(0)
- : topicRootId;
- const auto peer = thread->peer();
- const auto threadHistory = thread->owningHistory();
- const auto starsPaid = std::min(
- peer->starsPerMessageChecked(),
- options.starsApproved);
- if (starsPaid) {
- options.starsApproved -= starsPaid;
- }
- histories.sendRequest(threadHistory, requestType, [=](
- Fn<void()> finish) {
- const auto session = &threadHistory->session();
- auto &api = session->api();
- const auto sendFlags = commonSendFlags
- | (topMsgId ? Flag::f_top_msg_id : Flag(0))
- | (ShouldSendSilent(peer, options)
- ? Flag::f_silent
- : Flag(0))
- | (options.shortcutId
- ? Flag::f_quick_reply_shortcut
- : Flag(0))
- | (starsPaid ? Flag::f_allow_paid_stars : Flag());
- threadHistory->sendRequestId = api.request(
- MTPmessages_ForwardMessages(
- MTP_flags(sendFlags),
- history->peer->input,
- MTP_vector<MTPint>(mtpMsgIds),
- MTP_vector<MTPlong>(generateRandom()),
- peer->input,
- MTP_int(topMsgId),
- MTP_int(options.scheduled),
- MTP_inputPeerEmpty(), // send_as
- Data::ShortcutIdToMTP(session, options.shortcutId),
- MTP_int(videoTimestamp.value_or(0)),
- MTP_long(starsPaid)
- )).done([=](const MTPUpdates &updates, mtpRequestId reqId) {
- threadHistory->session().api().applyUpdates(updates);
- state->requests.remove(reqId);
- if (state->requests.empty()) {
- if (show->valid()) {
- auto phrase = rpl::variable<TextWithEntities>(
- ChatHelpers::ForwardedMessagePhrase(
- donePhraseArgs)).current();
- show->showToast(std::move(phrase));
- show->hideLayer();
- }
- }
- finish();
- }).fail([=](const MTP::Error &error) {
- const auto type = error.type();
- if (type.startsWith(u"ALLOW_PAYMENT_REQUIRED_"_q)) {
- show->showToast(u"Payment requirements changed. "
- "Please, try again."_q);
- } else if (type == u"VOICE_MESSAGES_FORBIDDEN"_q) {
- show->showToast(
- tr::lng_restricted_send_voice_messages(
- tr::now,
- lt_user,
- peer->name()));
- }
- finish();
- }).afterRequest(threadHistory->sendRequestId).send();
- return threadHistory->sendRequestId;
- });
- state->requests.insert(threadHistory->sendRequestId);
- }
- };
- }
- ShareBoxStyleOverrides DarkShareBoxStyle() {
- using namespace HistoryView;
- const auto schedule = [&] {
- auto date = Ui::ChooseDateTimeStyleArgs();
- date.labelStyle = &st::groupCallBoxLabel;
- date.dateFieldStyle = &st::groupCallScheduleDateField;
- date.timeFieldStyle = &st::groupCallScheduleTimeField;
- date.separatorStyle = &st::callMuteButtonLabel;
- date.atStyle = &st::callMuteButtonLabel;
- date.calendarStyle = &st::groupCallCalendarColors;
- auto st = ScheduleBoxStyleArgs();
- st.topButtonStyle = &st::groupCallMenuToggle;
- st.popupMenuStyle = &st::groupCallPopupMenu;
- st.chooseDateTimeArgs = std::move(date);
- return st;
- };
- return {
- .multiSelect = &st::groupCallMultiSelect,
- .comment = &st::groupCallShareBoxComment,
- .peerList = &st::groupCallShareBoxList,
- .label = &st::groupCallField,
- .checkbox = &st::groupCallCheckbox,
- .scheduleBox = std::make_shared<ScheduleBoxStyleArgs>(schedule()),
- };
- }
- void FastShareMessage(
- std::shared_ptr<Main::SessionShow> show,
- not_null<HistoryItem*> item,
- ShareBoxStyleOverrides st) {
- const auto history = item->history();
- const auto owner = &history->owner();
- const auto session = &history->session();
- const auto msgIds = owner->itemOrItsGroup(item);
- const auto isGame = item->getMessageBot()
- && item->media()
- && (item->media()->game() != nullptr);
- const auto canCopyLink = item->hasDirectLink() || isGame;
- const auto items = owner->idsToItems(msgIds);
- const auto hasCaptions = ranges::any_of(items, [](auto item) {
- return item->media()
- && !item->originalText().text.isEmpty()
- && item->media()->allowsEditCaption();
- });
- const auto hasOnlyForcedForwardedInfo = hasCaptions
- ? false
- : ranges::all_of(items, [](auto item) {
- return item->media() && item->media()->forceForwardedInfo();
- });
- auto copyCallback = [=] {
- const auto item = owner->message(msgIds[0]);
- if (!item) {
- return;
- }
- if (item->hasDirectLink()) {
- using namespace HistoryView;
- CopyPostLink(show, item->fullId(), Context::History);
- } else if (const auto bot = item->getMessageBot()) {
- if (const auto media = item->media()) {
- if (const auto game = media->game()) {
- const auto link = session->createInternalLinkFull(
- bot->username() + u"?game="_q + game->shortName);
- QGuiApplication::clipboard()->setText(link);
- show->showToast(
- tr::lng_share_game_link_copied(tr::now));
- }
- }
- }
- };
- const auto requiredRight = item->requiredSendRight();
- const auto requiresInline = item->requiresSendInlineRight();
- auto filterCallback = [=](not_null<Data::Thread*> thread) {
- if (const auto user = thread->peer()->asUser()) {
- if (user->canSendIgnoreMoneyRestrictions()) {
- return true;
- }
- }
- return Data::CanSend(thread, requiredRight)
- && (!requiresInline
- || Data::CanSend(thread, ChatRestriction::SendInline))
- && (!isGame || !thread->peer()->isBroadcast());
- };
- auto copyLinkCallback = canCopyLink
- ? Fn<void()>(std::move(copyCallback))
- : Fn<void()>();
- show->show(Box<ShareBox>(ShareBox::Descriptor{
- .session = session,
- .copyCallback = std::move(copyLinkCallback),
- .countMessagesCallback = ShareBox::DefaultForwardCountMessages(
- history,
- msgIds),
- .submitCallback = ShareBox::DefaultForwardCallback(
- show,
- history,
- msgIds),
- .filterCallback = std::move(filterCallback),
- .st = st,
- .forwardOptions = {
- .sendersCount = ItemsForwardSendersCount(items),
- .captionsCount = ItemsForwardCaptionsCount(items),
- .show = !hasOnlyForcedForwardedInfo,
- },
- .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
- }), Ui::LayerOption::CloseOther);
- }
- void FastShareMessage(
- not_null<Window::SessionController*> controller,
- not_null<HistoryItem*> item,
- ShareBoxStyleOverrides st) {
- FastShareMessage(controller->uiShow(), item, st);
- }
- void FastShareLink(
- not_null<Window::SessionController*> controller,
- const QString &url,
- ShareBoxStyleOverrides st) {
- FastShareLink(controller->uiShow(), url, st);
- }
- void FastShareLink(
- std::shared_ptr<Main::SessionShow> show,
- const QString &url,
- ShareBoxStyleOverrides st) {
- const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
- const auto sending = std::make_shared<bool>();
- auto copyCallback = [=] {
- QGuiApplication::clipboard()->setText(url);
- show->showToast(tr::lng_background_link_copied(tr::now));
- };
- auto countMessagesCallback = [=](const TextWithTags &comment) {
- return 1;
- };
- auto submitCallback = [=](
- std::vector<not_null<::Data::Thread*>> &&result,
- Fn<bool()> checkPaid,
- TextWithTags &&comment,
- Api::SendOptions options,
- ::Data::ForwardOptions) {
- if (*sending || result.empty()) {
- return;
- }
- const auto error = GetErrorForSending(
- result,
- { .text = &comment });
- if (error.error) {
- if (const auto weak = *box) {
- weak->getDelegate()->show(
- MakeSendErrorBox(error, result.size() > 1));
- }
- return;
- } else if (!checkPaid()) {
- return;
- }
- *sending = true;
- if (!comment.text.isEmpty()) {
- comment.text = url + "\n" + comment.text;
- const auto add = url.size() + 1;
- for (auto &tag : comment.tags) {
- tag.offset += add;
- }
- } else {
- comment.text = url;
- }
- auto &api = show->session().api();
- for (const auto thread : result) {
- auto message = Api::MessageToSend(
- Api::SendAction(thread, options));
- message.textWithTags = comment;
- message.action.clearDraft = false;
- api.sendMessage(std::move(message));
- }
- if (*box) {
- (*box)->closeBox();
- }
- show->showToast(tr::lng_share_done(tr::now));
- };
- auto filterCallback = [](not_null<::Data::Thread*> thread) {
- if (const auto user = thread->peer()->asUser()) {
- if (user->canSendIgnoreMoneyRestrictions()) {
- return true;
- }
- }
- return ::Data::CanSend(thread, ChatRestriction::SendOther);
- };
- *box = show->show(
- Box<ShareBox>(ShareBox::Descriptor{
- .session = &show->session(),
- .copyCallback = std::move(copyCallback),
- .countMessagesCallback = std::move(countMessagesCallback),
- .submitCallback = std::move(submitCallback),
- .filterCallback = std::move(filterCallback),
- .st = st,
- .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
- }),
- Ui::LayerOption::KeepOther,
- anim::type::normal);
- }
- auto ShareMessageMoneyRestrictionError()
- -> Fn<RecipientMoneyRestrictionError(not_null<UserData*>)> {
- return WriteMoneyRestrictionError;
- }
|