mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Improve saved / archive stories design.
This commit is contained in:
parent
119ee6044a
commit
e98770d418
29 changed files with 660 additions and 202 deletions
BIN
Telegram/Resources/icons/info/info_stories_archive.png
Normal file
BIN
Telegram/Resources/icons/info/info_stories_archive.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 727 B |
BIN
Telegram/Resources/icons/info/info_stories_archive@2x.png
Normal file
BIN
Telegram/Resources/icons/info/info_stories_archive@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Resources/icons/info/info_stories_archive@3x.png
Normal file
BIN
Telegram/Resources/icons/info/info_stories_archive@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
Telegram/Resources/icons/info/info_stories_recent.png
Normal file
BIN
Telegram/Resources/icons/info/info_stories_recent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 606 B |
BIN
Telegram/Resources/icons/info/info_stories_recent@2x.png
Normal file
BIN
Telegram/Resources/icons/info/info_stories_recent@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/info/info_stories_recent@3x.png
Normal file
BIN
Telegram/Resources/icons/info/info_stories_recent@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
|
@ -1123,8 +1123,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_profile_sure_kick_channel" = "Remove {user} from the channel?";
|
||||
"lng_profile_sure_remove_admin" = "Remove {user} from admins?";
|
||||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_stories#one" = "{count} story";
|
||||
"lng_profile_stories#other" = "{count} stories";
|
||||
"lng_profile_saved_stories#one" = "{count} saved story";
|
||||
"lng_profile_saved_stories#other" = "{count} saved stories";
|
||||
"lng_profile_photos#one" = "{count} photo";
|
||||
"lng_profile_photos#other" = "{count} photos";
|
||||
"lng_profile_gifs#one" = "{count} GIF";
|
||||
|
@ -3812,9 +3812,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_stories_views#other" = "{count} views";
|
||||
"lng_stories_no_views" = "No views";
|
||||
"lng_stories_unsupported" = "This story is not supported\nby your version of Telegram.";
|
||||
"lng_stories_cant_reply" = "You can't reply to this story.";
|
||||
|
||||
"lng_stories_my_title" = "My Stories";
|
||||
"lng_stories_archive_button" = "Archive";
|
||||
"lng_stories_my_title" = "Saved Stories";
|
||||
"lng_stories_archive_button" = "Stories Archive";
|
||||
"lng_stories_recent_button" = "Recent Stories";
|
||||
"lng_stories_archive_title" = "Stories Archive";
|
||||
"lng_stories_reply_sent" = "Message Sent";
|
||||
"lng_stories_hidden_to_contacts" = "Those stories are now shown only in your Contacts list.";
|
||||
|
|
|
@ -82,6 +82,7 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
|||
|
||||
auto stories = object_ptr<Stories::List>(
|
||||
box,
|
||||
st::dialogsStoriesList,
|
||||
Stories::ContentForSession(
|
||||
&sessionController->session(),
|
||||
Data::StorySourcesList::All),
|
||||
|
|
|
@ -423,6 +423,7 @@ void Stories::apply(const MTPDupdateStory &data) {
|
|||
if (!user->hasStoriesHidden()) {
|
||||
refreshInList(StorySourcesList::NotHidden);
|
||||
}
|
||||
_sourceChanged.fire_copy(peerId);
|
||||
}
|
||||
|
||||
void Stories::apply(not_null<PeerData*> peer, const MTPUserStories *data) {
|
||||
|
|
|
@ -500,6 +500,12 @@ DialogsStories {
|
|||
nameTop: pixels;
|
||||
nameStyle: TextStyle;
|
||||
}
|
||||
DialogsStoriesList {
|
||||
small: DialogsStories;
|
||||
full: DialogsStories;
|
||||
bg: color;
|
||||
readOpacity: double;
|
||||
}
|
||||
|
||||
dialogsStories: DialogsStories {
|
||||
left: 4px;
|
||||
|
@ -532,3 +538,16 @@ dialogsStoriesFull: DialogsStories {
|
|||
linkFontOver: font(11px);
|
||||
}
|
||||
}
|
||||
|
||||
dialogsStoriesList: DialogsStoriesList {
|
||||
small: dialogsStories;
|
||||
full: dialogsStoriesFull;
|
||||
bg: dialogsBg;
|
||||
readOpacity: 0.6;
|
||||
}
|
||||
dialogsStoriesListInfo: DialogsStoriesList(dialogsStoriesList) {
|
||||
bg: transparent;
|
||||
}
|
||||
dialogsStoriesListMine: DialogsStoriesList(dialogsStoriesListInfo) {
|
||||
readOpacity: 1.;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,7 @@ InnerWidget::InnerWidget(
|
|||
, _controller(controller)
|
||||
, _stories(std::make_unique<Stories::List>(
|
||||
this,
|
||||
st::dialogsStoriesList,
|
||||
Stories::ContentForSession(
|
||||
&controller->session(),
|
||||
Data::StorySourcesList::NotHidden),
|
||||
|
|
|
@ -8,6 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_file_origin.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_photo_media.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -19,7 +24,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Dialogs::Stories {
|
||||
namespace {
|
||||
|
||||
class PeerUserpic final : public Userpic {
|
||||
constexpr auto kShownLastCount = 3;
|
||||
|
||||
class PeerUserpic final : public Thumbnail {
|
||||
public:
|
||||
explicit PeerUserpic(not_null<PeerData*> peer);
|
||||
|
||||
|
@ -48,6 +55,70 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class StoryThumbnail : public Thumbnail {
|
||||
public:
|
||||
explicit StoryThumbnail(FullStoryId id);
|
||||
virtual ~StoryThumbnail() = default;
|
||||
|
||||
QImage image(int size) override;
|
||||
void subscribeToUpdates(Fn<void()> callback) override;
|
||||
|
||||
protected:
|
||||
struct Thumb {
|
||||
Image *image = nullptr;
|
||||
bool blurred = false;
|
||||
};
|
||||
[[nodiscard]] virtual Main::Session &session() = 0;
|
||||
[[nodiscard]] virtual Thumb loaded(FullStoryId id) = 0;
|
||||
virtual void clear() = 0;
|
||||
|
||||
private:
|
||||
const FullStoryId _id;
|
||||
QImage _full;
|
||||
rpl::lifetime _subscription;
|
||||
QImage _prepared;
|
||||
bool _blurred = false;
|
||||
|
||||
};
|
||||
|
||||
class PhotoThumbnail final : public StoryThumbnail {
|
||||
public:
|
||||
PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id);
|
||||
|
||||
private:
|
||||
Main::Session &session() override;
|
||||
Thumb loaded(FullStoryId id) override;
|
||||
void clear() override;
|
||||
|
||||
const not_null<PhotoData*> _photo;
|
||||
std::shared_ptr<Data::PhotoMedia> _media;
|
||||
|
||||
};
|
||||
|
||||
class VideoThumbnail final : public StoryThumbnail {
|
||||
public:
|
||||
VideoThumbnail(not_null<DocumentData*> video, FullStoryId id);
|
||||
|
||||
private:
|
||||
Main::Session &session() override;
|
||||
Thumb loaded(FullStoryId id) override;
|
||||
void clear() override;
|
||||
|
||||
const not_null<DocumentData*> _video;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
|
||||
};
|
||||
|
||||
class EmptyThumbnail final : public Thumbnail {
|
||||
public:
|
||||
QImage image(int size) override;
|
||||
void subscribeToUpdates(Fn<void()> callback) override;
|
||||
|
||||
private:
|
||||
QImage _cached;
|
||||
|
||||
};
|
||||
|
||||
class State final {
|
||||
public:
|
||||
State(not_null<Data::Stories*> data, Data::StorySourcesList list);
|
||||
|
@ -57,7 +128,9 @@ public:
|
|||
private:
|
||||
const not_null<Data::Stories*> _data;
|
||||
const Data::StorySourcesList _list;
|
||||
base::flat_map<not_null<UserData*>, std::shared_ptr<Userpic>> _userpics;
|
||||
base::flat_map<
|
||||
not_null<UserData*>,
|
||||
std::shared_ptr<Thumbnail>> _userpics;
|
||||
|
||||
};
|
||||
|
||||
|
@ -122,6 +195,127 @@ void PeerUserpic::processNewPhoto() {
|
|||
}, _subscribed->downloadLifetime);
|
||||
}
|
||||
|
||||
StoryThumbnail::StoryThumbnail(FullStoryId id)
|
||||
: _id(id) {
|
||||
}
|
||||
|
||||
QImage StoryThumbnail::image(int size) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (_prepared.width() != size * ratio) {
|
||||
if (_full.isNull()) {
|
||||
_prepared = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_prepared.fill(Qt::black);
|
||||
} else {
|
||||
const auto width = _full.width();
|
||||
const auto skip = std::max((_full.height() - width) / 2, 0);
|
||||
_prepared = _full.copy(0, skip, width, width).scaled(
|
||||
QSize(size, size) * ratio,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
_prepared = Images::Circle(std::move(_prepared));
|
||||
}
|
||||
return _prepared;
|
||||
}
|
||||
|
||||
void StoryThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||
_subscription.destroy();
|
||||
if (!callback) {
|
||||
clear();
|
||||
return;
|
||||
} else if (!_full.isNull() && !_blurred) {
|
||||
return;
|
||||
}
|
||||
const auto thumbnail = loaded(_id);
|
||||
if (const auto image = thumbnail.image) {
|
||||
_full = image->original();
|
||||
}
|
||||
_blurred = thumbnail.blurred;
|
||||
if (!_blurred) {
|
||||
_prepared = QImage();
|
||||
} else {
|
||||
_subscription = session().downloaderTaskFinished(
|
||||
) | rpl::filter([=] {
|
||||
const auto thumbnail = loaded(_id);
|
||||
if (!thumbnail.blurred) {
|
||||
_full = thumbnail.image->original();
|
||||
_prepared = QImage();
|
||||
_blurred = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}) | rpl::take(1) | rpl::start_with_next(callback);
|
||||
}
|
||||
}
|
||||
|
||||
PhotoThumbnail::PhotoThumbnail(not_null<PhotoData*> photo, FullStoryId id)
|
||||
: StoryThumbnail(id)
|
||||
, _photo(photo) {
|
||||
}
|
||||
|
||||
Main::Session &PhotoThumbnail::session() {
|
||||
return _photo->session();
|
||||
}
|
||||
|
||||
StoryThumbnail::Thumb PhotoThumbnail::loaded(FullStoryId id) {
|
||||
if (!_media) {
|
||||
_media = _photo->createMediaView();
|
||||
_media->wanted(
|
||||
Data::PhotoSize::Small,
|
||||
Data::FileOriginStory(id.peer, id.story));
|
||||
}
|
||||
if (const auto small = _media->image(Data::PhotoSize::Small)) {
|
||||
return { .image = small };
|
||||
}
|
||||
return { .image = _media->thumbnailInline(), .blurred = true };
|
||||
}
|
||||
|
||||
void PhotoThumbnail::clear() {
|
||||
_media = nullptr;
|
||||
}
|
||||
|
||||
VideoThumbnail::VideoThumbnail(
|
||||
not_null<DocumentData*> video,
|
||||
FullStoryId id)
|
||||
: StoryThumbnail(id)
|
||||
, _video(video) {
|
||||
}
|
||||
|
||||
Main::Session &VideoThumbnail::session() {
|
||||
return _video->session();
|
||||
}
|
||||
|
||||
StoryThumbnail::Thumb VideoThumbnail::loaded(FullStoryId id) {
|
||||
if (!_media) {
|
||||
_media = _video->createMediaView();
|
||||
_media->thumbnailWanted(Data::FileOriginStory(id.peer, id.story));
|
||||
}
|
||||
if (const auto small = _media->thumbnail()) {
|
||||
return { .image = small };
|
||||
}
|
||||
return { .image = _media->thumbnailInline(), .blurred = true };
|
||||
}
|
||||
|
||||
void VideoThumbnail::clear() {
|
||||
_media = nullptr;
|
||||
}
|
||||
|
||||
QImage EmptyThumbnail::image(int size) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
if (_cached.width() != size * ratio) {
|
||||
_cached = QImage(
|
||||
QSize(size, size) * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_cached.fill(Qt::black);
|
||||
}
|
||||
return _cached;
|
||||
}
|
||||
|
||||
void EmptyThumbnail::subscribeToUpdates(Fn<void()> callback) {
|
||||
}
|
||||
|
||||
State::State(not_null<Data::Stories*> data, Data::StorySourcesList list)
|
||||
: _data(data)
|
||||
, _list(list) {
|
||||
|
@ -130,12 +324,12 @@ State::State(not_null<Data::Stories*> data, Data::StorySourcesList list)
|
|||
Content State::next() {
|
||||
auto result = Content{ .full = (_list == Data::StorySourcesList::All) };
|
||||
const auto &sources = _data->sources(_list);
|
||||
result.users.reserve(sources.size());
|
||||
result.elements.reserve(sources.size());
|
||||
for (const auto &info : sources) {
|
||||
const auto source = _data->source(info.id);
|
||||
Assert(source != nullptr);
|
||||
|
||||
auto userpic = std::shared_ptr<Userpic>();
|
||||
auto userpic = std::shared_ptr<Thumbnail>();
|
||||
const auto user = source->user;
|
||||
if (const auto i = _userpics.find(user); i != end(_userpics)) {
|
||||
userpic = i->second;
|
||||
|
@ -143,15 +337,15 @@ Content State::next() {
|
|||
userpic = std::make_shared<PeerUserpic>(user);
|
||||
_userpics.emplace(user, userpic);
|
||||
}
|
||||
result.users.push_back({
|
||||
result.elements.push_back({
|
||||
.id = uint64(user->id.value),
|
||||
.name = (user->isSelf()
|
||||
? tr::lng_stories_my_name(tr::now)
|
||||
: user->shortName()),
|
||||
.userpic = std::move(userpic),
|
||||
.thumbnail = std::move(userpic),
|
||||
.unread = info.unread,
|
||||
.hidden = info.hidden,
|
||||
.self = user->isSelf(),
|
||||
.skipSmall = user->isSelf(),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
@ -177,4 +371,88 @@ rpl::producer<Content> ContentForSession(
|
|||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Thumbnail> PrepareThumbnail(
|
||||
not_null<Data::Story*> story) {
|
||||
using Result = std::shared_ptr<Thumbnail>;
|
||||
const auto id = story->fullId();
|
||||
return v::match(story->media().data, [](v::null_t) -> Result {
|
||||
return std::make_shared<EmptyThumbnail>();
|
||||
}, [&](not_null<PhotoData*> photo) -> Result {
|
||||
return std::make_shared<PhotoThumbnail>(photo, id);
|
||||
}, [&](not_null<DocumentData*> video) -> Result {
|
||||
return std::make_shared<VideoThumbnail>(video, id);
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<Content> LastForPeer(not_null<PeerData*> peer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto stories = &peer->owner().stories();
|
||||
const auto peerId = peer->id;
|
||||
|
||||
return rpl::single(
|
||||
peerId
|
||||
) | rpl::then(
|
||||
stories->sourceChanged() | rpl::filter(_1 == peerId)
|
||||
) | rpl::map([=] {
|
||||
auto ids = std::vector<StoryId>();
|
||||
auto readTill = StoryId();
|
||||
if (const auto source = stories->source(peerId)) {
|
||||
readTill = source->readTill;
|
||||
ids = ranges::views::all(source->ids)
|
||||
| ranges::views::reverse
|
||||
| ranges::views::take(kShownLastCount)
|
||||
| ranges::views::transform(&Data::StoryIdDates::id)
|
||||
| ranges::to_vector;
|
||||
}
|
||||
return rpl::make_producer<Content>([=](auto consumer) {
|
||||
auto lifetime = rpl::lifetime();
|
||||
|
||||
struct State {
|
||||
Fn<void()> check;
|
||||
base::has_weak_ptr guard;
|
||||
bool pushed = false;
|
||||
};
|
||||
const auto state = lifetime.make_state<State>();
|
||||
state->check = [=] {
|
||||
if (state->pushed) {
|
||||
return;
|
||||
}
|
||||
auto resolving = false;
|
||||
auto result = Content();
|
||||
for (const auto id : ids) {
|
||||
const auto storyId = FullStoryId{ peerId, id };
|
||||
const auto maybe = stories->lookup(storyId);
|
||||
if (maybe) {
|
||||
if (!resolving) {
|
||||
result.elements.reserve(ids.size());
|
||||
result.elements.push_back({
|
||||
.id = uint64(id),
|
||||
.thumbnail = PrepareThumbnail(*maybe),
|
||||
.unread = (id > readTill),
|
||||
});
|
||||
}
|
||||
} else if (maybe.error() == Data::NoStory::Unknown) {
|
||||
resolving = true;
|
||||
stories->resolve(
|
||||
storyId,
|
||||
crl::guard(&state->guard, state->check));
|
||||
}
|
||||
}
|
||||
if (resolving) {
|
||||
return;
|
||||
}
|
||||
state->pushed = true;
|
||||
consumer.put_next(std::move(result));
|
||||
consumer.put_done();
|
||||
};
|
||||
rpl::single(peerId) | rpl::then(
|
||||
stories->itemsChanged() | rpl::filter(_1 == peerId)
|
||||
) | rpl::start_with_next(state->check, lifetime);
|
||||
|
||||
return lifetime;
|
||||
});
|
||||
}) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
} // namespace Dialogs::Stories
|
||||
|
|
|
@ -23,4 +23,6 @@ struct Content;
|
|||
not_null<Main::Session*> session,
|
||||
Data::StorySourcesList list);
|
||||
|
||||
[[nodiscard]] rpl::producer<Content> LastForPeer(not_null<PeerData*> peer);
|
||||
|
||||
} // namespace Dialogs::Stories
|
||||
|
|
|
@ -17,13 +17,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
namespace Dialogs::Stories {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSmallUserpicsShown = 3;
|
||||
constexpr auto kSmallReadOpacity = 0.6;
|
||||
constexpr auto kSmallThumbsShown = 3;
|
||||
constexpr auto kSummaryExpandLeft = 1.5;
|
||||
constexpr auto kPreloadPages = 2;
|
||||
|
||||
[[nodiscard]] int AvailableNameWidth() {
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
[[nodiscard]] int AvailableNameWidth(const style::DialogsStoriesList &st) {
|
||||
const auto &full = st.full;
|
||||
const auto &font = full.nameStyle.font;
|
||||
const auto skip = font->spacew;
|
||||
return full.photoLeft * 2 + full.photo - 2 * skip;
|
||||
|
@ -35,7 +34,7 @@ struct List::Layout {
|
|||
int itemsCount = 0;
|
||||
int shownHeight = 0;
|
||||
float64 ratio = 0.;
|
||||
float64 userpicLeft = 0.;
|
||||
float64 thumbnailLeft = 0.;
|
||||
float64 photoLeft = 0.;
|
||||
float64 left = 0.;
|
||||
float64 single = 0.;
|
||||
|
@ -52,9 +51,11 @@ struct List::Layout {
|
|||
|
||||
List::List(
|
||||
not_null<QWidget*> parent,
|
||||
const style::DialogsStoriesList &st,
|
||||
rpl::producer<Content> content,
|
||||
Fn<int()> shownHeight)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _shownHeight(shownHeight) {
|
||||
setCursor(style::cur_default);
|
||||
|
||||
|
@ -64,45 +65,46 @@ List::List(
|
|||
|
||||
_shownAnimation.stop();
|
||||
setMouseTracking(true);
|
||||
resize(0, _data.empty() ? 0 : st::dialogsStoriesFull.height);
|
||||
resize(0, _data.empty() ? 0 : st.full.height);
|
||||
}
|
||||
|
||||
void List::showContent(Content &&content) {
|
||||
if (_content == content) {
|
||||
return;
|
||||
}
|
||||
if (content.users.empty()) {
|
||||
if (content.elements.empty()) {
|
||||
_hidingData = base::take(_data);
|
||||
if (!_hidingData.empty()) {
|
||||
toggleAnimated(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto hidden = _content.users.empty();
|
||||
const auto hidden = _content.elements.empty();
|
||||
_content = std::move(content);
|
||||
auto items = base::take(
|
||||
_data.items.empty() ? _hidingData.items : _data.items);
|
||||
_hidingData = {};
|
||||
_data.items.reserve(_content.users.size());
|
||||
for (const auto &user : _content.users) {
|
||||
const auto i = ranges::find(items, user.id, [](const Item &item) {
|
||||
return item.user.id;
|
||||
_data.items.reserve(_content.elements.size());
|
||||
for (const auto &element : _content.elements) {
|
||||
const auto id = element.id;
|
||||
const auto i = ranges::find(items, id, [](const Item &item) {
|
||||
return item.element.id;
|
||||
});
|
||||
if (i != end(items)) {
|
||||
_data.items.push_back(std::move(*i));
|
||||
auto &item = _data.items.back();
|
||||
if (item.user.userpic != user.userpic) {
|
||||
item.user.userpic = user.userpic;
|
||||
if (item.element.thumbnail != element.thumbnail) {
|
||||
item.element.thumbnail = element.thumbnail;
|
||||
item.subscribed = false;
|
||||
}
|
||||
if (item.user.name != user.name) {
|
||||
item.user.name = user.name;
|
||||
if (item.element.name != element.name) {
|
||||
item.element.name = element.name;
|
||||
item.nameCache = QImage();
|
||||
}
|
||||
item.user.unread = user.unread;
|
||||
item.user.hidden = user.hidden;
|
||||
item.element.unread = element.unread;
|
||||
item.element.hidden = element.hidden;
|
||||
} else {
|
||||
_data.items.emplace_back(Item{ .user = user });
|
||||
_data.items.emplace_back(Item{ .element = element });
|
||||
}
|
||||
}
|
||||
updateScrollMax();
|
||||
|
@ -115,23 +117,25 @@ void List::showContent(Content &&content) {
|
|||
|
||||
List::Summaries List::ComposeSummaries(Data &data) {
|
||||
const auto total = int(data.items.size());
|
||||
const auto skip = (total > 1 && data.items[0].user.self) ? 1 : 0;
|
||||
const auto skip = (total > 1 && data.items[0].element.skipSmall)
|
||||
? 1
|
||||
: 0;
|
||||
auto unreadInFirst = 0;
|
||||
auto unreadTotal = 0;
|
||||
for (auto i = skip; i != total; ++i) {
|
||||
if (data.items[i].user.unread) {
|
||||
if (data.items[i].element.unread) {
|
||||
++unreadTotal;
|
||||
if (i < skip + kSmallUserpicsShown) {
|
||||
if (i < skip + kSmallThumbsShown) {
|
||||
++unreadInFirst;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto result = Summaries{ .skipSelf = (skip > 0) };
|
||||
auto result = Summaries{ .skipOne = (skip > 0) };
|
||||
result.total.string
|
||||
= tr::lng_stories_row_count(tr::now, lt_count, total);
|
||||
const auto append = [&](QString &to, int index, bool last) {
|
||||
if (to.isEmpty()) {
|
||||
to = data.items[index].user.name;
|
||||
to = data.items[index].element.name;
|
||||
} else {
|
||||
to = (last
|
||||
? tr::lng_stories_row_unread_and_last
|
||||
|
@ -140,19 +144,19 @@ List::Summaries List::ComposeSummaries(Data &data) {
|
|||
lt_accumulated,
|
||||
to,
|
||||
lt_user,
|
||||
data.items[index].user.name);
|
||||
data.items[index].element.name);
|
||||
}
|
||||
};
|
||||
if (!total) {
|
||||
return result;
|
||||
} else if (total <= skip + kSmallUserpicsShown) {
|
||||
} else if (total <= skip + kSmallThumbsShown) {
|
||||
for (auto i = skip; i != total; ++i) {
|
||||
append(result.allNames.string, i, i == total - 1);
|
||||
}
|
||||
}
|
||||
if (unreadInFirst > 0 && unreadInFirst == unreadTotal) {
|
||||
for (auto i = skip; i != total; ++i) {
|
||||
if (data.items[i].user.unread) {
|
||||
if (data.items[i].element.unread) {
|
||||
append(result.unreadNames.string, i, !--unreadTotal);
|
||||
}
|
||||
}
|
||||
|
@ -166,20 +170,22 @@ bool List::StringsEqual(const Summaries &a, const Summaries &b) {
|
|||
&& (a.unreadNames.string == b.unreadNames.string);
|
||||
}
|
||||
|
||||
void List::Populate(Summary &summary) {
|
||||
void List::Populate(
|
||||
const style::DialogsStories &st,
|
||||
Summary &summary) {
|
||||
if (summary.empty()) {
|
||||
return;
|
||||
}
|
||||
summary.cache = QImage();
|
||||
summary.text = Ui::Text::String(
|
||||
st::dialogsStories.nameStyle,
|
||||
summary.string);
|
||||
summary.text = Ui::Text::String(st.nameStyle, summary.string);
|
||||
}
|
||||
|
||||
void List::Populate(Summaries &summaries) {
|
||||
Populate(summaries.total);
|
||||
Populate(summaries.allNames);
|
||||
Populate(summaries.unreadNames);
|
||||
void List::Populate(
|
||||
const style::DialogsStories &st,
|
||||
Summaries &summaries) {
|
||||
Populate(st, summaries.total);
|
||||
Populate(st, summaries.allNames);
|
||||
Populate(st, summaries.unreadNames);
|
||||
}
|
||||
|
||||
void List::updateSummary(Data &data) {
|
||||
|
@ -188,7 +194,7 @@ void List::updateSummary(Data &data) {
|
|||
return;
|
||||
}
|
||||
data.summaries = std::move(summaries);
|
||||
Populate(data.summaries);
|
||||
Populate(_st.small, data.summaries);
|
||||
}
|
||||
|
||||
void List::toggleAnimated(bool shown) {
|
||||
|
@ -203,14 +209,14 @@ void List::updateHeight() {
|
|||
const auto shown = _shownAnimation.value(_data.empty() ? 0. : 1.);
|
||||
resize(
|
||||
width(),
|
||||
anim::interpolate(0, st::dialogsStoriesFull.height, shown));
|
||||
anim::interpolate(0, _st.full.height, shown));
|
||||
if (_data.empty() && shown == 0.) {
|
||||
_hidingData = {};
|
||||
}
|
||||
}
|
||||
|
||||
void List::updateScrollMax() {
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto &full = _st.full;
|
||||
const auto singleFull = full.photoLeft * 2 + full.photo;
|
||||
const auto widthFull = full.left + int(_data.items.size()) * singleFull;
|
||||
_scrollLeftMax = std::max(widthFull - width(), 0);
|
||||
|
@ -252,8 +258,8 @@ void List::resizeEvent(QResizeEvent *e) {
|
|||
}
|
||||
|
||||
List::Layout List::computeLayout() const {
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto &st = _st.small;
|
||||
const auto &full = _st.full;
|
||||
const auto shownHeight = std::max(_shownHeight(), st.height);
|
||||
const auto ratio = float64(shownHeight - st.height)
|
||||
/ (full.height - st.height);
|
||||
|
@ -267,11 +273,12 @@ List::Layout List::computeLayout() const {
|
|||
+ st::defaultDialogRow.photoSize
|
||||
+ st::defaultDialogRow.padding.left();
|
||||
const auto narrow = (width() <= narrowWidth);
|
||||
const auto smallSkip = (itemsCount > 1 && rendering.items[0].user.self)
|
||||
const auto smallSkip = (itemsCount > 1
|
||||
&& rendering.items[0].element.skipSmall)
|
||||
? 1
|
||||
: 0;
|
||||
const auto smallCount = std::min(
|
||||
kSmallUserpicsShown,
|
||||
kSmallThumbsShown,
|
||||
itemsCount - smallSkip);
|
||||
const auto smallWidth = st.photo + (smallCount - 1) * st.shift;
|
||||
const auto leftSmall = (narrow
|
||||
|
@ -288,17 +295,17 @@ List::Layout List::computeLayout() const {
|
|||
const auto startIndexSmall = std::min(startIndexFull, smallSkip);
|
||||
const auto endIndexSmall = smallSkip + smallCount;
|
||||
const auto cellLeftSmall = leftSmall + (startIndexSmall * st.shift);
|
||||
const auto userpicLeftFull = cellLeftFull + full.photoLeft;
|
||||
const auto userpicLeftSmall = cellLeftSmall + st.photoLeft;
|
||||
const auto userpicLeft = lerp(userpicLeftSmall, userpicLeftFull);
|
||||
const auto thumbnailLeftFull = cellLeftFull + full.photoLeft;
|
||||
const auto thumbnailLeftSmall = cellLeftSmall + st.photoLeft;
|
||||
const auto thumbnailLeft = lerp(thumbnailLeftSmall, thumbnailLeftFull);
|
||||
const auto photoLeft = lerp(st.photoLeft, full.photoLeft);
|
||||
return Layout{
|
||||
.itemsCount = itemsCount,
|
||||
.shownHeight = shownHeight,
|
||||
.ratio = ratio,
|
||||
.userpicLeft = userpicLeft,
|
||||
.thumbnailLeft = thumbnailLeft,
|
||||
.photoLeft = photoLeft,
|
||||
.left = userpicLeft - photoLeft,
|
||||
.left = thumbnailLeft - photoLeft,
|
||||
.single = lerp(st.shift, singleFull),
|
||||
.smallSkip = smallSkip,
|
||||
.leftFull = leftFull,
|
||||
|
@ -313,8 +320,8 @@ List::Layout List::computeLayout() const {
|
|||
}
|
||||
|
||||
void List::paintEvent(QPaintEvent *e) {
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto &st = _st.small;
|
||||
const auto &full = _st.full;
|
||||
const auto layout = computeLayout();
|
||||
const auto ratio = layout.ratio;
|
||||
const auto lerp = [&](float64 a, float64 b) {
|
||||
|
@ -331,14 +338,14 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
+ (photoTop + (photo / 2.));
|
||||
const auto nameScale = layout.shownHeight / float64(full.height);
|
||||
const auto nameTop = nameScale * full.nameTop;
|
||||
const auto nameWidth = nameScale * AvailableNameWidth();
|
||||
const auto nameWidth = nameScale * AvailableNameWidth(_st);
|
||||
const auto nameHeight = nameScale * full.nameStyle.font->height;
|
||||
const auto nameLeft = layout.photoLeft + (photo - nameWidth) / 2.;
|
||||
const auto readUserpicOpacity = lerp(kSmallReadOpacity, 1.);
|
||||
const auto readUserpicAppearingOpacity = lerp(kSmallReadOpacity, 0.);
|
||||
const auto readUserpicOpacity = lerp(_st.readOpacity, 1.);
|
||||
const auto readUserpicAppearingOpacity = lerp(_st.readOpacity, 0.);
|
||||
|
||||
auto p = QPainter(this);
|
||||
p.fillRect(e->rect(), st::dialogsBg);
|
||||
p.fillRect(e->rect(), _st.bg);
|
||||
p.translate(0, height() - layout.shownHeight);
|
||||
|
||||
const auto drawSmall = (ratio < 1.);
|
||||
|
@ -375,8 +382,8 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
return Single{ x, indexSmall, small, indexFull, full };
|
||||
};
|
||||
const auto hasUnread = [&](const Single &single) {
|
||||
return (single.itemSmall && single.itemSmall->user.unread)
|
||||
|| (single.itemFull && single.itemFull->user.unread);
|
||||
return (single.itemSmall && single.itemSmall->element.unread)
|
||||
|| (single.itemFull && single.itemFull->element.unread);
|
||||
};
|
||||
const auto enumerate = [&](auto &&paintGradient, auto &&paintOther) {
|
||||
auto nextGradientPainted = false;
|
||||
|
@ -398,7 +405,9 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
if (i > first && hasUnread(current) && next) {
|
||||
if (current.itemSmall || !next.itemSmall) {
|
||||
if (i - 1 == first && first > 0 && !skippedPainted) {
|
||||
if (i - 1 == first
|
||||
&& first > 0
|
||||
&& !skippedPainted) {
|
||||
if (const auto skipped = lookup(i - 2)) {
|
||||
skippedPainted = true;
|
||||
paintGradient(skipped);
|
||||
|
@ -425,11 +434,15 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
|
||||
// Unread gradient.
|
||||
const auto x = single.x;
|
||||
const auto userpic = QRectF(x + layout.photoLeft, photoTop, photo, photo);
|
||||
const auto userpic = QRectF(
|
||||
x + layout.photoLeft,
|
||||
photoTop,
|
||||
photo,
|
||||
photo);
|
||||
const auto small = single.itemSmall;
|
||||
const auto itemFull = single.itemFull;
|
||||
const auto smallUnread = small && small->user.unread;
|
||||
const auto fullUnread = itemFull && itemFull->user.unread;
|
||||
const auto smallUnread = small && small->element.unread;
|
||||
const auto fullUnread = itemFull && itemFull->element.unread;
|
||||
const auto unreadOpacity = (smallUnread && fullUnread)
|
||||
? 1.
|
||||
: smallUnread
|
||||
|
@ -458,11 +471,15 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
Expects(single.itemSmall || single.itemFull);
|
||||
|
||||
const auto x = single.x;
|
||||
const auto userpic = QRectF(x + layout.photoLeft, photoTop, photo, photo);
|
||||
const auto userpic = QRectF(
|
||||
x + layout.photoLeft,
|
||||
photoTop,
|
||||
photo,
|
||||
photo);
|
||||
const auto small = single.itemSmall;
|
||||
const auto itemFull = single.itemFull;
|
||||
const auto smallUnread = small && small->user.unread;
|
||||
const auto fullUnread = itemFull && itemFull->user.unread;
|
||||
const auto smallUnread = small && small->element.unread;
|
||||
const auto fullUnread = itemFull && itemFull->element.unread;
|
||||
|
||||
// White circle with possible read gray line.
|
||||
const auto hasReadLine = (itemFull && !fullUnread);
|
||||
|
@ -483,25 +500,27 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
// Userpic.
|
||||
if (itemFull == small) {
|
||||
p.setOpacity(smallUnread ? 1. : readUserpicOpacity);
|
||||
validateUserpic(itemFull);
|
||||
validateThumbnail(itemFull);
|
||||
const auto size = full.photo;
|
||||
p.drawImage(userpic, itemFull->user.userpic->image(size));
|
||||
p.drawImage(userpic, itemFull->element.thumbnail->image(size));
|
||||
} else {
|
||||
if (small) {
|
||||
p.setOpacity(smallUnread
|
||||
? (itemFull ? 1. : (1. - ratio))
|
||||
: (itemFull
|
||||
? kSmallReadOpacity
|
||||
? _st.readOpacity
|
||||
: readUserpicAppearingOpacity));
|
||||
validateUserpic(small);
|
||||
validateThumbnail(small);
|
||||
const auto size = (ratio > 0.) ? full.photo : st.photo;
|
||||
p.drawImage(userpic, small->user.userpic->image(size));
|
||||
p.drawImage(userpic, small->element.thumbnail->image(size));
|
||||
}
|
||||
if (itemFull) {
|
||||
p.setOpacity(ratio);
|
||||
validateUserpic(itemFull);
|
||||
validateThumbnail(itemFull);
|
||||
const auto size = full.photo;
|
||||
p.drawImage(userpic, itemFull->user.userpic->image(size));
|
||||
p.drawImage(
|
||||
userpic,
|
||||
itemFull->element.thumbnail->image(size));
|
||||
}
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
|
@ -510,11 +529,11 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
paintSummary(p, rendering, summaryTop, ratio);
|
||||
}
|
||||
|
||||
void List::validateUserpic(not_null<Item*> item) {
|
||||
void List::validateThumbnail(not_null<Item*> item) {
|
||||
if (!item->subscribed) {
|
||||
item->subscribed = true;
|
||||
//const auto id = item.user.id;
|
||||
item->user.userpic->subscribeToUpdates([=] {
|
||||
//const auto id = item.element.id;
|
||||
item->element.thumbnail->subscribeToUpdates([=] {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
@ -525,10 +544,10 @@ void List::validateName(not_null<Item*> item) {
|
|||
if (!item->nameCache.isNull() && item->nameCacheColor == color->c) {
|
||||
return;
|
||||
}
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto &full = _st.full;
|
||||
const auto &font = full.nameStyle.font;
|
||||
const auto available = AvailableNameWidth();
|
||||
const auto text = Ui::Text::String(full.nameStyle, item->user.name);
|
||||
const auto available = AvailableNameWidth(_st);
|
||||
const auto text = Ui::Text::String(full.nameStyle, item->element.name);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
item->nameCacheColor = color->c;
|
||||
item->nameCache = QImage(
|
||||
|
@ -542,13 +561,13 @@ void List::validateName(not_null<Item*> item) {
|
|||
}
|
||||
|
||||
List::Summary &List::ChooseSummary(
|
||||
const style::DialogsStories &st,
|
||||
Summaries &summaries,
|
||||
int totalItems,
|
||||
int fullWidth) {
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto used = std::min(
|
||||
totalItems - (summaries.skipSelf ? 1 : 0),
|
||||
kSmallUserpicsShown);
|
||||
totalItems - (summaries.skipOne ? 1 : 0),
|
||||
kSmallThumbsShown);
|
||||
const auto taken = st.left
|
||||
+ st.photoLeft
|
||||
+ st.photo
|
||||
|
@ -572,13 +591,14 @@ List::Summary &List::ChooseSummary(
|
|||
return summaries.total;
|
||||
}
|
||||
|
||||
void List::PrerenderSummary(Summary &summary) {
|
||||
void List::PrerenderSummary(
|
||||
const style::DialogsStories &st,
|
||||
Summary &summary) {
|
||||
if (!summary.cache.isNull()
|
||||
&& summary.cacheForWidth == summary.available
|
||||
&& summary.cacheColor == st::dialogsNameFg->c) {
|
||||
return;
|
||||
}
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto use = std::min(summary.text.maxWidth(), summary.available);
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
summary.cache = QImage(
|
||||
|
@ -597,16 +617,20 @@ void List::paintSummary(
|
|||
float64 summaryTop,
|
||||
float64 hidden) {
|
||||
const auto total = int(data.items.size());
|
||||
auto &summary = ChooseSummary(data.summaries, total, width());
|
||||
PrerenderSummary(summary);
|
||||
auto &summary = ChooseSummary(
|
||||
_st.small,
|
||||
data.summaries,
|
||||
total,
|
||||
width());
|
||||
PrerenderSummary(_st.small, summary);
|
||||
const auto lerp = [&](float64 from, float64 to) {
|
||||
return from + (to - from) * hidden;
|
||||
};
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto &st = _st.small;
|
||||
const auto &full = _st.full;
|
||||
const auto used = std::min(
|
||||
total - (data.summaries.skipSelf ? 1 : 0),
|
||||
kSmallUserpicsShown);
|
||||
total - (data.summaries.skipOne ? 1 : 0),
|
||||
kSmallThumbsShown);
|
||||
const auto fullLeft = st.left
|
||||
+ st.photoLeft
|
||||
+ st.photo
|
||||
|
@ -671,7 +695,7 @@ void List::mouseMoveEvent(QMouseEvent *e) {
|
|||
if (!_dragging && _mouseDownPosition) {
|
||||
if ((_lastMousePosition - *_mouseDownPosition).manhattanLength()
|
||||
>= QApplication::startDragDistance()) {
|
||||
if (_shownHeight() < st::dialogsStoriesFull.height) {
|
||||
if (_shownHeight() < _st.full.height) {
|
||||
_expandRequests.fire({});
|
||||
}
|
||||
_dragging = true;
|
||||
|
@ -718,7 +742,7 @@ void List::mouseReleaseEvent(QMouseEvent *e) {
|
|||
if (_selected < 0) {
|
||||
_expandRequests.fire({});
|
||||
} else if (_selected < _data.items.size()) {
|
||||
_clicks.fire_copy(_data.items[_selected].user.id);
|
||||
_clicks.fire_copy(_data.items[_selected].element.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -737,8 +761,8 @@ void List::contextMenuEvent(QContextMenuEvent *e) {
|
|||
auto &item = _data.items[_selected];
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(this);
|
||||
|
||||
const auto id = item.user.id;
|
||||
const auto hidden = item.user.hidden;
|
||||
const auto id = item.element.id;
|
||||
const auto hidden = item.element.hidden;
|
||||
_menu->addAction(tr::lng_context_view_profile(tr::now), [=] {
|
||||
_showProfileRequests.fire_copy(id);
|
||||
});
|
||||
|
@ -781,8 +805,8 @@ void List::updateSelected() {
|
|||
if (_pressed >= 0) {
|
||||
return;
|
||||
}
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto &st = _st.small;
|
||||
const auto &full = _st.full;
|
||||
const auto p = mapFromGlobal(_lastMousePosition);
|
||||
const auto layout = computeLayout();
|
||||
const auto firstRightFull = layout.leftFull
|
||||
|
|
|
@ -13,31 +13,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
class QPainter;
|
||||
|
||||
namespace style {
|
||||
struct DialogsStories;
|
||||
struct DialogsStoriesList;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Dialogs::Stories {
|
||||
|
||||
class Userpic {
|
||||
class Thumbnail {
|
||||
public:
|
||||
[[nodiscard]] virtual QImage image(int size) = 0;
|
||||
virtual void subscribeToUpdates(Fn<void()> callback) = 0;
|
||||
};
|
||||
|
||||
struct User {
|
||||
struct Element {
|
||||
uint64 id = 0;
|
||||
QString name;
|
||||
std::shared_ptr<Userpic> userpic;
|
||||
std::shared_ptr<Thumbnail> thumbnail;
|
||||
bool unread = false;
|
||||
bool hidden = false;
|
||||
bool self = false;
|
||||
bool skipSmall = false;
|
||||
|
||||
friend inline bool operator==(const User &a, const User &b) = default;
|
||||
friend inline bool operator==(
|
||||
const Element &a,
|
||||
const Element &b) = default;
|
||||
};
|
||||
|
||||
struct Content {
|
||||
std::vector<User> users;
|
||||
std::vector<Element> elements;
|
||||
bool full = false;
|
||||
|
||||
friend inline bool operator==(
|
||||
|
@ -54,6 +61,7 @@ class List final : public Ui::RpWidget {
|
|||
public:
|
||||
List(
|
||||
not_null<QWidget*> parent,
|
||||
const style::DialogsStoriesList &st,
|
||||
rpl::producer<Content> content,
|
||||
Fn<int()> shownHeight);
|
||||
|
||||
|
@ -67,7 +75,7 @@ public:
|
|||
private:
|
||||
struct Layout;
|
||||
struct Item {
|
||||
User user;
|
||||
Element element;
|
||||
QImage nameCache;
|
||||
QColor nameCacheColor;
|
||||
bool subscribed = false;
|
||||
|
@ -88,7 +96,7 @@ private:
|
|||
Summary total;
|
||||
Summary allNames;
|
||||
Summary unreadNames;
|
||||
bool skipSelf = false;
|
||||
bool skipOne = false;
|
||||
};
|
||||
struct Data {
|
||||
std::vector<Item> items;
|
||||
|
@ -103,13 +111,20 @@ private:
|
|||
[[nodiscard]] static bool StringsEqual(
|
||||
const Summaries &a,
|
||||
const Summaries &b);
|
||||
static void Populate(Summary &summary);
|
||||
static void Populate(Summaries &summaries);
|
||||
static void Populate(
|
||||
const style::DialogsStories &st,
|
||||
Summary &summary);
|
||||
static void Populate(
|
||||
const style::DialogsStories &st,
|
||||
Summaries &summaries);
|
||||
[[nodiscard]] static Summary &ChooseSummary(
|
||||
const style::DialogsStories &st,
|
||||
Summaries &summaries,
|
||||
int totalItems,
|
||||
int fullWidth);
|
||||
static void PrerenderSummary(Summary &summary);
|
||||
static void PrerenderSummary(
|
||||
const style::DialogsStories &st,
|
||||
Summary &summary);
|
||||
|
||||
void showContent(Content &&content);
|
||||
void enterEventHook(QEnterEvent *e) override;
|
||||
|
@ -121,7 +136,7 @@ private:
|
|||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
void validateUserpic(not_null<Item*> item);
|
||||
void validateThumbnail(not_null<Item*> item);
|
||||
void validateName(not_null<Item*> item);
|
||||
void updateScrollMax();
|
||||
void updateSummary(Data &data);
|
||||
|
@ -140,6 +155,7 @@ private:
|
|||
|
||||
[[nodiscard]] Layout computeLayout() const;
|
||||
|
||||
const style::DialogsStoriesList &_st;
|
||||
Content _content;
|
||||
Data _data;
|
||||
Data _hidingData;
|
||||
|
|
|
@ -1979,6 +1979,8 @@ bool HistoryItem::forbidsSaving() const {
|
|||
bool HistoryItem::canDelete() const {
|
||||
if (isSponsored()) {
|
||||
return false;
|
||||
} else if (IsStoryMsgId(id)) {
|
||||
return false && _history->peer->isSelf(); // #TODO stories
|
||||
} else if (isService() && !isRegular()) {
|
||||
return false;
|
||||
} else if (topicRootId() == id) {
|
||||
|
|
|
@ -368,6 +368,8 @@ infoIconMediaLink: icon {{ "info/info_media_link", infoIconFg }};
|
|||
infoIconMediaGroup: icon {{ "info/info_common_groups", infoIconFg }};
|
||||
infoIconMediaVoice: icon {{ "info/info_media_voice", infoIconFg }};
|
||||
infoIconMediaStories: icon {{ "info/info_media_stories", infoIconFg }};
|
||||
infoIconMediaStoriesArchive: icon {{ "info/info_stories_archive", infoIconFg }};
|
||||
infoIconMediaStoriesRecent: icon {{ "info/info_stories_recent", infoIconFg }};
|
||||
|
||||
infoRoundedIconRequests: icon {{ "info/edit/group_manage_join_requests", settingsIconFg }};
|
||||
infoRoundedIconRecentActions: icon {{ "info/edit/group_manage_actions", settingsIconFg }};
|
||||
|
|
|
@ -534,6 +534,8 @@ Ui::StringWithNumbers TopBar::generateSelectedText() const {
|
|||
case Type::MusicFile: return tr::lng_media_selected_song;
|
||||
case Type::Link: return tr::lng_media_selected_link;
|
||||
case Type::RoundVoiceFile: return tr::lng_media_selected_audio;
|
||||
// #TODO stories
|
||||
case Type::PhotoVideo: return tr::lng_media_selected_photo;
|
||||
}
|
||||
Unexpected("Type in TopBar::generateSelectedText()");
|
||||
}();
|
||||
|
|
|
@ -142,7 +142,7 @@ inline auto AddStoriesButton(
|
|||
parent,
|
||||
std::move(count),
|
||||
[](int count) {
|
||||
return tr::lng_profile_stories(tr::now, lt_count, count);
|
||||
return tr::lng_profile_saved_stories(tr::now, lt_count, count);
|
||||
},
|
||||
tracker)->entity();
|
||||
result->addClickHandler([=] {
|
||||
|
|
|
@ -347,7 +347,7 @@ void ListSection::resizeToWidth(int newWidth) {
|
|||
_itemWidth = ((newWidth - _itemsLeft) / _itemsInRow)
|
||||
- st::infoMediaSkip;
|
||||
for (auto &item : _items) {
|
||||
item->resizeGetHeight(_itemWidth);
|
||||
_itemHeight = item->resizeGetHeight(_itemWidth);
|
||||
}
|
||||
} break;
|
||||
|
||||
|
@ -378,7 +378,7 @@ int ListSection::recountHeight() {
|
|||
case Type::Video:
|
||||
case Type::PhotoVideo: // #TODO stories
|
||||
case Type::RoundFile: {
|
||||
auto itemHeight = _itemWidth + st::infoMediaSkip;
|
||||
auto itemHeight = _itemHeight + st::infoMediaSkip;
|
||||
auto index = 0;
|
||||
result += _itemsTop;
|
||||
for (auto &item : _items) {
|
||||
|
|
|
@ -82,6 +82,7 @@ private:
|
|||
int _itemsLeft = 0;
|
||||
int _itemsTop = 0;
|
||||
int _itemWidth = 0;
|
||||
int _itemHeight = 0;
|
||||
int _itemsInRow = 1;
|
||||
mutable int _rowsCount = 0;
|
||||
int _top = 0;
|
||||
|
|
|
@ -421,19 +421,21 @@ std::unique_ptr<BaseLayout> Provider::createLayout(
|
|||
}
|
||||
return nullptr;
|
||||
};
|
||||
const auto spoiler = [&] {
|
||||
if (const auto media = item->media()) {
|
||||
return media->hasSpoiler();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto &songSt = st::overviewFileLayout;
|
||||
using namespace Overview::Layout;
|
||||
const auto options = [&] {
|
||||
const auto media = item->media();
|
||||
return MediaOptions{ .spoiler = media && media->hasSpoiler() };
|
||||
};
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
if (const auto photo = getPhoto()) {
|
||||
return std::make_unique<Photo>(delegate, item, photo, spoiler());
|
||||
return std::make_unique<Photo>(
|
||||
delegate,
|
||||
item,
|
||||
photo,
|
||||
options());
|
||||
}
|
||||
return nullptr;
|
||||
case Type::GIF:
|
||||
|
@ -443,7 +445,7 @@ std::unique_ptr<BaseLayout> Provider::createLayout(
|
|||
return nullptr;
|
||||
case Type::Video:
|
||||
if (const auto file = getFile()) {
|
||||
return std::make_unique<Video>(delegate, item, file, spoiler());
|
||||
return std::make_unique<Video>(delegate, item, file, options());
|
||||
}
|
||||
return nullptr;
|
||||
case Type::File:
|
||||
|
|
|
@ -8,17 +8,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "info/stories/info_stories_inner_widget.h"
|
||||
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_stories.h"
|
||||
#include "data/data_user.h"
|
||||
#include "dialogs/ui/dialogs_stories_content.h"
|
||||
#include "dialogs/ui/dialogs_stories_list.h"
|
||||
#include "info/media/info_media_list_widget.h"
|
||||
#include "info/profile/info_profile_icon.h"
|
||||
#include "info/stories/info_stories_widget.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_settings.h"
|
||||
|
||||
namespace Info::Stories {
|
||||
|
||||
|
@ -99,38 +108,112 @@ void InnerWidget::setupArchive() {
|
|||
&& _isStackBottom) {
|
||||
createArchiveButton();
|
||||
} else {
|
||||
_archive.destroy();
|
||||
_buttons.destroy();
|
||||
refreshHeight();
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::createArchiveButton() {
|
||||
_archive.create(this);
|
||||
_archive->show();
|
||||
_buttons.create(this);
|
||||
_buttons->show();
|
||||
|
||||
const auto button = ::Settings::AddButton(
|
||||
_archive,
|
||||
const auto stories = &_controller->session().data().stories();
|
||||
const auto self = _controller->session().user();
|
||||
const auto archive = ::Settings::AddButton(
|
||||
_buttons,
|
||||
tr::lng_stories_archive_button(),
|
||||
st::infoSharedMediaButton);
|
||||
button->addClickHandler([=] {
|
||||
archive->addClickHandler([=] {
|
||||
_controller->showSection(Info::Stories::Make(
|
||||
_controller->key().storiesPeer(),
|
||||
Stories::Tab::Archive));
|
||||
});
|
||||
auto count = rpl::single(
|
||||
rpl::empty
|
||||
) | rpl::then(stories->archiveChanged()) | rpl::map([=] {
|
||||
const auto value = stories->archiveCount();
|
||||
return (value > 0) ? QString::number(value) : QString();
|
||||
});
|
||||
::Settings::CreateRightLabel(
|
||||
archive,
|
||||
std::move(count),
|
||||
st::infoSharedMediaButton,
|
||||
tr::lng_stories_archive_button());
|
||||
object_ptr<Profile::FloatingIcon>(
|
||||
button,
|
||||
st::infoIconMediaGroup,
|
||||
archive,
|
||||
st::infoIconMediaStoriesArchive,
|
||||
st::infoSharedMediaButtonIconPosition)->show();
|
||||
_archive->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
_archive,
|
||||
st::infoProfileSkip));
|
||||
_archive->add(object_ptr<Ui::BoxContentDivider>(_archive));
|
||||
|
||||
_archive->resizeToWidth(width());
|
||||
_archive->heightValue(
|
||||
const auto recentWrap = _buttons->add(
|
||||
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||
_buttons,
|
||||
::Settings::CreateButton(
|
||||
_buttons,
|
||||
tr::lng_stories_recent_button(),
|
||||
st::infoSharedMediaButton)));
|
||||
|
||||
using namespace Dialogs::Stories;
|
||||
auto last = LastForPeer(
|
||||
self
|
||||
) | rpl::map([=](Content &&content) {
|
||||
for (auto &element : content.elements) {
|
||||
element.unread = false;
|
||||
}
|
||||
return std::move(content);
|
||||
}) | rpl::start_spawning(recentWrap->lifetime());
|
||||
const auto recent = recentWrap->entity();
|
||||
const auto thumbs = Ui::CreateChild<List>(
|
||||
recent,
|
||||
st::dialogsStoriesListMine,
|
||||
rpl::duplicate(last) | rpl::filter([](const Content &content) {
|
||||
return !content.elements.empty();
|
||||
}),
|
||||
[] { return st::dialogsStories.height; });
|
||||
rpl::combine(
|
||||
recent->sizeValue(),
|
||||
rpl::duplicate(last)
|
||||
) | rpl::start_with_next([=](QSize size, const Content &content) {
|
||||
if (content.elements.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto width = st::defaultDialogRow.padding.left()
|
||||
+ st::defaultDialogRow.photoSize
|
||||
+ st::defaultDialogRow.padding.left();
|
||||
const auto &small = st::dialogsStories;
|
||||
const auto count = int(content.elements.size());
|
||||
const auto smallWidth = small.photo + (count - 1) * small.shift;
|
||||
const auto real = smallWidth;
|
||||
const auto top = st::dialogsStories.height
|
||||
- st::dialogsStoriesFull.height
|
||||
+ (size.height() - st::dialogsStories.height) / 2;
|
||||
const auto right = st::settingsButtonRightSkip - (width - real) / 2;
|
||||
thumbs->resizeToWidth(width);
|
||||
thumbs->moveToRight(right, top);
|
||||
}, thumbs->lifetime());
|
||||
thumbs->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
recent->addClickHandler([=] {
|
||||
_controller->parentController()->openPeerStories(self->id);
|
||||
});
|
||||
object_ptr<Profile::FloatingIcon>(
|
||||
recent,
|
||||
st::infoIconMediaStoriesRecent,
|
||||
st::infoSharedMediaButtonIconPosition)->show();
|
||||
recentWrap->toggleOn(rpl::duplicate(
|
||||
last
|
||||
) | rpl::map([](const Content &content) {
|
||||
return !content.elements.empty();
|
||||
}));
|
||||
|
||||
_buttons->add(object_ptr<Ui::FixedHeightWidget>(
|
||||
_buttons,
|
||||
st::infoProfileSkip));
|
||||
_buttons->add(object_ptr<Ui::BoxContentDivider>(_buttons));
|
||||
|
||||
_buttons->resizeToWidth(width());
|
||||
_buttons->heightValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshHeight();
|
||||
}, _archive->lifetime());
|
||||
}, _buttons->lifetime());
|
||||
}
|
||||
|
||||
void InnerWidget::visibleTopBottomUpdated(
|
||||
|
@ -194,8 +277,8 @@ int InnerWidget::resizeGetHeight(int newWidth) {
|
|||
_inResize = true;
|
||||
auto guard = gsl::finally([this] { _inResize = false; });
|
||||
|
||||
if (_archive) {
|
||||
_archive->resizeToWidth(newWidth);
|
||||
if (_buttons) {
|
||||
_buttons->resizeToWidth(newWidth);
|
||||
}
|
||||
_list->resizeToWidth(newWidth);
|
||||
_empty->resizeToWidth(newWidth);
|
||||
|
@ -211,9 +294,9 @@ void InnerWidget::refreshHeight() {
|
|||
|
||||
int InnerWidget::recountHeight() {
|
||||
auto top = 0;
|
||||
if (_archive) {
|
||||
_archive->moveToLeft(0, top);
|
||||
top += _archive->heightNoMargins() - st::lineWidth;
|
||||
if (_buttons) {
|
||||
_buttons->moveToLeft(0, top);
|
||||
top += _buttons->heightNoMargins() - st::lineWidth;
|
||||
}
|
||||
auto listHeight = 0;
|
||||
if (_list) {
|
||||
|
|
|
@ -71,7 +71,7 @@ private:
|
|||
|
||||
const not_null<Controller*> _controller;
|
||||
|
||||
object_ptr<Ui::VerticalLayout> _archive = { nullptr };
|
||||
object_ptr<Ui::VerticalLayout> _buttons = { nullptr };
|
||||
object_ptr<Media::ListWidget> _list = { nullptr };
|
||||
object_ptr<EmptyWidget> _empty;
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ Type Provider::type() {
|
|||
}
|
||||
|
||||
bool Provider::hasSelectRestriction() {
|
||||
return false;
|
||||
return true; // #TODO stories
|
||||
}
|
||||
|
||||
rpl::producer<bool> Provider::hasSelectRestrictionChanges() {
|
||||
|
@ -292,22 +292,18 @@ std::unique_ptr<BaseLayout> Provider::createLayout(
|
|||
}
|
||||
return nullptr;
|
||||
};
|
||||
// #TODO stories
|
||||
const auto maybeStory = item->history()->owner().stories().lookup(
|
||||
{ item->history()->peer->id, StoryIdFromMsgId(item->id) });
|
||||
const auto spoiler = maybeStory && !(*maybeStory)->expired();
|
||||
|
||||
using namespace Overview::Layout;
|
||||
const auto options = MediaOptions{ .story = true };
|
||||
if (const auto photo = getPhoto()) {
|
||||
return std::make_unique<Photo>(delegate, item, photo, spoiler);
|
||||
return std::make_unique<Photo>(delegate, item, photo, options);
|
||||
} else if (const auto file = getFile()) {
|
||||
return std::make_unique<Video>(delegate, item, file, spoiler);
|
||||
return std::make_unique<Video>(delegate, item, file, options);
|
||||
} else {
|
||||
return std::make_unique<Photo>(
|
||||
delegate,
|
||||
item,
|
||||
Data::MediaStory::LoadingStoryPhoto(&item->history()->owner()),
|
||||
spoiler);
|
||||
options);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -316,9 +312,8 @@ ListItemSelectionData Provider::computeSelectionData(
|
|||
not_null<const HistoryItem*> item,
|
||||
TextSelection selection) {
|
||||
auto result = ListItemSelectionData(selection);
|
||||
result.canDelete = true;
|
||||
result.canForward = item->allowsForward()
|
||||
&& (&item->history()->session() == &_controller->session());
|
||||
result.canDelete = item->canDelete();
|
||||
result.canForward = item->allowsForward();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,12 +65,42 @@ TextParseOptions _documentNameOptions = {
|
|||
};
|
||||
|
||||
constexpr auto kMaxInlineArea = 1280 * 720;
|
||||
constexpr auto kStoryRatio = 1.46;
|
||||
|
||||
[[nodiscard]] bool CanPlayInline(not_null<DocumentData*> document) {
|
||||
const auto dimensions = document->dimensions;
|
||||
return dimensions.width() * dimensions.height() <= kMaxInlineArea;
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage CropMediaFrame(QImage image, int width, int height) {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
width *= ratio;
|
||||
height *= ratio;
|
||||
const auto finalize = [&](QImage result) {
|
||||
result = result.scaled(
|
||||
width,
|
||||
height,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
result.setDevicePixelRatio(ratio);
|
||||
return result;
|
||||
};
|
||||
if (image.width() * height == image.height() * width) {
|
||||
if (image.width() != width) {
|
||||
return finalize(std::move(image));
|
||||
}
|
||||
image.setDevicePixelRatio(ratio);
|
||||
return image;
|
||||
} else if (image.width() * height > image.height() * width) {
|
||||
const auto use = (image.height() * width) / height;
|
||||
const auto skip = (image.width() - use) / 2;
|
||||
return finalize(image.copy(skip, 0, use, image.height()));
|
||||
} else {
|
||||
const auto use = (image.width() * height) / width;
|
||||
const auto skip = (image.height() - use) / 2;
|
||||
return finalize(image.copy(0, skip, image.width(), use));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
@ -298,7 +328,7 @@ Photo::Photo(
|
|||
not_null<Delegate*> delegate,
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PhotoData*> photo,
|
||||
bool spoiler)
|
||||
MediaOptions options)
|
||||
: ItemBase(delegate, parent)
|
||||
, _data(photo)
|
||||
, _link(std::make_shared<PhotoOpenClickHandler>(
|
||||
|
@ -308,9 +338,10 @@ Photo::Photo(
|
|||
delegate->openPhoto(photo, id);
|
||||
}),
|
||||
parent->fullId()))
|
||||
, _spoiler(spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
, _spoiler(options.spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
delegate->repaintItem(this);
|
||||
}) : nullptr) {
|
||||
}) : nullptr)
|
||||
, _story(options.story) {
|
||||
if (_data->inlineThumbnailBytes().isEmpty()
|
||||
&& (_data->hasExact(Data::PhotoSize::Small)
|
||||
|| _data->hasExact(Data::PhotoSize::Thumbnail))) {
|
||||
|
@ -320,14 +351,14 @@ Photo::Photo(
|
|||
|
||||
void Photo::initDimensions() {
|
||||
_maxw = 2 * st::overviewPhotoMinSize;
|
||||
_minh = _maxw;
|
||||
_minh = _story ? qRound(_maxw * kStoryRatio) : _maxw;
|
||||
}
|
||||
|
||||
int32 Photo::resizeGetHeight(int32 width) {
|
||||
width = qMin(width, _maxw);
|
||||
if (width != _width || width != _height) {
|
||||
_width = qMin(width, _maxw);
|
||||
_height = _width;
|
||||
if (_width != width) {
|
||||
_width = width;
|
||||
_height = _story ? qRound(_width * kStoryRatio) : _width;
|
||||
}
|
||||
return _height;
|
||||
}
|
||||
|
@ -382,21 +413,14 @@ void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const
|
|||
}
|
||||
|
||||
void Photo::setPixFrom(not_null<Image*> image) {
|
||||
const auto size = _width * cIntRetinaFactor();
|
||||
Expects(_width > 0 && _height > 0);
|
||||
|
||||
auto img = image->original();
|
||||
if (!_goodLoaded) {
|
||||
img = Images::Blur(std::move(img));
|
||||
}
|
||||
if (img.width() == img.height()) {
|
||||
if (img.width() != size) {
|
||||
img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
}
|
||||
} else if (img.width() > img.height()) {
|
||||
img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
} else {
|
||||
img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
}
|
||||
img.setDevicePixelRatio(cRetinaFactor());
|
||||
_pix = Ui::PixmapFromImage(
|
||||
CropMediaFrame(std::move(img), _width, _height));
|
||||
|
||||
// In case we have inline thumbnail we can unload all images and we still
|
||||
// won't get a blank image in the media viewer when the photo is opened.
|
||||
|
@ -404,8 +428,6 @@ void Photo::setPixFrom(not_null<Image*> image) {
|
|||
_dataMedia = nullptr;
|
||||
delegate()->unregisterHeavyItem(this);
|
||||
}
|
||||
|
||||
_pix = Ui::PixmapFromImage(std::move(img));
|
||||
}
|
||||
|
||||
void Photo::ensureDataMediaCreated() const {
|
||||
|
@ -445,13 +467,14 @@ Video::Video(
|
|||
not_null<Delegate*> delegate,
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> video,
|
||||
bool spoiler)
|
||||
MediaOptions options)
|
||||
: RadialProgressItem(delegate, parent)
|
||||
, _data(video)
|
||||
, _duration(Ui::FormatDurationText(_data->duration() / 1000))
|
||||
, _spoiler(spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
, _spoiler(options.spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
|
||||
delegate->repaintItem(this);
|
||||
}) : nullptr) {
|
||||
}) : nullptr)
|
||||
, _story(options.story) {
|
||||
setDocumentLinks(_data);
|
||||
_data->loadThumbnail(parent->fullId());
|
||||
}
|
||||
|
@ -460,12 +483,15 @@ Video::~Video() = default;
|
|||
|
||||
void Video::initDimensions() {
|
||||
_maxw = 2 * st::overviewPhotoMinSize;
|
||||
_minh = _maxw;
|
||||
_minh = _story ? qRound(_maxw * kStoryRatio) : _maxw;
|
||||
}
|
||||
|
||||
int32 Video::resizeGetHeight(int32 width) {
|
||||
_width = qMin(width, _maxw);
|
||||
_height = _width;
|
||||
width = qMin(width, _maxw);
|
||||
if (_width != width) {
|
||||
_width = width;
|
||||
_height = _story ? qRound(_width * kStoryRatio) : _width;
|
||||
}
|
||||
return _height;
|
||||
}
|
||||
|
||||
|
@ -497,18 +523,8 @@ void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const
|
|||
: thumbnail
|
||||
? thumbnail->original()
|
||||
: Images::Blur(blurred->original());
|
||||
if (img.width() == img.height()) {
|
||||
if (img.width() != size) {
|
||||
img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
}
|
||||
} else if (img.width() > img.height()) {
|
||||
img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
} else {
|
||||
img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||
}
|
||||
img.setDevicePixelRatio(cRetinaFactor());
|
||||
|
||||
_pix = Ui::PixmapFromImage(std::move(img));
|
||||
_pix = Ui::PixmapFromImage(
|
||||
CropMediaFrame(std::move(img), _width, _height));
|
||||
_pixBlurred = !(thumbnail || good);
|
||||
}
|
||||
|
||||
|
|
|
@ -183,13 +183,18 @@ struct Info : public RuntimeComponent<Info, LayoutItemBase> {
|
|||
int top = 0;
|
||||
};
|
||||
|
||||
struct MediaOptions {
|
||||
bool spoiler = false;
|
||||
bool story = false;
|
||||
};
|
||||
|
||||
class Photo final : public ItemBase {
|
||||
public:
|
||||
Photo(
|
||||
not_null<Delegate*> delegate,
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<PhotoData*> photo,
|
||||
bool spoiler);
|
||||
MediaOptions options);
|
||||
|
||||
void initDimensions() override;
|
||||
int32 resizeGetHeight(int32 width) override;
|
||||
|
@ -212,6 +217,7 @@ private:
|
|||
|
||||
QPixmap _pix;
|
||||
bool _goodLoaded = false;
|
||||
bool _story = false;
|
||||
|
||||
};
|
||||
|
||||
|
@ -279,7 +285,7 @@ public:
|
|||
not_null<Delegate*> delegate,
|
||||
not_null<HistoryItem*> parent,
|
||||
not_null<DocumentData*> video,
|
||||
bool spoiler);
|
||||
MediaOptions options);
|
||||
~Video();
|
||||
|
||||
void initDimensions() override;
|
||||
|
@ -311,6 +317,7 @@ private:
|
|||
|
||||
QPixmap _pix;
|
||||
bool _pixBlurred = true;
|
||||
bool _story = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -2530,7 +2530,7 @@ void SessionController::openPeerStory(
|
|||
|
||||
void SessionController::openPeerStories(
|
||||
PeerId peerId,
|
||||
Data::StorySourcesList list) {
|
||||
std::optional<Data::StorySourcesList> list) {
|
||||
using namespace Media::View;
|
||||
using namespace Data;
|
||||
|
||||
|
@ -2541,7 +2541,9 @@ void SessionController::openPeerStories(
|
|||
openPeerStory(
|
||||
source->user,
|
||||
j != source->ids.end() ? j->id : source->ids.front().id,
|
||||
{ list });
|
||||
(list
|
||||
? StoriesContext{ *list }
|
||||
: StoriesContext{ StoriesContextPeer() }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -582,7 +582,9 @@ public:
|
|||
not_null<PeerData*> peer,
|
||||
StoryId storyId,
|
||||
Data::StoriesContext context);
|
||||
void openPeerStories(PeerId peerId, Data::StorySourcesList list);
|
||||
void openPeerStories(
|
||||
PeerId peerId,
|
||||
std::optional<Data::StorySourcesList> list = std::nullopt);
|
||||
|
||||
struct PaintContextArgs {
|
||||
not_null<Ui::ChatTheme*> theme;
|
||||
|
|
Loading…
Add table
Reference in a new issue