diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 991101b61..4b8b8b331 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -548,6 +548,8 @@ PRIVATE data/data_sparse_ids.h data/data_sponsored_messages.cpp data/data_sponsored_messages.h + data/data_stories.cpp + data/data_stories.h data/data_streaming.cpp data/data_streaming.h data/data_thread.cpp @@ -936,8 +938,6 @@ PRIVATE main/session/send_as_peers.h main/session/session_show.cpp main/session/session_show.h - media/system_media_controls_manager.h - media/system_media_controls_manager.cpp media/audio/media_audio.cpp media/audio/media_audio.h media/audio/media_audio_capture.cpp @@ -962,6 +962,16 @@ PRIVATE media/player/media_player_volume_controller.h media/player/media_player_widget.cpp media/player/media_player_widget.h + media/stories/media_stories_delegate.cpp + media/stories/media_stories_delegate.h + media/stories/media_stories_header.cpp + media/stories/media_stories_header.h + media/stories/media_stories_reply.cpp + media/stories/media_stories_reply.h + media/stories/media_stories_slider.cpp + media/stories/media_stories_slider.h + media/stories/media_stories_view.cpp + media/stories/media_stories_view.h media/streaming/media_streaming_audio_track.cpp media/streaming/media_streaming_audio_track.h media/streaming/media_streaming_common.h @@ -1007,6 +1017,8 @@ PRIVATE media/view/media_view_playback_progress.cpp media/view/media_view_playback_progress.h media/view/media_view_open_common.h + media/system_media_controls_manager.h + media/system_media_controls_manager.cpp menu/menu_antispam_validator.cpp menu/menu_antispam_validator.h menu/menu_item_download_files.cpp diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 4a8ce5173..62bf94ba7 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum.h" #include "data/data_scheduled_messages.h" #include "data/data_send_action.h" +#include "data/data_stories.h" #include "data/data_message_reactions.h" #include "inline_bots/bot_attach_web_view.h" #include "chat_helpers/emoji_interactions.h" @@ -2517,7 +2518,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateTranscribedAudio: { const auto &data = update.c_updateTranscribedAudio(); _session->api().transcribes().apply(data); - } + } break; + + case mtpc_updateStories: { + _session->data().stories().apply(update.c_updateStories()); + } break; } } diff --git a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h index ba2f391df..a1ad76e6c 100644 --- a/Telegram/SourceFiles/chat_helpers/compose/compose_show.h +++ b/Telegram/SourceFiles/chat_helpers/compose/compose_show.h @@ -59,7 +59,7 @@ public: Data::FileOrigin origin, not_null photo) const = 0; - virtual void processChosenSticker(FileChosen chosen) const = 0; + virtual void processChosenSticker(FileChosen &&chosen) const = 0; [[nodiscard]] virtual Window::SessionController *resolveWindow( WindowUsage) const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index bb1fc659f..42a8644d1 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_emoji_statuses.h" #include "data/data_forum_icons.h" #include "data/data_cloud_themes.h" +#include "data/data_stories.h" #include "data/data_streaming.h" #include "data/data_media_rotation.h" #include "data/data_histories.h" @@ -266,7 +267,8 @@ Session::Session(not_null session) , _emojiStatuses(std::make_unique(this)) , _forumIcons(std::make_unique(this)) , _notifySettings(std::make_unique(this)) -, _customEmojiManager(std::make_unique(this)) { +, _customEmojiManager(std::make_unique(this)) +, _stories(std::make_unique(this)) { _cache->open(_session->local().cacheKey()); _bigFileCache->open(_session->local().cacheBigFileKey()); @@ -311,6 +313,8 @@ Session::Session(not_null session) } } }, _lifetime); + + _stories->loadMore(); }); } diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 16c56c23e..154b1b73f 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -63,6 +63,7 @@ class Stickers; class GroupCall; class NotifySettings; class CustomEmojiManager; +class Stories; struct RepliesReadTillUpdate { FullMsgId id; @@ -136,6 +137,9 @@ public: [[nodiscard]] CustomEmojiManager &customEmojiManager() const { return *_customEmojiManager; } + [[nodiscard]] Stories &stories() const { + return *_stories; + } [[nodiscard]] MsgId nextNonHistoryEntryId() { return ++_nonHistoryEntryId; @@ -1007,6 +1011,7 @@ private: const std::unique_ptr _forumIcons; const std::unique_ptr _notifySettings; const std::unique_ptr _customEmojiManager; + const std::unique_ptr _stories; MsgId _nonHistoryEntryId = ServerMaxMsgId.bare + ScheduledMsgIdsRange; diff --git a/Telegram/SourceFiles/data/data_stories.cpp b/Telegram/SourceFiles/data/data_stories.cpp new file mode 100644 index 000000000..0a0fe8b02 --- /dev/null +++ b/Telegram/SourceFiles/data/data_stories.cpp @@ -0,0 +1,246 @@ +/* +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 "data/data_stories.h" + +#include "api/api_text_entities.h" +#include "data/data_document.h" +#include "data/data_photo.h" +#include "data/data_session.h" +#include "main/main_session.h" +#include "apiwrap.h" + +// #TODO stories testing +#include "data/data_user.h" +#include "history/history.h" +#include "history/history_item.h" +#include "storage/storage_shared_media.h" + +namespace Data { +namespace { + +} // namespace + +bool StoriesList::unread() const { + return !items.empty() && readTill < items.front().id; +} + +Stories::Stories(not_null owner) : _owner(owner) { +} + +Stories::~Stories() { +} + +Session &Stories::owner() const { + return *_owner; +} + +void Stories::apply(const MTPDupdateStories &data) { + pushToFront(parse(data.vstories())); +} + +StoriesList Stories::parse(const MTPUserStories &stories) { + const auto &data = stories.data(); + const auto userId = UserId(data.vuser_id()); + const auto readTill = data.vmax_read_id().value_or_empty(); + const auto count = int(data.vstories().v.size()); + auto result = StoriesList{ + .user = _owner->user(userId), + .readTill = readTill, + .total = count, + }; + const auto &list = data.vstories().v; + result.items.reserve(list.size()); + for (const auto &story : list) { + story.match([&](const MTPDstoryItem &data) { + if (auto entry = parse(data)) { + result.items.push_back(std::move(*entry)); + } else { + --result.total; + } + }, [&](const MTPDstoryItemSkipped &) { + }, [&](const MTPDstoryItemDeleted &) { + --result.total; + }); + } + result.total = std::min(result.total, int(result.items.size())); + return result; +} + +std::optional Stories::parse(const MTPDstoryItem &data) { + const auto id = data.vid().v; + using MaybeMedia = std::optional< + std::variant, not_null>>; + const auto media = data.vmedia().match([&]( + const MTPDmessageMediaPhoto &data) -> MaybeMedia { + if (const auto photo = data.vphoto()) { + const auto result = _owner->processPhoto(*photo); + if (!result->isNull()) { + return result; + } + } + return {}; + }, [&](const MTPDmessageMediaDocument &data) -> MaybeMedia { + if (const auto document = data.vdocument()) { + const auto result = _owner->processDocument(*document); + if (!result->isNull() + && (result->isGifv() || result->isVideoFile())) { + return result; + } + } + return {}; + }, [](const auto &) { return MaybeMedia(); }); + if (!media) { + return {}; + } + auto caption = TextWithEntities{ + data.vcaption().value_or_empty(), + Api::EntitiesFromMTP( + &_owner->session(), + data.ventities().value_or_empty()), + }; + auto privacy = StoryPrivacy(); + + const auto date = data.vdate().v; + return StoryItem{ + .id = data.vid().v, + .media = *media, + .caption = std::move(caption), + .date = date, + .privacy = privacy, + }; +} + +void Stories::loadMore() { + if (_loadMoreRequestId || _allLoaded) { + return; + } + const auto api = &_owner->session().api(); + using Flag = MTPstories_GetAllStories::Flag; + _loadMoreRequestId = api->request(MTPstories_GetAllStories( + MTP_flags(_state.isEmpty() ? Flag(0) : Flag::f_next), + MTP_string(_state) + )).done([=](const MTPstories_AllStories &result) { + _loadMoreRequestId = 0; + + result.match([&](const MTPDstories_allStories &data) { + _owner->processUsers(data.vusers()); + _state = qs(data.vstate()); + _allLoaded = !data.is_has_more(); + for (const auto &single : data.vuser_stories().v) { + pushToBack(parse(single)); + } + }, [](const MTPDstories_allStoriesNotModified &) { + }); + }).fail([=] { + _loadMoreRequestId = 0; + }).send(); +} + +const std::vector &Stories::all() { + return _all; +} + +bool Stories::allLoaded() const { + return _allLoaded; +} + +// #TODO stories testing +StoryId Stories::generate( + not_null item, + std::variant< + v::null_t, + not_null, + not_null> media) { + if (v::is_null(media) + || !item->from()->isUser() + || !item->isRegular()) { + return {}; + } + const auto document = v::is>(media) + ? v::get>(media).get() + : nullptr; + if (document && !document->isVideoFile()) { + return {}; + } + using namespace Storage; + auto resultId = StoryId(); + const auto listType = SharedMediaType::PhotoVideo; + const auto itemId = item->id; + const auto peer = item->history()->peer; + const auto session = &peer->session(); + auto stories = StoriesList{ .user = item->from()->asUser() }; + const auto lifetime = session->storage().query(SharedMediaQuery( + SharedMediaKey(peer->id, MsgId(0), listType, itemId), + 32, + 32 + )) | rpl::start_with_next([&](SharedMediaResult &&result) { + stories.total = result.count.value_or(1); + if (!result.messageIds.contains(itemId)) { + result.messageIds.emplace(itemId); + } + stories.items.reserve(result.messageIds.size()); + auto index = StoryId(); + const auto owner = &peer->owner(); + for (const auto id : result.messageIds) { + if (const auto item = owner->message(peer, id)) { + if (id == itemId) { + resultId = ++index; + stories.items.push_back({ + .id = resultId, + .media = (document + ? StoryMedia{ not_null(document) } + : StoryMedia{ v::get>(media) }), + .caption = item->originalText(), + .date = item->date(), + }); + } else if (const auto media = item->media()) { + const auto photo = media->photo(); + const auto document = media->document(); + if (photo || (document && document->isVideoFile())) { + stories.items.push_back({ + .id = ++index, + .media = (document + ? StoryMedia{ not_null(document) } + : StoryMedia{ not_null(photo) }), + .caption = item->originalText(), + .date = item->date(), + }); + } + } + } + } + const auto i = ranges::find(_all, stories.user, &StoriesList::user); + if (i != end(_all)) { + *i = std::move(stories); + } else { + _all.push_back(std::move(stories)); + } + }); + return resultId; +} + +void Stories::pushToBack(StoriesList &&list) { + const auto i = ranges::find(_all, list.user, &StoriesList::user); + if (i != end(_all)) { + *i = std::move(list); + } else { + _all.push_back(std::move(list)); + } +} + +void Stories::pushToFront(StoriesList &&list) { + const auto i = ranges::find(_all, list.user, &StoriesList::user); + if (i != end(_all)) { + *i = std::move(list); + ranges::rotate(begin(_all), i, i + 1); + } else { + _all.insert(begin(_all), std::move(list)); + } +} + +} // namespace Data \ No newline at end of file diff --git a/Telegram/SourceFiles/data/data_stories.h b/Telegram/SourceFiles/data/data_stories.h new file mode 100644 index 000000000..ea3a033f1 --- /dev/null +++ b/Telegram/SourceFiles/data/data_stories.h @@ -0,0 +1,74 @@ +/* +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 +*/ +#pragma once + +class PhotoData; +class DocumentData; + +namespace Data { + +class Session; + +struct StoryPrivacy { +}; + +struct StoryMedia { + std::variant, not_null> data; +}; + +struct StoryItem { + StoryId id = 0; + StoryMedia media; + TextWithEntities caption; + TimeId date = 0; + StoryPrivacy privacy; +}; + +struct StoriesList { + not_null user; + std::vector items; + int total = 0; +}; + +class Stories final { +public: + explicit Stories(not_null owner); + ~Stories(); + + void loadMore(); + void apply(const MTPDupdateStories &data); + + [[nodiscard]] const std::vector &all(); + [[nodiscard]] bool allLoaded() const; + + // #TODO stories testing + [[nodiscard]] StoryId generate( + not_null item, + std::variant< + v::null_t, + not_null, + not_null> media); + +private: + [[nodiscard]] StoriesList parse(const MTPUserStories &data); + [[nodiscard]] std::optional parse(const MTPDstoryItem &data); + + void pushToBack(StoriesList &&list); + void pushToFront(StoriesList &&list); + + const not_null _owner; + + std::vector _all; + QString _state; + bool _allLoaded = false; + + mtpRequestId _loadMoreRequestId = 0; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index e07df91e3..8f6ee551b 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -135,6 +135,8 @@ using PollId = uint64; using WallPaperId = uint64; using CallId = uint64; using BotAppId = uint64; +using StoryId = int32; + constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL); struct PreparedPhotoThumb { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 4146e8eb8..75a46de3e 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -2538,11 +2538,15 @@ bool ComposeControls::returnTabbedSelector() { } void ComposeControls::createTabbedPanel() { - auto descriptor = ChatHelpers::TabbedPanelDescriptor{ + using namespace ChatHelpers; + auto descriptor = TabbedPanelDescriptor{ .regularWindow = _regularWindow, - .nonOwnedSelector = _selector, + .ownedSelector = (_ownedSelector + ? object_ptr::fromRaw(_ownedSelector.release()) + : object_ptr(nullptr)), + .nonOwnedSelector = _ownedSelector ? nullptr : _selector.get(), }; - setTabbedPanel(std::make_unique( + setTabbedPanel(std::make_unique( _parent, std::move(descriptor))); } diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index c09b938d6..a24b5940a 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -315,7 +315,7 @@ private: const not_null _session; Window::SessionController * const _regularWindow = nullptr; - const std::unique_ptr _ownedSelector; + std::unique_ptr _ownedSelector; const not_null _selector; rpl::event_stream _stickerOrEmojiChosen; diff --git a/Telegram/SourceFiles/media/stories/media_stories_delegate.cpp b/Telegram/SourceFiles/media/stories/media_stories_delegate.cpp new file mode 100644 index 000000000..721d9803c --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_delegate.cpp @@ -0,0 +1,9 @@ +/* +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_delegate.h" + diff --git a/Telegram/SourceFiles/media/stories/media_stories_delegate.h b/Telegram/SourceFiles/media/stories/media_stories_delegate.h new file mode 100644 index 000000000..fd00b7cf9 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_delegate.h @@ -0,0 +1,30 @@ +/* +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 +*/ +#pragma once + +namespace ChatHelpers { +class Show; +struct FileChosen; +} // namespace ChatHelpers + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Media::Stories { + +class Delegate { +public: + [[nodiscard]] virtual not_null storiesWrap() = 0; + [[nodiscard]] virtual auto storiesShow() + -> std::shared_ptr = 0; + [[nodiscard]] virtual auto storiesStickerOrEmojiChosen() + -> rpl::producer = 0; +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.cpp b/Telegram/SourceFiles/media/stories/media_stories_header.cpp new file mode 100644 index 000000000..08e81fccc --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_header.cpp @@ -0,0 +1,65 @@ +/* +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_header.h" + +#include "base/unixtime.h" +#include "data/data_user.h" +#include "media/stories/media_stories_delegate.h" +#include "ui/controls/userpic_button.h" +#include "ui/text/format_values.h" +#include "ui/widgets/labels.h" +#include "ui/painter.h" +#include "ui/rp_widget.h" +#include "styles/style_boxes.h" // defaultUserpicButton. + +namespace Media::Stories { + +Header::Header(not_null delegate) +: _delegate(delegate) { +} + +Header::~Header() { +} + +void Header::show(HeaderData data) { + if (_data == data) { + return; + } + const auto userChanged = (!_data || _data->user != data.user); + _data = data; + if (userChanged) { + _date = nullptr; + const auto parent = _delegate->storiesWrap(); + auto widget = std::make_unique(parent); + const auto raw = widget.get(); + parent->sizeValue() | rpl::start_with_next([=](QSize size) { + raw->setGeometry(50, 50, 600, 100); + }, raw->lifetime()); + raw->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto userpic = Ui::CreateChild( + raw, + data.user, + st::defaultUserpicButton); + userpic->move(0, 0); + const auto name = Ui::CreateChild( + raw, + data.user->firstName, + st::defaultFlatLabel); + name->move(100, 0); + raw->show(); + _widget = std::move(widget); + } + _date = std::make_unique( + _widget.get(), + Ui::FormatDateTime(base::unixtime::parse(data.date)), + st::defaultFlatLabel); + _date->move(100, 50); + _date->show(); +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_header.h b/Telegram/SourceFiles/media/stories/media_stories_header.h new file mode 100644 index 000000000..8df09a64f --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_header.h @@ -0,0 +1,43 @@ +/* +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 +*/ +#pragma once + +#include "ui/userpic_view.h" + +namespace Ui { +class RpWidget; +class FlatLabel; +} // namespace Ui + +namespace Media::Stories { + +class Delegate; + +struct HeaderData { + not_null user; + TimeId date = 0; + + friend inline auto operator<=>(HeaderData, HeaderData) = default; +}; + +class Header final { +public: + explicit Header(not_null delegate); + ~Header(); + + void show(HeaderData data); + +private: + const not_null _delegate; + std::unique_ptr _widget; + std::unique_ptr _date; + std::optional _data; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp new file mode 100644 index 000000000..cf2226bd1 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp @@ -0,0 +1,50 @@ +/* +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_reply.h" + +#include "chat_helpers/compose/compose_show.h" +#include "chat_helpers/tabbed_selector.h" +#include "history/view/controls/history_view_compose_controls.h" +#include "media/stories/media_stories_delegate.h" +#include "menu/menu_send.h" + +namespace Media::Stories { + +ReplyArea::ReplyArea(not_null delegate) +: _delegate(delegate) +, _controls(std::make_unique( + _delegate->storiesWrap(), + HistoryView::ComposeControlsDescriptor{ + .show = _delegate->storiesShow(), + .unavailableEmojiPasted = [=](not_null emoji) { + showPremiumToast(emoji); + }, + .mode = HistoryView::ComposeControlsMode::Normal, + .sendMenuType = SendMenu::Type::SilentOnly, + .stickerOrEmojiChosen = _delegate->storiesStickerOrEmojiChosen(), + } +)) { + _delegate->storiesWrap()->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _controls->resizeToWidth(size.width() - 200); + _controls->move(100, size.height() - _controls->heightCurrent() - 20); + _controls->setAutocompleteBoundingRect({ QPoint() ,size }); + }, _lifetime); + + _controls->show(); + _controls->showFinished(); +} + +ReplyArea::~ReplyArea() { +} + +void ReplyArea::showPremiumToast(not_null emoji) { + // #TODO stories +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.h b/Telegram/SourceFiles/media/stories/media_stories_reply.h new file mode 100644 index 000000000..4d13cff79 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_reply.h @@ -0,0 +1,39 @@ +/* +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 +*/ +#pragma once + +namespace HistoryView { +class ComposeControls; +} // namespace HistoryView + +namespace Media::Stories { + +class Delegate; + +struct ReplyAreaData { + not_null user; + + friend inline auto operator<=>(ReplyAreaData, ReplyAreaData) = default; +}; + +class ReplyArea final { +public: + explicit ReplyArea(not_null delegate); + ~ReplyArea(); + +private: + void showPremiumToast(not_null emoji); + + const not_null _delegate; + const std::unique_ptr _controls; + + rpl::lifetime _lifetime; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_slider.cpp b/Telegram/SourceFiles/media/stories/media_stories_slider.cpp new file mode 100644 index 000000000..cc64c952e --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_slider.cpp @@ -0,0 +1,18 @@ +/* +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_slider.h" + +namespace Media::Stories { + +Slider::Slider() { +} + +Slider::~Slider() { +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_slider.h b/Telegram/SourceFiles/media/stories/media_stories_slider.h new file mode 100644 index 000000000..bb908a9e2 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_slider.h @@ -0,0 +1,20 @@ +/* +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 +*/ +#pragma once + +namespace Media::Stories { + +class Slider final { +public: + Slider(); + ~Slider(); + + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp new file mode 100644 index 000000000..131a4d8d2 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -0,0 +1,37 @@ +/* +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_view.h" + +#include "media/stories/media_stories_delegate.h" +#include "media/stories/media_stories_header.h" +#include "media/stories/media_stories_slider.h" +#include "media/stories/media_stories_reply.h" + +namespace Media::Stories { + +View::View(not_null delegate) +: _delegate(delegate) +, _wrap(_delegate->storiesWrap()) +, _header(std::make_unique
(_delegate)) +, _slider(std::make_unique()) +, _replyArea(std::make_unique(_delegate)) { +} + +View::~View() = default; + +void View::show(const Data::StoriesList &list, int index) { + Expects(index < list.items.size()); + + const auto &item = list.items[index]; + _header->show({ + .user = list.user, + .date = item.date, + }); +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h new file mode 100644 index 000000000..884d44219 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -0,0 +1,40 @@ +/* +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 +*/ +#pragma once + +#include "data/data_stories.h" + +namespace Ui { +class RpWidget; +} // namespace Ui + +namespace Media::Stories { + +class Header; +class Slider; +class ReplyArea; +class Delegate; + +class View final { +public: + explicit View(not_null delegate); + ~View(); + + void show(const Data::StoriesList &list, int index); + +private: + const not_null _delegate; + const not_null _wrap; + + std::unique_ptr
_header; + std::unique_ptr _slider; + std::unique_ptr _replyArea; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index f19623e48..cfedf5ef8 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/popup_menu.h" #include "ui/widgets/buttons.h" #include "ui/image/image.h" +#include "ui/layers/layer_manager.h" #include "ui/text/text_utilities.h" #include "ui/platform/ui_platform_utility.h" #include "ui/platform/ui_platform_window_title.h" @@ -45,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/view/media_view_pip.h" #include "media/view/media_view_overlay_raster.h" #include "media/view/media_view_overlay_opengl.h" +#include "media/stories/media_stories_view.h" #include "media/streaming/media_streaming_instance.h" #include "media/streaming/media_streaming_player.h" #include "media/player/media_player_instance.h" @@ -54,6 +56,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_media.h" #include "data/data_media_types.h" #include "data/data_session.h" +#include "data/data_stories.h" #include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_chat.h" @@ -331,6 +334,7 @@ OverlayWidget::OverlayWidget() , _widget(_surface->rpWidget()) , _fullscreen(Core::App().settings().mediaViewPosition().maximized == 2) , _windowed(Core::App().settings().mediaViewPosition().maximized == 0) +, _layerBg(std::make_unique(_body)) , _docDownload(_body, tr::lng_media_download(tr::now), st::mediaviewFileLink) , _docSaveAs(_body, tr::lng_mediaview_save_as(tr::now), st::mediaviewFileLink) , _docCancel(_body, tr::lng_cancel(tr::now), st::mediaviewFileLink) @@ -2870,6 +2874,14 @@ void OverlayWidget::show(OpenRequest request) { } setSession(&photo->session()); + // #TODO stories testing + if (const auto storyId = (!contextPeer && contextItem) + ? contextItem->history()->owner().stories().generate( + contextItem, + photo) + : StoryId()) { + setContext(StoriesContext{ contextItem->from()->asUser(), storyId }); + } else if (contextPeer) { setContext(contextPeer); } else if (contextItem) { @@ -2888,6 +2900,14 @@ void OverlayWidget::show(OpenRequest request) { } else if (document) { setSession(&document->session()); + // #TODO stories testing + if (const auto storyId = contextItem + ? contextItem->history()->owner().stories().generate( + contextItem, + document) + : StoryId()) { + setContext(StoriesContext{ contextItem->from()->asUser(), storyId }); + } else if (contextItem) { setContext(ItemContext{ contextItem, contextTopicRootId }); } else { @@ -3808,6 +3828,91 @@ void OverlayWidget::switchToPip() { } } +not_null OverlayWidget::storiesWrap() { + return _body; +} + +std::shared_ptr OverlayWidget::storiesShow() { + class Show final : public ChatHelpers::Show { + public: + explicit Show(not_null widget) : _widget(widget) { + } + + void showBox( + object_ptr content, + Ui::LayerOptions options + = Ui::LayerOption::KeepOther) const override { + _widget->_layerBg->showBox( + std::move(content), + options, + anim::type::normal); + } + void hideLayer() const override { + _widget->_layerBg->hideAll(anim::type::normal); + } + not_null toastParent() const override { + return _widget->_body; + } + bool valid() const override { + return _widget->_storiesUser != nullptr; + } + operator bool() const override { + return valid(); + } + + Main::Session &session() const override { + return _widget->_storiesUser->session(); + } + bool paused(ChatHelpers::PauseReason reason) const override { + if (_widget->isHidden() + || (!_widget->_fullscreen + && !_widget->_window->isActiveWindow())) { + return true; + } else if (reason < ChatHelpers::PauseReason::Layer + && _widget->_layerBg->topShownLayer() != nullptr) { + return true; + } + return false; + } + rpl::producer<> pauseChanged() const override { + return rpl::never<>(); + } + + rpl::producer adjustShadowLeft() const override { + return rpl::single(false); + } + SendMenu::Type sendMenuType() const override { + return SendMenu::Type::SilentOnly; + } + + bool showMediaPreview( + Data::FileOrigin origin, + not_null document) const override { + return false; // #TODO stories + } + bool showMediaPreview( + Data::FileOrigin origin, + not_null photo) const override { + return false; // #TODO stories + } + + void processChosenSticker( + ChatHelpers::FileChosen &&chosen) const override { + _widget->_storiesStickerOrEmojiChosen.fire(std::move(chosen)); + } + + private: + not_null _widget; + + }; + return std::make_shared(this); +} + +auto OverlayWidget::storiesStickerOrEmojiChosen() +-> rpl::producer { + return _storiesStickerOrEmojiChosen.events(); +} + void OverlayWidget::playbackToggleFullScreen() { Expects(_streamed != nullptr); @@ -4619,22 +4724,49 @@ void OverlayWidget::setContext( std::variant< v::null_t, ItemContext, - not_null> context) { + not_null, + StoriesContext> context) { if (const auto item = std::get_if(&context)) { _message = item->item; _history = _message->history(); _peer = _history->peer; _topicRootId = _peer->isForum() ? item->topicRootId : MsgId(); + _stories = nullptr; + _storiesUser = nullptr; } else if (const auto peer = std::get_if>(&context)) { _peer = *peer; _history = _peer->owner().history(_peer); _message = nullptr; _topicRootId = MsgId(); + _stories = nullptr; + _storiesUser = nullptr; + } else if (const auto story = std::get_if(&context)) { + _message = nullptr; + _topicRootId = MsgId(); + _history = nullptr; + _peer = nullptr; + const auto &all = story->user->owner().stories().all(); + const auto i = ranges::find( + all, + story->user, + &Data::StoriesList::user); + Assert(i != end(all)); + const auto j = ranges::find( + i->items, + story->id, + &Data::StoryItem::id); + _storiesUser = story->user; + if (!_stories) { + _stories = std::make_unique( + static_cast(this)); + } + _stories->show(*i, j - begin(i->items)); } else { _message = nullptr; _topicRootId = MsgId(); _history = nullptr; _peer = nullptr; + _stories = nullptr; } _migrated = nullptr; if (_history) { @@ -4704,6 +4836,14 @@ bool OverlayWidget::moveToEntity(const Entity &entity, int preloadDelta) { if (v::is_null(entity.data) && !entity.item) { return false; } + // #TODO stories testing + if (const auto storyId = entity.item + ? entity.item->history()->owner().stories().generate( + entity.item, + entity.data) + : StoryId()) { + setContext(StoriesContext{ entity.item->from()->asUser(), storyId }); + } else if (const auto item = entity.item) { setContext(ItemContext{ item, entity.topicRootId }); } else if (_peer) { diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index b47a8de03..4d6d6d3f2 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_cloud_themes.h" // Data::CloudTheme. #include "media/view/media_view_playback_controls.h" #include "media/view/media_view_open_common.h" +#include "media/stories/media_stories_delegate.h" class History; @@ -32,6 +33,7 @@ class PopupMenu; class LinkButton; class RoundButton; class RpWindow; +class LayerManager; } // namespace Ui namespace Ui::GL { @@ -50,17 +52,20 @@ struct Preview; } // namespace Theme } // namespace Window -namespace Media { -namespace Player { +namespace Media::Player { struct TrackState; -} // namespace Player -namespace Streaming { +} // namespace Media::Player + +namespace Media::Streaming { struct Information; struct Update; struct FrameWithInfo; enum class Error; -} // namespace Streaming -} // namespace Media +} // namespace Media::Streaming + +namespace Media::Stories { +class View; +} // namespace Media::Stories namespace Media::View { @@ -69,7 +74,8 @@ class Pip; class OverlayWidget final : public ClickHandlerHost - , private PlaybackControls::Delegate { + , private PlaybackControls::Delegate + , private Stories::Delegate { public: OverlayWidget(); ~OverlayWidget(); @@ -217,6 +223,11 @@ private: void playbackPauseMusic(); void switchToPip(); + not_null storiesWrap() override; + std::shared_ptr storiesShow() override; + auto storiesStickerOrEmojiChosen() + -> rpl::producer override; + void hideControls(bool force = false); void subscribeToScreenGeometry(); @@ -268,10 +279,15 @@ private: not_null item; MsgId topicRootId = 0; }; + struct StoriesContext { + not_null user; + StoryId id = 0; + }; void setContext(std::variant< v::null_t, ItemContext, - not_null> context); + not_null, + StoriesContext> context); void refreshLang(); void showSaveMsgFile(); @@ -551,6 +567,11 @@ private: int _streamedCreated = 0; bool _showAsPip = false; + std::unique_ptr _stories; + UserData *_storiesUser = nullptr; + rpl::event_stream _storiesStickerOrEmojiChosen; + std::unique_ptr _layerBg; + const style::icon *_docIcon = nullptr; style::color _docIconColor; QString _docName, _docSize, _docExt; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 601463962..8ec809049 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -140,7 +140,7 @@ public: not_null photo) const override; void processChosenSticker( - ChatHelpers::FileChosen chosen) const override; + ChatHelpers::FileChosen &&chosen) const override; private: const base::weak_ptr _window; @@ -233,7 +233,7 @@ bool MainWindowShow::showMediaPreview( } void MainWindowShow::processChosenSticker( - ChatHelpers::FileChosen chosen) const { + ChatHelpers::FileChosen &&chosen) const { if (const auto window = _window.get()) { Ui::PostponeCall(window, [=, chosen = std::move(chosen)]() mutable { window->stickerOrEmojiChosen(std::move(chosen));