Merge lp:~phablet-team/messaging-app/side_panel into lp:messaging-app

Proposed by Gustavo Pichorim Boiko
Status: Superseded
Proposed branch: lp:~phablet-team/messaging-app/side_panel
Merge into: lp:messaging-app
Prerequisite: lp:~phablet-team/messaging-app/add-apparmor-profile
Diff against target: 2824 lines (+1073/-1017)
29 files modified
CMakeLists.txt (+14/-18)
TODO.convergence (+3/-0)
config.h.in (+1/-0)
src/messagingapplication.cpp (+1/-0)
src/qml/AccountSectionDelegate.qml (+2/-1)
src/qml/ComposeBar.qml (+4/-0)
src/qml/EmptyState.qml (+38/-0)
src/qml/InputInfo.qml (+24/-0)
src/qml/LocalPageWithBottomEdge.qml (+0/-428)
src/qml/MMS/Previewer.qml (+26/-15)
src/qml/MMS/PreviewerImage.qml (+1/-0)
src/qml/MMS/PreviewerMultipleContacts.qml (+7/-2)
src/qml/MMS/PreviewerVideo.qml (+1/-0)
src/qml/MMSDelegate.qml (+1/-1)
src/qml/MainPage.qml (+136/-65)
src/qml/Messages.qml (+452/-350)
src/qml/MessagesHeader.qml (+7/-2)
src/qml/MessagingBottomEdge.qml (+43/-0)
src/qml/MessagingContactEditorPage.qml (+6/-2)
src/qml/MessagingContactViewPage.qml (+11/-10)
src/qml/MessagingPageLayout.qml (+64/-0)
src/qml/MultiRecipientInput.qml (+1/-1)
src/qml/NewRecipientPage.qml (+69/-37)
src/qml/RegularMessageDelegate.qml (+1/-1)
src/qml/SettingsPage.qml (+22/-1)
src/qml/messaging-app.qml (+115/-30)
tests/autopilot/messaging_app/emulators.py (+12/-48)
tests/qml/CMakeLists.txt (+2/-1)
tests/qml/tst_MessagesView.qml (+9/-4)
To merge this branch: bzr merge lp:~phablet-team/messaging-app/side_panel
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Ubuntu Phablet Team Pending
Review via email: mp+284299@code.launchpad.net

This proposal supersedes a proposal from 2016-01-14.

This proposal has been superseded by a proposal from 2016-02-05.

Commit message

Implement convergent layout for messaging-app.

Description of the change

Implement convergent layout for messaging-app.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
484. By Gustavo Pichorim Boiko

Set the title correctly.

485. By Gustavo Pichorim Boiko

Make it possible to unselect all too in the main view.

486. By Gustavo Pichorim Boiko

Fix the header in the messages view.

487. By Gustavo Pichorim Boiko

Clear some warnings.

488. By Gustavo Pichorim Boiko

Make sure the page has a height when loading.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
489. By Tiago Salem Herrmann

Fix ListView anchors also on NewRecipientPage

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
490. By Gustavo Pichorim Boiko

Create pages manually before pushing to the AdaptivePageLayout as it is very
slow to create the pages by itself.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
491. By Gustavo Pichorim Boiko

Make sure pages get destroyed when hitting back

492. By Tiago Salem Herrmann

Implement back button to destroy dynamically created pages

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
493. By Tiago Salem Herrmann

Push all views synchronously

494. By Gustavo Pichorim Boiko

Make it possible to launch the new message view from settings.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
495. By Tiago Salem Herrmann

fix anchors in the settings page

496. By Tiago Salem Herrmann

Empty stack before adding SettingsPage to the stack

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
497. By Tiago Salem Herrmann

Only set properties once during object creation
implement removePage() that destroy instances when needed

498. By Tiago Salem Herrmann

check before destroying instance

499. By Tiago Salem Herrmann

anchor previewer to header

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
500. By Gustavo Pichorim Boiko

Move the custom page layout functions to a separate QML file.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
501. By Gustavo Pichorim Boiko

Merge swipe to cancel fix.

502. By Gustavo Pichorim Boiko

Remove duplicated code.

503. By Gustavo Pichorim Boiko

Fix loading pages by source

504. By Gustavo Pichorim Boiko

Hide the bottom edge bar in the empty state screen.

505. By Gustavo Pichorim Boiko

Messaging-app doesn't belong to side stage anymore

506. By Gustavo Pichorim Boiko

Set a minimum window size.

507. By Gustavo Pichorim Boiko

Make it landscape by default on windowed environments

508. By Gustavo Pichorim Boiko

Hide the new message list item once the message is sent.

509. By Gustavo Pichorim Boiko

Show the group chat participants popup anchored to the right

510. By Gustavo Pichorim Boiko

Make the header fixed in two column mode

511. By Gustavo Pichorim Boiko

Add a scrollbar to the Messages view.

512. By Gustavo Pichorim Boiko

Merge trunk.

513. By Gustavo Pichorim Boiko

Make the readme more clear

514. By Gustavo Pichorim Boiko

In dual panel mode, always hide the empty state label.

