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