Changeset View
Changeset View
Standalone View
Standalone View
smsapp/conversationlistmodel.cpp
Show All 17 Lines | |||||
18 | * License along with this library; if not, write to the Free Software | 18 | * License along with this library; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | 19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
20 | */ | 20 | */ | ||
21 | 21 | | |||
22 | #include "conversationlistmodel.h" | 22 | #include "conversationlistmodel.h" | ||
23 | 23 | | |||
24 | #include <QLoggingCategory> | 24 | #include <QLoggingCategory> | ||
25 | #include "interfaces/conversationmessage.h" | 25 | #include "interfaces/conversationmessage.h" | ||
26 | #include "interfaces/dbusinterfaces.h" | ||||
26 | 27 | | |||
27 | Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL, "kdeconnect.sms.conversations_list") | 28 | Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL, "kdeconnect.sms.conversations_list") | ||
28 | 29 | | |||
29 | ConversationListModel::ConversationListModel(QObject* parent) | 30 | ConversationListModel::ConversationListModel(QObject* parent) | ||
30 | : QStandardItemModel(parent) | 31 | : QStandardItemModel(parent) | ||
31 | , m_conversationsInterface(nullptr) | 32 | , m_conversationsInterface(nullptr) | ||
32 | { | 33 | { | ||
33 | qCCritical(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Constructing" << this; | 34 | qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Constructing" << this; | ||
34 | auto roles = roleNames(); | 35 | auto roles = roleNames(); | ||
35 | roles.insert(FromMeRole, "fromMe"); | 36 | roles.insert(FromMeRole, "fromMe"); | ||
36 | roles.insert(AddressRole, "address"); | 37 | roles.insert(AddressRole, "address"); | ||
37 | roles.insert(PersonUriRole, "personUri"); | 38 | roles.insert(PersonUriRole, "personUri"); | ||
38 | roles.insert(ConversationIdRole, "conversationId"); | 39 | roles.insert(ConversationIdRole, "conversationId"); | ||
39 | roles.insert(DateRole, "date"); | 40 | roles.insert(DateRole, "date"); | ||
40 | setItemRoleNames(roles); | 41 | setItemRoleNames(roles); | ||
41 | 42 | | |||
42 | ConversationMessage::registerDbusType(); | 43 | ConversationMessage::registerDbusType(); | ||
43 | } | 44 | } | ||
44 | 45 | | |||
45 | ConversationListModel::~ConversationListModel() | 46 | ConversationListModel::~ConversationListModel() | ||
46 | { | 47 | { | ||
47 | } | 48 | } | ||
48 | 49 | | |||
49 | void ConversationListModel::setDeviceId(const QString& deviceId) | 50 | void ConversationListModel::setDeviceId(const QString& deviceId) | ||
50 | { | 51 | { | ||
51 | qCCritical(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "setDeviceId" << deviceId << "of" << this; | 52 | if (deviceId == m_deviceId) { | ||
52 | if (deviceId == m_deviceId) | | |||
53 | return; | 53 | return; | ||
54 | } | ||||
55 | | ||||
56 | qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "setDeviceId" << deviceId << "of" << this; | ||||
54 | 57 | | |||
55 | if (m_conversationsInterface) { | 58 | if (m_conversationsInterface) { | ||
56 | disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QString)), this, SLOT(handleCreatedConversation(QString))); | 59 | disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QVariantMap)), this, SLOT(handleCreatedConversation(QVariantMap))); | ||
60 | disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QVariantMap)), this, SLOT(handleConversationUpdated(QVariantMap))); | ||||
57 | delete m_conversationsInterface; | 61 | delete m_conversationsInterface; | ||
62 | m_conversationsInterface = nullptr; | ||||
58 | } | 63 | } | ||
59 | 64 | | |||
60 | m_deviceId = deviceId; | 65 | m_deviceId = deviceId; | ||
66 | | ||||
67 | // This method still gets called *with a valid deviceID* when the device is not connected while the component is setting up | ||||
68 | // Detect that case and don't do anything. | ||||
69 | DeviceDbusInterface device(deviceId); | ||||
70 | if (!(device.isValid() && device.isReachable())) { | ||||
sredman: What do you think about this being above the checks to delete m_conversationsInterface? That… | |||||
71 | return; | ||||
72 | } | ||||
73 | | ||||
61 | m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); | 74 | m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); | ||
62 | connect(m_conversationsInterface, SIGNAL(conversationCreated(QString)), this, SLOT(handleCreatedConversation(QString))); | 75 | connect(m_conversationsInterface, SIGNAL(conversationCreated(QVariantMap)), this, SLOT(handleCreatedConversation(QVariantMap))); | ||
63 | connect(m_conversationsInterface, SIGNAL(conversationMessageReceived(QVariantMap,int)), this, SLOT(createRowFromMessage(QVariantMap,int))); | 76 | connect(m_conversationsInterface, SIGNAL(conversationUpdated(QVariantMap)), this, SLOT(handleConversationUpdated(QVariantMap))); | ||
64 | prepareConversationsList(); | 77 | prepareConversationsList(); | ||
65 | 78 | | |||
66 | m_conversationsInterface->requestAllConversationThreads(); | 79 | m_conversationsInterface->requestAllConversationThreads(); | ||
67 | } | 80 | } | ||
68 | 81 | | |||
69 | void ConversationListModel::prepareConversationsList() | 82 | void ConversationListModel::prepareConversationsList() | ||
70 | { | 83 | { | ||
84 | if (!m_conversationsInterface->isValid()) { | ||||
85 | qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Tried to prepareConversationsList with an invalid interface!"; | ||||
86 | return; | ||||
87 | } | ||||
88 | QDBusPendingReply<QVariantList> validThreadIDsReply = m_conversationsInterface->activeConversations(); | ||||
71 | 89 | | |||
72 | QDBusPendingReply<QStringList> validThreadIDsReply = m_conversationsInterface->activeConversations(); | 90 | setWhenAvailable(validThreadIDsReply, [this](const QVariantList& convs) { | ||
73 | 91 | clear(); // If we clear before we receive the reply, there might be a (several second) visual gap! | |||
74 | setWhenAvailable(validThreadIDsReply, [this](const QStringList& convs) { | 92 | for (const QVariant& headMessage : convs) { | ||
75 | clear(); | 93 | QDBusArgument data = headMessage.value<QDBusArgument>(); | ||
76 | for (const QString& conversationId : convs) { | 94 | QVariantMap message; | ||
77 | handleCreatedConversation(conversationId); | 95 | data >> message; | ||
96 | handleCreatedConversation(message); | ||||
78 | } | 97 | } | ||
79 | }, this); | 98 | }, this); | ||
80 | } | 99 | } | ||
81 | 100 | | |||
82 | void ConversationListModel::handleCreatedConversation(const QString& conversationId) | 101 | void ConversationListModel::handleCreatedConversation(const QVariantMap& msg) | ||
102 | { | ||||
103 | createRowFromMessage(msg); | ||||
104 | } | ||||
105 | | ||||
106 | void ConversationListModel::handleConversationUpdated(const QVariantMap& msg) | ||||
83 | { | 107 | { | ||
84 | m_conversationsInterface->requestConversation(conversationId, 0, 1); | 108 | createRowFromMessage(msg); | ||
85 | } | 109 | } | ||
86 | 110 | | |||
87 | void ConversationListModel::printDBusError(const QDBusError& error) | 111 | void ConversationListModel::printDBusError(const QDBusError& error) | ||
88 | { | 112 | { | ||
89 | qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << error; | 113 | qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << error; | ||
apol: leave empty? | |||||
I left this here so I could put a breakpoint on it and decide what to do with the incoming data 😬 Working on filling out this method is my next to-do to solve bug 398818 sredman: I left this here so I could put a breakpoint on it and decide what to do with the incoming data… | |||||
90 | } | 114 | } | ||
91 | 115 | | |||
92 | QStandardItem * ConversationListModel::conversationForThreadId(qint32 threadId) | 116 | QStandardItem * ConversationListModel::conversationForThreadId(qint32 threadId) | ||
93 | { | 117 | { | ||
94 | for(int i=0, c=rowCount(); i<c; ++i) { | 118 | for(int i=0, c=rowCount(); i<c; ++i) { | ||
95 | auto it = item(i, 0); | 119 | auto it = item(i, 0); | ||
96 | if (it->data(ConversationIdRole) == threadId) | 120 | if (it->data(ConversationIdRole) == threadId) | ||
97 | return it; | 121 | return it; | ||
98 | } | 122 | } | ||
99 | return nullptr; | 123 | return nullptr; | ||
100 | } | 124 | } | ||
101 | 125 | | |||
102 | void ConversationListModel::createRowFromMessage(const QVariantMap& msg, int row) | 126 | void ConversationListModel::createRowFromMessage(const QVariantMap& msg) | ||
103 | { | 127 | { | ||
104 | if (row != 0) | | |||
105 | return; | | |||
106 | | ||||
107 | const ConversationMessage message(msg); | 128 | const ConversationMessage message(msg); | ||
108 | if (message.type() == -1) { | 129 | if (message.type() == -1) { | ||
109 | // The Android side currently hacks in -1 if something weird comes up | 130 | // The Android side currently hacks in -1 if something weird comes up | ||
110 | // TODO: Remove this hack when MMS support is implemented | 131 | // TODO: Remove this hack when MMS support is implemented | ||
111 | return; | 132 | return; | ||
112 | } | 133 | } | ||
113 | 134 | | |||
114 | bool toadd = false; | 135 | bool toadd = false; | ||
115 | QStandardItem* item = conversationForThreadId(message.threadID()); | 136 | QStandardItem* item = conversationForThreadId(message.threadID()); | ||
116 | if (!item) { | 137 | if (!item) { | ||
117 | toadd = true; | 138 | toadd = true; | ||
118 | item = new QStandardItem(); | 139 | item = new QStandardItem(); | ||
119 | QScopedPointer<KPeople::PersonData> personData(lookupPersonByAddress(message.address())); | 140 | QScopedPointer<KPeople::PersonData> personData(lookupPersonByAddress(message.address())); | ||
120 | if (personData) { | 141 | if (personData) { | ||
121 | item->setText(personData->name()); | 142 | item->setText(personData->name()); | ||
122 | item->setIcon(QIcon(personData->photo())); | 143 | item->setIcon(QIcon(personData->photo())); | ||
123 | item->setData(personData->personUri(), PersonUriRole); | 144 | item->setData(personData->personUri(), PersonUriRole); | ||
124 | } else { | 145 | } else { | ||
125 | item->setData(QString(), PersonUriRole); | 146 | item->setData(QString(), PersonUriRole); | ||
126 | item->setText(message.address()); | 147 | item->setText(message.address()); | ||
127 | } | 148 | } | ||
128 | item->setData(message.threadID(), ConversationIdRole); | 149 | item->setData(message.threadID(), ConversationIdRole); | ||
129 | } | 150 | } | ||
151 | | ||||
152 | // Update the message if the data is newer | ||||
153 | // This will be true if a conversation receives a new message, but false when the user | ||||
154 | // does something to trigger past conversation history loading | ||||
155 | bool oldDateExists; | ||||
156 | qint64 oldDate = item->data(DateRole).toLongLong(&oldDateExists); | ||||
157 | if (!oldDateExists || message.date() >= oldDate) { | ||||
158 | // If there was no old data or incoming data is newer, update the record | ||||
130 | item->setData(message.address(), AddressRole); | 159 | item->setData(message.address(), AddressRole); | ||
131 | item->setData(message.type() == ConversationMessage::MessageTypeSent, FromMeRole); | 160 | item->setData(message.type() == ConversationMessage::MessageTypeSent, FromMeRole); | ||
132 | item->setData(message.body(), Qt::ToolTipRole); | 161 | item->setData(message.body(), Qt::ToolTipRole); | ||
133 | item->setData(message.date(), DateRole); | 162 | item->setData(message.date(), DateRole); | ||
163 | } | ||||
134 | 164 | | |||
135 | if (toadd) | 165 | if (toadd) | ||
136 | appendRow(item); | 166 | appendRow(item); | ||
137 | } | 167 | } | ||
138 | 168 | | |||
139 | KPeople::PersonData* ConversationListModel::lookupPersonByAddress(const QString& address) | 169 | KPeople::PersonData* ConversationListModel::lookupPersonByAddress(const QString& address) | ||
140 | { | 170 | { | ||
141 | const QString& canonicalAddress = canonicalizePhoneNumber(address); | 171 | const QString& canonicalAddress = canonicalizePhoneNumber(address); | ||
Show All 39 Lines |
What do you think about this being above the checks to delete m_conversationsInterface? That way, when the device is disconnected, you can still browse messages locally cached in the conversationDbusInterface.