Unmerged revisions

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 2015-10-13 15:13:32 +0000
3+++ CMakeLists.txt 2016-02-01 23:18:57 +0000
4@@ -35,12 +35,7 @@
5 # Instruct CMake to run moc automatically when needed.
6 set(CMAKE_AUTOMOC ON)
7
8-configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY)
9-
10-#find_package(Qt5Contacts)
11 find_package(Qt5DBus)
12-#find_package(Qt5Gui)
13-#find_package(Qt5Multimedia)
14 find_package(Qt5Qml)
15 find_package(Qt5Quick)
16 find_package(Qt5Test)
17@@ -49,23 +44,24 @@
18 include(qt5)
19
20 find_package(PkgConfig REQUIRED)
21-#pkg_check_modules(TP_QT5 REQUIRED TelepathyQt5)
22-#pkg_check_modules(TPL_QT5 REQUIRED TelepathyLoggerQt5)
23-#pkg_check_modules(QTGLIB REQUIRED QtGLib-2.0)
24-#pkg_check_modules(GLIB REQUIRED glib-2.0)
25 pkg_check_modules(NOTIFY REQUIRED libnotify)
26-#pkg_check_modules(MESSAGING_MENU REQUIRED messaging-menu)
27-
28-# Check if the messaging menu has the message header
29-#set(CMAKE_REQUIRED_INCLUDES ${MESSAGING_MENU_INCLUDE_DIRS})
30-#check_include_file("messaging-menu-message.h" HAVE_MESSAGING_MENU_MESSAGE)
31-
32-if (HAVE_MESSAGING_MENU_MESSAGE)
33- add_definitions(-DHAVE_MESSAGING_MENU_MESSAGE)
34-endif (HAVE_MESSAGING_MENU_MESSAGE)
35+
36+#find unity8 qml libraries
37+set(UNITY8_QML_PATH /usr/lib/${CMAKE_C_LIBRARY_ARCHITECTURE}/unity8/qml/)
38+find_path(LIB_UNITY_QML_EXISTS NAMES libUnity-qml.so
39+ HINTS "${UNITY8_QML_PATH}"
40+ NO_CMAKE_PATH
41+ NO_CMAKE_ENVIRONMENT_PATH
42+ NO_SYSTEM_ENVIRONMENT_PATH
43+)
44+if(!LIB_UNITY_QML_EXISTS)
45+ MESSAGE(FATAL_ERROR "unity8 private package not-found")
46+endif()
47
48 add_definitions(-DQT_NO_KEYWORDS)
49
50+configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY)
51+
52 include_directories(
53 ${CMAKE_CURRENT_BINARY_DIR}
54 ${CMAKE_CURRENT_SOURCE_DIR}
55
56=== added file 'TODO.convergence'
57--- TODO.convergence 1970-01-01 00:00:00 +0000
58+++ TODO.convergence 2016-02-01 23:18:57 +0000
59@@ -0,0 +1,3 @@
60+- Right now if you call startChat() in the two panel layout, it won't match the
61+ thread on the left side, and if you resize it to one panel, the conversation
62+ is closed.
63
64=== modified file 'config.h.in'
65--- config.h.in 2014-05-28 20:28:55 +0000
66+++ config.h.in 2016-02-01 23:18:57 +0000
67@@ -27,6 +27,7 @@
68 #include <QtDBus/QDBusReply>
69
70 #define I18N_DIRECTORY "@CMAKE_INSTALL_PREFIX@/share/locale"
71+#define UNITY8_QML_PATH "@UNITY8_QML_PATH@"
72
73 inline bool isRunningInstalled() {
74 static bool installed = (QCoreApplication::applicationDirPath() ==
75
76=== modified file 'src/messagingapplication.cpp'
77--- src/messagingapplication.cpp 2016-02-01 23:18:57 +0000
78+++ src/messagingapplication.cpp 2016-02-01 23:18:57 +0000
79@@ -165,6 +165,7 @@
80 m_view->rootContext()->setContextProperty("application", this);
81 m_view->rootContext()->setContextProperty("i18nDirectory", I18N_DIRECTORY);
82 m_view->engine()->setBaseUrl(QUrl::fromLocalFile(messagingAppDirectory()));
83+ m_view->engine()->addImportPath(UNITY8_QML_PATH);
84
85 // check if there is a contacts backend override
86 QString contactsBackend = qgetenv("QTCONTACTS_MANAGER_OVERRIDE");
87
88=== modified file 'src/qml/AccountSectionDelegate.qml'
89--- src/qml/AccountSectionDelegate.qml 2015-09-14 13:51:27 +0000
90+++ src/qml/AccountSectionDelegate.qml 2016-02-01 23:18:57 +0000
91@@ -28,7 +28,8 @@
92 property var messageData: null
93 property int index: -1
94 property Item delegateItem
95- property string accountLabel: telepathyHelper.accountForId(messageData.accountId).displayName
96+ property var account: telepathyHelper.accountForId(messageData.accountId)
97+ property string accountLabel: account ? account.displayName : ""
98
99 // update the accountLabel when the list of accounts become available
100 Item {
101
102=== modified file 'src/qml/ComposeBar.qml'
103--- src/qml/ComposeBar.qml 2016-02-01 23:18:57 +0000
104+++ src/qml/ComposeBar.qml 2016-02-01 23:18:57 +0000
105@@ -64,6 +64,10 @@
106 }
107
108 function addAttachments(transfer) {
109+ if (!transfer || !transfer.items) {
110+ return
111+ }
112+
113 for (var i = 0; i < transfer.items.length; i++) {
114 if (String(transfer.items[i].text).length > 0) {
115 composeBar.text = String(transfer.items[i].text)
116
117=== added file 'src/qml/EmptyState.qml'
118--- src/qml/EmptyState.qml 1970-01-01 00:00:00 +0000
119+++ src/qml/EmptyState.qml 2016-02-01 23:18:57 +0000
120@@ -0,0 +1,38 @@
121+import QtQuick 2.0
122+import Ubuntu.Components 1.3
123+
124+Item {
125+ id: emptyStateScreen
126+
127+ property alias labelVisible: emptyStateLabel.visible
128+
129+ anchors {
130+ left: parent.left
131+ leftMargin: units.gu(6)
132+ right: parent.right
133+ rightMargin: units.gu(6)
134+ verticalCenter: parent.verticalCenter
135+ }
136+ height: childrenRect.height
137+ Icon {
138+ id: emptyStateIcon
139+ anchors.top: emptyStateScreen.top
140+ anchors.horizontalCenter: parent.horizontalCenter
141+ height: units.gu(5)
142+ width: height
143+ opacity: 0.3
144+ name: "message"
145+ }
146+ Label {
147+ id: emptyStateLabel
148+ anchors.top: emptyStateIcon.bottom
149+ anchors.topMargin: units.gu(2)
150+ anchors.left: parent.left
151+ anchors.right: parent.right
152+ text: i18n.tr("Compose a new message by swiping up from the bottom of the screen.")
153+ color: "#5d5d5d"
154+ fontSize: "x-large"
155+ wrapMode: Text.WordWrap
156+ horizontalAlignment: Text.AlignHCenter
157+ }
158+}
159
160=== added file 'src/qml/InputInfo.qml'
161--- src/qml/InputInfo.qml 1970-01-01 00:00:00 +0000
162+++ src/qml/InputInfo.qml 2016-02-01 23:18:57 +0000
163@@ -0,0 +1,24 @@
164+import QtQuick 2.0
165+//import Unity.InputInfo 0.1
166+
167+Item {
168+ // FIXME: implement correctly without relying on unity private stuff
169+ property bool hasMouse: mainView.dualPanel //miceModel.count > 0 || touchPadModel.count > 0
170+ property bool hasKeyboard: false //keyboardsModel.count > 0
171+
172+ /*InputDeviceModel {
173+ id: miceModel
174+ deviceFilter: InputInfo.Mouse
175+ }
176+
177+ InputDeviceModel {
178+ id: touchPadModel
179+ deviceFilter: InputInfo.TouchPad
180+ }
181+
182+ InputDeviceModel {
183+ id: keyboardsModel
184+ deviceFilter: InputInfo.Keyboard
185+ }*/
186+}
187+
188
189=== removed file 'src/qml/LocalPageWithBottomEdge.qml'
190--- src/qml/LocalPageWithBottomEdge.qml 2015-09-14 13:51:27 +0000
191+++ src/qml/LocalPageWithBottomEdge.qml 1970-01-01 00:00:00 +0000
192@@ -1,428 +0,0 @@
193-/*
194- * Copyright (C) 2014 Canonical, Ltd.
195- *
196- * This program is free software; you can redistribute it and/or modify
197- * it under the terms of the GNU General Public License as published by
198- * the Free Software Foundation; version 3.
199- *
200- * This program is distributed in the hope that it will be useful,
201- * but WITHOUT ANY WARRANTY; without even the implied warranty of
202- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
203- * GNU General Public License for more details.
204- *
205- * You should have received a copy of the GNU General Public License
206- * along with this program. If not, see <http://www.gnu.org/licenses/>.
207- */
208-
209-/*
210- Example:
211-
212- MainView {
213- objectName: "mainView"
214-
215- applicationName: "com.ubuntu.developer.boiko.bottomedge"
216-
217- width: units.gu(100)
218- height: units.gu(75)
219-
220- Component {
221- id: pageComponent
222-
223- PageWithBottomEdge {
224- id: mainPage
225- title: i18n.tr("Main Page")
226-
227- Rectangle {
228- anchors.fill: parent
229- color: "white"
230- }
231-
232- bottomEdgePageComponent: Page {
233- title: "Contents"
234- anchors.fill: parent
235- //anchors.topMargin: contentsPage.flickable.contentY
236-
237- ListView {
238- anchors.fill: parent
239- model: 50
240- delegate: ListItems.Standard {
241- text: "One Content Item: " + index
242- }
243- }
244- }
245- bottomEdgeTitle: i18n.tr("Bottom edge action")
246- }
247- }
248-
249- PageStack {
250- id: stack
251- Component.onCompleted: stack.push(pageComponent)
252- }
253- }
254-
255-*/
256-
257-import QtQuick 2.2
258-import Ubuntu.Components 1.3
259-
260-Page {
261- id: page
262-
263- property alias bottomEdgePageComponent: edgeLoader.sourceComponent
264- property alias bottomEdgePageSource: edgeLoader.source
265- property alias bottomEdgeTitle: tipLabel.text
266- property bool bottomEdgeEnabled: true
267- property int bottomEdgeExpandThreshold: page.height * 0.2
268- property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
269- property bool reloadBottomEdgePage: true
270-
271- readonly property alias bottomEdgePage: edgeLoader.item
272- readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
273- readonly property bool isCollapsed: (bottomEdge.y === page.height)
274- readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
275- property var temporaryProperties: null
276-
277- property bool _showEdgePageWhenReady: false
278- property int _areaWhenExpanded: 0
279-
280- signal bottomEdgeReleased()
281- signal bottomEdgeDismissed()
282-
283-
284- function showBottomEdgePage(source, properties)
285- {
286- edgeLoader.setSource(source, properties)
287- temporaryProperties = properties
288- _showEdgePageWhenReady = true
289- }
290-
291- function setBottomEdgePage(source, properties)
292- {
293- edgeLoader.setSource(source, properties)
294- }
295-
296- function _pushPage()
297- {
298- if (edgeLoader.status === Loader.Ready) {
299- edgeLoader.item.active = true
300- page.pageStack.push(edgeLoader.item)
301- if (edgeLoader.item.flickable) {
302- edgeLoader.item.flickable.contentY = -page.header.height
303- edgeLoader.item.flickable.returnToBounds()
304- }
305- if (edgeLoader.item.ready)
306- edgeLoader.item.ready()
307- }
308- }
309-
310-
311- Component.onCompleted: {
312- // avoid a binding on the expanded height value
313- var expandedHeight = height;
314- _areaWhenExpanded = expandedHeight;
315- }
316-
317- onActiveChanged: {
318- if (active) {
319- bottomEdge.state = "collapsed"
320- }
321- }
322-
323- onBottomEdgePageLoadedChanged: {
324- if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
325- bottomEdge.state = "expanded"
326- _showEdgePageWhenReady = false
327- }
328- }
329-
330- InverseMouseArea {
331- anchors.fill: edgeLoader.item
332- sensingArea: mainView
333- enabled: !tip.hidden
334- onPressed: {
335- mouse.accepted = false
336- page.focus = false
337- }
338- z: 1
339- }
340-
341- Rectangle {
342- id: bgVisual
343-
344- color: "black"
345- anchors.fill: page
346- opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
347- z: 1
348- }
349-
350- UbuntuShape {
351- id: tip
352- objectName: "bottomEdgeTip"
353-
354- property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
355-
356- enabled: mouseArea.enabled
357- anchors {
358- bottom: parent.bottom
359- horizontalCenter: bottomEdge.horizontalCenter
360- bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
361- Behavior on bottomMargin {
362- SequentialAnimation {
363- // wait some msecs in case of the focus change again, to avoid flickering
364- PauseAnimation {
365- duration: 300
366- }
367- UbuntuNumberAnimation {
368- duration: UbuntuAnimation.SnapDuration
369- }
370- }
371- }
372- }
373-
374- z: 1
375- width: tipLabel.paintedWidth + units.gu(6)
376- height: bottomEdge.tipHeight + units.gu(1)
377- color: Theme.palette.normal.overlay
378- Label {
379- id: tipLabel
380-
381- anchors {
382- top: parent.top
383- left: parent.left
384- right: parent.right
385- }
386- height: bottomEdge.tipHeight
387- verticalAlignment: Text.AlignVCenter
388- horizontalAlignment: Text.AlignHCenter
389- opacity: tip.hidden ? 0.0 : 1.0
390- Behavior on opacity {
391- UbuntuNumberAnimation {
392- duration: UbuntuAnimation.SnapDuration
393- }
394- }
395- }
396- }
397-
398- Rectangle {
399- id: shadow
400-
401- anchors {
402- left: parent.left
403- right: parent.right
404- bottom: parent.bottom
405- }
406- height: units.gu(1)
407- z: 1
408- opacity: 0.0
409- gradient: Gradient {
410- GradientStop { position: 0.0; color: "transparent" }
411- GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
412- }
413- }
414-
415- MouseArea {
416- id: mouseArea
417-
418- property real previousY: -1
419- property string dragDirection: "None"
420-
421- preventStealing: true
422- drag {
423- axis: Drag.YAxis
424- target: bottomEdge
425- minimumY: bottomEdge.pageStartY
426- maximumY: page.height
427- }
428- enabled: edgeLoader.status == Loader.Ready
429-
430- anchors {
431- left: parent.left
432- right: parent.right
433- bottom: parent.bottom
434-
435- }
436- height: bottomEdge.tipHeight
437- z: 1
438-
439- onReleased: {
440- page.bottomEdgeReleased()
441- if ((dragDirection === "BottomToTop") &&
442- bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
443- bottomEdge.state = "expanded"
444- } else {
445- bottomEdge.state = "collapsed"
446- }
447- previousY = -1
448- dragDirection = "None"
449- }
450-
451- onPressed: {
452- previousY = mouse.y
453- tip.forceActiveFocus()
454- }
455-
456- onMouseYChanged: {
457- var yOffset = previousY - mouseY
458- // skip if was a small move
459- if (Math.abs(yOffset) <= units.gu(2)) {
460- return
461- }
462- previousY = mouseY
463- dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
464- }
465- }
466-
467- Rectangle {
468- id: bottomEdge
469- objectName: "bottomEdge"
470-
471- readonly property int tipHeight: units.gu(3)
472- readonly property int pageStartY: 0
473-
474- z: 1
475- color: Theme.palette.normal.background
476- clip: true
477- anchors {
478- left: parent.left
479- right: parent.right
480- }
481- height: page.height
482- y: height
483- visible: page.bottomEdgeEnabled && !page.isCollapsed
484- state: "collapsed"
485- states: [
486- State {
487- name: "collapsed"
488- PropertyChanges {
489- target: bottomEdge
490- y: bottomEdge.height
491- }
492- },
493- State {
494- name: "expanded"
495- PropertyChanges {
496- target: bottomEdge
497- y: bottomEdge.pageStartY
498- }
499- },
500- State {
501- name: "floating"
502- when: mouseArea.drag.active
503- PropertyChanges {
504- target: shadow
505- opacity: 1.0
506- }
507- }
508- ]
509-
510- transitions: [
511- Transition {
512- to: "expanded"
513- SequentialAnimation {
514- alwaysRunToEnd: true
515-
516- SmoothedAnimation {
517- target: bottomEdge
518- property: "y"
519- duration: UbuntuAnimation.FastDuration
520- easing.type: Easing.Linear
521- }
522- SmoothedAnimation {
523- target: edgeLoader
524- property: "anchors.topMargin"
525- to: - units.gu(4)
526- duration: UbuntuAnimation.FastDuration
527- easing.type: Easing.Linear
528- }
529- SmoothedAnimation {
530- target: edgeLoader
531- property: "anchors.topMargin"
532- to: 0
533- duration: UbuntuAnimation.FastDuration
534- easing: UbuntuAnimation.StandardEasing
535- }
536- ScriptAction {
537- script: page._pushPage()
538- }
539- }
540- },
541- Transition {
542- from: "expanded"
543- to: "collapsed"
544- SequentialAnimation {
545- alwaysRunToEnd: true
546-
547- ScriptAction {
548- script: {
549- Qt.inputMethod.hide()
550- edgeLoader.item.parent = edgeLoader
551- edgeLoader.item.anchors.fill = edgeLoader
552- edgeLoader.item.active = false
553- }
554- }
555- SmoothedAnimation {
556- target: bottomEdge
557- property: "y"
558- duration: UbuntuAnimation.SlowDuration
559- }
560- ScriptAction {
561- script: {
562- // destroy current bottom page
563- if (page.reloadBottomEdgePage) {
564- edgeLoader.active = false
565- // remove properties from old instance
566- if (edgeLoader.source !== "") {
567- var properties = {}
568- if (temporaryProperties !== null) {
569- properties = temporaryProperties
570- temporaryProperties = null
571- }
572-
573- edgeLoader.setSource(edgeLoader.source, properties)
574- }
575- // tip will receive focus on page active true
576- } else {
577- tip.forceActiveFocus()
578- }
579-
580- // notify
581- page.bottomEdgeDismissed()
582-
583- edgeLoader.active = true
584- }
585- }
586- }
587- },
588- Transition {
589- from: "floating"
590- to: "collapsed"
591- SmoothedAnimation {
592- target: bottomEdge
593- property: "y"
594- duration: UbuntuAnimation.FastDuration
595- }
596- }
597- ]
598-
599- Loader {
600- id: edgeLoader
601-
602- asynchronous: true
603- anchors.fill: parent
604- //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
605- Binding {
606- target: edgeLoader.status === Loader.Ready ? edgeLoader : null
607- property: "anchors.topMargin"
608- value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
609- when: !page.isReady
610- }
611-
612- onLoaded: {
613- tip.forceActiveFocus()
614- if (page.isReady && edgeLoader.item.active !== true) {
615- page._pushPage()
616- }
617- }
618- }
619- }
620-}
621
622=== modified file 'src/qml/MMS/Previewer.qml'
623--- src/qml/MMS/Previewer.qml 2015-11-17 14:39:21 +0000
624+++ src/qml/MMS/Previewer.qml 2016-02-01 23:18:57 +0000
625@@ -31,7 +31,7 @@
626
627 function handleAttachment(filePath, handlerType)
628 {
629- mainStack.push(picker, {"url": filePath, "handler": handlerType});
630+ mainStack.addPageToCurrentColumn(previewerPage, picker, {"url": filePath, "handler": handlerType});
631 actionTriggered()
632 }
633
634@@ -45,23 +45,29 @@
635 previewerPage.handleAttachment(attachment.filePath, ContentHandler.Share)
636 }
637
638- function backAction()
639- {
640- mainStack.pop()
641+ header: PageHeader {
642+ id: pageHeader
643+
644+ property alias leadingActions: leadingBar.actions
645+ property alias trailingActions: trailingBar.actions
646+
647+ title: previewerPage.title
648+ leadingActionBar {
649+ id: leadingBar
650+ }
651+
652+ trailingActionBar {
653+ id: trailingBar
654+ }
655 }
656
657- title: ""
658 state: "default"
659 states: [
660- PageHeadState {
661+ State {
662+ id: defaultState
663 name: "default"
664- head: previewerPage.head
665- backAction: Action {
666- iconName: "back"
667- text: i18n.tr("Back")
668- onTriggered: previewerPage.backAction()
669- }
670- actions: [
671+
672+ property list<QtObject> trailingActions: [
673 Action {
674 objectName: "saveButton"
675 text: i18n.tr("Save")
676@@ -75,6 +81,11 @@
677 onTriggered: previewerPage.shareAttchment()
678 }
679 ]
680+ PropertyChanges {
681+ target: pageHeader
682+
683+ trailingActions: defaultState.trailingActions
684+ }
685 }
686 ]
687
688@@ -109,11 +120,11 @@
689
690 onPeerSelected: {
691 picker.curTransfer = peer.request();
692- mainStack.pop();
693+ mainStack.removePage(picker);
694 if (picker.curTransfer.state === ContentTransfer.InProgress)
695 picker.__exportItems(picker.url);
696 }
697- onCancelPressed: mainStack.pop();
698+ onCancelPressed: mainStack.removePage(picker);
699 }
700
701 Connections {
702
703=== modified file 'src/qml/MMS/PreviewerImage.qml'
704--- src/qml/MMS/PreviewerImage.qml 2016-02-01 23:18:57 +0000
705+++ src/qml/MMS/PreviewerImage.qml 2016-02-01 23:18:57 +0000
706@@ -25,6 +25,7 @@
707 Previewer {
708 id: imagePreviewer
709
710+ // FIXME: this won't work correctly in windowed mode
711 Component.onCompleted: application.fullscreen = true
712 Component.onDestruction: application.fullscreen = false
713
714
715=== modified file 'src/qml/MMS/PreviewerMultipleContacts.qml'
716--- src/qml/MMS/PreviewerMultipleContacts.qml 2015-11-19 00:34:45 +0000
717+++ src/qml/MMS/PreviewerMultipleContacts.qml 2016-02-01 23:18:57 +0000
718@@ -42,7 +42,12 @@
719 MultipleSelectionListView {
720 id: contactList
721
722- anchors.fill: parent
723+ anchors {
724+ top: root.header.bottom
725+ bottom: parent.bottom
726+ left: parent.left
727+ right: parent.right
728+ }
729 listModel: thumbnail.vcard.contacts
730 listDelegate: ContactDelegate {
731 id: contactDelegate
732@@ -51,7 +56,7 @@
733 property var contact: thumbnail.vcard.contacts[index]
734
735 onClicked: {
736- mainStack.push(sigleContatPreviewer, {'contact': contact})
737+ mainStack.addComponentToCurrentColumnSync(root, sigleContatPreviewer, {'contact': contact})
738 }
739 }
740 }
741
742=== modified file 'src/qml/MMS/PreviewerVideo.qml'
743--- src/qml/MMS/PreviewerVideo.qml 2016-02-01 23:18:57 +0000
744+++ src/qml/MMS/PreviewerVideo.qml 2016-02-01 23:18:57 +0000
745@@ -30,6 +30,7 @@
746 title: i18n.tr("Video Preview")
747 clip: true
748
749+ // FIXME: this won't work correctly in windowed mode
750 Component.onCompleted: {
751 application.fullscreen = true
752 // Load Video player after toggling fullscreen to reduce flickering
753
754=== modified file 'src/qml/MMSDelegate.qml'
755--- src/qml/MMSDelegate.qml 2016-02-01 23:18:57 +0000
756+++ src/qml/MMSDelegate.qml 2016-02-01 23:18:57 +0000
757@@ -45,7 +45,7 @@
758 var properties = {}
759 properties["attachment"] = attachment.item.attachment
760 properties["thumbnail"] = attachment.item
761- mainStack.push(Qt.resolvedUrl(attachment.item.previewer), properties)
762+ mainStack.addFileToCurrentColumnSync(messages.basePage, Qt.resolvedUrl(attachment.item.previewer), properties)
763 }
764 }
765
766
767=== modified file 'src/qml/MainPage.qml'
768--- src/qml/MainPage.qml 2016-02-01 23:18:57 +0000
769+++ src/qml/MainPage.qml 2016-02-01 23:18:57 +0000
770@@ -23,29 +23,27 @@
771 import Ubuntu.History 0.1
772 import "dateUtils.js" as DateUtils
773
774-LocalPageWithBottomEdge {
775+Page {
776 id: mainPage
777 property alias selectionMode: threadList.isInSelectionMode
778 property bool searching: false
779+ property bool isEmpty: threadCount == 0 && !threadModel.canFetchMore
780 property alias threadCount: threadList.count
781+ property alias displayedThreadIndex: threadList.currentIndex
782+
783+ property var _messagesPage: null
784
785 function startSelection() {
786 threadList.startSelection()
787 }
788
789- state: selectionMode ? "select" : searching ? "search" : "default"
790- title: selectionMode ? " " : i18n.tr("Messages")
791- flickable: null
792-
793- bottomEdgeEnabled: !selectionMode && !searching
794- bottomEdgeTitle: i18n.tr("+")
795- bottomEdgePageComponent: Messages { active: false }
796-
797 TextField {
798 id: searchField
799 objectName: "searchField"
800 visible: mainPage.searching
801 anchors {
802+ top: parent.top
803+ topMargin: units.gu(1)
804 left: parent.left
805 right: parent.right
806 rightMargin: units.gu(2)
807@@ -60,11 +58,30 @@
808 }
809 }
810
811+ header: PageHeader {
812+ id: pageHeader
813+
814+ property alias leadingActions: leadingBar.actions
815+ property alias trailingActions: trailingBar.actions
816+
817+ title: i18n.tr("Messages")
818+ flickable: threadList
819+ leadingActionBar {
820+ id: leadingBar
821+ }
822+
823+ trailingActionBar {
824+ id: trailingBar
825+ }
826+ }
827+
828 states: [
829- PageHeadState {
830+ State {
831+ id: defaultState
832 name: "default"
833- head: mainPage.head
834- actions: [
835+ when: !searching && !selectionMode
836+
837+ property list<QtObject> trailingActions: [
838 Action {
839 objectName: "searchAction"
840 iconName: "search"
841@@ -77,38 +94,76 @@
842 objectName: "settingsAction"
843 text: i18n.tr("Settings")
844 iconName: "settings"
845- onTriggered: pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
846+ onTriggered: {
847+ emptyStack()
848+ pageStack.addFileToNextColumnSync(mainPage, Qt.resolvedUrl("SettingsPage.qml"))
849+ }
850+ },
851+ Action {
852+ objectName: "newMessageAction"
853+ text: i18n.tr("New message")
854+ iconName: "add"
855+ visible: dualPanel
856+ onTriggered: mainView.bottomEdge.commit()
857 }
858+
859 ]
860+
861+ PropertyChanges {
862+ target: pageHeader
863+ trailingActions: defaultState.trailingActions
864+ leadingActions: []
865+ }
866 },
867- PageHeadState {
868+ State {
869+ id: searchState
870 name: "search"
871- head: mainPage.head
872- backAction: Action {
873- objectName: "cancelSearch"
874- visible: mainPage.searching
875- iconName: "back"
876- text: i18n.tr("Cancel")
877- onTriggered: {
878- searchField.text = ""
879- mainPage.searching = false
880+ when: searching
881+
882+ property list<QtObject> leadingActions: [
883+ Action {
884+ objectName: "cancelSearch"
885+ visible: mainPage.searching
886+ iconName: "back"
887+ text: i18n.tr("Cancel")
888+ onTriggered: {
889+ searchField.text = ""
890+ mainPage.searching = false
891+ }
892 }
893+ ]
894+
895+ PropertyChanges {
896+ target: pageHeader
897+ contents: searchField
898+ leadingActions: searchState.leadingActions
899+ trailingActions: []
900 }
901- contents: searchField
902 },
903- PageHeadState {
904- name: "select"
905- head: mainPage.head
906- backAction: Action {
907- objectName: "selectionModeCancelAction"
908- iconName: "back"
909- onTriggered: threadList.cancelSelection()
910- }
911- actions: [
912+ State {
913+ id: selectionState
914+ name: "selection"
915+ when: selectionMode
916+
917+ property list<QtObject> leadingActions: [
918+ Action {
919+ objectName: "selectionModeCancelAction"
920+ iconName: "back"
921+ onTriggered: threadList.cancelSelection()
922+ }
923+ ]
924+
925+ property list<QtObject> trailingActions: [
926 Action {
927 objectName: "selectionModeSelectAllAction"
928 iconName: "select"
929- onTriggered: threadList.selectAll()
930+ onTriggered: {
931+ if (threadList.selectedItems.count === threadList.count) {
932+ threadList.clearSelection()
933+ } else {
934+ threadList.selectAll()
935+ }
936+ }
937 },
938 Action {
939 objectName: "selectionModeDeleteAction"
940@@ -117,39 +172,18 @@
941 onTriggered: threadList.endSelection()
942 }
943 ]
944+ PropertyChanges {
945+ target: pageHeader
946+ title: " "
947+ leadingActions: selectionState.leadingActions
948+ trailingActions: selectionState.trailingActions
949+ }
950 }
951 ]
952
953- Item {
954+ EmptyState {
955 id: emptyStateScreen
956- anchors.left: parent.left
957- anchors.leftMargin: units.gu(6)
958- anchors.right: parent.right
959- anchors.rightMargin: units.gu(6)
960- height: childrenRect.height
961- anchors.verticalCenter: parent.verticalCenter
962- visible: threadCount == 0 && !threadModel.canFetchMore
963- Icon {
964- id: emptyStateIcon
965- anchors.top: emptyStateScreen.top
966- anchors.horizontalCenter: parent.horizontalCenter
967- height: units.gu(5)
968- width: height
969- opacity: 0.3
970- name: "message"
971- }
972- Label {
973- id: emptyStateLabel
974- anchors.top: emptyStateIcon.bottom
975- anchors.topMargin: units.gu(2)
976- anchors.left: parent.left
977- anchors.right: parent.right
978- text: i18n.tr("Compose a new message by swiping up from the bottom of the screen.")
979- color: "#5d5d5d"
980- fontSize: "x-large"
981- wrapMode: Text.WordWrap
982- horizontalAlignment: Text.AlignHCenter
983- }
984+ visible: mainPage.isEmpty && !mainView.dualPanel
985 }
986
987 Component {
988@@ -188,8 +222,18 @@
989 clip: true
990 cacheBuffer: threadList.height * 2
991 section.property: "eventDate"
992+ currentIndex: -1
993 //spacing: searchField.text === "" ? units.gu(-2) : 0
994 section.delegate: searching && searchField.text !== "" ? null : sectionDelegate
995+ header: ListItem.Standard {
996+ id: newItem
997+ height: mainView.bottomEdge.status === BottomEdge.Committed ? units.gu(10) : 0
998+ text: i18n.tr("New message")
999+ iconName: "message-new"
1000+ iconFrame: false
1001+ selected: true
1002+ }
1003+
1004 listDelegate: ThreadDelegate {
1005 id: threadDelegate
1006 // FIXME: find a better unique name
1007@@ -201,7 +245,15 @@
1008 }
1009 height: units.gu(8)
1010 selectionMode: threadList.isInSelectionMode
1011- selected: threadList.isSelected(threadDelegate)
1012+ selected: {
1013+ if (selectionMode) {
1014+ return threadList.isSelected(threadDelegate)
1015+ } else {
1016+ // FIXME: there might be a better way of doing this
1017+ return index === threadList.currentIndex && mainView.bottomEdge.status !== BottomEdge.Committed
1018+ }
1019+ }
1020+
1021 searchTerm: mainPage.searching ? searchField.text : ""
1022 onItemClicked: {
1023 if (threadList.isInSelectionMode) {
1024@@ -222,7 +274,11 @@
1025 if (displayedEvent != null) {
1026 properties["scrollToEventId"] = displayedEvent.eventId
1027 }
1028- mainStack.push(Qt.resolvedUrl("Messages.qml"), properties)
1029+ emptyStack()
1030+ mainStack.addComponentToNextColumnSync(mainPage, messagesWithBottomEdge, properties)
1031+
1032+ // mark this item as current
1033+ threadList.currentIndex = index
1034 }
1035 }
1036 onItemPressAndHold: {
1037@@ -242,6 +298,13 @@
1038 mainView.removeThreads(threadsToRemove);
1039 }
1040 }
1041+
1042+ Binding {
1043+ target: threadList
1044+ property: 'contentY'
1045+ value: -threadList.headerItem.height
1046+ when: mainView.composingNewMessage
1047+ }
1048 }
1049
1050 KeyboardRectangle {
1051@@ -252,4 +315,12 @@
1052 flickableItem: threadList
1053 align: Qt.AlignTrailing
1054 }
1055+
1056+ Loader {
1057+ id: bottomEdgeLoader
1058+ active: !selectionMode && !searching && !mainView.dualPanel
1059+ sourceComponent: MessagingBottomEdge {
1060+ parent: mainPage
1061+ }
1062+ }
1063 }
1064
1065=== modified file 'src/qml/Messages.qml'
1066--- src/qml/Messages.qml 2016-02-01 23:18:57 +0000
1067+++ src/qml/Messages.qml 2016-02-01 23:18:57 +0000
1068@@ -63,6 +63,19 @@
1069 property QtObject presenceRequest: presenceItem
1070 property var accountsModel: getAccountsModel()
1071 property alias oskEnabled: keyboard.oskEnabled
1072+ property bool isReady: false
1073+ property string firstRecipientAlias: ((contactWatcher.isUnknown &&
1074+ contactWatcher.isInteractive) ||
1075+ contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias
1076+
1077+
1078+ // When using this view from the bottom edge, we are not in the stack, so we need to push on top of the parent page
1079+ property var basePage: messages
1080+
1081+ property bool startedFromBottomEdge: false
1082+
1083+ signal ready
1084+ signal cancel
1085
1086 function getAccountsModel() {
1087 var accounts = []
1088@@ -221,99 +234,6 @@
1089 return thread
1090 }
1091
1092- Connections {
1093- target: telepathyHelper
1094- onSetupReady: {
1095- // force reevaluation
1096- messages.account = Qt.binding(getCurrentAccount)
1097- messages.phoneAccount = Qt.binding(isPhoneAccount)
1098- head.sections.model = Qt.binding(getSectionsModel)
1099- head.sections.selectedIndex = Qt.binding(getSelectedIndex)
1100- }
1101- }
1102-
1103-
1104- Connections {
1105- target: chatManager
1106- onChatEntryCreated: {
1107- // TODO: track using chatId and not participants
1108- if (accountId == account.accountId &&
1109- firstParticipant && participants[0] == firstParticipant.identifier) {
1110- messages.chatEntry = chatEntry
1111- }
1112- }
1113- onChatsChanged: {
1114- for (var i in chatManager.chats) {
1115- var chat = chatManager.chats[i]
1116- // TODO: track using chatId and not participants
1117- if (chat.account.accountId == account.accountId &&
1118- firstParticipant && chat.participants[0] == firstParticipant.identifier) {
1119- messages.chatEntry = chat
1120- return
1121- }
1122- }
1123- messages.chatEntry = null
1124- }
1125- }
1126-
1127- Timer {
1128- id: typingTimer
1129- interval: 6000
1130- onTriggered: {
1131- messages.userTyping = false;
1132- }
1133- }
1134-
1135- Repeater {
1136- model: messages.chatEntry ? messages.chatEntry.chatStates : null
1137- Item {
1138- function processChatState() {
1139- if (modelData.state == ChatEntry.ChannelChatStateComposing) {
1140- messages.userTyping = true
1141- typingTimer.start()
1142- } else {
1143- messages.userTyping = false
1144- }
1145- }
1146- Component.onCompleted: processChatState()
1147- Connections {
1148- target: modelData
1149- onStateChanged: processChatState()
1150- }
1151- }
1152- }
1153-
1154- MessagesHeader {
1155- id: header
1156- width: parent ? parent.width - units.gu(2) : undefined
1157- height: units.gu(5)
1158- title: messages.title
1159- subtitle: {
1160- if (userTyping) {
1161- return i18n.tr("Typing..")
1162- }
1163- switch (presenceRequest.type) {
1164- case PresenceRequest.PresenceTypeAvailable:
1165- return i18n.tr("Online")
1166- case PresenceRequest.PresenceTypeOffline:
1167- return i18n.tr("Offline")
1168- case PresenceRequest.PresenceTypeAway:
1169- return i18n.tr("Away")
1170- case PresenceRequest.PresenceTypeBusy:
1171- return i18n.tr("Busy")
1172- default:
1173- return ""
1174- }
1175- }
1176- visible: true
1177- }
1178-
1179- head {
1180- id: head
1181- sections.model: getSectionsModel()
1182- sections.selectedIndex: getSelectedIndex()
1183- }
1184-
1185 function sendMessageNetworkCheck() {
1186 if (messages.account.simLocked) {
1187 Qt.inputMethod.hide()
1188@@ -363,7 +283,7 @@
1189 if (event.senderId == "self" && event.accountId != messages.account.accountId) {
1190 var tmpAccount = telepathyHelper.accountForId(event.accountId)
1191 if (!tmpAccount || (tmpAccount.type == AccountEntry.MultimediaAccount && messages.account.type == AccountEntry.PhoneAccount)) {
1192- // we don't add the information event if the last outgoing message
1193+ // we don't add the information event if the last outgoing message
1194 // was a fallback to a multimedia service
1195 break;
1196 }
1197@@ -425,7 +345,7 @@
1198 var isSelfContactKnown = account.selfContactId != ""
1199 if (isMmsGroupChat && !isSelfContactKnown) {
1200 // TODO: inform the user to enter the phone number of the selected sim card manually
1201- // and use it in the telepathy-ofono account as selfContactId.
1202+ // and use it in the telepathy-ofono account as selfContactId.
1203 return false
1204 }
1205 var fallbackAccountId = chatManager.sendMessage(messages.account.accountId, participantIds, text, attachments, properties)
1206@@ -448,102 +368,6 @@
1207 return true
1208 }
1209
1210- PresenceRequest {
1211- id: presenceItem
1212- accountId: {
1213- // if this is a regular sms chat, try requesting the presence on
1214- // a multimedia account
1215- if (!account) {
1216- return ""
1217- }
1218- if (account.type == AccountEntry.PhoneAccount) {
1219- for (var i in telepathyHelper.accounts) {
1220- var tmpAccount = telepathyHelper.accounts[i]
1221- if (tmpAccount.type == AccountEntry.MultimediaAccount) {
1222- return tmpAccount.accountId
1223- }
1224- }
1225- return ""
1226- }
1227- return account.accountId
1228- }
1229- // we just request presence on 1-1 chats
1230- identifier: participants.length == 1 ? participants[0].identifier : ""
1231- }
1232-
1233- // this is necessary to automatically update the view when the
1234- // default account changes in system settings
1235- Connections {
1236- target: mainView
1237- onAccountChanged: {
1238- if (!messages.phoneAccount) {
1239- return
1240- }
1241- messages.account = mainView.account
1242- }
1243- }
1244-
1245- ActivityIndicator {
1246- id: activityIndicator
1247- anchors {
1248- verticalCenter: parent.verticalCenter
1249- horizontalCenter: parent.horizontalCenter
1250- }
1251- running: isSearching
1252- visible: running
1253- }
1254-
1255- flickable: null
1256-
1257- property bool isReady: false
1258- signal ready
1259- onReady: {
1260- isReady = true
1261- if (participants.length === 0 && keyboardFocus)
1262- multiRecipient.forceFocus()
1263- }
1264-
1265- property string firstRecipientAlias: ((contactWatcher.isUnknown &&
1266- contactWatcher.isInteractive) ||
1267- contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias
1268- title: {
1269- if (selectionMode || participants.length == 0) {
1270- return " "
1271- }
1272-
1273- if (landscape) {
1274- return ""
1275- }
1276- if (participants.length > 0) {
1277- if (participants.length == 1) {
1278- return firstRecipientAlias
1279- } else {
1280- // TRANSLATORS: %1 refers to the number of participants in a group chat
1281- return i18n.tr("Group (%1)").arg(participants.length)
1282- }
1283- }
1284- return i18n.tr("New Message")
1285- }
1286-
1287- Component.onCompleted: {
1288- if (messages.accountId !== "") {
1289- var account = telepathyHelper.accountForId(messages.accountId)
1290- if (account && account.type == AccountEntry.MultimediaAccount) {
1291- // fallback the first available phone account
1292- if (telepathyHelper.phoneAccounts.length > 0) {
1293- messages.accountId = telepathyHelper.phoneAccounts[0].accountId
1294- }
1295- }
1296- }
1297- composeBar.addAttachments(sharedAttachmentsTransfer)
1298- }
1299-
1300- onActiveChanged: {
1301- if (active && (eventModel.count > 0)){
1302- swipeItemDemo.enable()
1303- }
1304- }
1305-
1306 function updateFilters(accounts, participants, reload, threads) {
1307 if (participants.length == 0 || accounts.length == 0) {
1308 return null
1309@@ -603,8 +427,356 @@
1310 return eventModel.markEventAsRead(accountId, threadId, eventId, type);
1311 }
1312
1313+ header: PageHeader {
1314+ id: pageHeader
1315+
1316+ property alias leadingActions: leadingBar.actions
1317+ property alias trailingActions: trailingBar.actions
1318+
1319+ property list<QtObject> bottomEdgeLeadingActions: [
1320+ Action {
1321+ id: backAction
1322+
1323+ objectName: "cancel"
1324+ name: "cancel"
1325+ text: i18n.tr("Cancel")
1326+ iconName: "down"
1327+ shortcut: "Esc"
1328+ onTriggered: {
1329+ messages.cancel()
1330+ }
1331+ }
1332+ ]
1333+
1334+ property list<QtObject> singlePanelLeadingActions: [
1335+ Action {
1336+ id: singlePanelBackAction
1337+ objectName: "back"
1338+ name: "cancel"
1339+ text: i18n.tr("Cancel")
1340+ iconName: "back"
1341+ shortcut: "Esc"
1342+ onTriggered: {
1343+ // emptyStack will make sure the page gets removed.
1344+ mainView.emptyStack()
1345+ }
1346+ }
1347+ ]
1348+
1349+ title: {
1350+ if (landscape) {
1351+ return ""
1352+ }
1353+
1354+ if (participants.length == 1) {
1355+ return firstRecipientAlias
1356+ }
1357+
1358+ return i18n.tr("New Message")
1359+ }
1360+ flickable: null
1361+
1362+ Sections {
1363+ id: sections
1364+ anchors {
1365+ left: parent.left
1366+ leftMargin: units.gu(2)
1367+ bottom: parent.bottom
1368+ }
1369+ model: getSectionsModel()
1370+ selectedIndex: getSelectedIndex()
1371+ onSelectedIndexChanged: {
1372+ if (selectedIndex >= 0) {
1373+ messages.account = messages.accountsModel[selectedIndex]
1374+ }
1375+ }
1376+ }
1377+
1378+ extension: sections.model.length > 1 ? sections : null
1379+
1380+ leadingActionBar {
1381+ id: leadingBar
1382+
1383+ states: [
1384+ State {
1385+ name: "bottomEdgeBack"
1386+ when: startedFromBottomEdge
1387+ PropertyChanges {
1388+ target: leadingBar
1389+ actions: pageHeader.bottomEdgeLeadingActions
1390+ }
1391+ },
1392+ State {
1393+ name: "singlePanelBack"
1394+ when: !mainView.dualPanel && !startedFromBottomEdge
1395+ PropertyChanges {
1396+ target: leadingBar
1397+ actions: pageHeader.singlePanelLeadingActions
1398+ }
1399+ }
1400+
1401+ ]
1402+ }
1403+
1404+ trailingActionBar {
1405+ id: trailingBar
1406+ }
1407+ }
1408+
1409+ states: [
1410+ State {
1411+ id: selectionState
1412+ name: "selection"
1413+ when: selectionMode
1414+
1415+ property list<QtObject> leadingActions: [
1416+ Action {
1417+ objectName: "selectionModeCancelAction"
1418+ iconName: "back"
1419+ onTriggered: messageList.cancelSelection()
1420+ }
1421+ ]
1422+
1423+ property list<QtObject> trailingActions: [
1424+ Action {
1425+ objectName: "selectionModeSelectAllAction"
1426+ iconName: "select"
1427+ onTriggered: {
1428+ if (messageList.selectedItems.count === messageList.count) {
1429+ messageList.clearSelection()
1430+ } else {
1431+ messageList.selectAll()
1432+ }
1433+ }
1434+ },
1435+ Action {
1436+ objectName: "selectionModeDeleteAction"
1437+ enabled: messageList.selectedItems.count > 0
1438+ iconName: "delete"
1439+ onTriggered: messageList.endSelection()
1440+ }
1441+ ]
1442+
1443+ PropertyChanges {
1444+ target: pageHeader
1445+ title: " "
1446+ leadingActions: selectionState.leadingActions
1447+ trailingActions: selectionState.trailingActions
1448+ }
1449+ },
1450+ State {
1451+ id: groupChatState
1452+ name: "groupChat"
1453+ when: groupChat
1454+
1455+ property list<QtObject> trailingActions: [
1456+ Action {
1457+ objectName: "groupChatAction"
1458+ iconName: "contact-group"
1459+ onTriggered: PopupUtils.open(participantsPopover, pageHeader)
1460+ }
1461+ ]
1462+
1463+ PropertyChanges {
1464+ target: pageHeader
1465+ // TRANSLATORS: %1 refers to the number of participants in a group chat
1466+ title: i18n.tr("Group (%1)").arg(participants.length)
1467+ contents: headerContents
1468+ trailingActions: groupChatState.trailingActions
1469+ }
1470+ },
1471+ State {
1472+ id: unknownContactState
1473+ name: "unknownContact"
1474+ when: participants.length == 1 && contactWatcher.isUnknown
1475+
1476+ property list<QtObject> trailingActions: [
1477+ Action {
1478+ objectName: "contactCallAction"
1479+ visible: participants.length == 1 && contactWatcher.interactive
1480+ iconName: "call-start"
1481+ text: i18n.tr("Call")
1482+ onTriggered: {
1483+ Qt.inputMethod.hide()
1484+ // FIXME: support other things than just phone numbers
1485+ Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
1486+ }
1487+ },
1488+ Action {
1489+ objectName: "addContactAction"
1490+ visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive
1491+ iconName: "contact-new"
1492+ text: i18n.tr("Add")
1493+ onTriggered: {
1494+ Qt.inputMethod.hide()
1495+ // FIXME: support other things than just phone numbers
1496+ mainView.addPhoneToContact(messages, "", contactWatcher.identifier, null, null)
1497+ }
1498+ }
1499+ ]
1500+ PropertyChanges {
1501+ target: pageHeader
1502+ contents: headerContents
1503+ trailingActions: unknownContactState.trailingActions
1504+ }
1505+ },
1506+ State {
1507+ id: newMessageState
1508+ name: "newMessage"
1509+ when: participants.length === 0
1510+
1511+ property list<QtObject> trailingActions: [
1512+ Action {
1513+ objectName: "contactList"
1514+ iconName: "contact"
1515+ onTriggered: {
1516+ Qt.inputMethod.hide()
1517+ mainStack.addFileToCurrentColumnSync(messages.basePage, Qt.resolvedUrl("NewRecipientPage.qml"), {"multiRecipient": multiRecipient})
1518+ }
1519+ }
1520+
1521+ ]
1522+
1523+ property Item contents: MultiRecipientInput {
1524+ id: multiRecipient
1525+ objectName: "multiRecipient"
1526+ enabled: visible
1527+ anchors {
1528+ left: parent ? parent.left : undefined
1529+ right: parent ? parent.right : undefined
1530+ rightMargin: units.gu(2)
1531+ top: parent ? parent.top: undefined
1532+ topMargin: units.gu(1)
1533+ }
1534+ focus: true
1535+
1536+ Connections {
1537+ target: mainView.bottomEdge
1538+ onStatusChanged: {
1539+ if (mainView.bottomEdge.status === BottomEdge.Committed) {
1540+ multiRecipient.forceFocus()
1541+ }
1542+ }
1543+ }
1544+ }
1545+
1546+ PropertyChanges {
1547+ target: pageHeader
1548+ title: " "
1549+ trailingActions: newMessageState.trailingActions
1550+ contents: newMessageState.contents
1551+ }
1552+ },
1553+ State {
1554+ id: knownContactState
1555+ name: "knownContact"
1556+ when: participants.length == 1 && !contactWatcher.isUnknown
1557+ property list<QtObject> trailingActions: [
1558+ Action {
1559+ objectName: "contactCallKnownAction"
1560+ visible: participants.length == 1 && messages.phoneAccount
1561+ iconName: "call-start"
1562+ text: i18n.tr("Call")
1563+ onTriggered: {
1564+ Qt.inputMethod.hide()
1565+ // FIXME: support other things than just phone numbers
1566+ Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
1567+ }
1568+ },
1569+ Action {
1570+ objectName: "contactProfileAction"
1571+ visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount
1572+ iconSource: "image://theme/contact"
1573+ text: i18n.tr("Contact")
1574+ onTriggered: {
1575+ mainView.showContactDetails(messages.basePage, contactWatcher.contactId, null, null)
1576+ }
1577+ }
1578+ ]
1579+ PropertyChanges {
1580+ target: pageHeader
1581+ contents: headerContents
1582+ trailingActions: knownContactState.trailingActions
1583+ }
1584+ }
1585+ ]
1586+
1587+ Component.onCompleted: {
1588+ if (messages.accountId !== "") {
1589+ var account = telepathyHelper.accountForId(messages.accountId)
1590+ if (account && account.type == AccountEntry.MultimediaAccount) {
1591+ // fallback the first available phone account
1592+ if (telepathyHelper.phoneAccounts.length > 0) {
1593+ messages.accountId = telepathyHelper.phoneAccounts[0].accountId
1594+ }
1595+ }
1596+ }
1597+ composeBar.addAttachments(sharedAttachmentsTransfer)
1598+ }
1599+
1600+ Component.onDestruction: {
1601+ if (!mainView.dualPanel && !startedFromBottomEdge) {
1602+ mainPage.displayedThreadIndex = -1
1603+ }
1604+ }
1605+
1606+ onReady: {
1607+ isReady = true
1608+ if (participants.length === 0 && keyboardFocus)
1609+ multiRecipient.forceFocus()
1610+ }
1611+
1612+ onActiveChanged: {
1613+ if (active && (eventModel.count > 0)){
1614+ swipeItemDemo.enable()
1615+ }
1616+ }
1617+
1618+ Connections {
1619+ target: telepathyHelper
1620+ onSetupReady: {
1621+ // force reevaluation
1622+ messages.account = Qt.binding(getCurrentAccount)
1623+ messages.phoneAccount = Qt.binding(isPhoneAccount)
1624+ head.sections.model = Qt.binding(getSectionsModel)
1625+ head.sections.selectedIndex = Qt.binding(getSelectedIndex)
1626+ }
1627+ }
1628+
1629+ Connections {
1630+ target: chatManager
1631+ onChatEntryCreated: {
1632+ // TODO: track using chatId and not participants
1633+ if (accountId == account.accountId &&
1634+ firstParticipant && participants[0] == firstParticipant.identifier) {
1635+ messages.chatEntry = chatEntry
1636+ }
1637+ }
1638+ onChatsChanged: {
1639+ for (var i in chatManager.chats) {
1640+ var chat = chatManager.chats[i]
1641+ // TODO: track using chatId and not participants
1642+ if (chat.account.accountId == account.accountId &&
1643+ firstParticipant && chat.participants[0] == firstParticipant.identifier) {
1644+ messages.chatEntry = chat
1645+ return
1646+ }
1647+ }
1648+ messages.chatEntry = null
1649+ }
1650+ }
1651+
1652+ // this is necessary to automatically update the view when the
1653+ // default account changes in system settings
1654 Connections {
1655 target: mainView
1656+ onAccountChanged: {
1657+ if (!messages.phoneAccount) {
1658+ return
1659+ }
1660+ messages.account = mainView.account
1661+ }
1662+
1663 onApplicationActiveChanged: {
1664 if (mainView.applicationActive) {
1665 for (var i in pendingEventsToMarkAsRead) {
1666@@ -616,6 +788,91 @@
1667 }
1668 }
1669
1670+ Timer {
1671+ id: typingTimer
1672+ interval: 6000
1673+ onTriggered: {
1674+ messages.userTyping = false;
1675+ }
1676+ }
1677+
1678+ Repeater {
1679+ model: messages.chatEntry ? messages.chatEntry.chatStates : null
1680+ Item {
1681+ function processChatState() {
1682+ if (modelData.state == ChatEntry.ChannelChatStateComposing) {
1683+ messages.userTyping = true
1684+ typingTimer.start()
1685+ } else {
1686+ messages.userTyping = false
1687+ }
1688+ }
1689+ Component.onCompleted: processChatState()
1690+ Connections {
1691+ target: modelData
1692+ onStateChanged: processChatState()
1693+ }
1694+ }
1695+ }
1696+
1697+ MessagesHeader {
1698+ id: headerContents
1699+ width: parent ? parent.width - units.gu(2) : undefined
1700+ height: units.gu(5)
1701+ title: pageHeader.title
1702+ subtitle: {
1703+ if (userTyping) {
1704+ return i18n.tr("Typing..")
1705+ }
1706+ switch (presenceRequest.type) {
1707+ case PresenceRequest.PresenceTypeAvailable:
1708+ return i18n.tr("Online")
1709+ case PresenceRequest.PresenceTypeOffline:
1710+ return i18n.tr("Offline")
1711+ case PresenceRequest.PresenceTypeAway:
1712+ return i18n.tr("Away")
1713+ case PresenceRequest.PresenceTypeBusy:
1714+ return i18n.tr("Busy")
1715+ default:
1716+ return ""
1717+ }
1718+ }
1719+ visible: true
1720+ }
1721+
1722+ PresenceRequest {
1723+ id: presenceItem
1724+ accountId: {
1725+ // if this is a regular sms chat, try requesting the presence on
1726+ // a multimedia account
1727+ if (!account) {
1728+ return ""
1729+ }
1730+ if (account.type == AccountEntry.PhoneAccount) {
1731+ for (var i in telepathyHelper.accounts) {
1732+ var tmpAccount = telepathyHelper.accounts[i]
1733+ if (tmpAccount.type == AccountEntry.MultimediaAccount) {
1734+ return tmpAccount.accountId
1735+ }
1736+ }
1737+ return ""
1738+ }
1739+ return account.accountId
1740+ }
1741+ // we just request presence on 1-1 chats
1742+ identifier: participants.length == 1 ? participants[0].identifier : ""
1743+ }
1744+
1745+ ActivityIndicator {
1746+ id: activityIndicator
1747+ anchors {
1748+ verticalCenter: parent.verticalCenter
1749+ horizontalCenter: parent.horizontalCenter
1750+ }
1751+ running: isSearching
1752+ visible: running
1753+ }
1754+
1755 Component {
1756 id: participantsPopover
1757
1758@@ -678,13 +935,6 @@
1759 }
1760 }
1761
1762- Connections {
1763- target: messages.head.sections
1764- onSelectedIndexChanged: {
1765- messages.account = messages.accountsModel[head.sections.selectedIndex]
1766- }
1767- }
1768-
1769 Loader {
1770 id: searchListLoader
1771
1772@@ -696,7 +946,7 @@
1773 visible: source != ""
1774 anchors {
1775 top: parent.top
1776- topMargin: units.gu(2)
1777+ topMargin: header.height + units.gu(2)
1778 left: parent.left
1779 right: parent.right
1780 bottom: composeBar.top
1781@@ -756,156 +1006,6 @@
1782 addressableFields: messages.account ? messages.account.addressableVCardFields : ["tel"] // just to have a fallback there
1783 }
1784
1785- Action {
1786- id: backButton
1787- objectName: "backButton"
1788- iconName: "back"
1789- onTriggered: {
1790- if (typeof mainPage !== 'undefined') {
1791- mainPage.temporaryProperties = null
1792- }
1793- mainStack.pop()
1794- }
1795- }
1796-
1797- states: [
1798- PageHeadState {
1799- name: "selection"
1800- head: messages.head
1801- when: selectionMode
1802-
1803- backAction: Action {
1804- objectName: "selectionModeCancelAction"
1805- iconName: "back"
1806- onTriggered: messageList.cancelSelection()
1807- }
1808-
1809- actions: [
1810- Action {
1811- objectName: "selectionModeSelectAllAction"
1812- iconName: "select"
1813- onTriggered: {
1814- if (messageList.selectedItems.count === messageList.count) {
1815- messageList.clearSelection()
1816- } else {
1817- messageList.selectAll()
1818- }
1819- }
1820- },
1821- Action {
1822- objectName: "selectionModeDeleteAction"
1823- enabled: messageList.selectedItems.count > 0
1824- iconName: "delete"
1825- onTriggered: messageList.endSelection()
1826- }
1827- ]
1828- },
1829- PageHeadState {
1830- name: "groupChat"
1831- head: messages.head
1832- when: groupChat
1833- contents: header
1834- backAction: backButton
1835-
1836- actions: [
1837- Action {
1838- objectName: "groupChatAction"
1839- iconName: "contact-group"
1840- onTriggered: PopupUtils.open(participantsPopover, screenTop)
1841- }
1842- ]
1843- },
1844- PageHeadState {
1845- name: "unknownContact"
1846- head: messages.head
1847- when: participants.length == 1 && contactWatcher.isUnknown
1848- backAction: backButton
1849- contents: header
1850-
1851- actions: [
1852- Action {
1853- objectName: "contactCallAction"
1854- visible: participants.length == 1 && contactWatcher.interactive
1855- iconName: "call-start"
1856- text: i18n.tr("Call")
1857- onTriggered: {
1858- Qt.inputMethod.hide()
1859- // FIXME: support other things than just phone numbers
1860- Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
1861- }
1862- },
1863- Action {
1864- objectName: "addContactAction"
1865- visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive
1866- iconName: "contact-new"
1867- text: i18n.tr("Add")
1868- onTriggered: {
1869- Qt.inputMethod.hide()
1870- // FIXME: support other things than just phone numbers
1871- mainView.addPhoneToContact("", contactWatcher.identifier, null, null)
1872- }
1873- }
1874- ]
1875- },
1876- PageHeadState {
1877- name: "newMessage"
1878- head: messages.head
1879- when: participants.length === 0 && isReady
1880- backAction: backButton
1881- actions: [
1882- Action {
1883- objectName: "contactList"
1884- iconName: "contact"
1885- onTriggered: {
1886- Qt.inputMethod.hide()
1887- mainStack.push(Qt.resolvedUrl("NewRecipientPage.qml"), {"multiRecipient": multiRecipient, "parentPage": messages})
1888- }
1889- }
1890-
1891- ]
1892-
1893- contents: MultiRecipientInput {
1894- id: multiRecipient
1895- objectName: "multiRecipient"
1896- enabled: visible
1897- anchors {
1898- left: parent ? parent.left : undefined
1899- right: parent ? parent.right : undefined
1900- rightMargin: units.gu(2)
1901- }
1902- }
1903- },
1904- PageHeadState {
1905- name: "knownContact"
1906- head: messages.head
1907- when: participants.length == 1 && !contactWatcher.isUnknown
1908- backAction: backButton
1909- contents: header
1910- actions: [
1911- Action {
1912- objectName: "contactCallKnownAction"
1913- visible: participants.length == 1 && messages.phoneAccount
1914- iconName: "call-start"
1915- text: i18n.tr("Call")
1916- onTriggered: {
1917- Qt.inputMethod.hide()
1918- // FIXME: support other things than just phone numbers
1919- Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
1920- }
1921- },
1922- Action {
1923- objectName: "contactProfileAction"
1924- visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount
1925- iconSource: "image://theme/contact"
1926- text: i18n.tr("Contact")
1927- onTriggered: {
1928- mainView.showContactDetails(contactWatcher.contactId, null, null)
1929- }
1930- }
1931- ]
1932- }
1933- ]
1934-
1935 HistoryEventModel {
1936 id: eventModel
1937 type: HistoryThreadModel.EventTypeText
1938@@ -958,7 +1058,7 @@
1939 Item {
1940 id: screenTop
1941 anchors {
1942- top: parent.top
1943+ top: pageHeader.bottom
1944 left: parent.left
1945 right: parent.right
1946 }
1947@@ -1115,4 +1215,6 @@
1948 }
1949 }
1950 }
1951+
1952+ // FIXME: we need a bottom edge here somehow
1953 }
1954
1955=== modified file 'src/qml/MessagesHeader.qml'
1956--- src/qml/MessagesHeader.qml 2016-02-01 23:18:57 +0000
1957+++ src/qml/MessagesHeader.qml 2016-02-01 23:18:57 +0000
1958@@ -26,7 +26,12 @@
1959 property string title: ""
1960 property string subtitle: ""
1961
1962- height: units.gu(7)
1963+ height: units.gu(8)
1964+
1965+ anchors {
1966+ top: parent.top
1967+ topMargin: units.gu(1)
1968+ }
1969
1970 Behavior on height {
1971 UbuntuNumberAnimation {}
1972@@ -50,7 +55,7 @@
1973 }
1974 verticalAlignment: Text.AlignVCenter
1975
1976- font.pixelSize: FontUtils.sizeToPixels("x-large")
1977+ font.pixelSize: FontUtils.sizeToPixels("large")
1978 elide: Text.ElideRight
1979 text: title
1980 }
1981
1982=== added file 'src/qml/MessagingBottomEdge.qml'
1983--- src/qml/MessagingBottomEdge.qml 1970-01-01 00:00:00 +0000
1984+++ src/qml/MessagingBottomEdge.qml 2016-02-01 23:18:57 +0000
1985@@ -0,0 +1,43 @@
1986+import QtQuick 2.0
1987+import Ubuntu.Components 1.3
1988+
1989+BottomEdge {
1990+ id: bottomEdge
1991+
1992+ function commitWithProperties(properties) {
1993+ _realPage = messagesComponent.createObject(null, properties)
1994+ commit()
1995+ }
1996+
1997+ property var _realPage: null
1998+
1999+ height: parent ? parent.height : 0
2000+ hint.text: i18n.tr("+")
2001+ contentComponent: Item {
2002+ id: pageContent
2003+ implicitWidth: bottomEdge.width
2004+ implicitHeight: bottomEdge.height
2005+ children: bottomEdge._realPage
2006+ }
2007+
2008+ Component.onCompleted: {
2009+ mainView.bottomEdge = bottomEdge
2010+ _realPage = messagesComponent.createObject(null)
2011+ }
2012+
2013+ onCollapseCompleted: {
2014+ _realPage = messagesComponent.createObject(null)
2015+ }
2016+
2017+ Component {
2018+ id: messagesComponent
2019+
2020+ Messages {
2021+ anchors.fill: parent
2022+ onCancel: bottomEdge.collapse()
2023+ basePage: bottomEdge.parent
2024+ startedFromBottomEdge: true
2025+ }
2026+ }
2027+
2028+}
2029
2030=== modified file 'src/qml/MessagingContactEditorPage.qml'
2031--- src/qml/MessagingContactEditorPage.qml 2016-01-06 18:29:47 +0000
2032+++ src/qml/MessagingContactEditorPage.qml 2016-02-01 23:18:57 +0000
2033@@ -52,8 +52,12 @@
2034
2035 onContactSaved: {
2036 if (root.contactListPage) {
2037- root.contactListPage.moveListToContact(contact)
2038- root.contactListPage.phoneToAdd = ""
2039+ if (root.contactListPage.phoneToAdd !== "") {
2040+ mainStack.removePage(root.contactListPage)
2041+ } else {
2042+ root.contactListPage.moveListToContact(contact)
2043+ root.contactListPage.phoneToAdd = ""
2044+ }
2045 }
2046 }
2047 }
2048
2049=== modified file 'src/qml/MessagingContactViewPage.qml'
2050--- src/qml/MessagingContactViewPage.qml 2016-01-06 18:29:47 +0000
2051+++ src/qml/MessagingContactViewPage.qml 2016-02-01 23:18:57 +0000
2052@@ -42,12 +42,13 @@
2053 var newDetail = Qt.createQmlObject(detailSourceTemplate, contact)
2054 if (newDetail) {
2055 contact.addDetail(newDetail)
2056- pageStack.push(root.contactEditorPageURL,
2057- { model: root.model,
2058- contact: contact,
2059- initialFocusSection: "phones",
2060- newDetails: [newDetail],
2061- contactListPage: root.contactListPage })
2062+ mainStack.addPageToCurrentColumn(root,
2063+ root.contactEditorPageURL,
2064+ { model: root.model,
2065+ contact: contact,
2066+ initialFocusSection: "phones",
2067+ newDetails: [newDetail],
2068+ contactListPage: root.contactListPage })
2069 root.addPhoneToContact = ""
2070 } else {
2071 console.warn("Fail to create phone number detail")
2072@@ -61,7 +62,7 @@
2073 iconName: "share"
2074 visible: root.editable
2075 onTriggered: {
2076- pageStack.push(contactShareComponent,
2077+ pageStack.addComponentToCurrentColumnSync(root, contactShareComponent,
2078 { contactModel: root.model,
2079 contacts: [root.contact] })
2080 }
2081@@ -72,7 +73,7 @@
2082 iconName: "edit"
2083 visible: root.editable
2084 onTriggered: {
2085- pageStack.push(contactEditorPageURL,
2086+ pageStack.addFileToCurrentColumnSync(root, contactEditorPageURL,
2087 { model: root.model,
2088 contact: root.contact,
2089 contactListPage: root.contactListPage })
2090@@ -122,9 +123,9 @@
2091 } else {
2092 Qt.openUrlExternally(("%1:%2").arg(action).arg(detail.value(0)))
2093 }
2094- pageStack.pop()
2095+ pageStack.removePage(root)
2096 }
2097- onContactRemoved: pageStack.pop()
2098+ onContactRemoved: pageStack.removePage(root)
2099 onContactFetched: {
2100 root.contact = contact
2101 if (root.active && root.addPhoneToContact != "") {
2102
2103=== added file 'src/qml/MessagingPageLayout.qml'
2104--- src/qml/MessagingPageLayout.qml 1970-01-01 00:00:00 +0000
2105+++ src/qml/MessagingPageLayout.qml 2016-02-01 23:18:57 +0000
2106@@ -0,0 +1,64 @@
2107+import QtQuick 2.0
2108+import Ubuntu.Components 1.3
2109+
2110+AdaptivePageLayout {
2111+ id: layout
2112+ property var _pagesToRemove: []
2113+
2114+ function deleteInstances() {
2115+ for (var i in _pagesToRemove) {
2116+ if (_pagesToRemove[i].destroy) {
2117+ _pagesToRemove[i].destroy()
2118+ }
2119+ }
2120+ _pagesToRemove = []
2121+ }
2122+
2123+ function removePage(page) {
2124+ // check if this page was allocated dynamically and then remove it
2125+ for (var i in _pagesToRemove) {
2126+ if (_pagesToRemove[i] == page) {
2127+ _pagesToRemove[i].destroy()
2128+ _pagesToRemove.splice(i, 1)
2129+ break
2130+ }
2131+ }
2132+ removePages(page)
2133+ }
2134+
2135+ function addFileToNextColumnSync(parentObject, resolvedUrl, properties) {
2136+ if (typeof(properties) === 'undefined') {
2137+ properties = {}
2138+ }
2139+ var page = Qt.createComponent(resolvedUrl).createObject(parentObject, properties)
2140+ layout.addPageToNextColumn(parentObject, page)
2141+ _pagesToRemove.push(page)
2142+ }
2143+
2144+ function addFileToCurrentColumnSync(parentObject, resolvedUrl, properties) {
2145+ if (typeof(properties) === 'undefined') {
2146+ properties = {}
2147+ }
2148+ var page = Qt.createComponent(resolvedUrl).createObject(parentObject, properties)
2149+ layout.addPageToCurrentColumn(parentObject, page)
2150+ _pagesToRemove.push(page)
2151+ }
2152+
2153+ function addComponentToNextColumnSync(parentObject, component, properties) {
2154+ if (typeof(properties) === 'undefined') {
2155+ properties = {}
2156+ }
2157+ var page = component.createObject(parentObject, properties)
2158+ layout.addPageToNextColumn(parentObject, page)
2159+ _pagesToRemove.push(page)
2160+ }
2161+
2162+ function addComponentToCurrentColumnSync(parentObject, component, properties) {
2163+ if (typeof(properties) === 'undefined') {
2164+ properties = {}
2165+ }
2166+ var page = component.createObject(parentObject, properties)
2167+ layout.addPageToCurrentColumn(parentObject, page)
2168+ _pagesToRemove.push(page)
2169+ }
2170+}
2171
2172=== modified file 'src/qml/MultiRecipientInput.qml'
2173--- src/qml/MultiRecipientInput.qml 2015-09-14 13:51:27 +0000
2174+++ src/qml/MultiRecipientInput.qml 2016-02-01 23:18:57 +0000
2175@@ -29,7 +29,7 @@
2176 property variant recipients: []
2177 property string searchString: ""
2178 signal clearSearch()
2179- style: Theme.createStyleComponent("TextFieldStyle.qml", multiRecipientWidget)
2180+ styleName: "TextFieldStyle"
2181 clip: true
2182 height: contactFlow.height
2183 focus: activeFocus
2184
2185=== modified file 'src/qml/NewRecipientPage.qml'
2186--- src/qml/NewRecipientPage.qml 2015-11-16 22:16:06 +0000
2187+++ src/qml/NewRecipientPage.qml 2016-02-01 23:18:57 +0000
2188@@ -26,7 +26,6 @@
2189 objectName: "newRecipientPage"
2190
2191 property Item multiRecipient: null
2192- property Item parentPage: null
2193 property string phoneToAdd: ""
2194 property QtObject contactIndex: null
2195
2196@@ -44,11 +43,39 @@
2197 {
2198 multiRecipient.addRecipient(phoneNumber)
2199 multiRecipient.forceActiveFocus()
2200- mainStack.pop()
2201- }
2202-
2203- title: i18n.tr("Add recipient")
2204-
2205+ mainStack.removePage(newRecipientPage)
2206+ }
2207+
2208+ header: PageHeader {
2209+ id: pageHeader
2210+
2211+ property alias leadingActions: leadingBar.actions
2212+ property alias trailingActions: trailingBar.actions
2213+
2214+ title: i18n.tr("Add recipient")
2215+ leadingActionBar {
2216+ id: leadingBar
2217+ }
2218+
2219+ trailingActionBar {
2220+ id: trailingBar
2221+ actions: [
2222+ Action {
2223+ text: i18n.tr("Back")
2224+ iconName: "back"
2225+ onTriggered: {
2226+ mainStack.removePage(newRecipientPage)
2227+ newRecipientPage.destroy()
2228+ }
2229+ }
2230+ ]
2231+ }
2232+ }
2233+
2234+ Sections {
2235+ id: headerSections
2236+ model: [i18n.tr("All"), i18n.tr("Favorites")]
2237+ }
2238 TextField {
2239 id: searchField
2240
2241@@ -69,11 +96,10 @@
2242
2243 state: "default"
2244 states: [
2245- PageHeadState {
2246+ State {
2247 id: defaultState
2248-
2249 name: "default"
2250- actions: [
2251+ property list<QtObject> trailingActions: [
2252 Action {
2253 text: i18n.tr("Search")
2254 iconName: "search"
2255@@ -84,10 +110,11 @@
2256 }
2257 }
2258 ]
2259+
2260 PropertyChanges {
2261- target: newRecipientPage.head
2262- actions: defaultState.actions
2263- sections.model: [i18n.tr("All"), i18n.tr("Favorites")]
2264+ target: pageHeader
2265+ trailingActions: defaultState.trailingActions
2266+ extension: headerSections
2267 }
2268 PropertyChanges {
2269 target: searchField
2270@@ -95,27 +122,34 @@
2271 visible: false
2272 }
2273 },
2274- PageHeadState {
2275+ State {
2276 id: searchingState
2277-
2278 name: "searching"
2279- backAction: Action {
2280- iconName: "back"
2281- text: i18n.tr("Cancel")
2282- onTriggered: {
2283- newRecipientPage.forceActiveFocus()
2284- newRecipientPage.state = "default"
2285- newRecipientPage.head.sections.selectedIndex = 0
2286+ property list<QtObject> leadingActions: [
2287+ Action {
2288+ iconName: "back"
2289+ text: i18n.tr("Cancel")
2290+ onTriggered: {
2291+ newRecipientPage.forceActiveFocus()
2292+ newRecipientPage.state = "default"
2293+ headerSections.selectedIndex = 0
2294+ }
2295 }
2296- }
2297+ ]
2298
2299 PropertyChanges {
2300- target: newRecipientPage.head
2301- backAction: searchingState.backAction
2302+ target: pageHeader
2303+ leadingActions: searchingState.leadingActions
2304+ trailingActions: []
2305 contents: searchField
2306 }
2307
2308 PropertyChanges {
2309+ target: headerSections
2310+ visible: false
2311+ }
2312+
2313+ PropertyChanges {
2314 target: searchField
2315 text: ""
2316 visible: true
2317@@ -127,7 +161,7 @@
2318 id: contactList
2319 objectName: "newRecipientList"
2320 anchors {
2321- top: parent.top
2322+ top: pageHeader.bottom
2323 left: parent.left
2324 right: parent.right
2325 bottom: keyboard.top
2326@@ -139,12 +173,14 @@
2327 filterTerm: searchField.text
2328 onContactClicked: {
2329 if (newRecipientPage.phoneToAdd != "") {
2330- mainView.addPhoneToContact(contact,
2331+ mainView.addPhoneToContact(newRecipientPage,
2332+ contact,
2333 newRecipientPage.phoneToAdd,
2334 newRecipientPage,
2335 contactList.listModel)
2336 } else {
2337- mainView.showContactDetails(contact,
2338+ mainView.showContactDetails(newRecipientPage,
2339+ contact,
2340 newRecipientPage,
2341 contactList.listModel)
2342 }
2343@@ -152,24 +188,20 @@
2344
2345 onAddNewContactClicked: {
2346 var newContact = ContactsJS.createEmptyContact(newRecipientPage.phoneToAdd, newRecipientPage)
2347- pageStack.push(Qt.resolvedUrl("MessagingContactEditorPage.qml"),
2348- { model: contactList.listModel,
2349- contact: newContact,
2350- initialFocusSection: (newRecipientPage.phoneToAdd != "" ? "phones" : "name"),
2351- contactListPage: newRecipientPage
2352- })
2353+ mainStack.addFileToCurrentColumnSync(newRecipientPage,
2354+ Qt.resolvedUrl("MessagingContactEditorPage.qml"),
2355+ { model: contactList.listModel,
2356+ contact: newContact,
2357+ initialFocusSection: (newRecipientPage.phoneToAdd != "" ? "phones" : "name"),
2358+ contactListPage: newRecipientPage })
2359 }
2360 }
2361
2362- // WORKAROUND: This is necessary to make the header visible from a bottom edge page
2363 Component.onCompleted: {
2364- parentPage.active = false
2365 if (QTCONTACTS_PRELOAD_VCARD !== "") {
2366 contactList.listModel.importContacts("file://" + QTCONTACTS_PRELOAD_VCARD)
2367 }
2368 }
2369- Component.onDestruction: parentPage.active = true
2370-
2371 onActiveChanged: {
2372 if (active && (state === "searching")) {
2373 searchField.forceActiveFocus()
2374
2375=== modified file 'src/qml/RegularMessageDelegate.qml'
2376--- src/qml/RegularMessageDelegate.qml 2015-08-05 19:31:15 +0000
2377+++ src/qml/RegularMessageDelegate.qml 2016-02-01 23:18:57 +0000
2378@@ -57,7 +57,7 @@
2379 selectionMode: root.isInSelectionMode
2380 accountLabel: {
2381 var account = telepathyHelper.accountForId(accountId)
2382- if (account.type == AccountEntry.PhoneAccount || account.type == AccountEntry.MultimediaAccount) {
2383+ if (account && (account.type == AccountEntry.PhoneAccount || account.type == AccountEntry.MultimediaAccount)) {
2384 if (multiplePhoneAccounts) {
2385 return account.displayName
2386 }
2387
2388=== modified file 'src/qml/SettingsPage.qml'
2389--- src/qml/SettingsPage.qml 2015-09-14 13:51:27 +0000
2390+++ src/qml/SettingsPage.qml 2016-02-01 23:18:57 +0000
2391@@ -35,6 +35,11 @@
2392 schema.id: "com.ubuntu.phone"
2393 }
2394
2395+ header: PageHeader {
2396+ id: pageHeader
2397+ title: settingsPage.title
2398+ }
2399+
2400 Component {
2401 id: settingDelegate
2402 Item {
2403@@ -66,9 +71,25 @@
2404 }
2405
2406 ListView {
2407- anchors.fill: parent
2408+ anchors {
2409+ top: pageHeader.bottom
2410+ left: parent.left
2411+ right: parent.right
2412+ bottom: parent.bottom
2413+ }
2414 model: settingsModel
2415 delegate: settingDelegate
2416 }
2417+
2418+ Loader {
2419+ id: messagesBottomEdgeLoader
2420+ active: mainView.dualPanel
2421+ sourceComponent: MessagingBottomEdge {
2422+ id: messagesBottomEdge
2423+ parent: settingsPage
2424+ hint.text: ""
2425+ hint.height: 0
2426+ }
2427+ }
2428 }
2429
2430
2431=== modified file 'src/qml/messaging-app.qml'
2432--- src/qml/messaging-app.qml 2016-02-01 23:18:57 +0000
2433+++ src/qml/messaging-app.qml 2016-02-01 23:18:57 +0000
2434@@ -39,6 +39,10 @@
2435 }
2436 property QtObject account: defaultPhoneAccount()
2437 property bool applicationActive: Qt.application.active
2438+ property alias mainStack: layout
2439+ property bool dualPanel: mainStack.columns > 1
2440+ property QtObject bottomEdge: null
2441+ property bool composingNewMessage: bottomEdge.status === BottomEdge.Committed
2442
2443 activeFocusOnPress: false
2444
2445@@ -58,7 +62,7 @@
2446 return null
2447 }
2448
2449- function showContactDetails(contact, contactListPage, contactsModel) {
2450+ function showContactDetails(currentPage, contact, contactListPage, contactsModel) {
2451 var initialProperties = { "contactListPage": contactListPage,
2452 "model": contactsModel}
2453
2454@@ -68,21 +72,24 @@
2455 initialProperties['contact'] = contact
2456 }
2457
2458- mainStack.push(Qt.resolvedUrl("MessagingContactViewPage.qml"),
2459- initialProperties)
2460- }
2461-
2462- function addNewContact(phoneNumber, contactListPage) {
2463- mainStack.push(Qt.resolvedUrl("MessagingContactEditorPage.qml"),
2464- { "contactId": contactId,
2465- "addPhoneToContact": phoneNumber,
2466- "contactListPage": contactListPage })
2467- }
2468-
2469- function addPhoneToContact(contact, phoneNumber, contactListPage, contactsModel) {
2470+ mainStack.addFileToCurrentColumnSync(currentPage,
2471+ Qt.resolvedUrl("MessagingContactViewPage.qml"),
2472+ initialProperties)
2473+ }
2474+
2475+ function addNewContact(currentPage, phoneNumber, contactListPage) {
2476+ mainStack.addFileToCurrentColumnSync(currentPage,
2477+ Qt.resolvedUrl("MessagingContactEditorPage.qml"),
2478+ { "contactId": contactId,
2479+ "addPhoneToContact": phoneNumber,
2480+ "contactListPage": contactListPage })
2481+ }
2482+
2483+ function addPhoneToContact(currentPage, contact, phoneNumber, contactListPage, contactsModel) {
2484 if (contact === "") {
2485- mainStack.push(Qt.resolvedUrl("NewRecipientPage.qml"),
2486- { "phoneToAdd": phoneNumber })
2487+ mainStack.addFileToCurrentColumnSync(currentPage,
2488+ Qt.resolvedUrl("NewRecipientPage.qml"),
2489+ { "phoneToAdd": phoneNumber })
2490 } else {
2491 var initialProperties = { "addPhoneToContact": phoneNumber,
2492 "contactListPage": contactListPage,
2493@@ -92,8 +99,9 @@
2494 } else {
2495 initialProperties['contact'] = contact
2496 }
2497- mainStack.push(Qt.resolvedUrl("MessagingContactViewPage.qml"),
2498- initialProperties)
2499+ mainStack.addFileToCurrentColumnSync(currentPage,
2500+ Qt.resolvedUrl("MessagingContactViewPage.qml"),
2501+ initialProperties)
2502 }
2503 }
2504
2505@@ -119,6 +127,10 @@
2506 threadModel.removeThreads(threads);
2507 }
2508
2509+ function showBottomEdgePage(properties) {
2510+ bottomEdge.commitWithProperties(properties)
2511+ }
2512+
2513 Connections {
2514 target: telepathyHelper
2515 // restore default bindings if any system settings changed
2516@@ -141,13 +153,14 @@
2517 Component.onCompleted: {
2518 i18n.domain = "messaging-app"
2519 i18n.bindtextdomain("messaging-app", i18nDirectory)
2520+ emptyStack()
2521 }
2522
2523 Connections {
2524 target: telepathyHelper
2525 onSetupReady: {
2526 if (multiplePhoneAccounts && !telepathyHelper.defaultMessagingAccount &&
2527- !settings.mainViewIgnoreFirstTimeDialog && mainStack.depth === 1) {
2528+ !settings.mainViewIgnoreFirstTimeDialog && mainPage.displayedThreadIndex < 0) {
2529 PopupUtils.open(Qt.createComponent("Dialogs/NoDefaultSIMCardDialog.qml").createObject(mainView))
2530 }
2531 }
2532@@ -187,7 +200,7 @@
2533 var properties = {}
2534 emptyStack()
2535 properties["sharedAttachmentsTransfer"] = transfer
2536- mainStack.currentPage.showBottomEdgePage(Qt.resolvedUrl("Messages.qml"), properties)
2537+ mainView.showBottomEdgePage(properties)
2538 }
2539 }
2540
2541@@ -211,15 +224,21 @@
2542 }
2543
2544 function emptyStack() {
2545- while (mainStack.depth !== 1 && mainStack.depth !== 0) {
2546- mainStack.pop()
2547+ mainStack.removePage(mainPage)
2548+ layout.deleteInstances()
2549+ showEmptyState()
2550+ }
2551+
2552+ function showEmptyState() {
2553+ if (mainStack.columns > 1) {
2554+ layout.addComponentToNextColumnSync(mainStack.primaryPage, emptyStatePageComponent)
2555 }
2556 }
2557
2558 function startNewMessage() {
2559 var properties = {}
2560 emptyStack()
2561- mainStack.currentPage.showBottomEdgePage(Qt.resolvedUrl("Messages.qml"))
2562+ mainView.showBottomEdgePage(properties)
2563 }
2564
2565 function startChat(identifiers, text, accountId) {
2566@@ -261,9 +280,26 @@
2567 if (typeof(accountId)!=='undefined') {
2568 properties["accountId"] = accountId
2569 }
2570+
2571 emptyStack()
2572- mainStack.push(Qt.resolvedUrl("Messages.qml"), properties)
2573- }
2574+ // FIXME: AdaptivePageLayout takes a really long time to create pages,
2575+ // so we create manually and push that
2576+ mainStack.addComponentToNextColumnSync(mainPage, messagesWithBottomEdge, properties)
2577+ }
2578+
2579+ InputInfo {
2580+ id: inputInfo
2581+ }
2582+
2583+ // WORKAROUND: Due the missing feature on SDK, they can not detect if
2584+ // there is a mouse attached to device or not. And this will cause the
2585+ // bootom edge component to not work correct on desktop.
2586+ Binding {
2587+ target: QuickUtils
2588+ property: "mouseAttached"
2589+ value: inputInfo.hasMouse
2590+ }
2591+
2592
2593 Connections {
2594 target: UriHandler
2595@@ -274,11 +310,60 @@
2596 }
2597 }
2598
2599-
2600- PageStack {
2601- id: mainStack
2602-
2603- objectName: "mainStack"
2604- Component.onCompleted: mainStack.push(Qt.resolvedUrl("MainPage.qml"))
2605+ Component {
2606+ id: messagesWithBottomEdge
2607+
2608+ Messages {
2609+ id: messages
2610+ height: mainPage.height
2611+
2612+ Component.onCompleted: mainPage._messagesPage = messages
2613+ Loader {
2614+ id: messagesBottomEdgeLoader
2615+ active: mainView.dualPanel
2616+ sourceComponent: MessagingBottomEdge {
2617+ id: messagesBottomEdge
2618+ parent: messages
2619+ hint.text: ""
2620+ hint.height: 0
2621+ }
2622+ }
2623+ }
2624+ }
2625+
2626+ Component {
2627+ id: emptyStatePageComponent
2628+ Page {
2629+ id: emptyStatePage
2630+
2631+ EmptyState {
2632+ labelVisible: mainPage.isEmpty
2633+ }
2634+
2635+ header: PageHeader { }
2636+
2637+ Loader {
2638+ id: bottomEdgeLoader
2639+ sourceComponent: MessagingBottomEdge {
2640+ parent: emptyStatePage
2641+ }
2642+ }
2643+ }
2644+ }
2645+
2646+ MessagingPageLayout {
2647+ id: layout
2648+ anchors.fill: parent
2649+ primaryPage: MainPage {
2650+ id: mainPage
2651+ }
2652+
2653+ onColumnsChanged: {
2654+ // we only have things to do here in case no thread is selected
2655+ if (mainPage.displayedThreadIndex < 0) {
2656+ layout.removePage(mainPage)
2657+ showEmptyState()
2658+ }
2659+ }
2660 }
2661 }
2662
2663=== modified file 'tests/autopilot/messaging_app/emulators.py'
2664--- tests/autopilot/messaging_app/emulators.py 2015-11-04 19:57:45 +0000
2665+++ tests/autopilot/messaging_app/emulators.py 2016-02-01 23:18:57 +0000
2666@@ -78,14 +78,14 @@
2667
2668 def click_header_action(self, action):
2669 """Click the action 'action' on the header"""
2670- self.get_header().click_action_button(action)
2671+ action = self.wait_select_single(objectName='%s_button' % action)
2672+ self.pointing_device.click_object(action)
2673
2674 # messages page
2675 def get_messages_page(self):
2676 """Return messages with objectName messagesPage"""
2677
2678- return self.wait_select_single("Messages", objectName="messagesPage",
2679- active=True)
2680+ return self.wait_select_single("Messages", objectName="messagesPage")
2681
2682 def get_newmessage_textfield(self):
2683 """Return TextField with objectName newPhoneNumberField"""
2684@@ -116,40 +116,12 @@
2685
2686 return self.get_messages_page().select_single(objectName='sendButton')
2687
2688- def get_toolbar_back_button(self):
2689- """Return toolbar button with objectName back_toolbar_button"""
2690-
2691- return self.select_single(
2692- objectName='back_toolbar_button'
2693- )
2694-
2695- def get_toolbar_select_messages_button(self):
2696- """Return toolbar button with objectName selectMessagesButton"""
2697-
2698- return self.select_single(
2699- objectName='selectMessagesButton'
2700- )
2701-
2702 def get_contact_list_view(self):
2703 """Returns the ContactListView object"""
2704 return self.select_single(
2705 objectName='newRecipientList'
2706 )
2707
2708- def get_toolbar_contact_profile_button(self):
2709- """Return toolbar button with objectName contactProfileButton"""
2710-
2711- return self.select_single(
2712- objectName='contactProfileButton'
2713- )
2714-
2715- def get_toolbar_contact_call_button(self):
2716- """Return toolbar button with objectName contactCallButton"""
2717-
2718- return self.select_single(
2719- objectName='contactCallButton'
2720- )
2721-
2722 def get_dialog_buttons(self, visible=True):
2723 """Return DialogButtons
2724
2725@@ -213,7 +185,7 @@
2726
2727 def start_new_message(self):
2728 """Reveal the bottom edge page to start composing a new message"""
2729- self.get_main_page().reveal_bottom_edge_page()
2730+ self.reveal_bottom_edge_page()
2731
2732 def enable_messages_selection_mode(self):
2733 """Enable the selection mode on the messages page by pressing and
2734@@ -338,38 +310,30 @@
2735
2736 def open_settings_page(self):
2737 self.click_threads_header_settings()
2738- return self.wait_select_single(SettingsPage)
2739+ settings_page = self.wait_select_single(SettingsPage)
2740+ settings_page.active.waitFor(True)
2741+ return settings_page
2742
2743 def get_swipe_item_demo(self):
2744 return self.wait_select_single(
2745 'SwipeItemDemo', objectName='swipeItemDemo', parentActive=True)
2746
2747-
2748-class PageWithBottomEdge(MainView):
2749- """An emulator class that makes it easy to interact with the bottom edge
2750- swipe page"""
2751- def __init__(self, *args):
2752- super(PageWithBottomEdge, self).__init__(*args)
2753-
2754 def reveal_bottom_edge_page(self):
2755 """Bring the bottom edge page to the screen"""
2756- self.bottomEdgePageLoaded.wait_for(True)
2757 try:
2758- action_item = self.wait_select_single(objectName='bottomEdgeTip')
2759- start_x = (action_item.globalRect.x +
2760- (action_item.globalRect.width * 0.5))
2761- start_y = (action_item.globalRect.y +
2762- (action_item.height * 0.5))
2763+ start_x = (self.globalRect.x +
2764+ (self.globalRect.width * 0.5))
2765+ start_y = (self.globalRect.y + self.height)
2766 stop_y = start_y - (self.height * 0.7)
2767 self.pointing_device.drag(start_x, start_y,
2768 start_x, stop_y, rate=2)
2769- self.isReady.wait_for(True)
2770+ self.composingNewMessage.wait_for(True)
2771 except StateNotFoundError:
2772 logger.error('BottomEdge element not found.')
2773 raise
2774
2775
2776-class MainPage(PageWithBottomEdge):
2777+class MainPage(MainView):
2778 """Autopilot helper for the Main Page."""
2779
2780 def get_thread_count(self):
2781
2782=== modified file 'tests/qml/CMakeLists.txt'
2783--- tests/qml/CMakeLists.txt 2016-02-01 23:18:57 +0000
2784+++ tests/qml/CMakeLists.txt 2016-02-01 23:18:57 +0000
2785@@ -36,7 +36,8 @@
2786
2787 add_test(${TEST} ${XVFB_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/${TEST}
2788 -input ${CMAKE_CURRENT_SOURCE_DIR}
2789- -import ${CMAKE_BINARY_DIR}/src)
2790+ -import ${CMAKE_BINARY_DIR}/src
2791+ -import ${UNITY8_QML_PATH})
2792
2793 # make qml files visible in QtCreator
2794 file(GLOB_RECURSE NON_COMPILED_FILES *.qml)
2795
2796=== modified file 'tests/qml/tst_MessagesView.qml'
2797--- tests/qml/tst_MessagesView.qml 2015-11-19 00:34:45 +0000
2798+++ tests/qml/tst_MessagesView.qml 2016-02-01 23:18:57 +0000
2799@@ -129,16 +129,21 @@
2800 var senderId = "1234567"
2801 var stack = findChild(mainViewLoader, "mainStack")
2802 tryCompare(mainViewLoader.item, 'applicationActive', true)
2803- tryCompare(stack, 'depth', 1)
2804 // if messaging-app has no account set, it will not try to get the thread from history
2805 // and instead will generate the list of participants, take advantage of that
2806 var account = mainViewLoader.item.account
2807 mainViewLoader.item.account = null
2808 mainViewLoader.item.startChat(senderId, "")
2809 mainViewLoader.item.account = account
2810- tryCompare(stack, 'depth', 2)
2811- mainViewLoader.item.applicationActive = false
2812- var messageList = findChild(mainViewLoader, "messageList")
2813+ var messageList
2814+ while (true) {
2815+ messageList = findChild(mainViewLoader, "messageList")
2816+ if (messageList) {
2817+ break
2818+ }
2819+ wait(200)
2820+ }
2821+
2822 messageList.listModel = messagesModel
2823 tryCompare(messageList, 'count', 2)
2824 compare(messageAcknowledgeSpy.count, 0)

Subscribers

People subscribed via source and target branches

to all changes: