Merge lp:~phablet-team/messaging-app/side_panel into lp:messaging-app
- side_panel
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~phablet-team/messaging-app/side_panel |
Merge into: | lp:messaging-app |
Prerequisite: | lp:~tiagosh/messaging-app/update-sim-dialogs |
Diff against target: |
8706 lines (+6166/-1165) (has conflicts) 78 files modified
.bzrignore (+37/-0) debian/control (+5/-0) debian/messaging-app.install (+1/-0) src/CMakeLists.txt (+7/-1) src/audiorecorder.cpp (+245/-0) src/audiorecorder.h (+142/-0) src/fileoperations.cpp (+58/-0) src/fileoperations.h (+41/-0) src/messagingapplication.cpp (+44/-1) src/messagingapplication.h (+7/-1) src/qml/AttachmentPanel.qml (+174/-0) src/qml/AudioPlaybackBar.qml (+187/-0) src/qml/AudioRecordingBar.qml (+137/-0) src/qml/CMakeLists.txt (+1/-0) src/qml/ComposeBar.qml (+534/-0) src/qml/ContentImport.qml (+21/-4) src/qml/DeliveryStatus.qml (+38/-0) src/qml/Dialogs/FileSizeWarningDialog.qml (+70/-0) src/qml/KeyboardRectangle.qml (+8/-0) src/qml/LocalPageWithBottomEdge.qml (+3/-2) src/qml/MMS/MMSAudio.qml (+200/-0) src/qml/MMS/MMSBase.qml (+2/-0) src/qml/MMS/MMSContact.qml (+15/-2) src/qml/MMS/MMSImage.qml (+14/-0) src/qml/MMS/MMSVideo.qml (+73/-43) src/qml/MMS/Previewer.qml (+2/-2) src/qml/MMS/PreviewerImage.qml (+183/-8) src/qml/MMS/PreviewerVideo.qml (+124/-33) src/qml/MMSDelegate.qml (+21/-18) src/qml/MMSMessageBubble.qml (+1/-0) src/qml/MainPage.qml (+77/-30) src/qml/MessageBubble.qml (+63/-77) src/qml/MessageDelegate.qml (+1/-0) src/qml/MessageDelegateFactory.qml (+2/-0) src/qml/Messages.qml (+538/-831) src/qml/MessagesHeader.qml (+8/-3) src/qml/MessagingContactEditorPage.qml (+41/-2) src/qml/MessagingContactViewPage.qml (+42/-7) src/qml/MultiRecipientInput.qml (+1/-1) src/qml/NewRecipientPage.qml (+58/-35) src/qml/SMSDelegate.qml (+1/-0) src/qml/Stickers/CMakeLists.txt (+4/-0) src/qml/Stickers/HistoryButton.qml (+35/-0) src/qml/Stickers/StickerDelegate.qml (+32/-0) src/qml/Stickers/StickerPackDelegate.qml (+53/-0) src/qml/Stickers/StickerPacksModel.qml (+25/-0) src/qml/Stickers/StickersModel.qml (+27/-0) src/qml/Stickers/StickersPicker.qml (+138/-0) src/qml/ThreadDelegate.qml (+64/-5) src/qml/ThumbnailContact.qml (+106/-0) src/qml/ThumbnailImage.qml (+49/-0) src/qml/ThumbnailUnknown.qml (+45/-0) src/qml/ThumbnailVideo.qml (+76/-0) src/qml/TransparentButton.qml (+104/-0) src/qml/assets/blue_bubble@27.sci (+5/-0) src/qml/assets/burn-after-read.svg (+191/-0) src/qml/assets/double_tick.svg (+21/-0) src/qml/assets/face-smile-big-symbolic-2.svg (+182/-0) src/qml/assets/green_bubble@27.sci (+5/-0) src/qml/assets/grey_bubble@27.sci (+5/-0) src/qml/assets/history.svg (+173/-0) src/qml/assets/input-keyboard-symbolic.svg (+221/-0) src/qml/assets/media_bubble@27.sci (+5/-0) src/qml/assets/red_bubble@27.sci (+5/-0) src/qml/assets/single_tick.svg (+20/-0) src/qml/assets/stock_document.svg (+189/-0) src/qml/assets/white_bubble@27.sci (+5/-0) src/qml/dateUtils.js (+6/-1) src/qml/messaging-app.qml (+50/-30) src/stickers-history-model.cpp (+307/-0) src/stickers-history-model.h (+91/-0) tests/qml/CMakeLists.txt (+35/-25) tests/qml/tst_MMSDelegate.qml (+196/-0) tests/qml/tst_MessageBubble.qml (+3/-3) tests/qml/tst_PreviewerImage.qml.disabled (+103/-0) tests/qml/tst_PreviewerVideo.qml.disabled (+87/-0) tests/qml/tst_QmlTests.cpp (+88/-0) tests/qml/tst_StickersHistoryModel.qml (+188/-0) Text conflict in src/qml/MessagingContactEditorPage.qml Text conflict in src/qml/MessagingContactViewPage.qml |
To merge this branch: | bzr merge lp:~phablet-team/messaging-app/side_panel |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Ubuntu Phablet Team | Pending | ||
Review via email: mp+276528@code.launchpad.net |
This proposal has been superseded by a proposal from 2016-01-14.
Commit message
Implement convergent layout for messaging-app.
Description of the change
Implement convergent layout for messaging-app.
- 450. By Gustavo Pichorim Boiko
-
Re-add code removed by accident.
- 451. By Gustavo Pichorim Boiko
-
Remove code added by mistake when resolving conflicts.
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:451
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 452. By Gustavo Pichorim Boiko
-
Merge latest changes from parent branch.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:452
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 453. By Gustavo Pichorim Boiko
-
Merge trunk.
- 454. By Gustavo Pichorim Boiko
-
Merge changes from IM branches.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:454
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 455. By Gustavo Pichorim Boiko
-
Merge latest IM changes.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:455
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 456. By Gustavo Pichorim Boiko
-
Update messaging-app to use the new SDK header in preparation to migrate to the
new bottom edge. - 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. - 458. By Gustavo Pichorim Boiko
-
Use the bottom edge from SDK.
- 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.
- 461. By Gustavo Pichorim Boiko
-
Fix exposing the bottom edge page with arguments.
- 462. By Gustavo Pichorim Boiko
-
Merge trunk.
- 463. By Gustavo Pichorim Boiko
-
Fix qml tests.
- 464. By Gustavo Pichorim Boiko
-
Increase the waiting
- 465. By Gustavo Pichorim Boiko
-
Increase the waiting time even more.
- 466. By Gustavo Pichorim Boiko
-
Move the bottom edge action to the header on dual panel environments
- 467. By Gustavo Pichorim Boiko
-
Skip the header height from the contacts popup.
- 468. By Gustavo Pichorim Boiko
-
Remove the dependency on unity8-private
- 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.
- 471. By Gustavo Pichorim Boiko
-
Another attempt at scrolling the list to the new item.
- 472. By Gustavo Pichorim Boiko
-
Make sure we get the view on the test before proceeding.
- 473. By Gustavo Pichorim Boiko
-
Fix one more page header.
- 474. By Gustavo Pichorim Boiko
-
Fix one more pagestack interaction
- 475. By Gustavo Pichorim Boiko
-
Fix sending audio files.
- 476. By Gustavo Pichorim Boiko
-
Start fixing the interaction with header actions and the bottom edge in autopilot
tests. - 477. By Gustavo Pichorim Boiko
-
Make pep8 happy.
- 478. By Gustavo Pichorim Boiko
-
Fix loading the contact viewing page.
- 479. By Gustavo Pichorim Boiko
-
Make sure the To: field gets focus when composing a new message.
- 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. - 481. By Gustavo Pichorim Boiko
-
Keep the bottom edge down arrow (back action) visible after sending the first
message. - 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
1 | === added file '.bzrignore' |
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 |
3 | +++ .bzrignore 2016-01-14 12:41:18 +0000 |
4 | @@ -0,0 +1,37 @@ |
5 | +*.qmlproject.user |
6 | +CMakeCache.txt |
7 | +CMakeFiles/ |
8 | +CMakeLists.txt.user |
9 | +cmake_install.cmake |
10 | +cmake_uninstall.cmake |
11 | +Makefile |
12 | +install_manifest.txt |
13 | +CTestTestfile.cmake |
14 | + |
15 | +*.cbp |
16 | +*.moc |
17 | +moc_*.cpp |
18 | +*_automoc.cpp |
19 | + |
20 | +Testing |
21 | +RE:tests/qml/tst_\w+Tests$ |
22 | +po/*.gmo |
23 | +po/src |
24 | + |
25 | +config.h |
26 | +src/messaging-app |
27 | +src/messaging-app.desktop* |
28 | + |
29 | +obj-* |
30 | +debian/usr.bin.webbrowser-app |
31 | +debian/files |
32 | +debian/tmp/ |
33 | +debian/qtdeclarative5-ubuntu-ui-extras-browser-plugin/ |
34 | +debian/qtdeclarative5-ubuntu-web-plugin/ |
35 | +debian/qtdeclarative5-ubuntu-web-plugin-doc/ |
36 | +debian/messaging-app/ |
37 | +debian/messaging-app-autopilot/ |
38 | +debian/*.debhelper |
39 | +debian/*.debhelper.log |
40 | +debian/*.substvars |
41 | +debian/stamp-* |
42 | |
43 | === modified file 'debian/control' |
44 | --- debian/control 2015-09-11 14:25:57 +0000 |
45 | +++ debian/control 2016-01-14 12:41:18 +0000 |
46 | @@ -21,8 +21,12 @@ |
47 | qtdeclarative5-ubuntu-telephony0.1 | qtdeclarative5-ubuntu-telephony-plugin, |
48 | qtdeclarative5-ubuntu-content1, |
49 | qtdeclarative5-ubuntu-addressbook0.1, |
50 | + qtdeclarative5-ubuntu-thumbnailer0.1, |
51 | qtdeclarative5-qtcontacts-plugin, |
52 | + qtdeclarative5-folderlistmodel-plugin, |
53 | + qtmultimedia5-dev, |
54 | qml-module-qt-labs-settings, |
55 | + qml-module-qtmultimedia, |
56 | qtpim5-dev, |
57 | xvfb, |
58 | Standards-Version: 3.9.4 |
59 | @@ -37,6 +41,7 @@ |
60 | Architecture: any |
61 | Depends: ${misc:Depends}, |
62 | ${shlibs:Depends}, |
63 | + libqt5multimedia5, |
64 | qtdeclarative5-ubuntu-addressbook0.1, |
65 | qtdeclarative5-ubuntu-ui-toolkit-plugin | qt-components-ubuntu, |
66 | qtdeclarative5-ubuntu-telephony-phonenumber0.1, |
67 | |
68 | === modified file 'debian/messaging-app.install' |
69 | --- debian/messaging-app.install 2014-08-11 22:22:59 +0000 |
70 | +++ debian/messaging-app.install 2016-01-14 12:41:18 +0000 |
71 | @@ -8,4 +8,5 @@ |
72 | usr/share/messaging-app/3rd_party |
73 | usr/share/messaging-app/MMS/*.qml |
74 | usr/share/messaging-app/Dialogs/*.qml |
75 | +usr/share/messaging-app/Stickers/*.qml |
76 | usr/bin/*messaging-app* |
77 | |
78 | === modified file 'src/CMakeLists.txt' |
79 | --- src/CMakeLists.txt 2015-02-24 13:33:31 +0000 |
80 | +++ src/CMakeLists.txt 2016-01-14 12:41:18 +0000 |
81 | @@ -1,18 +1,24 @@ |
82 | set(MESSAGING_APP messaging-app) |
83 | |
84 | set(messaging_app_HDRS |
85 | + audiorecorder.h |
86 | + fileoperations.h |
87 | messagingapplication.h |
88 | + stickers-history-model.h |
89 | ) |
90 | |
91 | set(messaging_app_SRCS |
92 | + audiorecorder.cpp |
93 | + fileoperations.cpp |
94 | messagingapplication.cpp |
95 | main.cpp |
96 | + stickers-history-model.cpp |
97 | ) |
98 | |
99 | add_executable(${MESSAGING_APP} |
100 | ${messaging_app_SRCS} |
101 | ) |
102 | -qt5_use_modules(${MESSAGING_APP} Core DBus Gui Qml Quick Versit) |
103 | +qt5_use_modules(${MESSAGING_APP} Core DBus Gui Multimedia Qml Quick Sql Versit) |
104 | |
105 | include_directories( |
106 | ${CMAKE_CURRENT_BINARY_DIR} |
107 | |
108 | === added file 'src/audiorecorder.cpp' |
109 | --- src/audiorecorder.cpp 1970-01-01 00:00:00 +0000 |
110 | +++ src/audiorecorder.cpp 2016-01-14 12:41:18 +0000 |
111 | @@ -0,0 +1,245 @@ |
112 | +/* |
113 | + * Copyright (C) 2015 Canonical, Ltd. |
114 | + * |
115 | + * Authors: |
116 | + * Arthur Renato Mello <arthur.mello@canonical.com> |
117 | + * |
118 | + * This file is part of messaging-app. |
119 | + * |
120 | + * messaging-app is free software; you can redistribute it and/or modify |
121 | + * it under the terms of the GNU General Public License as published by |
122 | + * the Free Software Foundation; version 3. |
123 | + * |
124 | + * messaging-app is distributed in the hope that it will be useful, |
125 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
126 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
127 | + * GNU General Public License for more details. |
128 | + * |
129 | + * You should have received a copy of the GNU General Public License |
130 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
131 | + */ |
132 | + |
133 | +#include "audiorecorder.h" |
134 | + |
135 | +#include <QDebug> |
136 | +#include <QDir> |
137 | +#include <QUrl> |
138 | +#include <QStandardPaths> |
139 | +#include <QTemporaryFile> |
140 | + |
141 | +AudioRecorder::AudioRecorder(QObject *parent) |
142 | + : QObject(parent) |
143 | +{ |
144 | + m_audioRecorder = new QAudioRecorder(); |
145 | + connect(m_audioRecorder, SIGNAL(stateChanged(QMediaRecorder::State)), |
146 | + SIGNAL(recorderStateChanged())); |
147 | + connect(m_audioRecorder, SIGNAL(statusChanged(QMediaRecorder::Status)), |
148 | + SIGNAL(recorderStatusChanged())); |
149 | + connect(m_audioRecorder, SIGNAL(error(QMediaRecorder::Error)), |
150 | + SLOT(updateRecorderError(QMediaRecorder::Error))); |
151 | + connect(m_audioRecorder, SIGNAL(actualLocationChanged(QUrl)), |
152 | + SLOT(updateActualLocation(QUrl))); |
153 | + connect(m_audioRecorder, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); |
154 | + connect(m_audioRecorder, SIGNAL(audioInputChanged(const QString&)), |
155 | + SIGNAL(audioInputChanged(const QString&))); |
156 | + |
157 | + m_audioSettings = m_audioRecorder->audioSettings(); |
158 | +} |
159 | + |
160 | +AudioRecorder::~AudioRecorder() |
161 | +{ |
162 | + delete m_audioRecorder; |
163 | +} |
164 | + |
165 | +AudioRecorder::RecorderState AudioRecorder::recorderState() const |
166 | +{ |
167 | + return RecorderState(m_audioRecorder->state()); |
168 | +} |
169 | + |
170 | +AudioRecorder::RecorderStatus AudioRecorder::recorderStatus() const |
171 | +{ |
172 | + return RecorderStatus(m_audioRecorder->status()); |
173 | +} |
174 | + |
175 | +AudioRecorder::Error AudioRecorder::errorCode() const |
176 | +{ |
177 | + return Error(m_audioRecorder->error()); |
178 | +} |
179 | + |
180 | +QString AudioRecorder::errorString() const |
181 | +{ |
182 | + return m_audioRecorder->errorString(); |
183 | +} |
184 | + |
185 | +QString AudioRecorder::outputLocation() const |
186 | +{ |
187 | + return m_audioRecorder->outputLocation().toString(); |
188 | +} |
189 | + |
190 | +QString AudioRecorder::actualLocation() const |
191 | +{ |
192 | + return m_audioRecorder->actualLocation().toString(); |
193 | +} |
194 | + |
195 | +qint64 AudioRecorder::duration() const |
196 | +{ |
197 | + return m_audioRecorder->duration(); |
198 | +} |
199 | + |
200 | +int AudioRecorder::bitRate() const |
201 | +{ |
202 | + return m_audioSettings.bitRate(); |
203 | +} |
204 | + |
205 | +int AudioRecorder::channelCount() const |
206 | +{ |
207 | + return m_audioSettings.channelCount(); |
208 | +} |
209 | + |
210 | +QString AudioRecorder::codec() const |
211 | +{ |
212 | + return m_audioSettings.codec(); |
213 | +} |
214 | + |
215 | +AudioRecorder::EncodingQuality AudioRecorder::quality() const |
216 | +{ |
217 | + return EncodingQuality(m_audioSettings.quality()); |
218 | +} |
219 | + |
220 | +int AudioRecorder::sampleRate() const |
221 | +{ |
222 | + return m_audioSettings.sampleRate(); |
223 | +} |
224 | + |
225 | +QString AudioRecorder::audioInput() const |
226 | +{ |
227 | + return m_audioRecorder->audioInput(); |
228 | +} |
229 | + |
230 | +void AudioRecorder::record() |
231 | +{ |
232 | + setRecorderState(RecordingState); |
233 | +} |
234 | + |
235 | +void AudioRecorder::stop() |
236 | +{ |
237 | + setRecorderState(StoppedState); |
238 | +} |
239 | + |
240 | +void AudioRecorder::pause() |
241 | +{ |
242 | + setRecorderState(PausedState); |
243 | +} |
244 | + |
245 | +void AudioRecorder::setRecorderState(AudioRecorder::RecorderState state) |
246 | +{ |
247 | + if (!m_audioRecorder) |
248 | + return; |
249 | + |
250 | + switch (state){ |
251 | + case AudioRecorder::RecordingState: { |
252 | + // Create temporary file to store audio recorded |
253 | + QDir dataLocation(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); |
254 | + QTemporaryFile outputFile(dataLocation.absoluteFilePath("audioXXXXXX%1").arg(m_fileExtension)); |
255 | + outputFile.setAutoRemove(false); |
256 | + outputFile.open(); |
257 | + outputFile.close(); |
258 | + setOutputLocation(outputFile.fileName()); |
259 | + |
260 | + m_audioRecorder->record(); |
261 | + break; |
262 | + } |
263 | + case AudioRecorder::StoppedState: |
264 | + m_audioRecorder->stop(); |
265 | + break; |
266 | + case AudioRecorder::PausedState: |
267 | + m_audioRecorder->pause(); |
268 | + break; |
269 | + } |
270 | +} |
271 | + |
272 | +void AudioRecorder::setOutputLocation(const QString &location) |
273 | +{ |
274 | + if (outputLocation() != location) { |
275 | + // FIXME: implement auto-removal of previous recordings |
276 | + m_audioRecorder->setOutputLocation(location); |
277 | + Q_EMIT outputLocationChanged(outputLocation()); |
278 | + } |
279 | +} |
280 | + |
281 | +void AudioRecorder::setBitRate(int rate) |
282 | +{ |
283 | + if (bitRate() != rate) { |
284 | + m_audioSettings.setBitRate(rate); |
285 | + Q_EMIT bitRateChanged(rate); |
286 | + } |
287 | +} |
288 | + |
289 | +void AudioRecorder::setChannelCount(int count) |
290 | +{ |
291 | + if (channelCount() != count) { |
292 | + m_audioSettings.setChannelCount(count); |
293 | + Q_EMIT channelCountChanged(count); |
294 | + } |
295 | +} |
296 | + |
297 | +void AudioRecorder::setCodec(const QString &audioCodec) |
298 | +{ |
299 | + if (codec() != audioCodec) { |
300 | + if (!m_audioRecorder->supportedAudioCodecs().contains(audioCodec)) { |
301 | + qWarning() << "AudioRecorder error: Unsupported Audio Codec: " << audioCodec; |
302 | + return; |
303 | + } |
304 | + |
305 | + if (audioCodec == "audio/vorbis" || |
306 | + audioCodec == "audio/speex" || |
307 | + audioCodec == "audio/FLAC") { |
308 | + |
309 | + m_audioRecorder->setContainerFormat("ogg"); |
310 | + m_fileExtension = ".ogg"; |
311 | + } else if (audioCodec == "audio/PCM") { |
312 | + m_audioRecorder->setContainerFormat("wav"); |
313 | + m_fileExtension = ".wav"; |
314 | + } else { |
315 | + m_audioRecorder->setContainerFormat("raw"); |
316 | + } |
317 | + |
318 | + m_audioSettings.setCodec(audioCodec); |
319 | + Q_EMIT codecChanged(audioCodec); |
320 | + } |
321 | +} |
322 | + |
323 | +void AudioRecorder::setQuality(AudioRecorder::EncodingQuality encodingQuality) |
324 | +{ |
325 | + if (quality() != encodingQuality) { |
326 | + m_audioSettings.setQuality(QMultimedia::EncodingQuality(encodingQuality)); |
327 | + Q_EMIT qualityChanged(encodingQuality); |
328 | + } |
329 | +} |
330 | + |
331 | +void AudioRecorder::setSampleRate(int rate) |
332 | +{ |
333 | + if (sampleRate() != rate) { |
334 | + m_audioSettings.setSampleRate(rate); |
335 | + Q_EMIT sampleRateChanged(rate); |
336 | + } |
337 | +} |
338 | + |
339 | +void AudioRecorder::setAudioInput(const QString &input) |
340 | +{ |
341 | + if (audioInput() != input) { |
342 | + m_audioRecorder->setAudioInput(input); |
343 | + Q_EMIT audioInputChanged(input); |
344 | + } |
345 | +} |
346 | + |
347 | +void AudioRecorder::updateRecorderError(QMediaRecorder::Error errorCode) |
348 | +{ |
349 | + qWarning() << "AudioRecorder error:" << errorString(); |
350 | + Q_EMIT errorChanged(Error(errorCode), errorString()); |
351 | +} |
352 | + |
353 | +void AudioRecorder::updateActualLocation(const QUrl &url) |
354 | +{ |
355 | + Q_EMIT actualLocationChanged(url.toString()); |
356 | +} |
357 | |
358 | === added file 'src/audiorecorder.h' |
359 | --- src/audiorecorder.h 1970-01-01 00:00:00 +0000 |
360 | +++ src/audiorecorder.h 2016-01-14 12:41:18 +0000 |
361 | @@ -0,0 +1,142 @@ |
362 | +/* |
363 | + * Copyright (C) 2015 Canonical, Ltd. |
364 | + * |
365 | + * Authors: |
366 | + * Arthur Renato Mello <arthur.mello@canonical.com> |
367 | + * |
368 | + * This file is part of messaging-app. |
369 | + * |
370 | + * messaging-app is free software; you can redistribute it and/or modify |
371 | + * it under the terms of the GNU General Public License as published by |
372 | + * the Free Software Foundation; version 3. |
373 | + * |
374 | + * messaging-app is distributed in the hope that it will be useful, |
375 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
376 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
377 | + * GNU General Public License for more details. |
378 | + * |
379 | + * You should have received a copy of the GNU General Public License |
380 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
381 | + */ |
382 | + |
383 | +#ifndef AUDIORECORDER_H |
384 | +#define AUDIORECORDER_H |
385 | + |
386 | +#include <QObject> |
387 | +#include <QAudioRecorder> |
388 | + |
389 | +class AudioRecorder : public QObject |
390 | +{ |
391 | + Q_OBJECT |
392 | + |
393 | + Q_ENUMS(EncodingQuality) |
394 | + Q_ENUMS(Error) |
395 | + Q_ENUMS(RecorderState) |
396 | + Q_ENUMS(RecorderStatus) |
397 | + |
398 | + Q_PROPERTY(RecorderState recorderState READ recorderState WRITE setRecorderState NOTIFY recorderStateChanged) |
399 | + Q_PROPERTY(RecorderStatus recorderStatus READ recorderStatus NOTIFY recorderStatusChanged) |
400 | + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) |
401 | + Q_PROPERTY(Error errorCode READ errorCode NOTIFY errorChanged) |
402 | + Q_PROPERTY(QString outputLocation READ outputLocation NOTIFY outputLocationChanged) |
403 | + Q_PROPERTY(QString actualLocation READ actualLocation NOTIFY actualLocationChanged) |
404 | + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) |
405 | + Q_PROPERTY(int bitRate READ bitRate WRITE setBitRate NOTIFY bitRateChanged); |
406 | + Q_PROPERTY(int channelCount READ channelCount WRITE setChannelCount NOTIFY channelCountChanged); |
407 | + Q_PROPERTY(QString codec READ codec WRITE setCodec NOTIFY codecChanged); |
408 | + Q_PROPERTY(EncodingQuality quality READ quality WRITE setQuality NOTIFY qualityChanged); |
409 | + Q_PROPERTY(int sampleRate READ sampleRate WRITE setSampleRate NOTIFY sampleRateChanged); |
410 | + Q_PROPERTY(QString audioInput READ audioInput WRITE setAudioInput NOTIFY audioInputChanged); |
411 | + |
412 | +public: |
413 | + enum EncodingQuality |
414 | + { |
415 | + VeryLowQuality = QMultimedia::VeryLowQuality, |
416 | + LowQuality = QMultimedia::LowQuality, |
417 | + NormalQuality = QMultimedia::NormalQuality, |
418 | + HighQuality = QMultimedia::HighQuality, |
419 | + VeryHighQuality = QMultimedia::VeryHighQuality |
420 | + }; |
421 | + |
422 | + enum Error |
423 | + { |
424 | + NoError = QMediaRecorder::NoError, |
425 | + ResourceError = QMediaRecorder::ResourceError, |
426 | + FormatError = QMediaRecorder::FormatError, |
427 | + OutOfSpaceError = QMediaRecorder::OutOfSpaceError |
428 | + }; |
429 | + |
430 | + enum RecorderState |
431 | + { |
432 | + StoppedState = QMediaRecorder::StoppedState, |
433 | + RecordingState = QMediaRecorder::RecordingState, |
434 | + PausedState = QMediaRecorder::PausedState |
435 | + }; |
436 | + |
437 | + enum RecorderStatus |
438 | + { |
439 | + UnavailableStatus = QMediaRecorder::UnavailableStatus, |
440 | + UnloadedStatus = QMediaRecorder::UnloadedStatus, |
441 | + LoadingStatus = QMediaRecorder::LoadingStatus, |
442 | + LoadedStatus = QMediaRecorder::LoadedStatus, |
443 | + StartingStatus = QMediaRecorder::StartingStatus, |
444 | + RecordingStatus = QMediaRecorder::RecordingStatus, |
445 | + PausedStatus = QMediaRecorder::PausedStatus, |
446 | + FinalizingStatus = QMediaRecorder::FinalizingStatus |
447 | + }; |
448 | + |
449 | + AudioRecorder(QObject *parent = 0); |
450 | + ~AudioRecorder(); |
451 | + |
452 | + RecorderState recorderState() const; |
453 | + RecorderStatus recorderStatus() const; |
454 | + Error errorCode() const; |
455 | + QString errorString() const; |
456 | + QString outputLocation() const; |
457 | + QString actualLocation() const; |
458 | + qint64 duration() const; |
459 | + int bitRate() const; |
460 | + int channelCount() const; |
461 | + QString codec() const; |
462 | + EncodingQuality quality() const; |
463 | + int sampleRate() const; |
464 | + QString audioInput() const; |
465 | + |
466 | +public Q_SLOTS: |
467 | + void record(); |
468 | + void stop(); |
469 | + void pause(); |
470 | + void setRecorderState(AudioRecorder::RecorderState state); |
471 | + void setOutputLocation(const QString &location); |
472 | + void setBitRate(int rate); |
473 | + void setChannelCount(int count); |
474 | + void setCodec(const QString &audioCodec); |
475 | + void setQuality(AudioRecorder::EncodingQuality encodingQuality); |
476 | + void setSampleRate(int rate); |
477 | + void setAudioInput(const QString &input); |
478 | + |
479 | +Q_SIGNALS: |
480 | + void recorderStateChanged(); |
481 | + void recorderStatusChanged(); |
482 | + void errorChanged(AudioRecorder::Error errorCode, const QString &errorString); |
483 | + void outputLocationChanged(const QString &location); |
484 | + void actualLocationChanged(const QString &location); |
485 | + void durationChanged(qint64 duration); |
486 | + void bitRateChanged(int rate); |
487 | + void channelCountChanged(int count); |
488 | + void codecChanged(const QString &codec); |
489 | + void qualityChanged(AudioRecorder::EncodingQuality quality); |
490 | + void sampleRateChanged(int rate); |
491 | + void audioInputChanged(const QString &input); |
492 | + |
493 | +private Q_SLOTS: |
494 | + void updateRecorderError(QMediaRecorder::Error); |
495 | + void updateActualLocation(const QUrl&); |
496 | + |
497 | +private: |
498 | + QAudioRecorder *m_audioRecorder; |
499 | + QAudioEncoderSettings m_audioSettings; |
500 | + QString m_fileExtension; |
501 | +}; |
502 | + |
503 | +#endif // AUDIORECORDER_H |
504 | |
505 | === added file 'src/fileoperations.cpp' |
506 | --- src/fileoperations.cpp 1970-01-01 00:00:00 +0000 |
507 | +++ src/fileoperations.cpp 2016-01-14 12:41:18 +0000 |
508 | @@ -0,0 +1,58 @@ |
509 | +/* |
510 | + * Copyright (C) 2015 Canonical, Ltd. |
511 | + * |
512 | + * Authors: |
513 | + * Arthur Mello <arthur.mello@canonical.com> |
514 | + * |
515 | + * This file is part of messaging-app. |
516 | + * |
517 | + * messaging-app is free software; you can redistribute it and/or modify |
518 | + * it under the terms of the GNU General Public License as published by |
519 | + * the Free Software Foundation; version 3. |
520 | + * |
521 | + * messaging-app is distributed in the hope that it will be useful, |
522 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
523 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
524 | + * GNU General Public License for more details. |
525 | + * |
526 | + * You should have received a copy of the GNU General Public License |
527 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
528 | + */ |
529 | + |
530 | +#include "fileoperations.h" |
531 | + |
532 | +#include <QDir> |
533 | +#include <QFile> |
534 | +#include <QTemporaryFile> |
535 | + |
536 | +FileOperations::FileOperations(QObject *parent) |
537 | + : QObject(parent) |
538 | +{ |
539 | +} |
540 | + |
541 | +FileOperations::~FileOperations() |
542 | +{ |
543 | +} |
544 | + |
545 | +QString FileOperations::getTemporaryFile(const QString &fileExtension) const |
546 | +{ |
547 | + //TODO remove once lp:1420728 is fixed |
548 | + QTemporaryFile tmp(QDir::tempPath() + "/tmpXXXXXX" + fileExtension); |
549 | + tmp.open(); |
550 | + return tmp.fileName(); |
551 | +} |
552 | + |
553 | +bool FileOperations::link(const QString &from, const QString &to) |
554 | +{ |
555 | + return QFile::link(from, to); |
556 | +} |
557 | + |
558 | +bool FileOperations::remove(const QString &fileName) |
559 | +{ |
560 | + return QFile::remove(fileName); |
561 | +} |
562 | + |
563 | +qint64 FileOperations::size(const QString &filePath) |
564 | +{ |
565 | + return QFile(filePath).size(); |
566 | +} |
567 | |
568 | === added file 'src/fileoperations.h' |
569 | --- src/fileoperations.h 1970-01-01 00:00:00 +0000 |
570 | +++ src/fileoperations.h 2016-01-14 12:41:18 +0000 |
571 | @@ -0,0 +1,41 @@ |
572 | +/* |
573 | + * Copyright (C) 2015 Canonical, Ltd. |
574 | + * |
575 | + * Authors: |
576 | + * Arthur Mello <arthur.mello@canonical.com> |
577 | + * |
578 | + * This file is part of messaging-app. |
579 | + * |
580 | + * messaging-app is free software; you can redistribute it and/or modify |
581 | + * it under the terms of the GNU General Public License as published by |
582 | + * the Free Software Foundation; version 3. |
583 | + * |
584 | + * messaging-app is distributed in the hope that it will be useful, |
585 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
586 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
587 | + * GNU General Public License for more details. |
588 | + * |
589 | + * You should have received a copy of the GNU General Public License |
590 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
591 | + */ |
592 | + |
593 | +#ifndef FILEOPERATIONS_H |
594 | +#define FILEOPERATIONS_H |
595 | + |
596 | +#include <QObject> |
597 | + |
598 | +class FileOperations : public QObject |
599 | +{ |
600 | + Q_OBJECT |
601 | + |
602 | +public: |
603 | + FileOperations(QObject *parent = 0); |
604 | + ~FileOperations(); |
605 | + |
606 | + Q_INVOKABLE QString getTemporaryFile(const QString &fileExtension) const; |
607 | + Q_INVOKABLE bool link(const QString &from, const QString &to); |
608 | + Q_INVOKABLE bool remove(const QString &fileName); |
609 | + Q_INVOKABLE qint64 size(const QString &filePath); |
610 | +}; |
611 | + |
612 | +#endif // FILEOPERATIONS_H |
613 | |
614 | === modified file 'src/messagingapplication.cpp' |
615 | --- src/messagingapplication.cpp 2015-11-23 19:51:16 +0000 |
616 | +++ src/messagingapplication.cpp 2016-01-14 12:41:18 +0000 |
617 | @@ -1,5 +1,5 @@ |
618 | /* |
619 | - * Copyright (C) 2012 Canonical, Ltd. |
620 | + * Copyright (C) 2012-2015 Canonical, Ltd. |
621 | * |
622 | * This file is part of messaging-app. |
623 | * |
624 | @@ -17,6 +17,9 @@ |
625 | */ |
626 | |
627 | #include "messagingapplication.h" |
628 | +#include "audiorecorder.h" |
629 | +#include "fileoperations.h" |
630 | +#include "stickers-history-model.h" |
631 | |
632 | #include <libnotify/notify.h> |
633 | |
634 | @@ -24,6 +27,7 @@ |
635 | #include <QUrl> |
636 | #include <QUrlQuery> |
637 | #include <QDebug> |
638 | +#include <QDir> |
639 | #include <QStringList> |
640 | #include <QQuickItem> |
641 | #include <QQmlComponent> |
642 | @@ -36,6 +40,7 @@ |
643 | #include "config.h" |
644 | #include <QQmlEngine> |
645 | #include <QMimeDatabase> |
646 | +#include <QStandardPaths> |
647 | #include <QVersitReader> |
648 | |
649 | using namespace QtVersit; |
650 | @@ -65,12 +70,31 @@ |
651 | } |
652 | } |
653 | |
654 | +static QObject* FileOperations_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine) |
655 | +{ |
656 | + Q_UNUSED(engine); |
657 | + Q_UNUSED(scriptEngine); |
658 | + return new FileOperations(); |
659 | +} |
660 | + |
661 | +static QObject* StickersHistoryModel_singleton_factory(QQmlEngine* engine, QJSEngine* scriptEngine) |
662 | +{ |
663 | + Q_UNUSED(engine); |
664 | + Q_UNUSED(scriptEngine); |
665 | + return new StickersHistoryModel(); |
666 | +} |
667 | + |
668 | MessagingApplication::MessagingApplication(int &argc, char **argv) |
669 | : QGuiApplication(argc, argv), m_view(0), m_applicationIsReady(false) |
670 | { |
671 | setApplicationName("MessagingApp"); |
672 | } |
673 | |
674 | +bool MessagingApplication::fullscreen() const |
675 | +{ |
676 | + return m_view->windowState() == Qt::WindowFullScreen; |
677 | +} |
678 | + |
679 | bool MessagingApplication::setup() |
680 | { |
681 | installIconPath(); |
682 | @@ -149,6 +173,14 @@ |
683 | m_view->rootContext()->setContextProperty("QTCONTACTS_MANAGER_OVERRIDE", contactsBackend); |
684 | } |
685 | |
686 | + QDir dataLocation(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); |
687 | + m_view->rootContext()->setContextProperty("dataLocation", dataLocation.absolutePath()); |
688 | + dataLocation.mkpath("stickers"); |
689 | + const char* uri = "messagingapp.private"; |
690 | + qmlRegisterType<AudioRecorder>(uri, 0, 1, "AudioRecorder"); |
691 | + qmlRegisterSingletonType<FileOperations>(uri, 0, 1, "FileOperations", FileOperations_singleton_factory); |
692 | + qmlRegisterSingletonType<StickersHistoryModel>(uri, 0, 1, "StickersHistoryModel", StickersHistoryModel_singleton_factory); |
693 | + |
694 | // used by autopilot tests to load vcards during tests |
695 | QByteArray testData = qgetenv("QTCONTACTS_PRELOAD_VCARD"); |
696 | m_view->rootContext()->setContextProperty("QTCONTACTS_PRELOAD_VCARD", testData); |
697 | @@ -176,6 +208,17 @@ |
698 | } |
699 | } |
700 | |
701 | +void MessagingApplication::setFullscreen(bool fullscreen) |
702 | +{ |
703 | + if (fullscreen) { |
704 | + m_view->setWindowState(Qt::WindowFullScreen); |
705 | + } else { |
706 | + m_view->setWindowState(Qt::WindowNoState); |
707 | + } |
708 | + |
709 | + Q_EMIT fullscreenChanged(); |
710 | +} |
711 | + |
712 | void MessagingApplication::onViewStatusChanged(QQuickView::Status status) |
713 | { |
714 | if (status != QQuickView::Ready) { |
715 | |
716 | === modified file 'src/messagingapplication.h' |
717 | --- src/messagingapplication.h 2015-11-18 16:36:51 +0000 |
718 | +++ src/messagingapplication.h 2016-01-14 12:41:18 +0000 |
719 | @@ -1,5 +1,5 @@ |
720 | /* |
721 | - * Copyright (C) 2012-2013 Canonical, Ltd. |
722 | + * Copyright (C) 2012-2015 Canonical, Ltd. |
723 | * |
724 | * This file is part of messaging-app. |
725 | * |
726 | @@ -26,13 +26,18 @@ |
727 | class MessagingApplication : public QGuiApplication |
728 | { |
729 | Q_OBJECT |
730 | + Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged) |
731 | |
732 | public: |
733 | MessagingApplication(int &argc, char **argv); |
734 | virtual ~MessagingApplication(); |
735 | |
736 | + bool fullscreen() const; |
737 | bool setup(); |
738 | |
739 | +Q_SIGNALS: |
740 | + void fullscreenChanged(); |
741 | + |
742 | public Q_SLOTS: |
743 | void activateWindow(); |
744 | void parseArgument(const QString &arg); |
745 | @@ -41,6 +46,7 @@ |
746 | void showNotificationMessage(const QString &message, const QString &icon = QString()); |
747 | |
748 | private Q_SLOTS: |
749 | + void setFullscreen(bool fullscreen); |
750 | void onViewStatusChanged(QQuickView::Status status); |
751 | void onApplicationReady(); |
752 | |
753 | |
754 | === added file 'src/qml/AttachmentPanel.qml' |
755 | --- src/qml/AttachmentPanel.qml 1970-01-01 00:00:00 +0000 |
756 | +++ src/qml/AttachmentPanel.qml 2016-01-14 12:41:18 +0000 |
757 | @@ -0,0 +1,174 @@ |
758 | +/* |
759 | + * Copyright 2015 Canonical Ltd. |
760 | + * |
761 | + * This file is part of messaging-app. |
762 | + * |
763 | + * messaging-app is free software; you can redistribute it and/or modify |
764 | + * it under the terms of the GNU General Public License as published by |
765 | + * the Free Software Foundation; version 3. |
766 | + * |
767 | + * messaging-app is distributed in the hope that it will be useful, |
768 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
769 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
770 | + * GNU General Public License for more details. |
771 | + * |
772 | + * You should have received a copy of the GNU General Public License |
773 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
774 | + */ |
775 | + |
776 | +import QtQuick 2.0 |
777 | +import Ubuntu.Components 1.3 |
778 | +import Ubuntu.Components.ListItems 1.3 as ListItem |
779 | +import QtQuick.Layouts 1.0 |
780 | + |
781 | +Item { |
782 | + id: panel |
783 | + signal attachmentAvailable(var attachment) |
784 | + |
785 | + property bool expanded: false |
786 | + |
787 | + function show() { |
788 | + expanded = true |
789 | + } |
790 | + |
791 | + function hide() { |
792 | + expanded = false |
793 | + } |
794 | + |
795 | + height: expanded ? childrenRect.height + units.gu(3): 0 |
796 | + opacity: expanded ? 1 : 0 |
797 | + visible: opacity > 0 |
798 | + Behavior on height { |
799 | + UbuntuNumberAnimation {} |
800 | + } |
801 | + Behavior on opacity { |
802 | + UbuntuNumberAnimation { } |
803 | + } |
804 | + |
805 | + enabled: expanded |
806 | + |
807 | + Connections { |
808 | + target: Qt.inputMethod |
809 | + onVisibleChanged: { |
810 | + if (Qt.inputMethod.visible) { |
811 | + panel.expanded = false |
812 | + } |
813 | + } |
814 | + } |
815 | + |
816 | + ContentImport { |
817 | + id: contentImporter |
818 | + |
819 | + onContentReceived: { |
820 | + var attachment = {} |
821 | + var filePath = String(contentUrl).replace('file://', '') |
822 | + attachment["contentType"] = application.fileMimeType(filePath) |
823 | + attachment["name"] = filePath.split('/').reverse()[0] |
824 | + attachment["filePath"] = filePath |
825 | + panel.attachmentAvailable(attachment) |
826 | + hide() |
827 | + } |
828 | + } |
829 | + |
830 | + ListItem.ThinDivider { |
831 | + id: divider |
832 | + anchors { |
833 | + top: parent.top |
834 | + left: parent.left |
835 | + right: parent.right |
836 | + } |
837 | + } |
838 | + |
839 | + GridLayout { |
840 | + id: grid |
841 | + |
842 | + property int iconSize: units.gu(3) |
843 | + property int buttonSpacing: units.gu(2) |
844 | + anchors { |
845 | + top: parent.top |
846 | + topMargin: units.gu(3) |
847 | + left: parent.left |
848 | + right: parent.right |
849 | + } |
850 | + |
851 | + height: childrenRect.height |
852 | + columns: 4 |
853 | + rowSpacing: units.gu(3) |
854 | + |
855 | + TransparentButton { |
856 | + id: pictureButton |
857 | + objectName: "pictureButton" |
858 | + iconName: "stock_image" |
859 | + iconSize: grid.iconSize |
860 | + spacing: grid.buttonSpacing |
861 | + text: i18n.tr("Image") |
862 | + Layout.alignment: Qt.AlignHCenter |
863 | + onClicked: { |
864 | + contentImporter.requestPicture() |
865 | + } |
866 | + } |
867 | + |
868 | + TransparentButton { |
869 | + id: videoButton |
870 | + objectName: "videoButton" |
871 | + iconName: "stock_video" |
872 | + iconSize: grid.iconSize |
873 | + spacing: grid.buttonSpacing |
874 | + text: i18n.tr("Video") |
875 | + Layout.alignment: Qt.AlignHCenter |
876 | + onClicked: { |
877 | + contentImporter.requestVideo() |
878 | + } |
879 | + } |
880 | + |
881 | + // FIXME: enable generic file sharing if we ever support it |
882 | + /*TransparentButton { |
883 | + id: fileButton |
884 | + objectName: "fileButton" |
885 | + iconSource: Qt.resolvedUrl("assets/stock_document.svg") |
886 | + iconSize: grid.iconSize |
887 | + spacing: grid.buttonSpacing |
888 | + text: i18n.tr("File") |
889 | + Layout.alignment: Qt.AlignHCenter |
890 | + onClicked: { |
891 | + contentImporter.requestDocument() |
892 | + } |
893 | + }*/ |
894 | + |
895 | + // FIXME: enable location sharing if we ever support it |
896 | + /*TransparentButton { |
897 | + id: locationButton |
898 | + objectName: "locationButton" |
899 | + iconName: "location" |
900 | + iconSize: grid.iconSize |
901 | + spacing: grid.buttonSpacing |
902 | + text: i18n.tr("Location") |
903 | + Layout.alignment: Qt.AlignHCenter |
904 | + }*/ |
905 | + |
906 | + TransparentButton { |
907 | + id: contactButton |
908 | + objectName: "contactButton" |
909 | + iconName: "stock_contact" |
910 | + iconSize: grid.iconSize |
911 | + spacing: grid.buttonSpacing |
912 | + text: i18n.tr("Contact") |
913 | + Layout.alignment: Qt.AlignHCenter |
914 | + onClicked: { |
915 | + contentImporter.requestContact() |
916 | + } |
917 | + } |
918 | + |
919 | + // FIXME: enable that once we add support for burn-after-read |
920 | + /*TransparentButton { |
921 | + id: burnAfterReadButton |
922 | + objectName: "burnAfterReadButton" |
923 | + iconSource: Qt.resolvedUrl("assets/burn-after-read.svg") |
924 | + iconSize: grid.iconSize |
925 | + spacing: grid.buttonSpacing |
926 | + text: i18n.tr("Burn after read") |
927 | + Layout.alignment: Qt.AlignHCenter |
928 | + }*/ |
929 | + } |
930 | +} |
931 | + |
932 | |
933 | === added file 'src/qml/AudioPlaybackBar.qml' |
934 | --- src/qml/AudioPlaybackBar.qml 1970-01-01 00:00:00 +0000 |
935 | +++ src/qml/AudioPlaybackBar.qml 2016-01-14 12:41:18 +0000 |
936 | @@ -0,0 +1,187 @@ |
937 | +/* |
938 | + * Copyright 2015 Canonical Ltd. |
939 | + * |
940 | + * This file is part of messaging-app. |
941 | + * |
942 | + * messaging-app is free software; you can redistribute it and/or modify |
943 | + * it under the terms of the GNU General Public License as published by |
944 | + * the Free Software Foundation; version 3. |
945 | + * |
946 | + * messaging-app is distributed in the hope that it will be useful, |
947 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
948 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
949 | + * GNU General Public License for more details. |
950 | + * |
951 | + * You should have received a copy of the GNU General Public License |
952 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
953 | + */ |
954 | + |
955 | +import QtQuick 2.0 |
956 | +import QtMultimedia 5.0 |
957 | +import Ubuntu.Components 1.3 |
958 | +import Ubuntu.Components.Themes.Ambiance 1.3 |
959 | +import "dateUtils.js" as DateUtils |
960 | + |
961 | +Item { |
962 | + id: playbackBar |
963 | + |
964 | + signal resetRequested() |
965 | + property string source: "" |
966 | + property int duration: audioPlayer.duration |
967 | + readonly property bool playing: audioPlayer.playing |
968 | + |
969 | + Loader { |
970 | + id: audioPlayer |
971 | + readonly property bool playing: ready ? item.playing : false |
972 | + readonly property bool paused: ready ? item.paused : false |
973 | + readonly property bool stopped: ready ? item.stopped : false |
974 | + readonly property int position: ready ? item.position : 0 |
975 | + readonly property int duration: ready ? item.duration : 0 |
976 | + readonly property bool ready: status == Loader.Ready |
977 | + readonly property int playbackState: ready ? item.playbackState : Audio.StoppedState |
978 | + function play() { |
979 | + audioPlayer.active = true |
980 | + item.play() |
981 | + } |
982 | + function stop() { |
983 | + item.stop() |
984 | + audioPlayer.active = false |
985 | + } |
986 | + function pause() { item.pause() } |
987 | + function seek(pos) { item.seek(pos) } |
988 | + active: false |
989 | + sourceComponent: audioPlayerComponent |
990 | + } |
991 | + |
992 | + Component { |
993 | + id: audioPlayerComponent |
994 | + Audio { |
995 | + id: audioPlayer1 |
996 | + readonly property bool playing: audioPlayer1.playbackState == Audio.PlayingState |
997 | + readonly property bool paused: audioPlayer1.playbackState == Audio.PausedState |
998 | + readonly property bool stopped: audioPlayer1.playbackState == Audio.StoppedState |
999 | + source: playbackBar.source |
1000 | + } |
1001 | + } |
1002 | + |
1003 | + TransparentButton { |
1004 | + id: closeButton |
1005 | + objectName: "closeButton" |
1006 | + |
1007 | + anchors { |
1008 | + left: parent.left |
1009 | + leftMargin: units.gu(2) |
1010 | + verticalCenter: parent.verticalCenter |
1011 | + } |
1012 | + |
1013 | + iconName: "close" |
1014 | + |
1015 | + onClicked: { |
1016 | + playbackBar.resetRequested() |
1017 | + } |
1018 | + } |
1019 | + |
1020 | + Item { |
1021 | + id: audioPreview |
1022 | + anchors { |
1023 | + top: parent.top |
1024 | + bottom: parent.bottom |
1025 | + left: closeButton.right |
1026 | + right: parent.right |
1027 | + topMargin: units.gu(1) |
1028 | + bottomMargin: units.gu(1) |
1029 | + leftMargin: units.gu(3) |
1030 | + rightMargin: units.gu(1) |
1031 | + } |
1032 | + |
1033 | + TransparentButton { |
1034 | + id: playButton |
1035 | + |
1036 | + anchors { |
1037 | + top: parent.top |
1038 | + left: parent.left |
1039 | + topMargin: units.gu(0.5) |
1040 | + } |
1041 | + |
1042 | + iconColor: "grey" |
1043 | + iconName: audioPlayer.playing ? "media-playback-pause" : "media-playback-start" |
1044 | + |
1045 | + textSize: FontUtils.sizeToPixels("x-small") |
1046 | + text: { |
1047 | + if (audioPlayer.playing) { |
1048 | + return DateUtils.formattedTime(audioPlayer.position/ 1000) |
1049 | + } |
1050 | + return DateUtils.formattedTime(playbackBar.duration / 1000) |
1051 | + } |
1052 | + |
1053 | + onClicked: { |
1054 | + if (audioPlayer.playing) { |
1055 | + audioPlayer.pause() |
1056 | + } else { |
1057 | + audioPlayer.play() |
1058 | + } |
1059 | + } |
1060 | + } |
1061 | + |
1062 | + Slider { |
1063 | + id: slider |
1064 | + Connections { |
1065 | + target: audioPlayer |
1066 | + onDurationChanged: { |
1067 | + if (slider.maximumValue == 100) { |
1068 | + slider.maximumValue = audioPlayer.duration |
1069 | + } |
1070 | + } |
1071 | + } |
1072 | + style: SliderStyle { |
1073 | + Component.onCompleted: thumb.visible = false |
1074 | + Connections { |
1075 | + target: audioPlayer |
1076 | + onPlaybackStateChanged: { |
1077 | + thumb.visible = !audioPlayer.stopped |
1078 | + if (!thumb.visible) { |
1079 | + audioPlayer.seek(0) |
1080 | + } |
1081 | + } |
1082 | + } |
1083 | + } |
1084 | + enabled: !audioPlayer.stopped |
1085 | + function formatValue(v) { return DateUtils.formattedTime(v/1000) } |
1086 | + anchors { |
1087 | + top: parent.top |
1088 | + bottom: parent.bottom |
1089 | + left: playButton.right |
1090 | + right: parent.right |
1091 | + leftMargin: units.gu(1) |
1092 | + } |
1093 | + height: units.gu(3) |
1094 | + minimumValue: 0.0 |
1095 | + maximumValue: 100 |
1096 | + value: audioPlayer.position |
1097 | + activeFocusOnPress: false |
1098 | + onPressedChanged: { |
1099 | + if (!pressed) { |
1100 | + if (audioPlayer.playing || audioPlayer.paused) { |
1101 | + audioPlayer.seek(value) |
1102 | + } else { |
1103 | + audioPlayer.muted = true |
1104 | + // we only get the duration while playing |
1105 | + audioPlayer.play() |
1106 | + audioPlayer.pause() |
1107 | + if (audioPlayer.duration == 100) { |
1108 | + audioPlayer.seek((audioPlayer.duration*value)/100) |
1109 | + } else { |
1110 | + audioPlayer.seek(value) |
1111 | + } |
1112 | + audioPlayer.muted = false |
1113 | + |
1114 | + } |
1115 | + value = Qt.binding(function(){ return audioPlayer.position}) |
1116 | + } |
1117 | + } |
1118 | + } |
1119 | + } |
1120 | + |
1121 | + |
1122 | +} |
1123 | + |
1124 | |
1125 | === added file 'src/qml/AudioRecordingBar.qml' |
1126 | --- src/qml/AudioRecordingBar.qml 1970-01-01 00:00:00 +0000 |
1127 | +++ src/qml/AudioRecordingBar.qml 2016-01-14 12:41:18 +0000 |
1128 | @@ -0,0 +1,137 @@ |
1129 | +/* |
1130 | + * Copyright 2015 Canonical Ltd. |
1131 | + * |
1132 | + * This file is part of messaging-app. |
1133 | + * |
1134 | + * messaging-app is free software; you can redistribute it and/or modify |
1135 | + * it under the terms of the GNU General Public License as published by |
1136 | + * the Free Software Foundation; version 3. |
1137 | + * |
1138 | + * messaging-app is distributed in the hope that it will be useful, |
1139 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1140 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1141 | + * GNU General Public License for more details. |
1142 | + * |
1143 | + * You should have received a copy of the GNU General Public License |
1144 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1145 | + */ |
1146 | + |
1147 | +import QtQuick 2.0 |
1148 | +import Ubuntu.Components 1.3 |
1149 | +import messagingapp.private 0.1 |
1150 | +import "dateUtils.js" as DateUtils |
1151 | + |
1152 | +Item { |
1153 | + id: recordingBar |
1154 | + opacity: audioRecorder.recording ? 1.0 : 0.0 |
1155 | + Behavior on opacity { UbuntuNumberAnimation {} } |
1156 | + visible: opacity > 0 |
1157 | + |
1158 | + property int duration: 0 |
1159 | + readonly property bool recording: audioRecorder.recording |
1160 | + property real buttonOpacity: 1 |
1161 | + |
1162 | + signal audioRecorded(var audio) |
1163 | + |
1164 | + function startRecording() { |
1165 | + audioRecorder.record() |
1166 | + } |
1167 | + |
1168 | + function stopRecording() { |
1169 | + audioRecorder.stop() |
1170 | + } |
1171 | + |
1172 | + Loader { |
1173 | + id: audioRecorder |
1174 | + readonly property bool ready: status == Loader.Ready |
1175 | + readonly property bool recording: ready ? item.recorderState == AudioRecorder.RecordingState : false |
1176 | + readonly property int duration: ready ? item.duration : 0 |
1177 | + function record() { |
1178 | + audioRecorder.active = true |
1179 | + item.record() |
1180 | + } |
1181 | + function stop() { |
1182 | + item.stop() |
1183 | + audioRecorder.active = false |
1184 | + } |
1185 | + |
1186 | + active: false |
1187 | + sourceComponent: audioRecorderComponent |
1188 | + } |
1189 | + |
1190 | + Component { |
1191 | + id: audioRecorderComponent |
1192 | + AudioRecorder { |
1193 | + readonly property bool recording: recorderState == AudioRecorder.RecordingState |
1194 | + |
1195 | + onRecorderStateChanged: { |
1196 | + if (recorderState == AudioRecorder.StoppedState && actualLocation != "") { |
1197 | + var filePath = actualLocation |
1198 | + |
1199 | + if (application.fileMimeType(filePath).toLowerCase().indexOf("audio/") <= -1) { |
1200 | + //If the recording process is too quick the generated file is not an audio one and should be ignored |
1201 | + return; |
1202 | + } |
1203 | + |
1204 | + var attachment = {} |
1205 | + attachment["contentType"] = application.fileMimeType(filePath) |
1206 | + attachment["name"] = filePath.split('/').reverse()[0] |
1207 | + attachment["filePath"] = filePath |
1208 | + recordingBar.audioRecorded(attachment) |
1209 | + |
1210 | + recordingBar.duration = duration |
1211 | + } |
1212 | + } |
1213 | + |
1214 | + codec: "audio/vorbis" |
1215 | + quality: AudioRecorder.VeryHighQuality |
1216 | + } |
1217 | + } |
1218 | + |
1219 | + TransparentButton { |
1220 | + id: recordingIcon |
1221 | + objectName: "recordingIcon" |
1222 | + iconPulsate: true |
1223 | + sideBySide: true |
1224 | + spacing: units.gu(1) |
1225 | + opacity: buttonOpacity |
1226 | + |
1227 | + anchors { |
1228 | + left: parent.left |
1229 | + leftMargin: units.gu(2) |
1230 | + verticalCenter: parent.verticalCenter |
1231 | + } |
1232 | + |
1233 | + focus: false |
1234 | + |
1235 | + iconColor: "red" |
1236 | + iconName: "audio-input-microphone-symbolic" |
1237 | + |
1238 | + textSize: FontUtils.sizeToPixels("x-small") |
1239 | + text: { |
1240 | + if (audioRecorder.recording) { |
1241 | + return DateUtils.formattedTime(audioRecorder.duration / 1000) |
1242 | + } |
1243 | + return DateUtils.formattedTime(0) |
1244 | + } |
1245 | + } |
1246 | + |
1247 | + Label { |
1248 | + anchors { |
1249 | + top: parent.top |
1250 | + bottom: parent.bottom |
1251 | + left: recordingIcon.right |
1252 | + right: parent.right |
1253 | + topMargin: units.gu(1) |
1254 | + bottomMargin: units.gu(1) |
1255 | + leftMargin: units.gu(1) |
1256 | + rightMargin: units.gu(1) |
1257 | + } |
1258 | + opacity: buttonOpacity |
1259 | + |
1260 | + text: i18n.tr("<<< Swipe to cancel") |
1261 | + verticalAlignment: Text.AlignVCenter |
1262 | + horizontalAlignment: Text.AlignHCenter |
1263 | + } |
1264 | +} |
1265 | + |
1266 | |
1267 | === modified file 'src/qml/CMakeLists.txt' |
1268 | --- src/qml/CMakeLists.txt 2014-08-11 22:22:59 +0000 |
1269 | +++ src/qml/CMakeLists.txt 2016-01-14 12:41:18 +0000 |
1270 | @@ -17,3 +17,4 @@ |
1271 | |
1272 | add_subdirectory(MMS) |
1273 | add_subdirectory(Dialogs) |
1274 | +add_subdirectory(Stickers) |
1275 | |
1276 | === added file 'src/qml/ComposeBar.qml' |
1277 | --- src/qml/ComposeBar.qml 1970-01-01 00:00:00 +0000 |
1278 | +++ src/qml/ComposeBar.qml 2016-01-14 12:41:18 +0000 |
1279 | @@ -0,0 +1,534 @@ |
1280 | +/* |
1281 | + * Copyright 2012-2015 Canonical Ltd. |
1282 | + * |
1283 | + * This file is part of messaging-app. |
1284 | + * |
1285 | + * messaging-app is free software; you can redistribute it and/or modify |
1286 | + * it under the terms of the GNU General Public License as published by |
1287 | + * the Free Software Foundation; version 3. |
1288 | + * |
1289 | + * messaging-app is distributed in the hope that it will be useful, |
1290 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1291 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1292 | + * GNU General Public License for more details. |
1293 | + * |
1294 | + * You should have received a copy of the GNU General Public License |
1295 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1296 | + */ |
1297 | + |
1298 | +import QtQuick 2.0 |
1299 | +import QtMultimedia 5.0 |
1300 | +import Ubuntu.Components 1.3 |
1301 | +import Ubuntu.Components.ListItems 1.3 as ListItem |
1302 | +import Ubuntu.Components.Popups 1.3 |
1303 | +import Ubuntu.Content 0.1 |
1304 | +import Ubuntu.Telephony 0.1 |
1305 | +import "Stickers" |
1306 | + |
1307 | +Item { |
1308 | + id: composeBar |
1309 | + |
1310 | + property bool showContents: true |
1311 | + property int maxHeight: textEntry.height + units.gu(2) |
1312 | + property variant attachments: [] |
1313 | + property bool canSend: true |
1314 | + property alias text: messageTextArea.text |
1315 | + property bool audioAttached: attachments.count == 1 && attachments.get(0).contentType.toLowerCase().indexOf("audio/") > -1 |
1316 | + // Audio QML component needs to process the recorded audio to find duration and AudioRecorder seems to erase duration after some events |
1317 | + property alias audioRecordedDuration: audioRecordingBar.duration |
1318 | + property alias recording: audioRecordingBar.recording |
1319 | + property bool oskEnabled: true |
1320 | + |
1321 | + signal sendRequested(string text, var attachments) |
1322 | + |
1323 | + // internal properties |
1324 | + property int _activeAttachmentIndex: -1 |
1325 | + property int _defaultHeight: textEntry.height + attachmentPanel.height + stickersPicker.height + units.gu(2) |
1326 | + |
1327 | + Component.onDestruction: { |
1328 | + composeBar.reset() |
1329 | + } |
1330 | + |
1331 | + function forceFocus() { |
1332 | + messageTextArea.forceActiveFocus() |
1333 | + } |
1334 | + |
1335 | + function reset() { |
1336 | + if (composeBar.audioAttached) { |
1337 | + FileOperations.remove(attachments.get(0).filePath) |
1338 | + } |
1339 | + |
1340 | + textEntry.text = "" |
1341 | + attachments.clear() |
1342 | + } |
1343 | + |
1344 | + function addAttachments(transfer) { |
1345 | + for (var i = 0; i < transfer.items.length; i++) { |
1346 | + if (String(transfer.items[i].text).length > 0) { |
1347 | + composeBar.text = String(transfer.items[i].text) |
1348 | + continue |
1349 | + } |
1350 | + var attachment = {} |
1351 | + if (!startsWith(String(transfer.items[i].url),"file://")) { |
1352 | + composeBar.text = String(transfer.items[i].url) |
1353 | + continue |
1354 | + } |
1355 | + var filePath = String(transfer.items[i].url).replace('file://', '') |
1356 | + // get only the basename |
1357 | + attachment["contentType"] = application.fileMimeType(filePath) |
1358 | + if (startsWith(attachment["contentType"], "text/vcard") || |
1359 | + startsWith(attachment["contentType"], "text/x-vcard")) { |
1360 | + attachment["name"] = "contact.vcf" |
1361 | + } else { |
1362 | + attachment["name"] = filePath.split('/').reverse()[0] |
1363 | + } |
1364 | + attachment["filePath"] = filePath |
1365 | + attachments.append(attachment) |
1366 | + } |
1367 | + } |
1368 | + |
1369 | + anchors.bottom: isSearching ? parent.bottom : keyboard.top |
1370 | + anchors.left: parent.left |
1371 | + anchors.right: parent.right |
1372 | + height: showContents ? Math.min(_defaultHeight, maxHeight) : 0 |
1373 | + visible: showContents |
1374 | + clip: true |
1375 | + |
1376 | + Behavior on height { |
1377 | + UbuntuNumberAnimation { } |
1378 | + } |
1379 | + |
1380 | + MouseArea { |
1381 | + enabled: !composeBar.audioAttached |
1382 | + anchors.fill: parent |
1383 | + onClicked: { |
1384 | + forceFocus() |
1385 | + } |
1386 | + } |
1387 | + |
1388 | + ListModel { |
1389 | + id: attachments |
1390 | + } |
1391 | + |
1392 | + Component { |
1393 | + id: attachmentPopover |
1394 | + |
1395 | + Popover { |
1396 | + id: popover |
1397 | + Column { |
1398 | + id: containerLayout |
1399 | + anchors { |
1400 | + left: parent.left |
1401 | + top: parent.top |
1402 | + right: parent.right |
1403 | + } |
1404 | + ListItem.Standard { |
1405 | + text: i18n.tr("Remove") |
1406 | + onClicked: { |
1407 | + attachments.remove(_activeAttachmentIndex) |
1408 | + PopupUtils.close(popover) |
1409 | + } |
1410 | + } |
1411 | + } |
1412 | + Component.onDestruction: _activeAttachmentIndex = -1 |
1413 | + } |
1414 | + } |
1415 | + |
1416 | + ListItem.ThinDivider { |
1417 | + anchors.top: parent.top |
1418 | + } |
1419 | + |
1420 | + Row { |
1421 | + id: leftSideActions |
1422 | + opacity: { |
1423 | + if (composeBar.recording) { |
1424 | + // we need to fade the buttons in when dragging |
1425 | + return dragTarget.dragAmount |
1426 | + } else if (composeBar.audioAttached) { |
1427 | + return 0; |
1428 | + } else { |
1429 | + return 1 |
1430 | + } |
1431 | + } |
1432 | + |
1433 | + Behavior on opacity { UbuntuNumberAnimation {} } |
1434 | + visible: opacity > 0 |
1435 | + |
1436 | + width: childrenRect.width |
1437 | + height: childrenRect.height |
1438 | + |
1439 | + anchors { |
1440 | + left: parent.left |
1441 | + leftMargin: units.gu(2) |
1442 | + verticalCenter: sendButton.verticalCenter |
1443 | + } |
1444 | + spacing: units.gu(2) |
1445 | + |
1446 | + TransparentButton { |
1447 | + id: attachButton |
1448 | + objectName: "attachButton" |
1449 | + iconName: "add" |
1450 | + iconRotation: attachmentPanel.expanded ? 45 : 0 |
1451 | + onClicked: { |
1452 | + attachmentPanel.expanded = !attachmentPanel.expanded |
1453 | + if (attachmentPanel.expanded) { |
1454 | + stickersPicker.expanded = false |
1455 | + } |
1456 | + } |
1457 | + } |
1458 | + |
1459 | + TransparentButton { |
1460 | + id: stickersButton |
1461 | + objectName: "stickersButton" |
1462 | + iconSource: (stickersPicker.expanded && oskEnabled) ? Qt.resolvedUrl("./assets/input-keyboard-symbolic.svg") : |
1463 | + Qt.resolvedUrl("./assets/face-smile-big-symbolic-2.svg") |
1464 | + visible: stickerPacksModel.count > 0 |
1465 | + onClicked: { |
1466 | + if (!stickersPicker.expanded) { |
1467 | + messageTextArea.focus = false |
1468 | + stickersPicker.expanded = true |
1469 | + attachmentPanel.expanded = false |
1470 | + } else { |
1471 | + stickersPicker.expanded = false |
1472 | + messageTextArea.forceActiveFocus() |
1473 | + } |
1474 | + } |
1475 | + } |
1476 | + } |
1477 | + |
1478 | + AudioPlaybackBar { |
1479 | + id: audioPlaybackBar |
1480 | + |
1481 | + anchors { |
1482 | + left: parent.left |
1483 | + right: audioRecordingBar.right |
1484 | + top: parent.top |
1485 | + bottom: attachmentPanel.top |
1486 | + } |
1487 | + |
1488 | + source: composeBar.audioAttached ? attachments.get(0).filePath : "" |
1489 | + duration: audioRecordedDuration |
1490 | + |
1491 | + opacity: composeBar.audioAttached ? 1.0 : 0.0 |
1492 | + Behavior on opacity { UbuntuNumberAnimation {} } |
1493 | + visible: opacity > 0 |
1494 | + |
1495 | + onResetRequested: { |
1496 | + composeBar.reset() |
1497 | + } |
1498 | + } |
1499 | + |
1500 | + AudioRecordingBar { |
1501 | + id: audioRecordingBar |
1502 | + |
1503 | + anchors { |
1504 | + left: parent.left |
1505 | + right: dragTarget.left |
1506 | + top: parent.top |
1507 | + bottom: attachmentPanel.top |
1508 | + } |
1509 | + |
1510 | + buttonOpacity: 1 - dragTarget.dragAmount |
1511 | + |
1512 | + onAudioRecorded: { |
1513 | + attachments.append(audio) |
1514 | + } |
1515 | + } |
1516 | + |
1517 | + Item { |
1518 | + id: dragTarget |
1519 | + |
1520 | + property real recordingX: recordButton.x |
1521 | + property real normalX: leftSideActions.x + leftSideActions.width |
1522 | + property real delta: recordingX - normalX |
1523 | + property real dragAmount: 1 - (x - normalX) / (delta > 0 ? delta : 0.0001) |
1524 | + x: (composeBar.recording || composeBar.audioAttached) ? recordingX : normalX |
1525 | + Behavior on x { UbuntuNumberAnimation { } } |
1526 | + width: 0 |
1527 | + } |
1528 | + |
1529 | + StyledItem { |
1530 | + id: textEntry |
1531 | + property alias text: messageTextArea.text |
1532 | + property alias inputMethodComposing: messageTextArea.inputMethodComposing |
1533 | + property int fullSize: composeBar.audioAttached ? messageTextArea.height : attachmentThumbnails.height + messageTextArea.height |
1534 | + style: Theme.createStyleComponent("TextAreaStyle.qml", textEntry) |
1535 | + anchors { |
1536 | + topMargin: units.gu(1) |
1537 | + top: parent.top |
1538 | + left: dragTarget.right |
1539 | + leftMargin: units.gu(2) |
1540 | + right: sendButton.left |
1541 | + rightMargin: units.gu(2) |
1542 | + } |
1543 | + height: attachments.count !== 0 && !composeBar.audioAttached ? fullSize + units.gu(1.5) : fullSize |
1544 | + onActiveFocusChanged: { |
1545 | + if(activeFocus) { |
1546 | + stickersPicker.expanded = false |
1547 | + messageTextArea.forceActiveFocus() |
1548 | + } else { |
1549 | + focus = false |
1550 | + } |
1551 | + } |
1552 | + |
1553 | + onTextChanged: { |
1554 | + // in case there is audio attached and the user starts typing, we remove the attachment |
1555 | + // and continue the text message |
1556 | + if (text !== "" && composeBar.audioAttached) { |
1557 | + attachments.clear() |
1558 | + } |
1559 | + } |
1560 | + |
1561 | + focus: false |
1562 | + opacity: composeBar.audioAttached ? 0.0 : 1.0 |
1563 | + Behavior on opacity { UbuntuNumberAnimation {} } |
1564 | + MouseArea { |
1565 | + anchors.fill: parent |
1566 | + onClicked: forceFocus() |
1567 | + } |
1568 | + Flow { |
1569 | + id: attachmentThumbnails |
1570 | + spacing: units.gu(1) |
1571 | + anchors{ |
1572 | + left: parent.left |
1573 | + right: parent.right |
1574 | + top: parent.top |
1575 | + topMargin: units.gu(1) |
1576 | + leftMargin: units.gu(1) |
1577 | + rightMargin: units.gu(1) |
1578 | + } |
1579 | + height: childrenRect.height |
1580 | + |
1581 | + Repeater { |
1582 | + model: attachments |
1583 | + delegate: Loader { |
1584 | + id: loader |
1585 | + height: units.gu(8) |
1586 | + source: { |
1587 | + var contentType = getContentType(filePath) |
1588 | + console.log(contentType) |
1589 | + switch(contentType) { |
1590 | + case ContentType.Contacts: |
1591 | + return Qt.resolvedUrl("ThumbnailContact.qml") |
1592 | + case ContentType.Pictures: |
1593 | + return Qt.resolvedUrl("ThumbnailImage.qml") |
1594 | + case ContentType.Videos: |
1595 | + return Qt.resolvedUrl("ThumbnailVideo.qml") |
1596 | + case ContentType.Unknown: |
1597 | + return Qt.resolvedUrl("ThumbnailUnknown.qml") |
1598 | + default: |
1599 | + console.log("unknown content Type") |
1600 | + } |
1601 | + } |
1602 | + onStatusChanged: { |
1603 | + if (status == Loader.Ready) { |
1604 | + item.index = index |
1605 | + item.filePath = filePath |
1606 | + } |
1607 | + } |
1608 | + |
1609 | + Connections { |
1610 | + target: loader.status == Loader.Ready ? loader.item : null |
1611 | + ignoreUnknownSignals: true |
1612 | + onPressAndHold: { |
1613 | + Qt.inputMethod.hide() |
1614 | + _activeAttachmentIndex = target.index |
1615 | + PopupUtils.open(attachmentPopover, parent) |
1616 | + } |
1617 | + } |
1618 | + } |
1619 | + } |
1620 | + } |
1621 | + |
1622 | + ListItem.ThinDivider { |
1623 | + id: divider |
1624 | + |
1625 | + anchors { |
1626 | + left: parent.left |
1627 | + right: parent.right |
1628 | + top: attachmentThumbnails.bottom |
1629 | + margins: units.gu(0.5) |
1630 | + } |
1631 | + visible: attachments.count > 0 |
1632 | + } |
1633 | + |
1634 | + TextArea { |
1635 | + id: messageTextArea |
1636 | + objectName: "messageTextArea" |
1637 | + anchors { |
1638 | + top: attachments.count == 0 ? textEntry.top : attachmentThumbnails.bottom |
1639 | + left: parent.left |
1640 | + right: parent.right |
1641 | + } |
1642 | + // this value is to avoid letter being cut off |
1643 | + height: units.gu(4.3) |
1644 | + style: LocalTextAreaStyle {} |
1645 | + autoSize: true |
1646 | + maximumLineCount: attachments.count == 0 ? 8 : 4 |
1647 | + placeholderText: { |
1648 | + if (telepathyHelper.ready) { |
1649 | + var account = telepathyHelper.accountForId(presenceRequest.accountId) |
1650 | + if (account && |
1651 | + (presenceRequest.type != PresenceRequest.PresenceTypeUnknown && |
1652 | + presenceRequest.type != PresenceRequest.PresenceTypeUnset) && |
1653 | + account.protocolInfo.serviceName !== "") { |
1654 | + console.log(presenceRequest.accountId) |
1655 | + console.log(presenceRequest.type) |
1656 | + return account.protocolInfo.serviceName |
1657 | + } |
1658 | + } |
1659 | + return i18n.tr("Write a message...") |
1660 | + } |
1661 | + focus: textEntry.focus |
1662 | + font.family: "Ubuntu" |
1663 | + font.pixelSize: FontUtils.sizeToPixels("medium") |
1664 | + color: "#5d5d5d" |
1665 | + } |
1666 | + } |
1667 | + |
1668 | + AttachmentPanel { |
1669 | + id: attachmentPanel |
1670 | + |
1671 | + anchors { |
1672 | + left: parent.left |
1673 | + right: parent.right |
1674 | + top: textEntry.bottom |
1675 | + topMargin: units.gu(1) |
1676 | + } |
1677 | + |
1678 | + Connections { |
1679 | + target: composeBar |
1680 | + onAudioAttachedChanged: { |
1681 | + if (composeBar.audioAttached) { |
1682 | + attachmentPanel.expanded = false; |
1683 | + } |
1684 | + } |
1685 | + } |
1686 | + |
1687 | + onAttachmentAvailable: { |
1688 | + attachments.append(attachment) |
1689 | + forceFocus() |
1690 | + } |
1691 | + |
1692 | + onExpandedChanged: { |
1693 | + if (expanded && Qt.inputMethod.visible) { |
1694 | + attachmentPanel.forceActiveFocus() |
1695 | + } |
1696 | + } |
1697 | + } |
1698 | + |
1699 | + Loader { |
1700 | + id: stickersPicker |
1701 | + property bool expanded: false |
1702 | + height: expanded ? item.height : 0 |
1703 | + active: false |
1704 | + sourceComponent: stickersPickerComponent |
1705 | + anchors { |
1706 | + left: parent.left |
1707 | + right: parent.right |
1708 | + top: textEntry.bottom |
1709 | + } |
1710 | + onExpandedChanged: { |
1711 | + if (expanded) { |
1712 | + stickersPicker.active = expanded |
1713 | + } |
1714 | + if (active) { |
1715 | + item.expanded = expanded |
1716 | + } |
1717 | + } |
1718 | + } |
1719 | + |
1720 | + Component { |
1721 | + id: stickersPickerComponent |
1722 | + StickersPicker { |
1723 | + id: stickersPicker1 |
1724 | + |
1725 | + onExpandedChanged: { |
1726 | + if (expanded && Qt.inputMethod.visible) { |
1727 | + stickersPicker1.forceActiveFocus() |
1728 | + } |
1729 | + } |
1730 | + |
1731 | + onStickerSelected: { |
1732 | + if (!canSend) { |
1733 | + // FIXME: show a dialog saying what we need to do to be able to send |
1734 | + return |
1735 | + } |
1736 | + |
1737 | + var attachment = {} |
1738 | + var filePath = String(path).replace('file://', '') |
1739 | + attachment["contentType"] = application.fileMimeType(filePath) |
1740 | + attachment["name"] = filePath.split('/').reverse()[0] |
1741 | + attachment["filePath"] = filePath |
1742 | + |
1743 | + // we need to append the attachment to a ListModel, so create it dynamically |
1744 | + var attachments = Qt.createQmlObject("import QtQuick 2.0; ListModel { }", composeBar) |
1745 | + attachments.append(attachment) |
1746 | + composeBar.sendRequested("", attachments) |
1747 | + stickersPicker.expanded = false |
1748 | + } |
1749 | + } |
1750 | + } |
1751 | + |
1752 | + TransparentButton { |
1753 | + id: sendButton |
1754 | + objectName: "sendButton" |
1755 | + anchors.verticalCenter: textEntry.verticalCenter |
1756 | + anchors.right: parent.right |
1757 | + anchors.rightMargin: units.gu(2) |
1758 | + iconSource: Qt.resolvedUrl("./assets/send.svg") |
1759 | + enabled: (canSend && (textEntry.text != "" || textEntry.inputMethodComposing || attachments.count > 0)) |
1760 | + opacity: textEntry.text != "" || textEntry.inputMethodComposing || attachments.count > 0 ? 1.0 : 0.0 |
1761 | + Behavior on opacity { UbuntuNumberAnimation {} } |
1762 | + visible: opacity > 0 |
1763 | + |
1764 | + onClicked: { |
1765 | + // make sure we flush everything we have prepared in the OSK preedit |
1766 | + Qt.inputMethod.commit(); |
1767 | + if (textEntry.text == "" && attachments.count == 0) { |
1768 | + return |
1769 | + } |
1770 | + |
1771 | + if (composeBar.audioAttached) { |
1772 | + textEntry.text = "" |
1773 | + } |
1774 | + |
1775 | + composeBar.sendRequested(textEntry.text, attachments) |
1776 | + } |
1777 | + } |
1778 | + |
1779 | + TransparentButton { |
1780 | + id: recordButton |
1781 | + objectName: "recordButton" |
1782 | + |
1783 | + anchors { |
1784 | + verticalCenter: textEntry.verticalCenter |
1785 | + right: parent.right |
1786 | + rightMargin: units.gu(2) |
1787 | + } |
1788 | + |
1789 | + opacity: textEntry.text != "" || textEntry.inputMethodComposing || attachments.count > 0 ? 0.0 : 1.0 |
1790 | + Behavior on opacity { UbuntuNumberAnimation {} } |
1791 | + visible: opacity > 0 |
1792 | + |
1793 | + iconColor: composeBar.recording ? "black" : "gray" |
1794 | + iconName: "audio-input-microphone-symbolic" |
1795 | + |
1796 | + onPressed: audioRecordingBar.startRecording() |
1797 | + onReleased: { |
1798 | + audioRecordingBar.stopRecording() |
1799 | + |
1800 | + // if dragged past the threshold, cancel |
1801 | + if (dragTarget.dragAmount >= 0.5) { |
1802 | + composeBar.reset() |
1803 | + } |
1804 | + } |
1805 | + |
1806 | + // drag-to-cancel |
1807 | + drag.target: dragTarget |
1808 | + drag.axis: Drag.XAxis |
1809 | + drag.minimumX: (leftSideActions.x + leftSideActions.width) |
1810 | + drag.maximumX: recordButton.x |
1811 | + |
1812 | + } |
1813 | +} |
1814 | |
1815 | === renamed file 'src/qml/PictureImport.qml' => 'src/qml/ContentImport.qml' |
1816 | --- src/qml/PictureImport.qml 2015-09-14 13:51:27 +0000 |
1817 | +++ src/qml/ContentImport.qml 2016-01-14 12:41:18 +0000 |
1818 | @@ -24,17 +24,33 @@ |
1819 | |
1820 | property var importDialog: null |
1821 | |
1822 | - signal pictureReceived(string pictureUrl) |
1823 | + signal contentReceived(string contentUrl) |
1824 | |
1825 | - function requestNewPicture() |
1826 | - { |
1827 | + function requestContent(contentType) { |
1828 | if (!root.importDialog) { |
1829 | root.importDialog = PopupUtils.open(contentHubDialog, root) |
1830 | + root.importDialog.contentType = contentType |
1831 | } else { |
1832 | console.warn("Import dialog already running") |
1833 | } |
1834 | } |
1835 | |
1836 | + function requestPicture() { |
1837 | + requestContent(ContentHub.ContentType.Pictures) |
1838 | + } |
1839 | + |
1840 | + function requestVideo() { |
1841 | + requestContent(ContentHub.ContentType.Videos) |
1842 | + } |
1843 | + |
1844 | + function requestContact() { |
1845 | + requestContent(ContentHub.ContentType.Contacts) |
1846 | + } |
1847 | + |
1848 | + function requestDocument() { |
1849 | + requestContent(ContentHub.ContentType.Documents) |
1850 | + } |
1851 | + |
1852 | Component { |
1853 | id: contentHubDialog |
1854 | |
1855 | @@ -42,6 +58,7 @@ |
1856 | id: dialogue |
1857 | |
1858 | property alias activeTransfer: signalConnections.target |
1859 | + property alias contentType: peerPicker.contentType |
1860 | |
1861 | focus: true |
1862 | Rectangle { |
1863 | @@ -76,7 +93,7 @@ |
1864 | if (dialogue.activeTransfer.state === ContentHub.ContentTransfer.Charged) { |
1865 | dialogue.hide() |
1866 | if (dialogue.activeTransfer.items.length > 0) { |
1867 | - root.pictureReceived(dialogue.activeTransfer.items[0].url) |
1868 | + root.contentReceived(dialogue.activeTransfer.items[0].url) |
1869 | } |
1870 | } |
1871 | |
1872 | |
1873 | === added file 'src/qml/DeliveryStatus.qml' |
1874 | --- src/qml/DeliveryStatus.qml 1970-01-01 00:00:00 +0000 |
1875 | +++ src/qml/DeliveryStatus.qml 2016-01-14 12:41:18 +0000 |
1876 | @@ -0,0 +1,38 @@ |
1877 | +/* |
1878 | + * Copyright 2015 Canonical Ltd. |
1879 | + * |
1880 | + * This file is part of messaging-app. |
1881 | + * |
1882 | + * messaging-app is free software; you can redistribute it and/or modify |
1883 | + * it under the terms of the GNU General Public License as published by |
1884 | + * the Free Software Foundation; version 3. |
1885 | + * |
1886 | + * messaging-app is distributed in the hope that it will be useful, |
1887 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1888 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1889 | + * GNU General Public License for more details. |
1890 | + * |
1891 | + * You should have received a copy of the GNU General Public License |
1892 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1893 | + */ |
1894 | +import QtQuick 2.2 |
1895 | +import Ubuntu.History 0.1 |
1896 | + |
1897 | +Image { |
1898 | + property int messageStatus: -1 |
1899 | + enabled: true |
1900 | + height: enabled ? units.gu(1) : 0 |
1901 | + width: enabled ? undefined : 0 |
1902 | + fillMode: Image.PreserveAspectFit |
1903 | + source: { |
1904 | + if (!enabled) { |
1905 | + return "" |
1906 | + } |
1907 | + if (messageStatus == HistoryThreadModel.MessageStatusDelivered) { |
1908 | + return Qt.resolvedUrl("./assets/single_tick.svg") |
1909 | + } else if (messageStatus == HistoryThreadModel.MessageStatusRead) { |
1910 | + return Qt.resolvedUrl("./assets/double_tick.svg") |
1911 | + } |
1912 | + return "" |
1913 | + } |
1914 | +} |
1915 | |
1916 | === added file 'src/qml/Dialogs/FileSizeWarningDialog.qml' |
1917 | --- src/qml/Dialogs/FileSizeWarningDialog.qml 1970-01-01 00:00:00 +0000 |
1918 | +++ src/qml/Dialogs/FileSizeWarningDialog.qml 2016-01-14 12:41:18 +0000 |
1919 | @@ -0,0 +1,70 @@ |
1920 | +/* |
1921 | + * Copyright 2015 Canonical Ltd. |
1922 | + * |
1923 | + * This file is part of messaging-app. |
1924 | + * |
1925 | + * dialer-app is free software; you can redistribute it and/or modify |
1926 | + * it under the terms of the GNU General Public License as published by |
1927 | + * the Free Software Foundation; version 3. |
1928 | + * |
1929 | + * dialer-app is distributed in the hope that it will be useful, |
1930 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1931 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1932 | + * GNU General Public License for more details. |
1933 | + * |
1934 | + * You should have received a copy of the GNU General Public License |
1935 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1936 | + */ |
1937 | + |
1938 | +import QtQuick 2.0 |
1939 | +import Ubuntu.Components 1.3 |
1940 | +import Ubuntu.Components.Popups 1.3 |
1941 | + |
1942 | +Component { |
1943 | + Dialog { |
1944 | + id: dialogue |
1945 | + title: i18n.tr("File size warning") |
1946 | + Column { |
1947 | + anchors.left: parent.left |
1948 | + anchors.right: parent.right |
1949 | + spacing: units.gu(2) |
1950 | + |
1951 | + Label { |
1952 | + anchors.left: parent.left |
1953 | + anchors.right: parent.right |
1954 | + height: paintedHeight |
1955 | + verticalAlignment: Text.AlignVCenter |
1956 | + text: i18n.tr("You are trying to send big files (over 300Kb). Some operators might not be able to send it.") |
1957 | + wrapMode: Text.WordWrap |
1958 | + } |
1959 | + Row { |
1960 | + spacing: units.gu(4) |
1961 | + anchors.horizontalCenter: parent.horizontalCenter |
1962 | + Button { |
1963 | + objectName: "okFileSizeWarningDialog" |
1964 | + text: i18n.tr("Ok") |
1965 | + color: UbuntuColors.orange |
1966 | + onClicked: { |
1967 | + PopupUtils.close(dialogue) |
1968 | + } |
1969 | + } |
1970 | + } |
1971 | + |
1972 | + Row { |
1973 | + CheckBox { |
1974 | + id: dontAskAgainCheckbox |
1975 | + checked: false |
1976 | + onCheckedChanged: settings.messagesDontShowFileSizeWarning = checked |
1977 | + } |
1978 | + Label { |
1979 | + text: i18n.tr("Don't show again") |
1980 | + anchors.verticalCenter: dontAskAgainCheckbox.verticalCenter |
1981 | + MouseArea { |
1982 | + anchors.fill: parent |
1983 | + onClicked: dontAskAgainCheckbox.checked = !dontAskAgainCheckbox.checked |
1984 | + } |
1985 | + } |
1986 | + } |
1987 | + } |
1988 | + } |
1989 | +} |
1990 | |
1991 | === modified file 'src/qml/KeyboardRectangle.qml' |
1992 | --- src/qml/KeyboardRectangle.qml 2014-08-11 22:59:14 +0000 |
1993 | +++ src/qml/KeyboardRectangle.qml 2016-01-14 12:41:18 +0000 |
1994 | @@ -17,6 +17,7 @@ |
1995 | */ |
1996 | |
1997 | import QtQuick 2.2 |
1998 | +import GSettings 1.0 |
1999 | |
2000 | Item { |
2001 | id: keyboardRect |
2002 | @@ -25,6 +26,8 @@ |
2003 | anchors.bottom: parent.bottom |
2004 | height: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0 |
2005 | |
2006 | + property bool oskEnabled: !gsettings.stayHidden |
2007 | + |
2008 | function recursiveFindFocusedItem(parent) { |
2009 | if (parent.activeFocus) { |
2010 | return parent; |
2011 | @@ -58,4 +61,9 @@ |
2012 | } |
2013 | } |
2014 | } |
2015 | + |
2016 | + GSettings { |
2017 | + id: gsettings |
2018 | + schema.id: "com.canonical.keyboard.maliit" |
2019 | + } |
2020 | } |
2021 | |
2022 | === modified file 'src/qml/LocalPageWithBottomEdge.qml' |
2023 | --- src/qml/LocalPageWithBottomEdge.qml 2015-09-14 13:51:27 +0000 |
2024 | +++ src/qml/LocalPageWithBottomEdge.qml 2016-01-14 12:41:18 +0000 |
2025 | @@ -105,7 +105,7 @@ |
2026 | { |
2027 | if (edgeLoader.status === Loader.Ready) { |
2028 | edgeLoader.item.active = true |
2029 | - page.pageStack.push(edgeLoader.item) |
2030 | + page.pageStack.addPageToNextColumn(page, edgeLoader.item) |
2031 | if (edgeLoader.item.flickable) { |
2032 | edgeLoader.item.flickable.contentY = -page.header.height |
2033 | edgeLoader.item.flickable.returnToBounds() |
2034 | @@ -162,6 +162,7 @@ |
2035 | property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y) |
2036 | |
2037 | enabled: mouseArea.enabled |
2038 | + visible: bottomEdgeEnabled |
2039 | anchors { |
2040 | bottom: parent.bottom |
2041 | horizontalCenter: bottomEdge.horizontalCenter |
2042 | @@ -233,7 +234,7 @@ |
2043 | minimumY: bottomEdge.pageStartY |
2044 | maximumY: page.height |
2045 | } |
2046 | - enabled: edgeLoader.status == Loader.Ready |
2047 | + enabled: bottomEdgeEnabled && edgeLoader.status == Loader.Ready |
2048 | |
2049 | anchors { |
2050 | left: parent.left |
2051 | |
2052 | === added file 'src/qml/MMS/MMSAudio.qml' |
2053 | --- src/qml/MMS/MMSAudio.qml 1970-01-01 00:00:00 +0000 |
2054 | +++ src/qml/MMS/MMSAudio.qml 2016-01-14 12:41:18 +0000 |
2055 | @@ -0,0 +1,200 @@ |
2056 | +/* |
2057 | + * Copyright 2015 Canonical Ltd. |
2058 | + * |
2059 | + * This file is part of messaging-app. |
2060 | + * |
2061 | + * messaging-app is free software; you can redistribute it and/or modify |
2062 | + * it under the terms of the GNU General Public License as published by |
2063 | + * the Free Software Foundation; version 3. |
2064 | + * |
2065 | + * messaging-app is distributed in the hope that it will be useful, |
2066 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2067 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2068 | + * GNU General Public License for more details. |
2069 | + * |
2070 | + * You should have received a copy of the GNU General Public License |
2071 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2072 | + */ |
2073 | + |
2074 | +import QtQuick 2.2 |
2075 | +import QtMultimedia 5.0 |
2076 | +import Ubuntu.Components 1.3 |
2077 | +import Ubuntu.Components.Themes.Ambiance 1.3 |
2078 | +import messagingapp.private 0.1 |
2079 | +import ".." |
2080 | +import "../dateUtils.js" as DateUtils |
2081 | + |
2082 | +MMSBase { |
2083 | + id: audioDelegate |
2084 | + |
2085 | + height: units.gu(5) |
2086 | + width: units.gu(28) |
2087 | + property string textColor: incoming ? "#5D5D5D" : "#FFFFFF" |
2088 | + swipeLocked: audioPlayer.playing |
2089 | + |
2090 | + onAttachmentChanged: { |
2091 | + var tmpFile = FileOperations.getTemporaryFile(".ogg") |
2092 | + if (FileOperations.link(attachment.filePath, tmpFile)) { |
2093 | + audioPlayer.source = tmpFile; |
2094 | + } else { |
2095 | + console.log("MMSAudio: Failed to link", attachment.filePath, "to", tmpFile) |
2096 | + } |
2097 | + } |
2098 | + |
2099 | + Rectangle { |
2100 | + id: shape |
2101 | + radius: units.gu(1) |
2102 | + smooth: true |
2103 | + anchors.top: parent.top |
2104 | + width: parent.width |
2105 | + height: parent.height |
2106 | + color: incoming ? "#FFFFFF" : "#3fb24f" |
2107 | + border.color: incoming ? "#888888" : "transparent" |
2108 | + } |
2109 | + |
2110 | + Loader { |
2111 | + id: audioPlayer |
2112 | + readonly property bool playing: ready ? item.playing : false |
2113 | + readonly property bool paused: ready ? item.paused : false |
2114 | + readonly property bool stopped: ready ? item.stopped : false |
2115 | + readonly property int position: ready ? item.position : 0 |
2116 | + readonly property int duration: ready ? item.duration : 0 |
2117 | + readonly property bool ready: status == Loader.Ready |
2118 | + readonly property int playbackState: ready ? item.playbackState : Audio.StoppedState |
2119 | + property bool muted: false |
2120 | + property string source: "" |
2121 | + function play() { |
2122 | + audioPlayer.active = true |
2123 | + item.play() |
2124 | + } |
2125 | + function stop() { |
2126 | + item.stop() |
2127 | + audioPlayer.active = false |
2128 | + audioPlayer.muted = false |
2129 | + } |
2130 | + function pause() { item.pause() } |
2131 | + function seek(pos) { item.seek(pos) } |
2132 | + active: false |
2133 | + sourceComponent: audioPlayerComponent |
2134 | + } |
2135 | + |
2136 | + Component { |
2137 | + id: audioPlayerComponent |
2138 | + Audio { |
2139 | + id: audioPlayer1 |
2140 | + objectName: "audioPlayer" |
2141 | + readonly property bool playing: audioPlayer1.playbackState == Audio.PlayingState |
2142 | + readonly property bool paused: audioPlayer1.playbackState == Audio.PausedState |
2143 | + readonly property bool stopped: audioPlayer1.playbackState == Audio.StoppedState |
2144 | + source: audioPlayer.source |
2145 | + muted: audioPlayer.muted |
2146 | + } |
2147 | + } |
2148 | + |
2149 | + |
2150 | + TransparentButton { |
2151 | + id: playButton |
2152 | + objectName: "playButton" |
2153 | + |
2154 | + anchors { |
2155 | + left: parent.left |
2156 | + leftMargin: units.gu(1) |
2157 | + verticalCenter: shape.verticalCenter |
2158 | + } |
2159 | + |
2160 | + spacing: units.gu(1) |
2161 | + sideBySide: true |
2162 | + enabled: audioPlayer.source != "" |
2163 | + iconColor: audioDelegate.textColor |
2164 | + iconName: audioPlayer.playing ? "media-playback-pause" : "media-playback-start" |
2165 | + |
2166 | + textSize: FontUtils.sizeToPixels("x-small") |
2167 | + textColor: audioDelegate.textColor |
2168 | + text: { |
2169 | + if (audioPlayer.playing || audioPlayer.paused) { |
2170 | + return DateUtils.formattedTime(audioPlayer.position/ 1000) |
2171 | + } |
2172 | + if (audioPlayer.duration > 0) { |
2173 | + return DateUtils.formattedTime(audioPlayer.duration / 1000) |
2174 | + } |
2175 | + return "" |
2176 | + } |
2177 | + |
2178 | + onClicked: { |
2179 | + if (audioPlayer.playing) { |
2180 | + audioPlayer.pause() |
2181 | + } else { |
2182 | + audioPlayer.play() |
2183 | + } |
2184 | + } |
2185 | + } |
2186 | + |
2187 | + Slider { |
2188 | + id: slider |
2189 | + Connections { |
2190 | + target: audioPlayer |
2191 | + onDurationChanged: { |
2192 | + if (slider.maximumValue == 100) { |
2193 | + slider.maximumValue = audioPlayer.duration |
2194 | + } |
2195 | + } |
2196 | + } |
2197 | + style: SliderStyle { |
2198 | + Component.onCompleted: thumb.visible = false |
2199 | + Connections { |
2200 | + target: audioPlayer |
2201 | + onPlaybackStateChanged: { |
2202 | + thumb.visible = !audioPlayer.stopped |
2203 | + if (!thumb.visible) { |
2204 | + audioPlayer.seek(0) |
2205 | + } |
2206 | + } |
2207 | + } |
2208 | + } |
2209 | + enabled: !audioPlayer.stopped |
2210 | + function formatValue(v) { return DateUtils.formattedTime(v/1000) } |
2211 | + anchors { |
2212 | + left: playButton.right |
2213 | + right: deliveryStatus.left |
2214 | + leftMargin: units.gu(1) |
2215 | + rightMargin: units.gu(2) |
2216 | + verticalCenter: shape.verticalCenter |
2217 | + } |
2218 | + height: units.gu(3) |
2219 | + minimumValue: 0.0 |
2220 | + maximumValue: 100 |
2221 | + value: audioPlayer.position |
2222 | + activeFocusOnPress: false |
2223 | + onPressedChanged: { |
2224 | + if (!pressed) { |
2225 | + if (audioPlayer.playing || audioPlayer.paused) { |
2226 | + audioPlayer.seek(value) |
2227 | + } else { |
2228 | + audioPlayer.muted = true |
2229 | + // we only get the duration while playing |
2230 | + audioPlayer.play() |
2231 | + audioPlayer.pause() |
2232 | + if (audioPlayer.duration == 100) { |
2233 | + audioPlayer.seek((audioPlayer.duration*value)/100) |
2234 | + } else { |
2235 | + audioPlayer.seek(value) |
2236 | + } |
2237 | + audioPlayer.muted = false |
2238 | + |
2239 | + } |
2240 | + value = Qt.binding(function(){ return audioPlayer.position}) |
2241 | + } |
2242 | + } |
2243 | + } |
2244 | + |
2245 | + DeliveryStatus { |
2246 | + id: deliveryStatus |
2247 | + messageStatus: textMessageStatus |
2248 | + enabled: showDeliveryStatus |
2249 | + anchors { |
2250 | + right: parent.right |
2251 | + rightMargin: units.gu(0.5) |
2252 | + verticalCenter: slider.verticalCenter |
2253 | + } |
2254 | + } |
2255 | +} |
2256 | |
2257 | === modified file 'src/qml/MMS/MMSBase.qml' |
2258 | --- src/qml/MMS/MMSBase.qml 2014-08-19 18:41:57 +0000 |
2259 | +++ src/qml/MMS/MMSBase.qml 2016-01-14 12:41:18 +0000 |
2260 | @@ -23,4 +23,6 @@ |
2261 | property var attachment |
2262 | property string previewer |
2263 | property bool lastItem: false |
2264 | + property bool swipeLocked: false |
2265 | + property bool showDeliveryStatus: true |
2266 | } |
2267 | |
2268 | === modified file 'src/qml/MMS/MMSContact.qml' |
2269 | --- src/qml/MMS/MMSContact.qml 2015-11-19 13:37:36 +0000 |
2270 | +++ src/qml/MMS/MMSContact.qml 2016-01-14 12:41:18 +0000 |
2271 | @@ -20,6 +20,7 @@ |
2272 | import Ubuntu.Components 1.3 |
2273 | import Ubuntu.Contacts 0.1 |
2274 | import Ubuntu.History 0.1 |
2275 | +import ".." |
2276 | |
2277 | MMSBase { |
2278 | id: vcardDelegate |
2279 | @@ -76,8 +77,8 @@ |
2280 | return "#3fb24f" |
2281 | } |
2282 | } |
2283 | - border.color: "#ACACAC" |
2284 | - radius: height * 0.1 |
2285 | + border.color: incoming ? "#ACACAC" : "transparent" |
2286 | + radius: units.gu(1) |
2287 | |
2288 | ContactAvatar { |
2289 | id: avatar |
2290 | @@ -132,4 +133,16 @@ |
2291 | |
2292 | vCardUrl: attachment ? Qt.resolvedUrl(attachment.filePath) : "" |
2293 | } |
2294 | + |
2295 | + DeliveryStatus { |
2296 | + id: deliveryStatus |
2297 | + messageStatus: textMessageStatus |
2298 | + enabled: showDeliveryStatus |
2299 | + anchors { |
2300 | + right: parent.right |
2301 | + rightMargin: units.gu(0.5) |
2302 | + bottom: parent.bottom |
2303 | + bottomMargin: units.gu(0.5) |
2304 | + } |
2305 | + } |
2306 | } |
2307 | |
2308 | === modified file 'src/qml/MMS/MMSImage.qml' |
2309 | --- src/qml/MMS/MMSImage.qml 2015-09-14 13:51:27 +0000 |
2310 | +++ src/qml/MMS/MMSImage.qml 2016-01-14 12:41:18 +0000 |
2311 | @@ -18,6 +18,7 @@ |
2312 | |
2313 | import QtQuick 2.2 |
2314 | import Ubuntu.Components 1.3 |
2315 | +import ".." |
2316 | |
2317 | MMSBase { |
2318 | id: imageDelegate |
2319 | @@ -34,6 +35,7 @@ |
2320 | |
2321 | image: Image { |
2322 | id: imageAttachment |
2323 | + objectName: "imageAttachment" |
2324 | |
2325 | fillMode: Image.PreserveAspectCrop |
2326 | smooth: true |
2327 | @@ -83,4 +85,16 @@ |
2328 | } |
2329 | } |
2330 | } |
2331 | + |
2332 | + DeliveryStatus { |
2333 | + id: deliveryStatus |
2334 | + messageStatus: textMessageStatus |
2335 | + enabled: showDeliveryStatus |
2336 | + anchors { |
2337 | + right: parent.right |
2338 | + rightMargin: units.gu(0.5) |
2339 | + bottom: parent.bottom |
2340 | + bottomMargin: units.gu(0.5) |
2341 | + } |
2342 | + } |
2343 | } |
2344 | |
2345 | === modified file 'src/qml/MMS/MMSVideo.qml' |
2346 | --- src/qml/MMS/MMSVideo.qml 2015-09-14 13:51:27 +0000 |
2347 | +++ src/qml/MMS/MMSVideo.qml 2016-01-14 12:41:18 +0000 |
2348 | @@ -1,5 +1,5 @@ |
2349 | /* |
2350 | - * Copyright 2012, 2013, 2014 Canonical Ltd. |
2351 | + * Copyright 2012-2015 Canonical Ltd. |
2352 | * |
2353 | * This file is part of messaging-app. |
2354 | * |
2355 | @@ -18,65 +18,95 @@ |
2356 | |
2357 | import QtQuick 2.2 |
2358 | import Ubuntu.Components 1.3 |
2359 | -import QtMultimedia 5.0 |
2360 | +import Ubuntu.Thumbnailer 0.1 |
2361 | import ".." |
2362 | |
2363 | MMSBase { |
2364 | id: videoDelegate |
2365 | |
2366 | previewer: "MMS/PreviewerVideo.qml" |
2367 | - anchors.left: parent.left |
2368 | - anchors.right: parent.right |
2369 | - height: bubble.height + units.gu(1) |
2370 | + height: videoAttachment.height |
2371 | + width: videoAttachment.width |
2372 | |
2373 | - Item { |
2374 | + UbuntuShape { |
2375 | id: bubble |
2376 | anchors.top: parent.top |
2377 | - width: videoOutput.width + units.gu(3) |
2378 | - height: videoOutput.height + units.gu(2) |
2379 | - |
2380 | - MediaPlayer { |
2381 | - id: video |
2382 | - autoLoad: true |
2383 | - autoPlay: false |
2384 | - source: attachment.filePath |
2385 | - onStatusChanged: { |
2386 | - if (status === MediaPlayer.Loaded) { |
2387 | - // FIXME: there is no way to show the thumbnail |
2388 | - video.play(); video.stop(); |
2389 | - |
2390 | - // resize videoOutput, as width is not set |
2391 | - // properly when using PreserveAspectFit |
2392 | - if (videoOutput.height > units.gu(25)) { |
2393 | - var percentageResized = units.gu(25)*100/(metaData.resolution.height) |
2394 | - videoOutput.height = units.gu(25) |
2395 | - videoOutput.width = (metaData.resolution.width*percentageResized)/100 |
2396 | - } |
2397 | - if (videoOutput.width > units.gu(35)) { |
2398 | - percentageResized = units.gu(35)*100/(metaData.resolution.width) |
2399 | - videoOutput.width = units.gu(35) |
2400 | - videoOutput.height = (metaData.resolution.height*percentageResized)/100 |
2401 | - } |
2402 | + width: image.width |
2403 | + height: image.height |
2404 | + |
2405 | + image: Image { |
2406 | + id: videoAttachment |
2407 | + objectName: "videoAttachment" |
2408 | + |
2409 | + fillMode: Image.PreserveAspectCrop |
2410 | + smooth: true |
2411 | + source: "image://thumbnailer/" + attachment.filePath |
2412 | + visible: false |
2413 | + asynchronous: true |
2414 | + height: Math.min(implicitHeight, units.gu(14)) |
2415 | + width: Math.min(implicitWidth, units.gu(27)) |
2416 | + cache: false |
2417 | + |
2418 | + sourceSize.width: units.gu(27) |
2419 | + sourceSize.height: units.gu(27) |
2420 | + |
2421 | + onStatusChanged: { |
2422 | + if (status === Image.Error) { |
2423 | + source = "image://theme/image-missing" |
2424 | + width = 128 |
2425 | + height = 128 |
2426 | } |
2427 | } |
2428 | } |
2429 | - VideoOutput { |
2430 | - id: videoOutput |
2431 | - source: video |
2432 | + |
2433 | + Icon { |
2434 | + objectName: "playbackStartIcon" |
2435 | + width: units.gu(3) |
2436 | + height: units.gu(3) |
2437 | anchors.centerIn: parent |
2438 | - anchors.horizontalCenterOffset: incoming ? units.gu(0.5) : -units.gu(0.5) |
2439 | + name: "media-playback-start" |
2440 | + color: "white" |
2441 | + opacity: 0.8 |
2442 | } |
2443 | |
2444 | Rectangle { |
2445 | - color: "black" |
2446 | - opacity: 0.8 |
2447 | - anchors.fill: videoOutput |
2448 | - Icon { |
2449 | - name: "media-playback-start" |
2450 | - width: units.gu(4) |
2451 | - height: units.gu(4) |
2452 | - anchors.centerIn: parent |
2453 | + visible: videoDelegate.lastItem |
2454 | + gradient: Gradient { |
2455 | + GradientStop { position: 0.0; color: "transparent" } |
2456 | + GradientStop { position: 1.0; color: "gray" } |
2457 | + } |
2458 | + |
2459 | + anchors { |
2460 | + bottom: parent.bottom |
2461 | + left: parent.left |
2462 | + right: parent.right |
2463 | + } |
2464 | + height: units.gu(2) |
2465 | + radius: bubble.height * 0.1 |
2466 | + Label { |
2467 | + anchors{ |
2468 | + left: parent.left |
2469 | + bottom: parent.bottom |
2470 | + leftMargin: incoming ? units.gu(2) : units.gu(1) |
2471 | + bottomMargin: units.gu(0.5) |
2472 | + } |
2473 | + fontSize: "xx-small" |
2474 | + text: Qt.formatTime(timestamp).toLowerCase() |
2475 | + color: "white" |
2476 | } |
2477 | } |
2478 | } |
2479 | + |
2480 | + DeliveryStatus { |
2481 | + id: deliveryStatus |
2482 | + messageStatus: textMessageStatus |
2483 | + enabled: showDeliveryStatus |
2484 | + anchors { |
2485 | + right: parent.right |
2486 | + rightMargin: units.gu(0.5) |
2487 | + bottom: parent.bottom |
2488 | + bottomMargin: units.gu(0.5) |
2489 | + } |
2490 | + } |
2491 | + |
2492 | } |
2493 | |
2494 | === modified file 'src/qml/MMS/Previewer.qml' |
2495 | --- src/qml/MMS/Previewer.qml 2015-11-17 14:39:21 +0000 |
2496 | +++ src/qml/MMS/Previewer.qml 2016-01-14 12:41:18 +0000 |
2497 | @@ -109,11 +109,11 @@ |
2498 | |
2499 | onPeerSelected: { |
2500 | picker.curTransfer = peer.request(); |
2501 | - mainStack.pop(); |
2502 | + mainStack.removePages(picker); |
2503 | if (picker.curTransfer.state === ContentTransfer.InProgress) |
2504 | picker.__exportItems(picker.url); |
2505 | } |
2506 | - onCancelPressed: mainStack.pop(); |
2507 | + onCancelPressed: mainStack.removePages(picker); |
2508 | } |
2509 | |
2510 | Connections { |
2511 | |
2512 | === modified file 'src/qml/MMS/PreviewerImage.qml' |
2513 | --- src/qml/MMS/PreviewerImage.qml 2015-09-14 13:51:27 +0000 |
2514 | +++ src/qml/MMS/PreviewerImage.qml 2016-01-14 12:41:18 +0000 |
2515 | @@ -1,5 +1,5 @@ |
2516 | /* |
2517 | - * Copyright 2012, 2013, 2014 Canonical Ltd. |
2518 | + * Copyright 2012-2015 Canonical Ltd. |
2519 | * |
2520 | * This file is part of messaging-app. |
2521 | * |
2522 | @@ -19,18 +19,193 @@ |
2523 | import QtQuick 2.2 |
2524 | import Ubuntu.Components 1.3 |
2525 | import Ubuntu.Content 0.1 |
2526 | +import Ubuntu.Thumbnailer 0.1 |
2527 | import ".." |
2528 | |
2529 | Previewer { |
2530 | + id: imagePreviewer |
2531 | + |
2532 | + Component.onCompleted: application.fullscreen = true |
2533 | + Component.onDestruction: application.fullscreen = false |
2534 | + |
2535 | + Connections { |
2536 | + target: application |
2537 | + onFullscreenChanged: imagePreviewer.head.visible = !application.fullscreen |
2538 | + } |
2539 | + |
2540 | title: i18n.tr("Image Preview") |
2541 | clip: true |
2542 | - Image { |
2543 | - anchors.centerIn: parent |
2544 | + |
2545 | + Rectangle { |
2546 | anchors.fill: parent |
2547 | - fillMode: Image.PreserveAspectFit |
2548 | - source: attachment.filePath |
2549 | - cache: false |
2550 | - sourceSize.width: parent.width |
2551 | - sourceSize.height: parent.height |
2552 | + color: "black" |
2553 | + } |
2554 | + |
2555 | + Item { |
2556 | + id: imageItem |
2557 | + property bool pinchInProgress: zoomPinchArea.active |
2558 | + property size thumbSize: Qt.size(viewer.width * 1.05, viewer.height * 1.05) |
2559 | + |
2560 | + onWidthChanged: { |
2561 | + // Only change thumbSize if width increases more than 5% |
2562 | + // that way we do not reload image for small resizes |
2563 | + if (width > thumbSize.width) { |
2564 | + thumbSize = Qt.size(width * 1.05, height * 1.05); |
2565 | + } |
2566 | + } |
2567 | + |
2568 | + onHeightChanged: { |
2569 | + // Only change thumbSize if height increases more than 5% |
2570 | + // that way we do not reload image for small resizes |
2571 | + if (height > thumbSize.height) { |
2572 | + thumbSize = Qt.size(width * 1.05, height * 1.05); |
2573 | + } |
2574 | + } |
2575 | + |
2576 | + function zoomIn(centerX, centerY, factor) { |
2577 | + flickable.scaleCenterX = centerX / (flickable.sizeScale * flickable.width); |
2578 | + flickable.scaleCenterY = centerY / (flickable.sizeScale * flickable.height); |
2579 | + flickable.sizeScale = factor; |
2580 | + } |
2581 | + |
2582 | + function zoomOut() { |
2583 | + if (flickable.sizeScale != 1.0) { |
2584 | + flickable.scaleCenterX = flickable.contentX / flickable.width / (flickable.sizeScale - 1); |
2585 | + flickable.scaleCenterY = flickable.contentY / flickable.height / (flickable.sizeScale - 1); |
2586 | + flickable.sizeScale = 1.0; |
2587 | + } |
2588 | + } |
2589 | + |
2590 | + width: parent.width |
2591 | + height: parent.height |
2592 | + |
2593 | + ActivityIndicator { |
2594 | + objectName: "imageActivityIndicator" |
2595 | + anchors.centerIn: parent |
2596 | + visible: running |
2597 | + running: image.status != Image.Ready |
2598 | + } |
2599 | + |
2600 | + PinchArea { |
2601 | + id: zoomPinchArea |
2602 | + anchors.fill: parent |
2603 | + |
2604 | + property real initialZoom |
2605 | + property real maximumScale: 3.0 |
2606 | + property real minimumZoom: 1.0 |
2607 | + property real maximumZoom: 3.0 |
2608 | + property bool active: false |
2609 | + property var center |
2610 | + |
2611 | + onPinchStarted: { |
2612 | + active = true; |
2613 | + initialZoom = flickable.sizeScale; |
2614 | + center = zoomPinchArea.mapToItem(media, pinch.startCenter.x, pinch.startCenter.y); |
2615 | + imageItem.zoomIn(center.x, center.y, initialZoom); |
2616 | + } |
2617 | + onPinchUpdated: { |
2618 | + var zoomFactor = MathUtils.clamp(initialZoom * pinch.scale, minimumZoom, maximumZoom); |
2619 | + flickable.sizeScale = zoomFactor; |
2620 | + } |
2621 | + onPinchFinished: { |
2622 | + active = false; |
2623 | + } |
2624 | + |
2625 | + Flickable { |
2626 | + id: flickable |
2627 | + anchors.fill: parent |
2628 | + contentWidth: media.width |
2629 | + contentHeight: media.height |
2630 | + contentX: (sizeScale - 1) * scaleCenterX * width |
2631 | + contentY: (sizeScale - 1) * scaleCenterY * height |
2632 | + interactive: !imageItem.pinchInProgress |
2633 | + |
2634 | + property real sizeScale: 1.0 |
2635 | + property real scaleCenterX: 0.0 |
2636 | + property real scaleCenterY: 0.0 |
2637 | + |
2638 | + Behavior on sizeScale { |
2639 | + enabled: !imageItem.pinchInProgress |
2640 | + UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} |
2641 | + } |
2642 | + Behavior on scaleCenterX { |
2643 | + UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} |
2644 | + } |
2645 | + Behavior on scaleCenterY { |
2646 | + UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} |
2647 | + } |
2648 | + |
2649 | + Item { |
2650 | + id: media |
2651 | + |
2652 | + width: flickable.width * flickable.sizeScale |
2653 | + height: flickable.height * flickable.sizeScale |
2654 | + |
2655 | + Image { |
2656 | + id: image |
2657 | + objectName: "thumbnailImage" |
2658 | + anchors.fill: parent |
2659 | + asynchronous: true |
2660 | + cache: false |
2661 | + source: "image://thumbnailer/%1".arg(attachment.filePath.toString()) |
2662 | + sourceSize { |
2663 | + width: imageItem.thumbSize.width |
2664 | + height: imageItem.thumbSize.height |
2665 | + } |
2666 | + fillMode: Image.PreserveAspectFit |
2667 | + opacity: status == Image.Ready ? 1.0 : 0.0 |
2668 | + Behavior on opacity { UbuntuNumberAnimation {duration: UbuntuAnimation.FastDuration} } |
2669 | + } |
2670 | + |
2671 | + Image { |
2672 | + id: highResolutionImage |
2673 | + objectName: "highResolutionImage" |
2674 | + anchors.fill: parent |
2675 | + asynchronous: true |
2676 | + cache: false |
2677 | + source: flickable.sizeScale > 1.0 ? attachment.filePath : "" |
2678 | + sourceSize { |
2679 | + width: width |
2680 | + height: height |
2681 | + } |
2682 | + fillMode: Image.PreserveAspectFit |
2683 | + } |
2684 | + } |
2685 | + |
2686 | + MouseArea { |
2687 | + id: imageMouseArea |
2688 | + anchors.fill: parent |
2689 | + |
2690 | + property bool clickAccepted: false |
2691 | + |
2692 | + onDoubleClicked: { |
2693 | + if (imageMouseArea.clickAccepted) { |
2694 | + return |
2695 | + } |
2696 | + |
2697 | + clickTimer.stop() |
2698 | + |
2699 | + if (flickable.sizeScale < zoomPinchArea.maximumZoom) { |
2700 | + imageItem.zoomIn(mouse.x, mouse.y, zoomPinchArea.maximumZoom); |
2701 | + } else { |
2702 | + imageItem.zoomOut(); |
2703 | + } |
2704 | + } |
2705 | + onClicked: { |
2706 | + imageMouseArea.clickAccepted = false |
2707 | + clickTimer.start() |
2708 | + } |
2709 | + } |
2710 | + |
2711 | + Timer { |
2712 | + id: clickTimer |
2713 | + interval: 200 |
2714 | + onTriggered: { |
2715 | + imageMouseArea.clickAccepted = true |
2716 | + application.fullscreen = !application.fullscreen |
2717 | + } |
2718 | + } |
2719 | + } |
2720 | + } |
2721 | } |
2722 | } |
2723 | |
2724 | === modified file 'src/qml/MMS/PreviewerVideo.qml' |
2725 | --- src/qml/MMS/PreviewerVideo.qml 2015-09-14 13:51:27 +0000 |
2726 | +++ src/qml/MMS/PreviewerVideo.qml 2016-01-14 12:41:18 +0000 |
2727 | @@ -1,5 +1,5 @@ |
2728 | /* |
2729 | - * Copyright 2012, 2013, 2014 Canonical Ltd. |
2730 | + * Copyright 2012-2015 Canonical Ltd. |
2731 | * |
2732 | * This file is part of messaging-app. |
2733 | * |
2734 | @@ -17,61 +17,152 @@ |
2735 | */ |
2736 | |
2737 | import QtQuick 2.2 |
2738 | +import QtMultimedia 5.0 |
2739 | import Ubuntu.Components 1.3 |
2740 | -import QtMultimedia 5.0 |
2741 | +import Ubuntu.Content 0.1 |
2742 | +import Ubuntu.Thumbnailer 0.1 |
2743 | +import messagingapp.private 0.1 |
2744 | import ".." |
2745 | |
2746 | Previewer { |
2747 | + id: videoPreviewer |
2748 | + |
2749 | title: i18n.tr("Video Preview") |
2750 | - // This previewer implements only basic video controls: play/pause/rewind |
2751 | - onActionTriggered: video.pause() |
2752 | - MediaPlayer { |
2753 | - id: video |
2754 | - autoLoad: true |
2755 | - autoPlay: true |
2756 | - source: attachment.filePath |
2757 | - } |
2758 | - VideoOutput { |
2759 | - id: videoOutput |
2760 | - source: video |
2761 | - anchors.fill: parent |
2762 | + clip: true |
2763 | + |
2764 | + Component.onCompleted: { |
2765 | + application.fullscreen = true |
2766 | + // Load Video player after toggling fullscreen to reduce flickering |
2767 | + videoLoader.active = true |
2768 | + } |
2769 | + Component.onDestruction: application.fullscreen = false |
2770 | + |
2771 | + Connections { |
2772 | + target: application |
2773 | + onFullscreenChanged: { |
2774 | + videoPreviewer.head.visible = !application.fullscreen |
2775 | + toolbar.collapsed = application.fullscreen |
2776 | + } |
2777 | + } |
2778 | + |
2779 | + Rectangle { |
2780 | + anchors.fill: parent |
2781 | + color: "black" |
2782 | + } |
2783 | + |
2784 | + Loader { |
2785 | + id: videoLoader |
2786 | + |
2787 | + anchors.fill: parent |
2788 | + active: false |
2789 | + sourceComponent: videoComponent |
2790 | + |
2791 | + onStatusChanged: { |
2792 | + if (status == Loader.Ready) { |
2793 | + var tmpFile = FileOperations.getTemporaryFile(".mp4") |
2794 | + if (FileOperations.link(attachment.filePath, tmpFile)) { |
2795 | + videoLoader.item.source = tmpFile |
2796 | + } else { |
2797 | + console.log("MMSVideo: Failed to link", attachment.filePath, "to", tmpFile) |
2798 | + } |
2799 | + } |
2800 | + } |
2801 | + |
2802 | + Component { |
2803 | + id: videoComponent |
2804 | + |
2805 | + Item { |
2806 | + id: videoPlayer |
2807 | + objectName: "videoPlayer" |
2808 | + |
2809 | + property alias source: player.source |
2810 | + property alias playbackState: player.playbackState |
2811 | + |
2812 | + function play() { player.play() } |
2813 | + function pause() { player.pause() } |
2814 | + function stop() { player.stop() } |
2815 | + |
2816 | + anchors.fill: parent |
2817 | + |
2818 | + MediaPlayer { |
2819 | + id: player |
2820 | + autoPlay: true |
2821 | + } |
2822 | + |
2823 | + VideoOutput { |
2824 | + id: videoOutput |
2825 | + anchors.fill: parent |
2826 | + source: player |
2827 | + } |
2828 | + } |
2829 | + } |
2830 | } |
2831 | |
2832 | MouseArea { |
2833 | - id: playArea |
2834 | - anchors.fill: parent |
2835 | - onPressed: { |
2836 | - if (video.playbackState === MediaPlayer.PlayingState) { |
2837 | - video.pause() |
2838 | - } |
2839 | + anchors { |
2840 | + top: parent.top |
2841 | + bottom: toolbar.top |
2842 | + left: parent.left |
2843 | + right: parent.right |
2844 | } |
2845 | + onClicked: application.fullscreen = !application.fullscreen |
2846 | } |
2847 | |
2848 | Rectangle { |
2849 | - color: "black" |
2850 | - visible: video.playbackState !== MediaPlayer.PlayingState |
2851 | + id: toolbar |
2852 | + objectName: "toolbar" |
2853 | + |
2854 | + property bool collapsed: false |
2855 | + |
2856 | + anchors.bottom: parent.bottom |
2857 | + |
2858 | + width: parent.width |
2859 | + height: collapsed ? 0 : units.gu(7) |
2860 | + Behavior on height { UbuntuNumberAnimation {} } |
2861 | + |
2862 | + color: "gray" |
2863 | opacity: 0.8 |
2864 | - anchors.fill: videoOutput |
2865 | + |
2866 | Row { |
2867 | - anchors.centerIn: parent |
2868 | + anchors { |
2869 | + top: parent.top |
2870 | + bottom: parent.bottom |
2871 | + horizontalCenter: parent.horizontalCenter |
2872 | + } |
2873 | + |
2874 | + spacing: units.gu(2) |
2875 | + |
2876 | Icon { |
2877 | - name: "media-playback-pause" |
2878 | - width: units.gu(5) |
2879 | - height: units.gu(5) |
2880 | + anchors.verticalCenter: parent.verticalCenter |
2881 | + width: toolbar.collapsed ? 0 : units.gu(5) |
2882 | + height: width |
2883 | + Behavior on width { UbuntuNumberAnimation {} } |
2884 | + Behavior on height { UbuntuNumberAnimation {} } |
2885 | + name: videoLoader.item && videoLoader.item.playbackState == MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start" |
2886 | + color: "white" |
2887 | MouseArea { |
2888 | anchors.fill: parent |
2889 | - onClicked: video.play(); |
2890 | + onClicked: { |
2891 | + if (videoLoader.item.playbackState == MediaPlayer.PlayingState) { |
2892 | + videoLoader.item.pause() |
2893 | + } else { |
2894 | + videoLoader.item.play() |
2895 | + } |
2896 | + } |
2897 | } |
2898 | } |
2899 | Icon { |
2900 | - name: "media-seek-backward" |
2901 | - width: units.gu(5) |
2902 | - height: units.gu(5) |
2903 | + anchors.verticalCenter: parent.verticalCenter |
2904 | + width: toolbar.collapsed ? 0 : units.gu(5) |
2905 | + height: width |
2906 | + Behavior on width { UbuntuNumberAnimation {} } |
2907 | + Behavior on height { UbuntuNumberAnimation {} } |
2908 | + name: "media-playback-stop" |
2909 | + color: "white" |
2910 | MouseArea { |
2911 | anchors.fill: parent |
2912 | onClicked: { |
2913 | - video.stop(); |
2914 | - video.play(); |
2915 | + videoLoader.item.stop() |
2916 | } |
2917 | } |
2918 | } |
2919 | |
2920 | === modified file 'src/qml/MMSDelegate.qml' |
2921 | --- src/qml/MMSDelegate.qml 2015-11-23 19:51:16 +0000 |
2922 | +++ src/qml/MMSDelegate.qml 2016-01-14 12:41:18 +0000 |
2923 | @@ -27,6 +27,15 @@ |
2924 | property var dataAttachments: [] |
2925 | property var textAttachements: [] |
2926 | property string messageText: "" |
2927 | + swipeLocked: { |
2928 | + for (var i=0; i < attachmentsView.children.length; i++) { |
2929 | + if (attachmentsView.children[i].item && !attachmentsView.children[i].item.swipeLocked) { |
2930 | + return false |
2931 | + } |
2932 | + } |
2933 | + return true |
2934 | + } |
2935 | + |
2936 | |
2937 | function clicked(mouse) |
2938 | { |
2939 | @@ -36,7 +45,7 @@ |
2940 | var properties = {} |
2941 | properties["attachment"] = attachment.item.attachment |
2942 | properties["thumbnail"] = attachment.item |
2943 | - mainStack.push(Qt.resolvedUrl(attachment.item.previewer), properties) |
2944 | + mainStack.addPageToCurrentColumn(messages, Qt.resolvedUrl(attachment.item.previewer), properties) |
2945 | } |
2946 | } |
2947 | |
2948 | @@ -82,17 +91,16 @@ |
2949 | var attachment = attachments[i] |
2950 | if (startsWith(attachment.contentType, "text/plain") ) { |
2951 | root.textAttachements.push(attachment) |
2952 | + } else if (startsWith(attachment.contentType, "audio/")) { |
2953 | + root.dataAttachments.push({"type": "audio", |
2954 | + "data": attachment, |
2955 | + "delegateSource": "MMS/MMSAudio.qml", |
2956 | + }) |
2957 | } else if (startsWith(attachment.contentType, "image/")) { |
2958 | root.dataAttachments.push({"type": "image", |
2959 | "data": attachment, |
2960 | "delegateSource": "MMS/MMSImage.qml", |
2961 | }) |
2962 | - //} else if (startsWith(attachment.contentType, "video/")) { |
2963 | - // TODO: implement proper video attachment support |
2964 | - // dataAttachments.push({type: "video", |
2965 | - // data: attachment, |
2966 | - // delegateSource: "MMS/MMSVideo.qml", |
2967 | - // }) |
2968 | } else if (startsWith(attachment.contentType, "application/smil") || |
2969 | startsWith(attachment.contentType, "application/x-smil")) { |
2970 | // smil files will always be ignored here |
2971 | @@ -102,6 +110,11 @@ |
2972 | "data": attachment, |
2973 | "delegateSource": "MMS/MMSContact.qml" |
2974 | }) |
2975 | + } else if (startsWith(attachment.contentType, "video/")) { |
2976 | + root.dataAttachments.push({"type": "video", |
2977 | + "data": attachment, |
2978 | + "delegateSource": "MMS/MMSVideo.qml", |
2979 | + }) |
2980 | } else { |
2981 | root.dataAttachments.push({"type": "default", |
2982 | "data": attachment, |
2983 | @@ -150,11 +163,6 @@ |
2984 | target: attachmentLoader |
2985 | anchors.left: parent ? parent.left : undefined |
2986 | } |
2987 | - PropertyChanges { |
2988 | - target: attachmentLoader |
2989 | - anchors.leftMargin: units.gu(1) |
2990 | - anchors.rightMargin: 0 |
2991 | - } |
2992 | }, |
2993 | State { |
2994 | when: !root.incoming |
2995 | @@ -163,11 +171,6 @@ |
2996 | target: attachmentLoader |
2997 | anchors.right: parent ? parent.right : undefined |
2998 | } |
2999 | - PropertyChanges { |
3000 | - target: attachmentLoader |
3001 | - anchors.leftMargin: 0 |
3002 | - anchors.rightMargin: units.gu(1) |
3003 | - } |
3004 | } |
3005 | ] |
3006 | source: modelData.delegateSource |
3007 | @@ -221,7 +224,7 @@ |
3008 | target: bubbleLoader.item |
3009 | property: "sender" |
3010 | value: messageData.sender.alias !== "" ? messageData.sender.alias : messageData.senderId |
3011 | - when: participants.length > 1 && bubbleLoader.status === Loader.Ready && messageData.senderId !== "self" |
3012 | + when: messageData.participants.length > 1 && bubbleLoader.status === Loader.Ready && messageData.senderId !== "self" |
3013 | } |
3014 | } |
3015 | } |
3016 | |
3017 | === modified file 'src/qml/MMSMessageBubble.qml' |
3018 | --- src/qml/MMSMessageBubble.qml 2014-08-27 16:05:37 +0000 |
3019 | +++ src/qml/MMSMessageBubble.qml 2016-01-14 12:41:18 +0000 |
3020 | @@ -25,6 +25,7 @@ |
3021 | messageStatus: messageData.textMessageStatus |
3022 | messageIncoming: incoming |
3023 | accountName: accountLabel |
3024 | + showDeliveryStatus: true |
3025 | |
3026 | states: [ |
3027 | State { |
3028 | |
3029 | === modified file 'src/qml/MainPage.qml' |
3030 | --- src/qml/MainPage.qml 2015-10-30 13:21:59 +0000 |
3031 | +++ src/qml/MainPage.qml 2016-01-14 12:41:18 +0000 |
3032 | @@ -33,11 +33,7 @@ |
3033 | threadList.startSelection() |
3034 | } |
3035 | |
3036 | - state: selectionMode ? "select" : searching ? "search" : "default" |
3037 | - title: selectionMode ? " " : i18n.tr("Messages") |
3038 | - flickable: null |
3039 | - |
3040 | - bottomEdgeEnabled: !selectionMode && !searching |
3041 | + bottomEdgeEnabled: !selectionMode && !searching && !mainView.dualPanel |
3042 | bottomEdgeTitle: i18n.tr("+") |
3043 | bottomEdgePageComponent: Messages { active: false } |
3044 | |
3045 | @@ -46,6 +42,8 @@ |
3046 | objectName: "searchField" |
3047 | visible: mainPage.searching |
3048 | anchors { |
3049 | + top: parent.top |
3050 | + topMargin: units.gu(1) |
3051 | left: parent.left |
3052 | right: parent.right |
3053 | rightMargin: units.gu(2) |
3054 | @@ -60,11 +58,32 @@ |
3055 | } |
3056 | } |
3057 | |
3058 | + header: PageHeader { |
3059 | + id: pageHeader |
3060 | + |
3061 | + property alias leadingActions: leadingBar.actions |
3062 | + property alias trailingActions: trailingBar.actions |
3063 | + |
3064 | + onTrailingActionsChanged: console.log("Trailing actions lenght: " + trailingActions.length) |
3065 | + |
3066 | + title: i18n.tr("Messages") |
3067 | + flickable: threadList |
3068 | + leadingActionBar { |
3069 | + id: leadingBar |
3070 | + } |
3071 | + |
3072 | + trailingActionBar { |
3073 | + id: trailingBar |
3074 | + } |
3075 | + } |
3076 | + |
3077 | states: [ |
3078 | - PageHeadState { |
3079 | + State { |
3080 | + id: defaultState |
3081 | name: "default" |
3082 | - head: mainPage.head |
3083 | - actions: [ |
3084 | + when: !searching && !selectionMode |
3085 | + |
3086 | + property list<QtObject> trailingActions: [ |
3087 | Action { |
3088 | objectName: "searchAction" |
3089 | iconName: "search" |
3090 | @@ -77,34 +96,55 @@ |
3091 | objectName: "settingsAction" |
3092 | text: i18n.tr("Settings") |
3093 | iconName: "settings" |
3094 | - onTriggered: pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) |
3095 | + onTriggered: pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("SettingsPage.qml")) |
3096 | } |
3097 | ] |
3098 | + |
3099 | + PropertyChanges { |
3100 | + target: pageHeader |
3101 | + trailingActions: defaultState.trailingActions |
3102 | + leadingActions: [] |
3103 | + } |
3104 | }, |
3105 | - PageHeadState { |
3106 | + State { |
3107 | + id: searchState |
3108 | name: "search" |
3109 | - head: mainPage.head |
3110 | - backAction: Action { |
3111 | - objectName: "cancelSearch" |
3112 | - visible: mainPage.searching |
3113 | - iconName: "back" |
3114 | - text: i18n.tr("Cancel") |
3115 | - onTriggered: { |
3116 | - searchField.text = "" |
3117 | - mainPage.searching = false |
3118 | + when: searching |
3119 | + |
3120 | + property list<QtObject> leadingActions: [ |
3121 | + Action { |
3122 | + objectName: "cancelSearch" |
3123 | + visible: mainPage.searching |
3124 | + iconName: "back" |
3125 | + text: i18n.tr("Cancel") |
3126 | + onTriggered: { |
3127 | + searchField.text = "" |
3128 | + mainPage.searching = false |
3129 | + } |
3130 | } |
3131 | + ] |
3132 | + |
3133 | + PropertyChanges { |
3134 | + target: pageHeader |
3135 | + contents: searchField |
3136 | + leadingActions: searchState.leadingActions |
3137 | + trailingActions: [] |
3138 | } |
3139 | - contents: searchField |
3140 | }, |
3141 | - PageHeadState { |
3142 | - name: "select" |
3143 | - head: mainPage.head |
3144 | - backAction: Action { |
3145 | - objectName: "selectionModeCancelAction" |
3146 | - iconName: "back" |
3147 | - onTriggered: threadList.cancelSelection() |
3148 | - } |
3149 | - actions: [ |
3150 | + State { |
3151 | + id: selectionState |
3152 | + name: "selection" |
3153 | + when: selectionMode |
3154 | + |
3155 | + property list<QtObject> leadingActions: [ |
3156 | + Action { |
3157 | + objectName: "selectionModeCancelAction" |
3158 | + iconName: "back" |
3159 | + onTriggered: threadList.cancelSelection() |
3160 | + } |
3161 | + ] |
3162 | + |
3163 | + property list<QtObject> trailingActions: [ |
3164 | Action { |
3165 | objectName: "selectionModeSelectAllAction" |
3166 | iconName: "select" |
3167 | @@ -117,6 +157,12 @@ |
3168 | onTriggered: threadList.endSelection() |
3169 | } |
3170 | ] |
3171 | + PropertyChanges { |
3172 | + target: pageHeader |
3173 | + title: " " |
3174 | + leadingActions: selectionState.leadingActions |
3175 | + trailingActions: selectionState.trailingActions |
3176 | + } |
3177 | } |
3178 | ] |
3179 | |
3180 | @@ -218,10 +264,11 @@ |
3181 | } |
3182 | properties["participantIds"] = participantIds |
3183 | properties["participants"] = model.participants |
3184 | + properties["presenceRequest"] = threadDelegate.presenceItem |
3185 | if (displayedEvent != null) { |
3186 | properties["scrollToEventId"] = displayedEvent.eventId |
3187 | } |
3188 | - mainStack.push(Qt.resolvedUrl("Messages.qml"), properties) |
3189 | + mainStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("Messages.qml"), properties) |
3190 | } |
3191 | } |
3192 | onItemPressAndHold: { |
3193 | |
3194 | === modified file 'src/qml/MessageBubble.qml' |
3195 | --- src/qml/MessageBubble.qml 2015-10-22 15:31:05 +0000 |
3196 | +++ src/qml/MessageBubble.qml 2016-01-14 12:41:18 +0000 |
3197 | @@ -24,7 +24,7 @@ |
3198 | import "dateUtils.js" as DateUtils |
3199 | import "3rd_party/ba-linkify.js" as BaLinkify |
3200 | |
3201 | -Rectangle { |
3202 | +BorderImage { |
3203 | id: root |
3204 | |
3205 | property int messageStatus: -1 |
3206 | @@ -34,10 +34,15 @@ |
3207 | property var messageTimeStamp |
3208 | property int maxDelegateWidth: units.gu(27) |
3209 | property string accountName |
3210 | + // FIXME for now we just display the delivery status if it's greater than Accepted |
3211 | + property bool showDeliveryStatus: false |
3212 | + property bool deliveryStatusAvailable: showDeliveryStatus && (statusDelivered || statusRead) |
3213 | |
3214 | readonly property bool error: (messageStatus === HistoryThreadModel.MessageStatusPermanentlyFailed) |
3215 | readonly property bool sending: (messageStatus === HistoryThreadModel.MessageStatusUnknown || |
3216 | messageStatus === HistoryThreadModel.MessageStatusTemporarilyFailed) && !messageIncoming |
3217 | + readonly property bool statusDelivered: (messageStatus === HistoryThreadModel.MessageStatusDelivered) |
3218 | + readonly property bool statusRead: (messageStatus === HistoryThreadModel.MessageStatusRead) |
3219 | |
3220 | // XXXX: should be hoisted |
3221 | function getCountryCode() { |
3222 | @@ -69,28 +74,33 @@ |
3223 | return text |
3224 | } |
3225 | |
3226 | - color: { |
3227 | + property string color: { |
3228 | if (error) { |
3229 | - return "#fc4949" |
3230 | + return "red" |
3231 | } else if (sending) { |
3232 | - return "#b2b2b2" |
3233 | + return "grey" |
3234 | } else if (messageIncoming) { |
3235 | - return "#ffffff" |
3236 | + return "white" |
3237 | } else { |
3238 | - return "#3fb24f" |
3239 | + // FIXME: use blue for IM accounts |
3240 | + return "green" |
3241 | } |
3242 | } |
3243 | - border.color: "#ACACAC" |
3244 | - |
3245 | - radius: units.gu(1) |
3246 | - height: senderName.height + senderName.anchors.topMargin + textLabel.height + textTimestamp.height + units.gu(1) |
3247 | + source: "assets/" + color + "_bubble.sci" |
3248 | + smooth: true |
3249 | + |
3250 | + // FIXME: maybe we should put everything inside a container to make width and height calculation easier |
3251 | + height: senderName.height + senderName.anchors.topMargin + textLabel.height + border.bottom + units.gu(0.5) + (oneLine ? 0 : messageFooter.height + messageFooter.anchors.topMargin) |
3252 | + |
3253 | + // if possible, put the timestamp and the delivery status in the same line as the text |
3254 | + property int oneLineWidth: textLabel.contentWidth + messageFooter.width |
3255 | + property bool oneLine: oneLineWidth <= units.gu(27) |
3256 | width: Math.min(units.gu(27), |
3257 | - Math.max(textLabel.contentWidth, textTimestamp.contentWidth, senderName.contentWidth)) |
3258 | + Math.max(oneLine ? oneLineWidth : textLabel.contentWidth, |
3259 | + messageFooter.width, |
3260 | + senderName.contentWidth, |
3261 | + border.right + border.left - units.gu(3))) |
3262 | + units.gu(3) |
3263 | - anchors{ |
3264 | - leftMargin: units.gu(1) |
3265 | - rightMargin: units.gu(1) |
3266 | - } |
3267 | |
3268 | Label { |
3269 | id: senderName |
3270 | @@ -126,73 +136,49 @@ |
3271 | color: root.messageIncoming ? UbuntuColors.darkGrey : "white" |
3272 | } |
3273 | |
3274 | - Label { |
3275 | - id: textTimestamp |
3276 | - objectName: "messageDate" |
3277 | + Row { |
3278 | + id: messageFooter |
3279 | + width: childrenRect.width |
3280 | + spacing: units.gu(1) |
3281 | |
3282 | - anchors{ |
3283 | + anchors { |
3284 | top: textLabel.bottom |
3285 | - topMargin: units.gu(0.5) |
3286 | - left: parent.left |
3287 | - leftMargin: units.gu(1) |
3288 | + topMargin: oneLine ? -textTimestamp.height : units.gu(0.5) |
3289 | + right: parent.right |
3290 | + rightMargin: units.gu(1) |
3291 | } |
3292 | |
3293 | - visible: !root.sending |
3294 | - height: units.gu(2) |
3295 | - width: visible ? maxDelegateWidth : 0 |
3296 | - fontSize: "xx-small" |
3297 | - color: root.messageIncoming ? UbuntuColors.lightGrey : "white" |
3298 | - opacity: root.messageIncoming ? 1.0 : 0.8 |
3299 | - elide: Text.ElideRight |
3300 | - text: { |
3301 | - if (messageTimeStamp === "") |
3302 | - return "" |
3303 | - |
3304 | - var str = Qt.formatTime(messageTimeStamp, Qt.DefaultLocaleShortDate) |
3305 | - if (root.accountName.length === 0 || !root.messageIncoming) { |
3306 | + Label { |
3307 | + id: textTimestamp |
3308 | + objectName: "messageDate" |
3309 | + |
3310 | + anchors.bottom: parent.bottom |
3311 | + visible: !root.sending |
3312 | + height: units.gu(2) |
3313 | + width: paintedWidth > maxDelegateWidth ? maxDelegateWidth : undefined |
3314 | + fontSize: "xx-small" |
3315 | + color: root.messageIncoming ? UbuntuColors.lightGrey : "white" |
3316 | + opacity: root.messageIncoming ? 1.0 : 0.8 |
3317 | + elide: Text.ElideRight |
3318 | + verticalAlignment: Text.AlignVCenter |
3319 | + text: { |
3320 | + if (messageTimeStamp === "") |
3321 | + return "" |
3322 | + |
3323 | + var str = Qt.formatTime(messageTimeStamp, Qt.DefaultLocaleShortDate) |
3324 | + if (root.accountName.length === 0 || !root.messageIncoming) { |
3325 | + return str |
3326 | + } |
3327 | + str += " @ %1".arg(root.accountName) |
3328 | return str |
3329 | } |
3330 | - str += " @ %1".arg(root.accountName) |
3331 | - return str |
3332 | - } |
3333 | - } |
3334 | - |
3335 | - ColoredImage { |
3336 | - id: bubbleArrow |
3337 | - |
3338 | - source: Qt.resolvedUrl("./assets/conversation_bubble_arrow.png") |
3339 | - color: root.color |
3340 | - asynchronous: false |
3341 | - anchors { |
3342 | - bottom: parent.bottom |
3343 | - bottomMargin: units.gu(2) |
3344 | - leftMargin: -1 |
3345 | - rightMargin: -1 |
3346 | - } |
3347 | - width: units.gu(1) |
3348 | - height: units.gu(1.5) |
3349 | - |
3350 | - states: [ |
3351 | - State { |
3352 | - when: root.messageIncoming |
3353 | - name: "incoming" |
3354 | - AnchorChanges { |
3355 | - target: bubbleArrow |
3356 | - anchors.right: root.left |
3357 | - } |
3358 | - }, |
3359 | - State { |
3360 | - when: !root.messageIncoming |
3361 | - name: "outgoing" |
3362 | - AnchorChanges { |
3363 | - target: bubbleArrow |
3364 | - anchors.left: root.right |
3365 | - } |
3366 | - PropertyChanges { |
3367 | - target: bubbleArrow |
3368 | - mirror: true |
3369 | - } |
3370 | - } |
3371 | - ] |
3372 | + } |
3373 | + |
3374 | + DeliveryStatus { |
3375 | + id: deliveryStatus |
3376 | + messageStatus: messageStatus |
3377 | + enabled: deliveryStatusAvailable |
3378 | + anchors.verticalCenter: textTimestamp.verticalCenter |
3379 | + } |
3380 | } |
3381 | } |
3382 | |
3383 | === modified file 'src/qml/MessageDelegate.qml' |
3384 | --- src/qml/MessageDelegate.qml 2014-08-28 21:54:12 +0000 |
3385 | +++ src/qml/MessageDelegate.qml 2016-01-14 12:41:18 +0000 |
3386 | @@ -26,6 +26,7 @@ |
3387 | property bool incoming: (messageData && messageData.senderId !== "self") |
3388 | property string accountLabel: "" |
3389 | property var _lastItem |
3390 | + property bool swipeLocked: false |
3391 | |
3392 | function deleteMessage() |
3393 | { |
3394 | |
3395 | === modified file 'src/qml/MessageDelegateFactory.qml' |
3396 | --- src/qml/MessageDelegateFactory.qml 2015-09-14 13:51:27 +0000 |
3397 | +++ src/qml/MessageDelegateFactory.qml 2016-01-14 12:41:18 +0000 |
3398 | @@ -35,6 +35,8 @@ |
3399 | signal resendMessage() |
3400 | signal copyMessage() |
3401 | signal showMessageDetails() |
3402 | + color: "transparent" |
3403 | + locked: loader.item.swipeLocked |
3404 | |
3405 | width: messageList.width |
3406 | leftSideAction: Action { |
3407 | |
3408 | === modified file 'src/qml/Messages.qml' |
3409 | --- src/qml/Messages.qml 2015-12-07 17:55:17 +0000 |
3410 | +++ src/qml/Messages.qml 2016-01-14 12:41:18 +0000 |
3411 | @@ -1,5 +1,5 @@ |
3412 | /* |
3413 | - * Copyright 2012, 2013, 2014 Canonical Ltd. |
3414 | + * Copyright 2012-2015 Canonical Ltd. |
3415 | * |
3416 | * This file is part of messaging-app. |
3417 | * |
3418 | @@ -25,6 +25,7 @@ |
3419 | import Ubuntu.History 0.1 |
3420 | import Ubuntu.Telephony 0.1 |
3421 | import Ubuntu.Contacts 0.1 |
3422 | +import messagingapp.private 0.1 |
3423 | |
3424 | import "dateUtils.js" as DateUtils |
3425 | |
3426 | @@ -45,8 +46,6 @@ |
3427 | // FIXME: MainView should provide if the view is in portait or landscape |
3428 | property int orientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation) |
3429 | property bool landscape: orientationAngle == 90 || orientationAngle == 270 |
3430 | - property var activeTransfer: null |
3431 | - property int activeAttachmentIndex: -1 |
3432 | property var sharedAttachmentsTransfer: [] |
3433 | property alias contactWatcher: contactWatcherInternal |
3434 | property string text: "" |
3435 | @@ -61,7 +60,16 @@ |
3436 | property string firstParticipantId: participantIds.length > 0 ? participantIds[0] : "" |
3437 | property variant firstParticipant: participants.length > 0 ? participants[0] : null |
3438 | property var threads: [] |
3439 | + property QtObject presenceRequest: presenceItem |
3440 | property var accountsModel: getAccountsModel() |
3441 | + property alias oskEnabled: keyboard.oskEnabled |
3442 | + property bool isReady: false |
3443 | + property string firstRecipientAlias: ((contactWatcher.isUnknown && |
3444 | + contactWatcher.isInteractive) || |
3445 | + contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias |
3446 | + |
3447 | + signal ready |
3448 | + |
3449 | function getAccountsModel() { |
3450 | var accounts = [] |
3451 | // on new chat dialogs display all possible accounts |
3452 | @@ -179,123 +187,44 @@ |
3453 | return (!tmpAccount || tmpAccount.type == AccountEntry.PhoneAccount || tmpAccount.type == AccountEntry.MultimediaAccount) |
3454 | } |
3455 | |
3456 | - Connections { |
3457 | - target: telepathyHelper |
3458 | - onSetupReady: { |
3459 | - // force reevaluation |
3460 | - messages.account = Qt.binding(getCurrentAccount) |
3461 | - messages.phoneAccount = Qt.binding(isPhoneAccount) |
3462 | - head.sections.model = Qt.binding(getSectionsModel) |
3463 | - head.sections.selectedIndex = Qt.binding(getSelectedIndex) |
3464 | - } |
3465 | - } |
3466 | - |
3467 | - |
3468 | - Connections { |
3469 | - target: chatManager |
3470 | - onChatEntryCreated: { |
3471 | - // TODO: track using chatId and not participants |
3472 | - if (accountId == account.accountId && |
3473 | - firstParticipant && participants[0] == firstParticipant.identifier) { |
3474 | - messages.chatEntry = chatEntry |
3475 | - } |
3476 | - } |
3477 | - onChatsChanged: { |
3478 | - for (var i in chatManager.chats) { |
3479 | - var chat = chatManager.chats[i] |
3480 | - // TODO: track using chatId and not participants |
3481 | - if (chat.account.accountId == account.accountId && |
3482 | - firstParticipant && chat.participants[0] == firstParticipant.identifier) { |
3483 | - messages.chatEntry = chat |
3484 | - return |
3485 | - } |
3486 | - } |
3487 | - messages.chatEntry = null |
3488 | - } |
3489 | - } |
3490 | - |
3491 | - Timer { |
3492 | - id: typingTimer |
3493 | - interval: 6000 |
3494 | - onTriggered: { |
3495 | - messages.userTyping = false; |
3496 | - } |
3497 | - } |
3498 | - |
3499 | - Repeater { |
3500 | - model: messages.chatEntry ? messages.chatEntry.chatStates : null |
3501 | - Item { |
3502 | - function processChatState() { |
3503 | - if (modelData.state == ChatEntry.ChannelChatStateComposing) { |
3504 | - messages.userTyping = true |
3505 | - typingTimer.start() |
3506 | - } else { |
3507 | - messages.userTyping = false |
3508 | - } |
3509 | - } |
3510 | - Component.onCompleted: processChatState() |
3511 | - Connections { |
3512 | - target: modelData |
3513 | - onStateChanged: processChatState() |
3514 | - } |
3515 | - } |
3516 | - } |
3517 | - |
3518 | - MessagesHeader { |
3519 | - id: header |
3520 | - width: parent ? parent.width - units.gu(2) : undefined |
3521 | - height: units.gu(5) |
3522 | - title: messages.title |
3523 | - subtitle: { |
3524 | - if (userTyping) { |
3525 | - return i18n.tr("Typing..") |
3526 | - } |
3527 | - switch (presenceRequest.type) { |
3528 | - case PresenceRequest.PresenceTypeAvailable: |
3529 | - return i18n.tr("Online") |
3530 | - case PresenceRequest.PresenceTypeOffline: |
3531 | - return i18n.tr("Offline") |
3532 | - case PresenceRequest.PresenceTypeAway: |
3533 | - return i18n.tr("Away") |
3534 | - case PresenceRequest.PresenceTypeBusy: |
3535 | - return i18n.tr("Busy") |
3536 | - default: |
3537 | - return "" |
3538 | - } |
3539 | - } |
3540 | - visible: true |
3541 | - } |
3542 | - |
3543 | - head { |
3544 | - id: head |
3545 | - sections.model: getSectionsModel() |
3546 | - sections.selectedIndex: getSelectedIndex() |
3547 | - } |
3548 | - |
3549 | - |
3550 | - function addAttachmentsToModel(transfer) { |
3551 | - for (var i in transfer.items) { |
3552 | - if (String(transfer.items[i].text).length > 0) { |
3553 | - messages.text = String(transfer.items[i].text) |
3554 | - continue |
3555 | - } |
3556 | - var attachment = {} |
3557 | - if (!startsWith(String(transfer.items[i].url),"file://")) { |
3558 | - messages.text = String(transfer.items[i].url) |
3559 | - continue |
3560 | - } |
3561 | - var filePath = String(transfer.items[i].url).replace('file://', '') |
3562 | - // get only the basename |
3563 | - attachment["contentType"] = application.fileMimeType(filePath) |
3564 | - if (startsWith(attachment["contentType"], "text/vcard") || |
3565 | - startsWith(attachment["contentType"], "text/x-vcard")) { |
3566 | - attachment["name"] = "contact.vcf" |
3567 | - } else { |
3568 | - attachment["name"] = filePath.split('/').reverse()[0] |
3569 | - } |
3570 | - attachment["filePath"] = filePath |
3571 | - attachments.append(attachment) |
3572 | - } |
3573 | + function addNewThreadToFilter(newAccountId, participantIds) { |
3574 | + var newAccount = telepathyHelper.accountForId(newAccountId) |
3575 | + var matchType = HistoryThreadModel.MatchCaseSensitive |
3576 | + if (newAccount.type == AccountEntry.PhoneAccount || newAccount.type == AccountEntry.MultimediaAccount) { |
3577 | + matchType = HistoryThreadModel.MatchPhoneNumber |
3578 | + } |
3579 | + |
3580 | + var thread = eventModel.threadForParticipants(newAccountId, |
3581 | + HistoryThreadModel.EventTypeText, |
3582 | + participantIds, |
3583 | + matchType, |
3584 | + true) |
3585 | + var threadId = thread.threadId |
3586 | + |
3587 | + // dont change the participants list |
3588 | + if (messages.participants.length == 0) { |
3589 | + messages.participants = thread.participants |
3590 | + var ids = [] |
3591 | + for (var i in messages.participants) { |
3592 | + ids.push(messages.participants[i].identifier) |
3593 | + } |
3594 | + messages.participantIds = ids; |
3595 | + } |
3596 | + |
3597 | + var found = false; |
3598 | + for (var i in messages.threads) { |
3599 | + if (messages.threads[i].threadId == threadId && messages.threads[i].accountId == newAccountId) { |
3600 | + found = true; |
3601 | + break; |
3602 | + } |
3603 | + } |
3604 | + |
3605 | + if (!found) { |
3606 | + messages.threads.push({"accountId": newAccountId, "threadId": threadId}) |
3607 | + reloadFilters = !reloadFilters |
3608 | + } |
3609 | + |
3610 | + return thread |
3611 | } |
3612 | |
3613 | function sendMessageNetworkCheck() { |
3614 | @@ -340,48 +269,21 @@ |
3615 | } |
3616 | |
3617 | // create the new thread and update the threadId list |
3618 | - var thread = eventModel.threadForParticipants(messages.account.accountId, |
3619 | - HistoryThreadModel.EventTypeText, |
3620 | - participantIds, |
3621 | - messages.account.type == AccountEntry.PhoneAccount ? HistoryThreadModel.MatchPhoneNumber |
3622 | - : HistoryThreadModel.MatchCaseSensitive, |
3623 | - true) |
3624 | - var threadId = thread.threadId |
3625 | - |
3626 | - // dont change the participants list |
3627 | - if (messages.participants.length == 0) { |
3628 | - messages.participants = thread.participants |
3629 | - var ids = [] |
3630 | - for (var i in messages.participants) { |
3631 | - ids.push(messages.participants[i].identifier) |
3632 | - } |
3633 | - messages.participantIds = ids; |
3634 | - } |
3635 | - |
3636 | - var found = false; |
3637 | - for (var i in messages.threads) { |
3638 | - if (messages.threads[i].threadId == threadId && messages.threads[i].accountId == messages.account.accountId) { |
3639 | - found = true; |
3640 | - break; |
3641 | - } |
3642 | - } |
3643 | - if (!found) { |
3644 | - messages.threads.push({"accountId": messages.account.accountId, "threadId": threadId}) |
3645 | - reloadFilters = !reloadFilters |
3646 | - } |
3647 | + var thread = addNewThreadToFilter(messages.account.accountId, participantIds) |
3648 | + |
3649 | for (var i=0; i < eventModel.count; i++) { |
3650 | var event = eventModel.get(i) |
3651 | if (event.senderId == "self" && event.accountId != messages.account.accountId) { |
3652 | var tmpAccount = telepathyHelper.accountForId(event.accountId) |
3653 | if (!tmpAccount || (tmpAccount.type == AccountEntry.MultimediaAccount && messages.account.type == AccountEntry.PhoneAccount)) { |
3654 | - // we don't add the information event if the last outgoing message |
3655 | + // we don't add the information event if the last outgoing message |
3656 | // was a fallback to a multimedia service |
3657 | break; |
3658 | } |
3659 | // if the last outgoing message used a different accountId, add an |
3660 | // information event and quit the loop |
3661 | eventModel.writeTextInformationEvent(messages.account.accountId, |
3662 | - threadId, |
3663 | + thread.threadId, |
3664 | participantIds, |
3665 | "") |
3666 | break; |
3667 | @@ -399,7 +301,7 @@ |
3668 | var timestamp = new Date() |
3669 | var tmpEventId = timestamp.toISOString() |
3670 | event["accountId"] = messages.account.accountId |
3671 | - event["threadId"] = threadId |
3672 | + event["threadId"] = thread.threadId |
3673 | event["eventId"] = tmpEventId |
3674 | event["type"] = HistoryEventModel.MessageTypeText |
3675 | event["participants"] = thread.participants |
3676 | @@ -417,7 +319,7 @@ |
3677 | var attachment = {} |
3678 | var item = attachments[i] |
3679 | attachment["accountId"] = messages.account.accountId |
3680 | - attachment["threadId"] = threadId |
3681 | + attachment["threadId"] = thread.threadId |
3682 | attachment["eventId"] = tmpEventId |
3683 | attachment["attachmentId"] = item[0] |
3684 | attachment["contentType"] = item[1] |
3685 | @@ -436,10 +338,14 @@ |
3686 | var isSelfContactKnown = account.selfContactId != "" |
3687 | if (isMmsGroupChat && !isSelfContactKnown) { |
3688 | // TODO: inform the user to enter the phone number of the selected sim card manually |
3689 | - // and use it in the telepathy-ofono account as selfContactId. |
3690 | - return |
3691 | - } |
3692 | - chatManager.sendMessage(messages.account.accountId, participantIds, text, attachments, properties) |
3693 | + // and use it in the telepathy-ofono account as selfContactId. |
3694 | + return false |
3695 | + } |
3696 | + var fallbackAccountId = chatManager.sendMessage(messages.account.accountId, participantIds, text, attachments, properties) |
3697 | + // create the new thread and update the threadId list |
3698 | + if (fallbackAccountId != messages.account.accountId) { |
3699 | + addNewThreadToFilter(fallbackAccountId, participantIds) |
3700 | + } |
3701 | } |
3702 | |
3703 | // FIXME: soon it won't be just about SIM cards, so the dialogs need updating |
3704 | @@ -455,120 +361,6 @@ |
3705 | return true |
3706 | } |
3707 | |
3708 | - PresenceRequest { |
3709 | - id: presenceRequest |
3710 | - accountId: { |
3711 | - // if this is a regular sms chat, try requesting the presence on |
3712 | - // a multimedia account |
3713 | - if (!account) { |
3714 | - return "" |
3715 | - } |
3716 | - if (account.type == AccountEntry.PhoneAccount) { |
3717 | - for (var i in telepathyHelper.accounts) { |
3718 | - var tmpAccount = telepathyHelper.accounts[i] |
3719 | - if (tmpAccount.type == AccountEntry.MultimediaAccount) { |
3720 | - return tmpAccount.accountId |
3721 | - } |
3722 | - } |
3723 | - return "" |
3724 | - } |
3725 | - return account.accountId |
3726 | - } |
3727 | - // we just request presence on 1-1 chats |
3728 | - identifier: participants.length == 1 ? participants[0].identifier : "" |
3729 | - } |
3730 | - |
3731 | - // this is necessary to automatically update the view when the |
3732 | - // default account changes in system settings |
3733 | - Connections { |
3734 | - target: mainView |
3735 | - onAccountChanged: { |
3736 | - if (!messages.phoneAccount) { |
3737 | - return |
3738 | - } |
3739 | - messages.account = mainView.account |
3740 | - } |
3741 | - } |
3742 | - |
3743 | - ActivityIndicator { |
3744 | - id: activityIndicator |
3745 | - anchors { |
3746 | - verticalCenter: parent.verticalCenter |
3747 | - horizontalCenter: parent.horizontalCenter |
3748 | - } |
3749 | - running: isSearching |
3750 | - visible: running |
3751 | - } |
3752 | - |
3753 | - ListModel { |
3754 | - id: attachments |
3755 | - } |
3756 | - |
3757 | - PictureImport { |
3758 | - id: pictureImporter |
3759 | - |
3760 | - onPictureReceived: { |
3761 | - var attachment = {} |
3762 | - var filePath = String(pictureUrl).replace('file://', '') |
3763 | - attachment["contentType"] = application.fileMimeType(filePath) |
3764 | - attachment["name"] = filePath.split('/').reverse()[0] |
3765 | - attachment["filePath"] = filePath |
3766 | - attachments.append(attachment) |
3767 | - textEntry.forceActiveFocus() |
3768 | - } |
3769 | - } |
3770 | - |
3771 | - flickable: null |
3772 | - |
3773 | - property bool isReady: false |
3774 | - signal ready |
3775 | - onReady: { |
3776 | - isReady = true |
3777 | - if (participants.length === 0 && keyboardFocus) |
3778 | - multiRecipient.forceFocus() |
3779 | - } |
3780 | - |
3781 | - property string firstRecipientAlias: ((contactWatcher.isUnknown && |
3782 | - contactWatcher.isInteractive) || |
3783 | - contactWatcher.alias === "") ? contactWatcher.identifier : contactWatcher.alias |
3784 | - title: { |
3785 | - if (selectionMode || participants.length == 0) { |
3786 | - return " " |
3787 | - } |
3788 | - |
3789 | - if (landscape) { |
3790 | - return "" |
3791 | - } |
3792 | - if (participants.length > 0) { |
3793 | - if (participants.length == 1) { |
3794 | - return firstRecipientAlias |
3795 | - } else { |
3796 | - // TRANSLATORS: %1 refers to the number of participants in a group chat |
3797 | - return i18n.tr("Group (%1)").arg(participants.length) |
3798 | - } |
3799 | - } |
3800 | - return i18n.tr("New Message") |
3801 | - } |
3802 | - |
3803 | - Component.onCompleted: { |
3804 | - if (messages.accountId !== "") { |
3805 | - var account = telepathyHelper.accountForId(messages.accountId) |
3806 | - if (account && account.type == AccountEntry.MultimediaAccount) { |
3807 | - // fallback the first available phone account |
3808 | - if (telepathyHelper.phoneAccounts.length > 0) { |
3809 | - messages.accountId = telepathyHelper.phoneAccounts[0].accountId |
3810 | - } |
3811 | - } |
3812 | - } |
3813 | - addAttachmentsToModel(sharedAttachmentsTransfer) |
3814 | - } |
3815 | - |
3816 | - onActiveChanged: { |
3817 | - if (active && (eventModel.count > 0)){ |
3818 | - swipeItemDemo.enable() |
3819 | - } |
3820 | - } |
3821 | - |
3822 | function updateFilters(accounts, participants, reload, threads) { |
3823 | if (participants.length == 0 || accounts.length == 0) { |
3824 | return null |
3825 | @@ -584,7 +376,7 @@ |
3826 | } |
3827 | return Qt.createQmlObject(componentUnion.arg(componentFilters), eventModel) |
3828 | } |
3829 | - |
3830 | + |
3831 | var filterAccounts = [] |
3832 | |
3833 | if (messages.accountsModel.length == 1 && messages.accountsModel[0].type == AccountEntry.GenericAccount) { |
3834 | @@ -596,9 +388,9 @@ |
3835 | filterAccounts.push(account) |
3836 | } |
3837 | } |
3838 | - } |
3839 | + } |
3840 | |
3841 | - for (var i in filterAccounts) { |
3842 | + for (var i in filterAccounts) { |
3843 | var account = filterAccounts[i]; |
3844 | var filterValue = eventModel.threadIdForParticipants(account.accountId, |
3845 | HistoryThreadModel.EventTypeText, |
3846 | @@ -628,8 +420,283 @@ |
3847 | return eventModel.markEventAsRead(accountId, threadId, eventId, type); |
3848 | } |
3849 | |
3850 | + header: PageHeader { |
3851 | + id: pageHeader |
3852 | + |
3853 | + property alias leadingActions: leadingBar.actions |
3854 | + property alias trailingActions: trailingBar.actions |
3855 | + |
3856 | + title: { |
3857 | + if (landscape) { |
3858 | + return "" |
3859 | + } |
3860 | + |
3861 | + if (participants.length == 1) { |
3862 | + return firstRecipientAlias |
3863 | + } |
3864 | + |
3865 | + return i18n.tr("New Message") |
3866 | + } |
3867 | + |
3868 | + Sections { |
3869 | + id: sections |
3870 | + anchors { |
3871 | + left: parent.left |
3872 | + leftMargin: units.gu(2) |
3873 | + bottom: parent.bottom |
3874 | + } |
3875 | + model: getSectionsModel() |
3876 | + selectedIndex: getSelectedIndex() |
3877 | + } |
3878 | + |
3879 | + extension: sections.model.length > 1 ? sections : null |
3880 | + |
3881 | + leadingActionBar { |
3882 | + id: leadingBar |
3883 | + } |
3884 | + |
3885 | + trailingActionBar { |
3886 | + id: trailingBar |
3887 | + } |
3888 | + } |
3889 | + |
3890 | + states: [ |
3891 | + State { |
3892 | + id: selectionState |
3893 | + name: "selection" |
3894 | + when: selectionMode |
3895 | + |
3896 | + property list<QtObject> leadingActions: [ |
3897 | + Action { |
3898 | + objectName: "selectionModeCancelAction" |
3899 | + iconName: "back" |
3900 | + onTriggered: messageList.cancelSelection() |
3901 | + } |
3902 | + ] |
3903 | + |
3904 | + property list<QtObject> trailingActions: [ |
3905 | + Action { |
3906 | + objectName: "selectionModeSelectAllAction" |
3907 | + iconName: "select" |
3908 | + onTriggered: { |
3909 | + if (messageList.selectedItems.count === messageList.count) { |
3910 | + messageList.clearSelection() |
3911 | + } else { |
3912 | + messageList.selectAll() |
3913 | + } |
3914 | + } |
3915 | + }, |
3916 | + Action { |
3917 | + objectName: "selectionModeDeleteAction" |
3918 | + enabled: messageList.selectedItems.count > 0 |
3919 | + iconName: "delete" |
3920 | + onTriggered: messageList.endSelection() |
3921 | + } |
3922 | + ] |
3923 | + |
3924 | + PropertyChanges { |
3925 | + target: pageHeader |
3926 | + title: " " |
3927 | + leadingActions: selectionState.leadingActions |
3928 | + trailingActions: selectionState.trailingActions |
3929 | + } |
3930 | + }, |
3931 | + State { |
3932 | + id: groupChatState |
3933 | + name: "groupChat" |
3934 | + when: groupChat |
3935 | + |
3936 | + property list<QtObject> trailingActions: [ |
3937 | + Action { |
3938 | + objectName: "groupChatAction" |
3939 | + iconName: "contact-group" |
3940 | + onTriggered: PopupUtils.open(participantsPopover, screenTop) |
3941 | + } |
3942 | + ] |
3943 | + |
3944 | + PropertyChanges { |
3945 | + target: pageHeader |
3946 | + // TRANSLATORS: %1 refers to the number of participants in a group chat |
3947 | + title: i18n.tr("Group (%1)").arg(participants.length) |
3948 | + contents: headerContents |
3949 | + trailingActions: groupChatState.trailingActions |
3950 | + } |
3951 | + }, |
3952 | + State { |
3953 | + id: unknownContactState |
3954 | + name: "unknownContact" |
3955 | + when: participants.length == 1 && contactWatcher.isUnknown |
3956 | + |
3957 | + property list<QtObject> trailingActions: [ |
3958 | + Action { |
3959 | + objectName: "contactCallAction" |
3960 | + visible: participants.length == 1 && contactWatcher.interactive |
3961 | + iconName: "call-start" |
3962 | + text: i18n.tr("Call") |
3963 | + onTriggered: { |
3964 | + Qt.inputMethod.hide() |
3965 | + // FIXME: support other things than just phone numbers |
3966 | + Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier)) |
3967 | + } |
3968 | + }, |
3969 | + Action { |
3970 | + objectName: "addContactAction" |
3971 | + visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive |
3972 | + iconName: "contact-new" |
3973 | + text: i18n.tr("Add") |
3974 | + onTriggered: { |
3975 | + Qt.inputMethod.hide() |
3976 | + // FIXME: support other things than just phone numbers |
3977 | + mainView.addPhoneToContact(messages, "", contactWatcher.identifier, null, null) |
3978 | + } |
3979 | + } |
3980 | + ] |
3981 | + PropertyChanges { |
3982 | + target: pageHeader |
3983 | + contents: headerContents |
3984 | + trailingActions: unknownContactState.trailingActions |
3985 | + } |
3986 | + }, |
3987 | + State { |
3988 | + id: newMessageState |
3989 | + name: "newMessage" |
3990 | + when: participants.length === 0 |
3991 | + property list<QtObject> trailingActions: [ |
3992 | + Action { |
3993 | + objectName: "contactList" |
3994 | + iconName: "contact" |
3995 | + onTriggered: { |
3996 | + Qt.inputMethod.hide() |
3997 | + mainStack.addPageToCurrentColumn(messages, Qt.resolvedUrl("NewRecipientPage.qml"), {"multiRecipient": multiRecipient, "parentPage": messages}) |
3998 | + } |
3999 | + } |
4000 | + |
4001 | + ] |
4002 | + |
4003 | + property Item contents: MultiRecipientInput { |
4004 | + id: multiRecipient |
4005 | + objectName: "multiRecipient" |
4006 | + enabled: visible |
4007 | + anchors { |
4008 | + left: parent ? parent.left : undefined |
4009 | + right: parent ? parent.right : undefined |
4010 | + rightMargin: units.gu(2) |
4011 | + top: parent ? parent.top: undefined |
4012 | + topMargin: units.gu(1) |
4013 | + } |
4014 | + } |
4015 | + |
4016 | + PropertyChanges { |
4017 | + target: pageHeader |
4018 | + title: " " |
4019 | + trailingActions: newMessageState.trailingActions |
4020 | + contents: newMessageState.contents |
4021 | + } |
4022 | + }, |
4023 | + State { |
4024 | + id: knownContactState |
4025 | + name: "knownContact" |
4026 | + when: participants.length == 1 && !contactWatcher.isUnknown |
4027 | + property list<QtObject> trailingActions: [ |
4028 | + Action { |
4029 | + objectName: "contactCallKnownAction" |
4030 | + visible: participants.length == 1 && messages.phoneAccount |
4031 | + iconName: "call-start" |
4032 | + text: i18n.tr("Call") |
4033 | + onTriggered: { |
4034 | + Qt.inputMethod.hide() |
4035 | + // FIXME: support other things than just phone numbers |
4036 | + Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier)) |
4037 | + } |
4038 | + }, |
4039 | + Action { |
4040 | + objectName: "contactProfileAction" |
4041 | + visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount |
4042 | + iconSource: "image://theme/contact" |
4043 | + text: i18n.tr("Contact") |
4044 | + onTriggered: { |
4045 | + mainView.showContactDetails(messages, contactWatcher.contactId, null, null) |
4046 | + } |
4047 | + } |
4048 | + ] |
4049 | + PropertyChanges { |
4050 | + target: pageHeader |
4051 | + contents: headerContents |
4052 | + trailingActions: knownContactState.trailingActions |
4053 | + } |
4054 | + } |
4055 | + ] |
4056 | + |
4057 | + Component.onCompleted: { |
4058 | + if (messages.accountId !== "") { |
4059 | + var account = telepathyHelper.accountForId(messages.accountId) |
4060 | + if (account && account.type == AccountEntry.MultimediaAccount) { |
4061 | + // fallback the first available phone account |
4062 | + if (telepathyHelper.phoneAccounts.length > 0) { |
4063 | + messages.accountId = telepathyHelper.phoneAccounts[0].accountId |
4064 | + } |
4065 | + } |
4066 | + } |
4067 | + composeBar.addAttachments(sharedAttachmentsTransfer) |
4068 | + } |
4069 | + |
4070 | + onReady: { |
4071 | + isReady = true |
4072 | + if (participants.length === 0 && keyboardFocus) |
4073 | + multiRecipient.forceFocus() |
4074 | + } |
4075 | + |
4076 | + onActiveChanged: { |
4077 | + if (active && (eventModel.count > 0)){ |
4078 | + swipeItemDemo.enable() |
4079 | + } |
4080 | + } |
4081 | + |
4082 | + Connections { |
4083 | + target: telepathyHelper |
4084 | + onSetupReady: { |
4085 | + // force reevaluation |
4086 | + messages.account = Qt.binding(getCurrentAccount) |
4087 | + messages.phoneAccount = Qt.binding(isPhoneAccount) |
4088 | + head.sections.model = Qt.binding(getSectionsModel) |
4089 | + head.sections.selectedIndex = Qt.binding(getSelectedIndex) |
4090 | + } |
4091 | + } |
4092 | + |
4093 | + Connections { |
4094 | + target: chatManager |
4095 | + onChatEntryCreated: { |
4096 | + // TODO: track using chatId and not participants |
4097 | + if (accountId == account.accountId && |
4098 | + firstParticipant && participants[0] == firstParticipant.identifier) { |
4099 | + messages.chatEntry = chatEntry |
4100 | + } |
4101 | + } |
4102 | + onChatsChanged: { |
4103 | + for (var i in chatManager.chats) { |
4104 | + var chat = chatManager.chats[i] |
4105 | + // TODO: track using chatId and not participants |
4106 | + if (chat.account.accountId == account.accountId && |
4107 | + firstParticipant && chat.participants[0] == firstParticipant.identifier) { |
4108 | + messages.chatEntry = chat |
4109 | + return |
4110 | + } |
4111 | + } |
4112 | + messages.chatEntry = null |
4113 | + } |
4114 | + } |
4115 | + |
4116 | + // this is necessary to automatically update the view when the |
4117 | + // default account changes in system settings |
4118 | Connections { |
4119 | target: mainView |
4120 | + onAccountChanged: { |
4121 | + if (!messages.phoneAccount) { |
4122 | + return |
4123 | + } |
4124 | + messages.account = mainView.account |
4125 | + } |
4126 | + |
4127 | onApplicationActiveChanged: { |
4128 | if (mainView.applicationActive) { |
4129 | for (var i in pendingEventsToMarkAsRead) { |
4130 | @@ -641,28 +708,96 @@ |
4131 | } |
4132 | } |
4133 | |
4134 | - Component { |
4135 | - id: attachmentPopover |
4136 | - |
4137 | - Popover { |
4138 | - id: popover |
4139 | - Column { |
4140 | - id: containerLayout |
4141 | - anchors { |
4142 | - left: parent.left |
4143 | - top: parent.top |
4144 | - right: parent.right |
4145 | + Connections { |
4146 | + target: messages.head.sections |
4147 | + onSelectedIndexChanged: { |
4148 | + messages.account = messages.accountsModel[head.sections.selectedIndex] |
4149 | + } |
4150 | + } |
4151 | + |
4152 | + Timer { |
4153 | + id: typingTimer |
4154 | + interval: 6000 |
4155 | + onTriggered: { |
4156 | + messages.userTyping = false; |
4157 | + } |
4158 | + } |
4159 | + |
4160 | + Repeater { |
4161 | + model: messages.chatEntry ? messages.chatEntry.chatStates : null |
4162 | + Item { |
4163 | + function processChatState() { |
4164 | + if (modelData.state == ChatEntry.ChannelChatStateComposing) { |
4165 | + messages.userTyping = true |
4166 | + typingTimer.start() |
4167 | + } else { |
4168 | + messages.userTyping = false |
4169 | } |
4170 | - ListItem.Standard { |
4171 | - text: i18n.tr("Remove") |
4172 | - onClicked: { |
4173 | - attachments.remove(activeAttachmentIndex) |
4174 | - PopupUtils.close(popover) |
4175 | + } |
4176 | + Component.onCompleted: processChatState() |
4177 | + Connections { |
4178 | + target: modelData |
4179 | + onStateChanged: processChatState() |
4180 | + } |
4181 | + } |
4182 | + } |
4183 | + |
4184 | + MessagesHeader { |
4185 | + id: headerContents |
4186 | + width: parent ? parent.width - units.gu(2) : undefined |
4187 | + height: units.gu(5) |
4188 | + title: pageHeader.title |
4189 | + subtitle: { |
4190 | + if (userTyping) { |
4191 | + return i18n.tr("Typing..") |
4192 | + } |
4193 | + switch (presenceRequest.type) { |
4194 | + case PresenceRequest.PresenceTypeAvailable: |
4195 | + return i18n.tr("Online") |
4196 | + case PresenceRequest.PresenceTypeOffline: |
4197 | + return i18n.tr("Offline") |
4198 | + case PresenceRequest.PresenceTypeAway: |
4199 | + return i18n.tr("Away") |
4200 | + case PresenceRequest.PresenceTypeBusy: |
4201 | + return i18n.tr("Busy") |
4202 | + default: |
4203 | + return "" |
4204 | + } |
4205 | + } |
4206 | + visible: true |
4207 | + } |
4208 | + |
4209 | + PresenceRequest { |
4210 | + id: presenceItem |
4211 | + accountId: { |
4212 | + // if this is a regular sms chat, try requesting the presence on |
4213 | + // a multimedia account |
4214 | + if (!account) { |
4215 | + return "" |
4216 | + } |
4217 | + if (account.type == AccountEntry.PhoneAccount) { |
4218 | + for (var i in telepathyHelper.accounts) { |
4219 | + var tmpAccount = telepathyHelper.accounts[i] |
4220 | + if (tmpAccount.type == AccountEntry.MultimediaAccount) { |
4221 | + return tmpAccount.accountId |
4222 | } |
4223 | } |
4224 | + return "" |
4225 | } |
4226 | - Component.onDestruction: activeAttachmentIndex = -1 |
4227 | - } |
4228 | + return account.accountId |
4229 | + } |
4230 | + // we just request presence on 1-1 chats |
4231 | + identifier: participants.length == 1 ? participants[0].identifier : "" |
4232 | + } |
4233 | + |
4234 | + ActivityIndicator { |
4235 | + id: activityIndicator |
4236 | + anchors { |
4237 | + verticalCenter: parent.verticalCenter |
4238 | + horizontalCenter: parent.horizontalCenter |
4239 | + } |
4240 | + running: isSearching |
4241 | + visible: running |
4242 | } |
4243 | |
4244 | Component { |
4245 | @@ -727,13 +862,6 @@ |
4246 | } |
4247 | } |
4248 | |
4249 | - Connections { |
4250 | - target: messages.head.sections |
4251 | - onSelectedIndexChanged: { |
4252 | - messages.account = messages.accountsModel[head.sections.selectedIndex] |
4253 | - } |
4254 | - } |
4255 | - |
4256 | Loader { |
4257 | id: searchListLoader |
4258 | |
4259 | @@ -748,7 +876,7 @@ |
4260 | topMargin: units.gu(2) |
4261 | left: parent.left |
4262 | right: parent.right |
4263 | - bottom: bottomPanel.top |
4264 | + bottom: composeBar.top |
4265 | } |
4266 | z: 1 |
4267 | Behavior on height { |
4268 | @@ -805,160 +933,6 @@ |
4269 | addressableFields: messages.account ? messages.account.addressableVCardFields : ["tel"] // just to have a fallback there |
4270 | } |
4271 | |
4272 | - onAccountsModelChanged: { |
4273 | - reloadFilters = !reloadFilters |
4274 | - } |
4275 | - |
4276 | - Action { |
4277 | - id: backButton |
4278 | - objectName: "backButton" |
4279 | - iconName: "back" |
4280 | - onTriggered: { |
4281 | - if (typeof mainPage !== 'undefined') { |
4282 | - mainPage.temporaryProperties = null |
4283 | - } |
4284 | - mainStack.pop() |
4285 | - } |
4286 | - } |
4287 | - |
4288 | - states: [ |
4289 | - PageHeadState { |
4290 | - name: "selection" |
4291 | - head: messages.head |
4292 | - when: selectionMode |
4293 | - |
4294 | - backAction: Action { |
4295 | - objectName: "selectionModeCancelAction" |
4296 | - iconName: "back" |
4297 | - onTriggered: messageList.cancelSelection() |
4298 | - } |
4299 | - |
4300 | - actions: [ |
4301 | - Action { |
4302 | - objectName: "selectionModeSelectAllAction" |
4303 | - iconName: "select" |
4304 | - onTriggered: { |
4305 | - if (messageList.selectedItems.count === messageList.count) { |
4306 | - messageList.clearSelection() |
4307 | - } else { |
4308 | - messageList.selectAll() |
4309 | - } |
4310 | - } |
4311 | - }, |
4312 | - Action { |
4313 | - objectName: "selectionModeDeleteAction" |
4314 | - enabled: messageList.selectedItems.count > 0 |
4315 | - iconName: "delete" |
4316 | - onTriggered: messageList.endSelection() |
4317 | - } |
4318 | - ] |
4319 | - }, |
4320 | - PageHeadState { |
4321 | - name: "groupChat" |
4322 | - head: messages.head |
4323 | - when: groupChat |
4324 | - contents: header |
4325 | - backAction: backButton |
4326 | - |
4327 | - actions: [ |
4328 | - Action { |
4329 | - objectName: "groupChatAction" |
4330 | - iconName: "contact-group" |
4331 | - onTriggered: PopupUtils.open(participantsPopover, screenTop) |
4332 | - } |
4333 | - ] |
4334 | - }, |
4335 | - PageHeadState { |
4336 | - name: "unknownContact" |
4337 | - head: messages.head |
4338 | - when: participants.length == 1 && contactWatcher.isUnknown |
4339 | - backAction: backButton |
4340 | - contents: header |
4341 | - |
4342 | - actions: [ |
4343 | - Action { |
4344 | - objectName: "contactCallAction" |
4345 | - visible: participants.length == 1 && contactWatcher.interactive |
4346 | - iconName: "call-start" |
4347 | - text: i18n.tr("Call") |
4348 | - onTriggered: { |
4349 | - Qt.inputMethod.hide() |
4350 | - // FIXME: support other things than just phone numbers |
4351 | - Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier)) |
4352 | - } |
4353 | - }, |
4354 | - Action { |
4355 | - objectName: "addContactAction" |
4356 | - visible: contactWatcher.isUnknown && participants.length == 1 && contactWatcher.interactive |
4357 | - iconName: "contact-new" |
4358 | - text: i18n.tr("Add") |
4359 | - onTriggered: { |
4360 | - Qt.inputMethod.hide() |
4361 | - // FIXME: support other things than just phone numbers |
4362 | - mainView.addPhoneToContact("", contactWatcher.identifier, null, null) |
4363 | - } |
4364 | - } |
4365 | - ] |
4366 | - }, |
4367 | - PageHeadState { |
4368 | - name: "newMessage" |
4369 | - head: messages.head |
4370 | - when: participants.length === 0 && isReady |
4371 | - backAction: backButton |
4372 | - actions: [ |
4373 | - Action { |
4374 | - objectName: "contactList" |
4375 | - iconName: "contact" |
4376 | - onTriggered: { |
4377 | - Qt.inputMethod.hide() |
4378 | - mainStack.push(Qt.resolvedUrl("NewRecipientPage.qml"), {"multiRecipient": multiRecipient, "parentPage": messages}) |
4379 | - } |
4380 | - } |
4381 | - |
4382 | - ] |
4383 | - |
4384 | - contents: MultiRecipientInput { |
4385 | - id: multiRecipient |
4386 | - objectName: "multiRecipient" |
4387 | - enabled: visible |
4388 | - anchors { |
4389 | - left: parent ? parent.left : undefined |
4390 | - right: parent ? parent.right : undefined |
4391 | - rightMargin: units.gu(2) |
4392 | - } |
4393 | - } |
4394 | - }, |
4395 | - PageHeadState { |
4396 | - name: "knownContact" |
4397 | - head: messages.head |
4398 | - when: participants.length == 1 && !contactWatcher.isUnknown |
4399 | - backAction: backButton |
4400 | - contents: header |
4401 | - actions: [ |
4402 | - Action { |
4403 | - objectName: "contactCallKnownAction" |
4404 | - visible: participants.length == 1 && messages.phoneAccount |
4405 | - iconName: "call-start" |
4406 | - text: i18n.tr("Call") |
4407 | - onTriggered: { |
4408 | - Qt.inputMethod.hide() |
4409 | - // FIXME: support other things than just phone numbers |
4410 | - Qt.openUrlExternally("tel:///" + encodeURIComponent(contactWatcher.identifier)) |
4411 | - } |
4412 | - }, |
4413 | - Action { |
4414 | - objectName: "contactProfileAction" |
4415 | - visible: !contactWatcher.isUnknown && participants.length == 1 && messages.phoneAccount |
4416 | - iconSource: "image://theme/contact" |
4417 | - text: i18n.tr("Contact") |
4418 | - onTriggered: { |
4419 | - mainView.showContactDetails(contactWatcher.contactId, null, null) |
4420 | - } |
4421 | - } |
4422 | - ] |
4423 | - } |
4424 | - ] |
4425 | - |
4426 | HistoryEventModel { |
4427 | id: eventModel |
4428 | type: HistoryThreadModel.EventTypeText |
4429 | @@ -1023,392 +997,125 @@ |
4430 | objectName: "messageList" |
4431 | visible: !isSearching |
4432 | |
4433 | + Rectangle { |
4434 | + color: Theme.palette.normal.background |
4435 | + anchors.fill: parent |
4436 | + Image { |
4437 | + width: units.gu(20) |
4438 | + fillMode: Image.PreserveAspectFit |
4439 | + anchors.centerIn: parent |
4440 | + visible: source !== "" |
4441 | + source: { |
4442 | + var accountId = "" |
4443 | + |
4444 | + if (messages.account) { |
4445 | + accountId = messages.account.accountId |
4446 | + } |
4447 | + |
4448 | + if (presenceRequest.type != PresenceRequest.PresenceTypeUnknown |
4449 | + && presenceRequest.type != PresenceRequest.PresenceTypeUnset) { |
4450 | + accountId = presenceRequest.accountId |
4451 | + } |
4452 | + |
4453 | + return telepathyHelper.accountForId(accountId).protocolInfo.backgroundImage |
4454 | + } |
4455 | + z: 1 |
4456 | + } |
4457 | + z: -1 |
4458 | + } |
4459 | + |
4460 | // because of the header |
4461 | clip: true |
4462 | anchors { |
4463 | top: screenTop.bottom |
4464 | left: parent.left |
4465 | right: parent.right |
4466 | - bottom: bottomPanel.top |
4467 | + bottom: composeBar.top |
4468 | } |
4469 | } |
4470 | |
4471 | - Item { |
4472 | - id: bottomPanel |
4473 | - property int defaultHeight: textEntry.height + units.gu(2) |
4474 | - anchors.bottom: isSearching ? parent.bottom : keyboard.top |
4475 | - anchors.left: parent.left |
4476 | - anchors.right: parent.right |
4477 | - height: { |
4478 | - if (selectionMode || (participants.length > 0 && !contactWatcher.interactive)) { |
4479 | - return 0 |
4480 | - } else { |
4481 | - if (messages.height - keyboard.height - screenTop.y > defaultHeight) { |
4482 | - return defaultHeight |
4483 | - } else { |
4484 | - return messages.height - keyboard.height - screenTop.y |
4485 | - } |
4486 | - } |
4487 | - } |
4488 | - visible: !selectionMode && !isSearching |
4489 | - clip: true |
4490 | - MouseArea { |
4491 | - anchors.fill: parent |
4492 | - onClicked: { |
4493 | - messageTextArea.forceActiveFocus() |
4494 | - } |
4495 | - } |
4496 | - |
4497 | - Behavior on height { |
4498 | - UbuntuNumberAnimation { } |
4499 | - } |
4500 | - |
4501 | - ListItem.ThinDivider { |
4502 | - anchors.top: parent.top |
4503 | - } |
4504 | - |
4505 | - Icon { |
4506 | - id: attachButton |
4507 | - objectName: "attachButton" |
4508 | - anchors.left: parent.left |
4509 | - anchors.leftMargin: units.gu(2) |
4510 | - anchors.verticalCenter: sendButton.verticalCenter |
4511 | - height: units.gu(3) |
4512 | - width: units.gu(3) |
4513 | - color: "gray" |
4514 | - name: "camera-app-symbolic" |
4515 | - MouseArea { |
4516 | - anchors.fill: parent |
4517 | - anchors.margins: units.gu(-2) |
4518 | - onClicked: { |
4519 | - Qt.inputMethod.hide() |
4520 | - pictureImporter.requestNewPicture() |
4521 | - } |
4522 | - } |
4523 | - } |
4524 | - |
4525 | - StyledItem { |
4526 | - id: textEntry |
4527 | - property alias text: messageTextArea.text |
4528 | - property alias inputMethodComposing: messageTextArea.inputMethodComposing |
4529 | - property int fullSize: attachmentThumbnails.height + messageTextArea.height |
4530 | - style: Theme.createStyleComponent("TextAreaStyle.qml", textEntry) |
4531 | - anchors.bottomMargin: units.gu(1) |
4532 | - anchors.bottom: parent.bottom |
4533 | - anchors.left: attachButton.right |
4534 | - anchors.leftMargin: units.gu(1) |
4535 | - anchors.right: sendButton.left |
4536 | - anchors.rightMargin: units.gu(1) |
4537 | - height: attachments.count !== 0 ? fullSize + units.gu(1.5) : fullSize |
4538 | - onActiveFocusChanged: { |
4539 | - if(activeFocus) { |
4540 | - messageTextArea.forceActiveFocus() |
4541 | - } else { |
4542 | - focus = false |
4543 | - } |
4544 | - } |
4545 | - focus: false |
4546 | - MouseArea { |
4547 | - anchors.fill: parent |
4548 | - onClicked: messageTextArea.forceActiveFocus() |
4549 | - } |
4550 | - Flow { |
4551 | - id: attachmentThumbnails |
4552 | - spacing: units.gu(1) |
4553 | - anchors{ |
4554 | - left: parent.left |
4555 | - right: parent.right |
4556 | - top: parent.top |
4557 | - topMargin: units.gu(1) |
4558 | - leftMargin: units.gu(1) |
4559 | - rightMargin: units.gu(1) |
4560 | - } |
4561 | - height: childrenRect.height |
4562 | - |
4563 | - Component { |
4564 | - id: thumbnailImage |
4565 | - UbuntuShape { |
4566 | - property int index |
4567 | - property string filePath |
4568 | - |
4569 | - width: childrenRect.width |
4570 | - height: childrenRect.height |
4571 | - |
4572 | - image: Image { |
4573 | - id: avatarImage |
4574 | - width: units.gu(8) |
4575 | - height: units.gu(8) |
4576 | - sourceSize.height: height |
4577 | - sourceSize.width: width |
4578 | - fillMode: Image.PreserveAspectCrop |
4579 | - source: filePath |
4580 | - asynchronous: true |
4581 | - } |
4582 | - MouseArea { |
4583 | - anchors.fill: parent |
4584 | - onPressAndHold: { |
4585 | - mouse.accept = true |
4586 | - Qt.inputMethod.hide() |
4587 | - activeAttachmentIndex = index |
4588 | - PopupUtils.open(attachmentPopover, parent) |
4589 | - } |
4590 | - } |
4591 | - } |
4592 | - } |
4593 | - |
4594 | - Component { |
4595 | - id: thumbnailContact |
4596 | - Item { |
4597 | - id: attachment |
4598 | - |
4599 | - readonly property int contactsCount:vcardParser.contacts ? vcardParser.contacts.length : 0 |
4600 | - property int index |
4601 | - property string filePath |
4602 | - property alias vcard: vcardParser |
4603 | - property string contactDisplayName: { |
4604 | - if (contactsCount > 0) { |
4605 | - var contact = vcard.contacts[0] |
4606 | - if (contact.displayLabel.label && (contact.displayLabel.label != "")) { |
4607 | - return contact.displayLabel.label |
4608 | - } else if (contact.name) { |
4609 | - var contacFullName = contact.name.firstName |
4610 | - if (contact.name.midleName) { |
4611 | - contacFullName += " " + contact.name.midleName |
4612 | - } |
4613 | - if (contact.name.lastName) { |
4614 | - contacFullName += " " + contact.name.lastName |
4615 | - } |
4616 | - return contacFullName |
4617 | - } |
4618 | - return i18n.tr("Unknown contact") |
4619 | - } |
4620 | - return "" |
4621 | - } |
4622 | - property string title: { |
4623 | - var result = attachment.contactDisplayName |
4624 | - if (attachment.contactsCount > 1) { |
4625 | - return result + " (+%1)".arg(attachment.contactsCount-1) |
4626 | - } else { |
4627 | - return result |
4628 | - } |
4629 | - } |
4630 | - |
4631 | - height: units.gu(6) |
4632 | - width: textEntry.width |
4633 | - |
4634 | - ContactAvatar { |
4635 | - id: avatar |
4636 | - |
4637 | - anchors { |
4638 | - top: parent.top |
4639 | - bottom: parent.bottom |
4640 | - left: parent.left |
4641 | - } |
4642 | - contactElement: attachment.contactsCount === 1 ? attachment.vcard.contacts[0] : null |
4643 | - fallbackAvatarUrl: attachment.contactsCount === 1 ? "image://theme/contact" : "image://theme/contact-group" |
4644 | - fallbackDisplayName: attachment.contactsCount === 1 ? attachment.contactDisplayName : "" |
4645 | - width: height |
4646 | - } |
4647 | - Label { |
4648 | - id: label |
4649 | - |
4650 | - anchors { |
4651 | - left: avatar.right |
4652 | - leftMargin: units.gu(1) |
4653 | - top: avatar.top |
4654 | - bottom: avatar.bottom |
4655 | - right: parent.right |
4656 | - rightMargin: units.gu(1) |
4657 | - } |
4658 | - |
4659 | - verticalAlignment: Text.AlignVCenter |
4660 | - text: attachment.title |
4661 | - elide: Text.ElideMiddle |
4662 | - color: UbuntuColors.lightAubergine |
4663 | - } |
4664 | - MouseArea { |
4665 | - anchors.fill: parent |
4666 | - onPressAndHold: { |
4667 | - mouse.accept = true |
4668 | - Qt.inputMethod.hide() |
4669 | - activeAttachmentIndex = index |
4670 | - PopupUtils.open(attachmentPopover, parent) |
4671 | - } |
4672 | - } |
4673 | - VCardParser { |
4674 | - id: vcardParser |
4675 | - |
4676 | - vCardUrl: attachment ? Qt.resolvedUrl(attachment.filePath) : "" |
4677 | - } |
4678 | - } |
4679 | - } |
4680 | - |
4681 | - Component { |
4682 | - id: thumbnailUnknown |
4683 | - |
4684 | - UbuntuShape { |
4685 | - property int index |
4686 | - property string filePath |
4687 | - |
4688 | - width: units.gu(8) |
4689 | - height: units.gu(8) |
4690 | - |
4691 | - Icon { |
4692 | - anchors.centerIn: parent |
4693 | - width: units.gu(6) |
4694 | - height: units.gu(6) |
4695 | - name: "attachment" |
4696 | - } |
4697 | - MouseArea { |
4698 | - anchors.fill: parent |
4699 | - onPressAndHold: { |
4700 | - mouse.accept = true |
4701 | - Qt.inputMethod.hide() |
4702 | - activeAttachmentIndex = index |
4703 | - PopupUtils.open(attachmentPopover, parent) |
4704 | - } |
4705 | - } |
4706 | - } |
4707 | - } |
4708 | - |
4709 | - Repeater { |
4710 | - model: attachments |
4711 | - delegate: Loader { |
4712 | - height: units.gu(8) |
4713 | - sourceComponent: { |
4714 | - var contentType = getContentType(filePath) |
4715 | - console.log(contentType) |
4716 | - switch(contentType) { |
4717 | - case ContentType.Contacts: |
4718 | - return thumbnailContact |
4719 | - case ContentType.Pictures: |
4720 | - return thumbnailImage |
4721 | - case ContentType.Unknown: |
4722 | - return thumbnailUnknown |
4723 | - default: |
4724 | - console.log("unknown content Type") |
4725 | - } |
4726 | - } |
4727 | - onStatusChanged: { |
4728 | - if (status == Loader.Ready) { |
4729 | - item.index = index |
4730 | - item.filePath = filePath |
4731 | - } |
4732 | - } |
4733 | - } |
4734 | - } |
4735 | - } |
4736 | - |
4737 | - ListItem.ThinDivider { |
4738 | - id: divider |
4739 | - |
4740 | - anchors { |
4741 | - left: parent.left |
4742 | - right: parent.right |
4743 | - top: attachmentThumbnails.bottom |
4744 | - margins: units.gu(0.5) |
4745 | - } |
4746 | - visible: attachments.count > 0 |
4747 | - } |
4748 | - |
4749 | - TextArea { |
4750 | - id: messageTextArea |
4751 | - objectName: "messageTextArea" |
4752 | - anchors { |
4753 | - top: attachments.count == 0 ? textEntry.top : attachmentThumbnails.bottom |
4754 | - left: parent.left |
4755 | - right: parent.right |
4756 | - } |
4757 | - // this value is to avoid letter being cut off |
4758 | - height: units.gu(4.3) |
4759 | - style: LocalTextAreaStyle {} |
4760 | - autoSize: true |
4761 | - maximumLineCount: attachments.count == 0 ? 8 : 4 |
4762 | - placeholderText: i18n.tr("Write a message...") |
4763 | - focus: textEntry.focus |
4764 | - font.family: "Ubuntu" |
4765 | - font.pixelSize: FontUtils.sizeToPixels("medium") |
4766 | - color: "#5d5d5d" |
4767 | - text: messages.text |
4768 | - } |
4769 | - |
4770 | - /*InverseMouseArea { |
4771 | - anchors.fill: parent |
4772 | - visible: textEntry.activeFocus |
4773 | - onClicked: { |
4774 | - textEntry.focus = false; |
4775 | - } |
4776 | - }*/ |
4777 | - Component.onCompleted: { |
4778 | - // if page is active, it means this is not a bottom edge page |
4779 | - if (messages.active && messages.keyboardFocus && participants.length != 0) { |
4780 | - messageTextArea.forceActiveFocus() |
4781 | - } |
4782 | - } |
4783 | - } |
4784 | - |
4785 | - Icon { |
4786 | - id: sendButton |
4787 | - objectName: "sendButton" |
4788 | - anchors.verticalCenter: textEntry.verticalCenter |
4789 | - anchors.right: parent.right |
4790 | - anchors.rightMargin: units.gu(2) |
4791 | - color: "gray" |
4792 | - source: Qt.resolvedUrl("./assets/send.svg") |
4793 | - width: units.gu(3) |
4794 | - height: units.gu(3) |
4795 | - enabled: { |
4796 | - if (participants.length > 0 || multiRecipient.recipientCount > 0 || multiRecipient.searchString !== "") { |
4797 | - if (textEntry.text != "" || textEntry.inputMethodComposing || attachments.count > 0) { |
4798 | - return true |
4799 | - } |
4800 | - } |
4801 | - return false |
4802 | - } |
4803 | - |
4804 | - MouseArea { |
4805 | - anchors.fill: parent |
4806 | - anchors.margins: units.gu(-2) |
4807 | - onClicked: { |
4808 | - // make sure we flush everything we have prepared in the OSK preedit |
4809 | - Qt.inputMethod.commit(); |
4810 | - if (textEntry.text == "" && attachments.count == 0) { |
4811 | - return |
4812 | - } |
4813 | - // refresh the recipient list |
4814 | - multiRecipient.focus = false |
4815 | - |
4816 | - if (messages.account && messages.accountId == "") { |
4817 | - messages.accountId = messages.account.accountId |
4818 | - messages.head.sections.selectedIndex = Qt.binding(getSelectedIndex) |
4819 | - } |
4820 | - |
4821 | - var newAttachments = [] |
4822 | - for (var i = 0; i < attachments.count; i++) { |
4823 | - var attachment = [] |
4824 | - var item = attachments.get(i) |
4825 | - // we dont include smil files. they will be auto generated |
4826 | - if (item.contentType.toLowerCase() === "application/smil") { |
4827 | - continue |
4828 | - } |
4829 | - attachment.push(item.name) |
4830 | - attachment.push(item.contentType) |
4831 | - attachment.push(item.filePath) |
4832 | - newAttachments.push(attachment) |
4833 | - } |
4834 | - |
4835 | - var recipients = participantIds.length > 0 ? participantIds : |
4836 | - multiRecipient.recipients |
4837 | - // if sendMessage succeeds it means the message was either sent or |
4838 | - // injected into the history service so the user can retry later |
4839 | - if (sendMessage(textEntry.text, recipients, newAttachments)) { |
4840 | - textEntry.text = "" |
4841 | - attachments.clear() |
4842 | - } |
4843 | - if (eventModel.filter == null) { |
4844 | - reloadFilters = !reloadFilters |
4845 | - } |
4846 | - } |
4847 | + ComposeBar { |
4848 | + id: composeBar |
4849 | + anchors { |
4850 | + bottom: isSearching ? parent.bottom : keyboard.top |
4851 | + left: parent.left |
4852 | + right: parent.right |
4853 | + } |
4854 | + |
4855 | + showContents: !selectionMode && !isSearching |
4856 | + maxHeight: messages.height - keyboard.height - screenTop.y |
4857 | + text: messages.text |
4858 | + canSend: participants.length > 0 || multiRecipient.recipientCount > 0 || multiRecipient.searchString !== "" |
4859 | + oskEnabled: messages.oskEnabled |
4860 | + |
4861 | + Component.onCompleted: { |
4862 | + // if page is active, it means this is not a bottom edge page |
4863 | + if (messages.active && messages.keyboardFocus && participants.length != 0) { |
4864 | + forceFocus() |
4865 | + } |
4866 | + } |
4867 | + |
4868 | + onSendRequested: { |
4869 | + // refresh the recipient list |
4870 | + multiRecipient.focus = false |
4871 | + |
4872 | + if (messages.account && messages.accountId == "") { |
4873 | + messages.accountId = messages.account.accountId |
4874 | + messages.head.sections.selectedIndex = Qt.binding(getSelectedIndex) |
4875 | + } |
4876 | + |
4877 | + var newAttachments = [] |
4878 | + var videoSize = 0; |
4879 | + for (var i = 0; i < attachments.count; i++) { |
4880 | + var attachment = [] |
4881 | + var item = attachments.get(i) |
4882 | + // we dont include smil files. they will be auto generated |
4883 | + if (item.contentType.toLowerCase() === "application/smil") { |
4884 | + continue |
4885 | + } |
4886 | + if (startsWith(item.contentType.toLowerCase(),"video/")) { |
4887 | + videoSize += FileOperations.size(item.filePath) |
4888 | + } |
4889 | + attachment.push(item.name) |
4890 | + attachment.push(item.contentType) |
4891 | + attachment.push(item.filePath) |
4892 | + newAttachments.push(attachment) |
4893 | + } |
4894 | + if (videoSize > 307200 && !settings.messagesDontShowFileSizeWarning) { |
4895 | + // FIXME we are guessing here if the handler will try to send it over multimedia account |
4896 | + var isPhone = (account && account.type == AccountEntry.PhoneAccount) |
4897 | + if (isPhone) { |
4898 | + for (var i in telepathyHelper.accounts) { |
4899 | + var tmpAccount = telepathyHelper.accounts[i] |
4900 | + if (tmpAccount.type == AccountEntry.MultimediaAccount) { |
4901 | + // now check if the user is at least known by the account |
4902 | + if (presenceRequest.type != PresenceRequest.PresenceTypeUnknown |
4903 | + && presenceRequest.type != PresenceRequest.PresenceTypeUnset) { |
4904 | + isPhone = false |
4905 | + } |
4906 | + } |
4907 | + } |
4908 | + } |
4909 | + |
4910 | + if (isPhone) { |
4911 | + PopupUtils.open(Qt.createComponent("Dialogs/FileSizeWarningDialog.qml").createObject(messages)) |
4912 | + } |
4913 | + } |
4914 | + |
4915 | + var recipients = participantIds.length > 0 ? participantIds : |
4916 | + multiRecipient.recipients |
4917 | + var properties = {} |
4918 | + if (composeBar.audioAttached) { |
4919 | + properties["x-canonical-tmp-files"] = true |
4920 | + } |
4921 | + |
4922 | + // if sendMessage succeeds it means the message was either sent or |
4923 | + // injected into the history service so the user can retry later |
4924 | + if (sendMessage(text, recipients, newAttachments, properties)) { |
4925 | + composeBar.reset() |
4926 | + } |
4927 | + if (eventModel.filter == null) { |
4928 | + reloadFilters = !reloadFilters |
4929 | } |
4930 | } |
4931 | } |
4932 | |
4933 | === modified file 'src/qml/MessagesHeader.qml' |
4934 | --- src/qml/MessagesHeader.qml 2015-08-04 01:06:06 +0000 |
4935 | +++ src/qml/MessagesHeader.qml 2016-01-14 12:41:18 +0000 |
4936 | @@ -18,7 +18,7 @@ |
4937 | |
4938 | |
4939 | import QtQuick 2.2 |
4940 | -import Ubuntu.Components 1.1 |
4941 | +import Ubuntu.Components 1.3 |
4942 | |
4943 | Item { |
4944 | id: header |
4945 | @@ -26,7 +26,12 @@ |
4946 | property string title: "" |
4947 | property string subtitle: "" |
4948 | |
4949 | - height: units.gu(7) |
4950 | + height: units.gu(8) |
4951 | + |
4952 | + anchors { |
4953 | + top: parent.top |
4954 | + topMargin: units.gu(1) |
4955 | + } |
4956 | |
4957 | Behavior on height { |
4958 | UbuntuNumberAnimation {} |
4959 | @@ -50,7 +55,7 @@ |
4960 | } |
4961 | verticalAlignment: Text.AlignVCenter |
4962 | |
4963 | - font.pixelSize: FontUtils.sizeToPixels("x-large") |
4964 | + font.pixelSize: FontUtils.sizeToPixels("large") |
4965 | elide: Text.ElideRight |
4966 | text: title |
4967 | } |
4968 | |
4969 | === modified file 'src/qml/MessagingContactEditorPage.qml' |
4970 | --- src/qml/MessagingContactEditorPage.qml 2016-01-06 18:29:47 +0000 |
4971 | +++ src/qml/MessagingContactEditorPage.qml 2016-01-14 12:41:18 +0000 |
4972 | @@ -28,6 +28,7 @@ |
4973 | |
4974 | property var contactListPage: null |
4975 | |
4976 | +<<<<<<< TREE |
4977 | leadingActions: Action { |
4978 | objectName: "cancel" |
4979 | |
4980 | @@ -36,9 +37,41 @@ |
4981 | onTriggered: { |
4982 | root.cancel() |
4983 | root.active = false |
4984 | +======= |
4985 | + header: PageHeader { |
4986 | + id: pageHeader |
4987 | + |
4988 | + leadingActionBar { |
4989 | + actions: [ |
4990 | + Action { |
4991 | + objectName: "cancel" |
4992 | + |
4993 | + text: i18n.tr("Cancel") |
4994 | + iconName: "back" |
4995 | + onTriggered: { |
4996 | + root.cancel() |
4997 | + root.active = false |
4998 | + } |
4999 | + } |
5000 | + ] |
FAILED: Continuous integration, rev:449 jenkins. qa.ubuntu. com/job/ messaging- app-ci/ 685/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 4994/console jenkins. qa.ubuntu. com/job/ messaging- app-vivid- i386-ci/ 198/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 4991/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/messaging- app-ci/ 685/rebuild
http://