Add calls log box.

PeerListBox can have many rows with the same PeerData.
PeerListBox::Row can have arbitrary action on the right side.
This commit is contained in:
John Preston 2017-04-27 22:04:45 +03:00
parent f6eb2c5205
commit 06b081f509
26 changed files with 709 additions and 165 deletions

View File

@ -166,6 +166,9 @@ contactsStatusFgOnline: windowActiveTextFg; // contacts (and some other) box row
photoCropFadeBg: layerBg; // avatar crop box fade background (when choosing a new photo in Settings or for a group)
photoCropPointFg: #ffffff7f; // avatar crop box corner rectangles (when choosing a new photo in Settings or for a group)
callArrowFg: #2ab32a | boxTextFgGood; // received phone call arrow (in calls list box)
callArrowMissedFg: #dd5b4a | boxTextFgError; // missed phone call arrow (in calls list box)
// intro
introBg: windowBg; // login background
introTitleFg: windowBoldFg; // login title text
@ -267,6 +270,12 @@ historyIconFgInverted: windowFgActive; // media message tick / double tick icon
historySendingOutIconFg: #98d292; // outbox sending message icon (clock)
historySendingInIconFg: #a0adb5; // inbox sending message icon (clock) (like in sent messages to yourself or in sent messages to a channel)
historySendingInvertedIconFg: #ffffffc8; // media sending message icon (clock) (like in sent photo)
historyCallArrowInFg: callArrowFg; // received phone call arrow
historyCallArrowInFgSelected: callArrowFg; // received phone call arrow in a selected message
historyCallArrowMissedInFg: callArrowMissedFg; // missed phone call arrow
historyCallArrowMissedInFgSelected: callArrowMissedFg; // missed phone call arrow in a selected message
historyCallArrowOutFg: historyCallArrowInFg; // outgoing phone call arrow
historyCallArrowOutFgSelected: historyCallArrowInFgSelected; // outgoing phone call arrow
historyUnreadBarBg: #fcfbfa; // new unread messages bar background
historyUnreadBarBorder: shadowFg; // new unread messages bar shadow
@ -512,19 +521,19 @@ mediaviewTransparentFg: #cccccc; // another transparent filling part
notificationBg: windowBg; // custom notification window background
// calls
callBg: #26282cf2;
callNameFg: #ffffff;
callFingerprintBg: #00000066;
callStatusFg: #aaabac;
callIconFg: #ffffff;
callAnswerBg: #64c15b;
callAnswerRipple: #52b149;
callHangupBg: #d75a5a;
callHangupRipple: #c04646;
callMuteRipple: #ffffff12;
callBg: #26282cf2; // phone call popup background
callNameFg: #ffffff; // phone call popup name text
callFingerprintBg: #00000066; // phone call popup emoji fingerprint background
callStatusFg: #aaabac; // phone call popup status text
callIconFg: #ffffff; // phone call popup answer, hangup and mute mic icon
callAnswerBg: #64c15b; // phone call popup answer button background
callAnswerRipple: #52b149; // phone call popup answer button ripple effect
callHangupBg: #d75a5a; // phone call popup hangup button background
callHangupRipple: #c04646; // phone call popup hangup button ripple effect
callMuteRipple: #ffffff12; // phone call popup mute mic ripple effect
callBarBg: dialogsBgActive;
callBarMuteRipple: dialogsRippleBgActive;
callBarBgMuted: #8f8f8f | dialogsUnreadBgMuted;
callBarUnmuteRipple: #7f7f7f | shadowFg;
callBarFg: dialogsNameFgActive;
callBarBg: dialogsBgActive; // active phone call bar background
callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect
callBarBgMuted: #8f8f8f | dialogsUnreadBgMuted; // phone call bar with muted mic background
callBarUnmuteRipple: #7f7f7f | shadowFg; // phone call bar with muted mic mute and hangup button ripple effect
callBarFg: dialogsNameFgActive; // phone call bar text and icons

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

View File

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_switch_to_this" = "Switch to English";
"lng_menu_contacts" = "Contacts";
"lng_menu_calls" = "Calls";
"lng_menu_settings" = "Settings";
"lng_menu_about" = "About";
"lng_menu_update" = "Update";
@ -1137,6 +1138,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_call_bar_info" = "Show call info";
"lng_call_bar_hangup" = "End call";
"lng_call_box_title" = "Calls";
"lng_call_box_about" = "Your history of Telegram calls will be here.";
"lng_call_box_status_today" = "{time}";
"lng_call_box_status_yesterday" = "Yesterday at {time}";
"lng_call_box_status_date" = "{date} at {time}";
"lng_call_box_status_group" = "({count}) {status}";
// Not used
"lng_topbar_info" = "Info";

View File

