| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979 |
- /*
- 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 "media/stories/media_stories_controller.h"
- #include "base/platform/base_platform_info.h"
- #include "base/power_save_blocker.h"
- #include "base/qt_signal_producer.h"
- #include "base/unixtime.h"
- #include "boxes/peers/prepare_short_info_box.h"
- #include "boxes/report_messages_box.h"
- #include "chat_helpers/compose/compose_show.h"
- #include "core/application.h"
- #include "core/click_handler_types.h"
- #include "core/core_settings.h"
- #include "core/local_url_handlers.h"
- #include "core/update_checker.h"
- #include "data/data_changes.h"
- #include "data/data_document.h"
- #include "data/data_file_origin.h"
- #include "data/data_peer.h"
- #include "data/data_session.h"
- #include "data/data_stories.h"
- #include "history/view/reactions/history_view_reactions_strip.h"
- #include "lang/lang_keys.h"
- #include "main/main_session.h"
- #include "media/stories/media_stories_caption_full_view.h"
- #include "media/stories/media_stories_delegate.h"
- #include "media/stories/media_stories_header.h"
- #include "media/stories/media_stories_sibling.h"
- #include "media/stories/media_stories_slider.h"
- #include "media/stories/media_stories_reactions.h"
- #include "media/stories/media_stories_recent_views.h"
- #include "media/stories/media_stories_reply.h"
- #include "media/stories/media_stories_repost_view.h"
- #include "media/stories/media_stories_share.h"
- #include "media/stories/media_stories_stealth.h"
- #include "media/stories/media_stories_view.h"
- #include "media/audio/media_audio.h"
- #include "settings/settings_credits_graphics.h"
- #include "ui/boxes/confirm_box.h"
- #include "ui/boxes/report_box_graphics.h"
- #include "ui/text/text_utilities.h"
- #include "ui/toast/toast.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/labels.h"
- #include "ui/round_rect.h"
- #include "window/window_controller.h"
- #include "window/window_session_controller.h"
- #include "styles/style_chat_helpers.h" // defaultReportBox
- #include "styles/style_media_view.h"
- #include "styles/style_boxes.h" // UserpicButton
- #include <QtGui/QWindow>
- namespace Media::Stories {
- namespace {
- constexpr auto kPhotoProgressInterval = crl::time(100);
- constexpr auto kPhotoDuration = 5 * crl::time(1000);
- constexpr auto kFullContentFade = 0.6;
- constexpr auto kSiblingMultiplierDefault = 0.448;
- constexpr auto kSiblingMultiplierMax = 0.72;
- constexpr auto kSiblingOutsidePart = 0.24;
- constexpr auto kSiblingUserpicSize = 0.3;
- constexpr auto kInnerHeightMultiplier = 1.6;
- constexpr auto kPreloadPeersCount = 3;
- constexpr auto kPreloadStoriesCount = 5;
- constexpr auto kPreloadNextMediaCount = 3;
- constexpr auto kPreloadPreviousMediaCount = 1;
- constexpr auto kMarkAsReadAfterSeconds = 0.2;
- constexpr auto kMarkAsReadAfterProgress = 0.;
- struct SameDayRange {
- int from = 0;
- int till = 0;
- };
- [[nodiscard]] SameDayRange ComputeSameDayRange(
- not_null<Data::Story*> story,
- const Data::StoriesIds &ids,
- const std::vector<StoryId> &sorted,
- int index) {
- Expects(index >= 0 && index < ids.list.size());
- Expects(index >= 0 && index < sorted.size());
- const auto pinned = int(ids.pinnedToTop.size());
- if (index < pinned) {
- return SameDayRange{ .from = 0, .till = pinned - 1 };
- }
- auto result = SameDayRange{ .from = index, .till = index };
- const auto peerId = story->peer()->id;
- const auto stories = &story->owner().stories();
- const auto now = base::unixtime::parse(story->date());
- for (auto i = index; i != 0;) {
- const auto storyId = sorted[--i];
- if (const auto maybeStory = stories->lookup({ peerId, storyId })) {
- const auto day = base::unixtime::parse((*maybeStory)->date());
- if (day.date() != now.date()) {
- break;
- }
- }
- --result.from;
- }
- for (auto i = index + 1, c = int(sorted.size()); i != c; ++i) {
- const auto storyId = sorted[i];
- if (const auto maybeStory = stories->lookup({ peerId, storyId })) {
- const auto day = base::unixtime::parse((*maybeStory)->date());
- if (day.date() != now.date()) {
- break;
- }
- }
- ++result.till;
- }
- return result;
- }
- [[nodiscard]] QPoint Rotated(QPoint point, QPoint origin, float64 angle) {
- if (std::abs(angle) < 1.) {
- return point;
- }
- const auto alpha = angle / 180. * M_PI;
- const auto acos = cos(alpha);
- const auto asin = sin(alpha);
- point -= origin;
- return origin + QPoint(
- int(base::SafeRound(acos * point.x() - asin * point.y())),
- int(base::SafeRound(asin * point.x() + acos * point.y())));
- }
- [[nodiscard]] bool ResolveWeatherInCelsius() {
- const auto saved = Core::App().settings().weatherInCelsius();
- return saved.value_or(!ranges::contains(
- std::array{ u"US"_q, u"BS"_q, u"KY"_q, u"LR"_q, u"BZ"_q },
- Platform::SystemCountry().toUpper()));
- }
- } // namespace
- class Controller::PhotoPlayback final {
- public:
- explicit PhotoPlayback(not_null<Controller*> controller);
- [[nodiscard]] bool paused() const;
- void togglePaused(bool paused);
- private:
- void callback();
- const not_null<Controller*> _controller;
- base::Timer _timer;
- crl::time _started = 0;
- crl::time _paused = 0;
- };
- class Controller::Unsupported final {
- public:
- Unsupported(not_null<Controller*> controller, not_null<PeerData*> peer);
- private:
- void setup(not_null<PeerData*> peer);
- const not_null<Controller*> _controller;
- std::unique_ptr<Ui::RpWidget> _bg;
- std::unique_ptr<Ui::FlatLabel> _text;
- std::unique_ptr<Ui::RoundButton> _button;
- Ui::RoundRect _bgRound;
- };
- Controller::PhotoPlayback::PhotoPlayback(not_null<Controller*> controller)
- : _controller(controller)
- , _timer([=] { callback(); })
- , _started(crl::now())
- , _paused(_started) {
- }
- bool Controller::PhotoPlayback::paused() const {
- return _paused != 0;
- }
- void Controller::PhotoPlayback::togglePaused(bool paused) {
- if (!_paused == !paused) {
- return;
- } else if (paused) {
- const auto now = crl::now();
- if (now - _started >= kPhotoDuration) {
- return;
- }
- _paused = now;
- _timer.cancel();
- } else {
- _started += crl::now() - _paused;
- _paused = 0;
- _timer.callEach(kPhotoProgressInterval);
- }
- callback();
- }
- void Controller::PhotoPlayback::callback() {
- const auto now = crl::now();
- const auto elapsed = now - _started;
- const auto finished = (now - _started >= kPhotoDuration);
- if (finished) {
- _timer.cancel();
- }
- using State = Player::State;
- const auto state = finished
- ? State::StoppedAtEnd
- : _paused
- ? State::Paused
- : State::Playing;
- _controller->updatePhotoPlayback({
- .state = state,
- .position = elapsed,
- .receivedTill = kPhotoDuration,
- .length = kPhotoDuration,
- .frequency = 1000,
- });
- }
- Controller::Unsupported::Unsupported(
- not_null<Controller*> controller,
- not_null<PeerData*> peer)
- : _controller(controller)
- , _bgRound(st::storiesRadius, st::storiesComposeBg) {
- setup(peer);
- }
- void Controller::Unsupported::setup(not_null<PeerData*> peer) {
- const auto wrap = _controller->wrap();
- _bg = std::make_unique<Ui::RpWidget>(wrap);
- _bg->show();
- _bg->paintRequest() | rpl::start_with_next([=] {
- auto p = QPainter(_bg.get());
- _bgRound.paint(p, _bg->rect());
- }, _bg->lifetime());
- _controller->layoutValue(
- ) | rpl::start_with_next([=](const Layout &layout) {
- _bg->setGeometry(layout.content);
- }, _bg->lifetime());
- _text = std::make_unique<Ui::FlatLabel>(
- wrap,
- tr::lng_stories_unsupported(),
- st::storiesUnsupportedLabel);
- _text->show();
- _button = std::make_unique<Ui::RoundButton>(
- wrap,
- tr::lng_update_telegram(),
- st::storiesUnsupportedUpdate);
- _button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
- _button->show();
- rpl::combine(
- _controller->layoutValue(),
- _text->sizeValue(),
- _button->sizeValue()
- ) | rpl::start_with_next([=](
- const Layout &layout,
- QSize text,
- QSize button) {
- const auto wrap = layout.content;
- const auto totalHeight = st::storiesUnsupportedTop
- + text.height()
- + st::storiesUnsupportedSkip
- + button.height();
- const auto top = (wrap.height() - totalHeight) / 2;
- _text->move(
- wrap.x() + (wrap.width() - text.width()) / 2,
- wrap.y() + top + st::storiesUnsupportedTop);
- _button->move(
- wrap.x() + (wrap.width() - button.width()) / 2,
- wrap.y() + top + totalHeight - button.height());
- }, _button->lifetime());
- _button->setClickedCallback([=] {
- Core::UpdateApplication();
- });
- }
- Controller::Controller(not_null<Delegate*> delegate)
- : _delegate(delegate)
- , _wrap(_delegate->storiesWrap())
- , _header(std::make_unique<Header>(this))
- , _slider(std::make_unique<Slider>(this))
- , _replyArea(std::make_unique<ReplyArea>(this))
- , _reactions(std::make_unique<Reactions>(this))
- , _recentViews(std::make_unique<RecentViews>(this))
- , _weatherInCelsius(ResolveWeatherInCelsius()){
- initLayout();
- using namespace rpl::mappers;
- rpl::combine(
- _replyArea->activeValue(),
- _reactions->activeValue(),
- _1 || _2
- ) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](bool active) {
- _replyActive = active;
- updateContentFaded();
- }, _lifetime);
- _reactions->setReplyFieldState(
- _replyArea->focusedValue(),
- _replyArea->hasSendTextValue());
- if (const auto like = _replyArea->likeAnimationTarget()) {
- _reactions->attachToReactionButton(like);
- }
- _reactions->chosen(
- ) | rpl::start_with_next([=](Reactions::Chosen chosen) {
- if (reactionChosen(chosen.mode, chosen.reaction)) {
- _reactions->animateAndProcess(std::move(chosen));
- }
- }, _lifetime);
- _delegate->storiesLayerShown(
- ) | rpl::start_with_next([=](bool shown) {
- if (_layerShown != shown) {
- _layerShown = shown;
- updatePlayingAllowed();
- }
- }, _lifetime);
- _header->tooltipShownValue(
- ) | rpl::start_with_next([=](bool shown) {
- if (_tooltipShown != shown) {
- _tooltipShown = shown;
- updatePlayingAllowed();
- }
- }, _lifetime);
- _wrap->windowActiveValue(
- ) | rpl::start_with_next([=](bool active) {
- _windowActive = active;
- updatePlayingAllowed();
- }, _lifetime);
- _contentFadeAnimation.stop();
- }
- Controller::~Controller() {
- _captionFullView = nullptr;
- _repostView = nullptr;
- changeShown(nullptr);
- }
- void Controller::updateContentFaded() {
- const auto faded = _replyActive
- || (_captionFullView && !_captionFullView->closing());
- if (_contentFaded == faded) {
- return;
- }
- _contentFaded = faded;
- _contentFadeAnimation.start(
- [=] { _delegate->storiesRepaint(); },
- _contentFaded ? 0. : 1.,
- _contentFaded ? 1. : 0.,
- st::fadeWrapDuration);
- updatePlayingAllowed();
- }
- void Controller::initLayout() {
- const auto headerHeight = st::storiesHeaderMargin.top()
- + st::storiesHeaderPhoto.photoSize
- + st::storiesHeaderMargin.bottom();
- const auto sliderHeight = st::storiesSliderMargin.top()
- + st::storiesSliderWidth
- + st::storiesSliderMargin.bottom();
- const auto outsideHeaderHeight = headerHeight
- + sliderHeight
- + st::storiesSliderOutsideSkip;
- const auto fieldMinHeight = st::storiesFieldMargin.top()
- + st::storiesAttach.height
- + st::storiesFieldMargin.bottom();
- const auto minHeightForOutsideHeader = st::storiesFieldMargin.bottom()
- + outsideHeaderHeight
- + st::storiesMaxSize.height()
- + fieldMinHeight;
- _layout = _wrap->sizeValue(
- ) | rpl::map([=](QSize size) {
- const auto topNotchSkip = _delegate->storiesTopNotchSkip();
- size = QSize(
- std::max(size.width(), st::mediaviewMinWidth),
- std::max(size.height(), st::mediaviewMinHeight));
- auto layout = Layout();
- layout.headerLayout = (size.height() >= minHeightForOutsideHeader)
- ? HeaderLayout::Outside
- : HeaderLayout::Normal;
- const auto topSkip = topNotchSkip
- + st::storiesFieldMargin.bottom()
- + (layout.headerLayout == HeaderLayout::Outside
- ? outsideHeaderHeight
- : 0);
- const auto bottomSkip = fieldMinHeight;
- const auto maxWidth = size.width() - 2 * st::storiesSideSkip;
- const auto availableHeight = size.height() - topSkip - bottomSkip;
- const auto maxContentHeight = std::min(
- availableHeight,
- st::storiesMaxSize.height());
- const auto nowWidth = maxContentHeight * st::storiesMaxSize.width()
- / st::storiesMaxSize.height();
- const auto contentWidth = std::min(nowWidth, maxWidth);
- const auto contentHeight = (contentWidth < nowWidth)
- ? (contentWidth * st::storiesMaxSize.height()
- / st::storiesMaxSize.width())
- : maxContentHeight;
- const auto addedTopSkip = (availableHeight - contentHeight) / 2;
- layout.content = QRect(
- (size.width() - contentWidth) / 2,
- addedTopSkip + topSkip,
- contentWidth,
- contentHeight);
- const auto reactionsWidth = st::storiesReactionsWidth;
- layout.reactions = QRect(
- (size.width() - reactionsWidth) / 2,
- layout.content.y(),
- reactionsWidth,
- contentHeight);
- if (layout.headerLayout == HeaderLayout::Outside) {
- layout.header = QRect(
- layout.content.topLeft() - QPoint(0, outsideHeaderHeight),
- QSize(contentWidth, outsideHeaderHeight));
- layout.slider = QRect(
- layout.header.topLeft() + QPoint(0, headerHeight),
- QSize(contentWidth, sliderHeight));
- } else {
- layout.slider = QRect(
- layout.content.topLeft(),
- QSize(contentWidth, sliderHeight));
- layout.header = QRect(
- layout.slider.topLeft() + QPoint(0, sliderHeight),
- QSize(contentWidth, headerHeight));
- }
- layout.controlsWidth = std::max(
- layout.content.width(),
- st::storiesControlsMinWidth);
- layout.controlsBottomPosition = QPoint(
- (size.width() - layout.controlsWidth) / 2,
- (layout.content.y()
- + layout.content.height()
- + fieldMinHeight
- - st::storiesFieldMargin.bottom()));
- layout.views = QRect(
- layout.controlsBottomPosition - QPoint(0, fieldMinHeight),
- QSize(layout.controlsWidth, fieldMinHeight));
- layout.autocompleteRect = QRect(
- layout.controlsBottomPosition.x(),
- 0,
- layout.controlsWidth,
- layout.controlsBottomPosition.y());
- const auto sidesAvailable = size.width() - layout.content.width();
- const auto widthForSiblings = sidesAvailable
- - 2 * st::storiesFieldMargin.bottom();
- const auto siblingWidthMax = widthForSiblings
- / (2 * (1. - kSiblingOutsidePart));
- const auto siblingMultiplierMax = std::max(
- kSiblingMultiplierDefault,
- st::storiesSiblingWidthMin / float64(layout.content.width()));
- const auto siblingMultiplier = std::min({
- siblingMultiplierMax,
- kSiblingMultiplierMax,
- siblingWidthMax / layout.content.width(),
- });
- const auto siblingSize = layout.content.size() * siblingMultiplier;
- const auto siblingTop = (size.height() - siblingSize.height()) / 2;
- const auto outsideMax = int(base::SafeRound(
- siblingSize.width() * kSiblingOutsidePart));
- const auto leftAvailable = layout.content.x() - siblingSize.width();
- const auto xDesired = leftAvailable / 3;
- const auto xPossible = std::min(
- xDesired,
- (leftAvailable - st::storiesControlSize));
- const auto xLeft = std::max(xPossible, -outsideMax);
- const auto xRight = size.width() - siblingSize.width() - xLeft;
- const auto userpicSize = int(base::SafeRound(
- siblingSize.width() * kSiblingUserpicSize));
- const auto innerHeight = userpicSize * kInnerHeightMultiplier;
- const auto userpic = [&](QRect geometry) {
- return QRect(
- (geometry.width() - userpicSize) / 2,
- (geometry.height() - innerHeight) / 2,
- userpicSize,
- userpicSize
- ).translated(geometry.topLeft());
- };
- const auto nameFontSize = std::max(
- (st::storiesMaxNameFontSize * contentHeight
- / st::storiesMaxSize.height()),
- st::fsize);
- const auto nameBoundingRect = [&](QRect geometry, bool left) {
- const auto skipSmall = nameFontSize;
- const auto skipBig = skipSmall - std::min(xLeft, 0);
- return QRect(
- left ? skipBig : skipSmall,
- (geometry.height() - innerHeight) / 2,
- geometry.width() - skipSmall - skipBig,
- innerHeight
- ).translated(geometry.topLeft());
- };
- const auto left = QRect({ xLeft, siblingTop }, siblingSize);
- const auto right = QRect({ xRight, siblingTop }, siblingSize);
- layout.siblingLeft = {
- .geometry = left,
- .userpic = userpic(left),
- .nameBoundingRect = nameBoundingRect(left, true),
- .nameFontSize = nameFontSize,
- };
- layout.siblingRight = {
- .geometry = right,
- .userpic = userpic(right),
- .nameBoundingRect = nameBoundingRect(right, false),
- .nameFontSize = nameFontSize,
- };
- if (!_areas.empty()) {
- rebuildActiveAreas(layout);
- }
- return layout;
- });
- }
- void Controller::rebuildActiveAreas(const Layout &layout) const {
- const auto origin = layout.content.topLeft();
- const auto scale = layout.content.size();
- for (auto &area : _areas) {
- const auto &general = area.original;
- area.geometry = QRect(
- int(base::SafeRound(general.x() * scale.width())),
- int(base::SafeRound(general.y() * scale.height())),
- int(base::SafeRound(general.width() * scale.width())),
- int(base::SafeRound(general.height() * scale.height()))
- ).translated(origin);
- area.radius = scale.width() * area.radiusOriginal / 100.;
- if (const auto view = area.view.get()) {
- view->setAreaGeometry(area.geometry, area.radius);
- }
- }
- }
- Data::Story *Controller::story() const {
- if (!_session) {
- return nullptr;
- }
- const auto maybeStory = _session->data().stories().lookup(_shown);
- return maybeStory ? maybeStory->get() : nullptr;
- }
- not_null<Ui::RpWidget*> Controller::wrap() const {
- return _wrap;
- }
- Layout Controller::layout() const {
- Expects(_layout.current().has_value());
- return *_layout.current();
- }
- rpl::producer<Layout> Controller::layoutValue() const {
- return _layout.value() | rpl::filter_optional();
- }
- ContentLayout Controller::contentLayout() const {
- const auto ¤t = _layout.current();
- Assert(current.has_value());
- return {
- .geometry = current->content,
- .fade = (_contentFadeAnimation.value(_contentFaded ? 1. : 0.)
- * kFullContentFade),
- .radius = st::storiesRadius,
- .headerOutside = (current->headerLayout == HeaderLayout::Outside),
- };
- }
- bool Controller::closeByClickAt(QPoint position) const {
- const auto ¤t = _layout.current();
- Assert(current.has_value());
- return (position.x() < current->content.x() - st::storiesControlSize)
- || (position.x() > current->content.x() + current->content.width()
- + st::storiesControlSize);
- }
- Data::FileOrigin Controller::fileOrigin() const {
- return _shown;
- }
- TextWithEntities Controller::captionText() const {
- return _captionText;
- }
- bool Controller::skipCaption() const {
- return (_captionFullView != nullptr)
- || (_captionText.empty() && !repost());
- }
- bool Controller::repost() const {
- return _repostView != nullptr;
- }
- int Controller::repostSkipTop() const {
- return _repostView
- ? (_repostView->height()
- + (_captionText.empty() ? 0 : st::mediaviewTextSkip))
- : 0;
- }
- QMargins Controller::repostCaptionPadding() const {
- return { 0, repostSkipTop(), 0, 0 };
- }
- void Controller::drawRepostInfo(
- Painter &p,
- int x,
- int y,
- int availableWidth) const {
- Expects(_repostView != nullptr);
- _repostView->draw(p, x, y, availableWidth);
- }
- RepostClickHandler Controller::lookupRepostHandler(QPoint position) const {
- return _repostView
- ? _repostView->lookupHandler(position)
- : RepostClickHandler();
- }
- void Controller::toggleLiked() {
- _reactions->toggleLiked();
- }
- bool Controller::reactionChosen(ReactionsMode mode, ChosenReaction chosen) {
- auto result = true;
- if (mode == ReactionsMode::Message) {
- result = _replyArea->sendReaction(chosen.id);
- } else if (const auto peer = shownPeer()) {
- peer->owner().stories().sendReaction(_shown, chosen.id);
- }
- unfocusReply();
- return result;
- }
- void Controller::showFullCaption() {
- if (_captionText.empty()) {
- return;
- }
- _captionFullView = std::make_unique<CaptionFullView>(this);
- updateContentFaded();
- }
- void Controller::captionClosing() {
- updateContentFaded();
- }
- void Controller::captionClosed() {
- if (!_captionFullView) {
- return;
- } else if (_captionFullView->focused()) {
- _wrap->setFocus();
- }
- _captionFullView = nullptr;
- }
- std::shared_ptr<ChatHelpers::Show> Controller::uiShow() const {
- return _delegate->storiesShow();
- }
- auto Controller::stickerOrEmojiChosen() const
- -> rpl::producer<ChatHelpers::FileChosen> {
- return _delegate->storiesStickerOrEmojiChosen();
- }
- void Controller::rebuildFromContext(
- not_null<PeerData*> peer,
- FullStoryId storyId) {
- using namespace Data;
- auto &stories = peer->owner().stories();
- auto list = std::optional<StoriesList>();
- auto source = (const StoriesSource*)nullptr;
- const auto peerId = storyId.peer;
- const auto id = storyId.story;
- v::match(_context.data, [&](StoriesContextSingle) {
- hideSiblings();
- }, [&](StoriesContextPeer) {
- source = stories.source(peerId);
- hideSiblings();
- }, [&](StoriesContextSaved) {
- if (stories.savedCountKnown(peerId)) {
- const auto &saved = stories.saved(peerId);
- auto sorted = RespectingPinned(saved);
- const auto i = ranges::find(sorted, id);
- const auto tillEnd = int(end(sorted) - i);
- if (tillEnd > 0) {
- _index = int(i - begin(sorted));
- list = StoriesList{
- .peer = peer,
- .ids = saved,
- .sorted = std::move(sorted),
- .total = stories.savedCount(peerId),
- };
- if (saved.list.size() < list->total
- && tillEnd < kPreloadStoriesCount) {
- stories.savedLoadMore(peerId);
- }
- }
- }
- hideSiblings();
- }, [&](StoriesContextArchive) {
- if (stories.archiveCountKnown(peerId)) {
- const auto &archive = stories.archive(peerId);
- auto sorted = RespectingPinned(archive);
- const auto i = ranges::find(sorted, id);
- const auto tillEnd = int(end(sorted) - i);
- if (tillEnd > 0) {
- _index = int(i - begin(sorted));
- list = StoriesList{
- .peer = peer,
- .ids = archive,
- .sorted = std::move(sorted),
- .total = stories.archiveCount(peerId),
- };
- if (archive.list.size() < list->total
- && tillEnd < kPreloadStoriesCount) {
- stories.archiveLoadMore(peerId);
- }
- }
- }
- hideSiblings();
- }, [&](StorySourcesList list) {
- source = stories.source(peerId);
- const auto &sources = stories.sources(list);
- const auto i = ranges::find(
- sources,
- storyId.peer,
- &StoriesSourceInfo::id);
- if (i != end(sources)) {
- if (_cachedSourcesList.empty()) {
- _showingUnreadSources = source && (source->readTill < id);
- }
- rebuildCachedSourcesList(sources, (i - begin(sources)));
- _cachedSourcesList[_cachedSourceIndex].shownId = storyId.story;
- showSiblings(&peer->session());
- if (int(sources.end() - i) < kPreloadPeersCount) {
- stories.loadMore(list);
- }
- }
- });
- _sliderIndex = 0;
- _sliderCount = 0;
- if (list) {
- _source = std::nullopt;
- if (_list != list) {
- _list = std::move(list);
- }
- if (const auto maybe = peer->owner().stories().lookup(storyId)) {
- const auto now = *maybe;
- const auto range = ComputeSameDayRange(
- now,
- _list->ids,
- _list->sorted,
- _index);
- _sliderCount = range.till - range.from + 1;
- _sliderIndex = _index - range.from;
- }
- } else {
- if (source) {
- const auto i = source->ids.lower_bound(StoryIdDates{ id });
- if (i != end(source->ids) && i->id == id) {
- _index = int(i - begin(source->ids));
- } else {
- source = nullptr;
- }
- }
- if (!source) {
- _source = std::nullopt;
- _list = StoriesList{
- .peer = peer,
- .ids = { { id } },
- .sorted = { id },
- .total = 1,
- };
- _index = 0;
- } else {
- _list = std::nullopt;
- if (_source != *source) {
- _source = *source;
- }
- }
- }
- preloadNext();
- _slider->show({
- .index = _sliderCount ? _sliderIndex : _index,
- .total = _sliderCount ? _sliderCount : shownCount(),
- });
- }
- void Controller::preloadNext() {
- Expects(shown());
- auto ids = std::vector<FullStoryId>();
- ids.reserve(kPreloadPreviousMediaCount + kPreloadNextMediaCount);
- const auto peer = shownPeer();
- const auto count = shownCount();
- const auto till = std::min(_index + kPreloadNextMediaCount, count);
- for (auto i = _index + 1; i != till; ++i) {
- ids.push_back({ .peer = peer->id, .story = shownId(i) });
- }
- const auto from = std::max(_index - kPreloadPreviousMediaCount, 0);
- for (auto i = _index; i != from;) {
- ids.push_back({ .peer = peer->id, .story = shownId(--i) });
- }
- peer->owner().stories().setPreloadingInViewer(std::move(ids));
- }
- void Controller::checkMoveByDelta() {
- const auto index = _index + _waitingForDelta;
- if (_waitingForDelta && shown() && index >= 0 && index < shownCount()) {
- subjumpTo(index);
- }
- }
- void Controller::show(
- not_null<Data::Story*> story,
- Data::StoriesContext context) {
- auto &stories = story->owner().stories();
- const auto storyId = story->fullId();
- const auto peer = story->peer();
- _context = context;
- _waitingForId = {};
- _waitingForDelta = 0;
- rebuildFromContext(peer, storyId);
- _contextLifetime.destroy();
- const auto subscribeToSource = [&] {
- stories.sourceChanged() | rpl::filter(
- rpl::mappers::_1 == storyId.peer
- ) | rpl::start_with_next([=] {
- rebuildFromContext(peer, storyId);
- }, _contextLifetime);
- };
- v::match(_context.data, [&](Data::StoriesContextSingle) {
- }, [&](Data::StoriesContextPeer) {
- subscribeToSource();
- }, [&](Data::StoriesContextSaved) {
- stories.savedChanged() | rpl::filter(
- rpl::mappers::_1 == storyId.peer
- ) | rpl::start_with_next([=] {
- rebuildFromContext(peer, storyId);
- checkMoveByDelta();
- }, _contextLifetime);
- }, [&](Data::StoriesContextArchive) {
- stories.archiveChanged(
- ) | rpl::start_with_next([=] {
- rebuildFromContext(peer, storyId);
- checkMoveByDelta();
- }, _contextLifetime);
- }, [&](Data::StorySourcesList) {
- subscribeToSource();
- });
- const auto guard = gsl::finally([&] {
- _paused = false;
- _started = false;
- if (!story->document()) {
- _photoPlayback = std::make_unique<PhotoPlayback>(this);
- } else {
- _photoPlayback = nullptr;
- }
- });
- const auto unsupported = story->unsupported();
- if (!unsupported) {
- _unsupported = nullptr;
- } else {
- _unsupported = std::make_unique<Unsupported>(this, peer);
- _header->raise();
- _slider->raise();
- }
- captionClosed();
- _repostView = validateRepostView(story);
- _captionText = story->caption();
- _contentFaded = false;
- _contentFadeAnimation.stop();
- const auto document = story->document();
- _header->show({
- .peer = peer,
- .fromPeer = story->fromPeer(),
- .repostPeer = _repostView ? _repostView->fromPeer() : nullptr,
- .repostFrom = _repostView ? _repostView->fromName() : nullptr,
- .date = story->date(),
- .fullIndex = _sliderCount ? _index : 0,
- .fullCount = _sliderCount ? shownCount() : 0,
- .privacy = story->privacy(),
- .edited = story->edited(),
- .video = (document != nullptr),
- .silent = (document && document->isSilentVideo()),
- });
- uiShow()->hideLayer(anim::type::instant);
- if (!changeShown(story)) {
- return;
- }
- _replyArea->show({
- .peer = unsupported ? nullptr : peer.get(),
- .id = story->id(),
- }, _reactions->likedValue());
- const auto wasLikeButton = QPointer(_recentViews->likeButton());
- _recentViews->show({
- .list = story->recentViewers(),
- .reactions = story->reactions(),
- .forwards = story->forwards(),
- .views = story->views(),
- .total = story->interactions(),
- .type = RecentViewsTypeFor(peer),
- .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(),
- }, _reactions->likedValue());
- if (const auto nowLikeButton = _recentViews->likeButton()) {
- if (wasLikeButton != nowLikeButton) {
- _reactions->attachToReactionButton(nowLikeButton);
- }
- }
- if (peer->isSelf() || peer->isBroadcast() || peer->isServiceUser()) {
- _reactions->setReactionIconWidget(_recentViews->likeIconWidget());
- } else if (const auto like = _replyArea->likeAnimationTarget()) {
- _reactions->setReactionIconWidget(like);
- }
- _reactions->showLikeFrom(story);
- stories.loadAround(storyId, context);
- updatePlayingAllowed();
- peer->updateFull();
- }
- void Controller::jumpTo(
- not_null<Data::Story*> story,
- Data::StoriesContext context) {
- show(story, std::move(context));
- _delegate->storiesRedisplay(story);
- }
- bool Controller::changeShown(Data::Story *story) {
- const auto id = story ? story->fullId() : FullStoryId();
- const auto session = story ? &story->session() : nullptr;
- const auto sessionChanged = (_session != session);
- updateAreas(story);
- if (_shown == id && !sessionChanged) {
- return false;
- }
- if (_shown) {
- Assert(_session != nullptr);
- _session->data().stories().unregisterPolling(
- _shown,
- Data::Stories::Polling::Viewer);
- }
- if (sessionChanged) {
- _sessionLifetime.destroy();
- }
- _shown = id;
- _session = session;
- if (sessionChanged) {
- subscribeToSession();
- }
- if (story) {
- story->owner().stories().registerPolling(
- story,
- Data::Stories::Polling::Viewer);
- }
- _viewed = false;
- invalidate_weak_ptrs(&_viewsLoadGuard);
- _reactions->hide();
- _reactions->setReactionIconWidget(nullptr);
- if (_replyArea->focused()) {
- unfocusReply();
- }
- return true;
- }
- void Controller::subscribeToSession() {
- Expects(!_sessionLifetime);
- if (!_session) {
- return;
- }
- _session->changes().storyUpdates(
- Data::StoryUpdate::Flag::Destroyed
- ) | rpl::start_with_next([=](Data::StoryUpdate update) {
- if (update.story->fullId() == _shown) {
- _delegate->storiesClose();
- }
- }, _sessionLifetime);
- _session->data().stories().itemsChanged(
- ) | rpl::start_with_next([=](PeerId peerId) {
- if (_waitingForId.peer == peerId) {
- checkWaitingFor();
- }
- }, _sessionLifetime);
- _session->changes().storyUpdates(
- Data::StoryUpdate::Flag::Edited
- | Data::StoryUpdate::Flag::ViewsChanged
- | Data::StoryUpdate::Flag::Reaction
- ) | rpl::filter([=](const Data::StoryUpdate &update) {
- return (update.story == this->story());
- }) | rpl::start_with_next([=](const Data::StoryUpdate &update) {
- if (update.flags & Data::StoryUpdate::Flag::Edited) {
- show(update.story, _context);
- _delegate->storiesRedisplay(update.story);
- } else {
- const auto peer = update.story->peer();
- _recentViews->show({
- .list = update.story->recentViewers(),
- .reactions = update.story->reactions(),
- .forwards = update.story->forwards(),
- .views = update.story->views(),
- .total = update.story->interactions(),
- .type = RecentViewsTypeFor(peer),
- .canViewReactions = CanViewReactionsFor(peer) && !peer->isMegagroup(),
- });
- updateAreas(update.story);
- }
- }, _sessionLifetime);
- _sessionLifetime.add([=] {
- _session->data().stories().setPreloadingInViewer({});
- });
- }
- void Controller::updateAreas(Data::Story *story) {
- const auto &locations = story
- ? story->locations()
- : std::vector<Data::StoryLocation>();
- const auto &suggestedReactions = story
- ? story->suggestedReactions()
- : std::vector<Data::SuggestedReaction>();
- const auto &channelPosts = story
- ? story->channelPosts()
- : std::vector<Data::ChannelPost>();
- const auto &urlAreas = story
- ? story->urlAreas()
- : std::vector<Data::UrlArea>();
- const auto &weatherAreas = story
- ? story->weatherAreas()
- : std::vector<Data::WeatherArea>();
- if (_locations != locations) {
- _locations = locations;
- _areas.clear();
- }
- if (_channelPosts != channelPosts) {
- _channelPosts = channelPosts;
- _areas.clear();
- }
- if (_urlAreas != urlAreas) {
- _urlAreas = urlAreas;
- _areas.clear();
- }
- if (_weatherAreas != weatherAreas) {
- _weatherAreas = weatherAreas;
- _areas.clear();
- }
- const auto reactionsCount = int(suggestedReactions.size());
- if (_suggestedReactions.size() == reactionsCount && !_areas.empty()) {
- for (auto i = 0; i != reactionsCount; ++i) {
- const auto count = suggestedReactions[i].count;
- if (_suggestedReactions[i].count != count) {
- _suggestedReactions[i].count = count;
- const auto view = _areas[i + _locations.size()].view.get();
- view->updateReactionsCount(count);
- }
- if (_suggestedReactions[i] != suggestedReactions[i]) {
- _suggestedReactions = suggestedReactions;
- _areas.clear();
- break;
- }
- }
- } else if (_suggestedReactions != suggestedReactions) {
- _suggestedReactions = suggestedReactions;
- _areas.clear();
- }
- }
- PauseState Controller::pauseState() const {
- const auto inactive = !_windowActive
- || _replyActive
- || _layerShown
- || _menuShown;
- const auto playing = !inactive && !_paused;
- return playing
- ? PauseState::Playing
- : !inactive
- ? PauseState::Paused
- : _paused
- ? PauseState::InactivePaused
- : PauseState::Inactive;
- }
- float64 Controller::currentVolume() const {
- return Core::App().settings().videoVolume();
- }
- void Controller::toggleVolume() {
- _delegate->storiesVolumeToggle();
- }
- void Controller::changeVolume(float64 volume) {
- _delegate->storiesVolumeChanged(volume);
- }
- void Controller::volumeChangeFinished() {
- _delegate->storiesVolumeChangeFinished();
- }
- void Controller::updatePlayingAllowed() {
- if (!_shown) {
- return;
- }
- _header->updatePauseState();
- setPlayingAllowed(_started
- && _windowActive
- && !_paused
- && !_replyActive
- && (!_captionFullView || _captionFullView->closing())
- && !_layerShown
- && !_menuShown
- && !_tooltipShown);
- }
- void Controller::setPlayingAllowed(bool allowed) {
- if (_photoPlayback) {
- _photoPlayback->togglePaused(!allowed);
- } else {
- _delegate->storiesTogglePaused(!allowed);
- }
- }
- void Controller::showSiblings(not_null<Main::Session*> session) {
- showSibling(
- _siblingLeft,
- session,
- (_cachedSourceIndex > 0
- ? _cachedSourcesList[_cachedSourceIndex - 1]
- : CachedSource()));
- showSibling(
- _siblingRight,
- session,
- (_cachedSourceIndex + 1 < _cachedSourcesList.size()
- ? _cachedSourcesList[_cachedSourceIndex + 1]
- : CachedSource()));
- }
- void Controller::hideSiblings() {
- _siblingLeft = nullptr;
- _siblingRight = nullptr;
- }
- void Controller::showSibling(
- std::unique_ptr<Sibling> &sibling,
- not_null<Main::Session*> session,
- CachedSource cached) {
- if (!cached) {
- sibling = nullptr;
- return;
- }
- const auto source = session->data().stories().source(cached.peerId);
- if (!source) {
- sibling = nullptr;
- } else if (!sibling || !sibling->shows(*source, cached.shownId)) {
- sibling = std::make_unique<Sibling>(this, *source, cached.shownId);
- }
- }
- void Controller::ready() {
- if (_started) {
- return;
- }
- _started = true;
- updatePlayingAllowed();
- _reactions->ready();
- }
- void Controller::updateVideoPlayback(const Player::TrackState &state) {
- updatePlayback(state);
- }
- void Controller::updatePhotoPlayback(const Player::TrackState &state) {
- updatePlayback(state);
- }
- void Controller::updatePlayback(const Player::TrackState &state) {
- _slider->updatePlayback(state);
- updatePowerSaveBlocker(state);
- maybeMarkAsRead(state);
- if (Player::IsStoppedAtEnd(state.state)) {
- if (!subjumpFor(1)) {
- _delegate->storiesClose();
- }
- }
- }
- ClickHandlerPtr Controller::lookupAreaHandler(QPoint point) const {
- const auto &layout = _layout.current();
- if (!layout
- || (_locations.empty()
- && _suggestedReactions.empty()
- && _channelPosts.empty()
- && _urlAreas.empty()
- && _weatherAreas.empty())) {
- return nullptr;
- } else if (_areas.empty()) {
- const auto now = story();
- _areas.reserve(_locations.size()
- + _suggestedReactions.size()
- + _channelPosts.size()
- + _urlAreas.size());
- for (const auto &location : _locations) {
- _areas.push_back({
- .original = location.area.geometry,
- .rotation = location.area.rotation,
- .handler = std::make_shared<LocationClickHandler>(
- location.point),
- });
- }
- for (const auto &suggestedReaction : _suggestedReactions) {
- const auto id = suggestedReaction.reaction;
- auto widget = _reactions->makeSuggestedReactionWidget(
- suggestedReaction);
- const auto raw = widget.get();
- _areas.push_back({
- .original = suggestedReaction.area.geometry,
- .rotation = suggestedReaction.area.rotation,
- .handler = std::make_shared<LambdaClickHandler>([=] {
- raw->playEffect();
- if (const auto now = story()) {
- if (now->sentReactionId() != id) {
- now->owner().stories().sendReaction(
- now->fullId(),
- id);
- }
- }
- }),
- .view = std::move(widget),
- });
- }
- if (const auto session = now ? &now->session() : nullptr) {
- for (const auto &channelPost : _channelPosts) {
- _areas.push_back({
- .original = channelPost.area.geometry,
- .rotation = channelPost.area.rotation,
- .handler = MakeChannelPostHandler(
- session,
- channelPost.itemId),
- });
- }
- }
- const auto weak = base::make_weak(this);
- for (const auto &url : _urlAreas) {
- _areas.push_back({
- .original = url.area.geometry,
- .rotation = url.area.rotation,
- .handler = MakeUrlAreaHandler(weak, url.url),
- });
- }
- for (const auto &weather : _weatherAreas) {
- _areas.push_back({
- .original = weather.area.geometry,
- .radiusOriginal = weather.area.radius,
- .rotation = weather.area.rotation,
- .handler = std::make_shared<LambdaClickHandler>([=] {
- toggleWeatherMode();
- }),
- .view = _reactions->makeWeatherAreaWidget(
- weather,
- _weatherInCelsius.value()),
- });
- }
- rebuildActiveAreas(*layout);
- }
- for (const auto &area : _areas) {
- const auto center = area.geometry.center();
- const auto angle = -area.rotation;
- const auto contains = area.view
- ? area.view->contains(point)
- : area.geometry.contains(Rotated(point, center, angle));
- if (contains) {
- return area.handler;
- }
- }
- return nullptr;
- }
- void Controller::toggleWeatherMode() const {
- const auto now = !_weatherInCelsius.current();
- Core::App().settings().setWeatherInCelsius(now);
- Core::App().saveSettingsDelayed();
- _weatherInCelsius = now;
- }
- void Controller::maybeMarkAsRead(const Player::TrackState &state) {
- const auto length = state.length;
- const auto position = Player::IsStoppedAtEnd(state.state)
- ? state.length
- : Player::IsStoppedOrStopping(state.state)
- ? 0
- : state.position;
- if (position > state.frequency * kMarkAsReadAfterSeconds) {
- if (position > kMarkAsReadAfterProgress * length) {
- markAsRead();
- }
- }
- }
- void Controller::markAsRead() {
- Expects(shown());
- if (_viewed) {
- return;
- }
- _viewed = true;
- shownPeer()->owner().stories().markAsRead(_shown, _started);
- }
- bool Controller::subjumpAvailable(int delta) const {
- const auto index = _index + delta;
- if (index < 0) {
- return _siblingLeft && _siblingLeft->shownId().valid();
- } else if (index >= shownCount()) {
- return _siblingRight && _siblingRight->shownId().valid();
- }
- return index >= 0 && index < shownCount();
- }
- bool Controller::subjumpFor(int delta) {
- if (delta > 0) {
- markAsRead();
- }
- const auto index = _index + delta;
- if (index < 0) {
- if (_siblingLeft && _siblingLeft->shownId().valid()) {
- return jumpFor(-1);
- } else if (!shown() || !shownCount()) {
- return false;
- }
- subjumpTo(0);
- return true;
- } else if (index >= shownCount()) {
- return _siblingRight
- && _siblingRight->shownId().valid()
- && jumpFor(1);
- } else {
- subjumpTo(index);
- }
- return true;
- }
- void Controller::subjumpTo(int index) {
- Expects(shown());
- Expects(index >= 0 && index < shownCount());
- const auto peer = shownPeer();
- const auto id = FullStoryId{
- .peer = peer->id,
- .story = shownId(index),
- };
- auto &stories = peer->owner().stories();
- if (!id.story) {
- const auto delta = index - _index;
- if (_waitingForDelta != delta) {
- _waitingForDelta = delta;
- _waitingForId = {};
- loadMoreToList();
- }
- } else if (stories.lookup(id)) {
- _delegate->storiesJumpTo(&peer->session(), id, _context);
- } else if (_waitingForId != id) {
- _waitingForId = id;
- _waitingForDelta = 0;
- stories.loadAround(id, _context);
- }
- }
- void Controller::checkWaitingFor() {
- Expects(_waitingForId.valid());
- Expects(shown());
- const auto peer = shownPeer();
- auto &stories = peer->owner().stories();
- const auto maybe = stories.lookup(_waitingForId);
- if (!maybe) {
- if (maybe.error() == Data::NoStory::Deleted) {
- _waitingForId = {};
- }
- return;
- }
- _delegate->storiesJumpTo(
- &peer->session(),
- base::take(_waitingForId),
- _context);
- }
- bool Controller::jumpFor(int delta) {
- if (delta == -1) {
- if (const auto left = _siblingLeft.get()) {
- _delegate->storiesJumpTo(
- &left->peer()->session(),
- left->shownId(),
- _context);
- return true;
- }
- } else if (delta == 1) {
- if (shown() && _index + 1 >= shownCount()) {
- markAsRead();
- }
- if (const auto right = _siblingRight.get()) {
- _delegate->storiesJumpTo(
- &right->peer()->session(),
- right->shownId(),
- _context);
- return true;
- }
- }
- return false;
- }
- bool Controller::paused() const {
- return _paused;
- }
- void Controller::togglePaused(bool paused) {
- if (_paused != paused) {
- _paused = paused;
- updatePlayingAllowed();
- }
- }
- void Controller::contentPressed(bool pressed) {
- togglePaused(pressed);
- if (_captionFullView) {
- _captionFullView->close();
- }
- if (pressed) {
- _reactions->outsidePressed();
- }
- }
- void Controller::setMenuShown(bool shown) {
- if (_menuShown != shown) {
- _menuShown = shown;
- updatePlayingAllowed();
- }
- }
- void Controller::repaintSibling(not_null<Sibling*> sibling) {
- if (sibling == _siblingLeft.get() || sibling == _siblingRight.get()) {
- _delegate->storiesRepaint();
- }
- }
- void Controller::repaint() {
- if (_captionFullView) {
- _captionFullView->repaint();
- }
- _delegate->storiesRepaint();
- }
- SiblingView Controller::sibling(SiblingType type) const {
- const auto &pointer = (type == SiblingType::Left)
- ? _siblingLeft
- : _siblingRight;
- if (const auto value = pointer.get()) {
- const auto over = _delegate->storiesSiblingOver(type);
- const auto layout = (type == SiblingType::Left)
- ? _layout.current()->siblingLeft
- : _layout.current()->siblingRight;
- return value->view(layout, over);
- }
- return {};
- }
- const Data::StoryViews &Controller::views(int limit, bool initial) {
- invalidate_weak_ptrs(&_viewsLoadGuard);
- if (initial) {
- refreshViewsFromData();
- }
- if (_viewsSlice.total > _viewsSlice.list.size()
- && _viewsSlice.list.size() < limit) {
- const auto done = viewsGotMoreCallback();
- const auto peer = shownPeer();
- auto &stories = peer->owner().stories();
- if (peer->isChannel()) {
- stories.loadReactionsSlice(
- peer,
- _shown.story,
- _viewsSlice.nextOffset,
- done);
- } else {
- stories.loadViewsSlice(
- peer,
- _shown.story,
- _viewsSlice.nextOffset,
- done);
- }
- }
- return _viewsSlice;
- }
- rpl::producer<> Controller::moreViewsLoaded() const {
- return _moreViewsLoaded.events();
- }
- Fn<void(Data::StoryViews)> Controller::viewsGotMoreCallback() {
- return crl::guard(&_viewsLoadGuard, [=](Data::StoryViews result) {
- if (_viewsSlice.list.empty()) {
- const auto peer = shownPeer();
- auto &stories = peer->owner().stories();
- if (const auto maybeStory = stories.lookup(_shown)) {
- if (peer->isChannel()) {
- _viewsSlice = (*maybeStory)->channelReactionsList();
- } else {
- _viewsSlice = (*maybeStory)->viewsList();
- }
- } else {
- _viewsSlice = {};
- }
- } else {
- _viewsSlice.list.insert(
- end(_viewsSlice.list),
- begin(result.list),
- end(result.list));
- _viewsSlice.total = result.nextOffset.isEmpty()
- ? int(_viewsSlice.list.size())
- : std::max(result.total, int(_viewsSlice.list.size()));
- _viewsSlice.nextOffset = result.nextOffset;
- }
- _moreViewsLoaded.fire({});
- });
- }
- bool Controller::shown() const {
- return _source || _list;
- }
- PeerData *Controller::shownPeer() const {
- return _source
- ? _source->peer.get()
- : _list
- ? _list->peer.get()
- : nullptr;
- }
- int Controller::shownCount() const {
- return _source ? int(_source->ids.size()) : _list ? _list->total : 0;
- }
- StoryId Controller::shownId(int index) const {
- Expects(index >= 0 && index < shownCount());
- return _source
- ? (_source->ids.begin() + index)->id
- : (index < int(_list->sorted.size()))
- ? _list->sorted[index]
- : StoryId();
- }
- std::unique_ptr<RepostView> Controller::validateRepostView(
- not_null<Data::Story*> story) {
- return (story->repost() || !story->channelPosts().empty())
- ? std::make_unique<RepostView>(this, story)
- : nullptr;
- }
- void Controller::loadMoreToList() {
- Expects(shown());
- using namespace Data;
- const auto peer = shownPeer();
- const auto peerId = _shown.peer;
- auto &stories = peer->owner().stories();
- v::match(_context.data, [&](StoriesContextSaved) {
- stories.savedLoadMore(peerId);
- }, [&](StoriesContextArchive) {
- stories.archiveLoadMore(peerId);
- }, [](const auto &) {
- });
- }
- void Controller::rebuildCachedSourcesList(
- const std::vector<Data::StoriesSourceInfo> &lists,
- int index) {
- Expects(index >= 0 && index < lists.size());
- const auto currentPeerId = lists[index].id;
- // Remove removed.
- _cachedSourcesList.erase(ranges::remove_if(_cachedSourcesList, [&](
- CachedSource source) {
- return !ranges::contains(
- lists,
- source.peerId,
- &Data::StoriesSourceInfo::id);
- }), end(_cachedSourcesList));
- // Find current, full rebuild if can't find.
- const auto i = ranges::find(
- _cachedSourcesList,
- currentPeerId,
- &CachedSource::peerId);
- if (i == end(_cachedSourcesList)) {
- _cachedSourcesList.clear();
- } else {
- _cachedSourceIndex = int(i - begin(_cachedSourcesList));
- }
- if (_cachedSourcesList.empty()) {
- // Full rebuild.
- const auto predicate = [&](const Data::StoriesSourceInfo &info) {
- return !_showingUnreadSources
- || (info.unreadCount > 0)
- || (info.id == currentPeerId);
- };
- const auto mapper = [](const Data::StoriesSourceInfo &info) {
- return CachedSource{ info.id };
- };
- _cachedSourcesList = lists
- | ranges::views::filter(predicate)
- | ranges::views::transform(mapper)
- | ranges::to_vector;
- _cachedSourceIndex = ranges::find(
- _cachedSourcesList,
- currentPeerId,
- &CachedSource::peerId
- ) - begin(_cachedSourcesList);
- } else if (ranges::equal(
- lists,
- _cachedSourcesList,
- ranges::equal_to(),
- &Data::StoriesSourceInfo::id,
- &CachedSource::peerId)) {
- // No rebuild needed.
- } else {
- // All that go before the current push to front.
- for (auto before = index; before > 0;) {
- const auto &info = lists[--before];
- if (_showingUnreadSources && !info.unreadCount) {
- continue;
- } else if (!ranges::contains(
- _cachedSourcesList,
- info.id,
- &CachedSource::peerId)) {
- _cachedSourcesList.insert(
- begin(_cachedSourcesList),
- { info.id });
- ++_cachedSourceIndex;
- }
- }
- // All that go after the current push to back.
- for (auto after = index + 1, count = int(lists.size())
- ; after != count
- ; ++after) {
- const auto &info = lists[after];
- if (_showingUnreadSources && !info.unreadCount) {
- continue;
- } else if (!ranges::contains(
- _cachedSourcesList,
- info.id,
- &CachedSource::peerId)) {
- _cachedSourcesList.push_back({ info.id });
- }
- }
- }
- Ensures(_cachedSourcesList.size() <= lists.size());
- Ensures(_cachedSourceIndex >= 0
- && _cachedSourceIndex < _cachedSourcesList.size());
- }
- void Controller::refreshViewsFromData() {
- Expects(shown());
- const auto peer = shownPeer();
- auto &stories = peer->owner().stories();
- const auto maybeStory = stories.lookup(_shown);
- const auto check = peer->isSelf()
- || CanViewReactionsFor(peer);
- if (!maybeStory || !check) {
- _viewsSlice = {};
- } else if (peer->isChannel()) {
- _viewsSlice = (*maybeStory)->channelReactionsList();
- } else {
- _viewsSlice = (*maybeStory)->viewsList();
- }
- }
- void Controller::unfocusReply() {
- _wrap->setFocus();
- }
- void Controller::shareRequested() {
- const auto show = _delegate->storiesShow();
- if (auto box = PrepareShareBox(show, _shown, true)) {
- show->show(std::move(box));
- }
- }
- void Controller::deleteRequested() {
- const auto story = this->story();
- if (!story) {
- return;
- }
- const auto id = story->fullId();
- const auto weak = base::make_weak(this);
- const auto owner = &story->owner();
- const auto confirmed = [=](Fn<void()> close) {
- if (const auto strong = weak.get()) {
- if (const auto story = strong->story()) {
- if (story->fullId() == id) {
- moveFromShown();
- }
- }
- }
- owner->stories().deleteList({ id });
- close();
- };
- uiShow()->show(Ui::MakeConfirmBox({
- .text = tr::lng_stories_delete_one_sure(),
- .confirmed = confirmed,
- .confirmText = tr::lng_selected_delete(),
- .labelStyle = &st::storiesBoxLabel,
- }));
- }
- void Controller::reportRequested() {
- ReportRequested(uiShow(), _shown, &st::storiesReportBox);
- }
- void Controller::toggleInProfileRequested(bool inProfile) {
- const auto story = this->story();
- if (!story || !story->peer()->isSelf()) {
- return;
- }
- if (!inProfile && v::is<Data::StoriesContextSaved>(_context.data)) {
- moveFromShown();
- }
- story->owner().stories().toggleInProfileList(
- { story->fullId() },
- inProfile);
- const auto channel = story->peer()->isChannel();
- uiShow()->showToast(PrepareToggleInProfileToast(channel, 1, inProfile));
- }
- void Controller::moveFromShown() {
- if (!subjumpFor(1)) {
- [[maybe_unused]] const auto jumped = subjumpFor(-1);
- }
- }
- bool Controller::ignoreWindowMove(QPoint position) const {
- return _replyArea->ignoreWindowMove(position)
- || _header->ignoreWindowMove(position);
- }
- void Controller::tryProcessKeyInput(not_null<QKeyEvent*> e) {
- _replyArea->tryProcessKeyInput(e);
- }
- bool Controller::allowStealthMode() const {
- const auto story = this->story();
- return story
- && !story->peer()->isSelf()
- && story->peer()->session().premiumPossible();
- }
- void Controller::setupStealthMode() {
- SetupStealthMode(uiShow());
- }
- auto Controller::attachReactionsToMenu(
- not_null<Ui::PopupMenu*> menu,
- QPoint desiredPosition)
- -> AttachStripResult {
- return _reactions->attachToMenu(menu, desiredPosition);
- }
- rpl::lifetime &Controller::lifetime() {
- return _lifetime;
- }
- void Controller::updatePowerSaveBlocker(const Player::TrackState &state) {
- const auto block = !Player::IsPausedOrPausing(state.state)
- && !Player::IsStoppedOrStopping(state.state);
- base::UpdatePowerSaveBlocker(
- _powerSaveBlocker,
- block,
- base::PowerSaveBlockType::PreventDisplaySleep,
- [] { return u"Stories playback is active"_q; },
- [=] { return _wrap->window()->windowHandle(); });
- }
- Ui::Toast::Config PrepareToggleInProfileToast(
- bool channel,
- int count,
- bool inProfile) {
- return {
- .text = (inProfile
- ? (count == 1
- ? (channel
- ? tr::lng_stories_channel_save_done
- : tr::lng_stories_save_done)(
- tr::now,
- Ui::Text::Bold)
- : (channel
- ? tr::lng_stories_channel_save_done_many
- : tr::lng_stories_save_done_many)(
- tr::now,
- lt_count,
- count,
- Ui::Text::Bold)).append(
- '\n').append((channel
- ? tr::lng_stories_channel_save_done_about
- : tr::lng_stories_save_done_about)(tr::now))
- : (count == 1
- ? (channel
- ? tr::lng_stories_channel_archive_done
- : tr::lng_stories_archive_done)(
- tr::now,
- Ui::Text::WithEntities)
- : (channel
- ? tr::lng_stories_channel_archive_done_many
- : tr::lng_stories_archive_done_many)(
- tr::now,
- lt_count,
- count,
- Ui::Text::WithEntities))),
- .st = &st::storiesActionToast,
- .duration = (inProfile
- ? Data::Stories::kInProfileToastDuration
- : Ui::Toast::kDefaultDuration),
- };
- }
- Ui::Toast::Config PrepareTogglePinToast(
- bool channel,
- int count,
- bool pin) {
- return {
- .title = (pin
- ? (count == 1
- ? tr::lng_mediaview_pin_story_done(tr::now)
- : tr::lng_mediaview_pin_stories_done(
- tr::now,
- lt_count,
- count))
- : QString()),
- .text = { (pin
- ? (count == 1
- ? tr::lng_mediaview_pin_story_about(tr::now)
- : tr::lng_mediaview_pin_stories_about(
- tr::now,
- lt_count,
- count))
- : (count == 1
- ? tr::lng_mediaview_unpin_story_done(tr::now)
- : tr::lng_mediaview_unpin_stories_done(
- tr::now,
- lt_count,
- count))) },
- .st = &st::storiesActionToast,
- .duration = (pin
- ? Data::Stories::kInProfileToastDuration
- : Ui::Toast::kDefaultDuration),
- };
- }
- void ReportRequested(
- std::shared_ptr<Main::SessionShow> show,
- FullStoryId id,
- const style::ReportBox *stOverride) {
- if (const auto maybeStory = show->session().data().stories().lookup(id)) {
- const auto story = *maybeStory;
- const auto st = stOverride ? stOverride : &st::defaultReportBox;
- // show->hideLayer();
- ShowReportMessageBox(show, story->peer(), {}, { story->id() }, st);
- }
- }
- object_ptr<Ui::BoxContent> PrepareShortInfoBox(not_null<PeerData*> peer) {
- const auto open = [=] {
- if (const auto window = Core::App().windowFor(peer)) {
- window->invokeForSessionController(
- &peer->session().account(),
- peer,
- [&](not_null<Window::SessionController*> controller) {
- Core::App().hideMediaView();
- controller->showPeerHistory(peer);
- });
- }
- };
- return ::PrepareShortInfoBox(
- peer,
- open,
- [] { return false; },
- nullptr,
- &st::storiesShortInfoBox);
- }
- ClickHandlerPtr MakeChannelPostHandler(
- not_null<Main::Session*> session,
- FullMsgId item) {
- return std::make_shared<LambdaClickHandler>(crl::guard(session, [=] {
- const auto peer = session->data().peer(item.peer);
- if (const auto controller = session->tryResolveWindow(peer)) {
- Core::App().hideMediaView();
- controller->showPeerHistory(
- peer,
- Window::SectionShow::Way::ClearStack,
- item.msg);
- }
- }));
- }
- ClickHandlerPtr MakeUrlAreaHandler(
- base::weak_ptr<Controller> weak,
- const QString &url) {
- class Handler final : public HiddenUrlClickHandler {
- public:
- Handler(const QString &url, base::weak_ptr<Controller> weak)
- : HiddenUrlClickHandler(url), _weak(weak) {
- }
- void onClick(ClickContext context) const override {
- const auto raw = url();
- const auto strong = _weak.get();
- const auto prefix = u"tg://nft?slug="_q;
- if (raw.startsWith(prefix) && strong) {
- const auto slug = raw.mid(
- prefix.size()
- ).split('&').front().split('#').front();
- Core::ResolveAndShowUniqueGift(
- strong->uiShow(),
- slug,
- ::Settings::DarkCreditsEntryBoxStyle());
- } else {
- HiddenUrlClickHandler::onClick(context);
- }
- }
- private:
- base::weak_ptr<Controller> _weak;
- };
- return std::make_shared<Handler>(url, weak);
- }
- } // namespace Media::Stories
|