Merge lp:~phablet-team/messaging-app/irc-style into lp:messaging-app/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
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.

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

Revision history for this message
Tiago Salem Herrmann (tiagosh) wrote :

looks good. thanks.

review: Approve

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,'&lt;').replace(/>/g,'<tt>&gt;</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

Subscribers

People subscribed via source and target branches

to all changes: