Improve saved / archive stories design.

This commit is contained in:
John Preston 2023-06-19 21:00:34 +04:00
parent 119ee6044a
commit e98770d418
29 changed files with 660 additions and 202 deletions

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

View File

@ -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.";

View File

@ -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),

View File

@ -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) {

View File

@ -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.;
}

View File

@ -142,6 +142,7 @@ InnerWidget::InnerWidget(
, _controller(controller)
, _stories(std::make_unique<Stories::List>(
this,
st::dialogsStoriesList,
Stories::ContentForSession(
&controller->session(),
Data::StorySourcesList::NotHidden),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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) {

View File

@ -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 }};

View File

@ -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()");
}();

View File

@ -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([=] {

View File

@ -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) {

View File

@ -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;

View File

@ -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:

View 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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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() }));
}
}

View File

@ -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;