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
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2015-10-13 15:13:32 +0000
+++ CMakeLists.txt 2016-02-01 23:18:57 +0000
@@ -35,12 +35,7 @@
35# Instruct CMake to run moc automatically when needed.35# Instruct CMake to run moc automatically when needed.
36set(CMAKE_AUTOMOC ON)36set(CMAKE_AUTOMOC ON)
3737
38configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY)
39
40#find_package(Qt5Contacts)
41find_package(Qt5DBus)38find_package(Qt5DBus)
42#find_package(Qt5Gui)
43#find_package(Qt5Multimedia)
44find_package(Qt5Qml)39find_package(Qt5Qml)
45find_package(Qt5Quick)40find_package(Qt5Quick)
46find_package(Qt5Test)41find_package(Qt5Test)
@@ -49,23 +44,24 @@
49include(qt5)44include(qt5)
5045
51find_package(PkgConfig REQUIRED)46find_package(PkgConfig REQUIRED)
52#pkg_check_modules(TP_QT5 REQUIRED TelepathyQt5)
53#pkg_check_modules(TPL_QT5 REQUIRED TelepathyLoggerQt5)
54#pkg_check_modules(QTGLIB REQUIRED QtGLib-2.0)
55#pkg_check_modules(GLIB REQUIRED glib-2.0)
56pkg_check_modules(NOTIFY REQUIRED libnotify)47pkg_check_modules(NOTIFY REQUIRED libnotify)
57#pkg_check_modules(MESSAGING_MENU REQUIRED messaging-menu)48
5849#find unity8 qml libraries
59# Check if the messaging menu has the message header50set(UNITY8_QML_PATH /usr/lib/${CMAKE_C_LIBRARY_ARCHITECTURE}/unity8/qml/)
60#set(CMAKE_REQUIRED_INCLUDES ${MESSAGING_MENU_INCLUDE_DIRS})51find_path(LIB_UNITY_QML_EXISTS NAMES libUnity-qml.so
61#check_include_file("messaging-menu-message.h" HAVE_MESSAGING_MENU_MESSAGE)52 HINTS "${UNITY8_QML_PATH}"
6253 NO_CMAKE_PATH
63if (HAVE_MESSAGING_MENU_MESSAGE)54 NO_CMAKE_ENVIRONMENT_PATH
64 add_definitions(-DHAVE_MESSAGING_MENU_MESSAGE)55 NO_SYSTEM_ENVIRONMENT_PATH
65endif (HAVE_MESSAGING_MENU_MESSAGE)56)
57if(!LIB_UNITY_QML_EXISTS)
58 MESSAGE(FATAL_ERROR "unity8 private package not-found")
59endif()
6660
67add_definitions(-DQT_NO_KEYWORDS)61add_definitions(-DQT_NO_KEYWORDS)
6862
63configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY)
64
69include_directories(65include_directories(
70 ${CMAKE_CURRENT_BINARY_DIR}66 ${CMAKE_CURRENT_BINARY_DIR}
71 ${CMAKE_CURRENT_SOURCE_DIR}67 ${CMAKE_CURRENT_SOURCE_DIR}
7268
=== added file 'TODO.convergence'
--- TODO.convergence 1970-01-01 00:00:00 +0000
+++ TODO.convergence 2016-02-01 23:18:57 +0000
@@ -0,0 +1,3 @@
1- Right now if you call startChat() in the two panel layout, it won't match the
2 thread on the left side, and if you resize it to one panel, the conversation
3 is closed.
04
=== modified file 'config.h.in'
--- config.h.in 2014-05-28 20:28:55 +0000
+++ config.h.in 2016-02-01 23:18:57 +0000
@@ -27,6 +27,7 @@
27#include <QtDBus/QDBusReply>27#include <QtDBus/QDBusReply>
2828
29#define I18N_DIRECTORY "@CMAKE_INSTALL_PREFIX@/share/locale"29#define I18N_DIRECTORY "@CMAKE_INSTALL_PREFIX@/share/locale"
30#define UNITY8_QML_PATH "@UNITY8_QML_PATH@"
3031
31inline bool isRunningInstalled() {32inline bool isRunningInstalled() {
32 static bool installed = (QCoreApplication::applicationDirPath() ==33 static bool installed = (QCoreApplication::applicationDirPath() ==
3334
=== modified file 'src/messagingapplication.cpp'
--- src/messagingapplication.cpp 2016-02-01 23:18:57 +0000
+++ src/messagingapplication.cpp 2016-02-01 23:18:57 +0000
@@ -165,6 +165,7 @@
165 m_view->rootContext()->setContextProperty("application", this);165 m_view->rootContext()->setContextProperty("application", this);
166 m_view->rootContext()->setContextProperty("i18nDirectory", I18N_DIRECTORY);166 m_view->rootContext()->setContextProperty("i18nDirectory", I18N_DIRECTORY);
167 m_view->engine()->setBaseUrl(QUrl::fromLocalFile(messagingAppDirectory()));167 m_view->engine()->setBaseUrl(QUrl::fromLocalFile(messagingAppDirectory()));
168 m_view->engine()->addImportPath(UNITY8_QML_PATH);
168169
169 // check if there is a contacts backend override170 // check if there is a contacts backend override
170 QString contactsBackend = qgetenv("QTCONTACTS_MANAGER_OVERRIDE");171 QString contactsBackend = qgetenv("QTCONTACTS_MANAGER_OVERRIDE");
171172
=== modified file 'src/qml/AccountSectionDelegate.qml'
--- src/qml/AccountSectionDelegate.qml 2015-09-14 13:51:27 +0000
+++ src/qml/AccountSectionDelegate.qml 2016-02-01 23:18:57 +0000
@@ -28,7 +28,8 @@
28 property var messageData: null28 property var messageData: null
29 property int index: -129 property int index: -1
30 property Item delegateItem30 property Item delegateItem
31 property string accountLabel: telepathyHelper.accountForId(messageData.accountId).displayName31 property var account: telepathyHelper.accountForId(messageData.accountId)
32 property string accountLabel: account ? account.displayName : ""
3233
33 // update the accountLabel when the list of accounts become available34 // update the accountLabel when the list of accounts become available
34 Item {35 Item {
3536
=== modified file 'src/qml/ComposeBar.qml'
--- src/qml/ComposeBar.qml 2016-02-01 23:18:57 +0000
+++ src/qml/ComposeBar.qml 2016-02-01 23:18:57 +0000
@@ -64,6 +64,10 @@
64 }64 }
6565
66 function addAttachments(transfer) {66 function addAttachments(transfer) {
67 if (!transfer || !transfer.items) {
68 return
69 }
70
67 for (var i = 0; i < transfer.items.length; i++) {71 for (var i = 0; i < transfer.items.length; i++) {
68 if (String(transfer.items[i].text).length > 0) {72 if (String(transfer.items[i].text).length > 0) {
69 composeBar.text = String(transfer.items[i].text)73 composeBar.text = String(transfer.items[i].text)
7074
=== added file 'src/qml/EmptyState.qml'
--- src/qml/EmptyState.qml 1970-01-01 00:00:00 +0000
+++ src/qml/EmptyState.qml 2016-02-01 23:18:57 +0000
@@ -0,0 +1,38 @@
1import QtQuick 2.0
2import Ubuntu.Components 1.3
3
4Item {
5 id: emptyStateScreen
6
7 property alias labelVisible: emptyStateLabel.visible
8
9 anchors {
10 left: parent.left
11 leftMargin: units.gu(6)
12 right: parent.right
13 rightMargin: units.gu(6)
14 verticalCenter: parent.verticalCenter
15 }
16 height: childrenRect.height
17 Icon {
18 id: emptyStateIcon
19 anchors.top: emptyStateScreen.top
20 anchors.horizontalCenter: parent.horizontalCenter
21 height: units.gu(5)
22 width: height
23 opacity: 0.3
24 name: "message"
25 }
26 Label {
27 id: emptyStateLabel
28 anchors.top: emptyStateIcon.bottom
29 anchors.topMargin: units.gu(2)
30 anchors.left: parent.left
31 anchors.right: parent.right
32 text: i18n.tr("Compose a new message by swiping up from the bottom of the screen.")
33 color: "#5d5d5d"
34 fontSize: "x-large"
35 wrapMode: Text.WordWrap
36 horizontalAlignment: Text.AlignHCenter
37 }
38}
039
=== added file 'src/qml/InputInfo.qml'
--- src/qml/InputInfo.qml 1970-01-01 00:00:00 +0000
+++ src/qml/InputInfo.qml 2016-02-01 23:18:57 +0000
@@ -0,0 +1,24 @@
1import QtQuick 2.0
2//import Unity.InputInfo 0.1
3
4Item {
5 // FIXME: implement correctly without relying on unity private stuff
6 property bool hasMouse: mainView.dualPanel //miceModel.count > 0 || touchPadModel.count > 0
7 property bool hasKeyboard: false //keyboardsModel.count > 0
8
9 /*InputDeviceModel {
10 id: miceModel
11 deviceFilter: InputInfo.Mouse
12 }
13
14 InputDeviceModel {
15 id: touchPadModel
16 deviceFilter: InputInfo.TouchPad
17 }
18
19 InputDeviceModel {
20 id: keyboardsModel
21 deviceFilter: InputInfo.Keyboard
22 }*/
23}
24
025
=== removed file 'src/qml/LocalPageWithBottomEdge.qml'
--- src/qml/LocalPageWithBottomEdge.qml 2015-09-14 13:51:27 +0000
+++ src/qml/LocalPageWithBottomEdge.qml 1970-01-01 00:00:00 +0000
@@ -1,428 +0,0 @@
1/*
2 * Copyright (C) 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 Example:
19
20 MainView {
21 objectName: "mainView"
22
23 applicationName: "com.ubuntu.developer.boiko.bottomedge"
24
25 width: units.gu(100)
26 height: units.gu(75)
27
28 Component {
29 id: pageComponent
30
31 PageWithBottomEdge {
32 id: mainPage
33 title: i18n.tr("Main Page")
34
35 Rectangle {
36 anchors.fill: parent
37 color: "white"
38 }
39
40 bottomEdgePageComponent: Page {
41 title: "Contents"
42 anchors.fill: parent
43 //anchors.topMargin: contentsPage.flickable.contentY
44
45 ListView {
46 anchors.fill: parent
47 model: 50
48 delegate: ListItems.Standard {
49 text: "One Content Item: " + index
50 }
51 }
52 }
53 bottomEdgeTitle: i18n.tr("Bottom edge action")
54 }
55 }
56
57 PageStack {
58 id: stack
59 Component.onCompleted: stack.push(pageComponent)
60 }
61 }
62
63*/
64
65import QtQuick 2.2
66import Ubuntu.Components 1.3
67
68Page {
69 id: page
70
71 property alias bottomEdgePageComponent: edgeLoader.sourceComponent
72 property alias bottomEdgePageSource: edgeLoader.source
73 property alias bottomEdgeTitle: tipLabel.text
74 property bool bottomEdgeEnabled: true
75 property int bottomEdgeExpandThreshold: page.height * 0.2
76 property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
77 property bool reloadBottomEdgePage: true
78
79 readonly property alias bottomEdgePage: edgeLoader.item
80 readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
81 readonly property bool isCollapsed: (bottomEdge.y === page.height)
82 readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
83 property var temporaryProperties: null
84
85 property bool _showEdgePageWhenReady: false
86 property int _areaWhenExpanded: 0
87
88 signal bottomEdgeReleased()
89 signal bottomEdgeDismissed()
90
91
92 function showBottomEdgePage(source, properties)
93 {
94 edgeLoader.setSource(source, properties)
95 temporaryProperties = properties
96 _showEdgePageWhenReady = true
97 }
98
99 function setBottomEdgePage(source, properties)
100 {
101 edgeLoader.setSource(source, properties)
102 }
103
104 function _pushPage()
105 {
106 if (edgeLoader.status === Loader.Ready) {
107 edgeLoader.item.active = true
108 page.pageStack.push(edgeLoader.item)
109 if (edgeLoader.item.flickable) {
110 edgeLoader.item.flickable.contentY = -page.header.height
111 edgeLoader.item.flickable.returnToBounds()
112 }
113 if (edgeLoader.item.ready)
114 edgeLoader.item.ready()
115 }
116 }
117
118
119 Component.onCompleted: {
120 // avoid a binding on the expanded height value
121 var expandedHeight = height;
122 _areaWhenExpanded = expandedHeight;
123 }
124
125 onActiveChanged: {
126 if (active) {
127 bottomEdge.state = "collapsed"
128 }
129 }
130
131 onBottomEdgePageLoadedChanged: {
132 if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
133 bottomEdge.state = "expanded"
134 _showEdgePageWhenReady = false
135 }
136 }
137
138 InverseMouseArea {
139 anchors.fill: edgeLoader.item
140 sensingArea: mainView
141 enabled: !tip.hidden
142 onPressed: {
143 mouse.accepted = false
144 page.focus = false
145 }
146 z: 1
147 }
148
149 Rectangle {
150 id: bgVisual
151
152 color: "black"
153 anchors.fill: page
154 opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
155 z: 1
156 }
157
158 UbuntuShape {
159 id: tip
160 objectName: "bottomEdgeTip"
161
162 property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
163
164 enabled: mouseArea.enabled
165 anchors {
166 bottom: parent.bottom
167 horizontalCenter: bottomEdge.horizontalCenter
168 bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
169 Behavior on bottomMargin {
170 SequentialAnimation {
171 // wait some msecs in case of the focus change again, to avoid flickering
172 PauseAnimation {
173 duration: 300
174 }
175 UbuntuNumberAnimation {
176 duration: UbuntuAnimation.SnapDuration
177 }
178 }
179 }
180 }
181
182 z: 1
183 width: tipLabel.paintedWidth + units.gu(6)
184 height: bottomEdge.tipHeight + units.gu(1)
185 color: Theme.palette.normal.overlay
186 Label {
187 id: tipLabel
188
189 anchors {
190 top: parent.top
191 left: parent.left
192 right: parent.right
193 }
194 height: bottomEdge.tipHeight
195 verticalAlignment: Text.AlignVCenter
196 horizontalAlignment: Text.AlignHCenter
197 opacity: tip.hidden ? 0.0 : 1.0
198 Behavior on opacity {
199 UbuntuNumberAnimation {
200 duration: UbuntuAnimation.SnapDuration
201 }
202 }
203 }
204 }
205
206 Rectangle {
207 id: shadow
208
209 anchors {
210 left: parent.left
211 right: parent.right
212 bottom: parent.bottom
213 }
214 height: units.gu(1)
215 z: 1
216 opacity: 0.0
217 gradient: Gradient {
218 GradientStop { position: 0.0; color: "transparent" }
219 GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
220 }
221 }
222
223 MouseArea {
224 id: mouseArea
225
226 property real previousY: -1
227 property string dragDirection: "None"
228
229 preventStealing: true
230 drag {
231 axis: Drag.YAxis
232 target: bottomEdge
233 minimumY: bottomEdge.pageStartY
234 maximumY: page.height
235 }
236 enabled: edgeLoader.status == Loader.Ready
237
238 anchors {
239 left: parent.left
240 right: parent.right
241 bottom: parent.bottom
242
243 }
244 height: bottomEdge.tipHeight
245 z: 1
246
247 onReleased: {
248 page.bottomEdgeReleased()
249 if ((dragDirection === "BottomToTop") &&
250 bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
251 bottomEdge.state = "expanded"
252 } else {
253 bottomEdge.state = "collapsed"
254 }
255 previousY = -1
256 dragDirection = "None"
257 }
258
259 onPressed: {
260 previousY = mouse.y
261 tip.forceActiveFocus()
262 }
263
264 onMouseYChanged: {
265 var yOffset = previousY - mouseY
266 // skip if was a small move
267 if (Math.abs(yOffset) <= units.gu(2)) {
268 return
269 }
270 previousY = mouseY
271 dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
272 }
273 }
274
275 Rectangle {
276 id: bottomEdge
277 objectName: "bottomEdge"
278
279 readonly property int tipHeight: units.gu(3)
280 readonly property int pageStartY: 0
281
282 z: 1
283 color: Theme.palette.normal.background
284 clip: true
285 anchors {
286 left: parent.left
287 right: parent.right
288 }
289 height: page.height
290 y: height
291 visible: page.bottomEdgeEnabled && !page.isCollapsed
292 state: "collapsed"
293 states: [
294 State {
295 name: "collapsed"
296 PropertyChanges {
297 target: bottomEdge
298 y: bottomEdge.height
299 }
300 },
301 State {
302 name: "expanded"
303 PropertyChanges {
304 target: bottomEdge
305 y: bottomEdge.pageStartY
306 }
307 },
308 State {
309 name: "floating"
310 when: mouseArea.drag.active
311 PropertyChanges {
312 target: shadow
313 opacity: 1.0
314 }
315 }
316 ]
317
318 transitions: [
319 Transition {
320 to: "expanded"
321 SequentialAnimation {
322 alwaysRunToEnd: true
323
324 SmoothedAnimation {
325 target: bottomEdge
326 property: "y"
327 duration: UbuntuAnimation.FastDuration
328 easing.type: Easing.Linear
329 }
330 SmoothedAnimation {
331 target: edgeLoader
332 property: "anchors.topMargin"
333 to: - units.gu(4)
334 duration: UbuntuAnimation.FastDuration
335 easing.type: Easing.Linear
336 }
337 SmoothedAnimation {
338 target: edgeLoader
339 property: "anchors.topMargin"
340 to: 0
341 duration: UbuntuAnimation.FastDuration
342 easing: UbuntuAnimation.StandardEasing
343 }
344 ScriptAction {
345 script: page._pushPage()
346 }
347 }
348 },
349 Transition {
350 from: "expanded"
351 to: "collapsed"
352 SequentialAnimation {
353 alwaysRunToEnd: true
354
355 ScriptAction {
356 script: {
357 Qt.inputMethod.hide()
358 edgeLoader.item.parent = edgeLoader
359 edgeLoader.item.anchors.fill = edgeLoader
360 edgeLoader.item.active = false
361 }
362 }
363 SmoothedAnimation {
364 target: bottomEdge
365 property: "y"
366 duration: UbuntuAnimation.SlowDuration
367 }
368 ScriptAction {
369 script: {
370 // destroy current bottom page
371 if (page.reloadBottomEdgePage) {
372 edgeLoader.active = false
373 // remove properties from old instance
374 if (edgeLoader.source !== "") {
375 var properties = {}
376 if (temporaryProperties !== null) {
377 properties = temporaryProperties
378 temporaryProperties = null
379 }
380
381 edgeLoader.setSource(edgeLoader.source, properties)
382 }
383 // tip will receive focus on page active true
384 } else {
385 tip.forceActiveFocus()
386 }
387
388 // notify
389 page.bottomEdgeDismissed()
390
391 edgeLoader.active = true
392 }
393 }
394 }
395 },
396 Transition {
397 from: "floating"
398 to: "collapsed"
399 SmoothedAnimation {
400 target: bottomEdge
401 property: "y"
402 duration: UbuntuAnimation.FastDuration
403 }
404 }
405 ]
406
407 Loader {
408 id: edgeLoader
409
410 asynchronous: true
411 anchors.fill: parent
412 //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
413 Binding {
414 target: edgeLoader.status === Loader.Ready ? edgeLoader : null
415 property: "anchors.topMargin"
416 value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
417 when: !page.isReady
418 }
419
420 onLoaded: {
421 tip.forceActiveFocus()
422 if (page.isReady && edgeLoader.item.active !== true) {
423 page._pushPage()
424 }
425 }
426 }
427 }
428}
4290
=== modified file 'src/qml/MMS/Previewer.qml'
--- src/qml/MMS/Previewer.qml 2015-11-17 14:39:21 +0000
+++ src/qml/MMS/Previewer.qml 2016-02-01 23:18:57 +0000
@@ -31,7 +31,7 @@
3131
32 function handleAttachment(filePath, handlerType)32 function handleAttachment(filePath, handlerType)
33 {33 {
34 mainStack.push(picker, {"url": filePath, "handler": handlerType});34 mainStack.addPageToCurrentColumn(previewerPage, picker, {"url": filePath, "handler": handlerType});
35 actionTriggered()35 actionTriggered()
36 }36 }
3737
@@ -45,23 +45,29 @@
45 previewerPage.handleAttachment(attachment.filePath, ContentHandler.Share)45 previewerPage.handleAttachment(attachment.filePath, ContentHandler.Share)
46 }46 }
4747
48 function backAction()48 header: PageHeader {
49 {49 id: pageHeader
50 mainStack.pop()50
51 property alias leadingActions: leadingBar.actions
52 property alias trailingActions: trailingBar.actions
53
54 title: previewerPage.title
55 leadingActionBar {
56 id: leadingBar
57 }
58
59 trailingActionBar {
60 id: trailingBar
61 }
51 }62 }
5263
53 title: ""
54 state: "default"64 state: "default"
55 states: [65 states: [
56 PageHeadState {66 State {
67 id: defaultState
57 name: "default"68 name: "default"
58 head: previewerPage.head69
59 backAction: Action {70 property list<QtObject> trailingActions: [
60 iconName: "back"
61 text: i18n.tr("Back")
62 onTriggered: previewerPage.backAction()
63 }
64 actions: [
65 Action {71 Action {
66 objectName: "saveButton"72 objectName: "saveButton"
67 text: i18n.tr("Save")73 text: i18n.tr("Save")
@@ -75,6 +81,11 @@
75 onTriggered: previewerPage.shareAttchment()81 onTriggered: previewerPage.shareAttchment()
76 }82 }
77 ]83 ]
84 PropertyChanges {
85 target: pageHeader
86
87 trailingActions: defaultState.trailingActions
88 }
78 }89 }
79 ]90 ]
8091
@@ -109,11 +120,11 @@
109120
110 onPeerSelected: {121 onPeerSelected: {
111 picker.curTransfer = peer.request();122 picker.curTransfer = peer.request();
112 mainStack.pop();123 mainStack.removePage(picker);
113 if (picker.curTransfer.state === ContentTransfer.InProgress)124 if (picker.curTransfer.state === ContentTransfer.InProgress)
114 picker.__exportItems(picker.url);125 picker.__exportItems(picker.url);
115 }126 }
116 onCancelPressed: mainStack.pop();127 onCancelPressed: mainStack.removePage(picker);
117 }128 }
118129
119 Connections {130 Connections {
120131
=== modified file 'src/qml/MMS/PreviewerImage.qml'
--- src/qml/MMS/PreviewerImage.qml 2016-02-01 23:18:57 +0000
+++ src/qml/MMS/PreviewerImage.qml 2016-02-01 23:18:57 +0000
@@ -25,6 +25,7 @@
25Previewer {25Previewer {
26 id: imagePreviewer26 id: imagePreviewer
2727
28 // FIXME: this won't work correctly in windowed mode
28 Component.onCompleted: application.fullscreen = true29 Component.onCompleted: application.fullscreen = true
29 Component.onDestruction: application.fullscreen = false30 Component.onDestruction: application.fullscreen = false
3031
3132
=== modified file 'src/qml/MMS/PreviewerMultipleContacts.qml'
--- src/qml/MMS/PreviewerMultipleContacts.qml 2015-11-19 00:34:45 +0000
+++ src/qml/MMS/PreviewerMultipleContacts.qml 2016-02-01 23:18:57 +0000
@@ -42,7 +42,12 @@
42 MultipleSelectionListView {42 MultipleSelectionListView {
43 id: contactList43 id: contactList
4444
45 anchors.fill: parent45 anchors {
46 top: root.header.bottom
47 bottom: parent.bottom
48 left: parent.left
49 right: parent.right
50 }
46 listModel: thumbnail.vcard.contacts51 listModel: thumbnail.vcard.contacts
47 listDelegate: ContactDelegate {52 listDelegate: ContactDelegate {
48 id: contactDelegate53 id: contactDelegate
@@ -51,7 +56,7 @@
51 property var contact: thumbnail.vcard.contacts[index]56 property var contact: thumbnail.vcard.contacts[index]
5257
53 onClicked: {58 onClicked: {
54 mainStack.push(sigleContatPreviewer, {'contact': contact})59 mainStack.addComponentToCurrentColumnSync(root, sigleContatPreviewer, {'contact': contact})
55 }60 }
56 }61 }
57 }62 }
5863
=== modified file 'src/qml/MMS/PreviewerVideo.qml'
--- src/qml/MMS/PreviewerVideo.qml 2016-02-01 23:18:57 +0000
+++ src/qml/MMS/PreviewerVideo.qml 2016-02-01 23:18:57 +0000
@@ -30,6 +30,7 @@
30 title: i18n.tr("Video Preview")30 title: i18n.tr("Video Preview")
31 clip: true31 clip: true
3232
33 // FIXME: this won't work correctly in windowed mode
33 Component.onCompleted: {34 Component.onCompleted: {
34 application.fullscreen = true35 application.fullscreen = true
35 // Load Video player after toggling fullscreen to reduce flickering36 // Load Video player after toggling fullscreen to reduce flickering
3637
=== modified file 'src/qml/MMSDelegate.qml'
--- src/qml/MMSDelegate.qml 2016-02-01 23:18:57 +0000
+++ src/qml/MMSDelegate.qml 2016-02-01 23:18:57 +0000
@@ -45,7 +45,7 @@
45 var properties = {}45 var properties = {}
46 properties["attachment"] = attachment.item.attachment46 properties["attachment"] = attachment.item.attachment
47 properties["thumbnail"] = attachment.item47 properties["thumbnail"] = attachment.item
48 mainStack.push(Qt.resolvedUrl(attachment.item.previewer), properties)48 mainStack.addFileToCurrentColumnSync(messages.basePage, Qt.resolvedUrl(attachment.item.previewer), properties)
49 }49 }
50 }50 }
5151
5252
=== modified file 'src/qml/MainPage.qml'
--- src/qml/MainPage.qml 2016-02-01 23:18:57 +0000
+++ src/qml/MainPage.qml 2016-02-01 23:18:57 +0000
@@ -23,29 +23,27 @@
23import Ubuntu.History 0.123import Ubuntu.History 0.1
24import "dateUtils.js" as DateUtils24import "dateUtils.js" as DateUtils
2525
26LocalPageWithBottomEdge {26Page {
27 id: mainPage27 id: mainPage
28 property alias selectionMode: threadList.isInSelectionMode28 property alias selectionMode: threadList.isInSelectionMode
29 property bool searching: false29 property bool searching: false
30 property bool isEmpty: threadCount == 0 && !threadModel.canFetchMore
30 property alias threadCount: threadList.count31 property alias threadCount: threadList.count
32 property alias displayedThreadIndex: threadList.currentIndex
33
34 property var _messagesPage: null
3135
32 function startSelection() {36 function startSelection() {
33 threadList.startSelection()37 threadList.startSelection()
34 }38 }
3539
36 state: selectionMode ? "select" : searching ? "search" : "default"
37 title: selectionMode ? " " : i18n.tr("Messages")
38 flickable: null
39
40 bottomEdgeEnabled: !selectionMode && !searching
41 bottomEdgeTitle: i18n.tr("+")
42 bottomEdgePageComponent: Messages { active: false }
43
44 TextField {40 TextField {
45 id: searchField41 id: searchField
46 objectName: "searchField"42 objectName: "searchField"
47 visible: mainPage.searching43 visible: mainPage.searching
48 anchors {44 anchors {
45 top: parent.top
46 topMargin: units.gu(1)
49 left: parent.left47 left: parent.left
50 right: parent.right48 right: parent.right
51 rightMargin: units.gu(2)49 rightMargin: units.gu(2)
@@ -60,11 +58,30 @@
60 }58 }
61 }59 }
6260
61 header: PageHeader {
62 id: pageHeader
63
64 property alias leadingActions: leadingBar.actions
65 property alias trailingActions: trailingBar.actions
66
67 title: i18n.tr("Messages")
68 flickable: threadList
69 leadingActionBar {
70 id: leadingBar
71 }
72
73 trailingActionBar {
74 id: trailingBar
75 }
76 }
77
63 states: [78 states: [
64 PageHeadState {79 State {
80 id: defaultState
65 name: "default"81 name: "default"
66 head: mainPage.head82 when: !searching && !selectionMode
67 actions: [83
84 property list<QtObject> trailingActions: [
68 Action {85 Action {
69 objectName: "searchAction"86 objectName: "searchAction"
70 iconName: "search"87 iconName: "search"
@@ -77,38 +94,76 @@
77 objectName: "settingsAction"94 objectName: "settingsAction"
78 text: i18n.tr("Settings")95 text: i18n.tr("Settings")
79 iconName: "settings"96 iconName: "settings"
80 onTriggered: pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))97 onTriggered: {
98 emptyStack()
99 pageStack.addFileToNextColumnSync(mainPage, Qt.resolvedUrl("SettingsPage.qml"))
100 }
101 },
102 Action {
103 objectName: "newMessageAction"
104 text: i18n.tr("New message")
105 iconName: "add"
106 visible: dualPanel
107 onTriggered: mainView.bottomEdge.commit()
81 }108 }
109
82 ]110 ]
111
112 PropertyChanges {
113 target: pageHeader
114 trailingActions: defaultState.trailingActions
115 leadingActions: []
116 }
83 },117 },
84 PageHeadState {118 State {
119 id: searchState
85 name: "search"120 name: "search"
86 head: mainPage.head121 when: searching
87 backAction: Action {122
88 objectName: "cancelSearch"123 property list<QtObject> leadingActions: [
89 visible: mainPage.searching124 Action {
90 iconName: "back"125 objectName: "cancelSearch"
91 text: i18n.tr("Cancel")126 visible: mainPage.searching
92 onTriggered: {127 iconName: "back"
93 searchField.text = ""128 text: i18n.tr("Cancel")
94 mainPage.searching = false129 onTriggered: {
130 searchField.text = ""
131 mainPage.searching = false
132 }
95 }133 }
134 ]
135
136 PropertyChanges {
137 target: pageHeader
138 contents: searchField
139 leadingActions: searchState.leadingActions
140 trailingActions: []
96 }141 }
97 contents: searchField
98 },142 },
99 PageHeadState {143 State {
100 name: "select"144 id: selectionState
101 head: mainPage.head145 name: "selection"
102 backAction: Action {146 when: selectionMode
103 objectName: "selectionModeCancelAction"147
104 iconName: "back"148 property list<QtObject> leadingActions: [
105 onTriggered: threadList.cancelSelection()149 Action {
106 }150 objectName: "selectionModeCancelAction"
107 actions: [151 iconName: "back"
152 onTriggered: threadList.cancelSelection()
153 }
154 ]
155
156 property list<QtObject> trailingActions: [
108 Action {157 Action {
109 objectName: "selectionModeSelectAllAction"158 objectName: "selectionModeSelectAllAction"
110 iconName: "select"159 iconName: "select"
111 onTriggered: threadList.selectAll()160 onTriggered: {
161 if (threadList.selectedItems.count === threadList.count) {
162 threadList.clearSelection()
163 } else {
164 threadList.selectAll()
165 }
166 }
112 },167 },
113 Action {168 Action {
114 objectName: "selectionModeDeleteAction"169 objectName: "selectionModeDeleteAction"
@@ -117,39 +172,18 @@
117 onTriggered: threadList.endSelection()172 onTriggered: threadList.endSelection()
118 }173 }
119 ]174 ]
175 PropertyChanges {
176 target: pageHeader
177 title: " "
178 leadingActions: selectionState.leadingActions
179 trailingActions: selectionState.trailingActions
180 }
120 }181 }
121 ]182 ]
122183
123 Item {184 EmptyState {
124 id: emptyStateScreen185 id: emptyStateScreen
125 anchors.left: parent.left186 visible: mainPage.isEmpty && !mainView.dualPanel
126 anchors.leftMargin: units.gu(6)
127 anchors.right: parent.right
128 anchors.rightMargin: units.gu(6)
129 height: childrenRect.height
130 anchors.verticalCenter: parent.verticalCenter
131 visible: threadCount == 0 && !threadModel.canFetchMore
132 Icon {
133 id: emptyStateIcon
134 anchors.top: emptyStateScreen.top
135 anchors.horizontalCenter: parent.horizontalCenter
136 height: units.gu(5)
137 width: height
138 opacity: 0.3
139 name: "message"
140 }
141 Label {
142 id: emptyStateLabel
143 anchors.top: emptyStateIcon.bottom
144 anchors.topMargin: units.gu(2)
145 anchors.left: parent.left
146 anchors.right: parent.right
147 text: i18n.tr("Compose a new message by swiping up from the bottom of the screen.")
148 color: "#5d5d5d"
149 fontSize: "x-large"
150 wrapMode: Text.WordWrap
151 horizontalAlignment: Text.AlignHCenter
152 }
153 }187 }
154188
155 Component {189 Component {
@@ -188,8 +222,18 @@
188 clip: true222 clip: true
189 cacheBuffer: threadList.height * 2223 cacheBuffer: threadList.height * 2
190 section.property: "eventDate"224 section.property: "eventDate"
225 currentIndex: -1
191 //spacing: searchField.text === "" ? units.gu(-2) : 0226 //spacing: searchField.text === "" ? units.gu(-2) : 0
192 section.delegate: searching && searchField.text !== "" ? null : sectionDelegate227 section.delegate: searching && searchField.text !== "" ? null : sectionDelegate
228 header: ListItem.Standard {
229 id: newItem
230 height: mainView.bottomEdge.status === BottomEdge.Committed ? units.gu(10) : 0
231 text: i18n.tr("New message")
232 iconName: "message-new"
233 iconFrame: false
234 selected: true
235 }
236
193 listDelegate: ThreadDelegate {237 listDelegate: ThreadDelegate {
194 id: threadDelegate238 id: threadDelegate
195 // FIXME: find a better unique name239 // FIXME: find a better unique name
@@ -201,7 +245,15 @@
201 }245 }
202 height: units.gu(8)246 height: units.gu(8)
203 selectionMode: threadList.isInSelectionMode247 selectionMode: threadList.isInSelectionMode
204 selected: threadList.isSelected(threadDelegate)248 selected: {
249 if (selectionMode) {
250 return threadList.isSelected(threadDelegate)
251 } else {
252 // FIXME: there might be a better way of doing this
253 return index === threadList.currentIndex && mainView.bottomEdge.status !== BottomEdge.Committed
254 }
255 }
256
205 searchTerm: mainPage.searching ? searchField.text : ""257 searchTerm: mainPage.searching ? searchField.text : ""
206 onItemClicked: {258 onItemClicked: {
207 if (threadList.isInSelectionMode) {259 if (threadList.isInSelectionMode) {
@@ -222,7 +274,11 @@
222 if (displayedEvent != null) {274 if (displayedEvent != null) {
223 properties["scrollToEventId"] = displayedEvent.eventId275 properties["scrollToEventId"] = displayedEvent.eventId
224 }276 }
225 mainStack.push(Qt.resolvedUrl("Messages.qml"), properties)277 emptyStack()
278 mainStack.addComponentToNextColumnSync(mainPage, messagesWithBottomEdge, properties)
279
280 // mark this item as current
281 threadList.currentIndex = index
226 }282 }
227 }283 }
228 onItemPressAndHold: {284 onItemPressAndHold: {
@@ -242,6 +298,13 @@
242 mainView.removeThreads(threadsToRemove);298 mainView.removeThreads(threadsToRemove);
243 }299 }
244 }300 }
301
302 Binding {
303 target: threadList
304 property: 'contentY'
305 value: -threadList.headerItem.height
306 when: mainView.composingNewMessage
307 }
245 }308 }
246309
247 KeyboardRectangle {310 KeyboardRectangle {
@@ -252,4 +315,12 @@
252 flickableItem: threadList315 flickableItem: threadList
253 align: Qt.AlignTrailing316 align: Qt.AlignTrailing
254 }317 }
318
319 Loader {
320 id: bottomEdgeLoader
321 active: !selectionMode && !searching && !mainView.dualPanel
322 sourceComponent: MessagingBottomEdge {
323 parent: mainPage
324 }
325 }
255}326}
256327
=== modified file 'src/qml/Messages.qml'
--- src/qml/Messages.qml 2016-02-01 23:18:57 +0000
+++ src/qml/Messages.qml 2016-02-01 23:18:57 +0000
@@ -63,6 +63,19 @@
63 property QtObject presenceRequest: presenceItem63 property QtObject presenceRequest: presenceItem
64 property var accountsModel: getAccountsModel()64 property var accountsModel: getAccountsModel()
65 property alias oskEnabled: keyboard.oskEnabled65 property alias oskEnabled: keyboard.oskEnabled
66 property bool isReady: false
67 property string firstRecipientAlias: ((contactWatcher.isUnknown &&
68 contactWatcher.isInteractive) ||
69 contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias
70
71
72 // 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
73 property var basePage: messages
74
75 property bool startedFromBottomEdge: false
76
77 signal ready
78 signal cancel
6679
67 function getAccountsModel() {80 function getAccountsModel() {
68 var accounts = []81 var accounts = []
@@ -221,99 +234,6 @@
221 return thread234 return thread
222 }235 }
223236
224 Connections {
225 target: telepathyHelper
226 onSetupReady: {
227 // force reevaluation
228 messages.account = Qt.binding(getCurrentAccount)
229 messages.phoneAccount = Qt.binding(isPhoneAccount)
230 head.sections.model = Qt.binding(getSectionsModel)
231 head.sections.selectedIndex = Qt.binding(getSelectedIndex)
232 }
233 }
234
235
236 Connections {
237 target: chatManager
238 onChatEntryCreated: {
239 // TODO: track using chatId and not participants
240 if (accountId == account.accountId &&
241 firstParticipant && participants[0] == firstParticipant.identifier) {
242 messages.chatEntry = chatEntry
243 }
244 }
245 onChatsChanged: {
246 for (var i in chatManager.chats) {
247 var chat = chatManager.chats[i]
248 // TODO: track using chatId and not participants
249 if (chat.account.accountId == account.accountId &&
250 firstParticipant && chat.participants[0] == firstParticipant.identifier) {
251 messages.chatEntry = chat
252 return
253 }
254 }
255 messages.chatEntry = null
256 }
257 }
258
259 Timer {
260 id: typingTimer
261 interval: 6000
262 onTriggered: {
263 messages.userTyping = false;
264 }
265 }
266
267 Repeater {
268 model: messages.chatEntry ? messages.chatEntry.chatStates : null
269 Item {
270 function processChatState() {
271 if (modelData.state == ChatEntry.ChannelChatStateComposing) {
272 messages.userTyping = true
273 typingTimer.start()
274 } else {
275 messages.userTyping = false
276 }
277 }
278 Component.onCompleted: processChatState()
279 Connections {
280 target: modelData
281 onStateChanged: processChatState()
282 }
283 }
284 }
285
286 MessagesHeader {
287 id: header
288 width: parent ? parent.width - units.gu(2) : undefined
289 height: units.gu(5)
290 title: messages.title
291 subtitle: {
292 if (userTyping) {
293 return i18n.tr("Typing..")
294 }
295 switch (presenceRequest.type) {
296 case PresenceRequest.PresenceTypeAvailable:
297 return i18n.tr("Online")
298 case PresenceRequest.PresenceTypeOffline:
299 return i18n.tr("Offline")
300 case PresenceRequest.PresenceTypeAway:
301 return i18n.tr("Away")
302 case PresenceRequest.PresenceTypeBusy:
303 return i18n.tr("Busy")
304 default:
305 return ""
306 }
307 }
308 visible: true
309 }
310
311 head {
312 id: head
313 sections.model: getSectionsModel()
314 sections.selectedIndex: getSelectedIndex()
315 }
316
317 function sendMessageNetworkCheck() {237 function sendMessageNetworkCheck() {
318 if (messages.account.simLocked) {238 if (messages.account.simLocked) {
319 Qt.inputMethod.hide()239 Qt.inputMethod.hide()
@@ -363,7 +283,7 @@
363 if (event.senderId == "self" && event.accountId != messages.account.accountId) {283 if (event.senderId == "self" && event.accountId != messages.account.accountId) {
364 var tmpAccount = telepathyHelper.accountForId(event.accountId)284 var tmpAccount = telepathyHelper.accountForId(event.accountId)
365 if (!tmpAccount || (tmpAccount.type == AccountEntry.MultimediaAccount && messages.account.type == AccountEntry.PhoneAccount)) {285 if (!tmpAccount || (tmpAccount.type == AccountEntry.MultimediaAccount && messages.account.type == AccountEntry.PhoneAccount)) {
366 // we don't add the information event if the last outgoing message 286 // we don't add the information event if the last outgoing message
367 // was a fallback to a multimedia service287 // was a fallback to a multimedia service
368 break;288 break;
369 }289 }
@@ -425,7 +345,7 @@
425 var isSelfContactKnown = account.selfContactId != ""345 var isSelfContactKnown = account.selfContactId != ""
426 if (isMmsGroupChat && !isSelfContactKnown) {346 if (isMmsGroupChat && !isSelfContactKnown) {
427 // TODO: inform the user to enter the phone number of the selected sim card manually347 // TODO: inform the user to enter the phone number of the selected sim card manually
428 // and use it in the telepathy-ofono account as selfContactId. 348 // and use it in the telepathy-ofono account as selfContactId.
429 return false349 return false
430 }350 }
431 var fallbackAccountId = chatManager.sendMessage(messages.account.accountId, participantIds, text, attachments, properties)351 var fallbackAccountId = chatManager.sendMessage(messages.account.accountId, participantIds, text, attachments, properties)
@@ -448,102 +368,6 @@
448 return true368 return true
449 }369 }
450370
451 PresenceRequest {
452 id: presenceItem
453 accountId: {
454 // if this is a regular sms chat, try requesting the presence on
455 // a multimedia account
456 if (!account) {
457 return ""
458 }
459 if (account.type == AccountEntry.PhoneAccount) {
460 for (var i in telepathyHelper.accounts) {
461 var tmpAccount = telepathyHelper.accounts[i]
462 if (tmpAccount.type == AccountEntry.MultimediaAccount) {
463 return tmpAccount.accountId
464 }
465 }
466 return ""
467 }
468 return account.accountId
469 }
470 // we just request presence on 1-1 chats
471 identifier: participants.length == 1 ? participants[0].identifier : ""
472 }
473
474 // this is necessary to automatically update the view when the
475 // default account changes in system settings
476 Connections {
477 target: mainView
478 onAccountChanged: {
479 if (!messages.phoneAccount) {
480 return
481 }
482 messages.account = mainView.account
483 }
484 }
485
486 ActivityIndicator {
487 id: activityIndicator
488 anchors {
489 verticalCenter: parent.verticalCenter
490 horizontalCenter: parent.horizontalCenter
491 }
492 running: isSearching
493 visible: running
494 }
495
496 flickable: null
497
498 property bool isReady: false
499 signal ready
500 onReady: {
501 isReady = true
502 if (participants.length === 0 && keyboardFocus)
503 multiRecipient.forceFocus()
504 }
505
506 property string firstRecipientAlias: ((contactWatcher.isUnknown &&
507 contactWatcher.isInteractive) ||
508 contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias
509 title: {
510 if (selectionMode || participants.length == 0) {
511 return " "
512 }
513
514 if (landscape) {
515 return ""
516 }
517 if (participants.length > 0) {
518 if (participants.length == 1) {
519 return firstRecipientAlias
520 } else {
521 // TRANSLATORS: %1 refers to the number of participants in a group chat
522 return i18n.tr("Group (%1)").arg(participants.length)
523 }
524 }
525 return i18n.tr("New Message")
526 }
527
528 Component.onCompleted: {
529 if (messages.accountId !== "") {
530 var account = telepathyHelper.accountForId(messages.accountId)
531 if (account && account.type == AccountEntry.MultimediaAccount) {
532 // fallback the first available phone account
533 if (telepathyHelper.phoneAccounts.length > 0) {
534 messages.accountId = telepathyHelper.phoneAccounts[0].accountId
535 }
536 }
537 }
538 composeBar.addAttachments(sharedAttachmentsTransfer)
539 }
540
541 onActiveChanged: {
542 if (active && (eventModel.count > 0)){
543 swipeItemDemo.enable()
544 }
545 }
546
547 function updateFilters(accounts, participants, reload, threads) {371 function updateFilters(accounts, participants, reload, threads) {
548 if (participants.length == 0 || accounts.length == 0) {372 if (participants.length == 0 || accounts.length == 0) {
549 return null373 return null
@@ -603,8 +427,356 @@
603 return eventModel.markEventAsRead(accountId, threadId, eventId, type);427 return eventModel.markEventAsRead(accountId, threadId, eventId, type);
604 }428 }
605429
430 header: PageHeader {
431 id: pageHeader
432
433 property alias leadingActions: leadingBar.actions
434 property alias trailingActions: trailingBar.actions
435
436 property list<QtObject> bottomEdgeLeadingActions: [
437 Action {
438 id: backAction
439
440 objectName: "cancel"
441 name: "cancel"
442 text: i18n.tr("Cancel")
443 iconName: "down"
444 shortcut: "Esc"
445 onTriggered: {
446 messages.cancel()
447 }
448 }
449 ]
450
451 property list<QtObject> singlePanelLeadingActions: [
452 Action {
453 id: singlePanelBackAction
454 objectName: "back"
455 name: "cancel"
456 text: i18n.tr("Cancel")
457 iconName: "back"
458 shortcut: "Esc"
459 onTriggered: {
460 // emptyStack will make sure the page gets removed.
461 mainView.emptyStack()
462 }
463 }
464 ]
465
466 title: {
467 if (landscape) {
468 return ""
469 }
470
471 if (participants.length == 1) {
472 return firstRecipientAlias
473 }
474
475 return i18n.tr("New Message")
476 }
477 flickable: null
478
479 Sections {
480 id: sections
481 anchors {
482 left: parent.left
483 leftMargin: units.gu(2)
484 bottom: parent.bottom
485 }
486 model: getSectionsModel()
487 selectedIndex: getSelectedIndex()
488 onSelectedIndexChanged: {
489 if (selectedIndex >= 0) {
490 messages.account = messages.accountsModel[selectedIndex]
491 }
492 }
493 }
494
495 extension: sections.model.length > 1 ? sections : null
496
497 leadingActionBar {
498 id: leadingBar
499
500 states: [
501 State {
502 name: "bottomEdgeBack"
503 when: startedFromBottomEdge
504 PropertyChanges {
505 target: leadingBar
506 actions: pageHeader.bottomEdgeLeadingActions
507 }
508 },
509 State {
510 name: "singlePanelBack"
511 when: !mainView.dualPanel && !startedFromBottomEdge
512 PropertyChanges {
513 target: leadingBar
514 actions: pageHeader.singlePanelLeadingActions
515 }
516 }
517
518 ]
519 }
520
521 trailingActionBar {
522 id: trailingBar
523 }
524 }
525
526 states: [
527 State {
528 id: selectionState
529 name: "selection"
530 when: selectionMode
531
532 property list<QtObject> leadingActions: [
533 Action {
534 objectName: "selectionModeCancelAction"
535 iconName: "back"
536 onTriggered: messageList.cancelSelection()
537 }
538 ]
539
540 property list<QtObject> trailingActions: [
541 Action {
542 objectName: "selectionModeSelectAllAction"
543 iconName: "select"
544 onTriggered: {
545 if (messageList.selectedItems.count === messageList.count) {
546 messageList.clearSelection()
547 } else {
548 messageList.selectAll()
549 }
550 }
551 },
552 Action {
553 objectName: "selectionModeDeleteAction"
554 enabled: messageList.selectedItems.count > 0
555 iconName: "delete"
556 onTriggered: messageList.endSelection()
557 }
558 ]
559
560 PropertyChanges {
561 target: pageHeader
562 title: " "
563 leadingActions: selectionState.leadingActions
564 trailingActions: selectionState.trailingActions
565 }
566 },
567 State {
568 id: groupChatState
569 name: "groupChat"
570 when: groupChat
571
572 property list<QtObject> trailingActions: [
573 Action {
574 objectName: "groupChatAction"
575 iconName: "contact-group"
576 onTriggered: PopupUtils.open(participantsPopover, pageHeader)
577 }
578 ]
579
580 PropertyChanges {
581 target: pageHeader
582 // TRANSLATORS: %1 refers to the number of participants in a group chat
583 title: i18n.tr("Group (%1)").arg(participants.length)
584 contents: headerContents
585 trailingActions: groupChatState.trailingActions
586 }
587 },
588 State {
589 id: unknownContactState
590 name: "unknownContact"
591 when: participants.length == 1 && contactWatcher.isUnknown
592
593 property list<QtObject> trailingActions: [
594 Action {
595 objectName: "contactCallAction"
596 visible: participants.length == 1 && contactWatcher.interactive
597 iconName: "call-start"
598 text: i18n.tr("Call")
599 onTriggered: {
600 Qt.inputMethod.hide()
601 // FIXME: support other things than just phone numbers
602 Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
603 }
604 },
605 Action {
606 objectName: "addContactAction"
607 visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive
608 iconName: "contact-new"
609 text: i18n.tr("Add")
610 onTriggered: {
611 Qt.inputMethod.hide()
612 // FIXME: support other things than just phone numbers
613 mainView.addPhoneToContact(messages, "", contactWatcher.identifier, null, null)
614 }
615 }
616 ]
617 PropertyChanges {
618 target: pageHeader
619 contents: headerContents
620 trailingActions: unknownContactState.trailingActions
621 }
622 },
623 State {
624 id: newMessageState
625 name: "newMessage"
626 when: participants.length === 0
627
628 property list<QtObject> trailingActions: [
629 Action {
630 objectName: "contactList"
631 iconName: "contact"
632 onTriggered: {
633 Qt.inputMethod.hide()
634 mainStack.addFileToCurrentColumnSync(messages.basePage, Qt.resolvedUrl("NewRecipientPage.qml"), {"multiRecipient": multiRecipient})
635 }
636 }
637
638 ]
639
640 property Item contents: MultiRecipientInput {
641 id: multiRecipient
642 objectName: "multiRecipient"
643 enabled: visible
644 anchors {
645 left: parent ? parent.left : undefined
646 right: parent ? parent.right : undefined
647 rightMargin: units.gu(2)
648 top: parent ? parent.top: undefined
649 topMargin: units.gu(1)
650 }
651 focus: true
652
653 Connections {
654 target: mainView.bottomEdge
655 onStatusChanged: {
656 if (mainView.bottomEdge.status === BottomEdge.Committed) {
657 multiRecipient.forceFocus()
658 }
659 }
660 }
661 }
662
663 PropertyChanges {
664 target: pageHeader
665 title: " "
666 trailingActions: newMessageState.trailingActions
667 contents: newMessageState.contents
668 }
669 },
670 State {
671 id: knownContactState
672 name: "knownContact"
673 when: participants.length == 1 && !contactWatcher.isUnknown
674 property list<QtObject> trailingActions: [
675 Action {
676 objectName: "contactCallKnownAction"
677 visible: participants.length == 1 && messages.phoneAccount
678 iconName: "call-start"
679 text: i18n.tr("Call")
680 onTriggered: {
681 Qt.inputMethod.hide()
682 // FIXME: support other things than just phone numbers
683 Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
684 }
685 },
686 Action {
687 objectName: "contactProfileAction"
688 visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount
689 iconSource: "image://theme/contact"
690 text: i18n.tr("Contact")
691 onTriggered: {
692 mainView.showContactDetails(messages.basePage, contactWatcher.contactId, null, null)
693 }
694 }
695 ]
696 PropertyChanges {
697 target: pageHeader
698 contents: headerContents
699 trailingActions: knownContactState.trailingActions
700 }
701 }
702 ]
703
704 Component.onCompleted: {
705 if (messages.accountId !== "") {
706 var account = telepathyHelper.accountForId(messages.accountId)
707 if (account && account.type == AccountEntry.MultimediaAccount) {
708 // fallback the first available phone account
709 if (telepathyHelper.phoneAccounts.length > 0) {
710 messages.accountId = telepathyHelper.phoneAccounts[0].accountId
711 }
712 }
713 }
714 composeBar.addAttachments(sharedAttachmentsTransfer)
715 }
716
717 Component.onDestruction: {
718 if (!mainView.dualPanel && !startedFromBottomEdge) {
719 mainPage.displayedThreadIndex = -1
720 }
721 }
722
723 onReady: {
724 isReady = true
725 if (participants.length === 0 && keyboardFocus)
726 multiRecipient.forceFocus()
727 }
728
729 onActiveChanged: {
730 if (active && (eventModel.count > 0)){
731 swipeItemDemo.enable()
732 }
733 }
734
735 Connections {
736 target: telepathyHelper
737 onSetupReady: {
738 // force reevaluation
739 messages.account = Qt.binding(getCurrentAccount)
740 messages.phoneAccount = Qt.binding(isPhoneAccount)
741 head.sections.model = Qt.binding(getSectionsModel)
742 head.sections.selectedIndex = Qt.binding(getSelectedIndex)
743 }
744 }
745
746 Connections {
747 target: chatManager
748 onChatEntryCreated: {
749 // TODO: track using chatId and not participants
750 if (accountId == account.accountId &&
751 firstParticipant && participants[0] == firstParticipant.identifier) {
752 messages.chatEntry = chatEntry
753 }
754 }
755 onChatsChanged: {
756 for (var i in chatManager.chats) {
757 var chat = chatManager.chats[i]
758 // TODO: track using chatId and not participants
759 if (chat.account.accountId == account.accountId &&
760 firstParticipant && chat.participants[0] == firstParticipant.identifier) {
761 messages.chatEntry = chat
762 return
763 }
764 }
765 messages.chatEntry = null
766 }
767 }
768
769 // this is necessary to automatically update the view when the
770 // default account changes in system settings
606 Connections {771 Connections {
607 target: mainView772 target: mainView
773 onAccountChanged: {
774 if (!messages.phoneAccount) {
775 return
776 }
777 messages.account = mainView.account
778 }
779
608 onApplicationActiveChanged: {780 onApplicationActiveChanged: {
609 if (mainView.applicationActive) {781 if (mainView.applicationActive) {
610 for (var i in pendingEventsToMarkAsRead) {782 for (var i in pendingEventsToMarkAsRead) {
@@ -616,6 +788,91 @@
616 }788 }
617 }789 }
618790
791 Timer {
792 id: typingTimer
793 interval: 6000
794 onTriggered: {
795 messages.userTyping = false;
796 }
797 }
798
799 Repeater {
800 model: messages.chatEntry ? messages.chatEntry.chatStates : null
801 Item {
802 function processChatState() {
803 if (modelData.state == ChatEntry.ChannelChatStateComposing) {
804 messages.userTyping = true
805 typingTimer.start()
806 } else {
807 messages.userTyping = false
808 }
809 }
810 Component.onCompleted: processChatState()
811 Connections {
812 target: modelData
813 onStateChanged: processChatState()
814 }
815 }
816 }
817
818 MessagesHeader {
819 id: headerContents
820 width: parent ? parent.width - units.gu(2) : undefined
821 height: units.gu(5)
822 title: pageHeader.title
823 subtitle: {
824 if (userTyping) {
825 return i18n.tr("Typing..")
826 }
827 switch (presenceRequest.type) {
828 case PresenceRequest.PresenceTypeAvailable:
829 return i18n.tr("Online")
830 case PresenceRequest.PresenceTypeOffline:
831 return i18n.tr("Offline")
832 case PresenceRequest.PresenceTypeAway:
833 return i18n.tr("Away")
834 case PresenceRequest.PresenceTypeBusy:
835 return i18n.tr("Busy")
836 default:
837 return ""
838 }
839 }
840 visible: true
841 }
842
843 PresenceRequest {
844 id: presenceItem
845 accountId: {
846 // if this is a regular sms chat, try requesting the presence on
847 // a multimedia account
848 if (!account) {
849 return ""
850 }
851 if (account.type == AccountEntry.PhoneAccount) {
852 for (var i in telepathyHelper.accounts) {
853 var tmpAccount = telepathyHelper.accounts[i]
854 if (tmpAccount.type == AccountEntry.MultimediaAccount) {
855 return tmpAccount.accountId
856 }
857 }
858 return ""
859 }
860 return account.accountId
861 }
862 // we just request presence on 1-1 chats
863 identifier: participants.length == 1 ? participants[0].identifier : ""
864 }
865
866 ActivityIndicator {
867 id: activityIndicator
868 anchors {
869 verticalCenter: parent.verticalCenter
870 horizontalCenter: parent.horizontalCenter
871 }
872 running: isSearching
873 visible: running
874 }
875
619 Component {876 Component {
620 id: participantsPopover877 id: participantsPopover
621878
@@ -678,13 +935,6 @@
678 }935 }
679 }936 }
680937
681 Connections {
682 target: messages.head.sections
683 onSelectedIndexChanged: {
684 messages.account = messages.accountsModel[head.sections.selectedIndex]
685 }
686 }
687
688 Loader {938 Loader {
689 id: searchListLoader939 id: searchListLoader
690940
@@ -696,7 +946,7 @@
696 visible: source != ""946 visible: source != ""
697 anchors {947 anchors {
698 top: parent.top948 top: parent.top
699 topMargin: units.gu(2)949 topMargin: header.height + units.gu(2)
700 left: parent.left950 left: parent.left
701 right: parent.right951 right: parent.right
702 bottom: composeBar.top952 bottom: composeBar.top
@@ -756,156 +1006,6 @@
756 addressableFields: messages.account ? messages.account.addressableVCardFields : ["tel"] // just to have a fallback there1006 addressableFields: messages.account ? messages.account.addressableVCardFields : ["tel"] // just to have a fallback there
757 }1007 }
7581008
759 Action {
760 id: backButton
761 objectName: "backButton"
762 iconName: "back"
763 onTriggered: {
764 if (typeof mainPage !== 'undefined') {
765 mainPage.temporaryProperties = null
766 }
767 mainStack.pop()
768 }
769 }
770
771 states: [
772 PageHeadState {
773 name: "selection"
774 head: messages.head
775 when: selectionMode
776
777 backAction: Action {
778 objectName: "selectionModeCancelAction"
779 iconName: "back"
780 onTriggered: messageList.cancelSelection()
781 }
782
783 actions: [
784 Action {
785 objectName: "selectionModeSelectAllAction"
786 iconName: "select"
787 onTriggered: {
788 if (messageList.selectedItems.count === messageList.count) {
789 messageList.clearSelection()
790 } else {
791 messageList.selectAll()
792 }
793 }
794 },
795 Action {
796 objectName: "selectionModeDeleteAction"
797 enabled: messageList.selectedItems.count > 0
798 iconName: "delete"
799 onTriggered: messageList.endSelection()
800 }
801 ]
802 },
803 PageHeadState {
804 name: "groupChat"
805 head: messages.head
806 when: groupChat
807 contents: header
808 backAction: backButton
809
810 actions: [
811 Action {
812 objectName: "groupChatAction"
813 iconName: "contact-group"
814 onTriggered: PopupUtils.open(participantsPopover, screenTop)
815 }
816 ]
817 },
818 PageHeadState {
819 name: "unknownContact"
820 head: messages.head
821 when: participants.length == 1 && contactWatcher.isUnknown
822 backAction: backButton
823 contents: header
824
825 actions: [
826 Action {
827 objectName: "contactCallAction"
828 visible: participants.length == 1 && contactWatcher.interactive
829 iconName: "call-start"
830 text: i18n.tr("Call")
831 onTriggered: {
832 Qt.inputMethod.hide()
833 // FIXME: support other things than just phone numbers
834 Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
835 }
836 },
837 Action {
838 objectName: "addContactAction"
839 visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive
840 iconName: "contact-new"
841 text: i18n.tr("Add")
842 onTriggered: {
843 Qt.inputMethod.hide()
844 // FIXME: support other things than just phone numbers
845 mainView.addPhoneToContact("", contactWatcher.identifier, null, null)
846 }
847 }
848 ]
849 },
850 PageHeadState {
851 name: "newMessage"
852 head: messages.head
853 when: participants.length === 0 && isReady
854 backAction: backButton
855 actions: [
856 Action {
857 objectName: "contactList"
858 iconName: "contact"
859 onTriggered: {
860 Qt.inputMethod.hide()
861 mainStack.push(Qt.resolvedUrl("NewRecipientPage.qml"), {"multiRecipient": multiRecipient, "parentPage": messages})
862 }
863 }
864
865 ]
866
867 contents: MultiRecipientInput {
868 id: multiRecipient
869 objectName: "multiRecipient"
870 enabled: visible
871 anchors {
872 left: parent ? parent.left : undefined
873 right: parent ? parent.right : undefined
874 rightMargin: units.gu(2)
875 }
876 }
877 },
878 PageHeadState {
879 name: "knownContact"
880 head: messages.head
881 when: participants.length == 1 && !contactWatcher.isUnknown
882 backAction: backButton
883 contents: header
884 actions: [
885 Action {
886 objectName: "contactCallKnownAction"
887 visible: participants.length == 1 && messages.phoneAccount
888 iconName: "call-start"
889 text: i18n.tr("Call")
890 onTriggered: {
891 Qt.inputMethod.hide()
892 // FIXME: support other things than just phone numbers
893 Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier))
894 }
895 },
896 Action {
897 objectName: "contactProfileAction"
898 visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount
899 iconSource: "image://theme/contact"
900 text: i18n.tr("Contact")
901 onTriggered: {
902 mainView.showContactDetails(contactWatcher.contactId, null, null)
903 }
904 }
905 ]
906 }
907 ]
908
909 HistoryEventModel {1009 HistoryEventModel {
910 id: eventModel1010 id: eventModel
911 type: HistoryThreadModel.EventTypeText1011 type: HistoryThreadModel.EventTypeText
@@ -958,7 +1058,7 @@
958 Item {1058 Item {
959 id: screenTop1059 id: screenTop
960 anchors {1060 anchors {
961 top: parent.top1061 top: pageHeader.bottom
962 left: parent.left1062 left: parent.left
963 right: parent.right1063 right: parent.right
964 }1064 }
@@ -1115,4 +1215,6 @@
1115 }1215 }
1116 }1216 }
1117 }1217 }
1218
1219 // FIXME: we need a bottom edge here somehow
1118}1220}
11191221
=== modified file 'src/qml/MessagesHeader.qml'
--- src/qml/MessagesHeader.qml 2016-02-01 23:18:57 +0000
+++ src/qml/MessagesHeader.qml 2016-02-01 23:18:57 +0000
@@ -26,7 +26,12 @@
26 property string title: ""26 property string title: ""
27 property string subtitle: ""27 property string subtitle: ""
2828
29 height: units.gu(7)29 height: units.gu(8)
30
31 anchors {
32 top: parent.top
33 topMargin: units.gu(1)
34 }
3035
31 Behavior on height {36 Behavior on height {
32 UbuntuNumberAnimation {}37 UbuntuNumberAnimation {}
@@ -50,7 +55,7 @@
50 }55 }
51 verticalAlignment: Text.AlignVCenter56 verticalAlignment: Text.AlignVCenter
5257
53 font.pixelSize: FontUtils.sizeToPixels("x-large")58 font.pixelSize: FontUtils.sizeToPixels("large")
54 elide: Text.ElideRight59 elide: Text.ElideRight
55 text: title60 text: title
56 }61 }
5762
=== added file 'src/qml/MessagingBottomEdge.qml'
--- src/qml/MessagingBottomEdge.qml 1970-01-01 00:00:00 +0000
+++ src/qml/MessagingBottomEdge.qml 2016-02-01 23:18:57 +0000
@@ -0,0 +1,43 @@
1import QtQuick 2.0
2import Ubuntu.Components 1.3
3
4BottomEdge {
5 id: bottomEdge
6
7 function commitWithProperties(properties) {
8 _realPage = messagesComponent.createObject(null, properties)
9 commit()
10 }
11
12 property var _realPage: null
13
14 height: parent ? parent.height : 0
15 hint.text: i18n.tr("+")
16 contentComponent: Item {
17 id: pageContent
18 implicitWidth: bottomEdge.width
19 implicitHeight: bottomEdge.height
20 children: bottomEdge._realPage
21 }
22
23 Component.onCompleted: {
24 mainView.bottomEdge = bottomEdge
25 _realPage = messagesComponent.createObject(null)
26 }
27
28 onCollapseCompleted: {
29 _realPage = messagesComponent.createObject(null)
30 }
31
32 Component {
33 id: messagesComponent
34
35 Messages {
36 anchors.fill: parent
37 onCancel: bottomEdge.collapse()
38 basePage: bottomEdge.parent
39 startedFromBottomEdge: true
40 }
41 }
42
43}
044
=== modified file 'src/qml/MessagingContactEditorPage.qml'
--- src/qml/MessagingContactEditorPage.qml 2016-01-06 18:29:47 +0000
+++ src/qml/MessagingContactEditorPage.qml 2016-02-01 23:18:57 +0000
@@ -52,8 +52,12 @@
5252
53 onContactSaved: {53 onContactSaved: {
54 if (root.contactListPage) {54 if (root.contactListPage) {
55 root.contactListPage.moveListToContact(contact)55 if (root.contactListPage.phoneToAdd !== "") {
56 root.contactListPage.phoneToAdd = ""56 mainStack.removePage(root.contactListPage)
57 } else {
58 root.contactListPage.moveListToContact(contact)
59 root.contactListPage.phoneToAdd = ""
60 }
57 }61 }
58 }62 }
59}63}
6064
=== modified file 'src/qml/MessagingContactViewPage.qml'
--- src/qml/MessagingContactViewPage.qml 2016-01-06 18:29:47 +0000
+++ src/qml/MessagingContactViewPage.qml 2016-02-01 23:18:57 +0000
@@ -42,12 +42,13 @@
42 var newDetail = Qt.createQmlObject(detailSourceTemplate, contact)42 var newDetail = Qt.createQmlObject(detailSourceTemplate, contact)
43 if (newDetail) {43 if (newDetail) {
44 contact.addDetail(newDetail)44 contact.addDetail(newDetail)
45 pageStack.push(root.contactEditorPageURL,45 mainStack.addPageToCurrentColumn(root,
46 { model: root.model,46 root.contactEditorPageURL,
47 contact: contact,47 { model: root.model,
48 initialFocusSection: "phones",48 contact: contact,
49 newDetails: [newDetail],49 initialFocusSection: "phones",
50 contactListPage: root.contactListPage })50 newDetails: [newDetail],
51 contactListPage: root.contactListPage })
51 root.addPhoneToContact = ""52 root.addPhoneToContact = ""
52 } else {53 } else {
53 console.warn("Fail to create phone number detail")54 console.warn("Fail to create phone number detail")
@@ -61,7 +62,7 @@
61 iconName: "share"62 iconName: "share"
62 visible: root.editable63 visible: root.editable
63 onTriggered: {64 onTriggered: {
64 pageStack.push(contactShareComponent,65 pageStack.addComponentToCurrentColumnSync(root, contactShareComponent,
65 { contactModel: root.model,66 { contactModel: root.model,
66 contacts: [root.contact] })67 contacts: [root.contact] })
67 }68 }
@@ -72,7 +73,7 @@
72 iconName: "edit"73 iconName: "edit"
73 visible: root.editable74 visible: root.editable
74 onTriggered: {75 onTriggered: {
75 pageStack.push(contactEditorPageURL,76 pageStack.addFileToCurrentColumnSync(root, contactEditorPageURL,
76 { model: root.model,77 { model: root.model,
77 contact: root.contact,78 contact: root.contact,
78 contactListPage: root.contactListPage })79 contactListPage: root.contactListPage })
@@ -122,9 +123,9 @@
122 } else {123 } else {
123 Qt.openUrlExternally(("%1:%2").arg(action).arg(detail.value(0)))124 Qt.openUrlExternally(("%1:%2").arg(action).arg(detail.value(0)))
124 }125 }
125 pageStack.pop()126 pageStack.removePage(root)
126 }127 }
127 onContactRemoved: pageStack.pop()128 onContactRemoved: pageStack.removePage(root)
128 onContactFetched: {129 onContactFetched: {
129 root.contact = contact130 root.contact = contact
130 if (root.active && root.addPhoneToContact != "") {131 if (root.active && root.addPhoneToContact != "") {
131132
=== added file 'src/qml/MessagingPageLayout.qml'
--- src/qml/MessagingPageLayout.qml 1970-01-01 00:00:00 +0000
+++ src/qml/MessagingPageLayout.qml 2016-02-01 23:18:57 +0000
@@ -0,0 +1,64 @@
1import QtQuick 2.0
2import Ubuntu.Components 1.3
3
4AdaptivePageLayout {
5 id: layout
6 property var _pagesToRemove: []
7
8 function deleteInstances() {
9 for (var i in _pagesToRemove) {
10 if (_pagesToRemove[i].destroy) {
11 _pagesToRemove[i].destroy()
12 }
13 }
14 _pagesToRemove = []
15 }
16
17 function removePage(page) {
18 // check if this page was allocated dynamically and then remove it
19 for (var i in _pagesToRemove) {
20 if (_pagesToRemove[i] == page) {
21 _pagesToRemove[i].destroy()
22 _pagesToRemove.splice(i, 1)
23 break
24 }
25 }
26 removePages(page)
27 }
28
29 function addFileToNextColumnSync(parentObject, resolvedUrl, properties) {
30 if (typeof(properties) === 'undefined') {
31 properties = {}
32 }
33 var page = Qt.createComponent(resolvedUrl).createObject(parentObject, properties)
34 layout.addPageToNextColumn(parentObject, page)
35 _pagesToRemove.push(page)
36 }
37
38 function addFileToCurrentColumnSync(parentObject, resolvedUrl, properties) {
39 if (typeof(properties) === 'undefined') {
40 properties = {}
41 }
42 var page = Qt.createComponent(resolvedUrl).createObject(parentObject, properties)
43 layout.addPageToCurrentColumn(parentObject, page)
44 _pagesToRemove.push(page)
45 }
46
47 function addComponentToNextColumnSync(parentObject, component, properties) {
48 if (typeof(properties) === 'undefined') {
49 properties = {}
50 }
51 var page = component.createObject(parentObject, properties)
52 layout.addPageToNextColumn(parentObject, page)
53 _pagesToRemove.push(page)
54 }
55
56 function addComponentToCurrentColumnSync(parentObject, component, properties) {
57 if (typeof(properties) === 'undefined') {
58 properties = {}
59 }
60 var page = component.createObject(parentObject, properties)
61 layout.addPageToCurrentColumn(parentObject, page)
62 _pagesToRemove.push(page)
63 }
64}
065
=== modified file 'src/qml/MultiRecipientInput.qml'
--- src/qml/MultiRecipientInput.qml 2015-09-14 13:51:27 +0000
+++ src/qml/MultiRecipientInput.qml 2016-02-01 23:18:57 +0000
@@ -29,7 +29,7 @@
29 property variant recipients: []29 property variant recipients: []
30 property string searchString: ""30 property string searchString: ""
31 signal clearSearch()31 signal clearSearch()
32 style: Theme.createStyleComponent("TextFieldStyle.qml", multiRecipientWidget)32 styleName: "TextFieldStyle"
33 clip: true33 clip: true
34 height: contactFlow.height34 height: contactFlow.height
35 focus: activeFocus35 focus: activeFocus
3636
=== modified file 'src/qml/NewRecipientPage.qml'
--- src/qml/NewRecipientPage.qml 2015-11-16 22:16:06 +0000
+++ src/qml/NewRecipientPage.qml 2016-02-01 23:18:57 +0000
@@ -26,7 +26,6 @@
26 objectName: "newRecipientPage"26 objectName: "newRecipientPage"
2727
28 property Item multiRecipient: null28 property Item multiRecipient: null
29 property Item parentPage: null
30 property string phoneToAdd: ""29 property string phoneToAdd: ""
31 property QtObject contactIndex: null30 property QtObject contactIndex: null
3231
@@ -44,11 +43,39 @@
44 {43 {
45 multiRecipient.addRecipient(phoneNumber)44 multiRecipient.addRecipient(phoneNumber)
46 multiRecipient.forceActiveFocus()45 multiRecipient.forceActiveFocus()
47 mainStack.pop()46 mainStack.removePage(newRecipientPage)
48 }47 }
4948
50 title: i18n.tr("Add recipient")49 header: PageHeader {
5150 id: pageHeader
51
52 property alias leadingActions: leadingBar.actions
53 property alias trailingActions: trailingBar.actions
54
55 title: i18n.tr("Add recipient")
56 leadingActionBar {
57 id: leadingBar
58 }
59
60 trailingActionBar {
61 id: trailingBar
62 actions: [
63 Action {
64 text: i18n.tr("Back")
65 iconName: "back"
66 onTriggered: {
67 mainStack.removePage(newRecipientPage)
68 newRecipientPage.destroy()
69 }
70 }
71 ]
72 }
73 }
74
75 Sections {
76 id: headerSections
77 model: [i18n.tr("All"), i18n.tr("Favorites")]
78 }
52 TextField {79 TextField {
53 id: searchField80 id: searchField
5481
@@ -69,11 +96,10 @@
6996
70 state: "default"97 state: "default"
71 states: [98 states: [
72 PageHeadState {99 State {
73 id: defaultState100 id: defaultState
74
75 name: "default"101 name: "default"
76 actions: [102 property list<QtObject> trailingActions: [
77 Action {103 Action {
78 text: i18n.tr("Search")104 text: i18n.tr("Search")
79 iconName: "search"105 iconName: "search"
@@ -84,10 +110,11 @@
84 }110 }
85 }111 }
86 ]112 ]
113
87 PropertyChanges {114 PropertyChanges {
88 target: newRecipientPage.head115 target: pageHeader
89 actions: defaultState.actions116 trailingActions: defaultState.trailingActions
90 sections.model: [i18n.tr("All"), i18n.tr("Favorites")]117 extension: headerSections
91 }118 }
92 PropertyChanges {119 PropertyChanges {
93 target: searchField120 target: searchField
@@ -95,27 +122,34 @@
95 visible: false122 visible: false
96 }123 }
97 },124 },
98 PageHeadState {125 State {
99 id: searchingState126 id: searchingState
100
101 name: "searching"127 name: "searching"
102 backAction: Action {128 property list<QtObject> leadingActions: [
103 iconName: "back"129 Action {
104 text: i18n.tr("Cancel")130 iconName: "back"
105 onTriggered: {131 text: i18n.tr("Cancel")
106 newRecipientPage.forceActiveFocus()132 onTriggered: {
107 newRecipientPage.state = "default"133 newRecipientPage.forceActiveFocus()
108 newRecipientPage.head.sections.selectedIndex = 0134 newRecipientPage.state = "default"
135 headerSections.selectedIndex = 0
136 }
109 }137 }
110 }138 ]
111139
112 PropertyChanges {140 PropertyChanges {
113 target: newRecipientPage.head141 target: pageHeader
114 backAction: searchingState.backAction142 leadingActions: searchingState.leadingActions
143 trailingActions: []
115 contents: searchField144 contents: searchField
116 }145 }
117146
118 PropertyChanges {147 PropertyChanges {
148 target: headerSections
149 visible: false
150 }
151
152 PropertyChanges {
119 target: searchField153 target: searchField
120 text: ""154 text: ""
121 visible: true155 visible: true
@@ -127,7 +161,7 @@
127 id: contactList161 id: contactList
128 objectName: "newRecipientList"162 objectName: "newRecipientList"
129 anchors {163 anchors {
130 top: parent.top164 top: pageHeader.bottom
131 left: parent.left165 left: parent.left
132 right: parent.right166 right: parent.right
133 bottom: keyboard.top167 bottom: keyboard.top
@@ -139,12 +173,14 @@
139 filterTerm: searchField.text173 filterTerm: searchField.text
140 onContactClicked: {174 onContactClicked: {
141 if (newRecipientPage.phoneToAdd != "") {175 if (newRecipientPage.phoneToAdd != "") {
142 mainView.addPhoneToContact(contact,176 mainView.addPhoneToContact(newRecipientPage,
177 contact,
143 newRecipientPage.phoneToAdd,178 newRecipientPage.phoneToAdd,
144 newRecipientPage,179 newRecipientPage,
145 contactList.listModel)180 contactList.listModel)
146 } else {181 } else {
147 mainView.showContactDetails(contact,182 mainView.showContactDetails(newRecipientPage,
183 contact,
148 newRecipientPage,184 newRecipientPage,
149 contactList.listModel)185 contactList.listModel)
150 }186 }
@@ -152,24 +188,20 @@
152188
153 onAddNewContactClicked: {189 onAddNewContactClicked: {
154 var newContact = ContactsJS.createEmptyContact(newRecipientPage.phoneToAdd, newRecipientPage)190 var newContact = ContactsJS.createEmptyContact(newRecipientPage.phoneToAdd, newRecipientPage)
155 pageStack.push(Qt.resolvedUrl("MessagingContactEditorPage.qml"),191 mainStack.addFileToCurrentColumnSync(newRecipientPage,
156 { model: contactList.listModel,192 Qt.resolvedUrl("MessagingContactEditorPage.qml"),
157 contact: newContact,193 { model: contactList.listModel,
158 initialFocusSection: (newRecipientPage.phoneToAdd != "" ? "phones" : "name"),194 contact: newContact,
159 contactListPage: newRecipientPage195 initialFocusSection: (newRecipientPage.phoneToAdd != "" ? "phones" : "name"),
160 })196 contactListPage: newRecipientPage })
161 }197 }
162 }198 }
163199
164 // WORKAROUND: This is necessary to make the header visible from a bottom edge page
165 Component.onCompleted: {200 Component.onCompleted: {
166 parentPage.active = false
167 if (QTCONTACTS_PRELOAD_VCARD !== "") {201 if (QTCONTACTS_PRELOAD_VCARD !== "") {
168 contactList.listModel.importContacts("file://" + QTCONTACTS_PRELOAD_VCARD)202 contactList.listModel.importContacts("file://" + QTCONTACTS_PRELOAD_VCARD)
169 }203 }
170 }204 }
171 Component.onDestruction: parentPage.active = true
172
173 onActiveChanged: {205 onActiveChanged: {
174 if (active && (state === "searching")) {206 if (active && (state === "searching")) {
175 searchField.forceActiveFocus()207 searchField.forceActiveFocus()
176208
=== modified file 'src/qml/RegularMessageDelegate.qml'
--- src/qml/RegularMessageDelegate.qml 2015-08-05 19:31:15 +0000
+++ src/qml/RegularMessageDelegate.qml 2016-02-01 23:18:57 +0000
@@ -57,7 +57,7 @@
57 selectionMode: root.isInSelectionMode57 selectionMode: root.isInSelectionMode
58 accountLabel: {58 accountLabel: {
59 var account = telepathyHelper.accountForId(accountId)59 var account = telepathyHelper.accountForId(accountId)
60 if (account.type == AccountEntry.PhoneAccount || account.type == AccountEntry.MultimediaAccount) {60 if (account && (account.type == AccountEntry.PhoneAccount || account.type == AccountEntry.MultimediaAccount)) {
61 if (multiplePhoneAccounts) {61 if (multiplePhoneAccounts) {
62 return account.displayName62 return account.displayName
63 }63 }
6464
=== modified file 'src/qml/SettingsPage.qml'
--- src/qml/SettingsPage.qml 2015-09-14 13:51:27 +0000
+++ src/qml/SettingsPage.qml 2016-02-01 23:18:57 +0000
@@ -35,6 +35,11 @@
35 schema.id: "com.ubuntu.phone"35 schema.id: "com.ubuntu.phone"
36 }36 }
3737
38 header: PageHeader {
39 id: pageHeader
40 title: settingsPage.title
41 }
42
38 Component {43 Component {
39 id: settingDelegate44 id: settingDelegate
40 Item {45 Item {
@@ -66,9 +71,25 @@
66 }71 }
6772
68 ListView {73 ListView {
69 anchors.fill: parent74 anchors {
75 top: pageHeader.bottom
76 left: parent.left
77 right: parent.right
78 bottom: parent.bottom
79 }
70 model: settingsModel80 model: settingsModel
71 delegate: settingDelegate81 delegate: settingDelegate
72 }82 }
83
84 Loader {
85 id: messagesBottomEdgeLoader
86 active: mainView.dualPanel
87 sourceComponent: MessagingBottomEdge {
88 id: messagesBottomEdge
89 parent: settingsPage
90 hint.text: ""
91 hint.height: 0
92 }
93 }
73}94}
7495
7596
=== modified file 'src/qml/messaging-app.qml'
--- src/qml/messaging-app.qml 2016-02-01 23:18:57 +0000
+++ src/qml/messaging-app.qml 2016-02-01 23:18:57 +0000
@@ -39,6 +39,10 @@
39 }39 }
40 property QtObject account: defaultPhoneAccount()40 property QtObject account: defaultPhoneAccount()
41 property bool applicationActive: Qt.application.active41 property bool applicationActive: Qt.application.active
42 property alias mainStack: layout
43 property bool dualPanel: mainStack.columns > 1
44 property QtObject bottomEdge: null
45 property bool composingNewMessage: bottomEdge.status === BottomEdge.Committed
4246
43 activeFocusOnPress: false47 activeFocusOnPress: false
4448
@@ -58,7 +62,7 @@
58 return null62 return null
59 }63 }
6064
61 function showContactDetails(contact, contactListPage, contactsModel) {65 function showContactDetails(currentPage, contact, contactListPage, contactsModel) {
62 var initialProperties = { "contactListPage": contactListPage,66 var initialProperties = { "contactListPage": contactListPage,
63 "model": contactsModel}67 "model": contactsModel}
6468
@@ -68,21 +72,24 @@
68 initialProperties['contact'] = contact72 initialProperties['contact'] = contact
69 }73 }
7074
71 mainStack.push(Qt.resolvedUrl("MessagingContactViewPage.qml"),75 mainStack.addFileToCurrentColumnSync(currentPage,
72 initialProperties)76 Qt.resolvedUrl("MessagingContactViewPage.qml"),
73 }77 initialProperties)
7478 }
75 function addNewContact(phoneNumber, contactListPage) {79
76 mainStack.push(Qt.resolvedUrl("MessagingContactEditorPage.qml"),80 function addNewContact(currentPage, phoneNumber, contactListPage) {
77 { "contactId": contactId,81 mainStack.addFileToCurrentColumnSync(currentPage,
78 "addPhoneToContact": phoneNumber,82 Qt.resolvedUrl("MessagingContactEditorPage.qml"),
79 "contactListPage": contactListPage })83 { "contactId": contactId,
80 }84 "addPhoneToContact": phoneNumber,
8185 "contactListPage": contactListPage })
82 function addPhoneToContact(contact, phoneNumber, contactListPage, contactsModel) {86 }
87
88 function addPhoneToContact(currentPage, contact, phoneNumber, contactListPage, contactsModel) {
83 if (contact === "") {89 if (contact === "") {
84 mainStack.push(Qt.resolvedUrl("NewRecipientPage.qml"),90 mainStack.addFileToCurrentColumnSync(currentPage,
85 { "phoneToAdd": phoneNumber })91 Qt.resolvedUrl("NewRecipientPage.qml"),
92 { "phoneToAdd": phoneNumber })
86 } else {93 } else {
87 var initialProperties = { "addPhoneToContact": phoneNumber,94 var initialProperties = { "addPhoneToContact": phoneNumber,
88 "contactListPage": contactListPage,95 "contactListPage": contactListPage,
@@ -92,8 +99,9 @@
92 } else {99 } else {
93 initialProperties['contact'] = contact100 initialProperties['contact'] = contact
94 }101 }
95 mainStack.push(Qt.resolvedUrl("MessagingContactViewPage.qml"),102 mainStack.addFileToCurrentColumnSync(currentPage,
96 initialProperties)103 Qt.resolvedUrl("MessagingContactViewPage.qml"),
104 initialProperties)
97 }105 }
98 }106 }
99107
@@ -119,6 +127,10 @@
119 threadModel.removeThreads(threads);127 threadModel.removeThreads(threads);
120 }128 }
121129
130 function showBottomEdgePage(properties) {
131 bottomEdge.commitWithProperties(properties)
132 }
133
122 Connections {134 Connections {
123 target: telepathyHelper135 target: telepathyHelper
124 // restore default bindings if any system settings changed136 // restore default bindings if any system settings changed
@@ -141,13 +153,14 @@
141 Component.onCompleted: {153 Component.onCompleted: {
142 i18n.domain = "messaging-app"154 i18n.domain = "messaging-app"
143 i18n.bindtextdomain("messaging-app", i18nDirectory)155 i18n.bindtextdomain("messaging-app", i18nDirectory)
156 emptyStack()
144 }157 }
145158
146 Connections {159 Connections {
147 target: telepathyHelper160 target: telepathyHelper
148 onSetupReady: {161 onSetupReady: {
149 if (multiplePhoneAccounts && !telepathyHelper.defaultMessagingAccount &&162 if (multiplePhoneAccounts && !telepathyHelper.defaultMessagingAccount &&
150 !settings.mainViewIgnoreFirstTimeDialog && mainStack.depth === 1) {163 !settings.mainViewIgnoreFirstTimeDialog && mainPage.displayedThreadIndex < 0) {
151 PopupUtils.open(Qt.createComponent("Dialogs/NoDefaultSIMCardDialog.qml").createObject(mainView))164 PopupUtils.open(Qt.createComponent("Dialogs/NoDefaultSIMCardDialog.qml").createObject(mainView))
152 }165 }
153 }166 }
@@ -187,7 +200,7 @@
187 var properties = {}200 var properties = {}
188 emptyStack()201 emptyStack()
189 properties["sharedAttachmentsTransfer"] = transfer202 properties["sharedAttachmentsTransfer"] = transfer
190 mainStack.currentPage.showBottomEdgePage(Qt.resolvedUrl("Messages.qml"), properties)203 mainView.showBottomEdgePage(properties)
191 }204 }
192 }205 }
193206
@@ -211,15 +224,21 @@
211 }224 }
212225
213 function emptyStack() {226 function emptyStack() {
214 while (mainStack.depth !== 1 && mainStack.depth !== 0) {227 mainStack.removePage(mainPage)
215 mainStack.pop()228 layout.deleteInstances()
229 showEmptyState()
230 }
231
232 function showEmptyState() {
233 if (mainStack.columns > 1) {
234 layout.addComponentToNextColumnSync(mainStack.primaryPage, emptyStatePageComponent)
216 }235 }
217 }236 }
218237
219 function startNewMessage() {238 function startNewMessage() {
220 var properties = {}239 var properties = {}
221 emptyStack()240 emptyStack()
222 mainStack.currentPage.showBottomEdgePage(Qt.resolvedUrl("Messages.qml"))241 mainView.showBottomEdgePage(properties)
223 }242 }
224243
225 function startChat(identifiers, text, accountId) {244 function startChat(identifiers, text, accountId) {
@@ -261,9 +280,26 @@
261 if (typeof(accountId)!=='undefined') {280 if (typeof(accountId)!=='undefined') {
262 properties["accountId"] = accountId281 properties["accountId"] = accountId
263 }282 }
283
264 emptyStack()284 emptyStack()
265 mainStack.push(Qt.resolvedUrl("Messages.qml"), properties)285 // FIXME: AdaptivePageLayout takes a really long time to create pages,
266 }286 // so we create manually and push that
287 mainStack.addComponentToNextColumnSync(mainPage, messagesWithBottomEdge, properties)
288 }
289
290 InputInfo {
291 id: inputInfo
292 }
293
294 // WORKAROUND: Due the missing feature on SDK, they can not detect if
295 // there is a mouse attached to device or not. And this will cause the
296 // bootom edge component to not work correct on desktop.
297 Binding {
298 target: QuickUtils
299 property: "mouseAttached"
300 value: inputInfo.hasMouse
301 }
302
267303
268 Connections {304 Connections {
269 target: UriHandler305 target: UriHandler
@@ -274,11 +310,60 @@
274 }310 }
275 }311 }
276312
277313 Component {
278 PageStack {314 id: messagesWithBottomEdge
279 id: mainStack315
280316 Messages {
281 objectName: "mainStack"317 id: messages
282 Component.onCompleted: mainStack.push(Qt.resolvedUrl("MainPage.qml"))318 height: mainPage.height
319
320 Component.onCompleted: mainPage._messagesPage = messages
321 Loader {
322 id: messagesBottomEdgeLoader
323 active: mainView.dualPanel
324 sourceComponent: MessagingBottomEdge {
325 id: messagesBottomEdge
326 parent: messages
327 hint.text: ""
328 hint.height: 0
329 }
330 }
331 }
332 }
333
334 Component {
335 id: emptyStatePageComponent
336 Page {
337 id: emptyStatePage
338
339 EmptyState {
340 labelVisible: mainPage.isEmpty
341 }
342
343 header: PageHeader { }
344
345 Loader {
346 id: bottomEdgeLoader
347 sourceComponent: MessagingBottomEdge {
348 parent: emptyStatePage
349 }
350 }
351 }
352 }
353
354 MessagingPageLayout {
355 id: layout
356 anchors.fill: parent
357 primaryPage: MainPage {
358 id: mainPage
359 }
360
361 onColumnsChanged: {
362 // we only have things to do here in case no thread is selected
363 if (mainPage.displayedThreadIndex < 0) {
364 layout.removePage(mainPage)
365 showEmptyState()
366 }
367 }
283 }368 }
284}369}
285370
=== modified file 'tests/autopilot/messaging_app/emulators.py'
--- tests/autopilot/messaging_app/emulators.py 2015-11-04 19:57:45 +0000
+++ tests/autopilot/messaging_app/emulators.py 2016-02-01 23:18:57 +0000
@@ -78,14 +78,14 @@
7878
79 def click_header_action(self, action):79 def click_header_action(self, action):
80 """Click the action 'action' on the header"""80 """Click the action 'action' on the header"""
81 self.get_header().click_action_button(action)81 action = self.wait_select_single(objectName='%s_button' % action)
82 self.pointing_device.click_object(action)
8283
83 # messages page84 # messages page
84 def get_messages_page(self):85 def get_messages_page(self):
85 """Return messages with objectName messagesPage"""86 """Return messages with objectName messagesPage"""
8687
87 return self.wait_select_single("Messages", objectName="messagesPage",88 return self.wait_select_single("Messages", objectName="messagesPage")
88 active=True)
8989
90 def get_newmessage_textfield(self):90 def get_newmessage_textfield(self):
91 """Return TextField with objectName newPhoneNumberField"""91 """Return TextField with objectName newPhoneNumberField"""
@@ -116,40 +116,12 @@
116116
117 return self.get_messages_page().select_single(objectName='sendButton')117 return self.get_messages_page().select_single(objectName='sendButton')
118118
119 def get_toolbar_back_button(self):
120 """Return toolbar button with objectName back_toolbar_button"""
121
122 return self.select_single(
123 objectName='back_toolbar_button'
124 )
125
126 def get_toolbar_select_messages_button(self):
127 """Return toolbar button with objectName selectMessagesButton"""
128
129 return self.select_single(
130 objectName='selectMessagesButton'
131 )
132
133 def get_contact_list_view(self):119 def get_contact_list_view(self):
134 """Returns the ContactListView object"""120 """Returns the ContactListView object"""
135 return self.select_single(121 return self.select_single(
136 objectName='newRecipientList'122 objectName='newRecipientList'
137 )123 )
138124
139 def get_toolbar_contact_profile_button(self):
140 """Return toolbar button with objectName contactProfileButton"""
141
142 return self.select_single(
143 objectName='contactProfileButton'
144 )
145
146 def get_toolbar_contact_call_button(self):
147 """Return toolbar button with objectName contactCallButton"""
148
149 return self.select_single(
150 objectName='contactCallButton'
151 )
152
153 def get_dialog_buttons(self, visible=True):125 def get_dialog_buttons(self, visible=True):
154 """Return DialogButtons126 """Return DialogButtons
155127
@@ -213,7 +185,7 @@
213185
214 def start_new_message(self):186 def start_new_message(self):
215 """Reveal the bottom edge page to start composing a new message"""187 """Reveal the bottom edge page to start composing a new message"""
216 self.get_main_page().reveal_bottom_edge_page()188 self.reveal_bottom_edge_page()
217189
218 def enable_messages_selection_mode(self):190 def enable_messages_selection_mode(self):
219 """Enable the selection mode on the messages page by pressing and191 """Enable the selection mode on the messages page by pressing and
@@ -338,38 +310,30 @@
338310
339 def open_settings_page(self):311 def open_settings_page(self):
340 self.click_threads_header_settings()312 self.click_threads_header_settings()
341 return self.wait_select_single(SettingsPage)313 settings_page = self.wait_select_single(SettingsPage)
314 settings_page.active.waitFor(True)
315 return settings_page
342316
343 def get_swipe_item_demo(self):317 def get_swipe_item_demo(self):
344 return self.wait_select_single(318 return self.wait_select_single(
345 'SwipeItemDemo', objectName='swipeItemDemo', parentActive=True)319 'SwipeItemDemo', objectName='swipeItemDemo', parentActive=True)
346320
347
348class PageWithBottomEdge(MainView):
349 """An emulator class that makes it easy to interact with the bottom edge
350 swipe page"""
351 def __init__(self, *args):
352 super(PageWithBottomEdge, self).__init__(*args)
353
354 def reveal_bottom_edge_page(self):321 def reveal_bottom_edge_page(self):
355 """Bring the bottom edge page to the screen"""322 """Bring the bottom edge page to the screen"""
356 self.bottomEdgePageLoaded.wait_for(True)
357 try:323 try:
358 action_item = self.wait_select_single(objectName='bottomEdgeTip')324 start_x = (self.globalRect.x +
359 start_x = (action_item.globalRect.x +325 (self.globalRect.width * 0.5))
360 (action_item.globalRect.width * 0.5))326 start_y = (self.globalRect.y + self.height)
361 start_y = (action_item.globalRect.y +
362 (action_item.height * 0.5))
363 stop_y = start_y - (self.height * 0.7)327 stop_y = start_y - (self.height * 0.7)
364 self.pointing_device.drag(start_x, start_y,328 self.pointing_device.drag(start_x, start_y,
365 start_x, stop_y, rate=2)329 start_x, stop_y, rate=2)
366 self.isReady.wait_for(True)330 self.composingNewMessage.wait_for(True)
367 except StateNotFoundError:331 except StateNotFoundError:
368 logger.error('BottomEdge element not found.')332 logger.error('BottomEdge element not found.')
369 raise333 raise
370334
371335
372class MainPage(PageWithBottomEdge):336class MainPage(MainView):
373 """Autopilot helper for the Main Page."""337 """Autopilot helper for the Main Page."""
374338
375 def get_thread_count(self):339 def get_thread_count(self):
376340
=== modified file 'tests/qml/CMakeLists.txt'
--- tests/qml/CMakeLists.txt 2016-02-01 23:18:57 +0000
+++ tests/qml/CMakeLists.txt 2016-02-01 23:18:57 +0000
@@ -36,7 +36,8 @@
3636
37add_test(${TEST} ${XVFB_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/${TEST}37add_test(${TEST} ${XVFB_COMMAND} ${CMAKE_CURRENT_BINARY_DIR}/${TEST}
38 -input ${CMAKE_CURRENT_SOURCE_DIR}38 -input ${CMAKE_CURRENT_SOURCE_DIR}
39 -import ${CMAKE_BINARY_DIR}/src)39 -import ${CMAKE_BINARY_DIR}/src
40 -import ${UNITY8_QML_PATH})
4041
41# make qml files visible in QtCreator42# make qml files visible in QtCreator
42file(GLOB_RECURSE NON_COMPILED_FILES *.qml)43file(GLOB_RECURSE NON_COMPILED_FILES *.qml)
4344
=== modified file 'tests/qml/tst_MessagesView.qml'
--- tests/qml/tst_MessagesView.qml 2015-11-19 00:34:45 +0000
+++ tests/qml/tst_MessagesView.qml 2016-02-01 23:18:57 +0000
@@ -129,16 +129,21 @@
129 var senderId = "1234567"129 var senderId = "1234567"
130 var stack = findChild(mainViewLoader, "mainStack")130 var stack = findChild(mainViewLoader, "mainStack")
131 tryCompare(mainViewLoader.item, 'applicationActive', true)131 tryCompare(mainViewLoader.item, 'applicationActive', true)
132 tryCompare(stack, 'depth', 1)
133 // if messaging-app has no account set, it will not try to get the thread from history132 // if messaging-app has no account set, it will not try to get the thread from history
134 // and instead will generate the list of participants, take advantage of that133 // and instead will generate the list of participants, take advantage of that
135 var account = mainViewLoader.item.account134 var account = mainViewLoader.item.account
136 mainViewLoader.item.account = null135 mainViewLoader.item.account = null
137 mainViewLoader.item.startChat(senderId, "")136 mainViewLoader.item.startChat(senderId, "")
138 mainViewLoader.item.account = account137 mainViewLoader.item.account = account
139 tryCompare(stack, 'depth', 2)138 var messageList
140 mainViewLoader.item.applicationActive = false139 while (true) {
141 var messageList = findChild(mainViewLoader, "messageList")140 messageList = findChild(mainViewLoader, "messageList")
141 if (messageList) {
142 break
143 }
144 wait(200)
145 }
146
142 messageList.listModel = messagesModel147 messageList.listModel = messagesModel
143 tryCompare(messageList, 'count', 2)148 tryCompare(messageList, 'count', 2)
144 compare(messageAcknowledgeSpy.count, 0)149 compare(messageAcknowledgeSpy.count, 0)

Subscribers

People subscribed via source and target branches

to all changes: