From 06b081f50950423194bc5dd8d2f3c11752cc7364 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 27 Apr 2017 22:04:45 +0300 Subject: [PATCH] Add calls log box. PeerListBox can have many rows with the same PeerData. PeerListBox::Row can have arbitrary action on the right side. --- Telegram/Resources/colors.palette | 39 +- Telegram/Resources/icons/call_arrow_in.png | Bin 0 -> 209 bytes Telegram/Resources/icons/call_arrow_in@2x.png | Bin 0 -> 306 bytes Telegram/Resources/icons/call_arrow_out.png | Bin 0 -> 211 bytes .../Resources/icons/call_arrow_out@2x.png | Bin 0 -> 298 bytes Telegram/Resources/icons/menu_calls.png | Bin 0 -> 399 bytes Telegram/Resources/icons/menu_calls@2x.png | Bin 0 -> 793 bytes Telegram/Resources/langs/lang.strings | 8 + Telegram/SourceFiles/boxes/peer_list_box.cpp | 184 ++++++---- Telegram/SourceFiles/boxes/peer_list_box.h | 76 +++- Telegram/SourceFiles/calls/calls.style | 19 + .../calls/calls_box_controller.cpp | 345 ++++++++++++++++++ .../SourceFiles/calls/calls_box_controller.h | 51 +++ Telegram/SourceFiles/calls/calls_call.cpp | 2 +- Telegram/SourceFiles/dialogswidget.cpp | 37 +- Telegram/SourceFiles/history/history.style | 7 + Telegram/SourceFiles/history/history_item.h | 9 + .../SourceFiles/history/history_message.cpp | 35 +- Telegram/SourceFiles/mainwidget.cpp | 7 +- Telegram/SourceFiles/overviewwidget.cpp | 6 +- .../profile/profile_block_settings.cpp | 8 +- .../settings/settings_privacy_controllers.cpp | 12 +- Telegram/SourceFiles/structs.h | 2 +- Telegram/SourceFiles/window/window.style | 20 +- .../SourceFiles/window/window_main_menu.cpp | 5 + Telegram/gyp/telegram_sources.txt | 2 + 26 files changed, 709 insertions(+), 165 deletions(-) create mode 100644 Telegram/Resources/icons/call_arrow_in.png create mode 100644 Telegram/Resources/icons/call_arrow_in@2x.png create mode 100644 Telegram/Resources/icons/call_arrow_out.png create mode 100644 Telegram/Resources/icons/call_arrow_out@2x.png create mode 100644 Telegram/Resources/icons/menu_calls.png create mode 100644 Telegram/Resources/icons/menu_calls@2x.png create mode 100644 Telegram/SourceFiles/calls/calls_box_controller.cpp create mode 100644 Telegram/SourceFiles/calls/calls_box_controller.h diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette index 8e9e642684..b90d21dab5 100644 --- a/Telegram/Resources/colors.palette +++ b/Telegram/Resources/colors.palette @@ -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 diff --git a/Telegram/Resources/icons/call_arrow_in.png b/Telegram/Resources/icons/call_arrow_in.png new file mode 100644 index 0000000000000000000000000000000000000000..a5331ec8533a5c9375d6dff3d76ddd5f61435453 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Gdx`!Lo9liPCm_hM1jX;?p3h| zK^hH*ueeCdtY~8r;0@rC_3`s8JLxXq_UY&&+nxqj7Y*T`XAZ1qQDiAR_xJ0KXhoJu zOTPcxZqF*u6spRw>J>)~2g3vv#-0P`oA`djUEqx8W_z$%;kgad=i7E7*=v_fQ8}i; z@U2eMHEZqLB1_+b>EFt>MQtlyu}UIYue@Sbg7}`ZU-DxZ=g6>Kp0@I+1<;KQp00i_ I>zopr0MSQIivR!s literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/call_arrow_in@2x.png b/Telegram/Resources/icons/call_arrow_in@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..59c1fe963a3a9db26b963b42b97522f59727197c GIT binary patch literal 306 zcmV-20nPr2P)50?Ww7Nw}B~?*Yl6A|g=6(mCO` zz$F4&Yntbo>bm}zfz;4iV_6mez;PUCn#Prrho3bJ!!-gf_&APGO34cZ!qW;wz|#pt z!rcPK7%(%K8O9h%xLbgk-wQFbBpiP=qO-|VRrUThO%t_kO91@V$O(@YNCl4)NC^)X zNDU7Xkbrv&NWr}XB;jX$ZAAONM>iRt@$&?Vq7d6T5z)gpxNRG)>q>p!f6ZBI$y%FU zpe#%3y3Q*bB9Q&B*m(jWJNS35qSu?8tFm9gEs$N{3Ad99X@Dq=)c^nh07*qoM6N<$ Ef)*5jDgXcg literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/call_arrow_out.png b/Telegram/Resources/icons/call_arrow_out.png new file mode 100644 index 0000000000000000000000000000000000000000..dd334ebe8bc65f9c51ab989e10b846c9c1fad232 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`vpiiKLo9liPCm$cSb@j2eih$? zK#7Lt$WBp~Iq=A~R(T_1fXVo)^z literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/call_arrow_out@2x.png b/Telegram/Resources/icons/call_arrow_out@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..61830afb5ee02000ee6ad63a20c0259391557a5d GIT binary patch literal 298 zcmV+_0oDGAP)+_OUAnH* z&oS1#2_k}&63Vjtj4jJ@KQ9)1nkMM`KEA*iysm2?x~~~80bjV6KnS?AKq$DAKuEZ? zKxnv?Kn%FCKrA?bK^OSIzq{kAN372FaU6*;7OX!`1rova0tw+qfyD5mfRr*N{3sxV wpmkkS!S(+@RaG<$Lkc*t%_3K_--;iBH+t{R#GxJ%X8-^I07*qoM6N<$f9pZ)u8_P@JQ&MTq9;281rat%h!+=q_3sf-XRamc}4jR0x-#F2NdH;(vyN zQYf5`d>%Ltyo)}(Kk!qmi3n0*RF%vB9ypy&0=$jmIQjtFwsVDEucr@qJRZ4%VHo;= z+wGPsbh}-BfbaXcLciaaMx&tz*zfnbLJ$O!BuQyNaW#NairsDpzGYks8C{5F|<3r(av4U1*wyEYdX1cKp8L2q=BPFbp7st-=IBfYoZndc#~5)uFEI z%t=m9PhBzNL{xz3bjqCM;^M*;GfqSW7z_r?Ny6cy&1Q4w^cjW$`F#FMtCriM(kY6v_0BJsOU!1oTwf)&0{H!YoX_V} z^SNA(xB2Y=0D!NrueRo8S>|n=4L}q{3&7*!BX85}004mR@9!G`s;VL(gtu{afOI-- znFUFbcpGO2AcSD6)!G2)_xl(K1bCZg0{{Sw$K$wME;k^m)#^_uCH9k_pPwzIR45d@ z18{P3f}Kuh13=exyuH2k1_1mnrN5ioWHP~IGU*Kf06eVHs$ z`#Bp<%$xdW0A|RtjI-Iy_Ts9lVyRSeY<@2Q0PFAQcDwD^BzFJ+K%dWtPft&l*ACAB z{5czqM#H-RX58J~p{lCh1u#PtMSOXAp+4w89>5I0-;c5^$W+#0=S|7cbC#DIsovN=K+2J XDtet?TWTaQ00000NkvXXu0mjfmPBhG literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9035d57f76..60cae09bbd 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 81c4c3f0b4..16795f1d6e 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -124,8 +124,8 @@ void PeerListBox::prependRow(std::unique_ptr 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 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) { - 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) { void PeerListBox::Inner::appendGlobalSearchRow(std::unique_ptr 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) { - 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 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 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)); return false; } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 1c16018b0e..ba078eae47 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -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 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 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 _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 _nameFirstChars; @@ -187,14 +205,17 @@ public: // Interface for the controller. void appendRow(std::unique_ptr row); void prependRow(std::unique_ptr 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 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); void prependRow(std::unique_ptr 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 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> _rows; - std::map _rowsByPeer; + std::map _rowsById; + std::map> _rowsByPeer; SearchMode _searchMode = SearchMode::None; std::map> _searchIndex; @@ -418,6 +442,26 @@ inline void PeerListBox::reorderRows(ReorderCallback &&callback) { _inner->reorderRows(std::forward(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; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index ef18619d98..5780c40ca1 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -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; +} diff --git a/Telegram/SourceFiles/calls/calls_box_controller.cpp b/Telegram/SourceFiles/calls/calls_box_controller.cpp new file mode 100644 index 0000000000..b08be61fbc --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_box_controller.cpp @@ -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 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 _items; + QDate _date; + Type _type; + + std::unique_ptr _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()) { + 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 updateCallback) { + if (!_actionRipple) { + auto mask = Ui::RippleAnimation::ellipseMask(QSize(st::callReDial.rippleAreaSize, st::callReDial.rippleAreaSize)); + _actionRipple = std::make_unique(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); + 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 &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(*a).maxItemId() > static_cast(*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(v->rowAt(fullRowsCount - 1)); + if (itemId < lastRow->minItemId()) { + return checkForReturn(lastRow); + } + auto firstRow = static_cast(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(v->rowAt(middle)); + if (middleRow->maxItemId() >= itemId) { + left = middle; + } else { + right = middle; + } + } + auto result = static_cast(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(v->rowAt(left + 1)); + t_assert(possibleResult->maxItemId() < itemId); + if (possibleResult->canAddItem(item)) { + return possibleResult; + } + } + return checkForReturn(result); + } + return nullptr; +} + +std::unique_ptr BoxController::createRow(HistoryItem *item) const { + auto row = std::make_unique(item); + return std::move(row); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_box_controller.h b/Telegram/SourceFiles/calls/calls_box_controller.h new file mode 100644 index 0000000000..8314f0ff44 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_box_controller.h @@ -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 &result); + void refreshAbout(); + + class Row; + Row *rowForItem(HistoryItem *item); + + bool appendRow(HistoryItem *item); + bool prependRow(HistoryItem *item); + std::unique_ptr createRow(HistoryItem *item) const; + + MsgId _offsetId = 0; + mtpRequestId _loadRequestId = 0; + bool _allLoaded = false; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 57b013106e..0c73952f2c 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -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; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 2461242ad5..6666183703 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1504,31 +1504,28 @@ bool DialogsInner::searchReceived(const QVector &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(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(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) { diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index e3305cc9df..6a2c975495 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -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); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index fc36358eaf..b307baf868 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -413,6 +413,15 @@ struct HistoryMessageUnreadBar : public RuntimeComponent { + 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; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 70849ba345..739220788d 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -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(); 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()->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()->reason = reason; + } } if (message.has_reply_to_msg_id()) { if (message.vaction.type() == mtpc_messageActionPinMessage) { diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 327b6fe3f2..da53c3d22c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -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); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index d64dd2232d..607b0db8df 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -253,9 +253,9 @@ void OverviewInner::searchReceived(SearchRequestType type, const MTPmessages_Mes if (type == SearchMigratedFromStart) { _lastSearchMigratedId = 0; } - for (QVector::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; diff --git a/Telegram/SourceFiles/profile/profile_block_settings.cpp b/Telegram/SourceFiles/profile/profile_block_settings.cpp index 537589fe65..c65d0cd1bd 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.cpp +++ b/Telegram/SourceFiles/profile/profile_block_settings.cpp @@ -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 BlockedBoxController::createRow(UserData *user) const { - auto row = std::make_unique(user); + auto row = std::make_unique(user); row->setActionLink(lang(lng_blocked_list_unblock)); auto status = [user]() -> QString { if (user->botInfo) { @@ -151,7 +151,7 @@ std::unique_ptr BlockedBoxController::createRow(UserData *user return App::formatPhone(user->phone()); }; row->setCustomStatus(status()); - return row; + return std::move(row); } } // namespace diff --git a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp index f712de91bd..00fc8e8693 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_controllers.cpp @@ -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 BlockedBoxController::createRow(UserData *user) const { - auto row = std::make_unique(user); + auto row = std::make_unique(user); row->setActionLink(lang(lng_blocked_list_unblock)); auto status = [user]() -> QString { if (user->botInfo) { @@ -215,7 +215,7 @@ std::unique_ptr BlockedBoxController::createRow(UserData *user return App::formatPhone(user->phone()); }; row->setCustomStatus(status()); - return row; + return std::move(row); } MTPInputPrivacyKey LastSeenPrivacyController::key() { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index e660fb7f25..50d55f4465 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -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()) { diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 5271edef4f..309b4cbb2e 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -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 }}; diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index c2a2be948f..ecfa3b155d 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -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()); }, &st::mainMenuContacts, &st::mainMenuContactsOver); + _menu->addAction(lang(lng_menu_calls), [] { + Ui::show(Box(std::make_unique())); + }, &st::mainMenuCalls, &st::mainMenuCallsOver); _menu->addAction(lang(lng_menu_settings), [] { App::wnd()->showSettings(); }, &st::mainMenuSettings, &st::mainMenuSettingsOver); diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 5d9aba1db5..2bf7463b28 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -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