Redesign calls service messages.

This commit is contained in:
John Preston 2017-04-28 00:17:00 +03:00
parent 06b081f509
commit c4f90983af
23 changed files with 384 additions and 180 deletions

View File

@ -671,13 +671,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_action_game_you_scored" = "You scored {count:#|#|#} in {game}";
"lng_action_game_score_no_game" = "{from} scored {count:#|#|#}";
"lng_action_game_you_scored_no_game" = "You scored {count:#|#|#}";
"lng_action_call_outgoing" = "Outgoing call at {time}";
"lng_action_call_incoming" = "Incoming call at {time}";
"lng_action_call_outgoing_duration" = "Outgoing call ({duration}) at {time}";
"lng_action_call_incoming_duration" = "Incoming call ({duration}) at {time}";
"lng_action_call_incoming_missed" = "Missed call at {time}";
"lng_action_call_outgoing_missed" = "Cancelled call at {time}";
"lng_action_call_incoming_declined" = "Declined call at {time}";
"lng_action_payment_done" = "You have just successfully transferred {amount} to {user}";
"lng_action_payment_done_for" = "You have just successfully transferred {amount} to {user} for {invoice}";
@ -1145,6 +1138,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_call_box_status_date" = "{date} at {time}";
"lng_call_box_status_group" = "({count}) {status}";
"lng_call_outgoing" = "Outgoing call";
"lng_call_incoming" = "Incoming call";
"lng_call_missed" = "Missed call";
"lng_call_cancelled" = "Cancelled call";
"lng_call_declined" = "Declined call";
"lng_call_duration_info" = "{time}, {duration}";
"lng_call_type_and_duration" = "{type} ({duration})";
// Not used
"lng_topbar_info" = "Info";

View File

@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "observer_peer.h"
#include "ui/effects/ripple_animation.h"
#include "calls/calls_instance.h"
#include "history/history_media_types.h"
namespace Calls {
namespace {
@ -86,7 +87,7 @@ private:
return false;
}
QSize actionSize() const override {
return QSize(st::callReDial.width, st::callReDial.height);
return peer()->isUser() ? QSize(st::callReDial.width, st::callReDial.height) : QSize();
}
QMargins actionMargins() const override {
return QMargins(0, 0, 0, 0);
@ -157,10 +158,12 @@ void BoxController::Row::refreshStatus() {
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;
} else if (auto media = item->getMedia()) {
if (media->type() == MediaTypeCall) {
auto reason = static_cast<HistoryCall*>(media)->reason();
if (reason == HistoryCall::FinishReason::Busy || reason == HistoryCall::FinishReason::Missed) {
return Type::Missed;
}
}
}
return Type::In;
@ -243,7 +246,7 @@ void BoxController::rowClicked(PeerListBox::Row *row) {
void BoxController::rowActionClicked(PeerListBox::Row *row) {
auto user = row->peer()->asUser();
Expects(user != nullptr);
t_assert(user != nullptr);
Current().startOutgoingCall(user);
}
@ -258,7 +261,7 @@ void BoxController::receivedCalls(const QVector<MTPMessage> &result) {
auto peerId = peerFromMessage(message);
if (auto peer = App::peerLoaded(peerId)) {
auto item = App::histories().addNewMessage(message, NewMessageExisting);
appendRow(item);
insertRow(item, InsertWay::Append);
} else {
LOG(("API Error: a search results with not loaded peer %1").arg(peerId));
}
@ -269,12 +272,14 @@ void BoxController::receivedCalls(const QVector<MTPMessage> &result) {
view()->refreshRows();
}
bool BoxController::appendRow(HistoryItem *item) {
bool BoxController::insertRow(HistoryItem *item, InsertWay way) {
if (auto row = rowForItem(item)) {
row->addItem(item);
return false;
if (row->canAddItem(item)) {
row->addItem(item);
return false;
}
}
view()->appendRow(createRow(item));
(way == InsertWay::Append) ? view()->appendRow(createRow(item)) : view()->prependRow(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();
@ -283,29 +288,17 @@ bool BoxController::appendRow(HistoryItem *item) {
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);
return lastRow;
}
auto firstRow = static_cast<Row*>(v->rowAt(0));
if (itemId > firstRow->maxItemId()) {
return checkForReturn(firstRow);
return firstRow;
}
// Binary search. Invariant:
@ -332,7 +325,7 @@ BoxController::Row *BoxController::rowForItem(HistoryItem *item) {
return possibleResult;
}
}
return checkForReturn(result);
return result;
}
return nullptr;
}

View File

@ -38,8 +38,11 @@ private:
class Row;
Row *rowForItem(HistoryItem *item);
bool appendRow(HistoryItem *item);
bool prependRow(HistoryItem *item);
enum class InsertWay {
Append,
Prepend,
};
bool insertRow(HistoryItem *item, InsertWay way);
std::unique_ptr<PeerListBox::Row> createRow(HistoryItem *item) const;
MsgId _offsetId = 0;

View File

@ -31,6 +31,7 @@ Instance::Instance() = default;
void Instance::startOutgoingCall(gsl::not_null<UserData*> user) {
if (_currentCall) {
_currentCallPanel->showAndActivate();
return; // Already in a call.
}
createCall(user, Call::Type::Outgoing);

View File

@ -835,7 +835,11 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
case mtpc_messageService: {
auto &m = msg.c_messageService();
result = HistoryService::create(this, m);
if (m.vaction.type() == mtpc_messageActionPhoneCall) {
result = HistoryMessage::create(this, m);
} else {
result = HistoryService::create(this, m);
}
if (applyServiceAction) {
auto &action = m.vaction;

View File

@ -110,6 +110,7 @@ enum HistoryMediaType {
MediaTypePhoto,
MediaTypeVideo,
MediaTypeContact,
MediaTypeCall,
MediaTypeFile,
MediaTypeGif,
MediaTypeSticker,

View File

@ -402,6 +402,18 @@ historyCallArrowMissedIn: icon {{ "call_arrow_in", historyCallArrowMissedInFg }}
historyCallArrowMissedInSelected: icon {{ "call_arrow_in", historyCallArrowMissedInFgSelected }};
historyCallArrowOut: icon {{ "call_arrow_out", historyCallArrowOutFg }};
historyCallArrowOutSelected: icon {{ "call_arrow_out", historyCallArrowOutFgSelected }};
historyCallWidth: 240px;
historyCallHeight: 56px;
historyCallInIcon: icon {{ "menu_calls", msgFileInBg }};
historyCallInIconSelected: icon {{ "menu_calls", msgFileInBgSelected }};
historyCallOutIcon: icon {{ "menu_calls", msgFileOutBg }};
historyCallOutIconSelected: icon {{ "menu_calls", msgFileOutBgSelected }};
historyCallIconPosition: point(17px, 18px);
historyCallLeft: 16px;
historyCallTop: 9px;
historyCallStatusTop: 29px;
historyCallStatusSkip: 4px;
historyCallArrowPosition: point(-1px, 1px);
msgFileMenuSize: size(36px, 36px);
msgFileSize: 44px;

View File

@ -1007,8 +1007,11 @@ void HistoryInner::onDragExec() {
auto drag = std::make_unique<QDrag>(App::wnd());
if (!urls.isEmpty()) mimeData->setUrls(urls);
if (uponSelected && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && !Adaptive::OneColumn()) {
mimeData->setData(qsl("application/x-td-forward-selected"), "1");
if (uponSelected && !Adaptive::OneColumn()) {
auto selectedState = getSelectionState();
if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
mimeData->setData(qsl("application/x-td-forward-selected"), "1");
}
}
drag->setMimeData(mimeData);
drag->exec(Qt::CopyAction);
@ -1213,9 +1216,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
dragActionUpdate(e->globalPos());
}
int32 selectedForForward, selectedForDelete;
getSelectionState(selectedForForward, selectedForDelete);
bool canSendMessages = _widget->canSendMessages(_peer);
auto selectedState = getSelectionState();
auto canSendMessages = _widget->canSendMessages(_peer);
// -2 - has full selected items, but not over, -1 - has selection, but no over, 0 - no selection, 1 - over text, 2 - over full selected items
int32 isUponSelected = 0, hasSelected = 0;;
@ -1296,8 +1298,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(lang(lng_context_copy_post_link), _widget, SLOT(onCopyPostLink()));
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
if (selectedForDelete == selectedForForward) {
if (selectedState.count > 0 && selectedState.canForwardCount == selectedState.count) {
_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
}
if (selectedState.count > 0 && selectedState.canDeleteCount == selectedState.count) {
_menu->addAction(lang(lng_context_delete_selected), base::lambda_guarded(this, [this] {
_widget->confirmDeleteSelectedItems();
}));
@ -1305,7 +1309,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem()) && App::hoveredLinkItem()->id > 0) {
if (App::hoveredLinkItem()->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
}
if (App::hoveredLinkItem()->canDelete()) {
@ -1321,9 +1325,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
} else { // maybe cursor on some text history item?
bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg());
bool canForward = item && (item->id > 0) && !item->serviceMsg();
bool canForward = item && item->canForward();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
auto msg = dynamic_cast<HistoryMessage*>(item);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
if (item && item->id > 0 && isUponSelected != 2) {
@ -1391,7 +1395,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
QString linkCopyToClipboardText = _contextMenuLnk ? _contextMenuLnk->copyToClipboardContextItemText() : QString();
auto linkCopyToClipboardText = _contextMenuLnk ? _contextMenuLnk->copyToClipboardContextItemText() : QString();
if (!linkCopyToClipboardText.isEmpty()) {
_menu->addAction(linkCopyToClipboardText, this, SLOT(copyContextUrl()))->setEnabled(true);
}
@ -1399,8 +1403,10 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(lang(lng_context_copy_post_link), _widget, SLOT(onCopyPostLink()));
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
if (selectedForDelete == selectedForForward) {
if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
_menu->addAction(lang(lng_context_forward_selected), _widget, SLOT(onForwardSelected()));
}
if (selectedState.count > 0 && selectedState.count == selectedState.canDeleteCount) {
_menu->addAction(lang(lng_context_delete_selected), base::lambda_guarded(this, [this] {
_widget->confirmDeleteSelectedItems();
}));
@ -1595,9 +1601,8 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
setToClipboard(getSelectedText(), QClipboard::FindBuffer);
#endif // Q_OS_MAC
} else if (e == QKeySequence::Delete) {
int32 selectedForForward, selectedForDelete;
getSelectionState(selectedForForward, selectedForDelete);
if (!_selected.isEmpty() && selectedForDelete == selectedForForward) {
auto selectedState = getSelectionState();
if (selectedState.count > 0 && selectedState.canDeleteCount == selectedState.count) {
_widget->confirmDeleteSelectedItems();
}
} else {
@ -1957,25 +1962,26 @@ bool HistoryInner::canCopySelected() const {
}
bool HistoryInner::canDeleteSelected() const {
if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return false;
int32 selectedForForward, selectedForDelete;
getSelectionState(selectedForForward, selectedForDelete);
return (selectedForForward == selectedForDelete);
auto selectedState = getSelectionState();
return (selectedState.count > 0) && (selectedState.count == selectedState.canDeleteCount);
}
void HistoryInner::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const {
selectedForForward = selectedForDelete = 0;
Window::TopBarWidget::SelectedState HistoryInner::getSelectionState() const {
auto result = Window::TopBarWidget::SelectedState {};
for (auto i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
if (i.value() == FullSelection) {
++result.count;
if (i.key()->canDelete()) {
++selectedForDelete;
++result.canDeleteCount;
}
++selectedForForward;
if (i.key()->canForward()) {
++result.canForwardCount;
}
} else {
result.textSelected = true;
}
}
if (!selectedForDelete && !selectedForForward && !_selected.isEmpty()) { // text selection
selectedForForward = -1;
}
return result;
}
void HistoryInner::clearSelectedItems(bool onlyTextSelection) {
@ -1989,8 +1995,8 @@ void HistoryInner::clearSelectedItems(bool onlyTextSelection) {
void HistoryInner::fillSelectedItems(SelectedItemSet &sel, bool forDelete) {
if (_selected.isEmpty() || _selected.cbegin().value() != FullSelection) return;
for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
HistoryItem *item = i.key();
for (auto i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
auto item = i.key();
if (item && item->toHistoryMessage() && item->id > 0) {
if (item->history() == _migrated) {
sel.insert(item->id - ServerMaxMsgId, item);
@ -2437,7 +2443,7 @@ void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
QString HistoryInner::tooltipText() const {
if (_dragCursorState == HistoryInDateCursorState && _dragAction == NoDrag) {
if (App::hoveredItem()) {
QString dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
if (auto edited = App::hoveredItem()->Get<HistoryMessageEdited>()) {
dateText += '\n' + lng_edited_date(lt_date, edited->_editDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
@ -2449,7 +2455,7 @@ QString HistoryInner::tooltipText() const {
return forwarded->_text.originalText(AllTextSelection, ExpandLinksNone);
}
}
} else if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
} else if (auto lnk = ClickHandler::getActive()) {
return lnk->tooltip();
}
return QString();

View File

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/tooltip.h"
#include "ui/widgets/scroll_area.h"
#include "window/top_bar_widget.h"
namespace Window {
class Controller;
@ -59,7 +60,7 @@ public:
bool canCopySelected() const;
bool canDeleteSelected() const;
void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const;
Window::TopBarWidget::SelectedState getSelectionState() const;
void clearSelectedItems(bool onlyTextSelection = false);
void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true);
void selectItem(HistoryItem *item);

View File

@ -757,6 +757,21 @@ bool HistoryItem::canPin() const {
return id > 0 && _history->peer->isMegagroup() && (_history->peer->asChannel()->amEditor() || _history->peer->asChannel()->amCreator()) && toHistoryMessage();
}
bool HistoryItem::canForward() const {
if (id < 0) {
return false;
}
if (auto message = toHistoryMessage()) {
if (auto media = message->getMedia()) {
if (media->type() == MediaTypeCall) {
return false;
}
}
return true;
}
return false;
}
bool HistoryItem::canEdit(const QDateTime &cur) const {
auto messageToMyself = (_history->peer->id == AuthSession::CurrentUserPeerId());
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
@ -816,6 +831,11 @@ bool HistoryItem::canDeleteForEveryone(const QDateTime &cur) const {
if (!toHistoryMessage()) {
return false;
}
if (auto media = getMedia()) {
if (media->type() == MediaTypeCall) {
return false;
}
}
if (!out()) {
if (auto chat = history()->peer->asChat()) {
if (!chat->amCreator() && (!chat->amAdmin() || !chat->adminsEnabled())) {

View File

@ -413,15 +413,6 @@ 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;
@ -694,6 +685,7 @@ public:
}
bool canPin() const;
bool canForward() const;
bool canEdit(const QDateTime &cur) const;
bool canDelete() const;
bool canDeleteForEveryone(const QDateTime &cur) const;
@ -974,6 +966,7 @@ public:
result->finishCreate();
return result;
}
};
ClickHandlerPtr goToMessageClickHandler(PeerData *peer, MsgId msgId);

View File

@ -33,6 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_location_manager.h"
#include "window/window_controller.h"
#include "styles/style_history.h"
#include "calls/calls_instance.h"
namespace {
@ -2787,6 +2788,123 @@ void HistoryContact::updateSentMedia(const MTPMessageMedia &media) {
}
}
HistoryCall::HistoryCall(HistoryItem *parent, const MTPDmessageActionPhoneCall &call) : HistoryMedia(parent)
, _reason(GetReason(call)) {
if (_parent->out()) {
_text = lang(_reason == FinishReason::Missed ? lng_call_cancelled : lng_call_outgoing);
} else if (_reason == FinishReason::Missed) {
_text = lang(lng_call_missed);
} else if (_reason == FinishReason::Busy) {
_text = lang(lng_call_declined);
} else {
_text = lang(lng_call_incoming);
}
_duration = call.has_duration() ? call.vduration.v : 0;
_status = _parent->date.time().toString(cTimeFormat());
if (_duration) {
if (_reason != FinishReason::Missed && _reason != FinishReason::Busy) {
_status = lng_call_duration_info(lt_time, _status, lt_duration, formatDurationWords(_duration));
} else {
_duration = 0;
}
}
}
HistoryCall::FinishReason HistoryCall::GetReason(const MTPDmessageActionPhoneCall &call) {
if (call.has_reason()) {
switch (call.vreason.type()) {
case mtpc_phoneCallDiscardReasonBusy: return FinishReason::Busy;
case mtpc_phoneCallDiscardReasonDisconnect: return FinishReason::Disconnected;
case mtpc_phoneCallDiscardReasonHangup: return FinishReason::Hangup;
case mtpc_phoneCallDiscardReasonMissed: return FinishReason::Missed;
}
Unexpected("Call reason type.");
}
return FinishReason::Hangup;
}
void HistoryCall::initDimensions() {
_maxw = st::msgFileMinWidth;
_link = MakeShared<LambdaClickHandler>([peer = _parent->history()->peer] {
if (auto user = peer->asUser()) {
Calls::Current().startOutgoingCall(user);
}
});
_maxw = st::historyCallWidth;
_minh = st::historyCallHeight;
if (!isBubbleTop()) {
_minh -= st::msgFileTopMinus;
}
_height = _minh;
}
void HistoryCall::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto skipx = 0, skipy = 0, width = _width, height = _height;
auto out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost;
auto selected = (selection == FullSelection);
if (width >= _maxw) {
width = _maxw;
}
auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0;
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
nameleft = st::historyCallLeft;
nametop = st::historyCallTop - topMinus;
nameright = st::msgFilePadding.left();
statustop = st::historyCallStatusTop - topMinus;
auto namewidth = width - nameleft - nameright;
p.setFont(st::semiboldFont);
p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
p.drawTextLeft(nameleft, nametop, width, _text);
auto statusleft = nameleft;
auto missed = (_reason == FinishReason::Missed || _reason == FinishReason::Busy);
auto &arrow = outbg ? (selected ? st::historyCallArrowOutSelected : st::historyCallArrowOut) : missed ? (selected ? st::historyCallArrowMissedInSelected : st::historyCallArrowMissedIn) : (selected ? st::historyCallArrowInSelected : st::historyCallArrowIn);
arrow.paint(p, statusleft + st::historyCallArrowPosition.x(), statustop + st::historyCallArrowPosition.y(), width);
statusleft += arrow.width() + st::historyCallStatusSkip;
auto &statusFg = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
p.setFont(st::normalFont);
p.setPen(statusFg);
p.drawTextLeft(statusleft, statustop, width, _status);
auto &icon = outbg ? (selected ? st::historyCallOutIconSelected : st::historyCallOutIcon) : (selected ? st::historyCallInIconSelected : st::historyCallInIcon);
icon.paint(p, width - st::historyCallIconPosition.x() - icon.width(), st::historyCallIconPosition.y() - topMinus, width);
}
HistoryTextState HistoryCall::getState(int x, int y, HistoryStateRequest request) const {
HistoryTextState result;
if (x >= 0 && y >= 0 && x < _width && y < _height) {
result.link = _link;
return result;
}
return result;
}
QString HistoryCall::notificationText() const {
auto result = _text;
if (_duration > 0) {
result = lng_call_type_and_duration(lt_type, result, lt_duration, formatDurationWords(_duration));
}
return result;
}
TextWithEntities HistoryCall::selectedText(TextSelection selection) const {
if (selection != FullSelection) {
return TextWithEntities();
}
return { qsl("[ ") + _text + qsl(" ]"), EntitiesInText() };
}
namespace {
QString siteNameFromUrl(const QString &url) {

View File

@ -709,6 +709,61 @@ private:
};
class HistoryCall : public HistoryMedia {
public:
HistoryCall(HistoryItem *parent, const MTPDmessageActionPhoneCall &call);
HistoryMediaType type() const override {
return MediaTypeCall;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
Unexpected("Clone HistoryCall.");
}
void initDimensions() override;
void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
return true;
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return false;
}
QString notificationText() const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool needsBubble() const override {
return true;
}
bool customInfoLayout() const override {
return true;
}
enum class FinishReason {
Missed,
Busy,
Disconnected,
Hangup,
};
FinishReason reason() const {
return _reason;
}
private:
static FinishReason GetReason(const MTPDmessageActionPhoneCall &call);
FinishReason _reason = FinishReason::Missed;
int _duration = 0;
QString _text;
QString _status;
ClickHandlerPtr _link;
};
class HistoryWebPage : public HistoryMedia {
public:
HistoryWebPage(HistoryItem *parent, WebPageData *data);

View File

@ -423,6 +423,25 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg)
setText(textWithEntities);
}
HistoryMessage::HistoryMessage(History *history, const MTPDmessageService &msg)
: HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
CreateConfig config;
if (msg.has_reply_to_msg_id()) config.replyTo = msg.vreply_to_msg_id.v;
createComponents(config);
switch (msg.vaction.type()) {
case mtpc_messageActionPhoneCall: {
_media = std::make_unique<HistoryCall>(this, msg.vaction.c_messageActionPhoneCall());
} break;
default: Unexpected("Service message action type in HistoryMessage.");
}
setText(TextWithEntities {});
}
namespace {
MTPDmessage::Flags newForwardedFlags(PeerData *p, int32 from, HistoryMessage *fwd) {
@ -1972,44 +1991,6 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result;
};
auto preparePhoneCallText = [this](const MTPDmessageActionPhoneCall &action) {
auto result = PreparedText {};
auto timeText = date.toString(cTimeFormat());
auto duration = action.has_duration() ? qMax(action.vduration.v, 0) : 0;
auto durationText = ([duration]() -> QString {
if (!duration) {
return QString();
}
if (duration >= 60) {
auto minutes = duration / 60;
auto seconds = duration % 60;
return lng_duration_minutes_seconds(lt_count_minutes, minutes, lt_count_seconds, seconds);
}
return lng_duration_seconds(lt_count, duration);
})();
auto info = this->Get<HistoryMessageCallInfo>();
if (out()) {
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);
} else {
result.text = lng_action_call_outgoing(lt_time, timeText);
}
} else {
if (info && info->reason == HistoryMessageCallInfo::Reason::Missed) {
result.text = lng_action_call_incoming_missed(lt_time, timeText);
} 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);
} else {
result.text = lng_action_call_incoming(lt_time, timeText);
}
}
return result;
};
auto messageText = PreparedText {};
switch (action.type()) {
@ -2026,7 +2007,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
case mtpc_messageActionChannelMigrateFrom: messageText.text = lang(lng_action_group_migrate); break;
case mtpc_messageActionPinMessage: messageText = preparePinnedText(); break;
case mtpc_messageActionGameScore: messageText = prepareGameScoreText(); break;
case mtpc_messageActionPhoneCall: messageText = preparePhoneCallText(action.c_messageActionPhoneCall()); break;
case mtpc_messageActionPhoneCall: Unexpected("PhoneCall type in HistoryService.");
case mtpc_messageActionPaymentSent: messageText = preparePaymentSentText(); break;
default: messageText.text = lang(lng_message_empty); break;
}
@ -2438,22 +2419,6 @@ 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

@ -27,6 +27,9 @@ public:
static HistoryMessage *create(History *history, const MTPDmessage &msg) {
return _create(history, msg);
}
static HistoryMessage *create(History *history, const MTPDmessageService &msg) {
return _create(history, msg);
}
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) {
return _create(history, msgId, flags, date, from, fwd);
}
@ -147,6 +150,7 @@ public:
private:
HistoryMessage(History *history, const MTPDmessage &msg);
HistoryMessage(History *history, const MTPDmessageService &msg);
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document

View File

@ -982,7 +982,7 @@ void HistoryWidget::setInnerFocus() {
if (_scroll->isHidden()) {
setFocus();
} else if (_list) {
if (_selCount || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
if (_nonEmptySelection || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
_list->setFocus();
} else {
_field->setFocus();
@ -1748,8 +1748,8 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
_titlePeerTextWidth = 0;
noSelectingScroll();
_selCount = 0;
_topBar->showSelected(0);
_nonEmptySelection = false;
_topBar->showSelected(Window::TopBarWidget::SelectedState {});
App::hoveredItem(nullptr);
App::pressedItem(nullptr);
@ -5864,7 +5864,7 @@ void HistoryWidget::deleteSelectedItems(bool forEveryone) {
}
void HistoryWidget::onListEscapePressed() {
if (_selCount && _list) {
if (_nonEmptySelection && _list) {
onClearSelected();
} else {
onCancel();
@ -5915,18 +5915,17 @@ void HistoryWidget::fillSelectedItems(SelectedItemSet &sel, bool forDelete) {
void HistoryWidget::updateTopBarSelection() {
if (!_list) {
_topBar->showSelected(0);
_topBar->showSelected(Window::TopBarWidget::SelectedState {});
return;
}
int32 selectedForForward, selectedForDelete;
_list->getSelectionState(selectedForForward, selectedForDelete);
_selCount = selectedForForward ? selectedForForward : selectedForDelete;
_topBar->showSelected(_selCount > 0 ? _selCount : 0, (selectedForDelete == selectedForForward));
auto selectedState = _list->getSelectionState();
_nonEmptySelection = (selectedState.count > 0) || selectedState.textSelected;
_topBar->showSelected(selectedState);
updateControlsVisibility();
updateListSize();
if (!Ui::isLayerShown() && !App::passcoded()) {
if (_selCount || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
if (_nonEmptySelection || (_list && _list->wasSelectedText()) || _recording || isBotStart() || isBlocked() || !_canSendMessages) {
_list->setFocus();
} else {
_field->setFocus();

View File

@ -794,7 +794,7 @@ private:
DragState _attachDrag = DragStateNone;
object_ptr<DragArea> _attachDragDocument, _attachDragPhoto;
int32 _selCount; // < 0 - text selected, focus list, not _field
bool _nonEmptySelection = false;
TaskQueue _fileLoader;
TextUpdateEvents _textUpdateEvents = (TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);

View File

@ -127,6 +127,15 @@ QString formatDurationText(qint64 duration) {
return (hours ? QString::number(hours) + ':' : QString()) + (minutes >= 10 ? QString() : QString('0')) + QString::number(minutes) + ':' + (seconds >= 10 ? QString() : QString('0')) + QString::number(seconds);
}
QString formatDurationWords(qint64 duration) {
if (duration > 59) {
auto minutes = (duration / 60);
auto seconds = (duration % 60);
return lng_duration_minutes_seconds(lt_count_minutes, minutes, lt_count_seconds, seconds);
}
return lng_duration_seconds(lt_count, duration);
}
QString formatDurationAndSizeText(qint64 duration, qint64 size) {
return lng_duration_and_size(lt_duration, formatDurationText(duration), lt_size, formatSizeText(size));
}

View File

@ -77,6 +77,7 @@ static const int32 FileStatusSizeFailed = 0x7FFFFFF2;
QString formatSizeText(qint64 size);
QString formatDownloadText(qint64 ready, qint64 total);
QString formatDurationText(qint64 duration);
QString formatDurationWords(qint64 duration);
QString formatDurationAndSizeText(qint64 duration, qint64 size);
QString formatGifAndSizeText(qint64 size);
QString formatPlayedText(qint64 played, qint64 duration);

View File

@ -580,7 +580,12 @@ void OverviewInner::onDragExec() {
QList<QUrl> urls;
bool forwardSelected = false;
if (uponSelected) {
forwardSelected = !_selected.isEmpty() && _selected.cbegin().value() == FullSelection && !Adaptive::OneColumn();
if (!Adaptive::OneColumn()) {
auto selectedState = getSelectionState();
if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
forwardSelected = true;
}
}
} else if (pressedHandler) {
sel = pressedHandler->dragText();
//if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') {
@ -1176,8 +1181,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
int32 selectedForForward, selectedForDelete;
getSelectionState(selectedForForward, selectedForDelete);
auto selectedState = getSelectionState();
// -2 - has full selected items, but not over, 0 - no selection, 2 - over full selected items
int32 isUponSelected = 0, hasSelected = 0;
@ -1223,8 +1227,10 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), _overview, SLOT(onForwardSelected()));
if (selectedForDelete == selectedForForward) {
if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
_menu->addAction(lang(lng_context_forward_selected), _overview, SLOT(onForwardSelected()));
}
if (selectedState.count > 0 && selectedState.count == selectedState.canDeleteCount) {
_menu->addAction(lang(lng_context_delete_selected), base::lambda_guarded(this, [this] {
_overview->confirmDeleteSelectedItems();
}));
@ -1232,7 +1238,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(lang(lng_context_clear_selection), _overview, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (App::hoveredLinkItem()->toHistoryMessage()) {
if (App::hoveredLinkItem()->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), this, SLOT(forwardMessage()))->setEnabled(true);
}
if (App::hoveredLinkItem()->canDelete()) {
@ -1256,8 +1262,10 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
_menu->addAction(lang(lng_context_to_msg), this, SLOT(goToMessage()))->setEnabled(true);
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), _overview, SLOT(onForwardSelected()));
if (selectedForDelete == selectedForForward) {
if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) {
_menu->addAction(lang(lng_context_forward_selected), _overview, SLOT(onForwardSelected()));
}
if (selectedState.count > 0 && selectedState.count == selectedState.canDeleteCount) {
_menu->addAction(lang(lng_context_delete_selected), base::lambda_guarded(this, [this] {
_overview->confirmDeleteSelectedItems();
}));
@ -1265,7 +1273,7 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu->addAction(lang(lng_context_clear_selection), _overview, SLOT(onClearSelected()));
} else {
if (isUponSelected != -2) {
if (App::mousedItem()->toHistoryMessage()) {
if (App::mousedItem()->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), this, SLOT(forwardMessage()))->setEnabled(true);
}
if (App::mousedItem()->canDelete()) {
@ -1546,21 +1554,22 @@ void OverviewInner::onMenuDestroy(QObject *obj) {
}
}
void OverviewInner::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const {
selectedForForward = selectedForDelete = 0;
for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
Window::TopBarWidget::SelectedState OverviewInner::getSelectionState() const {
auto result = Window::TopBarWidget::SelectedState {};
for (auto i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
if (i.value() == FullSelection) {
if (HistoryItem *item = App::histItemById(itemChannel(i.key()), itemMsgId(i.key()))) {
if (auto item = App::histItemById(itemChannel(i.key()), itemMsgId(i.key()))) {
++result.count;
if (item->canForward()) {
++result.canForwardCount;
}
if (item->canDelete()) {
++selectedForDelete;
++result.canDeleteCount;
}
}
++selectedForForward;
}
}
if (!selectedForDelete && !selectedForForward && !_selected.isEmpty()) { // text selection
selectedForForward = -1;
}
return result;
}
void OverviewInner::clearSelectedItems(bool onlyTextSelection) {
@ -2045,8 +2054,6 @@ MediaOverviewType OverviewWidget::type() const {
}
void OverviewWidget::switchType(MediaOverviewType type) {
_selCount = 0;
disconnect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
_inner->setSelectMode(false);
@ -2062,7 +2069,7 @@ void OverviewWidget::switchType(MediaOverviewType type) {
_header = _header.toUpper();
noSelectingScroll();
_topBar->showSelected(0);
_topBar->showSelected(Window::TopBarWidget::SelectedState {});
updateTopBarSelection();
scrollReset();
@ -2086,12 +2093,10 @@ bool OverviewWidget::contentOverlapped(const QRect &globalRect) {
}
void OverviewWidget::updateTopBarSelection() {
int32 selectedForForward, selectedForDelete;
_inner->getSelectionState(selectedForForward, selectedForDelete);
_selCount = selectedForForward ? selectedForForward : selectedForDelete;
_inner->setSelectMode(_selCount > 0);
auto selectedState = _inner->getSelectionState();
_inner->setSelectMode(selectedState.count > 0);
if (App::main()) {
_topBar->showSelected(_selCount > 0 ? _selCount : 0, (selectedForDelete == selectedForForward));
_topBar->showSelected(selectedState);
_topBar->update();
}
if (App::wnd() && !Ui::isLayerShown()) {

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "window/section_widget.h"
#include "window/top_bar_widget.h"
#include "ui/widgets/tooltip.h"
#include "ui/widgets/scroll_area.h"
@ -90,7 +91,7 @@ public:
void changingMsgId(HistoryItem *row, MsgId newId);
void repaintItem(const HistoryItem *msg);
void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const;
Window::TopBarWidget::SelectedState getSelectionState() const;
void clearSelectedItems(bool onlyTextSelection = false);
void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true);
@ -396,8 +397,6 @@ private:
QTimer _scrollTimer;
int32 _scrollDelta = 0;
int32 _selCount = 0;
object_ptr<Ui::PlainShadow> _topShadow;
bool _inGrab = false;

View File

@ -263,7 +263,9 @@ void TopBarWidget::updateControlsGeometry() {
selectedButtonsTop += (height() - _forward->height()) / 2;
_forward->moveToLeft(buttonsLeft, selectedButtonsTop);
buttonsLeft += _forward->width() + st::topBarActionSkip;
if (!_forward->isHidden()) {
buttonsLeft += _forward->width() + st::topBarActionSkip;
}
_delete->moveToLeft(buttonsLeft, selectedButtonsTop);
_clearSelection->moveToRight(st::topBarActionSkip, selectedButtonsTop);
@ -293,7 +295,7 @@ void TopBarWidget::showAll() {
_clearSelection->show();
_delete->setVisible(_canDelete);
_forward->show();
_forward->setVisible(_canForward);
_mediaType->setVisible(App::main() ? App::main()->showMediaTypeSwitch() : false);
if (historyPeer && !overviewPeer) {
@ -349,17 +351,20 @@ void TopBarWidget::updateMembersShowArea() {
_membersShowArea->setGeometry(App::main()->getMembersShowAreaGeometry());
}
void TopBarWidget::showSelected(int selectedCount, bool canDelete) {
if (_selectedCount == selectedCount && _canDelete == canDelete) {
void TopBarWidget::showSelected(SelectedState state) {
auto canDelete = (state.count > 0 && state.count == state.canDeleteCount);
auto canForward = (state.count > 0 && state.count == state.canForwardCount);
if (_selectedCount == state.count && _canDelete == canDelete && _canForward == canForward) {
return;
}
if (selectedCount == 0) {
if (state.count == 0) {
// Don't change the visible buttons if the selection is cancelled.
canDelete = _canDelete;
canForward = _canForward;
}
auto wasSelected = (_selectedCount > 0);
_selectedCount = selectedCount;
_selectedCount = state.count;
if (_selectedCount > 0) {
_forward->setNumbersText(_selectedCount);
_delete->setNumbersText(_selectedCount);
@ -369,8 +374,9 @@ void TopBarWidget::showSelected(int selectedCount, bool canDelete) {
}
}
auto hasSelected = (_selectedCount > 0);
if (_canDelete != canDelete) {
if (_canDelete != canDelete || _canForward != canForward) {
_canDelete = canDelete;
_canForward = canForward;
showAll();
}
if (wasSelected != hasSelected) {

View File

@ -39,8 +39,15 @@ class TopBarWidget : public TWidget, private base::Subscriber {
public:
TopBarWidget(QWidget *parent, gsl::not_null<Window::Controller*> controller);
struct SelectedState {
bool textSelected = false;
int count = 0;
int canDeleteCount = 0;
int canForwardCount = 0;
};
void showAll();
void showSelected(int selectedCount, bool canDelete = false);
void showSelected(SelectedState state);
void animationFinished();
void updateMembersShowArea();
@ -77,6 +84,7 @@ private:
PeerData *_searchInPeer = nullptr;
int _selectedCount = 0;
bool _canDelete = false;
bool _canForward = false;
Animation _selectedShown;