Merge lp:~phablet-team/messaging-app/irc-style into lp:messaging-app/staging
- irc-style
- Merge into staging
Proposed by
Renato Araujo Oliveira Filho
Status: | Merged |
---|---|
Approved by: | Tiago Salem Herrmann |
Approved revision: | 689 |
Merged at revision: | 654 |
Proposed branch: | lp:~phablet-team/messaging-app/irc-style |
Merge into: | lp:messaging-app/staging |
Diff against target: |
2900 lines (+1390/-331) 31 files modified
CMakeLists.txt (+1/-0) accounts/CMakeLists.txt (+2/-0) accounts/messaging-app.application (+13/-0) debian/messaging-app.install (+1/-0) src/messagingapplication.cpp (+20/-0) src/messagingapplication.h (+1/-0) src/qml/ComposeBar.qml (+126/-16) src/qml/ContactSearchList.qml (+59/-77) src/qml/ContactSearchWidget.qml (+26/-9) src/qml/Dialogs/InformationDialog.qml (+11/-6) src/qml/GroupChatInfoPage.qml (+44/-4) src/qml/MainPage.qml (+81/-27) src/qml/MessageBubble.qml (+7/-0) src/qml/MessageDelegate.qml (+25/-6) src/qml/MessageInfoDialog.qml (+67/-32) src/qml/Messages.qml (+143/-33) src/qml/MessagesListView.qml (+10/-1) src/qml/MessagingContactEditorPage.qml (+7/-0) src/qml/MessagingContactViewPage.qml (+15/-1) src/qml/MultiRecipientInput.qml (+41/-30) src/qml/NewGroupPage.qml (+61/-20) src/qml/NewRecipientPage.qml (+27/-0) src/qml/OnlineAccountsHelper.qml (+91/-0) src/qml/ParticipantInfoPage.qml (+9/-0) src/qml/ParticipantsPopover.qml (+126/-34) src/qml/RegularMessageDelegate.qml (+1/-0) src/qml/RegularMessageDelegate_irc.qml (+206/-0) src/qml/SettingsPage.qml (+72/-32) src/qml/TransparentButton.qml (+18/-1) src/qml/messaging-app.qml (+73/-2) tests/qml/tst_MessagesView.qml (+6/-0) |
To merge this branch: | bzr merge lp:~phablet-team/messaging-app/irc-style |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tiago Salem Herrmann (community) | Approve | ||
Review via email: mp+316160@code.launchpad.net |
Commit message
Use message delegates based on protocol if that exists.
Description of the change
To post a comment you must log in.
- 670. By Renato Araujo Oliveira Filho
-
Mark messages that contains the username in the text bold.
- 671. By Renato Araujo Oliveira Filho
-
Update unit test.
- 672. By Renato Araujo Oliveira Filho
-
Fine tunning on the IRC ui.
- 673. By Renato Araujo Oliveira Filho
-
Mark message as read after display it.
- 674. By Renato Araujo Oliveira Filho
-
Make only the user nickname bold.
Fix delete icon size. - 675. By Renato Araujo Oliveira Filho
-
Show message timestamp for irc style.
- 676. By Renato Araujo Oliveira Filho
-
Add support for url in IRC delegate.
- 677. By Renato Araujo Oliveira Filho
-
Fixed blank messages.
- 678. By Renato Araujo Oliveira Filho
-
Fixed "Info" and "forward" message, actions.
- 679. By Renato Araujo Oliveira Filho
-
Fixed info message dialog for IRC messages.
- 680. By Renato Araujo Oliveira Filho
-
Use black timestamp.
- 681. By Renato Araujo Oliveira Filho
-
Create shortcut to close info dialog.
- 682. By Renato Araujo Oliveira Filho
-
Does not show the participant list on message info dialog.
- 683. By Tiago Salem Herrmann
-
merge parent branch
- 684. By Tiago Salem Herrmann
-
merge parent branch
- 685. By Tiago Salem Herrmann
-
merge parent branch
- 686. By Renato Araujo Oliveira Filho
-
Parent merged.
- 687. By Tiago Salem Herrmann
-
merge parent branch
- 688. By Tiago Salem Herrmann
-
merge parent branch
- 689. By Tiago Salem Herrmann
-
add FIXME to irc specific stuff
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2016-06-08 18:35:43 +0000 |
3 | +++ CMakeLists.txt 2017-03-22 15:12:51 +0000 |
4 | @@ -71,6 +71,7 @@ |
5 | |
6 | add_subdirectory(po) |
7 | add_subdirectory(src) |
8 | +add_subdirectory(accounts) |
9 | if (NOT ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64") AND (NOT ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64el")) AND (NOT ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le"))) |
10 | add_subdirectory(tests) |
11 | endif() |
12 | |
13 | === added directory 'accounts' |
14 | === added file 'accounts/CMakeLists.txt' |
15 | --- accounts/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
16 | +++ accounts/CMakeLists.txt 2017-03-22 15:12:51 +0000 |
17 | @@ -0,0 +1,2 @@ |
18 | +file(GLOB APPLICATION_FILES *.application) |
19 | +install(FILES ${APPLICATION_FILES} DESTINATION share/accounts/applications/) |
20 | |
21 | === added file 'accounts/messaging-app.application' |
22 | --- accounts/messaging-app.application 1970-01-01 00:00:00 +0000 |
23 | +++ accounts/messaging-app.application 2017-03-22 15:12:51 +0000 |
24 | @@ -0,0 +1,13 @@ |
25 | +<?xml version="1.0" encoding="UTF-8"?> |
26 | +<application id="messaging-app"> |
27 | + <description>Messaging</description> |
28 | + <desktop-entry>messaging-app.desktop</desktop-entry> |
29 | + <translations>messaging</translations> |
30 | + <profile>messaging-app</profile> |
31 | + |
32 | + <services> |
33 | + <service id="telephony-irc-im"> |
34 | + <description>Integrate your IRC accounts</description> |
35 | + </service> |
36 | + </services> |
37 | +</application> |
38 | |
39 | === modified file 'debian/messaging-app.install' |
40 | --- debian/messaging-app.install 2016-05-20 04:32:06 +0000 |
41 | +++ debian/messaging-app.install 2017-03-22 15:12:51 +0000 |
42 | @@ -1,3 +1,4 @@ |
43 | +usr/share/accounts/applications/* |
44 | usr/share/applications/messaging-app*.desktop |
45 | usr/share/url-dispatcher/urls |
46 | usr/share/content-hub/peers/messaging-app |
47 | |
48 | === modified file 'src/messagingapplication.cpp' |
49 | --- src/messagingapplication.cpp 2016-10-17 13:02:38 +0000 |
50 | +++ src/messagingapplication.cpp 2017-03-22 15:12:51 +0000 |
51 | @@ -258,6 +258,8 @@ |
52 | QStringList participantIds = value.split(";"); |
53 | properties["participantIds"] = participantIds; |
54 | } |
55 | + } else { |
56 | + properties["participantIds"] = QStringList() << value; |
57 | } |
58 | QUrlQuery query(url); |
59 | Q_FOREACH(const Pair &item, query.queryItems(QUrl::FullyDecoded)) { |
60 | @@ -368,3 +370,21 @@ |
61 | { |
62 | return findRecursiveChild(m_view->rootObject(), objectName, property, value); |
63 | } |
64 | + |
65 | +// Check if a delegate file exists with protocol name as suffix |
66 | +// If true use the specialized delegate |
67 | +// If false use the default delegate |
68 | +QUrl MessagingApplication::delegateFromProtocol(const QUrl &delegate, const QString &protocol) |
69 | +{ |
70 | + if (protocol.isEmpty()) |
71 | + return delegate; |
72 | + |
73 | + QString localFile = delegate.toLocalFile(); |
74 | + QString fileNameWithProtocol = QString("%1_%2.qml") |
75 | + .arg(localFile.mid(0, localFile.lastIndexOf("."))) |
76 | + .arg(protocol.toLower()); |
77 | + |
78 | + if (QFile::exists(fileNameWithProtocol)) |
79 | + return fileNameWithProtocol; |
80 | + return delegate; |
81 | +} |
82 | |
83 | === modified file 'src/messagingapplication.h' |
84 | --- src/messagingapplication.h 2016-07-28 04:14:35 +0000 |
85 | +++ src/messagingapplication.h 2017-03-22 15:12:51 +0000 |
86 | @@ -46,6 +46,7 @@ |
87 | void showNotificationMessage(const QString &message, const QString &icon = QString()); |
88 | QObject *findMessagingChild(const QString &objectName); |
89 | QObject *findMessagingChild(const QString &objectName, const QString &property, const QVariant &value); |
90 | + QUrl delegateFromProtocol(const QUrl &delegate, const QString &protocol); |
91 | |
92 | private Q_SLOTS: |
93 | void setFullscreen(bool fullscreen); |
94 | |
95 | === modified file 'src/qml/ComposeBar.qml' |
96 | --- src/qml/ComposeBar.qml 2016-11-07 17:21:02 +0000 |
97 | +++ src/qml/ComposeBar.qml 2017-03-22 15:12:51 +0000 |
98 | @@ -43,6 +43,10 @@ |
99 | property alias inputMethodComposing: messageTextArea.inputMethodComposing |
100 | property bool usingMMS: false |
101 | property bool isBroadcast: false |
102 | + property bool returnToSend: false |
103 | + property bool enableAttachments: true |
104 | + property alias participants: participantPopover.participants |
105 | + readonly property alias textArea: messageTextArea |
106 | |
107 | onRecordingChanged: { |
108 | if (recording) { |
109 | @@ -63,7 +67,8 @@ |
110 | } |
111 | |
112 | function forceFocus() { |
113 | - messageTextArea.forceActiveFocus() |
114 | + if (showContents) |
115 | + messageTextArea.forceActiveFocus() |
116 | } |
117 | |
118 | function reset() { |
119 | @@ -192,14 +197,14 @@ |
120 | } |
121 | |
122 | Behavior on opacity { UbuntuNumberAnimation {} } |
123 | - visible: opacity > 0 |
124 | + visible: opacity > 0 && composeBar.enableAttachments |
125 | |
126 | - width: childrenRect.width |
127 | - height: childrenRect.height |
128 | + width: visible ? childrenRect.width : 0 |
129 | + height: visible ? childrenRect.height : 0 |
130 | |
131 | anchors { |
132 | left: parent.left |
133 | - leftMargin: units.gu(2) |
134 | + leftMargin: visible ? units.gu(2) : 0 |
135 | verticalCenter: sendButton.verticalCenter |
136 | } |
137 | spacing: units.gu(2) |
138 | @@ -408,11 +413,92 @@ |
139 | TextArea { |
140 | id: messageTextArea |
141 | objectName: "messageTextArea" |
142 | + |
143 | + property bool autoCompleteLock: false |
144 | + |
145 | + property int autoCompleteStartIndex: -1 |
146 | + |
147 | + function updateAutoComplete(startIndex, input) |
148 | + { |
149 | + var showPopup = false |
150 | + if (input.charAt(startIndex) === "@") { |
151 | + showPopup = true |
152 | + startIndex += 1 |
153 | + } |
154 | + |
155 | + var autoCompletePrefix = input.slice(startIndex, input.length) |
156 | + |
157 | + return participantPopover.showParticpantsStartWith(composeBar, autoCompletePrefix, showPopup) |
158 | + } |
159 | + |
160 | + function autoComplete() |
161 | + { |
162 | + autoCompleteLock = true |
163 | + var suggestion = "" |
164 | + var lastSpace = -1 |
165 | + |
166 | + var autoCompleteText = text |
167 | + if (participantPopover.active) { |
168 | + if (participantPopover.popupVisible) { |
169 | + suggestion = updateAutoComplete(autoCompleteStartIndex, autoCompleteText) |
170 | + } else { |
171 | + suggestion = participantPopover.nextItem() |
172 | + } |
173 | + } else if (autoCompleteText.length > 0) { |
174 | + autoCompleteStartIndex = autoCompleteText.lastIndexOf(" ") + 1 |
175 | + suggestion = updateAutoComplete(autoCompleteStartIndex, autoCompleteText) |
176 | + forceFocus() |
177 | + } else { |
178 | + autoCompleteLock = false |
179 | + return false |
180 | + } |
181 | + |
182 | + if (suggestion.length > 0) { |
183 | + var sliceEnd = autoCompleteText.charAt(autoCompleteStartIndex) === "@" ? autoCompleteStartIndex + 1 : autoCompleteStartIndex |
184 | + messageTextArea.text = text.slice(0, sliceEnd) + suggestion + ", " |
185 | + if (participantPopover.popupVisible) { |
186 | + messageTextArea.select(autoCompleteText.length, text.length) |
187 | + } else { |
188 | + messageTextArea.cursorPosition = messageTextArea.text.length |
189 | + } |
190 | + |
191 | + } else { |
192 | + participantPopover.close() |
193 | + autoCompleteStartIndex = -1 |
194 | + } |
195 | + |
196 | + autoCompleteLock = false |
197 | + return true |
198 | + } |
199 | + |
200 | + onTextChanged: { |
201 | + if (autoCompleteLock) |
202 | + return |
203 | + |
204 | + // non-visual popover does not care about text change |
205 | + if (!participantPopover.popupVisible) { |
206 | + participantPopover.close() |
207 | + autoCompleteStartIndex = -1 |
208 | + return |
209 | + } |
210 | + |
211 | + if (autoCompleteStartIndex != -1) |
212 | + autoComplete() |
213 | + } |
214 | + |
215 | anchors { |
216 | top: parent.top |
217 | left: parent.left |
218 | right: parent.right |
219 | } |
220 | + Keys.onReturnPressed: { |
221 | + if (composeBar.returnToSend) { |
222 | + sendButton.processSend() |
223 | + event.accepted = true |
224 | + return |
225 | + } |
226 | + event.accepted = false |
227 | + } |
228 | // this value is to avoid letter being cut off |
229 | height: units.gu(4.3) |
230 | style: LocalTextAreaStyle {} |
231 | @@ -438,6 +524,21 @@ |
232 | font.family: "Ubuntu" |
233 | font.pixelSize: FontUtils.sizeToPixels("medium") |
234 | color: Theme.palette.normal.backgroundText |
235 | + Keys.onPressed: { |
236 | + if (event.key === Qt.Key_Tab) { |
237 | + event.accepted = autoComplete() |
238 | + } else if (participantPopover.popupVisible) { |
239 | + // cancel non-visual autocomplete if any other key is pressed |
240 | + participantPopover.close() |
241 | + autoCompleteStartIndex = -1 |
242 | + } |
243 | + } |
244 | + |
245 | + Keys.onReleased: { |
246 | + if (event.key === Qt.Key_At) { |
247 | + event.accepted = autoComplete() |
248 | + } |
249 | + } |
250 | } |
251 | |
252 | // show the counts if option is enabled, and more than one line |
253 | @@ -578,15 +679,7 @@ |
254 | anchors.rightMargin: units.gu(2) |
255 | iconSource: Qt.resolvedUrl("./assets/send.svg") |
256 | enabled: !recordButton.enabled |
257 | - onEnabledChanged: { |
258 | - if (enabled) { |
259 | - enableSendButton.start() |
260 | - } |
261 | - } |
262 | - opacity: 0 |
263 | - visible: enabled |
264 | - |
265 | - onClicked: { |
266 | + function processSend() { |
267 | // make sure we flush everything we have prepared in the OSK preedit |
268 | Qt.inputMethod.commit(); |
269 | if ((textEntry.text == "" && attachments.count == 0) || !canSend) { |
270 | @@ -599,6 +692,17 @@ |
271 | |
272 | composeBar.sendRequested(textEntry.text, attachments) |
273 | } |
274 | + onEnabledChanged: { |
275 | + if (enabled) { |
276 | + enableSendButton.start() |
277 | + } |
278 | + } |
279 | + opacity: composeBar.enableAttachments ? 0 : 1 |
280 | + visible: enabled |
281 | + |
282 | + onClicked: { |
283 | + processSend() |
284 | + } |
285 | } |
286 | |
287 | TransparentButton { |
288 | @@ -611,7 +715,7 @@ |
289 | rightMargin: units.gu(2) |
290 | } |
291 | |
292 | - enabled: textEntry.text != "" || textEntry.inputMethodComposing || attachments.count > 0 ? false : true |
293 | + enabled: textEntry.text != "" || textEntry.inputMethodComposing || attachments.count > 0 || !composeBar.enableAttachments ? false : true |
294 | onEnabledChanged: { |
295 | if (enabled) { |
296 | enableRecordButton.start() |
297 | @@ -653,6 +757,12 @@ |
298 | drag.axis: Drag.XAxis |
299 | drag.minimumX: (leftSideActions.x + leftSideActions.width) |
300 | drag.maximumX: recordButton.x |
301 | - |
302 | + } |
303 | + |
304 | + ParticipantsPopover { |
305 | + id: participantPopover |
306 | + |
307 | + height: parent.parent.height |
308 | + width: parent.width |
309 | } |
310 | } |
311 | |
312 | === modified file 'src/qml/ContactSearchList.qml' |
313 | --- src/qml/ContactSearchList.qml 2016-10-18 13:08:39 +0000 |
314 | +++ src/qml/ContactSearchList.qml 2017-03-22 15:12:51 +0000 |
315 | @@ -20,21 +20,26 @@ |
316 | import QtContacts 5.0 |
317 | |
318 | import Ubuntu.Components 1.3 |
319 | -import Ubuntu.Components.ListItems 1.3 as ListItem |
320 | import Ubuntu.Contacts 0.1 |
321 | |
322 | -ListView { |
323 | +UbuntuListView { |
324 | id: root |
325 | |
326 | // FIXME: change the Ubuntu.Contacts model to search for more fields |
327 | property alias filterTerm: contactModel.filterTerm |
328 | - onFilterTermChanged: console.debug("FILTER :" + filterTerm) |
329 | |
330 | signal contactPicked(string identifier, string label, string avatar) |
331 | - |
332 | - model: ContactListModel { |
333 | + signal focusUp() |
334 | + |
335 | + ContactDetailPhoneNumberTypeModel { |
336 | + id: phoneTypeModel |
337 | + } |
338 | + |
339 | + ContactListModel { |
340 | id: contactModel |
341 | |
342 | + property var proxyModel: [] |
343 | + |
344 | manager: "galera" |
345 | view: root |
346 | autoUpdate: false |
347 | @@ -61,84 +66,61 @@ |
348 | detailTypesHint: [ ContactDetail.DisplayLabel, |
349 | ContactDetail.PhoneNumber ] |
350 | } |
351 | - } |
352 | - |
353 | - ContactDetailPhoneNumberTypeModel { |
354 | - id: phoneTypeModel |
355 | - } |
356 | - |
357 | - delegate: Item { |
358 | + |
359 | + onContactsChanged: { |
360 | + var proxy = [] |
361 | + for (var i=0; i < contacts.length; i++) { |
362 | + for (var p=0; p < contacts[i].phoneNumbers.length; p++) { |
363 | + proxy.push({"contact": contacts[i], "phoneIndex": p}) |
364 | + } |
365 | + } |
366 | + contactModel.proxyModel = proxy |
367 | + } |
368 | + } |
369 | + |
370 | + model: contactModel.proxyModel |
371 | + delegate: ListItem { |
372 | anchors { |
373 | left: parent.left |
374 | right: parent.right |
375 | } |
376 | - height: phoneRepeater.count * units.gu(6) |
377 | - Column { |
378 | - anchors.fill: parent |
379 | - |
380 | - Repeater { |
381 | - id: phoneRepeater |
382 | - |
383 | - model: contact.phoneNumbers.length |
384 | - |
385 | - delegate: MouseArea { |
386 | - anchors { |
387 | - left: parent.left |
388 | - right: parent.right |
389 | - } |
390 | - height: units.gu(6) |
391 | - |
392 | - onClicked: root.contactPicked(contact.phoneNumbers[index].number, contact.displayLabel.label, contact.avatar.url) |
393 | - |
394 | - Column { |
395 | - anchors.right: parent.right |
396 | - anchors.left: parent.left |
397 | - anchors.verticalCenter: parent.verticalCenter |
398 | - height: childrenRect.height |
399 | - spacing: units.gu(.5) |
400 | - |
401 | - Label { |
402 | - anchors { |
403 | - left: parent.left |
404 | - leftMargin: units.gu(2) |
405 | - right: parent.right |
406 | - } |
407 | - height: units.gu(2) |
408 | - text: { |
409 | - // this is necessary to keep the string in the original format |
410 | - var originalText = contact.displayLabel.label |
411 | - var lowerSearchText = filterTerm.toLowerCase() |
412 | - var lowerText = originalText.toLowerCase() |
413 | - var searchIndex = lowerText.indexOf(lowerSearchText) |
414 | - if (searchIndex !== -1) { |
415 | - var piece = originalText.substr(searchIndex, lowerSearchText.length) |
416 | - return originalText.replace(piece, "<b>" + piece + "</b>") |
417 | - } else { |
418 | - return originalText |
419 | - } |
420 | - } |
421 | - fontSize: "medium" |
422 | - color: Theme.palette.normal.backgroundText |
423 | - } |
424 | - Label { |
425 | - anchors { |
426 | - left: parent.left |
427 | - leftMargin: units.gu(2) |
428 | - right: parent.right |
429 | - } |
430 | - height: units.gu(2) |
431 | - text: { |
432 | - var phoneDetail = contact.phoneNumbers[index] |
433 | - return ("%1 %2").arg(phoneTypeModel.get(phoneTypeModel.getTypeIndex(phoneDetail)).label) |
434 | - .arg(phoneDetail.number) |
435 | - } |
436 | - color: Theme.palette.normal.backgroundSecondaryText |
437 | - } |
438 | - |
439 | - ListItem.ThinDivider {} |
440 | - } |
441 | + height: itemLayout.height |
442 | + |
443 | + onClicked: root.contactPicked(modelData.contact.phoneNumbers[modelData.phoneIndex].number, |
444 | + modelData.contact.displayLabel.label, modelData.contact.avatar.url) |
445 | + |
446 | + ListItemLayout { |
447 | + id: itemLayout |
448 | + |
449 | + title.text: { |
450 | + // this is necessary to keep the string in the original format |
451 | + var originalText = modelData.contact.displayLabel.label |
452 | + var lowerSearchText = filterTerm.toLowerCase() |
453 | + var lowerText = originalText.toLowerCase() |
454 | + var searchIndex = lowerText.indexOf(lowerSearchText) |
455 | + if (searchIndex !== -1) { |
456 | + var piece = originalText.substr(searchIndex, lowerSearchText.length) |
457 | + return originalText.replace(piece, "<b>" + piece + "</b>") |
458 | + } else { |
459 | + return originalText |
460 | } |
461 | } |
462 | + title.fontSize: "medium" |
463 | + title.color: Theme.palette.normal.backgroundText |
464 | + |
465 | + subtitle.text: { |
466 | + var phoneDetail = modelData.contact.phoneNumbers[modelData.phoneIndex] |
467 | + return ("%1 %2").arg(phoneTypeModel.get(phoneTypeModel.getTypeIndex(phoneDetail)).label) |
468 | + .arg(phoneDetail.number) |
469 | + } |
470 | + subtitle.color: Theme.palette.normal.backgroundSecondaryText |
471 | } |
472 | } |
473 | + |
474 | + Keys.onUpPressed: { |
475 | + if (currentIndex == 0) |
476 | + focusUp() |
477 | + |
478 | + event.accepted = false |
479 | + } |
480 | } |
481 | |
482 | === modified file 'src/qml/ContactSearchWidget.qml' |
483 | --- src/qml/ContactSearchWidget.qml 2016-08-25 21:00:04 +0000 |
484 | +++ src/qml/ContactSearchWidget.qml 2017-03-22 15:12:51 +0000 |
485 | @@ -20,7 +20,7 @@ |
486 | import Ubuntu.Components 1.3 |
487 | import Ubuntu.Components.ListItems 1.3 as ListItems |
488 | |
489 | -Item { |
490 | +FocusScope { |
491 | id: searchItem |
492 | property alias text: contactSearch.text |
493 | property alias hasFocus: contactSearch.focus |
494 | @@ -28,6 +28,7 @@ |
495 | property int searchResultsHeight: 0 |
496 | |
497 | signal contactPicked(string identifier, string alias, string avatar) |
498 | + |
499 | anchors { |
500 | left: parent.left |
501 | right: parent.right |
502 | @@ -42,8 +43,20 @@ |
503 | anchors.verticalCenter: contactSearch.verticalCenter |
504 | text: i18n.tr("Members:") |
505 | } |
506 | + |
507 | + onContactPicked: contactSearch.forceActiveFocus() |
508 | + |
509 | TextField { |
510 | id: contactSearch |
511 | + |
512 | + function commit() |
513 | + { |
514 | + if (text == "") |
515 | + return |
516 | + searchItem.contactPicked(text, "","") |
517 | + text = "" |
518 | + } |
519 | + |
520 | anchors.top: parent.top |
521 | anchors.left: membersLabel.right |
522 | anchors.leftMargin: units.gu(1) |
523 | @@ -53,16 +66,13 @@ |
524 | hasClearButton: false |
525 | placeholderText: i18n.tr("Number or contact name") |
526 | inputMethodHints: Qt.ImhNoPredictiveText |
527 | - Keys.onReturnPressed: { |
528 | - if (text == "") |
529 | - return |
530 | - searchItem.contactPicked(text, "","") |
531 | - text = "" |
532 | - } |
533 | + focus: true |
534 | + Keys.onReturnPressed: commit() |
535 | + Keys.onEnterPressed: commit() |
536 | + Keys.onDownPressed: searchListLoader.item.forceActiveFocus() |
537 | |
538 | Icon { |
539 | name: "add" |
540 | - color: Theme.palette.normal.backgroundText |
541 | height: units.gu(2) |
542 | anchors { |
543 | right: parent.right |
544 | @@ -75,8 +85,8 @@ |
545 | Qt.inputMethod.hide() |
546 | mainStack.addPageToCurrentColumn(searchItem.parentPage, Qt.resolvedUrl("NewRecipientPage.qml"), {"itemCallback": searchItem.parentPage}) |
547 | } |
548 | - z: 2 |
549 | } |
550 | + z: 2 |
551 | } |
552 | } |
553 | Loader { |
554 | @@ -111,5 +121,12 @@ |
555 | item.contactPicked.connect(searchItem.contactPicked) |
556 | } |
557 | } |
558 | + |
559 | + Connections { |
560 | + target: searchListLoader.item |
561 | + onFocusUp: { |
562 | + contactSearch.forceActiveFocus() |
563 | + } |
564 | + } |
565 | } |
566 | } |
567 | |
568 | === modified file 'src/qml/Dialogs/InformationDialog.qml' |
569 | --- src/qml/Dialogs/InformationDialog.qml 2016-07-13 20:42:55 +0000 |
570 | +++ src/qml/Dialogs/InformationDialog.qml 2017-03-22 15:12:51 +0000 |
571 | @@ -16,7 +16,7 @@ |
572 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
573 | */ |
574 | |
575 | -import QtQuick 2.0 |
576 | +import QtQuick 2.4 |
577 | import Ubuntu.Components 1.3 |
578 | import Ubuntu.Components.Popups 1.3 |
579 | |
580 | @@ -26,12 +26,17 @@ |
581 | objectName: "informationDialog" |
582 | Button { |
583 | objectName: "closeInformationDialog" |
584 | - text: i18n.tr("Close") |
585 | + action: Action { |
586 | + text: i18n.tr("Close") |
587 | + shortcut: "Esc" |
588 | + onTriggered: { |
589 | + PopupUtils.close(dialogue) |
590 | + Qt.inputMethod.hide() |
591 | + } |
592 | + } |
593 | color: UbuntuColors.orange |
594 | - onClicked: { |
595 | - PopupUtils.close(dialogue) |
596 | - Qt.inputMethod.hide() |
597 | - } |
598 | + Component.onCompleted: forceActiveFocus() |
599 | } |
600 | + |
601 | } |
602 | } |
603 | |
604 | === modified file 'src/qml/GroupChatInfoPage.qml' |
605 | --- src/qml/GroupChatInfoPage.qml 2016-10-28 16:22:15 +0000 |
606 | +++ src/qml/GroupChatInfoPage.qml 2017-03-22 15:12:51 +0000 |
607 | @@ -95,6 +95,38 @@ |
608 | property bool chatRoom: chatType == HistoryThreadModel.ChatTypeRoom |
609 | property var chatRoomInfo: threads.length > 0 ? threads[0].chatRoomInfo : [] |
610 | |
611 | + property var leaveString: { |
612 | + // FIXME: temporary workaround |
613 | + if (account && account.protocolInfo.name == "irc") { |
614 | + return i18n.tr("Leave channel") |
615 | + } |
616 | + return i18n.tr("Leave group") |
617 | + } |
618 | + |
619 | + property var headerString: { |
620 | + // FIXME: temporary workaround |
621 | + if (account && account.protocolInfo.name == "irc") { |
622 | + return i18n.tr("Channel Info") |
623 | + } |
624 | + return i18n.tr("Group Info") |
625 | + } |
626 | + |
627 | + property var leaveSuccessString: { |
628 | + // FIXME: temporary workaround |
629 | + if (account && account.protocolInfo.name == "irc") { |
630 | + return i18n.tr("Successfully left channel") |
631 | + } |
632 | + return i18n.tr("Successfully left group") |
633 | + } |
634 | + |
635 | + property var leaveFailedString: { |
636 | + // FIXME: temporary workaround |
637 | + if (account && account.protocolInfo.name == "irc") { |
638 | + return i18n.tr("Failed to leave channel") |
639 | + } |
640 | + return i18n.tr("Failed to leave group") |
641 | + } |
642 | + |
643 | // self contact isn't provided by history or chatEntry, so we manually add it here |
644 | Item { |
645 | id: selfContactWatcher |
646 | @@ -127,7 +159,7 @@ |
647 | |
648 | header: PageHeader { |
649 | id: pageHeader |
650 | - title: i18n.tr("Group Info") |
651 | + title: groupChatInfoPage.headerString |
652 | // FIXME: uncomment once the header supports subtitle |
653 | //subtitle: i18n.tr("%1 member", "%1 members", allParticipants.length) |
654 | flickable: contentsFlickable |
655 | @@ -318,6 +350,10 @@ |
656 | if (!chatRoom || !chatEntry.active) { |
657 | return false |
658 | } |
659 | + // FIXME: temporary workaround |
660 | + if (account && account.protocolInfo.name == "irc") { |
661 | + return false |
662 | + } |
663 | return (chatEntry.groupFlags & ChatEntry.ChannelGroupFlagCanAdd) |
664 | } |
665 | text: !searchItem.enabled ? i18n.tr("Add...") : i18n.tr("Cancel") |
666 | @@ -404,6 +440,10 @@ |
667 | || modelData.state === 2 /*remote pending*/) { |
668 | return false |
669 | } |
670 | + // FIXME: temporary workaround |
671 | + if (account && account.protocolInfo.name == "irc") { |
672 | + return false |
673 | + } |
674 | return (chatEntry.groupFlags & ChatEntry.ChannelGroupFlagCanRemove) |
675 | } |
676 | function removeFromGroup() { |
677 | @@ -452,13 +492,13 @@ |
678 | Button { |
679 | id: leaveButton |
680 | visible: chatRoom && !isPhoneAccount && chatEntry.active && !(chatEntry.selfContactRoles & 2) |
681 | - text: i18n.tr("Leave group") |
682 | + text: groupChatInfoPage.leaveString |
683 | onClicked: { |
684 | if (chatEntry.leaveChat()) { |
685 | - application.showNotificationMessage(i18n.tr("Successfully left group"), "tick") |
686 | + application.showNotificationMessage(groupChatInfoPage.leaveSuccessString, "tick") |
687 | mainView.emptyStack() |
688 | } else { |
689 | - application.showNotificationMessage(i18n.tr("Failed to leave group"), "dialog-error-symbolic") |
690 | + application.showNotificationMessage(groupChatInfoPage.leaveFailedString, "dialog-error-symbolic") |
691 | } |
692 | } |
693 | } |
694 | |
695 | === modified file 'src/qml/MainPage.qml' |
696 | --- src/qml/MainPage.qml 2016-11-09 10:33:23 +0000 |
697 | +++ src/qml/MainPage.qml 2017-03-22 15:12:51 +0000 |
698 | @@ -32,13 +32,18 @@ |
699 | property bool isEmpty: threadCount == 0 && !threadModel.canFetchMore |
700 | property alias threadCount: threadList.count |
701 | property alias displayedThreadIndex: threadList.currentIndex |
702 | - |
703 | - property var _messagesPage: null |
704 | + property bool _keepFocus: true |
705 | |
706 | function startSelection() { |
707 | threadList.startSelection() |
708 | } |
709 | |
710 | + function selectMessage(index) { |
711 | + if (index !== -1) |
712 | + _keepFocus = false |
713 | + threadList.currentIndex = index |
714 | + } |
715 | + |
716 | signal newThreadCreated(var newThread) |
717 | |
718 | TextField { |
719 | @@ -91,6 +96,8 @@ |
720 | objectName: "searchAction" |
721 | iconName: "search" |
722 | text: i18n.tr("Search") |
723 | + shortcut: "Ctrl+F" |
724 | + enabled: mainPage.state == "default" |
725 | onTriggered: { |
726 | mainPage.searching = true |
727 | searchField.forceActiveFocus() |
728 | @@ -101,6 +108,7 @@ |
729 | text: i18n.tr("Settings") |
730 | iconName: "settings" |
731 | onTriggered: { |
732 | + threadList.currentIndex = -1 |
733 | pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("SettingsPage.qml")) |
734 | } |
735 | }, |
736 | @@ -108,7 +116,12 @@ |
737 | objectName: "newMessageAction" |
738 | text: i18n.tr("New message") |
739 | iconName: "add" |
740 | - onTriggered: mainView.startNewMessage() |
741 | + shortcut: "Ctrl+N" |
742 | + enabled: mainPage.state == "default" |
743 | + onTriggered: { |
744 | + threadList.currentIndex = -1 |
745 | + mainView.startNewMessage() |
746 | + } |
747 | } |
748 | ] |
749 | |
750 | @@ -129,6 +142,8 @@ |
751 | visible: mainPage.searching |
752 | iconName: "back" |
753 | text: i18n.tr("Cancel") |
754 | + shortcut: "Esc" |
755 | + enabled: mainPage.state == "search" |
756 | onTriggered: { |
757 | searchField.text = "" |
758 | mainPage.searching = false |
759 | @@ -152,7 +167,9 @@ |
760 | Action { |
761 | objectName: "selectionModeCancelAction" |
762 | iconName: "back" |
763 | + shortcut: "Esc" |
764 | onTriggered: threadList.cancelSelection() |
765 | + enabled: mainPage.state == "selection" |
766 | } |
767 | ] |
768 | |
769 | @@ -225,8 +242,42 @@ |
770 | selected: true |
771 | } |
772 | |
773 | - listDelegate: ThreadDelegate { |
774 | + onCurrentItemChanged: { |
775 | + if (pageStack.columns > 1) { |
776 | + currentItem.show() |
777 | + if (mainPage._keepFocus) |
778 | + // Keep focus on current page |
779 | + threadList.forceActiveFocus() |
780 | + else if (pageStack.activePage) |
781 | + pageStack.activePage.forceActiveFocus() |
782 | + mainPage._keepFocus = true |
783 | + } |
784 | + } |
785 | + |
786 | + listDelegate: ThreadDelegate { |
787 | id: threadDelegate |
788 | + |
789 | + function show() |
790 | + { |
791 | + var properties = model.properties |
792 | + properties["keyboardFocus"] = false |
793 | + properties["threads"] = model.threads |
794 | + var participantIds = []; |
795 | + for (var i in model.participants) { |
796 | + participantIds.push(model.participants[i].identifier) |
797 | + } |
798 | + properties["participantIds"] = participantIds |
799 | + properties["presenceRequest"] = threadDelegate.presenceItem |
800 | + if (displayedEvent != null) { |
801 | + properties["scrollToEventId"] = displayedEvent.eventId |
802 | + } |
803 | + delete properties["participants"] |
804 | + delete properties["localPendingParticipants"] |
805 | + delete properties["remotePendingParticipants"] |
806 | + mainView.showMessagesView(properties) |
807 | + } |
808 | + |
809 | + |
810 | // FIXME: find a better unique name |
811 | objectName: "thread%1".arg(participants[0].identifier) |
812 | Component.onCompleted: mainPage.newThreadCreated(model) |
813 | @@ -245,32 +296,17 @@ |
814 | } |
815 | |
816 | searchTerm: mainPage.searching ? searchField.text : "" |
817 | + |
818 | onClicked: { |
819 | if (threadList.isInSelectionMode) { |
820 | if (!threadList.selectItem(threadDelegate)) { |
821 | threadList.deselectItem(threadDelegate) |
822 | } |
823 | - } else { |
824 | - var properties = model.properties |
825 | - |
826 | - properties["keyboardFocus"] = false |
827 | - properties["threads"] = model.threads |
828 | - var participantIds = []; |
829 | - for (var i in model.participants) { |
830 | - participantIds.push(model.participants[i].identifier) |
831 | - } |
832 | - properties["participantIds"] = participantIds |
833 | - properties["presenceRequest"] = threadDelegate.presenceItem |
834 | - if (displayedEvent != null) { |
835 | - properties["scrollToEventId"] = displayedEvent.eventId |
836 | - } |
837 | - delete properties["participants"] |
838 | - delete properties["localPendingParticipants"] |
839 | - delete properties["remotePendingParticipants"] |
840 | - mainView.showMessagesView(properties) |
841 | + } |
842 | + threadList.currentIndex = index |
843 | |
844 | - // mark this item as current |
845 | - threadList.currentIndex = index |
846 | + if (pageStack.columns <= 1) { |
847 | + show() |
848 | } |
849 | } |
850 | onPressAndHold: { |
851 | @@ -330,9 +366,12 @@ |
852 | interval: 1 |
853 | repeat: false |
854 | running: true |
855 | - onTriggered: createQmlObjectAsynchronously(Qt.resolvedUrl("Scrollbar.qml"), |
856 | - mainPage, |
857 | - {"flickableItem": threadList}) |
858 | + onTriggered: { |
859 | + createQmlObjectAsynchronously(Qt.resolvedUrl("Scrollbar.qml"), |
860 | + mainPage, |
861 | + {"flickableItem": threadList}) |
862 | + threadList.forceActiveFocus() |
863 | + } |
864 | } |
865 | |
866 | Loader { |
867 | @@ -348,4 +387,19 @@ |
868 | hint.visible: enabled |
869 | } |
870 | } |
871 | + |
872 | + onActiveFocusChanged: { |
873 | + if (activeFocus) { |
874 | + threadList.currentItem.forceActiveFocus() |
875 | + } |
876 | + } |
877 | + |
878 | + Binding { |
879 | + target: pageStack |
880 | + property: "activePage" |
881 | + value: mainPage |
882 | + when: pageStack.columns === 1 |
883 | + } |
884 | + |
885 | + KeyNavigation.right: pageStack.activePage |
886 | } |
887 | |
888 | === modified file 'src/qml/MessageBubble.qml' |
889 | --- src/qml/MessageBubble.qml 2016-10-21 12:22:44 +0000 |
890 | +++ src/qml/MessageBubble.qml 2017-03-22 15:12:51 +0000 |
891 | @@ -35,6 +35,8 @@ |
892 | property var messageTimeStamp |
893 | property int maxDelegateWidth: units.gu(27) |
894 | property string accountName |
895 | + property var account |
896 | + property var _accountRegex: account && (account.selfContactId != "") ? new RegExp('\\b' + account.selfContactId + '\\b', 'g') : null |
897 | property bool isMultimedia: false |
898 | // FIXME for now we just display the delivery status if it's greater than Accepted |
899 | property bool showDeliveryStatus: false |
900 | @@ -77,6 +79,11 @@ |
901 | var currentNumber = phoneNumbers[i] |
902 | text = text.replace(currentNumber, formatTelSchemeWith(currentNumber)) |
903 | } |
904 | + |
905 | + // hightlight participants names |
906 | + if (_accountRegex) |
907 | + text = text.replace(_accountRegex, "<b>" + account.selfContactId + "</b>") |
908 | + |
909 | return text |
910 | } |
911 | |
912 | |
913 | === modified file 'src/qml/MessageDelegate.qml' |
914 | --- src/qml/MessageDelegate.qml 2016-11-18 21:15:46 +0000 |
915 | +++ src/qml/MessageDelegate.qml 2017-03-22 15:12:51 +0000 |
916 | @@ -46,6 +46,8 @@ |
917 | property string accountLabel: "" |
918 | property bool isMultimedia: false |
919 | property var _lastItem: textBubble.visible ? textBubble : attachmentsLoader.item.lastItem |
920 | + property alias account: textBubble.account |
921 | + |
922 | swipeEnabled: !(attachmentsLoader.item && attachmentsLoader.item.swipeLocked) |
923 | |
924 | function deleteMessage() |
925 | @@ -188,11 +190,14 @@ |
926 | } |
927 | highlightColor: "transparent" |
928 | |
929 | - onClicked: { |
930 | - if (!selectMode) { |
931 | - // we only have actions for attachment items, so forward the click |
932 | - if (attachmentsLoader.item) { |
933 | - attachmentsLoader.item.clicked(mouse) |
934 | + MouseArea { |
935 | + anchors.fill: parent |
936 | + onClicked: { |
937 | + if (!selectMode) { |
938 | + // we only have actions for attachment items, so forward the click |
939 | + if (attachmentsLoader.item) { |
940 | + attachmentsLoader.item.clicked(mouse) |
941 | + } |
942 | } |
943 | } |
944 | } |
945 | @@ -245,6 +250,7 @@ |
946 | |
947 | MessageBubble { |
948 | id: textBubble |
949 | + |
950 | isMultimedia: messageDelegate.isMultimedia |
951 | anchors { |
952 | bottom: parent.bottom |
953 | @@ -283,7 +289,20 @@ |
954 | messageTimeStamp: messageData.timestamp |
955 | accountName: messageDelegate.accountLabel |
956 | messageStatus: messageData.textMessageStatus |
957 | - sender: (messages.chatType == HistoryThreadModel.ChatTypeRoom || messageData.participants.length > 1) ? messageData.sender.alias !== "" ? messageData.sender.alias : messageData.senderId : "" |
958 | + sender: { |
959 | + if (messages.chatType == HistoryThreadModel.ChatTypeRoom || messageData.participants.length > 1) { |
960 | + if (messageData.sender && messageIncoming) { |
961 | + if (messageData.sender.alias !== undefined && messageData.sender.alias !== "") { |
962 | + return messageData.sender.alias |
963 | + } else if (messageData.sender.identifier !== undefined && messageData.sender.identifier !== "") { |
964 | + return messageData.sender.identifier |
965 | + } else if (messageData.senderId !== "") { |
966 | + return messageData.senderId |
967 | + } |
968 | + } |
969 | + } |
970 | + return "" |
971 | + } |
972 | showDeliveryStatus: true |
973 | } |
974 | |
975 | |
976 | === modified file 'src/qml/MessageInfoDialog.qml' |
977 | --- src/qml/MessageInfoDialog.qml 2015-11-03 13:16:43 +0000 |
978 | +++ src/qml/MessageInfoDialog.qml 2017-03-22 15:12:51 +0000 |
979 | @@ -78,61 +78,96 @@ |
980 | |
981 | function getTargetName(message) |
982 | { |
983 | + if (!message) |
984 | + return "" |
985 | + |
986 | if (message.senderId !== "self") { |
987 | return i18n.tr("Myself") |
988 | - } else if (message.participants.length > 1) { |
989 | + } else if (message.participants && (message.participants.length > 1)) { |
990 | return i18n.tr("Group") |
991 | + } else if (message.participants.length > 0) { |
992 | + return message.participants[0].identifier |
993 | } else { |
994 | - return PhoneUtils.PhoneUtils.format(message.participants[0].identifier) |
995 | + return i18n.tr("Unknown") |
996 | } |
997 | } |
998 | |
999 | title: i18n.tr("Message info") |
1000 | |
1001 | Label { |
1002 | - text: "<b>%1:</b> %2".arg(i18n.tr("Type")).arg(root.activeMessage.type) |
1003 | + text: root.activeMessage ? "<b>%1:</b> %2".arg(i18n.tr("Type")).arg(root.activeMessage.type) : "" |
1004 | } |
1005 | |
1006 | Label { |
1007 | text: "<b>%1:</b> %2".arg(i18n.tr("From")) |
1008 | - .arg(root.activeMessage.senderId !== "self" ? |
1009 | - PhoneUtils.PhoneUtils.format(root.activeMessage.senderId) : i18n.tr("Myself")) |
1010 | + .arg(root.activeMessage && root.activeMessage.senderId !== "self" ? |
1011 | + root.activeMessage && root.activeMessage.senderId : i18n.tr("Myself")) |
1012 | } |
1013 | |
1014 | Label { |
1015 | text: "<b>%1:</b> %2".arg(i18n.tr("To")) |
1016 | .arg(getTargetName(root.activeMessage)) |
1017 | } |
1018 | - Repeater { |
1019 | - model: root.activeMessage.senderId === "self" && root.activeMessage.participants.length > 1 ? root.activeMessage.participants : [] |
1020 | - Label { |
1021 | - text: PhoneUtils.PhoneUtils.format(modelData.identifier) |
1022 | - } |
1023 | - } |
1024 | - |
1025 | - Label { |
1026 | - text: "<b>%1:</b> %2".arg(i18n.tr("Sent")).arg(Qt.formatDateTime(root.activeMessage.timestamp, Qt.DefaultLocaleShortDate)) |
1027 | - visible: (root.activeMessage.senderId === "self") |
1028 | - } |
1029 | - |
1030 | - Label { |
1031 | - text: "<b>%1:</b> %2".arg(i18n.tr("Received")).arg(Qt.formatDateTime(root.activeMessage.timestamp, Qt.DefaultLocaleShortDate)) |
1032 | - visible: (root.activeMessage.senderId !== "self") |
1033 | - } |
1034 | - |
1035 | - Label { |
1036 | - text: "<b>%1:</b> %2".arg(i18n.tr("Read")).arg(Qt.formatDateTime(root.activeMessage.textReadTimestamp, Qt.DefaultLocaleShortDate)) |
1037 | - visible: (root.activeMessage.senderId !== "self") && (root.activeMessage.textReadTimestamp > 0) |
1038 | - } |
1039 | - |
1040 | - Label { |
1041 | - text: "<b>%1:</b> %2".arg(i18n.tr("Status")).arg(statusToString(root.activeMessage.status)) |
1042 | + |
1043 | + /* |
1044 | + // Disable list of contacts for now, this is not reliable on a IRC channel for example |
1045 | + // the current participants can not the same at the moment when the message was sent |
1046 | + ListView { |
1047 | + anchors { |
1048 | + left: parent.left |
1049 | + right: parent.right |
1050 | + } |
1051 | + |
1052 | + height: units.gu(10) //Math.min(count * units.gu(3), units.gu(3)) |
1053 | + model: root.activeMessage && root.activeMessage.senderId === "self" && root.activeMessage.participants.length > 1 ? root.activeMessage.participants : [] |
1054 | + delegate: ListItem { |
1055 | + height: itemLayout.height + (divider.visible ? divider.height : 0) |
1056 | + |
1057 | + ListItemLayout { |
1058 | + id: itemLayout |
1059 | + |
1060 | + title.text: { |
1061 | + var formatted = PhoneUtils.PhoneUtils.format(modelData.identifier) |
1062 | + if (formatted.length > 0) |
1063 | + return formatted |
1064 | + else |
1065 | + return modelData.identifier |
1066 | + } |
1067 | + } |
1068 | + } |
1069 | + } |
1070 | + */ |
1071 | + |
1072 | + Label { |
1073 | + text: root.activeMessage ? |
1074 | + "<b>%1:</b> %2".arg(i18n.tr("Sent")).arg(Qt.formatDateTime(root.activeMessage.timestamp, Qt.DefaultLocaleShortDate)) : |
1075 | + "" |
1076 | + visible: root.activeMessage && (root.activeMessage.senderId === "self") |
1077 | + } |
1078 | + |
1079 | + Label { |
1080 | + text: root.activeMessage ? |
1081 | + "<b>%1:</b> %2".arg(i18n.tr("Received")).arg(Qt.formatDateTime(root.activeMessage.timestamp, Qt.DefaultLocaleShortDate)) : |
1082 | + "" |
1083 | + visible: (root.activeMessage && root.activeMessage.senderId !== "self") |
1084 | + } |
1085 | + |
1086 | + Label { |
1087 | + text: root.activeMessage ? |
1088 | + "<b>%1:</b> %2".arg(i18n.tr("Read")).arg(Qt.formatDateTime(root.activeMessage.textReadTimestamp, Qt.DefaultLocaleShortDate)) : |
1089 | + "" |
1090 | + visible: root.activeMessage && (root.activeMessage.senderId !== "self") && (root.activeMessage.textReadTimestamp > 0) |
1091 | + } |
1092 | + |
1093 | + Label { |
1094 | + text: root.activeMessage ? "<b>%1:</b> %2".arg(i18n.tr("Status")).arg(statusToString(root.activeMessage.status)) : "" |
1095 | } |
1096 | |
1097 | Button { |
1098 | - text: i18n.tr("Close") |
1099 | - onClicked: { |
1100 | - PopupUtils.close(root.activeDialog) |
1101 | + action: Action { |
1102 | + text: i18n.tr("Close") |
1103 | + shortcut: "esc" |
1104 | + onTriggered: PopupUtils.close(root.activeDialog) |
1105 | } |
1106 | } |
1107 | |
1108 | |
1109 | === modified file 'src/qml/Messages.qml' |
1110 | --- src/qml/Messages.qml 2016-11-22 17:32:53 +0000 |
1111 | +++ src/qml/Messages.qml 2017-03-22 15:12:51 +0000 |
1112 | @@ -89,7 +89,7 @@ |
1113 | property bool userTyping: false |
1114 | property string userTypingId: "" |
1115 | property string firstParticipantId: participantIds.length > 0 ? participantIds[0] : "" |
1116 | - property variant firstParticipant: participants.length > 0 ? participants[0] : null |
1117 | + property variant firstParticipant: (participants && participants.length > 0) ? participants[0] : null |
1118 | property var threads: [] |
1119 | property QtObject presenceRequest: presenceItem |
1120 | property var accountsModel: getAccountsModel() |
1121 | @@ -135,6 +135,9 @@ |
1122 | for (var i in messages.accountsModel) { |
1123 | accountNames.push(messages.accountsModel[i].displayName) |
1124 | } |
1125 | + if (messages.accountsModel.length == 1 && messages.accountsModel[0].type == AccountEntry.GenericAccount) { |
1126 | + return accountNames |
1127 | + } |
1128 | return accountNames.length > 1 ? accountNames : [] |
1129 | } |
1130 | |
1131 | @@ -189,9 +192,10 @@ |
1132 | } |
1133 | } |
1134 | return null |
1135 | - } else { |
1136 | - return mainView.account |
1137 | + } else if (!(telepathyHelper.phoneAccounts.active.length > 0) && messages.accountsModel.length > 0) { |
1138 | + return messages.accountsModel[0] |
1139 | } |
1140 | + return mainView.account |
1141 | } |
1142 | |
1143 | function checkThreadInFilters(newAccountId, threadId) { |
1144 | @@ -457,6 +461,7 @@ |
1145 | } |
1146 | |
1147 | function updateFilters(accounts, chatType, participantIds, reload, threads) { |
1148 | + selectThreadOnIdle.restart() |
1149 | if (participantIds.length == 0 || accounts.length == 0) { |
1150 | if (chatType != HistoryThreadModel.ChatTypeRoom) { |
1151 | return null |
1152 | @@ -529,18 +534,44 @@ |
1153 | } |
1154 | } |
1155 | |
1156 | + function selectActiveThread(threads) { |
1157 | + if ((messages.chatType == HistoryEventModel.ChatTypeContact) && |
1158 | + (messages.threads.length > 0)) { |
1159 | + var index = threadModel.indexOf(messages.threads[0].threadId, messages.threads[0].accountId) |
1160 | + if (index != -1) { |
1161 | + mainPage.selectMessage(index) |
1162 | + } |
1163 | + } |
1164 | + } |
1165 | + |
1166 | + // Use a timer to make sure that 'threads' are correct set before try to select it |
1167 | + Timer { |
1168 | + id: selectThreadOnIdle |
1169 | + interval: 100 |
1170 | + repeat: false |
1171 | + running: false |
1172 | + onTriggered: selectActiveThread(messages.threads) |
1173 | + } |
1174 | + |
1175 | + |
1176 | header: PageHeader { |
1177 | id: pageHeader |
1178 | |
1179 | - property alias leadingActions: leadingBar.actions |
1180 | + property bool backEnabled: true |
1181 | property alias trailingActions: trailingBar.actions |
1182 | + property bool showSections: { |
1183 | + if (headerSections.model.length > 1) { |
1184 | + return true |
1185 | + } |
1186 | + return (messages.accountsModel.length == 1 && messages.accountsModel[0].type == AccountEntry.GenericAccount) |
1187 | + } |
1188 | |
1189 | title: { |
1190 | if (landscape) { |
1191 | return "" |
1192 | } |
1193 | |
1194 | - if (participants.length == 1) { |
1195 | + if (participants && participants.length === 1) { |
1196 | return firstRecipientAlias |
1197 | } |
1198 | |
1199 | @@ -556,7 +587,7 @@ |
1200 | leftMargin: units.gu(2) |
1201 | bottom: parent.bottom |
1202 | } |
1203 | - visible: headerSections.model.length > 1 |
1204 | + visible: pageHeader.showSections |
1205 | enabled: visible |
1206 | model: getSectionsModel() |
1207 | selectedIndex: getSelectedIndex() |
1208 | @@ -569,11 +600,23 @@ |
1209 | Component.onCompleted: model = getSectionsModel() |
1210 | } |
1211 | |
1212 | - extension: headerSections.model.length > 1 ? headerSections : null |
1213 | + extension: pageHeader.showSections ? headerSections : null |
1214 | |
1215 | - leadingActionBar { |
1216 | - id: leadingBar |
1217 | - } |
1218 | + leadingActionBar.actions: [ |
1219 | + Action { |
1220 | + iconName: "back" |
1221 | + text: i18n.tr("Back") |
1222 | + shortcut: visible ? "Esc" : "" |
1223 | + visible: pageHeader.backEnabled |
1224 | + onTriggered: { |
1225 | + if (messages.state == "selection") { |
1226 | + messageList.cancelSelection() |
1227 | + } else { |
1228 | + mainView.emptyStack(true) |
1229 | + } |
1230 | + } |
1231 | + } |
1232 | + ] |
1233 | |
1234 | trailingActionBar { |
1235 | id: trailingBar |
1236 | @@ -584,6 +627,7 @@ |
1237 | anchors { |
1238 | bottom: parent.bottom |
1239 | right: parent.right |
1240 | + bottomMargin: -headerSections.height |
1241 | } |
1242 | } |
1243 | } |
1244 | @@ -594,14 +638,6 @@ |
1245 | name: "selection" |
1246 | when: selectionMode |
1247 | |
1248 | - property list<QtObject> leadingActions: [ |
1249 | - Action { |
1250 | - objectName: "selectionModeCancelAction" |
1251 | - iconName: "back" |
1252 | - onTriggered: messageList.cancelSelection() |
1253 | - } |
1254 | - ] |
1255 | - |
1256 | property list<QtObject> trailingActions: [ |
1257 | Action { |
1258 | objectName: "selectionModeSelectAllAction" |
1259 | @@ -631,8 +667,8 @@ |
1260 | PropertyChanges { |
1261 | target: pageHeader |
1262 | title: " " |
1263 | - leadingActions: selectionState.leadingActions |
1264 | trailingActions: selectionState.trailingActions |
1265 | + backEnabled: true |
1266 | } |
1267 | }, |
1268 | State { |
1269 | @@ -646,14 +682,23 @@ |
1270 | objectName: "groupChatAction" |
1271 | iconName: "contact-group" |
1272 | onTriggered: mainStack.addPageToCurrentColumn(messages, Qt.resolvedUrl("GroupChatInfoPage.qml"), { threadInformation: threadInformation, chatEntry: messages.chatEntry, eventModel: eventModel}) |
1273 | + }, |
1274 | + Action { |
1275 | + id: rejoinGroupChatAction |
1276 | + objectName: "rejoinGroupChatAction" |
1277 | + enabled: !chatEntry.active && messages.account.protocolInfo.enableRejoin && messages.account.connected |
1278 | + visible: enabled |
1279 | + iconName: "view-refresh" |
1280 | + onTriggered: messages.chatEntry.startChat() |
1281 | } |
1282 | + |
1283 | ] |
1284 | |
1285 | PropertyChanges { |
1286 | target: pageHeader |
1287 | // TRANSLATORS: %1 refers to the number of participants in a group chat |
1288 | title: { |
1289 | - var finalParticipants = participants.length |
1290 | + var finalParticipants = (participants ? participants.length : 0) |
1291 | if (messages.chatType == HistoryThreadModel.ChatTypeRoom) { |
1292 | if (chatEntry.title !== "") { |
1293 | return chatEntry.title |
1294 | @@ -674,6 +719,7 @@ |
1295 | } |
1296 | contents: headerContents |
1297 | trailingActions: groupChatState.trailingActions |
1298 | + backEnabled: pageStack.columns === 1 |
1299 | } |
1300 | }, |
1301 | State { |
1302 | @@ -684,7 +730,7 @@ |
1303 | property list<QtObject> trailingActions: [ |
1304 | Action { |
1305 | objectName: "contactCallAction" |
1306 | - visible: participants.length == 1 && contactWatcher.interactive |
1307 | + visible: participants && participants.length === 1 && contactWatcher.interactive |
1308 | iconName: "call-start" |
1309 | text: i18n.tr("Call") |
1310 | onTriggered: { |
1311 | @@ -695,7 +741,7 @@ |
1312 | }, |
1313 | Action { |
1314 | objectName: "addContactAction" |
1315 | - visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive |
1316 | + visible: contactWatcher.isUnknown && participants && participants.length === 1 && contactWatcher.interactive |
1317 | iconName: "contact-new" |
1318 | text: i18n.tr("Add") |
1319 | onTriggered: { |
1320 | @@ -709,6 +755,7 @@ |
1321 | target: pageHeader |
1322 | contents: headerContents |
1323 | trailingActions: unknownContactState.trailingActions |
1324 | + backEnabled: pageStack.columns === 1 |
1325 | } |
1326 | }, |
1327 | State { |
1328 | @@ -744,7 +791,7 @@ |
1329 | mmsGroupAction.trigger() |
1330 | return |
1331 | } |
1332 | - contextMenu.caller = header; |
1333 | + contextMenu.caller = trailingActionArea; |
1334 | contextMenu.updateGroupTypes(); |
1335 | contextMenu.show(); |
1336 | } |
1337 | @@ -762,6 +809,12 @@ |
1338 | top: parent ? parent.top: undefined |
1339 | topMargin: units.gu(1) |
1340 | } |
1341 | + onActiveFocusChanged: { |
1342 | + if (!activeFocus && (searchListLoader.status != Loader.Ready || !searchListLoader.item.activeFocus)) |
1343 | + commit() |
1344 | + } |
1345 | + |
1346 | + KeyNavigation.down: searchListLoader.item ? searchListLoader.item : composeBar.textArea |
1347 | } |
1348 | |
1349 | PropertyChanges { |
1350 | @@ -769,16 +822,18 @@ |
1351 | title: " " |
1352 | trailingActions: newMessageState.trailingActions |
1353 | contents: newMessageState.contents |
1354 | + backEnabled: true |
1355 | } |
1356 | }, |
1357 | State { |
1358 | id: knownContactState |
1359 | name: "knownContact" |
1360 | when: participants.length == 1 && !contactWatcher.isUnknown |
1361 | + |
1362 | property list<QtObject> trailingActions: [ |
1363 | Action { |
1364 | objectName: "contactCallKnownAction" |
1365 | - visible: participants.length == 1 |
1366 | + visible: participants && participants.length === 1 |
1367 | iconName: "call-start" |
1368 | text: i18n.tr("Call") |
1369 | onTriggered: { |
1370 | @@ -801,6 +856,7 @@ |
1371 | target: pageHeader |
1372 | contents: headerContents |
1373 | trailingActions: knownContactState.trailingActions |
1374 | + backEnabled: pageStack.columns === 1 |
1375 | } |
1376 | } |
1377 | ] |
1378 | @@ -820,7 +876,7 @@ |
1379 | } |
1380 | } |
1381 | } |
1382 | - newMessage = (messages.accountId == "" && messages.participants.length === 0) |
1383 | + newMessage = (messages.threadId == "") || (messages.accountId == "" && messages.participants.length === 0) |
1384 | restoreBindings() |
1385 | if (threadId !== "" && accountId !== "" && threads.length == 0) { |
1386 | addNewThreadToFilter(accountId, {"threadId": threadId, "chatType": chatType}) |
1387 | @@ -856,7 +912,7 @@ |
1388 | |
1389 | onReady: { |
1390 | isReady = true |
1391 | - if (participants.length === 0 && keyboardFocus) |
1392 | + if (participants && participants.length === 0 && keyboardFocus) |
1393 | multiRecipient.forceFocus() |
1394 | } |
1395 | |
1396 | @@ -869,6 +925,8 @@ |
1397 | messages.ready() |
1398 | } |
1399 | processPendingEvents() |
1400 | + if (!newMessage) |
1401 | + composeBar.forceFocus() |
1402 | } |
1403 | |
1404 | // These fake items are used to track if there are instances loaded |
1405 | @@ -912,6 +970,10 @@ |
1406 | property var participants: null |
1407 | property var account: null |
1408 | text: { |
1409 | + // FIXME: temporary workaround |
1410 | + if (account.protocolInfo.name == "irc") { |
1411 | + return i18n.tr("Join IRC Channel...") |
1412 | + } |
1413 | var protocolDisplayName = account.protocolInfo.serviceDisplayName; |
1414 | if (protocolDisplayName === "") { |
1415 | protocolDisplayName = account.protocolInfo.serviceName; |
1416 | @@ -930,16 +992,23 @@ |
1417 | } |
1418 | actionList.actions = [] |
1419 | |
1420 | - actionList.addAction(mmsGroupAction) |
1421 | - |
1422 | - for (var i in telepathyHelper.textAccounts.active) { |
1423 | + if (telepathyHelper.phoneAccounts.active.length > 0) { |
1424 | + actionList.addAction(mmsGroupAction) |
1425 | + } |
1426 | + if (!account || account.type == AccountEntry.PhoneAccount) { |
1427 | + return |
1428 | + } |
1429 | + var action = customGroupChatActionComponent.createObject(actionList, {"account": account, "participants": multiRecipient.participants}) |
1430 | + actionList.addAction(action) |
1431 | + |
1432 | + /*for (var i in telepathyHelper.textAccounts.active) { |
1433 | var account = telepathyHelper.textAccounts.active[i] |
1434 | if (account.type == AccountEntry.PhoneAccount) { |
1435 | continue |
1436 | } |
1437 | var action = customGroupChatActionComponent.createObject(actionList, {"account": account, "participants": multiRecipient.participants}) |
1438 | actionList.addAction(action) |
1439 | - } |
1440 | + }*/ |
1441 | } |
1442 | } |
1443 | |
1444 | @@ -988,7 +1057,7 @@ |
1445 | participantIds: messages.participantIds |
1446 | chatId: messages.threadId |
1447 | accountId: messages.accountId |
1448 | - autoRequest: !newMessage |
1449 | + autoRequest: !newMessage && !messages.account.protocolInfo.enableRejoin |
1450 | |
1451 | onChatTypeChanged: { |
1452 | messages.chatType = chatEntryObject.chatType |
1453 | @@ -1091,7 +1160,7 @@ |
1454 | return account.accountId |
1455 | } |
1456 | // we just request presence on 1-1 chats |
1457 | - identifier: participants.length == 1 ? participants[0].identifier : "" |
1458 | + identifier: participants && participants.length === 1 ? participants[0].identifier : "" |
1459 | } |
1460 | |
1461 | ActivityIndicator { |
1462 | @@ -1109,7 +1178,7 @@ |
1463 | |
1464 | property int resultCount: (status === Loader.Ready) ? item.count : 0 |
1465 | |
1466 | - source: (multiRecipient.searchString !== "") && multiRecipient.focus ? |
1467 | + source: (multiRecipient.searchString !== "") ? |
1468 | Qt.resolvedUrl("ContactSearchList.qml") : "" |
1469 | clip: true |
1470 | visible: source != "" |
1471 | @@ -1138,6 +1207,17 @@ |
1472 | when: (searchListLoader.status === Loader.Ready) |
1473 | } |
1474 | |
1475 | + Connections { |
1476 | + target: searchListLoader.item |
1477 | + onActiveFocusChanged: { |
1478 | + if (!searchListLoader.item.activeFocus && !multiRecipient.activeFocus) |
1479 | + multiRecipient.commit() |
1480 | + } |
1481 | + onFocusUp: { |
1482 | + multiRecipient.forceActiveFocus() |
1483 | + } |
1484 | + } |
1485 | + |
1486 | Timer { |
1487 | id: checkHeight |
1488 | |
1489 | @@ -1278,12 +1358,21 @@ |
1490 | objectName: "messageList" |
1491 | visible: !isSearching |
1492 | listModel: messages.newMessage ? null : eventModel |
1493 | + account: messages.account |
1494 | + activeFocusOnTab: false |
1495 | + focus: false |
1496 | + onActiveFocusChanged: { |
1497 | + if (activeFocus) { |
1498 | + composeBar.forceFocus() |
1499 | + } |
1500 | + } |
1501 | |
1502 | Rectangle { |
1503 | color: Theme.palette.normal.background |
1504 | anchors.fill: parent |
1505 | Image { |
1506 | width: units.gu(20) |
1507 | + opacity: 0.1 |
1508 | fillMode: Image.PreserveAspectFit |
1509 | anchors.centerIn: parent |
1510 | visible: source !== "" |
1511 | @@ -1346,6 +1435,9 @@ |
1512 | return false |
1513 | } |
1514 | if (threads.length > 0) { |
1515 | + if (!chatEntry.active && messages.account.protocolInfo.enableRejoin) { |
1516 | + return true |
1517 | + } |
1518 | return !threadInformation.chatRoomInfo.Joined |
1519 | } |
1520 | return false |
1521 | @@ -1367,7 +1459,10 @@ |
1522 | right: parent.right |
1523 | } |
1524 | |
1525 | + participants: messages.participants |
1526 | isBroadcast: messages.isBroadcast |
1527 | + returnToSend: messages.account.protocolInfo.returnToSend |
1528 | + enableAttachments: messages.account.protocolInfo.enableAttachments |
1529 | |
1530 | showContents: !selectionMode && !isSearching && !chatInactiveLabel.visible |
1531 | maxHeight: messages.height - keyboard.height - screenTop.y |
1532 | @@ -1439,6 +1534,8 @@ |
1533 | reloadFilters = !reloadFilters |
1534 | } |
1535 | } |
1536 | + |
1537 | + KeyNavigation.up: messages.header.contents |
1538 | } |
1539 | |
1540 | SendMessageValidator { |
1541 | @@ -1474,4 +1571,17 @@ |
1542 | flickableItem: messageList |
1543 | align: Qt.AlignTrailing |
1544 | } |
1545 | + |
1546 | + Binding { |
1547 | + target: pageStack |
1548 | + property: "activePage" |
1549 | + value: messages |
1550 | + when: messages.active |
1551 | + } |
1552 | + |
1553 | + onActiveFocusChanged: { |
1554 | + if (activeFocus && !newMessage) { |
1555 | + composeBar.textArea.forceActiveFocus() |
1556 | + } |
1557 | + } |
1558 | } |
1559 | |
1560 | === modified file 'src/qml/MessagesListView.qml' |
1561 | --- src/qml/MessagesListView.qml 2016-10-12 13:49:49 +0000 |
1562 | +++ src/qml/MessagesListView.qml 2017-03-22 15:12:51 +0000 |
1563 | @@ -30,6 +30,7 @@ |
1564 | |
1565 | property var _currentSwipedItem: null |
1566 | property string latestEventId: "" |
1567 | + property var account: null |
1568 | |
1569 | function shareSelectedMessages() |
1570 | { |
1571 | @@ -92,9 +93,17 @@ |
1572 | var properties = {"messageData": model, |
1573 | "index": Qt.binding(function(){ return index }), |
1574 | "delegateItem": Qt.binding(function(){ return loader })} |
1575 | - var sourceFile = textMessageType == HistoryThreadModel.MessageTypeInformation ? "AccountSectionDelegate.qml" : "RegularMessageDelegate.qml" |
1576 | + var sourceFile =textMessageType == HistoryThreadModel.MessageTypeInformation ? "AccountSectionDelegate.qml" : "RegularMessageDelegate.qml" |
1577 | + sourceFile = application.delegateFromProtocol(Qt.resolvedUrl(sourceFile), account ? account.protocolInfo.name : "") |
1578 | loader.setSource(sourceFile, properties) |
1579 | } |
1580 | + |
1581 | + Binding { |
1582 | + target: loader.item |
1583 | + property: "account" |
1584 | + value: root.account |
1585 | + when: (textMessageType !== HistoryThreadModel.MessageTypeInformation && Loader.Ready) |
1586 | + } |
1587 | } |
1588 | |
1589 | onSelectionDone: { |
1590 | |
1591 | === modified file 'src/qml/MessagingContactEditorPage.qml' |
1592 | --- src/qml/MessagingContactEditorPage.qml 2016-07-19 00:43:19 +0000 |
1593 | +++ src/qml/MessagingContactEditorPage.qml 2017-03-22 15:12:51 +0000 |
1594 | @@ -35,6 +35,7 @@ |
1595 | |
1596 | text: i18n.tr("Cancel") |
1597 | iconName: "back" |
1598 | + shortcut: "Esc" |
1599 | onTriggered: { |
1600 | root.cancel() |
1601 | root.active = false |
1602 | @@ -47,11 +48,17 @@ |
1603 | |
1604 | text: i18n.tr("Save") |
1605 | iconName: "ok" |
1606 | + shortcut: "Ctrl+S" |
1607 | enabled: root.isContactValid |
1608 | onTriggered: root.save() |
1609 | } |
1610 | ] |
1611 | |
1612 | + onActiveChanged: { |
1613 | + if (active) |
1614 | + forceActiveFocus() |
1615 | + } |
1616 | + |
1617 | onContactSaved: { |
1618 | if (root.contactListPage) { |
1619 | if (root.contactListPage.phoneToAdd !== "") { |
1620 | |
1621 | === modified file 'src/qml/MessagingContactViewPage.qml' |
1622 | --- src/qml/MessagingContactViewPage.qml 2016-08-04 20:56:47 +0000 |
1623 | +++ src/qml/MessagingContactViewPage.qml 2017-03-22 15:12:51 +0000 |
1624 | @@ -1,4 +1,4 @@ |
1625 | -/* |
1626 | +/* |
1627 | * Copyright 2015 Canonical Ltd. |
1628 | * |
1629 | * This file is part of messaging-app. |
1630 | @@ -56,6 +56,19 @@ |
1631 | } |
1632 | } |
1633 | |
1634 | + |
1635 | + leadingActions: [ |
1636 | + Action { |
1637 | + objectName: "cancel" |
1638 | + |
1639 | + text: i18n.tr("Cancel") |
1640 | + iconName: "back" |
1641 | + shortcut: "Esc" |
1642 | + onTriggered: pageStack.removePages(root) |
1643 | + } |
1644 | + |
1645 | + ] |
1646 | + |
1647 | headerActions: [ |
1648 | Action { |
1649 | objectName: "share" |
1650 | @@ -73,6 +86,7 @@ |
1651 | text: i18n.tr("Edit") |
1652 | iconName: "edit" |
1653 | visible: root.editable |
1654 | + shortcut: "Ctrl+E" |
1655 | onTriggered: { |
1656 | pageStack.addPageToCurrentColumn(root, contactEditorPageURL, |
1657 | { model: root.model, |
1658 | |
1659 | === modified file 'src/qml/MultiRecipientInput.qml' |
1660 | --- src/qml/MultiRecipientInput.qml 2016-11-17 14:27:35 +0000 |
1661 | +++ src/qml/MultiRecipientInput.qml 2017-03-22 15:12:51 +0000 |
1662 | @@ -16,7 +16,7 @@ |
1663 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1664 | */ |
1665 | |
1666 | -import QtQuick 2.2 |
1667 | +import QtQuick 2.4 |
1668 | import Ubuntu.Components 1.3 |
1669 | import Ubuntu.Contacts 0.1 |
1670 | import Ubuntu.Telephony 0.1 |
1671 | @@ -30,13 +30,11 @@ |
1672 | readonly property var participants: getParticipants() |
1673 | property string searchString: "" |
1674 | property var repeater: null |
1675 | + property string defaultHint: i18n.tr("To:") |
1676 | + |
1677 | signal clearSearch() |
1678 | - styleName: "TextFieldStyle" |
1679 | - clip: true |
1680 | - height: contactFlow.height |
1681 | - focus: activeFocus |
1682 | - property string defaultHint: i18n.tr("To:") |
1683 | - onRecipientsChanged: getParticipants() |
1684 | + signal forceFocus() |
1685 | + |
1686 | function getParticipants() { |
1687 | var participants = [] |
1688 | var repeater = multiRecipientWidget.repeater |
1689 | @@ -55,15 +53,6 @@ |
1690 | return participants |
1691 | } |
1692 | |
1693 | - signal forceFocus() |
1694 | - |
1695 | - MouseArea { |
1696 | - anchors.fill: scrollableArea |
1697 | - enabled: parent.focus === false |
1698 | - onClicked: forceFocus() |
1699 | - z: 1 |
1700 | - } |
1701 | - |
1702 | function addRecipient(identifier, contact) { |
1703 | for (var i = 0; i<recipientModel.count; i++) { |
1704 | // FIXME: replace by a phone number comparison method |
1705 | @@ -77,6 +66,35 @@ |
1706 | scrollableArea.contentX = contactFlow.width |
1707 | } |
1708 | |
1709 | + function commit() { |
1710 | + for (var i=0; i < rpt.count; i++) { |
1711 | + var loader = rpt.itemAt(i) |
1712 | + if (loader.status !== Loader.Ready) |
1713 | + continue |
1714 | + |
1715 | + var obj = loader.item |
1716 | + if (obj.objectName === "contactSearchInput") { |
1717 | + if (obj.text != "") { |
1718 | + addRecipient(obj.text) |
1719 | + obj.text = "" |
1720 | + } |
1721 | + } |
1722 | + } |
1723 | + } |
1724 | + |
1725 | + onRecipientsChanged: getParticipants() |
1726 | + styleName: "TextFieldStyle" |
1727 | + clip: true |
1728 | + height: contactFlow.height |
1729 | + focus: activeFocus |
1730 | + |
1731 | + MouseArea { |
1732 | + anchors.fill: scrollableArea |
1733 | + enabled: parent.focus === false |
1734 | + onClicked: forceFocus() |
1735 | + z: 1 |
1736 | + } |
1737 | + |
1738 | Behavior on height { |
1739 | UbuntuNumberAnimation {} |
1740 | } |
1741 | @@ -193,12 +211,7 @@ |
1742 | color: Theme.palette.normal.backgroundText |
1743 | font.pixelSize: FontUtils.sizeToPixels("medium") |
1744 | inputMethodHints: Qt.ImhNoPredictiveText |
1745 | - onActiveFocusChanged: { |
1746 | - if (!activeFocus && text !== "") { |
1747 | - addRecipient(text) |
1748 | - text = "" |
1749 | - } |
1750 | - } |
1751 | + |
1752 | onTextChanged: { |
1753 | if (text.substring(text.length -1, text.length) == ",") { |
1754 | addRecipient(text.substring(0, text.length - 1)) |
1755 | @@ -207,6 +220,7 @@ |
1756 | } |
1757 | searchString = text |
1758 | } |
1759 | + |
1760 | Keys.onReturnPressed: { |
1761 | if (text == "") |
1762 | return |
1763 | @@ -277,22 +291,19 @@ |
1764 | } |
1765 | } |
1766 | |
1767 | - Icon { |
1768 | + TransparentButton { |
1769 | id: addIcon |
1770 | - name: "add" |
1771 | - height: units.gu(2) |
1772 | + |
1773 | + iconName: "add" |
1774 | + height: units.gu(1.5) |
1775 | anchors { |
1776 | right: parent.right |
1777 | rightMargin: units.gu(2) |
1778 | verticalCenter: parent.verticalCenter |
1779 | } |
1780 | - MouseArea { |
1781 | - anchors.fill: parent |
1782 | - anchors.margins: units.gu(-3) |
1783 | - onClicked: { |
1784 | + onClicked: { |
1785 | Qt.inputMethod.hide() |
1786 | mainStack.addPageToCurrentColumn(messages, Qt.resolvedUrl("NewRecipientPage.qml"), {"itemCallback": multiRecipient}) |
1787 | - } |
1788 | } |
1789 | z: 2 |
1790 | } |
1791 | |
1792 | === modified file 'src/qml/NewGroupPage.qml' |
1793 | --- src/qml/NewGroupPage.qml 2016-10-11 02:01:24 +0000 |
1794 | +++ src/qml/NewGroupPage.qml 2017-03-22 15:12:51 +0000 |
1795 | @@ -16,7 +16,7 @@ |
1796 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1797 | */ |
1798 | |
1799 | -import QtQuick 2.0 |
1800 | +import QtQuick 2.4 |
1801 | import Ubuntu.Components 1.3 |
1802 | import Ubuntu.Components.ListItems 1.3 as ListItems |
1803 | import Ubuntu.History 0.1 |
1804 | @@ -29,6 +29,21 @@ |
1805 | property bool creationInProgress: false |
1806 | property var participants: [] |
1807 | property var account: null |
1808 | + readonly property bool allowCreateGroup: { |
1809 | + if (newGroupPage.creationInProgress) { |
1810 | + return false |
1811 | + } |
1812 | + if (account.protocolInfo.joinExistingChannels && groupTitleField.text != "") { |
1813 | + return true |
1814 | + } |
1815 | + if (participantsModel.count == 0) { |
1816 | + return false |
1817 | + } |
1818 | + if (!mmsGroup) { |
1819 | + return ((groupTitleField.text != "" || groupTitleField.inputMethodComposing) && participantsModel.count > 1) |
1820 | + } |
1821 | + return participantsModel.count > 1 |
1822 | + } |
1823 | |
1824 | function addRecipient(identifier, contact) { |
1825 | var alias = contact.displayLabel.label |
1826 | @@ -49,6 +64,17 @@ |
1827 | participantsModel.append({"identifier": identifier, "alias": alias, "avatar": avatar }) |
1828 | } |
1829 | |
1830 | + function commit() { |
1831 | + if (allowCreateGroup) { |
1832 | + Qt.inputMethod.commit() |
1833 | + newGroupPage.creationInProgress = true |
1834 | + if (account.protocolInfo.joinExistingChannels) { |
1835 | + chatEntry.chatId = groupTitleField.text |
1836 | + } |
1837 | + chatEntry.startChat() |
1838 | + } |
1839 | + } |
1840 | + |
1841 | header: PageHeader { |
1842 | title: { |
1843 | if (creationInProgress) { |
1844 | @@ -57,6 +83,10 @@ |
1845 | if (mmsGroup) { |
1846 | return i18n.tr("New MMS Group") |
1847 | } else { |
1848 | + // FIXME: temporary workaround |
1849 | + if (account && account.protocolInfo.name == "irc") { |
1850 | + return i18n.tr("Join IRC channel:") |
1851 | + } |
1852 | var protocolDisplayName = account.protocolInfo.serviceDisplayName; |
1853 | if (protocolDisplayName === "") { |
1854 | protocolDisplayName = account.protocolInfo.serviceName; |
1855 | @@ -69,6 +99,7 @@ |
1856 | Action { |
1857 | objectName: "cancelAction" |
1858 | iconName: "close" |
1859 | + shortcut: "Esc" |
1860 | onTriggered: { |
1861 | Qt.inputMethod.commit() |
1862 | mainStack.removePages(newGroupPage) |
1863 | @@ -79,25 +110,11 @@ |
1864 | trailingActionBar { |
1865 | actions: [ |
1866 | Action { |
1867 | + id: createAction |
1868 | objectName: "createAction" |
1869 | - enabled: { |
1870 | - if (newGroupPage.creationInProgress) { |
1871 | - return false |
1872 | - } |
1873 | - if (participantsModel.count == 0) { |
1874 | - return false |
1875 | - } |
1876 | - if (!mmsGroup) { |
1877 | - return ((groupTitleField.text != "" || groupTitleField.inputMethodComposing) && participantsModel.count > 1) |
1878 | - } |
1879 | - return participantsModel.count > 1 |
1880 | - } |
1881 | + enabled: newGroupPage.allowCreateGroup |
1882 | iconName: "ok" |
1883 | - onTriggered: { |
1884 | - Qt.inputMethod.commit() |
1885 | - newGroupPage.creationInProgress = true |
1886 | - chatEntry.startChat() |
1887 | - } |
1888 | + onTriggered: newGroupPage.commit() |
1889 | } |
1890 | ] |
1891 | } |
1892 | @@ -209,7 +226,13 @@ |
1893 | verticalAlignment: Text.AlignVCenter |
1894 | anchors.verticalCenter: groupTitleField.verticalCenter |
1895 | anchors.left: parent.left |
1896 | - text: i18n.tr("Group name:") |
1897 | + text: { |
1898 | + // FIXME: temporary workaround |
1899 | + if (account && account.protocolInfo.name == "irc") { |
1900 | + return i18n.tr("Channel name:") |
1901 | + } |
1902 | + return i18n.tr("Group name:") |
1903 | + } |
1904 | } |
1905 | TextField { |
1906 | id: groupTitleField |
1907 | @@ -221,8 +244,16 @@ |
1908 | top: parent.top |
1909 | } |
1910 | height: units.gu(4) |
1911 | - placeholderText: i18n.tr("Type a name...") |
1912 | + placeholderText: { |
1913 | + // FIXME: temporary workaround |
1914 | + if (account && account.protocolInfo.name == "irc") { |
1915 | + return i18n.tr("#channelName") |
1916 | + } |
1917 | + return i18n.tr("Type a name...") |
1918 | + } |
1919 | inputMethodHints: Qt.ImhNoPredictiveText |
1920 | + Keys.onReturnPressed: newGroupPage.commit() |
1921 | + Keys.onEnterPressed: newGroupPage.commit() |
1922 | Timer { |
1923 | interval: 1 |
1924 | onTriggered: { |
1925 | @@ -249,6 +280,7 @@ |
1926 | ContactSearchWidget { |
1927 | id: searchItem |
1928 | parentPage: newGroupPage |
1929 | + visible: !account.protocolInfo.joinExistingChannels |
1930 | searchResultsHeight: flick.emptySpaceHeight |
1931 | onContactPicked: addRecipientFromSearch(identifier, alias, avatar) |
1932 | anchors { |
1933 | @@ -259,6 +291,7 @@ |
1934 | } |
1935 | Rectangle { |
1936 | id: separator2 |
1937 | + visible: !account.protocolInfo.joinExistingChannels |
1938 | anchors { |
1939 | left: parent.left |
1940 | right: parent.right |
1941 | @@ -285,6 +318,7 @@ |
1942 | anchors.top: searchItem.bottom |
1943 | anchors.left: parent.left |
1944 | anchors.right: parent.right |
1945 | + visible: !account.protocolInfo.joinExistingChannels |
1946 | Repeater { |
1947 | id: participantsRepeater |
1948 | model: participantsModel |
1949 | @@ -302,4 +336,11 @@ |
1950 | KeyboardRectangle { |
1951 | id: keyboard |
1952 | } |
1953 | + |
1954 | + onActiveChanged: { |
1955 | + if (active) |
1956 | + searchItem.forceActiveFocus() |
1957 | + } |
1958 | + |
1959 | + Component.onCompleted: searchItem.forceActiveFocus() |
1960 | } |
1961 | |
1962 | === modified file 'src/qml/NewRecipientPage.qml' |
1963 | --- src/qml/NewRecipientPage.qml 2016-08-04 20:56:47 +0000 |
1964 | +++ src/qml/NewRecipientPage.qml 2017-03-22 15:12:51 +0000 |
1965 | @@ -74,6 +74,8 @@ |
1966 | } |
1967 | ] |
1968 | } |
1969 | + |
1970 | + |
1971 | } |
1972 | |
1973 | Sections { |
1974 | @@ -133,6 +135,8 @@ |
1975 | Action { |
1976 | iconName: "back" |
1977 | text: i18n.tr("Cancel") |
1978 | + enabled: newRecipientPage.state == "searching" |
1979 | + shortcut: "Esc" |
1980 | onTriggered: { |
1981 | newRecipientPage.forceActiveFocus() |
1982 | newRecipientPage.state = "default" |
1983 | @@ -171,6 +175,10 @@ |
1984 | bottom: keyboard.top |
1985 | } |
1986 | |
1987 | + focus: true |
1988 | + currentIndex: -1 |
1989 | + highlightSelected: true |
1990 | + activeFocusOnTab: true |
1991 | showAddNewButton: true |
1992 | showImportOptions: (contactList.count === 0) && (filterTerm == "") |
1993 | // this will be used to callback the app, after create account |
1994 | @@ -211,6 +219,10 @@ |
1995 | onActiveChanged: { |
1996 | if (active && (state === "searching")) { |
1997 | searchField.forceActiveFocus() |
1998 | + } else { |
1999 | + if (contactList.currentIndex === -1) |
2000 | + contactList.currentIndex = 0 |
2001 | + contactList.forceActiveFocus() |
2002 | } |
2003 | } |
2004 | |
2005 | @@ -243,4 +255,19 @@ |
2006 | } |
2007 | } |
2008 | } |
2009 | + |
2010 | + // WORKAROUND: Wee need this button to register the "Esc" shortcut, |
2011 | + // adding it into the trailingActionBar cause the app to crash due a bug on SDK |
2012 | + Button { |
2013 | + visible: false |
2014 | + action: Action { |
2015 | + text: i18n.tr("Back") |
2016 | + enabled: newRecipientPage.active |
2017 | + shortcut: "Esc" |
2018 | + onTriggered: { |
2019 | + mainStack.removePages(newRecipientPage) |
2020 | + newRecipientPage.destroy() |
2021 | + } |
2022 | + } |
2023 | + } |
2024 | } |
2025 | |
2026 | === added file 'src/qml/OnlineAccountsHelper.qml' |
2027 | --- src/qml/OnlineAccountsHelper.qml 1970-01-01 00:00:00 +0000 |
2028 | +++ src/qml/OnlineAccountsHelper.qml 2017-03-22 15:12:51 +0000 |
2029 | @@ -0,0 +1,91 @@ |
2030 | +/* |
2031 | + * Copyright (C) 2014 Canonical, Ltd. |
2032 | + * |
2033 | + * This program is free software; you can redistribute it and/or modify |
2034 | + * it under the terms of the GNU General Public License as published by |
2035 | + * the Free Software Foundation; version 3. |
2036 | + * |
2037 | + * This program is distributed in the hope that it will be useful, |
2038 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2039 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2040 | + * GNU General Public License for more details. |
2041 | + * |
2042 | + * You should have received a copy of the GNU General Public License |
2043 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2044 | + */ |
2045 | + |
2046 | +import QtQuick 2.4 |
2047 | +import Ubuntu.Components 1.3 |
2048 | +import Ubuntu.OnlineAccounts 0.1 |
2049 | +import Ubuntu.OnlineAccounts.Client 0.1 |
2050 | +import Ubuntu.Components.Popups 1.3 |
2051 | + |
2052 | +Item { |
2053 | + id: root |
2054 | + |
2055 | + property var dialogInstance: null |
2056 | + |
2057 | + function run(){ |
2058 | + if (!root.dialogInstance) { |
2059 | + root.dialogInstance = PopupUtils.open(dialog) |
2060 | + } |
2061 | + } |
2062 | + |
2063 | + Component { |
2064 | + id: dialog |
2065 | + Dialog { |
2066 | + id: dialogue |
2067 | + title: "Online Accounts" |
2068 | + text: i18n.tr("Pick an account to create.") |
2069 | + |
2070 | + ScrollView { |
2071 | + width: dialog.width |
2072 | + height: Math.min(listView.count, 3) * units.gu(7) |
2073 | + |
2074 | + ListView { |
2075 | + id: listView |
2076 | + |
2077 | + anchors.fill: parent |
2078 | + clip: true |
2079 | + model: ProviderModel { |
2080 | + applicationId: "messaging-app" |
2081 | + } |
2082 | + delegate: ListItem { |
2083 | + ListItemLayout { |
2084 | + title.text: model.displayName |
2085 | + |
2086 | + Image { |
2087 | + SlotsLayout.position: SlotsLayout.First |
2088 | + source: "image://theme/" + model.iconName |
2089 | + width: units.gu(5) |
2090 | + height: width |
2091 | + } |
2092 | + } |
2093 | + onClicked: { |
2094 | + listView.enabled = false |
2095 | + setup.providerId = model.providerId |
2096 | + setup.exec() |
2097 | + } |
2098 | + } |
2099 | + } |
2100 | + } |
2101 | + Button { |
2102 | + text: i18n.tr("Cancel") |
2103 | + onClicked: PopupUtils.close(dialogue) |
2104 | + } |
2105 | + |
2106 | + Component.onDestruction: { |
2107 | + root.dialogInstance = null |
2108 | + } |
2109 | + } |
2110 | + } |
2111 | + |
2112 | + Setup { |
2113 | + id: setup |
2114 | + applicationId: "messaging-app" |
2115 | + providerId: "irc" |
2116 | + onFinished: { |
2117 | + PopupUtils.close(root.dialogInstance) |
2118 | + } |
2119 | + } |
2120 | +} |
2121 | |
2122 | === modified file 'src/qml/ParticipantInfoPage.qml' |
2123 | --- src/qml/ParticipantInfoPage.qml 2016-10-07 13:28:15 +0000 |
2124 | +++ src/qml/ParticipantInfoPage.qml 2017-03-22 15:12:51 +0000 |
2125 | @@ -154,6 +154,15 @@ |
2126 | } |
2127 | |
2128 | Button { |
2129 | + id: sendMessageButton |
2130 | + text: i18n.tr("Send private message") |
2131 | + onClicked: { |
2132 | + mainView.startChat({accountId: chatEntry.accountId, participantIds: [participant.identifier]}) |
2133 | + pageStack.removePages(participantInfoPage) |
2134 | + } |
2135 | + } |
2136 | + |
2137 | + Button { |
2138 | id: leaveButton |
2139 | visible: delegate.canRemove() |
2140 | text: i18n.tr("Remove from group") |
2141 | |
2142 | === modified file 'src/qml/ParticipantsPopover.qml' |
2143 | --- src/qml/ParticipantsPopover.qml 2016-10-06 07:41:14 +0000 |
2144 | +++ src/qml/ParticipantsPopover.qml 2017-03-22 15:12:51 +0000 |
2145 | @@ -16,53 +16,145 @@ |
2146 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2147 | */ |
2148 | |
2149 | -import QtQuick 2.2 |
2150 | +import QtQuick 2.4 |
2151 | import Ubuntu.Components 1.3 |
2152 | -import Ubuntu.Components.ListItems 1.3 as ListItem |
2153 | import Ubuntu.Components.Popups 1.3 |
2154 | import Ubuntu.Contacts 0.1 |
2155 | import Ubuntu.Telephony 0.1 |
2156 | |
2157 | import "dateUtils.js" as DateUtils |
2158 | |
2159 | -Popover { |
2160 | - id: participantsPopover |
2161 | + |
2162 | +Item { |
2163 | + id: root |
2164 | |
2165 | property variant participants: [] |
2166 | - |
2167 | - anchorToKeyboard: false |
2168 | - Column { |
2169 | - id: containerLayout |
2170 | - anchors { |
2171 | - left: parent.left |
2172 | - top: parent.top |
2173 | - right: parent.right |
2174 | - } |
2175 | - Repeater { |
2176 | - model: participants |
2177 | - Item { |
2178 | - height: childrenRect.height |
2179 | - width: participantsPopover.width |
2180 | - ListItem.Standard { |
2181 | - id: participant |
2182 | + readonly property bool active: (_popover != null) |
2183 | + readonly property bool popupVisible: active && _popover.isPopup |
2184 | + |
2185 | + property variant _popover: null |
2186 | + property var _sortedParticipants: [] |
2187 | + |
2188 | + function compareParticipants(p0, p1) |
2189 | + { |
2190 | + var i0 = String(p0.identifier).toLocaleLowerCase() |
2191 | + var i1 = String(p1.identifier).toLocaleLowerCase() |
2192 | + |
2193 | + if (i0 < i1) |
2194 | + return -1 |
2195 | + if (i0 > i1) |
2196 | + return 1 |
2197 | + return 0 |
2198 | + } |
2199 | + |
2200 | + function close() |
2201 | + { |
2202 | + if (_popover) { |
2203 | + if (_popover.isPopup) |
2204 | + PopupUtils.close(_popover) |
2205 | + else |
2206 | + root._popover.destroy() |
2207 | + root._popover = null |
2208 | + } |
2209 | + } |
2210 | + |
2211 | + function showParticpantsStartWith(parent, prefix, showPopup) |
2212 | + { |
2213 | + var filter = [] |
2214 | + for(var i = 0; i < participants.length; i++) { |
2215 | + var valid = true |
2216 | + if (prefix.length !== 0) { |
2217 | + valid = String(participants[i].identifier).indexOf(prefix) === 0 |
2218 | + } |
2219 | + |
2220 | + if (valid) { |
2221 | + filter.push(participants[i]) |
2222 | + } |
2223 | + } |
2224 | + |
2225 | + root._sortedParticipants = filter |
2226 | + if (filter.length === 0 && popupVisible) |
2227 | + { |
2228 | + return "" |
2229 | + } |
2230 | + |
2231 | + if ((filter.length === 1) && popupVisible) |
2232 | + { |
2233 | + return filter[0].identifier |
2234 | + } |
2235 | + |
2236 | + if (_popover === null) { |
2237 | + if (showPopup) |
2238 | + _popover = PopupUtils.open(componentParticipantsPopover, parent) |
2239 | + else |
2240 | + _popover = nonVisualPopover.createObject(root, {"currentIndex": 0}) |
2241 | + |
2242 | + } |
2243 | + |
2244 | + _popover.model = _sortedParticipants |
2245 | + return (filter.length > 0 ? filter[0].identifier : "") |
2246 | + } |
2247 | + |
2248 | + function nextItem() |
2249 | + { |
2250 | + if (_popover === null) |
2251 | + return "" |
2252 | + |
2253 | + var newIndex = -1 |
2254 | + if (_popover.currentIndex < (_sortedParticipants.length - 1)) |
2255 | + newIndex = _popover.currentIndex + 1 |
2256 | + else |
2257 | + newIndex = 0 |
2258 | + |
2259 | + _popover.currentIndex = newIndex |
2260 | + return (_sortedParticipants[newIndex].identifier) |
2261 | + } |
2262 | + |
2263 | + Component { |
2264 | + id: nonVisualPopover |
2265 | + |
2266 | + QtObject { |
2267 | + property var model: view.model |
2268 | + property int currentIndex: -1 |
2269 | + readonly property bool isPopup: false |
2270 | + |
2271 | + Component.onDestruction: root._popover = null |
2272 | + } |
2273 | + } |
2274 | + |
2275 | + Component { |
2276 | + id: componentParticipantsPopover |
2277 | + |
2278 | + Popover { |
2279 | + id: participantsPopover |
2280 | + |
2281 | + property alias model: view.model |
2282 | + property alias currentIndex: view.currentIndex |
2283 | + readonly property bool isPopup: true |
2284 | + |
2285 | + UbuntuListView { |
2286 | + id: view |
2287 | + |
2288 | + width: root.width |
2289 | + height: Math.min(contentHeight, root.height / 2) |
2290 | + model: [] |
2291 | + |
2292 | + delegate: ListItem { |
2293 | objectName: "participant%1".arg(index) |
2294 | - text: contactWatcher.isUnknown ? contactWatcher.identifier : contactWatcher.alias |
2295 | - onClicked: { |
2296 | - PopupUtils.close(participantsPopover) |
2297 | - mainView.startChat(contactWatcher.identifier) |
2298 | + |
2299 | + width: view.width |
2300 | + height: layout.height |
2301 | + onClicked: root.selected(modelData) |
2302 | + |
2303 | + ListItemLayout { |
2304 | + id: layout |
2305 | + title.text: modelData.identifier |
2306 | } |
2307 | } |
2308 | - ContactWatcher { |
2309 | - id: contactWatcher |
2310 | - identifier: modelData.identifier |
2311 | - contactId: modelData.contactId |
2312 | - alias: modelData.alias |
2313 | - avatar: modelData.avatar |
2314 | - detailProperties: modelData.detailProperties |
2315 | - |
2316 | - addressableFields: messages.account.addressableVCardFields |
2317 | - } |
2318 | + Keys.onEscapePressed: root.selected(null) |
2319 | } |
2320 | + |
2321 | + Component.onDestruction: root._popover = null |
2322 | } |
2323 | } |
2324 | } |
2325 | |
2326 | === modified file 'src/qml/RegularMessageDelegate.qml' |
2327 | --- src/qml/RegularMessageDelegate.qml 2016-10-14 14:02:04 +0000 |
2328 | +++ src/qml/RegularMessageDelegate.qml 2017-03-22 15:12:51 +0000 |
2329 | @@ -36,6 +36,7 @@ |
2330 | property var textMessage: messageData.textMessage |
2331 | property string accountId: messageData.accountId |
2332 | property int index: -1 |
2333 | + property alias account: messageDelegate.account |
2334 | |
2335 | // WORKAROUND: we can not use sections because the verticalLayoutDirection is ListView.BottomToTop the sections will appear |
2336 | // bellow the item |
2337 | |
2338 | === added file 'src/qml/RegularMessageDelegate_irc.qml' |
2339 | --- src/qml/RegularMessageDelegate_irc.qml 1970-01-01 00:00:00 +0000 |
2340 | +++ src/qml/RegularMessageDelegate_irc.qml 2017-03-22 15:12:51 +0000 |
2341 | @@ -0,0 +1,206 @@ |
2342 | +/* |
2343 | + * Copyright 2012-2016 Canonical Ltd. |
2344 | + * |
2345 | + * This file is part of messaging-app. |
2346 | + * |
2347 | + * messaging-app is free software; you can redistribute it and/or modify |
2348 | + * it under the terms of the GNU General Public License as published by |
2349 | + * the Free Software Foundation; version 3. |
2350 | + * |
2351 | + * messaging-app is distributed in the hope that it will be useful, |
2352 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2353 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2354 | + * GNU General Public License for more details. |
2355 | + * |
2356 | + * You should have received a copy of the GNU General Public License |
2357 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2358 | + */ |
2359 | + |
2360 | +import QtQuick 2.2 |
2361 | +import Ubuntu.Components 1.3 |
2362 | +import Ubuntu.Contacts 0.1 |
2363 | +import Ubuntu.History 0.1 |
2364 | +import Ubuntu.Telephony.PhoneNumber 0.1 as PhoneNumber |
2365 | + |
2366 | +import "3rd_party/ba-linkify.js" as BaLinkify |
2367 | + |
2368 | +ListItem { |
2369 | + id: messageDelegate |
2370 | + objectName: "messageDelegate" |
2371 | + |
2372 | + // To be used by actions |
2373 | + property int _index: index |
2374 | + |
2375 | + property var messageData: null |
2376 | + property string messageText: messageData ? messageData.textMessage : "" |
2377 | + property bool incoming: (messageData && messageData.senderId !== "self") |
2378 | + property string accountLabel: "" |
2379 | + property var account: null |
2380 | + property var _accountRegex: account && (account.selfContactId != "") ? new RegExp('\\b' + account.selfContactId + '\\b', 'g') : null |
2381 | + |
2382 | + function getCountryCode() { |
2383 | + var localeName = Qt.locale().name |
2384 | + return localeName.substr(localeName.length - 2, 2) |
2385 | + } |
2386 | + |
2387 | + function deleteMessage() |
2388 | + { |
2389 | + eventModel.removeEvents([messageData.properties]); |
2390 | + } |
2391 | + |
2392 | + function forwardMessage() |
2393 | + { |
2394 | + var properties = {} |
2395 | + var items = [{"text": textMessage, "url":""}] |
2396 | + emptyStack() |
2397 | + var transfer = {} |
2398 | + transfer["items"] = items |
2399 | + properties["sharedAttachmentsTransfer"] = transfer |
2400 | + |
2401 | + mainView.showMessagesView(properties) |
2402 | + } |
2403 | + |
2404 | + function copyMessage() |
2405 | + { |
2406 | + Clipboard.push(messageText) |
2407 | + application.showNotificationMessage(i18n.tr("Text message copied to clipboard"), "edit-copy") |
2408 | + } |
2409 | + |
2410 | + function resendMessage() |
2411 | + { |
2412 | + messages.validator.validateMessageAndSend(textMessage, messages.participantIds, [], {"x-canonical-tmp-files": true}, [messageDelegate.deleteMessage]) |
2413 | + } |
2414 | + |
2415 | + width: messageList.width |
2416 | + height: label.contentHeight |
2417 | + divider.visible: false |
2418 | + contentItem.clip: false |
2419 | + |
2420 | + Label { |
2421 | + id: label |
2422 | + |
2423 | + function parseText(text) { |
2424 | + if (!text) { |
2425 | + return text; |
2426 | + } |
2427 | + |
2428 | + // remove html tags |
2429 | + text = text.replace(/</g,'<').replace(/>/g,'<tt>></tt>'); |
2430 | + // wrap text in a div to keep whitespaces and new lines from collapsing |
2431 | + text = '<div style="white-space: pre-wrap;">' + text + '</div>'; |
2432 | + // check for links |
2433 | + var htmlText = BaLinkify.linkify(text); |
2434 | + if (htmlText !== text) { |
2435 | + return htmlText |
2436 | + } |
2437 | + |
2438 | + // linkify phone numbers if no web links were found |
2439 | + var phoneNumbers = PhoneNumber.PhoneUtils.matchInText(text, getCountryCode()) |
2440 | + for (var i = 0; i < phoneNumbers.length; ++i) { |
2441 | + var currentNumber = phoneNumbers[i] |
2442 | + text = text.replace(currentNumber, formatTelSchemeWith(currentNumber)) |
2443 | + } |
2444 | + |
2445 | + if ((messages.chatType !== HistoryThreadModel.ChatTypeRoom) || |
2446 | + !messageDelegate.incoming || |
2447 | + !_accountRegex) { |
2448 | + } |
2449 | + |
2450 | + return text.replace(_accountRegex, "<b>" + account.selfContactId + "</b>") |
2451 | + } |
2452 | + |
2453 | + property string sender: { |
2454 | + if (messageData.sender && incoming) { |
2455 | + if (messageData.sender.alias !== undefined && messageData.sender.alias !== "") { |
2456 | + return messageData.sender.alias |
2457 | + } else if (messageData.sender.identifier !== undefined && messageData.sender.identifier !== "") { |
2458 | + return messageData.sender.identifier |
2459 | + } else if (messageData.senderId !== "") { |
2460 | + return messageData.senderId |
2461 | + } |
2462 | + } else if (account.selfContactId == "") { |
2463 | + // Return first part of display name if account id is empty |
2464 | + var displayName = account.displayName.substring(0, account.displayName.indexOf('@')) |
2465 | + return displayName |
2466 | + } else { |
2467 | + return account.selfContactId |
2468 | + } |
2469 | + } |
2470 | + |
2471 | + |
2472 | + anchors { |
2473 | + left: parent.left |
2474 | + right: parent.right |
2475 | + margins: units.gu(1) |
2476 | + } |
2477 | + text: "%1 <font color=\"%2\">[%3]</font>\t%4" |
2478 | + .arg(Qt.formatTime(messageData.timestamp, Qt.DefaultLocaleShortDate)) |
2479 | + .arg(incoming ? "green" : "blue") |
2480 | + .arg(sender) |
2481 | + .arg(parseText(messageDelegate.messageText)) |
2482 | + |
2483 | + wrapMode: Text.WordWrap |
2484 | + |
2485 | + onLinkActivated: Qt.openUrlExternally(link) |
2486 | + } |
2487 | + |
2488 | + leadingActions: ListItemActions { |
2489 | + actions: [ |
2490 | + Action { |
2491 | + iconName: "delete" |
2492 | + text: i18n.tr("Delete") |
2493 | + onTriggered: deleteMessage() |
2494 | + } |
2495 | + ] |
2496 | + } |
2497 | + |
2498 | + trailingActions: ListItemActions { |
2499 | + actions: [ |
2500 | + Action { |
2501 | + id: retryAction |
2502 | + |
2503 | + iconName: "reload" |
2504 | + text: i18n.tr("Retry") |
2505 | + visible: messageData.textMessageStatus === HistoryThreadModel.MessageStatusPermanentlyFailed |
2506 | + onTriggered: messageDelegate.resendMessage() |
2507 | + }, |
2508 | + Action { |
2509 | + id: copyAction |
2510 | + |
2511 | + iconName: "edit-copy" |
2512 | + text: i18n.tr("Copy") |
2513 | + visible: messageText !== "" |
2514 | + onTriggered: messageDelegate.copyMessage() |
2515 | + }, |
2516 | + Action { |
2517 | + id: forwardAction |
2518 | + |
2519 | + iconName: "mail-forward" |
2520 | + text: i18n.tr("Forward") |
2521 | + onTriggered: messageDelegate.forwardMessage() |
2522 | + }, |
2523 | + Action { |
2524 | + id: infoAction |
2525 | + |
2526 | + iconName: "info" |
2527 | + text: i18n.tr("Info") |
2528 | + onTriggered: { |
2529 | + var messageInfo = {"type": i18n.tr("IRC"), |
2530 | + "senderId": messageData.senderId, |
2531 | + "sender": messageData.sender, |
2532 | + "timestamp": messageData.timestamp, |
2533 | + "textReadTimestamp": messageData.textReadTimestamp, |
2534 | + "status": messageData.textMessageStatus, |
2535 | + "participants": messages.participants } |
2536 | + messageInfoDialog.showMessageInfo(messageInfo) |
2537 | + } |
2538 | + } |
2539 | + ] |
2540 | + } |
2541 | + |
2542 | + Component.onCompleted: { |
2543 | + if (messageData.newEvent) { |
2544 | + messages.markMessageAsRead(messageData.accountId, threadId, eventId, type); |
2545 | + } |
2546 | + } |
2547 | +} |
2548 | |
2549 | === modified file 'src/qml/SettingsPage.qml' |
2550 | --- src/qml/SettingsPage.qml 2016-11-10 01:36:05 +0000 |
2551 | +++ src/qml/SettingsPage.qml 2017-03-22 15:12:51 +0000 |
2552 | @@ -18,7 +18,7 @@ |
2553 | |
2554 | import QtQuick 2.2 |
2555 | import Ubuntu.Components 1.3 |
2556 | -import Ubuntu.Components.ListItems 1.3 as ListItem |
2557 | +import Ubuntu.OnlineAccounts.Client 0.1 |
2558 | |
2559 | Page { |
2560 | id: settingsPage |
2561 | @@ -31,8 +31,15 @@ |
2562 | property var settingsModel: [ |
2563 | { "name": "mmsEnabled", |
2564 | "description": i18n.tr("Enable MMS messages"), |
2565 | - "property": telepathyHelper.mmsEnabled |
2566 | - }/*, |
2567 | + "property": telepathyHelper.mmsEnabled, |
2568 | + "activatedFuncion": null |
2569 | + }, |
2570 | + { "name": "addAccount", |
2571 | + "description": i18n.tr("Add an online account"), |
2572 | + "onActivated": "createAccount", |
2573 | + "property": null |
2574 | + } |
2575 | + /*, |
2576 | { "name": "characterCountEnabled", |
2577 | "description": i18n.tr("Show character count"), |
2578 | "property": msgSettings.showCharacterCount |
2579 | @@ -53,47 +60,64 @@ |
2580 | header: PageHeader { |
2581 | id: pageHeader |
2582 | title: settingsPage.title |
2583 | - leadingActionBar { |
2584 | - id: leadingBar |
2585 | + leadingActionBar.actions: [ |
2586 | + Action { |
2587 | + iconName: "back" |
2588 | + text: i18n.tr("Back") |
2589 | + shortcut: "Esc" |
2590 | + onTriggered: mainView.emptyStack(true) |
2591 | + } |
2592 | + ] |
2593 | + flickable: settingsList |
2594 | + } |
2595 | + |
2596 | + onActiveChanged: { |
2597 | + if (active) { |
2598 | + settingsList.forceActiveFocus() |
2599 | } |
2600 | } |
2601 | |
2602 | + |
2603 | Component { |
2604 | id: settingDelegate |
2605 | - Item { |
2606 | - anchors.left: parent.left |
2607 | - anchors.right: parent.right |
2608 | - height: units.gu(6) |
2609 | - Label { |
2610 | - id: descriptionLabel |
2611 | - text: modelData.description |
2612 | - anchors.left: parent.left |
2613 | - anchors.right: checkbox.left |
2614 | - anchors.verticalCenter: parent.verticalCenter |
2615 | - anchors.leftMargin: units.gu(2) |
2616 | + ListItem { |
2617 | + onClicked: { |
2618 | + if (checkbox.visible) { |
2619 | + checkbox.checked = !checkbox.checked |
2620 | + } else { |
2621 | + settingsPage[modelData.onActivated]() |
2622 | + } |
2623 | } |
2624 | - Switch { |
2625 | - id: checkbox |
2626 | - objectName: modelData.name |
2627 | - anchors.right: parent.right |
2628 | - anchors.rightMargin: units.gu(2) |
2629 | - anchors.verticalCenter: parent.verticalCenter |
2630 | - checked: modelData.property |
2631 | - onCheckedChanged: { |
2632 | - if (checked != modelData.property) { |
2633 | - settingsPage.setMethods[modelData.name](checked) |
2634 | + ListItemLayout { |
2635 | + title.text: modelData.description |
2636 | + |
2637 | + CheckBox { |
2638 | + id: checkbox |
2639 | + objectName: modelData.name |
2640 | + |
2641 | + visible: modelData.property !== null |
2642 | + SlotsLayout.position: SlotsLayout.trailing |
2643 | + checked: modelData.property |
2644 | + onCheckedChanged: { |
2645 | + if (checked != modelData.property) { |
2646 | + settingsPage.setMethods[modelData.name](checked) |
2647 | + } |
2648 | } |
2649 | } |
2650 | + |
2651 | + ProgressionSlot { |
2652 | + visible: modelData.property === null |
2653 | + } |
2654 | } |
2655 | } |
2656 | } |
2657 | |
2658 | - ListView { |
2659 | + UbuntuListView { |
2660 | + id: settingsList |
2661 | + |
2662 | anchors { |
2663 | - top: pageHeader.bottom |
2664 | - left: parent.left |
2665 | - right: parent.right |
2666 | - bottom: parent.bottom |
2667 | + //topMargin: mainPage.header.flickable ? 0 : mainPage.header.height |
2668 | + fill: parent |
2669 | } |
2670 | model: settingsModel |
2671 | delegate: settingDelegate |
2672 | @@ -102,7 +126,7 @@ |
2673 | Loader { |
2674 | id: messagesBottomEdgeLoader |
2675 | active: mainView.dualPanel |
2676 | - asynchronous: true |
2677 | + //asynchronous: true |
2678 | /* FIXME: would be even more efficient to use setSource() to |
2679 | delay the compilation step but a bug in Qt prevents us. |
2680 | Ref.: https://bugreports.qt.io/browse/QTBUG-54657 |
2681 | @@ -114,4 +138,20 @@ |
2682 | hint.height: 0 |
2683 | } |
2684 | } |
2685 | + |
2686 | + function createAccount() |
2687 | + { |
2688 | + if (onlineAccountHelper.item) |
2689 | + onlineAccountHelper.item.run() |
2690 | + } |
2691 | + |
2692 | + Loader { |
2693 | + id: onlineAccountHelper |
2694 | + |
2695 | + anchors.fill: parent |
2696 | + asynchronous: true |
2697 | + source: Qt.resolvedUrl("OnlineAccountsHelper.qml") |
2698 | + } |
2699 | + |
2700 | + |
2701 | } |
2702 | |
2703 | === modified file 'src/qml/TransparentButton.qml' |
2704 | --- src/qml/TransparentButton.qml 2016-06-27 11:59:26 +0000 |
2705 | +++ src/qml/TransparentButton.qml 2017-03-22 15:12:51 +0000 |
2706 | @@ -16,7 +16,7 @@ |
2707 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2708 | */ |
2709 | |
2710 | -import QtQuick 2.0 |
2711 | +import QtQuick 2.4 |
2712 | import Ubuntu.Components 1.3 |
2713 | |
2714 | Item { |
2715 | @@ -45,10 +45,14 @@ |
2716 | signal pressed() |
2717 | signal released() |
2718 | |
2719 | + Keys.onEnterPressed: clicked() |
2720 | + Keys.onReturnPressed: clicked() |
2721 | + |
2722 | Item { |
2723 | id: iconShape |
2724 | height: iconSize |
2725 | width: iconSize |
2726 | + //visible: false |
2727 | anchors { |
2728 | left: parent.left |
2729 | right: sideBySide ? undefined : parent.right |
2730 | @@ -103,4 +107,17 @@ |
2731 | font.family: "Ubuntu" |
2732 | font.pixelSize: FontUtils.sizeToPixels("small") |
2733 | } |
2734 | + |
2735 | + // draw focus border |
2736 | + activeFocusOnTab: true |
2737 | + Rectangle { |
2738 | + anchors { |
2739 | + fill: parent |
2740 | + margins: units.gu(-1) |
2741 | + } |
2742 | + border.color: Theme.palette.selected.focus |
2743 | + color: "transparent" |
2744 | + visible: parent.activeFocus |
2745 | + radius: 10 |
2746 | + } |
2747 | } |
2748 | |
2749 | === modified file 'src/qml/messaging-app.qml' |
2750 | --- src/qml/messaging-app.qml 2016-10-21 12:22:44 +0000 |
2751 | +++ src/qml/messaging-app.qml 2017-03-22 15:12:51 +0000 |
2752 | @@ -36,6 +36,7 @@ |
2753 | property bool dualPanel: mainStack.columns > 1 |
2754 | property bool composingNewMessage: activeMessagesView && activeMessagesView.newMessage |
2755 | property QtObject activeMessagesView: null |
2756 | + property var _pendingProperties: null |
2757 | |
2758 | function updateNewMessageStatus() { |
2759 | activeMessagesView = application.findMessagingChild("messagesPage", "active", true) |
2760 | @@ -138,6 +139,15 @@ |
2761 | } |
2762 | } |
2763 | |
2764 | + Component.onDestruction: { |
2765 | + for (var i in telepathyHelper.textAccounts.active) { |
2766 | + var account = telepathyHelper.textAccounts.active[i] |
2767 | + if (account.protocolInfo.leaveRoomsOnClose) { |
2768 | + chatManager.leaveRooms(account.accountId, "") |
2769 | + } |
2770 | + } |
2771 | + } |
2772 | + |
2773 | Connections { |
2774 | target: telepathyHelper |
2775 | // restore default bindings if any system settings changed |
2776 | @@ -170,6 +180,23 @@ |
2777 | |
2778 | HistoryGroupedThreadsModel { |
2779 | id: threadModel |
2780 | + |
2781 | + function indexOf(threadId, accountId) { |
2782 | + for (var i=0; i < count; i++) { |
2783 | + var threads = get(i) |
2784 | + for (var t=0; t < threads.length; t++) { |
2785 | + var thread = threads[t] |
2786 | + if (thread.threadId === threadId) { |
2787 | + if (accountId && (thread.accountId == accountId)) |
2788 | + return i |
2789 | + else if (!accountId) |
2790 | + return i |
2791 | + } |
2792 | + } |
2793 | + } |
2794 | + return -1 |
2795 | + } |
2796 | + |
2797 | type: HistoryThreadModel.EventTypeText |
2798 | sort: HistorySort { |
2799 | sortField: "lastEventTimestamp" |
2800 | @@ -233,12 +260,13 @@ |
2801 | if (showEmpty) { |
2802 | showEmptyState() |
2803 | } |
2804 | - mainPage.displayedThreadIndex = -1 |
2805 | + mainPage.forceActiveFocus() |
2806 | } |
2807 | |
2808 | function showEmptyState() { |
2809 | if (mainStack.columns > 1 && !application.findMessagingChild("emptyStatePage")) { |
2810 | layout.addPageToNextColumn(mainPage, Qt.resolvedUrl("EmptyStatePage.qml")) |
2811 | + mainPage.displayedThreadIndex = -1 |
2812 | } |
2813 | } |
2814 | |
2815 | @@ -317,7 +345,17 @@ |
2816 | return threads |
2817 | } |
2818 | |
2819 | - function startChat(properties) { |
2820 | + function startChatLate(properties) { |
2821 | + if (!properties && !_pendingProperties) |
2822 | + return |
2823 | + |
2824 | + if (!properties) |
2825 | + properties = _pendingProperties |
2826 | + |
2827 | + // make sure that is called only once, disconnect |
2828 | + _pendingProperties = null |
2829 | + telepathyHelper.onSetupReady.disconnect(startChatLate) |
2830 | + |
2831 | var participantIds = [] |
2832 | var accountId = "" |
2833 | var match = HistoryThreadModel.MatchCaseSensitive |
2834 | @@ -346,9 +384,39 @@ |
2835 | } |
2836 | } |
2837 | |
2838 | + // Try to select the corrent thread on thread list |
2839 | + accountId = properties.accountId |
2840 | + var threadId = properties.threadId |
2841 | + if (!threadId && (properties["threads"].length > 0)) { |
2842 | + threadId = properties["threads"][0].threadId |
2843 | + if (!accountId) |
2844 | + accountId = properties["threads"][0].accountId |
2845 | + } |
2846 | + |
2847 | + if (threadId) { |
2848 | + var index = threadModel.indexOf(properties.threadId, accountId) |
2849 | + if (index !== -1) { |
2850 | + mainPage.selectMessage(index) |
2851 | + return |
2852 | + } |
2853 | + } |
2854 | showMessagesView(properties) |
2855 | } |
2856 | |
2857 | + function startChat(properties) { |
2858 | + if (!telepathyHelper.ready) { |
2859 | + if (_pendingProperties) { |
2860 | + _pendingProperties = properties |
2861 | + } else { |
2862 | + _pendingProperties = properties |
2863 | + // wait for telepathy |
2864 | + telepathyHelper.onSetupReady.connect(startChatLate) |
2865 | + } |
2866 | + } else { |
2867 | + startChatLate(properties) |
2868 | + } |
2869 | + } |
2870 | + |
2871 | Connections { |
2872 | target: UriHandler |
2873 | onOpened: { |
2874 | @@ -360,6 +428,9 @@ |
2875 | |
2876 | AdaptivePageLayout { |
2877 | id: layout |
2878 | + |
2879 | + property var activePage: null |
2880 | + |
2881 | anchors.fill: parent |
2882 | layouts: PageColumnsLayout { |
2883 | when: mainStack.width >= units.gu(90) |
2884 | |
2885 | === modified file 'tests/qml/tst_MessagesView.qml' |
2886 | --- tests/qml/tst_MessagesView.qml 2016-11-04 18:24:21 +0000 |
2887 | +++ tests/qml/tst_MessagesView.qml 2017-03-22 15:12:51 +0000 |
2888 | @@ -75,6 +75,12 @@ |
2889 | |
2890 | Item { |
2891 | id: application |
2892 | + |
2893 | + function delegateFromProtocol(delegate, protocol) |
2894 | + { |
2895 | + return delegate |
2896 | + } |
2897 | + |
2898 | function findMessagingChild(name) |
2899 | { |
2900 | return null |
looks good. thanks.