@ -124,8 +124,8 @@ void PeerListBox::prependRow(std::unique_ptr<Row> row) {
_inner->prependRow(std::move(row));
}
PeerListBox::Row *PeerListBox::findRow(PeerData *peer) {
return _inner->findRow(peer);
PeerListBox::Row *PeerListBox::findRow(RowId id) {
return _inner->findRow(id);
}
void PeerListBox::updateRow(Row *row) {
@ -156,6 +156,10 @@ int PeerListBox::fullRowsCount() const {
return _inner->fullRowsCount();
}
PeerListBox::Row *PeerListBox::rowAt(int index) const {
return _inner->rowAt(index);
}
void PeerListBox::setAboutText(const QString &aboutText) {
if (aboutText.isEmpty()) {
setAbout(nullptr);
@ -180,7 +184,7 @@ void PeerListBox::setSearchMode(SearchMode mode) {
_select->entity()->setQueryChangedCallback([this](const QString &query) { searchQueryChanged(query); });
_select->entity()->setItemRemovedCallback([this](uint64 itemId) {
if (auto peer = App::peerLoaded(itemId)) {
if (auto row = findRow(peer)) {
if (auto row = findRow(peer->id)) {
_inner->changeCheckState(row, false, Row::SetStyle::Animated);
update();
}
@ -250,23 +254,16 @@ bool PeerListBox::isRowSelected(PeerData *peer) const {
return _select->entity()->hasItem(peer->id);
}
PeerListBox::Row::Row(PeerData *peer) : _peer(peer) {
PeerListBox::Row::Row(PeerData *peer) : Row(peer, peer->id) {
}
PeerListBox::Row::Row(PeerData *peer, RowId id) : _id(id), _peer(peer) {
}
bool PeerListBox::Row::checked() const {
return _checkbox && _checkbox->checked();
}
void PeerListBox::Row::setActionLink(const QString &action) {
_action = action;
refreshActionLink();
}
void PeerListBox::Row::refreshActionLink() {
if (!_initialized) return;
_actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action);
}
void PeerListBox::Row::setCustomStatus(const QString &status) {
_status = status;
_statusType = StatusType::Custom;
@ -288,10 +285,6 @@ void PeerListBox::Row::refreshStatus() {
}
}
PeerListBox::Row::StatusType PeerListBox::Row::statusType() const {
return _statusType;
}
void PeerListBox::Row::refreshName() {
if (!_initialized) {
return;
@ -299,18 +292,6 @@ void PeerListBox::Row::refreshName() {
_name.setText(st::contactsNameStyle, peer()->name, _textNameOptions);
}
QString PeerListBox::Row::status() const {
return _status;
}
QString PeerListBox::Row::action() const {
return _action;
}
int PeerListBox::Row::actionWidth() const {
return _actionWidth;
}
PeerListBox::Row::~Row() = default;
void PeerListBox::Row::invalidatePixmapsCache() {
@ -319,6 +300,12 @@ void PeerListBox::Row::invalidatePixmapsCache() {
}
}
void PeerListBox::Row::paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected) {
auto statusHasOnlineColor = (_statusType == Row::StatusType::Online);
p.setPen(statusHasOnlineColor ? st::contactsStatusFgOnline : (selected ? st::contactsStatusFgOver : st::contactsStatusFg));
p.drawTextLeft(x, y, outerWidth, _status);
}
template <typename UpdateCallback>
void PeerListBox::Row::addRipple(QSize size, QPoint point, UpdateCallback updateCallback) {
if (!_ripple) {
@ -399,7 +386,6 @@ void PeerListBox::Row::lazyInitialize() {
return;
}
_initialized = true;
refreshActionLink();
refreshName();
refreshStatus();
}
@ -431,7 +417,7 @@ PeerListBox::Inner::Inner(QWidget *parent, Controller *controller) : TWidget(par
}
void PeerListBox::Inner::appendRow(std::unique_ptr<Row> row) {
if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) {
if (_rowsById.find(row->id()) == _rowsById.cend()) {
row->setAbsoluteIndex(_rows.size());
addRowEntry(row.get());
_rows.push_back(std::move(row));
@ -440,7 +426,7 @@ void PeerListBox::Inner::appendRow(std::unique_ptr<Row> row) {
void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr<Row> row) {
Expects(showingSearch());
if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) {
if (_rowsById.find(row->id()) == _rowsById.cend()) {
row->setAbsoluteIndex(_globalSearchRows.size());
row->setIsGlobalSearchResult(true);
addRowEntry(row.get());
@ -456,11 +442,13 @@ void PeerListBox::Inner::changeCheckState(Row *row, bool checked, Row::SetStyle
}
void PeerListBox::Inner::addRowEntry(Row *row) {
_rowsByPeer.emplace(row->peer(), row);
_rowsById.emplace(row->id(), row);
_rowsByPeer[row->peer()].push_back(row);
if (addingToSearchIndex()) {
addToSearchIndex(row);
}
if (_searchMode != SearchMode::None) {
t_assert(row->id() == row->peer()->id);
if (_controller->view()->isRowSelected(row->peer())) {
changeCheckState(row, true, Row::SetStyle::Fast);
}
@ -511,7 +499,7 @@ void PeerListBox::Inner::removeFromSearchIndex(Row *row) {
}
void PeerListBox::Inner::prependRow(std::unique_ptr<Row> row) {
if (_rowsByPeer.find(row->peer()) == _rowsByPeer.cend()) {
if (_rowsById.find(row->id()) == _rowsById.cend()) {
addRowEntry(row.get());
_rows.insert(_rows.begin(), std::move(row));
refreshIndices();
@ -525,9 +513,9 @@ void PeerListBox::Inner::refreshIndices() {
}
}
PeerListBox::Row *PeerListBox::Inner::findRow(PeerData *peer) {
auto it = _rowsByPeer.find(peer);
return (it == _rowsByPeer.cend()) ? nullptr : it->second;
PeerListBox::Row *PeerListBox::Inner::findRow(RowId id) {
auto it = _rowsById.find(id);
return (it == _rowsById.cend()) ? nullptr : it->second;
}
void PeerListBox::Inner::removeRow(Row *row) {
@ -541,7 +529,9 @@ void PeerListBox::Inner::removeRow(Row *row) {
setSelected(Selected());
setPressed(Selected());
_rowsByPeer.erase(row->peer());
_rowsById.erase(row->id());
auto &byPeer = _rowsByPeer[row->peer()];
byPeer.erase(std::remove(byPeer.begin(), byPeer.end(), row), byPeer.end());
removeFromSearchIndex(row);
_filterResults.erase(std::find(_filterResults.begin(), _filterResults.end(), row), _filterResults.end());
eraseFrom.erase(eraseFrom.begin() + index);
@ -556,6 +546,11 @@ int PeerListBox::Inner::fullRowsCount() const {
return _rows.size();
}
PeerListBox::Row *PeerListBox::Inner::rowAt(int index) const {
Expects(index >= 0 && index < _rows.size());
return _rows[index].get();
}
void PeerListBox::Inner::setAbout(object_ptr<Ui::FlatLabel> about) {
_about = std::move(about);
if (_about) {
@ -688,14 +683,20 @@ void PeerListBox::Inner::mousePressEvent(QMouseEvent *e) {
updateSelection();
setPressed(_selected);
if (!_selected.action) {
if (auto row = getRow(_selected.index)) {
if (auto row = getRow(_selected.index)) {
auto updateCallback = [this, row, hint = _selected.index] {
updateRow(row, hint);
};
if (_selected.action) {
auto actionRect = getActionRect(row, _selected.index);
if (!actionRect.isEmpty()) {
auto point = mapFromGlobal(QCursor::pos()) - actionRect.topLeft();
row->addActionRipple(point, std::move(updateCallback));
}
} else {
auto size = QSize(width(), _rowHeight);
auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index));
auto hint = _selected.index;
row->addRipple(size, point, [this, row, hint] {
updateRow(row, hint);
});
row->addRipple(size, point, std::move(updateCallback));
}
}
}
@ -720,6 +721,7 @@ void PeerListBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
void PeerListBox::Inner::setPressed(Selected pressed) {
if (auto row = getRow(_pressed.index)) {
row->stopLastRipple();
row->stopLastActionRipple();
}
_pressed = pressed;
}
@ -741,11 +743,15 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) {
p.setPen(st::contactsNameFg);
auto actionWidth = row->actionWidth();
auto actionSize = row->actionSize();
auto actionMargins = actionSize.isEmpty() ? QMargins() : row->actionMargins();
auto &name = row->name();
auto namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
auto namew = width() - namex - st::contactsPadding.right() - (actionWidth ? (actionWidth + st::contactsCheckPosition.x() * 2) : 0);
if (peer->isVerified()) {
auto namew = width() - namex - st::contactsPadding.right();
if (!actionSize.isEmpty()) {
namew -= actionMargins.left() + actionSize.width() + actionMargins.right();
}
if (row->needsVerifiedIcon()) {
auto icon = &st::dialogsVerifiedIcon;
namew -= icon->width();
icon->paint(p, namex + qMin(name.maxWidth(), namew), st::contactsPadding.top() + st::contactsNameTop, width());
@ -753,12 +759,10 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) {
p.setPen(anim::pen(st::contactsNameFg, st::contactsNameCheckedFg, row->checkedRatio()));
name.drawLeftElided(p, namex, st::contactsPadding.top() + st::contactsNameTop, namew, width());
if (actionWidth) {
p.setFont(actionSelected ? st::linkOverFont : st::linkFont);
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
auto actionRight = st::contactsPadding.right() + st::contactsCheckPosition.x();
auto actionTop = (_rowHeight - st::normalFont->height) / 2;
p.drawTextRight(actionRight, actionTop, width(), row->action(), actionWidth);
if (!actionSize.isEmpty()) {
auto actionLeft = width() - st::contactsPadding.right() - actionMargins.right() - actionSize.width();
auto actionTop = actionMargins.top();
row->paintAction(p, ms, actionLeft, actionTop, width(), actionSelected);
}
p.setFont(st::contactsStatusFont);
@ -788,9 +792,7 @@ void PeerListBox::Inner::paintRow(Painter &p, TimeMs ms, RowIndex index) {
p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), '@' + username);
}
} else {
auto statusHasOnlineColor = (row->statusType() == Row::StatusType::Online);
p.setPen(statusHasOnlineColor ? st::contactsStatusFgOnline : (selected ? st::contactsStatusFgOver : st::contactsStatusFg));
p.drawTextLeft(namex, st::contactsPadding.top() + st::contactsStatusTop, width(), row->status());
row->paintStatusText(p, namex, st::contactsPadding.top() + st::contactsStatusTop, width(), selected);
}
}
@ -1014,10 +1016,11 @@ void PeerListBox::Inner::globalSearchDone(const MTPcontacts_Found &result, mtpRe
for_const (auto &mtpPeer, contacts.vresults.v) {
if (auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) {
if (findRow(peer)) {
if (findRow(peer->id)) {
continue;
}
if (auto row = _controller->createGlobalRow(peer)) {
t_assert(row->id() == row->peer()->id);
appendGlobalSearchRow(std::move(row));
}
}
@ -1073,13 +1076,7 @@ void PeerListBox::Inner::updateSelection() {
if (row->disabled()) {
selected = Selected();
} else {
auto actionRight = st::contactsPadding.right() + st::contactsCheckPosition.x();
auto actionTop = (_rowHeight - st::normalFont->height) / 2;
auto actionWidth = row->actionWidth();
auto actionLeft = width() - actionWidth - actionRight;
auto rowTop = getRowTop(selected.index);
auto actionRect = myrtlrect(actionLeft, rowTop + actionTop, actionWidth, st::normalFont->height);
if (actionRect.contains(point)) {
if (getActionRect(row, selected.index).contains(point)) {
selected.action = true;
}
}
@ -1087,6 +1084,19 @@ void PeerListBox::Inner::updateSelection() {
setSelected(selected);
}
QRect PeerListBox::Inner::getActionRect(Row *row, RowIndex index) const {
auto actionSize = row->actionSize();
if (actionSize.isEmpty()) {
return QRect();
}
auto actionMargins = row->actionMargins();
auto actionRight = st::contactsPadding.right() + actionMargins.right();
auto actionTop = actionMargins.top();
auto actionLeft = width() - actionRight - actionSize.width();
auto rowTop = getRowTop(index);
return myrtlrect(actionLeft, rowTop + actionTop, actionSize.width(), actionSize.height());
}
void PeerListBox::Inner::peerUpdated(PeerData *peer) {
update();
}
@ -1180,15 +1190,47 @@ PeerListBox::Inner::RowIndex PeerListBox::Inner::findRowIndex(Row *row, RowIndex
}
void PeerListBox::Inner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
if (auto row = findRow(peer)) {
if (addingToSearchIndex()) {
addToSearchIndex(row);
auto byPeer = _rowsByPeer.find(peer);
if (byPeer != _rowsByPeer.cend()) {
for (auto row : byPeer->second) {
if (addingToSearchIndex()) {
addToSearchIndex(row);
}
row->refreshName();
updateRow(row);
}
row->refreshName();
updateRow(row);
}
}
void PeerListRowWithLink::setActionLink(const QString &action) {
_action = action;
refreshActionLink();
}
void PeerListRowWithLink::refreshActionLink() {
if (!isInitialized()) return;
_actionWidth = _action.isEmpty() ? 0 : st::normalFont->width(_action);
}
void PeerListRowWithLink::lazyInitialize() {
Row::lazyInitialize();
refreshActionLink();
}
QSize PeerListRowWithLink::actionSize() const {
return QSize(_actionWidth, st::normalFont->height);
}
QMargins PeerListRowWithLink::actionMargins() const {
return QMargins(st::contactsCheckPosition.x(), (st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom() - st::normalFont->height) / 2, st::contactsCheckPosition.x(), 0);
}
void PeerListRowWithLink::paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) {
p.setFont(actionSelected ? st::linkOverFont : st::linkFont);
p.setPen(actionSelected ? st::defaultLinkButton.overColor : st::defaultLinkButton.color);
p.drawTextLeft(x, y, outerWidth, _action, _actionWidth);
}
void ChatsListBoxController::prepare() {
view()->setSearchNoResultsText(lang(lng_blocked_list_not_found));
view()->setSearchMode(PeerListBox::SearchMode::Global);
@ -1254,7 +1296,7 @@ std::unique_ptr<PeerListBox::Row> ChatsListBoxController::createGlobalRow(PeerDa
}
bool ChatsListBoxController::appendRow(History *history) {
if (auto row = view()->findRow(history->peer)) {
if (auto row = view()->findRow(history->peer->id)) {
updateRowHook(static_cast<Row*>(row));
return false;
}

View File

@ -33,14 +33,15 @@ class FlatLabel;
} // namespace Ui
class PeerListBox : public BoxContent {
Q_OBJECT
class Inner;
public:
using RowId = uint64;
class Row {
public:
Row(PeerData *peer);
Row(PeerData *peer, RowId id);
void setDisabled(bool disabled) {
_disabled = disabled;
@ -52,21 +53,47 @@ public:
// added to the box it is always false.
bool checked() const;
void setActionLink(const QString &action);
PeerData *peer() const {
return _peer;
}
RowId id() const {
return _id;
}
void setCustomStatus(const QString &status);
void clearCustomStatus();
virtual ~Row();
protected:
virtual void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected);
bool isInitialized() const {
return _initialized;
}
virtual void lazyInitialize();
private:
// Inner interface.
friend class PeerListBox;
friend class Inner;
virtual bool needsVerifiedIcon() const {
return _peer->isVerified();
}
virtual QSize actionSize() const {
return QSize();
}
virtual QMargins actionMargins() const {
return QMargins();
}
virtual void addActionRipple(QPoint point, base::lambda<void()> updateCallback) {
}
virtual void stopLastActionRipple() {
}
virtual void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) {
}
void refreshName();
const Text &name() const {
return _name;
@ -78,12 +105,6 @@ public:
Custom,
};
void refreshStatus();
StatusType statusType() const;
QString status() const;
void refreshActionLink();
QString action() const;
int actionWidth() const;
void setAbsoluteIndex(int index) {
_absoluteIndex = index;
@ -128,13 +149,12 @@ public:
return _nameFirstChars;
}
void lazyInitialize();
private:
void createCheckbox(base::lambda<void()> updateCallback);
void setCheckedInternal(bool checked, SetStyle style);
void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const;
RowId _id = 0;
PeerData *_peer = nullptr;
bool _initialized = false;
std::unique_ptr<Ui::RippleAnimation> _ripple;
@ -142,8 +162,6 @@ public:
Text _name;
QString _status;
StatusType _statusType = StatusType::Online;
QString _action;
int _actionWidth = 0;
bool _disabled = false;
int _absoluteIndex = -1;
OrderedSet<QChar> _nameFirstChars;
@ -187,14 +205,17 @@ public:
// Interface for the controller.
void appendRow(std::unique_ptr<Row> row);
void prependRow(std::unique_ptr<Row> row);
Row *findRow(PeerData *peer);
Row *findRow(RowId id);
void updateRow(Row *row);
void removeRow(Row *row);
void setRowChecked(Row *row, bool checked);
int fullRowsCount() const;
Row *rowAt(int index) const;
void setAboutText(const QString &aboutText);
void setAbout(object_ptr<Ui::FlatLabel> about);
void refreshRows();
// Search works only with RowId == peer->id.
enum class SearchMode {
None,
Local,
@ -265,12 +286,13 @@ public:
// Interface for the controller.
void appendRow(std::unique_ptr<Row> row);
void prependRow(std::unique_ptr<Row> row);
Row *findRow(PeerData *peer);
Row *findRow(RowId id);
void updateRow(Row *row) {
updateRow(row, RowIndex());
}
void removeRow(Row *row);
int fullRowsCount() const;
Row *rowAt(int index) const;
void setAbout(object_ptr<Ui::FlatLabel> about);
void refreshRows();
void setSearchMode(SearchMode mode);
@ -353,6 +375,7 @@ private:
int getRowTop(RowIndex row) const;
Row *getRow(RowIndex element);
RowIndex findRowIndex(Row *row, RowIndex hint = RowIndex());
QRect getActionRect(Row *row, RowIndex index) const;
void paintRow(Painter &p, TimeMs ms, RowIndex index);
@ -390,7 +413,8 @@ private:
bool _mouseSelection = false;
std::vector<std::unique_ptr<Row>> _rows;
std::map<PeerData*, Row*> _rowsByPeer;
std::map<RowId, Row*> _rowsById;
std::map<PeerData*, std::vector<Row*>> _rowsByPeer;
SearchMode _searchMode = SearchMode::None;
std::map<QChar, std::vector<Row*>> _searchIndex;
@ -418,6 +442,26 @@ inline void PeerListBox::reorderRows(ReorderCallback &&callback) {
_inner->reorderRows(std::forward<ReorderCallback>(callback));
}
class PeerListRowWithLink : public PeerListBox::Row {
public:
using Row::Row;
void setActionLink(const QString &action);
protected:
void lazyInitialize() override;
private:
void refreshActionLink();
QSize actionSize() const override;
QMargins actionMargins() const override;
void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override;
QString _action;
int _actionWidth = 0;
};
class ChatsListBoxController : public PeerListBox::Controller, protected base::Subscriber {
public:
void prepare() override final;

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
using "basic.style";
using "ui/widgets/widgets.style";
using "window/window.style";
callWidth: 300px;
callHeight: 470px;
@ -134,3 +135,21 @@ callBarLabel: LabelSimple(defaultLabelSimple) {
textFg: callBarFg;
}
callBarLabelTop: 10px;
callArrowPosition: point(-2px, 1px);
callArrowIn: icon {{ "call_arrow_in", callArrowFg }};
callArrowOut: icon {{ "call_arrow_out", callArrowFg }};
callArrowMissed: icon {{ "call_arrow_in", callArrowMissedFg }};
callArrowSkip: 4px;
callReDial: IconButton {
width: 40px;
height: 56px;
icon: mainMenuCalls;
iconOver: mainMenuCallsOver;
iconPosition: point(-1px, -1px);
ripple: defaultRippleAnimation;
rippleAreaPosition: point(4px, 12px);
rippleAreaSize: 32px;
}

View File

@ -0,0 +1,345 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "calls/calls_box_controller.h"
#include "styles/style_calls.h"
#include "styles/style_boxes.h"
#include "lang.h"
#include "observer_peer.h"
#include "ui/effects/ripple_animation.h"
#include "calls/calls_instance.h"
namespace Calls {
namespace {
constexpr auto kFirstPageCount = 20;
constexpr auto kPerPageCount = 100;
} // namespace
class BoxController::Row : public PeerListBox::Row {
public:
Row(HistoryItem *item);
enum class Type {
Out,
In,
Missed,
};
bool canAddItem(HistoryItem *item) const {
return (ComputeType(item) == _type && item->date.date() == _date);
}
void addItem(HistoryItem *item) {
Expects(canAddItem(item));
_items.push_back(item);
std::sort(_items.begin(), _items.end(), [](HistoryItem *a, HistoryItem *b) {
return (a->id > b->id);
});
refreshStatus();
}
void itemRemoved(HistoryItem *item) {
if (hasItems() && item->id >= minItemId() && item->id <= maxItemId()) {
_items.erase(std::remove(_items.begin(), _items.end(), item), _items.end());
refreshStatus();
}
}
bool hasItems() const {
return !_items.empty();
}
MsgId minItemId() const {
Expects(hasItems());
return _items.back()->id;
}
MsgId maxItemId() const {
Expects(hasItems());
return _items.front()->id;
}
protected:
void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected) override;
void addActionRipple(QPoint point, base::lambda<void()> updateCallback) override;
void stopLastActionRipple() override;
private:
bool needsVerifiedIcon() const override {
return false;
}
QSize actionSize() const override {
return QSize(st::callReDial.width, st::callReDial.height);
}
QMargins actionMargins() const override {
return QMargins(0, 0, 0, 0);
}
void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override;
void refreshStatus();
static Type ComputeType(HistoryItem *item);
std::vector<HistoryItem*> _items;
QDate _date;
Type _type;
std::unique_ptr<Ui::RippleAnimation> _actionRipple;
};
BoxController::Row::Row(HistoryItem *item) : PeerListBox::Row(item->history()->peer, item->id)
, _items(1, item)
, _date(item->date.date())
, _type(ComputeType(item)) {
refreshStatus();
}
void BoxController::Row::paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected) {
auto &icon = ([this] {
switch (_type) {
case Type::In: return st::callArrowIn;
case Type::Out: return st::callArrowOut;
case Type::Missed: return st::callArrowMissed;
}
Unexpected("_type in Calls::BoxController::Row::paintStatusText().");
})();
icon.paint(p, x + st::callArrowPosition.x(), y + st::callArrowPosition.y(), outerWidth);
x += + st::callArrowPosition.x() + icon.width() + st::callArrowSkip;
PeerListBox::Row::paintStatusText(p, x, y, outerWidth, selected);
}
void BoxController::Row::paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) {
auto size = actionSize();
if (_actionRipple) {
_actionRipple->paint(p, x + st::callReDial.rippleAreaPosition.x(), y + st::callReDial.rippleAreaPosition.y(), outerWidth, ms);
if (_actionRipple->empty()) {
_actionRipple.reset();
}
}
st::callReDial.icon.paintInCenter(p, rtlrect(x, y, size.width(), size.height(), outerWidth));
}
void BoxController::Row::refreshStatus() {
if (!hasItems()) {
return;
}
auto text = [this] {
auto time = _items.front()->date.time().toString(cTimeFormat());
auto today = QDateTime::currentDateTime().date();
if (_date == today) {
return lng_call_box_status_today(lt_time, time);
} else if (_date.addDays(1) == today) {
return lng_call_box_status_yesterday(lt_time, time);
}
return lng_call_box_status_date(lt_date, langDayOfMonthFull(_date), lt_time, time);
};
setCustomStatus((_items.size() > 1) ? lng_call_box_status_group(lt_count, QString::number(_items.size()), lt_status, text()) : text());
}
BoxController::Row::Type BoxController::Row::ComputeType(HistoryItem *item) {
if (item->out()) {
return Type::Out;
} else if (auto call = item->Get<HistoryMessageCallInfo>()) {
using Reason = HistoryMessageCallInfo::Reason;
if (call->reason == Reason::Busy || call->reason == Reason::Missed) {
return Type::Missed;
}
}
return Type::In;
}
void BoxController::Row::addActionRipple(QPoint point, base::lambda<void()> updateCallback) {
if (!_actionRipple) {
auto mask = Ui::RippleAnimation::ellipseMask(QSize(st::callReDial.rippleAreaSize, st::callReDial.rippleAreaSize));
_actionRipple = std::make_unique<Ui::RippleAnimation>(st::callReDial.ripple, std::move(mask), std::move(updateCallback));
}
_actionRipple->add(point - st::callReDial.rippleAreaPosition);
}
void BoxController::Row::stopLastActionRipple() {
if (_actionRipple) {
_actionRipple->lastStop();
}
}
void BoxController::prepare() {
subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) {
if (auto row = rowForItem(item)) {
row->itemRemoved(item);
if (!row->hasItems()) {
view()->removeRow(row);
if (!view()->fullRowsCount()) {
refreshAbout();
}
}
view()->refreshRows();
}
});
view()->setTitle(lang(lng_call_box_title));
view()->addButton(lang(lng_close), [this] { view()->closeBox(); });
view()->setAboutText(lang(lng_contacts_loading));
view()->refreshRows();
preloadRows();
}
void BoxController::preloadRows() {
if (_loadRequestId || _allLoaded) {
return;
}
_loadRequestId = request(MTPmessages_Search(MTP_flags(0), MTP_inputPeerEmpty(), MTP_string(QString()), MTP_inputMessagesFilterPhoneCalls(MTP_flags(0)), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(_offsetId), MTP_int(_offsetId ? kFirstPageCount : kPerPageCount))).done([this](const MTPmessages_Messages &result) {
_loadRequestId = 0;
auto handleResult = [this](auto &data) {
App::feedUsers(data.vusers);
App::feedChats(data.vchats);
receivedCalls(data.vmessages.v);
};
switch (result.type()) {
case mtpc_messages_messages: handleResult(result.c_messages_messages()); _allLoaded = true; break;
case mtpc_messages_messagesSlice: handleResult(result.c_messages_messagesSlice()); break;
case mtpc_messages_channelMessages: {
LOG(("API Error: received messages.channelMessages! (Calls::BoxController::preloadRows)"));
handleResult(result.c_messages_channelMessages());
} break;
default: Unexpected("Type of messages.Messages (Calls::BoxController::preloadRows)");
}
}).fail([this](const RPCError &error) {
_loadRequestId = 0;
}).send();
}
void BoxController::refreshAbout() {
view()->setAboutText(view()->fullRowsCount() ? QString() : lang(lng_call_box_about));
}
void BoxController::rowClicked(PeerListBox::Row *row) {
auto itemsRow = static_cast<Row*>(row);
auto itemId = itemsRow->maxItemId();
Ui::showPeerHistoryAsync(row->peer()->id, itemId);
}
void BoxController::rowActionClicked(PeerListBox::Row *row) {
auto user = row->peer()->asUser();
Expects(user != nullptr);
Current().startOutgoingCall(user);
}
void BoxController::receivedCalls(const QVector<MTPMessage> &result) {
if (result.empty()) {
_allLoaded = true;
}
for_const (auto &message, result) {
auto msgId = idFromMessage(message);
auto peerId = peerFromMessage(message);
if (auto peer = App::peerLoaded(peerId)) {
auto item = App::histories().addNewMessage(message, NewMessageExisting);
appendRow(item);
} else {
LOG(("API Error: a search results with not loaded peer %1").arg(peerId));
}
_offsetId = msgId;
}
refreshAbout();
view()->refreshRows();
}
bool BoxController::appendRow(HistoryItem *item) {
if (auto row = rowForItem(item)) {
row->addItem(item);
return false;
}
view()->appendRow(createRow(item));
view()->reorderRows([](auto &begin, auto &end) {
std::sort(begin, end, [](auto &a, auto &b) {
return static_cast<Row&>(*a).maxItemId() > static_cast<Row&>(*a).maxItemId();
});
});
return true;
}
bool BoxController::prependRow(HistoryItem *item) {
if (auto row = rowForItem(item)) {
row->addItem(item);
return false;
}
view()->prependRow(createRow(item));
return true;
}
BoxController::Row *BoxController::rowForItem(HistoryItem *item) {
auto v = view();
auto checkForReturn = [item](Row *row) {
return row->canAddItem(item) ? row : nullptr;
};
if (auto fullRowsCount = v->fullRowsCount()) {
auto itemId = item->id;
auto lastRow = static_cast<Row*>(v->rowAt(fullRowsCount - 1));
if (itemId < lastRow->minItemId()) {
return checkForReturn(lastRow);
}
auto firstRow = static_cast<Row*>(v->rowAt(0));
if (itemId > firstRow->maxItemId()) {
return checkForReturn(firstRow);
}
// Binary search. Invariant:
// 1. rowAt(left)->maxItemId() >= itemId.
// 2. (left + 1 == fullRowsCount) OR rowAt(left + 1)->maxItemId() < itemId.
auto left = 0;
auto right = fullRowsCount;
while (left + 1 < right) {
auto middle = (right + left) / 2;
auto middleRow = static_cast<Row*>(v->rowAt(middle));
if (middleRow->maxItemId() >= itemId) {
left = middle;
} else {
right = middle;
}
}
auto result = static_cast<Row*>(v->rowAt(left));
// Check for rowAt(left)->minItemId > itemId > rowAt(left + 1)->maxItemId.
// In that case we sometimes need to return rowAt(left + 1), not rowAt(left).
if (result->minItemId() > itemId && left + 1 < fullRowsCount) {
auto possibleResult = static_cast<Row*>(v->rowAt(left + 1));
t_assert(possibleResult->maxItemId() < itemId);
if (possibleResult->canAddItem(item)) {
return possibleResult;
}
}
return checkForReturn(result);
}
return nullptr;
}
std::unique_ptr<PeerListBox::Row> BoxController::createRow(HistoryItem *item) const {
auto row = std::make_unique<Row>(item);
return std::move(row);
}
} // namespace Calls

View File

@ -0,0 +1,51 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "boxes/peer_list_box.h"
namespace Calls {
class BoxController : public PeerListBox::Controller, private base::Subscriber, private MTP::Sender {
public:
void prepare() override;
void rowClicked(PeerListBox::Row *row) override;
void rowActionClicked(PeerListBox::Row *row) override;
void preloadRows() override;
private:
void receivedCalls(const QVector<MTPMessage> &result);
void refreshAbout();
class Row;
Row *rowForItem(HistoryItem *item);
bool appendRow(HistoryItem *item);
bool prependRow(HistoryItem *item);
std::unique_ptr<PeerListBox::Row> createRow(HistoryItem *item) const;
MsgId _offsetId = 0;
mtpRequestId _loadRequestId = 0;
bool _allLoaded = false;
};
} // namespace Calls

View File

@ -366,7 +366,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
return;
}
voip_config_t config;
voip_config_t config = { 0 };
config.data_saving = DATA_SAVING_NEVER;
config.enableAEC = true;
config.enableNS = true;

View File

@ -1504,31 +1504,28 @@ bool DialogsInner::searchReceived(const QVector<MTPMessage> &messages, DialogsSe
TimeId lastDateFound = 0;
for_const (auto message, messages) {
if (auto msgId = idFromMessage(message)) {
auto peerId = peerFromMessage(message);
auto lastDate = dateFromMessage(message);
if (auto peer = App::peerLoaded(peerId)) {
if (lastDate) {
auto item = App::histories().addNewMessage(message, NewMessageExisting);
_searchResults.push_back(std::make_unique<Dialogs::FakeRow>(item));
lastDateFound = lastDate;
if (isGlobalSearch) {
_lastSearchDate = lastDateFound;
}
}
auto msgId = idFromMessage(message);
auto peerId = peerFromMessage(message);
auto lastDate = dateFromMessage(message);
if (auto peer = App::peerLoaded(peerId)) {
if (lastDate) {
auto item = App::histories().addNewMessage(message, NewMessageExisting);
_searchResults.push_back(std::make_unique<Dialogs::FakeRow>(item));
lastDateFound = lastDate;
if (isGlobalSearch) {
_lastSearchPeer = peer;
_lastSearchDate = lastDateFound;
}
} else {
LOG(("API Error: a search results with not loaded peer %1").arg(peerId));
}
if (isMigratedSearch) {
_lastSearchMigratedId = msgId;
} else {
_lastSearchId = msgId;
if (isGlobalSearch) {
_lastSearchPeer = peer;
}
} else {
LOG(("API Error: a search results with not message id"));
LOG(("API Error: a search results with not loaded peer %1").arg(peerId));
}
if (isMigratedSearch) {
_lastSearchMigratedId = msgId;
} else {
_lastSearchId = msgId;
}
}
if (isMigratedSearch) {

View File

@ -396,6 +396,13 @@ historyUnreadBarFont: semiboldFont;
historyForwardChooseMargins: margins(30px, 10px, 30px, 10px);
historyForwardChooseFont: font(16px);
historyCallArrowIn: icon {{ "call_arrow_in", historyCallArrowInFg }};
historyCallArrowInSelected: icon {{ "call_arrow_in", historyCallArrowInFgSelected }};
historyCallArrowMissedIn: icon {{ "call_arrow_in", historyCallArrowMissedInFg }};
historyCallArrowMissedInSelected: icon {{ "call_arrow_in", historyCallArrowMissedInFgSelected }};
historyCallArrowOut: icon {{ "call_arrow_out", historyCallArrowOutFg }};
historyCallArrowOutSelected: icon {{ "call_arrow_out", historyCallArrowOutFgSelected }};
msgFileMenuSize: size(36px, 36px);
msgFileSize: 44px;
msgFilePadding: margins(14px, 12px, 11px, 12px);

View File

@ -413,6 +413,15 @@ struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar
};
struct HistoryMessageCallInfo : public RuntimeComponent<HistoryMessageCallInfo> {
enum class Reason {
None,
Missed,
Busy,
};
Reason reason = Reason::None;
};
// HistoryMedia has a special owning smart pointer
// which regs/unregs this media to the holding HistoryItem
class HistoryMedia;

View File

@ -1987,20 +1987,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
}
return lng_duration_seconds(lt_count, duration);
})();
auto wasMissed = [&action] {
if (action.has_reason()) {
return (action.vreason.type() == mtpc_phoneCallDiscardReasonMissed);
}
return false;
};
auto wasBusy = [&action] {
if (action.has_reason()) {
return (action.vreason.type() == mtpc_phoneCallDiscardReasonBusy);
}
return false;
};
auto info = this->Get<HistoryMessageCallInfo>();
if (out()) {
if (wasMissed()) {
if (info && info->reason == HistoryMessageCallInfo::Reason::Missed) {
result.text = lng_action_call_outgoing_missed(lt_time, timeText);
} else if (duration) {
result.text = lng_action_call_outgoing_duration(lt_duration, durationText, lt_time, timeText);
@ -2008,9 +1997,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
result.text = lng_action_call_outgoing(lt_time, timeText);
}
} else {
if (wasMissed()) {
if (info && info->reason == HistoryMessageCallInfo::Reason::Missed) {
result.text = lng_action_call_incoming_missed(lt_time, timeText);
} else if (wasBusy()) {
} else if (info && info->reason == HistoryMessageCallInfo::Reason::Busy) {
result.text = lng_action_call_incoming_declined(lt_time, timeText);
} else if (duration) {
result.text = lng_action_call_incoming_duration(lt_duration, durationText, lt_time, timeText);
@ -2449,6 +2438,22 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
auto amount = message.vaction.c_messageActionPaymentSent().vtotal_amount.v;
auto currency = qs(message.vaction.c_messageActionPaymentSent().vcurrency);
Get<HistoryServicePayment>()->amount = HistoryInvoice::fillAmountAndCurrency(amount, currency);
} else if (message.vaction.type() == mtpc_messageActionPhoneCall) {
using Reason = HistoryMessageCallInfo::Reason;
auto &action = message.vaction.c_messageActionPhoneCall();
auto reason = ([&action] {
if (action.has_reason()) {
switch (action.vreason.type()) {
case mtpc_phoneCallDiscardReasonBusy: return Reason::Busy;
case mtpc_phoneCallDiscardReasonMissed: return Reason::Missed;
}
}
return Reason::None;
})();
if (reason != Reason::None) {
UpdateComponents(HistoryMessageCallInfo::Bit());
Get<HistoryMessageCallInfo>()->reason = reason;
}
}
if (message.has_reply_to_msg_id()) {
if (message.vaction.type() == mtpc_messageActionPinMessage) {

View File

@ -2842,10 +2842,9 @@ void MainWidget::jumpToDate(PeerData *peer, const QDate &date) {
if (auto list = getMessagesList()) {
App::feedMsgs(*list, NewMessageExisting);
for (auto &message : *list) {
if (auto id = idFromMessage(message)) {
Ui::showPeerHistory(peer, id);
return;
}
auto id = idFromMessage(message);
Ui::showPeerHistory(peer, id);
return;
}
}
Ui::showPeerHistory(peer, ShowAtUnreadMsgId);

View File

@ -253,9 +253,9 @@ void OverviewInner::searchReceived(SearchRequestType type, const MTPmessages_Mes
if (type == SearchMigratedFromStart) {
_lastSearchMigratedId = 0;
}
for (QVector<MTPMessage>::const_iterator i = messages->cbegin(), e = messages->cend(); i != e; ++i) {
HistoryItem *item = App::histories().addNewMessage(*i, NewMessageExisting);
MsgId msgId = item ? item->id : idFromMessage(*i);
for (auto i = messages->cbegin(), e = messages->cend(); i != e; ++i) {
auto item = App::histories().addNewMessage(*i, NewMessageExisting);
auto msgId = item ? item->id : idFromMessage(*i);
if (migratedSearch) {
if (item) _searchResults.push_front(-item->id);
_lastSearchMigratedId = msgId;

View File

@ -124,7 +124,7 @@ void BlockedBoxController::rowActionClicked(PeerListBox::Row *row) {
}
bool BlockedBoxController::appendRow(UserData *user) {
if (view()->findRow(user)) {
if (view()->findRow(user->id)) {
return false;
}
view()->appendRow(createRow(user));
@ -132,7 +132,7 @@ bool BlockedBoxController::appendRow(UserData *user) {
}
bool BlockedBoxController::prependRow(UserData *user) {
if (view()->findRow(user)) {
if (view()->findRow(user->id)) {
return false;
}
view()->prependRow(createRow(user));
@ -140,7 +140,7 @@ bool BlockedBoxController::prependRow(UserData *user) {
}
std::unique_ptr<PeerListBox::Row> BlockedBoxController::createRow(UserData *user) const {
auto row = std::make_unique<PeerListBox::Row>(user);
auto row = std::make_unique<PeerListRowWithLink>(user);
row->setActionLink(lang(lng_blocked_list_unblock));
auto status = [user]() -> QString {
if (user->botInfo) {
@ -151,7 +151,7 @@ std::unique_ptr<PeerListBox::Row> BlockedBoxController::createRow(UserData *user
return App::formatPhone(user->phone());
};
row->setCustomStatus(status());
return row;
return std::move(row);
}
} // namespace

View File

@ -56,7 +56,7 @@ void BlockUserBoxController::prepareViewHook() {
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::UserIsBlocked, [this](const Notify::PeerUpdate &update) {
if (auto user = update.peer->asUser()) {
if (auto row = view()->findRow(user)) {
if (auto row = view()->findRow(user->id)) {
updateIsBlocked(row, user);
view()->updateRow(row);
}
@ -177,7 +177,7 @@ void BlockedBoxController::handleBlockedEvent(UserData *user) {
view()->refreshRows();
view()->onScrollToY(0);
}
} else if (auto row = view()->findRow(user)) {
} else if (auto row = view()->findRow(user->id)) {
view()->removeRow(row);
view()->refreshRows();
}
@ -188,7 +188,7 @@ void BlockedBoxController::blockUser() {
}
bool BlockedBoxController::appendRow(UserData *user) {
if (view()->findRow(user)) {
if (view()->findRow(user->id)) {
return false;
}
view()->appendRow(createRow(user));
@ -196,7 +196,7 @@ bool BlockedBoxController::appendRow(UserData *user) {
}
bool BlockedBoxController::prependRow(UserData *user) {
if (view()->findRow(user)) {
if (view()->findRow(user->id)) {
return false;
}
view()->prependRow(createRow(user));
@ -204,7 +204,7 @@ bool BlockedBoxController::prependRow(UserData *user) {
}
std::unique_ptr<PeerListBox::Row> BlockedBoxController::createRow(UserData *user) const {
auto row = std::make_unique<PeerListBox::Row>(user);
auto row = std::make_unique<PeerListRowWithLink>(user);
row->setActionLink(lang(lng_blocked_list_unblock));
auto status = [user]() -> QString {
if (user->botInfo) {
@ -215,7 +215,7 @@ std::unique_ptr<PeerListBox::Row> BlockedBoxController::createRow(UserData *user
return App::formatPhone(user->phone());
};
row->setCustomStatus(status());
return row;
return std::move(row);
}
MTPInputPrivacyKey LastSeenPrivacyController::key() {

View File

@ -154,7 +154,7 @@ inline MsgId idFromMessage(const MTPmessage &msg) {
case mtpc_message: return msg.c_message().vid.v;
case mtpc_messageService: return msg.c_messageService().vid.v;
}
return 0;
Unexpected("Type in idFromMessage()");
}
inline TimeId dateFromMessage(const MTPmessage &msg) {
switch (msg.type()) {

View File

@ -94,30 +94,30 @@ titleUnreadCounterTop: 5px;
titleUnreadCounterRight: 35px;
mainMenuWidth: 274px;
mainMenuCoverHeight: 140px;
mainMenuCoverHeight: 134px;
mainMenuUserpicLeft: 24px;
mainMenuUserpicTop: 22px;
mainMenuUserpicTop: 20px;
mainMenuUserpicSize: 48px;
mainMenuCloudButton: IconButton {
width: 68px;
height: 68px;
width: 64px;
height: 64px;
icon: icon {
{ "menu_cloud", mainMenuCloudFg },
};
iconPosition: point(24px, 24px);
iconPosition: point(22px, 22px);
}
mainMenuCloudSize: 32px;
mainMenuCoverTextLeft: 30px;
mainMenuCoverNameTop: 88px;
mainMenuCoverStatusTop: 106px;
mainMenuCoverNameTop: 84px;
mainMenuCoverStatusTop: 102px;
mainMenuSkip: 13px;
mainMenu: Menu(defaultMenu) {
itemFg: windowBoldFg;
itemFgOver: windowBoldFgOver;
itemFont: semiboldFont;
itemIconPosition: point(28px, 11px);
itemPadding: margins(76px, 14px, 28px, 14px);
itemIconPosition: point(28px, 10px);
itemPadding: margins(76px, 13px, 28px, 13px);
}
mainMenuNewGroup: icon {{ "menu_new_group", menuIconFg }};
mainMenuNewGroupOver: icon {{ "menu_new_group", menuIconFgOver }};
@ -125,6 +125,8 @@ mainMenuNewChannel: icon {{ "menu_new_channel", menuIconFg }};
mainMenuNewChannelOver: icon {{ "menu_new_channel", menuIconFgOver }};
mainMenuContacts: icon {{ "menu_contacts", menuIconFg }};
mainMenuContactsOver: icon {{ "menu_contacts", menuIconFgOver }};
mainMenuCalls: icon {{ "menu_calls", menuIconFg }};
mainMenuCallsOver: icon {{ "menu_calls", menuIconFgOver }};
mainMenuSettings: icon {{ "menu_settings", menuIconFg }};
mainMenuSettingsOver: icon {{ "menu_settings", menuIconFgOver }};
mainMenuHelp: icon {{ "menu_help", menuIconFg }};

View File

@ -29,6 +29,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "boxes/contacts_box.h"
#include "boxes/about_box.h"
#include "boxes/peer_list_box.h"
#include "calls/calls_box_controller.h"
#include "lang.h"
#include "core/click_handler_types.h"
#include "observer_peer.h"
@ -61,6 +63,9 @@ MainMenu::MainMenu(QWidget *parent) : TWidget(parent)
_menu->addAction(lang(lng_menu_contacts), [] {
Ui::show(Box<ContactsBox>());
}, &st::mainMenuContacts, &st::mainMenuContactsOver);
_menu->addAction(lang(lng_menu_calls), [] {
Ui::show(Box<PeerListBox>(std::make_unique<Calls::BoxController>()));
}, &st::mainMenuCalls, &st::mainMenuCallsOver);
_menu->addAction(lang(lng_menu_settings), [] {
App::wnd()->showSettings();
}, &st::mainMenuSettings, &st::mainMenuSettingsOver);

View File

@ -80,6 +80,8 @@
<(src_loc)/boxes/stickers_box.h
<(src_loc)/boxes/username_box.cpp
<(src_loc)/boxes/username_box.h
<(src_loc)/calls/calls_box_controller.cpp
<(src_loc)/calls/calls_box_controller.h
<(src_loc)/calls/calls_call.cpp
<(src_loc)/calls/calls_call.h
<(src_loc)/calls/calls_emoji_fingerprint.cpp