mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
scrollTop counted relative to the last scrollTop item, attachedToPrevious messages support added, service messages now display date and unreadbar
This commit is contained in:
parent
a2f5acdff1
commit
84e67b8ec0
Telegram
Resources
SourceFiles
@ -1049,10 +1049,11 @@ msgServiceNameFont: semiboldFont;
|
||||
msgServicePhotoWidth: 100px;
|
||||
msgDateFont: font(13px);
|
||||
msgMinWidth: 190px;
|
||||
msgPhotoSize: 30px;
|
||||
msgPhotoSize: 35px;
|
||||
msgPhotoSkip: 40px;
|
||||
msgPadding: margins(13px, 7px, 13px, 8px);
|
||||
msgMargin: margins(13px, 4px, 53px, 4px);
|
||||
msgMargin: margins(13px, 6px, 53px, 2px);
|
||||
msgMarginTopAttached: 2px;
|
||||
msgLnkPadding: 2px; // for media open / save links
|
||||
msgBorder: #f0f0f0;
|
||||
msgInBg: #fff;
|
||||
|
@ -101,6 +101,9 @@ enum {
|
||||
MediaOverviewStartPerPage = 5,
|
||||
MediaOverviewPreloadCount = 4,
|
||||
|
||||
// a new message from the same sender is attached to previous within 30 seconds
|
||||
AttachMessageToPreviousSecondsDelta = 30,
|
||||
|
||||
AudioVoiceMsgSimultaneously = 4,
|
||||
AudioSongSimultaneously = 4,
|
||||
AudioCheckPositionTimeout = 100, // 100ms per check audio pos
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -303,7 +303,7 @@ public:
|
||||
MsgId maxMsgId() const;
|
||||
MsgId msgIdForRead() const;
|
||||
|
||||
int resize(int newWidth, int32 *ytransform = nullptr); // return new size
|
||||
int resizeGetHeight(int newWidth);
|
||||
|
||||
void removeNotification(HistoryItem *item) {
|
||||
if (!notifies.isEmpty()) {
|
||||
@ -373,8 +373,38 @@ public:
|
||||
editDraft = draft;
|
||||
}
|
||||
|
||||
int32 lastWidth, lastScrollTop;
|
||||
MsgId lastShowAtMsgId;
|
||||
// some fields below are a property of a currently displayed instance of this
|
||||
// conversation history not a property of the conversation history itself
|
||||
public:
|
||||
// we save the last showAtMsgId to restore the state when switching
|
||||
// between different conversation histories
|
||||
MsgId showAtMsgId;
|
||||
|
||||
// we save a pointer of the history item at the top of the displayed window
|
||||
// together with an offset from the window top to the top of this message
|
||||
// resulting scrollTop = top(scrollTopItem) + scrollTopOffset
|
||||
HistoryItem *scrollTopItem;
|
||||
int scrollTopOffset;
|
||||
void forgetScrollState() {
|
||||
scrollTopItem = nullptr;
|
||||
}
|
||||
|
||||
// find the correct scrollTopItem and scrollTopOffset using given top
|
||||
// of the displayed window relative to the history start coord
|
||||
void countScrollState(int top);
|
||||
|
||||
protected:
|
||||
// when this item is destroyed scrollTopItem just points to the next one
|
||||
// and scrollTopOffset remains the same
|
||||
// if we are at the bottom of the window scrollTopItem == nullptr and
|
||||
// scrollTopOffset is undefined
|
||||
void getNextScrollTopItem(HistoryBlock *block, int32 i);
|
||||
|
||||
// helper method for countScrollState(int top)
|
||||
void countScrollTopItem(int top);
|
||||
|
||||
public:
|
||||
|
||||
bool mute;
|
||||
|
||||
bool lastKeyboardInited, lastKeyboardUsed;
|
||||
@ -468,10 +498,10 @@ private:
|
||||
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption);
|
||||
|
||||
HistoryItem *addItemAfterPrevToBlock(HistoryItem *item, HistoryItem *prev, HistoryBlock *block);
|
||||
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
|
||||
HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
|
||||
HistoryItem *addMessageGroupAfterPrevToBlock(const MTPDmessageGroup &group, HistoryItem *prev, HistoryBlock *block);
|
||||
HistoryItem *addMessageGroupAfterPrev(HistoryItem *newItem, HistoryItem *prev);
|
||||
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
|
||||
|
||||
History(const History &) = delete;
|
||||
History &operator=(const History &) = delete;
|
||||
@ -844,16 +874,24 @@ public:
|
||||
}
|
||||
void removeItem(HistoryItem *item);
|
||||
|
||||
int resize(int newWidth, int *ytransform, bool force); // return new size
|
||||
int resizeGetHeight(int newWidth, bool resizeAllItems);
|
||||
int32 y, height;
|
||||
History *history;
|
||||
|
||||
HistoryBlock *previous() const {
|
||||
t_assert(_indexInHistory >= 0);
|
||||
|
||||
return (_indexInHistory > 0) ? history->blocks.at(_indexInHistory - 1) : nullptr;
|
||||
}
|
||||
void setIndexInHistory(int index) {
|
||||
_indexInHistory = index;
|
||||
}
|
||||
int indexInHistory() const {
|
||||
t_assert(_indexInHistory >= 0);
|
||||
t_assert(history->blocks.at(_indexInHistory) == this);
|
||||
|
||||
return _indexInHistory;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@ -1002,7 +1040,7 @@ public:
|
||||
HistoryItem(const HistoryItem &) = delete;
|
||||
HistoryItem &operator=(const HistoryItem &) = delete;
|
||||
|
||||
int resize(int width) {
|
||||
int resizeGetHeight(int width) {
|
||||
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_pending_init_dimensions;
|
||||
initDimensions();
|
||||
@ -1010,7 +1048,7 @@ public:
|
||||
if (_flags & MTPDmessage_ClientFlag::f_pending_resize) {
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_pending_resize;
|
||||
}
|
||||
return resizeImpl(width);
|
||||
return resizeGetHeight_(width);
|
||||
}
|
||||
virtual void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const = 0;
|
||||
|
||||
@ -1060,6 +1098,16 @@ public:
|
||||
void setIndexInBlock(int index) {
|
||||
_indexInBlock = index;
|
||||
}
|
||||
int indexInBlock() const {
|
||||
if (_indexInBlock >= 0) {
|
||||
t_assert(_block != nullptr);
|
||||
t_assert(_block->items.at(_indexInBlock) == this);
|
||||
} else if (_block != nullptr) {
|
||||
t_assert(_indexInBlock >= 0);
|
||||
t_assert(_block->items.at(_indexInBlock) == this);
|
||||
}
|
||||
return _indexInBlock;
|
||||
}
|
||||
bool out() const {
|
||||
return _flags & MTPDmessage::Flag::f_out;
|
||||
}
|
||||
@ -1163,9 +1211,6 @@ public:
|
||||
virtual void setViewsCount(int32 count) {
|
||||
}
|
||||
virtual void setId(MsgId newId);
|
||||
virtual void setDate(const QDateTime &date) { // for date items
|
||||
this->date = date;
|
||||
}
|
||||
virtual void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const = 0;
|
||||
virtual QString notificationHeader() const {
|
||||
return QString();
|
||||
@ -1272,7 +1317,6 @@ public:
|
||||
PeerData *author() const {
|
||||
return isPost() ? history()->peer : _from;
|
||||
}
|
||||
bool displayFromPhoto() const;
|
||||
|
||||
PeerData *fromOriginal() const {
|
||||
if (const HistoryMessageForwarded *fwd = Get<HistoryMessageForwarded>()) {
|
||||
@ -1315,6 +1359,29 @@ public:
|
||||
setPendingResize();
|
||||
}
|
||||
|
||||
int displayedDateHeight() const {
|
||||
if (auto *date = Get<HistoryMessageDate>()) {
|
||||
return date->height();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int marginTop() const {
|
||||
int result = 0;
|
||||
if (isAttachedToPrevious()) {
|
||||
result += st::msgMarginTopAttached;
|
||||
} else {
|
||||
result += st::msgMargin.top();
|
||||
}
|
||||
result += displayedDateHeight();
|
||||
if (auto *unreadbar = Get<HistoryMessageUnreadBar>()) {
|
||||
result += unreadbar->height();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int marginBottom() const {
|
||||
return st::msgMargin.bottom();
|
||||
}
|
||||
|
||||
void clipCallback(ClipReaderNotification notification);
|
||||
|
||||
virtual ~HistoryItem();
|
||||
@ -1327,9 +1394,10 @@ protected:
|
||||
// a virtual method, it can not be done from constructor
|
||||
virtual void finishCreate();
|
||||
|
||||
// called from resize() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set
|
||||
// called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set
|
||||
virtual void initDimensions() = 0;
|
||||
virtual int resizeImpl(int width) = 0;
|
||||
|
||||
virtual int resizeGetHeight_(int width) = 0;
|
||||
|
||||
PeerData *_from;
|
||||
History *_history;
|
||||
@ -1359,6 +1427,19 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
// this should be used only in previousItemChanged() or when
|
||||
// HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Interfaces mask
|
||||
// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous
|
||||
void recountAttachToPrevious();
|
||||
bool isAttachedToPrevious() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||
}
|
||||
|
||||
// hasFromPhoto() returns true even if we don't display the photo
|
||||
// but we need to skip a place at the left side for this photo
|
||||
bool displayFromPhoto() const;
|
||||
bool hasFromPhoto() const;
|
||||
|
||||
};
|
||||
|
||||
// make all the constructors in HistoryItem children protected
|
||||
@ -2313,7 +2394,10 @@ public:
|
||||
return drawBubble();
|
||||
}
|
||||
bool displayFromName() const {
|
||||
return hasFromName() && (!emptyText() || !_media || !_media->isDisplayed() || toHistoryReply() || Is<HistoryMessageForwarded>() || viaBot() || !_media->hideFromName());
|
||||
if (!hasFromName()) return false;
|
||||
if (isAttachedToPrevious()) return false;
|
||||
|
||||
return (!emptyText() || !_media || !_media->isDisplayed() || toHistoryReply() || Is<HistoryMessageForwarded>() || viaBot() || !_media->hideFromName());
|
||||
}
|
||||
bool uploading() const {
|
||||
return _media && _media->uploading();
|
||||
@ -2420,7 +2504,7 @@ protected:
|
||||
friend class HistoryItemInstantiated<HistoryMessage>;
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeImpl(int width) override;
|
||||
int resizeGetHeight_(int width) override;
|
||||
|
||||
void createInterfaces(int32 viaBotId, int32 viewsCount, const PeerId &authorIdOriginal = 0, const PeerId &fromIdOriginal = 0, MsgId originalId = 0);
|
||||
|
||||
@ -2473,7 +2557,6 @@ public:
|
||||
void drawReplyTo(Painter &p, int32 x, int32 y, int32 w, bool selected, bool likeService = false) const;
|
||||
void drawMessageText(Painter &p, QRect trect, uint32 selection) const override;
|
||||
void resizeVia(int32 w) const;
|
||||
bool hasPoint(int32 x, int32 y) const override;
|
||||
void getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const override;
|
||||
void getStateFromMessageText(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const QRect &r) const override;
|
||||
void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const override;
|
||||
@ -2501,7 +2584,7 @@ protected:
|
||||
friend class HistoryItemInstantiated<HistoryReply>;
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeImpl(int width) override;
|
||||
int resizeGetHeight_(int width) override;
|
||||
|
||||
bool updateReplyTo(bool force = false);
|
||||
void replyToNameUpdated() const;
|
||||
@ -2555,13 +2638,13 @@ struct HistoryServicePinned : public BasicInterface<HistoryServicePinned> {
|
||||
TextLinkPtr lnk;
|
||||
};
|
||||
|
||||
class HistoryServiceMsg : public HistoryItem, private HistoryItemInstantiated<HistoryServiceMsg> {
|
||||
class HistoryServiceMessage : public HistoryItem, private HistoryItemInstantiated<HistoryServiceMessage> {
|
||||
public:
|
||||
|
||||
static HistoryServiceMsg *create(History *history, const MTPDmessageService &msg) {
|
||||
static HistoryServiceMessage *create(History *history, const MTPDmessageService &msg) {
|
||||
return _create(history, msg);
|
||||
}
|
||||
static HistoryServiceMsg *create(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, HistoryMedia *media = 0, int32 from = 0) {
|
||||
static HistoryServiceMessage *create(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, HistoryMedia *media = 0, int32 from = 0) {
|
||||
return _create(history, msgId, date, msg, flags, media, from);
|
||||
}
|
||||
|
||||
@ -2615,16 +2698,16 @@ public:
|
||||
|
||||
void setServiceText(const QString &text);
|
||||
|
||||
~HistoryServiceMsg();
|
||||
~HistoryServiceMessage();
|
||||
|
||||
protected:
|
||||
|
||||
HistoryServiceMsg(History *history, const MTPDmessageService &msg);
|
||||
HistoryServiceMsg(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, HistoryMedia *media = 0, int32 from = 0);
|
||||
friend class HistoryItemInstantiated<HistoryServiceMsg>;
|
||||
HistoryServiceMessage(History *history, const MTPDmessageService &msg);
|
||||
HistoryServiceMessage(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, HistoryMedia *media = 0, int32 from = 0);
|
||||
friend class HistoryItemInstantiated<HistoryServiceMessage>;
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeImpl(int width) override;
|
||||
int resizeGetHeight_(int width) override;
|
||||
|
||||
void setMessageByAction(const MTPmessageAction &action);
|
||||
bool updatePinned(bool force = false);
|
||||
@ -2636,7 +2719,7 @@ protected:
|
||||
int32 _textWidth, _textHeight;
|
||||
};
|
||||
|
||||
class HistoryGroup : public HistoryServiceMsg, private HistoryItemInstantiated<HistoryGroup> {
|
||||
class HistoryGroup : public HistoryServiceMessage, private HistoryItemInstantiated<HistoryGroup> {
|
||||
public:
|
||||
|
||||
static HistoryGroup *create(History *history, const MTPDmessageGroup &group, const QDateTime &date) {
|
||||
@ -2692,7 +2775,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class HistoryCollapse : public HistoryServiceMsg, private HistoryItemInstantiated<HistoryCollapse> {
|
||||
class HistoryCollapse : public HistoryServiceMessage, private HistoryItemInstantiated<HistoryCollapse> {
|
||||
public:
|
||||
|
||||
static HistoryCollapse *create(History *history, MsgId wasMinId, const QDateTime &date) {
|
||||
@ -2727,7 +2810,7 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class HistoryJoined : public HistoryServiceMsg, private HistoryItemInstantiated<HistoryJoined> {
|
||||
class HistoryJoined : public HistoryServiceMessage, private HistoryItemInstantiated<HistoryJoined> {
|
||||
public:
|
||||
|
||||
static HistoryJoined *create(History *history, const QDateTime &date, UserData *from, MTPDmessage::Flags flags) {
|
||||
|
@ -926,8 +926,6 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||
bool canForward = (item && item->type() == HistoryItemMsg) && (item->id > 0) && !item->serviceMsg();
|
||||
|
||||
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
|
||||
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
|
||||
|
||||
if (isUponSelected > 0) {
|
||||
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
|
||||
if (item && item->id > 0 && isUponSelected != 2) {
|
||||
@ -1211,40 +1209,37 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
int32 HistoryInner::recountHeight() {
|
||||
int32 htop = historyTop(), mtop = migratedTop();
|
||||
int32 st1 = (htop >= 0) ? (_history->lastScrollTop - htop) : -1, st2 = (_migrated && mtop >= 0) ? (_history->lastScrollTop - mtop) : -1;
|
||||
void HistoryInner::recountHeight() {
|
||||
int htop = historyTop(), mtop = migratedTop();
|
||||
|
||||
int32 ph = _scroll->height(), minadd = 0;
|
||||
int32 wasYSkip = ph - historyHeight() - st::historyPadding;
|
||||
int ph = _scroll->height(), minadd = 0;
|
||||
int wasYSkip = ph - historyHeight() - st::historyPadding;
|
||||
if (_botInfo && !_botInfo->text.isEmpty()) {
|
||||
minadd = st::msgMargin.top() + st::msgMargin.bottom() + st::msgPadding.top() + st::msgPadding.bottom() + st::msgNameFont->height + st::botDescSkip + _botDescHeight;
|
||||
}
|
||||
if (wasYSkip < minadd) wasYSkip = minadd;
|
||||
|
||||
_history->resize(_scroll->width(), &st1);
|
||||
_history->resizeGetHeight(_scroll->width());
|
||||
if (_migrated) {
|
||||
_migrated->resize(_scroll->width(), &st2);
|
||||
_migrated->resizeGetHeight(_scroll->width());
|
||||
}
|
||||
|
||||
int32 skip = 0;
|
||||
if (_migrated) { // check first messages of _history - maybe no need to display them
|
||||
// with migrated history we perhaps do not need to display first _history message
|
||||
// (if last _migrated message and first _history message are both isGroupMigrate)
|
||||
// or at least we don't need to display first _history date (just skip it by height)
|
||||
_historySkipHeight = 0;
|
||||
if (_migrated) {
|
||||
if (!_migrated->isEmpty() && !_history->isEmpty() && _migrated->loadedAtBottom() && _history->loadedAtTop()) {
|
||||
if (_migrated->blocks.back()->items.back()->date.date() == _history->blocks.front()->items.front()->date.date()) {
|
||||
skip += _history->blocks.front()->items.front()->height();
|
||||
if (_migrated->blocks.back()->items.back()->isGroupMigrate() && _history->blocks.front()->items.size() == 1 && _history->blocks.size() > 1 && _history->blocks.at(1)->items.front()->isGroupMigrate()) {
|
||||
skip += _history->blocks.at(1)->items.at(0)->height();
|
||||
if (_migrated->blocks.back()->items.back()->isGroupMigrate() && _history->blocks.front()->items.front()->isGroupMigrate()) {
|
||||
_historySkipHeight += _history->blocks.front()->items.front()->height();
|
||||
} else {
|
||||
_historySkipHeight += _history->blocks.front()->items.front()->displayedDateHeight();
|
||||
}
|
||||
}
|
||||
if (skip > migratedTop() + _migrated->height) {
|
||||
skip = migratedTop() + _migrated->height; // should not happen, just check.. we need historyTop() >= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (skip != _historySkipHeight) {
|
||||
if (st1 >= 0) st1 -= (skip - _historySkipHeight);
|
||||
_historySkipHeight = skip;
|
||||
}
|
||||
|
||||
updateBotInfo(false);
|
||||
if (_botInfo && !_botInfo->text.isEmpty()) {
|
||||
int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right();
|
||||
@ -1257,7 +1252,11 @@ int32 HistoryInner::recountHeight() {
|
||||
_botDescHeight = _botInfo->text.countHeight(_botDescWidth);
|
||||
|
||||
int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botDescHeight + st::msgPadding.bottom() + st::msgMargin.bottom();
|
||||
int32 descAtX = (_scroll->width() - _botDescWidth) / 2 - st::msgPadding.left();
|
||||
int32 descMaxWidth = _scroll->width();
|
||||
if (Adaptive::Wide()) {
|
||||
descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
|
||||
}
|
||||
int32 descAtX = (descMaxWidth - _botDescWidth) / 2 - st::msgPadding.left();
|
||||
int32 descAtY = qMin(_historyOffset - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top();
|
||||
|
||||
_botDescRect = QRect(descAtX, descAtY, _botDescWidth + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
|
||||
@ -1272,7 +1271,13 @@ int32 HistoryInner::recountHeight() {
|
||||
}
|
||||
if (newYSkip < minadd) newYSkip = minadd;
|
||||
|
||||
return ((st1 >= 0 || st2 < 0) ? (st1 + htop) : (st2 + mtop)) + (newYSkip - wasYSkip);
|
||||
if (newYSkip != wasYSkip) {
|
||||
if (_history->scrollTopItem) {
|
||||
_history->scrollTopOffset += (newYSkip - wasYSkip);
|
||||
} else if (_migrated && _migrated->scrollTopItem) {
|
||||
_migrated->scrollTopOffset += (newYSkip - wasYSkip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryInner::updateBotInfo(bool recount) {
|
||||
@ -1353,6 +1358,38 @@ HistoryItem *HistoryInner::atTopImportantMsg(int32 top, int32 height, int32 &bot
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HistoryInner::visibleAreaUpdated(int top, int bottom) {
|
||||
// if history has pending resize events we should not update scrollTopItem
|
||||
if (_history->hasPendingResizedItems()) {
|
||||
return;
|
||||
} else if (_migrated && _migrated->hasPendingResizedItems()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bottom >= historyHeight()) {
|
||||
_history->forgetScrollState();
|
||||
if (_migrated) {
|
||||
_migrated->forgetScrollState();
|
||||
}
|
||||
} else {
|
||||
int htop = historyTop(), mtop = migratedTop();
|
||||
if ((htop >= 0 && top >= htop) || mtop < 0) {
|
||||
_history->countScrollState(top - htop);
|
||||
if (_migrated) {
|
||||
_migrated->forgetScrollState();
|
||||
}
|
||||
} else if (mtop >= 0 && top >= mtop) {
|
||||
_history->forgetScrollState();
|
||||
_migrated->countScrollState(top - mtop);
|
||||
} else {
|
||||
_history->countScrollState(top - htop);
|
||||
if (_migrated) {
|
||||
_migrated->forgetScrollState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryInner::updateSize() {
|
||||
int32 ph = _scroll->height(), minadd = 0;
|
||||
int32 newYSkip = ph - historyHeight() - st::historyPadding;
|
||||
@ -1363,7 +1400,11 @@ void HistoryInner::updateSize() {
|
||||
|
||||
if (_botDescHeight > 0) {
|
||||
int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botDescHeight + st::msgPadding.bottom() + st::msgMargin.bottom();
|
||||
int32 descAtX = (_scroll->width() - _botDescWidth) / 2 - st::msgPadding.left();
|
||||
int32 descMaxWidth = _scroll->width();
|
||||
if (Adaptive::Wide()) {
|
||||
descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
|
||||
}
|
||||
int32 descAtX = (descMaxWidth - _botDescWidth) / 2 - st::msgPadding.left();
|
||||
int32 descAtY = qMin(newYSkip - descH, qMax(0, (_scroll->height() - descH) / 2)) + st::msgMargin.top();
|
||||
|
||||
_botDescRect = QRect(descAtX, descAtY, _botDescWidth + st::msgPadding.left() + st::msgPadding.right(), descH - st::msgMargin.top() - st::msgMargin.bottom());
|
||||
@ -1665,22 +1706,22 @@ void HistoryInner::onUpdateSelected() {
|
||||
HistoryItem *dragSelFrom = _dragItem, *dragSelTo = item;
|
||||
if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom
|
||||
if (selectingDown) {
|
||||
if (_dragStartPos.y() >= dragSelFrom->height() - st::msgMargin.bottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance()))) {
|
||||
if (_dragStartPos.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) {
|
||||
dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelFrom);
|
||||
}
|
||||
} else {
|
||||
if (_dragStartPos.y() < st::msgMargin.top() || ((item == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance()))) {
|
||||
if (_dragStartPos.y() < dragSelFrom->marginTop() || ((item == dragSelFrom) && (m.y() >= _dragStartPos.y() - QApplication::startDragDistance() || m.y() >= dragSelFrom->height() - dragSelFrom->marginBottom()))) {
|
||||
dragSelFrom = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelFrom);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_dragItem != item) { // maybe exclude dragSelTo
|
||||
if (selectingDown) {
|
||||
if (m.y() < st::msgMargin.top()) {
|
||||
if (m.y() < dragSelTo->marginTop()) {
|
||||
dragSelTo = (dragSelFrom == dragSelTo) ? 0 : prevItem(dragSelTo);
|
||||
}
|
||||
} else {
|
||||
if (m.y() >= dragSelTo->height() - st::msgMargin.bottom()) {
|
||||
if (m.y() >= dragSelTo->height() - dragSelTo->marginBottom()) {
|
||||
dragSelTo = (dragSelFrom == dragSelTo) ? 0 : nextItem(dragSelTo);
|
||||
}
|
||||
}
|
||||
@ -1739,8 +1780,8 @@ void HistoryInner::updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dr
|
||||
update();
|
||||
}
|
||||
|
||||
int32 HistoryInner::historyHeight() const {
|
||||
int32 result = 0;
|
||||
int HistoryInner::historyHeight() const {
|
||||
int result = 0;
|
||||
if (!_history || _history->isEmpty()) {
|
||||
result += _migrated ? _migrated->height : 0;
|
||||
} else {
|
||||
@ -1749,25 +1790,38 @@ int32 HistoryInner::historyHeight() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 HistoryInner::migratedTop() const {
|
||||
int HistoryInner::historyScrollTop() const {
|
||||
int htop = historyTop(), mtop = migratedTop();
|
||||
if (htop >= 0 && _history->scrollTopItem) {
|
||||
t_assert(!_history->scrollTopItem->detached());
|
||||
return htop + _history->scrollTopItem->block()->y + _history->scrollTopItem->y + _history->scrollTopOffset;
|
||||
}
|
||||
if (mtop >= 0 && _migrated->scrollTopItem) {
|
||||
t_assert(!_migrated->scrollTopItem->detached());
|
||||
return mtop + _migrated->scrollTopItem->block()->y + _migrated->scrollTopItem->y + _migrated->scrollTopOffset;
|
||||
}
|
||||
return ScrollMax;
|
||||
}
|
||||
|
||||
int HistoryInner::migratedTop() const {
|
||||
return (_migrated && !_migrated->isEmpty()) ? _historyOffset : -1;
|
||||
}
|
||||
|
||||
int32 HistoryInner::historyTop() const {
|
||||
int32 mig = migratedTop();
|
||||
int HistoryInner::historyTop() const {
|
||||
int mig = migratedTop();
|
||||
return (_history && !_history->isEmpty()) ? (mig >= 0 ? (mig + _migrated->height - _historySkipHeight) : _historyOffset) : -1;
|
||||
}
|
||||
|
||||
int32 HistoryInner::historyDrawTop() const {
|
||||
int32 his = historyTop();
|
||||
int HistoryInner::historyDrawTop() const {
|
||||
int his = historyTop();
|
||||
return (his >= 0) ? (his + _historySkipHeight) : -1;
|
||||
}
|
||||
|
||||
int32 HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not be visible, -2 if bad history()
|
||||
int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not be visible, -2 if bad history()
|
||||
if (!item) return -2;
|
||||
if (item->detached()) return -1;
|
||||
|
||||
int32 top = (item->history() == _history) ? historyTop() : (item->history() == _migrated ? migratedTop() : -2);
|
||||
int top = (item->history() == _history) ? historyTop() : (item->history() == _migrated ? migratedTop() : -2);
|
||||
return (top < 0) ? top : (top + item->y + item->block()->y);
|
||||
}
|
||||
|
||||
@ -2729,7 +2783,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
||||
setAcceptDrops(true);
|
||||
|
||||
connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(updateField()));
|
||||
connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll()));
|
||||
connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
|
||||
connect(&_reportSpamPanel, SIGNAL(reportClicked()), this, SLOT(onReportSpamClicked()));
|
||||
connect(&_reportSpamPanel, SIGNAL(hideClicked()), this, SLOT(onReportSpamHide()));
|
||||
connect(&_reportSpamPanel, SIGNAL(clearClicked()), this, SLOT(onReportSpamClear()));
|
||||
@ -2753,7 +2807,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent)
|
||||
connect(&_field, SIGNAL(changed()), this, SLOT(onTextChange()));
|
||||
connect(&_field, SIGNAL(spacedReturnedPasted()), this, SLOT(onPreviewParse()));
|
||||
connect(&_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck()));
|
||||
connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onVisibleChanged()));
|
||||
connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged()));
|
||||
connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer()));
|
||||
connect(&_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr)));
|
||||
connect(&_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*)));
|
||||
@ -3516,7 +3570,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
|
||||
|
||||
if (_history) {
|
||||
if (_peer->id == peerId && !reload) {
|
||||
_history->lastWidth = 0;
|
||||
_history->forgetScrollState();
|
||||
|
||||
bool wasOnlyImportant = _history->isChannel() ? _history->asChannelHistory()->onlyImportant() : true;
|
||||
|
||||
@ -3582,14 +3636,8 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
|
||||
|
||||
writeDrafts(&_history->msgDraft, &_history->editDraft);
|
||||
|
||||
if (_scroll.scrollTop() + 1 <= _scroll.scrollTopMax()) {
|
||||
_history->lastWidth = _list->width();
|
||||
_history->lastShowAtMsgId = _showAtMsgId;
|
||||
} else {
|
||||
_history->lastWidth = 0;
|
||||
_history->lastShowAtMsgId = ShowAtUnreadMsgId;
|
||||
}
|
||||
_history->lastScrollTop = _scroll.scrollTop();
|
||||
_history->showAtMsgId = _showAtMsgId;
|
||||
|
||||
destroyUnreadBar();
|
||||
if (_pinnedBar) destroyPinnedBar();
|
||||
_history = _migrated = nullptr;
|
||||
@ -3662,11 +3710,11 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
|
||||
}
|
||||
|
||||
if (_showAtMsgId == ShowAtUnreadMsgId) {
|
||||
if (_history->lastWidth) {
|
||||
_showAtMsgId = _history->lastShowAtMsgId;
|
||||
if (_history->scrollTopItem) {
|
||||
_showAtMsgId = _history->showAtMsgId;
|
||||
}
|
||||
} else {
|
||||
_history->lastWidth = 0;
|
||||
_history->forgetScrollState();
|
||||
}
|
||||
|
||||
_list = new HistoryInner(this, &_scroll, _history);
|
||||
@ -3678,7 +3726,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
|
||||
_updateHistoryItems.stop();
|
||||
|
||||
pinnedMsgVisibilityUpdated();
|
||||
if (_history->lastWidth || _history->isReadyFor(_showAtMsgId, _fixedInScrollMsgId, _fixedInScrollMsgTop)) {
|
||||
if (_history->scrollTopItem || _history->isReadyFor(_showAtMsgId, _fixedInScrollMsgId, _fixedInScrollMsgTop)) {
|
||||
_fixedInScrollMsgId = 0;
|
||||
_fixedInScrollMsgTop = 0;
|
||||
historyLoaded();
|
||||
@ -4142,7 +4190,7 @@ void HistoryWidget::newUnreadMsg(History *history, HistoryItem *item) {
|
||||
}
|
||||
|
||||
void HistoryWidget::historyToDown(History *history) {
|
||||
history->lastScrollTop = ScrollMax;
|
||||
history->forgetScrollState();
|
||||
if (history == _history) {
|
||||
_scroll.scrollToY(_scroll.scrollTopMax());
|
||||
}
|
||||
@ -4231,7 +4279,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
|
||||
if (_preloadRequest == requestId) {
|
||||
addMessagesToFront(peer, *histList, histCollapsed);
|
||||
_preloadRequest = 0;
|
||||
onListScroll();
|
||||
preloadHistoryIfNeeded();
|
||||
if (_reportSpamStatus == dbiprsUnknown) {
|
||||
updateReportSpamStatus();
|
||||
if (_reportSpamStatus != dbiprsUnknown) updateControlsVisibility();
|
||||
@ -4239,7 +4287,7 @@ void HistoryWidget::messagesReceived(PeerData *peer, const MTPmessages_Messages
|
||||
} else if (_preloadDownRequest == requestId) {
|
||||
addMessagesToBack(peer, *histList, histCollapsed);
|
||||
_preloadDownRequest = 0;
|
||||
onListScroll();
|
||||
preloadHistoryIfNeeded();
|
||||
if (_history->loadedAtBottom() && App::wnd()) App::wnd()->checkHistoryActivation();
|
||||
} else if (_firstLoadRequest == requestId) {
|
||||
if (toMigrated) {
|
||||
@ -4516,8 +4564,17 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::onListScroll() {
|
||||
void HistoryWidget::onScroll() {
|
||||
App::checkImageCacheSize();
|
||||
preloadHistoryIfNeeded();
|
||||
|
||||
if (_list && !_scroll.isHidden()) {
|
||||
int st = _scroll.scrollTop();
|
||||
_list->visibleAreaUpdated(st, st + _scroll.height());
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::preloadHistoryIfNeeded() {
|
||||
if (_firstLoadRequest || _scroll.isHidden() || !_peer) return;
|
||||
|
||||
updateToEndVisibility();
|
||||
@ -4550,8 +4607,8 @@ void HistoryWidget::onListScroll() {
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::onVisibleChanged() {
|
||||
QTimer::singleShot(0, this, SLOT(onListScroll()));
|
||||
void HistoryWidget::onWindowVisibleChanged() {
|
||||
QTimer::singleShot(0, this, SLOT(preloadHistoryIfNeeded()));
|
||||
}
|
||||
|
||||
void HistoryWidget::onHistoryToEnd() {
|
||||
@ -4937,7 +4994,7 @@ void HistoryWidget::doneShow() {
|
||||
updateBotKeyboard();
|
||||
updateControlsVisibility();
|
||||
updateListSize(true);
|
||||
onListScroll();
|
||||
preloadHistoryIfNeeded();
|
||||
if (App::wnd()) {
|
||||
App::wnd()->checkHistoryActivation();
|
||||
App::wnd()->setInnerFocus();
|
||||
@ -6322,16 +6379,13 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||
updateCollapseCommentsVisibility();
|
||||
}
|
||||
|
||||
if (!initial) {
|
||||
_history->lastScrollTop = _scroll.scrollTop();
|
||||
}
|
||||
int32 newSt = _list->recountHeight();
|
||||
_list->recountHeight();
|
||||
|
||||
bool washidden = _scroll.isHidden();
|
||||
if (washidden) {
|
||||
_scroll.show();
|
||||
}
|
||||
_list->updateSize();
|
||||
int32 historyTop = _list->historyTop(), migratedTop = _list->migratedTop();
|
||||
if (washidden) {
|
||||
_scroll.hide();
|
||||
}
|
||||
@ -6343,7 +6397,7 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||
} else if (change.type == ScrollChangeOldHistoryHeight) {
|
||||
addToY = _list->historyHeight() - change.value;
|
||||
}
|
||||
_scroll.scrollToY(newSt + addToY);
|
||||
_scroll.scrollToY(_list->historyScrollTop() + addToY);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -6352,9 +6406,8 @@ void HistoryWidget::updateListSize(bool initial, bool loadedDown, const ScrollCh
|
||||
}
|
||||
|
||||
int32 toY = ScrollMax;
|
||||
if (initial && _history->lastWidth) {
|
||||
toY = newSt;
|
||||
_history->lastWidth = 0;
|
||||
if (initial && _history->scrollTopItem) {
|
||||
toY = _list->historyScrollTop();
|
||||
} else if (initial && _migrated && _showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) {
|
||||
HistoryItem *item = App::histItemById(0, -_showAtMsgId);
|
||||
int32 iy = _list->itemTop(item);
|
||||
@ -6454,7 +6507,7 @@ void HistoryWidget::addMessagesToFront(PeerData *peer, const QVector<MTPMessage>
|
||||
int oldH = _list->historyHeight();
|
||||
_list->messagesReceived(peer, messages, collapsed);
|
||||
if (!_firstLoadRequest) {
|
||||
updateListSize(false, false, { ScrollChangeOldHistoryHeight, oldH });
|
||||
updateListSize();
|
||||
if (_animActiveTimer.isActive() && _activeAnimMsgId > 0 && _migrated && !_migrated->isEmpty() && _migrated->loadedAtBottom() && _migrated->blocks.back()->items.back()->isGroupMigrate() && _list->historyTop() != _list->historyDrawTop() && _history) {
|
||||
HistoryItem *animActiveItem = App::histItemById(_history->channelId(), _activeAnimMsgId);
|
||||
if (animActiveItem && animActiveItem->isGroupMigrate()) {
|
||||
@ -7406,7 +7459,6 @@ void HistoryWidget::onCancel() {
|
||||
}
|
||||
|
||||
void HistoryWidget::onFullPeerUpdated(PeerData *data) {
|
||||
int32 newScrollTop = _scroll.scrollTop();
|
||||
if (_list && data == _peer) {
|
||||
bool newCanSendMessages = canSendMessages(_peer);
|
||||
if (newCanSendMessages != _canSendMessages) {
|
||||
@ -7418,9 +7470,7 @@ void HistoryWidget::onFullPeerUpdated(PeerData *data) {
|
||||
}
|
||||
onCheckMentionDropdown();
|
||||
updateReportSpamStatus();
|
||||
int32 lh = _list->height(), st = _scroll.scrollTop();
|
||||
_list->updateBotInfo();
|
||||
newScrollTop = st + _list->height() - lh;
|
||||
}
|
||||
if (updateCmdStartShown()) {
|
||||
updateControlsVisibility();
|
||||
@ -7430,13 +7480,6 @@ void HistoryWidget::onFullPeerUpdated(PeerData *data) {
|
||||
updateControlsVisibility();
|
||||
resizeEvent(0);
|
||||
}
|
||||
if (newScrollTop != _scroll.scrollTop()) {
|
||||
if (_scroll.isVisible()) {
|
||||
_scroll.scrollToY(newScrollTop);
|
||||
} else {
|
||||
_history->lastScrollTop = newScrollTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::peerUpdated(PeerData *data) {
|
||||
|
@ -66,7 +66,7 @@ public:
|
||||
void touchScrollUpdated(const QPoint &screenPos);
|
||||
QPoint mapMouseToItem(QPoint p, HistoryItem *item);
|
||||
|
||||
int32 recountHeight();
|
||||
void recountHeight();
|
||||
void updateSize();
|
||||
|
||||
void repaintItem(const HistoryItem *item);
|
||||
@ -88,11 +88,15 @@ public:
|
||||
|
||||
HistoryItem *atTopImportantMsg(int32 top, int32 height, int32 &bottomUnderScrollTop) const;
|
||||
|
||||
int32 historyHeight() const;
|
||||
int32 migratedTop() const;
|
||||
int32 historyTop() const;
|
||||
int32 historyDrawTop() const;
|
||||
int32 itemTop(const HistoryItem *item) const; // -1 if should not be visible, -2 if bad history()
|
||||
// updates history->scrollTopItem/scrollTopOffset
|
||||
void visibleAreaUpdated(int top, int bottom);
|
||||
|
||||
int historyHeight() const;
|
||||
int historyScrollTop() const;
|
||||
int migratedTop() const;
|
||||
int historyTop() const;
|
||||
int historyDrawTop() const;
|
||||
int itemTop(const HistoryItem *item) const; // -1 if should not be visible, -2 if bad history()
|
||||
|
||||
void notifyIsBotChanged();
|
||||
void notifyMigrateUpdated();
|
||||
@ -646,7 +650,7 @@ public slots:
|
||||
void onReportSpamHide();
|
||||
void onReportSpamClear();
|
||||
|
||||
void onListScroll();
|
||||
void onScroll();
|
||||
void onHistoryToEnd();
|
||||
void onCollapseComments();
|
||||
void onSend(bool ctrlShiftEnter = false, MsgId replyTo = -1);
|
||||
@ -676,7 +680,7 @@ public slots:
|
||||
void onPhotoSend(PhotoData *photo);
|
||||
void onInlineResultSend(InlineResult *result, UserData *bot);
|
||||
|
||||
void onVisibleChanged();
|
||||
void onWindowVisibleChanged();
|
||||
|
||||
void deleteMessage();
|
||||
void forwardMessage();
|
||||
@ -709,6 +713,10 @@ public slots:
|
||||
|
||||
void onUpdateHistoryItems();
|
||||
|
||||
// checks if we are too close to the top or to the bottom
|
||||
// in the scroll area and preloads history if needed
|
||||
void preloadHistoryIfNeeded();
|
||||
|
||||
private:
|
||||
|
||||
MsgId _replyToId;
|
||||
|
@ -3012,7 +3012,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha
|
||||
}
|
||||
if (history.peer() == channel) {
|
||||
history.updateToEndVisibility();
|
||||
history.onListScroll();
|
||||
history.preloadHistoryIfNeeded();
|
||||
}
|
||||
h->asChannelHistory()->getRangeDifference();
|
||||
}
|
||||
@ -3412,8 +3412,7 @@ void MainWidget::getChannelDifference(ChannelData *channel, GetChannelDifference
|
||||
int32 fixInScrollMsgTop = 0;
|
||||
history->asChannelHistory()->getSwitchReadyFor(SwitchAtTopMsgId, fixInScrollMsgId, fixInScrollMsgTop);
|
||||
history->getReadyFor(ShowAtTheEndMsgId, fixInScrollMsgId, fixInScrollMsgTop);
|
||||
history->lastWidth = 0;
|
||||
history->lastScrollTop = INT_MAX;
|
||||
history->forgetScrollState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1036,8 +1036,11 @@ enum class MTPDmessage_ClientFlag : int32 {
|
||||
// message needs paint()
|
||||
f_pending_paint = (1 << 26),
|
||||
|
||||
// message is attached to previous one when displaying the history
|
||||
f_attach_to_previous = (1 << 25),
|
||||
|
||||
// update this when adding new client side flags
|
||||
MIN_FIELD = (1 << 26),
|
||||
MIN_FIELD = (1 << 25),
|
||||
};
|
||||
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
|
||||
|
||||
|
@ -161,7 +161,10 @@ void PeerData::setUserpic(ImagePtr userpic) {
|
||||
ImagePtr PeerData::currentUserpic() const {
|
||||
if (_userpic->loaded()) {
|
||||
return _userpic;
|
||||
} else if (isUser()) {
|
||||
}
|
||||
_userpic->load();
|
||||
|
||||
if (isUser()) {
|
||||
return userDefPhoto(colorIndex);
|
||||
} else if (isMegagroup() || isChat()) {
|
||||
return chatDefPhoto(colorIndex);
|
||||
|
Loading…
Reference in New Issue
Block a user