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/fix_history_reloading
Diff against target: 2988 lines (+1164/-1023)
34 files modified
CMakeLists.txt (+14/-18)
TODO.convergence (+3/-0)
config.h.in (+1/-0)
debian/control (+5/-1)
debian/messaging-app-apparmor.additions (+86/-0)
debian/messaging-app-apparmor.manifest (+37/-0)
debian/messaging-app.install (+1/-0)
debian/rules (+22/-0)
src/fileoperations.cpp (+6/-1)
src/messagingapplication.cpp (+1/-0)
src/qml/AudioRecordingBar.qml (+28/-4)
src/qml/ComposeBar.qml (+5/-0)
src/qml/Dialogs/NoMicrophonePermission.qml (+58/-0)
src/qml/EmptyState.qml (+38/-0)
src/qml/InputInfo.qml (+24/-0)
src/qml/LocalPageWithBottomEdge.qml (+0/-428)
src/qml/MMS/MMSAudio.qml (+10/-11)
src/qml/MMS/Previewer.qml (+26/-15)
src/qml/MMS/PreviewerImage.qml (+1/-0)
src/qml/MMS/PreviewerMultipleContacts.qml (+1/-1)
src/qml/MMS/PreviewerVideo.qml (+1/-0)
src/qml/MMSDelegate.qml (+1/-1)
src/qml/MainPage.qml (+123/-64)
src/qml/Messages.qml (+418/-350)
src/qml/MessagesHeader.qml (+7/-2)
src/qml/MessagingBottomEdge.qml (+43/-0)
src/qml/MessagingContactEditorPage.qml (+6/-2)
src/qml/MessagingContactViewPage.qml (+7/-6)
src/qml/MultiRecipientInput.qml (+1/-1)
src/qml/NewRecipientPage.qml (+58/-35)
src/qml/messaging-app.qml (+109/-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+282583@code.launchpad.net

This proposal supersedes a proposal from 2015-11-03.

This proposal has been superseded by a proposal from 2016-01-28.

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 :
review: Needs Fixing (continuous-integration)
457. By Gustavo Pichorim Boiko

Make sure we always have a view on the right side of the screen so that we can
add a bottom edge gesture there.

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

Use the bottom edge from SDK.

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

Make it possible to create the bottom edge page using different properties.

460. By Gustavo Pichorim Boiko

Add a cancel action to the new message state to be used in the bottom edge pages.

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

Fix exposing the bottom edge page with arguments.

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

Merge trunk.

463. By Gustavo Pichorim Boiko

Fix qml tests.

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)
464. By Gustavo Pichorim Boiko

Increase the waiting

465. By Gustavo Pichorim Boiko

Increase the waiting time even more.

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)
466. By Gustavo Pichorim Boiko

Move the bottom edge action to the header on dual panel environments

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

Skip the header height from the contacts popup.

468. By Gustavo Pichorim Boiko

Remove the dependency on unity8-private

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

Remove debug leftover.

470. By Gustavo Pichorim Boiko

Show a new item on listview when the bottom edge page is visible.

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)
471. By Gustavo Pichorim Boiko

Another attempt at scrolling the list to the new item.

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

Make sure we get the view on the test before proceeding.

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

Fix one more page header.

474. By Gustavo Pichorim Boiko

Fix one more pagestack interaction

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

Fix sending audio files.

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

Start fixing the interaction with header actions and the bottom edge in autopilot
tests.

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

Make pep8 happy.

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

Fix loading the contact viewing page.

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

Make sure the To: field gets focus when composing a new message.

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

Pages loaded in the bottom edge are not in the layout, so we need to use the
underneath page as the parent to push new pages to the stack.

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

Keep the bottom edge down arrow (back action) visible after sending the first
message.

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

merge apparmor branch

483. By Tiago Salem Herrmann

Fix ListView anchors to avoid hide the content under the header

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.

489. By Tiago Salem Herrmann

Fix ListView anchors also on NewRecipientPage

490. By Gustavo Pichorim Boiko

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

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

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.

495. By Tiago Salem Herrmann

fix anchors in the settings page

496. By Tiago Salem Herrmann

Empty stack before adding SettingsPage to the stack

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

500. By Gustavo Pichorim Boiko

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

501. By Gustavo Pichorim Boiko

Merge swipe to cancel fix.

502. By Gustavo Pichorim Boiko

Remove duplicated code.

503. By Gustavo Pichorim Boiko

Fix loading pages by source

504. By Gustavo Pichorim Boiko

Hide the bottom edge bar in the empty state screen.

505. By Gustavo Pichorim Boiko

Messaging-app doesn't belong to side stage anymore

506. By Gustavo Pichorim Boiko

Set a minimum window size.

507. By Gustavo Pichorim Boiko

Make it landscape by default on windowed environments

508. By Gustavo Pichorim Boiko

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

509. By Gustavo Pichorim Boiko

Show the group chat participants popup anchored to the right

510. By Gustavo Pichorim Boiko

Make the header fixed in two column mode

511. By Gustavo Pichorim Boiko

Add a scrollbar to the Messages view.

512. By Gustavo Pichorim Boiko

Merge trunk.

513. By Gustavo Pichorim Boiko

Make the readme more clear

514. By Gustavo Pichorim Boiko

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

Unmerged revisions

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: