Improve sticker by emoji ordering.

First display recent by send/install date, then trending, then other.
This commit is contained in:
John Preston 2018-03-08 00:25:03 +03:00
parent ccef155f7a
commit 90179188b9
7 changed files with 178 additions and 54 deletions

View file

@ -2200,6 +2200,10 @@ void ApiWrap::updateStickers() {
requestSavedGifs(now);
}
void ApiWrap::requestRecentStickersForce() {
requestRecentStickersWithHash(0);
}
void ApiWrap::setGroupStickerSet(not_null<ChannelData*> megagroup, const MTPInputStickerSet &set) {
Expects(megagroup->mgInfo != nullptr);
@ -2283,13 +2287,19 @@ void ApiWrap::requestStickers(TimeId now) {
}
void ApiWrap::requestRecentStickers(TimeId now) {
if (!_session->data().recentStickersUpdateNeeded(now)
|| _recentStickersUpdateRequest) {
if (!_session->data().recentStickersUpdateNeeded(now)) {
return;
}
requestRecentStickersWithHash(Local::countRecentStickersHash());
}
void ApiWrap::requestRecentStickersWithHash(int32 hash) {
if (_recentStickersUpdateRequest) {
return;
}
_recentStickersUpdateRequest = request(MTPmessages_GetRecentStickers(
MTP_flags(0),
MTP_int(Local::countRecentStickersHash())
MTP_int(hash)
)).done([=](const MTPmessages_RecentStickers &result) {
_session->data().setLastRecentStickersUpdate(getms(true));
_recentStickersUpdateRequest = 0;

View file

@ -126,6 +126,7 @@ public:
const Stickers::Order &localOrder,
const Stickers::Order &localRemoved);
void updateStickers();
void requestRecentStickersForce();
void setGroupStickerSet(
not_null<ChannelData*> megagroup,
const MTPInputStickerSet &set);
@ -345,6 +346,7 @@ private:
void requestStickers(TimeId now);
void requestRecentStickers(TimeId now);
void requestRecentStickersWithHash(int32 hash);
void requestFavedStickers(TimeId now);
void requestFeaturedStickers(TimeId now);
void requestSavedGifs(TimeId now);

View file

@ -101,7 +101,7 @@ void FieldAutocomplete::showStickers(EmojiPtr emoji) {
_emoji = emoji;
_type = Type::Stickers;
if (!emoji) {
rowsUpdated(_mrows, _hrows, _brows, Stickers::Pack(), false);
rowsUpdated(_mrows, _hrows, _brows, internal::StickerRows(), false);
return;
}
@ -135,9 +135,9 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
internal::MentionRows mrows;
internal::HashtagRows hrows;
internal::BotCommandRows brows;
Stickers::Pack srows;
internal::StickerRows srows;
if (_emoji) {
srows = Stickers::GetListByEmoji(_emoji);
srows = Stickers::GetListByEmoji(_emoji, _stickersSeed);
} else if (_type == Type::Mentions) {
int maxListSize = _addInlineBots ? cRecentInlineBots().size() : 0;
if (_chat) {
@ -319,8 +319,8 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
_inner->setRecentInlineBotsInRows(recentInlineBots);
}
void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const Stickers::Pack &srows, bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) {
void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const internal::StickerRows &srows, bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.empty()) {
if (!isHidden()) {
hideAnimated();
}
@ -356,7 +356,7 @@ void FieldAutocomplete::setBoundings(QRect boundings) {
void FieldAutocomplete::recount(bool resetScroll) {
int32 h = 0, oldst = _scroll->scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight;
if (!_srows.isEmpty()) {
if (!_srows.empty()) {
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
int32 rows = rowscount(_srows.size(), stickersPerRow);
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
@ -416,6 +416,7 @@ void FieldAutocomplete::showAnimated() {
return;
}
if (_cache.isNull()) {
_stickersSeed = rand_value<uint64>();
_scroll->show();
_cache = Ui::GrabWidget(this);
}
@ -477,7 +478,7 @@ bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
QKeyEvent *ev = static_cast<QKeyEvent*>(e);
if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) {
if (!hidden) {
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.empty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
return _inner->moveSel(ev->key());
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
return _inner->chooseSelected(ChooseMethod::ByEnter);
@ -498,7 +499,7 @@ FieldAutocomplete::~FieldAutocomplete() {
namespace internal {
FieldAutocompleteInner::FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, Stickers::Pack *srows)
FieldAutocompleteInner::FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerRows *srows)
: _parent(parent)
, _mrows(mrows)
, _hrows(hrows)
@ -527,7 +528,7 @@ void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
int32 htagleft = st::historyAttach.width + st::historyComposeField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
if (!_srows->isEmpty()) {
if (!_srows->empty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
@ -697,7 +698,7 @@ bool FieldAutocompleteInner::moveSel(int key) {
_mouseSel = false;
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
if (!_srows->isEmpty()) {
if (!_srows->empty()) {
if (key == Qt::Key_Left) {
direction = -1;
} else if (key == Qt::Key_Right) {
@ -721,7 +722,7 @@ bool FieldAutocompleteInner::moveSel(int key) {
}
bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod method) const {
if (!_srows->isEmpty()) {
if (!_srows->empty()) {
if (_sel >= 0 && _sel < _srows->size()) {
emit stickerChosen(_srows->at(_sel), method);
return true;
@ -791,7 +792,7 @@ void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
_mouseSel = true;
onUpdateSelected(true);
} else if (_srows->isEmpty()) {
} else if (_srows->empty()) {
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
} else {
_down = _sel;
@ -815,7 +816,7 @@ void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
return;
}
if (_sel < 0 || _sel != pressed || _srows->isEmpty()) return;
if (_sel < 0 || _sel != pressed || _srows->empty()) return;
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
}
@ -835,7 +836,7 @@ void FieldAutocompleteInner::leaveEventHook(QEvent *e) {
void FieldAutocompleteInner::updateSelectedRow() {
if (_sel >= 0) {
if (_srows->isEmpty()) {
if (_srows->empty()) {
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
} else {
int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow;
@ -850,7 +851,7 @@ void FieldAutocompleteInner::setSel(int sel, bool scroll) {
updateSelectedRow();
if (scroll && _sel >= 0) {
if (_srows->isEmpty()) {
if (_srows->empty()) {
emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
} else {
int32 row = _sel / _stickersPerRow;
@ -866,7 +867,7 @@ void FieldAutocompleteInner::onUpdateSelected(bool force) {
if (_down >= 0 && !_previewShown) return;
int32 sel = -1, maxSel = 0;
if (!_srows->isEmpty()) {
if (!_srows->empty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1;
int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1;

View file

@ -19,6 +19,7 @@ namespace internal {
using MentionRows = QList<UserData*>;
using HashtagRows = QList<QString>;
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
using StickerRows = std::vector<not_null<DocumentData*>>;
class FieldAutocompleteInner;
@ -53,7 +54,7 @@ public:
bool chooseSelected(ChooseMethod method) const;
bool stickersShown() const {
return !_srows.isEmpty();
return !_srows.empty();
}
bool overlaps(const QRect &globalRect) {
@ -92,9 +93,9 @@ private:
internal::MentionRows _mrows;
internal::HashtagRows _hrows;
internal::BotCommandRows _brows;
Stickers::Pack _srows;
internal::StickerRows _srows;
void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const Stickers::Pack &srows, bool resetScroll);
void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const internal::StickerRows &srows, bool resetScroll);
object_ptr<Ui::ScrollArea> _scroll;
QPointer<internal::FieldAutocompleteInner> _inner;
@ -103,6 +104,7 @@ private:
UserData *_user = nullptr;
ChannelData *_channel = nullptr;
EmojiPtr _emoji;
uint64 _stickersSeed = 0;
enum class Type {
Mentions,
Hashtags,
@ -129,7 +131,7 @@ class FieldAutocompleteInner final : public TWidget, private base::Subscriber {
Q_OBJECT
public:
FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, Stickers::Pack *srows);
FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerRows *srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
@ -167,7 +169,7 @@ private:
MentionRows *_mrows;
HashtagRows *_hrows;
BotCommandRows *_brows;
Stickers::Pack *_srows;
StickerRows *_srows;
int32 _stickersPerRow, _recentInlineBotsInRows;
int32 _sel, _down;
bool _mouseSel;

View file

@ -668,19 +668,84 @@ void GifsReceived(const QVector<MTPDocument> &items, int32 hash) {
Auth().data().notifySavedGifsUpdated();
}
Pack GetListByEmoji(not_null<EmojiPtr> emoji) {
auto original = emoji->original();
auto result = Pack();
auto setsToRequest = QMap<uint64, uint64>();
auto &sets = Auth().data().stickerSetsRef();
std::vector<not_null<DocumentData*>> GetListByEmoji(
not_null<EmojiPtr> emoji,
uint64 seed) {
const auto original = emoji->original();
auto faved = Pack();
auto favedIt = sets.find(Stickers::FavedSetId);
if (favedIt != sets.cend()) {
auto i = favedIt->emoji.constFind(original);
if (i != favedIt->emoji.cend()) {
faved = *i;
result = faved;
struct StickerWithDate {
not_null<DocumentData*> document;
TimeId date = 0;
};
auto result = std::vector<StickerWithDate>();
auto &sets = Auth().data().stickerSetsRef();
auto setsToRequest = base::flat_map<uint64, uint64>();
const auto add = [&](not_null<DocumentData*> document, TimeId date) {
if (ranges::find(result, document, [](const StickerWithDate &data) {
return data.document;
}) == result.end()) {
result.push_back({ document, date });
}
};
constexpr auto kSlice = 65536;
const auto CreateSortKey = [&](
not_null<DocumentData*> document,
int base) {
return TimeId(base + int((document->id ^ seed) % kSlice));
};
const auto CreateRecentSortKey = [&](not_null<DocumentData*> document) {
return CreateSortKey(document, kSlice * 4);
};
auto myCounter = 0;
const auto CreateMySortKey = [&] {
return (kSlice * 4 - (++myCounter));
};
const auto CreateFeaturedSortKey = [&](not_null<DocumentData*> document) {
return CreateSortKey(document, kSlice * 2);
};
const auto CreateOtherSortKey = [&](not_null<DocumentData*> document) {
return CreateSortKey(document, kSlice);
};
const auto InstallDate = [&](not_null<DocumentData*> document) {
Expects(document->sticker() != nullptr);
const auto sticker = document->sticker();
if (sticker->set.type() == mtpc_inputStickerSetID) {
const auto setId = sticker->set.c_inputStickerSetID().vid.v;
const auto setIt = sets.find(setId);
if (setIt != sets.end()) {
return setIt->installDate;
}
}
return TimeId(0);
};
auto recentIt = sets.find(Stickers::CloudRecentSetId);
if (recentIt != sets.cend()) {
auto i = recentIt->emoji.constFind(original);
if (i != recentIt->emoji.cend()) {
result.reserve(i->size());
for (const auto document : *i) {
const auto usageDate = [&] {
if (recentIt->dates.empty()) {
return TimeId(0);
}
const auto index = recentIt->stickers.indexOf(document);
if (index < 0) {
return TimeId(0);
}
Assert(index < recentIt->dates.size());
return recentIt->dates[index];
}();
const auto date = usageDate
? usageDate
: InstallDate(document);
result.push_back({
document,
date ? date : CreateRecentSortKey(document) });
}
}
}
const auto addList = [&](const Order &order, MTPDstickerSet::Flag skip) {
@ -690,7 +755,7 @@ Pack GetListByEmoji(not_null<EmojiPtr> emoji) {
continue;
}
if (it->emoji.isEmpty()) {
setsToRequest.insert(it->id, it->access);
setsToRequest.emplace(it->id, it->access);
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
continue;
}
@ -698,11 +763,16 @@ Pack GetListByEmoji(not_null<EmojiPtr> emoji) {
if (i == it->emoji.cend()) {
continue;
}
const auto my = (it->flags & MTPDstickerSet::Flag::f_installed_date);
result.reserve(result.size() + i->size());
for_const (const auto document, *i) {
if (!faved.contains(document)) {
result.push_back(document);
}
for (const auto document : *i) {
const auto installDate = my ? it->installDate : TimeId(0);
const auto date = (installDate > 1)
? installDate
: my
? CreateMySortKey()
: CreateFeaturedSortKey(document);
add(document, date);
}
}
};
@ -714,21 +784,32 @@ Pack GetListByEmoji(not_null<EmojiPtr> emoji) {
Auth().data().featuredStickerSetsOrder(),
MTPDstickerSet::Flag::f_installed_date);
if (!setsToRequest.isEmpty()) {
for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
Auth().api().scheduleStickerSetRequest(i.key(), i.value());
if (!setsToRequest.empty()) {
for (const auto [setId, accessHash] : setsToRequest) {
Auth().api().scheduleStickerSetRequest(setId, accessHash);
}
Auth().api().requestStickerSets();
}
if (const auto pack = Auth().api().stickersByEmoji(original)) {
for (const auto document : *pack) {
if (!base::contains(result, document)) {
result.push_back(document);
}
}
return result;
const auto others = Auth().api().stickersByEmoji(original);
if (!others) {
return {};
}
return Pack();
result.reserve(result.size() + others->size());
for (const auto document : *others) {
add(document, CreateOtherSortKey(document));
}
ranges::action::sort(
result,
std::greater<>(),
[](const StickerWithDate &data) { return data.date; });
return ranges::view::all(
result
) | ranges::view::transform([](const StickerWithDate &data) {
return data.document;
}) | ranges::to_vector;
}
base::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet(

View file

@ -86,7 +86,9 @@ void FeaturedSetsReceived(
int32 hash);
void GifsReceived(const QVector<MTPDocument> &items, int32 hash);
Pack GetListByEmoji(not_null<EmojiPtr> emoji);
std::vector<not_null<DocumentData*>> GetListByEmoji(
not_null<EmojiPtr> emoji,
uint64 seed);
base::optional<std::vector<not_null<EmojiPtr>>> GetEmojiListFromSet(
not_null<DocumentData*> document);

View file

@ -4058,19 +4058,45 @@ void MainWidget::incrementSticker(DocumentData *sticker) {
it->title = lang(lng_recent_stickers);
}
}
auto removedFromEmoji = std::vector<not_null<EmojiPtr>>();
auto index = it->stickers.indexOf(sticker);
if (index > 0) {
if (!it->dates.empty()) {
if (it->dates.empty()) {
Auth().api().requestRecentStickersForce();
} else {
Assert(it->dates.size() == it->stickers.size());
it->dates.erase(it->dates.begin() + index);
}
it->stickers.removeAt(index);
for (auto i = it->emoji.begin(); i != it->emoji.end();) {
if (const auto index = i->indexOf(sticker); index >= 0) {
removedFromEmoji.push_back(i.key());
i->removeAt(index);
if (i->isEmpty()) {
i = it->emoji.erase(i);
continue;
}
}
++i;
}
}
if (index) {
if (it->dates.size() == it->stickers.size()) {
it->dates.insert(it->dates.begin(), unixtime());
}
it->stickers.push_front(sticker);
if (const auto emojiList = Stickers::GetEmojiListFromSet(sticker)) {
for (const auto emoji : *emojiList) {
it->emoji[emoji].push_front(sticker);
}
} else if (!removedFromEmoji.empty()) {
for (const auto emoji : removedFromEmoji) {
it->emoji[emoji].push_front(sticker);
}
} else {
Auth().api().requestRecentStickersForce();
}
writeRecentStickers = true;
}