Merge lp:~nik90/ubuntu-clock-app/13-next-alarm-bottomedge into lp:ubuntu-clock-app
- 13-next-alarm-bottomedge
- Merge into trunk
Proposed by
Nekhelesh Ramananthan
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~nik90/ubuntu-clock-app/13-next-alarm-bottomedge | ||||
Merge into: | lp:ubuntu-clock-app | ||||
Diff against target: |
1034 lines (+355/-290) 14 files modified
app/alarm/AlarmDelegate.qml (+7/-31) app/alarm/AlarmList.qml (+2/-5) app/alarm/AlarmPage.qml (+4/-1) app/alarm/AlarmUtils.qml (+98/-15) app/alarm/EditAlarmPage.qml (+1/-2) app/clock/ClockPage.qml (+20/-4) app/clock/MainClock.qml (+0/-39) app/components/Clock.qml (+1/-1) app/components/Utils.js (+0/-25) app/ubuntu-clock-app.qml (+36/-11) app/upstreamcomponents/PageWithBottomEdge.qml (+167/-154) app/worldclock/UserWorldCityList.qml (+0/-2) tests/autopilot/ubuntu_clock_app/emulators.py (+2/-0) tests/unit/tst_alarm.qml (+17/-0) |
||||
To merge this branch: | bzr merge lp:~nik90/ubuntu-clock-app/13-next-alarm-bottomedge | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Clock Developers | Pending | ||
Review via email: mp+237187@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-10-05.
Commit message
Dynamically sets the bottom edge title with the time to the next active alarm.
Description of the change
Dynamically sets the bottom edge title with the time to the next active alarm.
To post a comment you must log in.
- 110. By Nekhelesh Ramananthan
-
Fixed failing qml tests
- 111. By Nekhelesh Ramananthan
-
merged pre-requisite branch changes
- 112. By Nekhelesh Ramananthan
-
Added unit tests for bottom edge title
- 113. By Nekhelesh Ramananthan
-
Split qml unit test into smaller unit test for better debugging when a test fails
- 114. By Nekhelesh Ramananthan
-
Merged pre-requisite branch changes
- 115. By Nekhelesh Ramananthan
-
merged prerequisite branch
- 116. By Nekhelesh Ramananthan
-
updated changelog
- 117. By Nekhelesh Ramananthan
-
Updated if loop according to riccardo's suggestion
- 118. By Nekhelesh Ramananthan
-
merged prerequisite branch
- 119. By Nekhelesh Ramananthan
-
Fixed AP test
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'app/alarm/AlarmDelegate.qml' |
2 | --- app/alarm/AlarmDelegate.qml 2014-09-03 22:51:48 +0000 |
3 | +++ app/alarm/AlarmDelegate.qml 2014-10-05 15:35:22 +0000 |
4 | @@ -23,6 +23,8 @@ |
5 | ListItemWithActions { |
6 | id: root |
7 | |
8 | + property var localTime |
9 | + |
10 | width: parent ? parent.width : 0 |
11 | height: units.gu(6) |
12 | color: "Transparent" |
13 | @@ -39,6 +41,7 @@ |
14 | |
15 | fontSize: "medium" |
16 | text: Qt.formatTime(date) |
17 | + opacity: model.enabled ? 1.0 : 0.8 |
18 | } |
19 | |
20 | Column { |
21 | @@ -70,9 +73,9 @@ |
22 | |
23 | fontSize: "xx-small" |
24 | width: parent.width |
25 | - visible: type === Alarm.Repeating || _internalTimerLoader.sourceComponent != undefined |
26 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere |
27 | - text: alarmUtils.format_day_string(daysOfWeek, type) |
28 | + text: type === Alarm.Repeating ? alarmUtils.format_day_string(daysOfWeek, type) |
29 | + : alarmUtils.get_time_to_next_alarm(model.date - localTime) |
30 | } |
31 | } |
32 | |
33 | @@ -126,35 +129,8 @@ |
34 | if (model.status === Alarm.Ready) { |
35 | alarmStatus.checked = model.enabled; |
36 | |
37 | - if(alarmStatus.checked) { |
38 | - var timeObject = alarmUtils.get_time_to_next_alarm(model.date - new Date()) |
39 | - var alarmETA |
40 | - |
41 | - // TRANSLATORS: the first argument is the number of days, |
42 | - // followed by hour and minute (eg. in 1d 20h 3m) |
43 | - if(timeObject.days) { |
44 | - alarmETA = i18n.tr("in %1d %1h %2m") |
45 | - .arg(timeObject.days) |
46 | - .arg(timeObject.hours) |
47 | - .arg(timeObject.minutes) |
48 | - } |
49 | - |
50 | - // TRANSLATORS: the first argument is the number of |
51 | - // hours followed by the minutes (eg. in 4h 3m) |
52 | - else if (timeObject.hours) { |
53 | - alarmETA = i18n.tr("in %1h %2m") |
54 | - .arg(timeObject.hours) |
55 | - .arg(timeObject.minutes) |
56 | - } |
57 | - |
58 | - // TRANSLATORS: the argument is the number of |
59 | - // minutes to the alarm (eg. in 3m) |
60 | - else { |
61 | - alarmETA = i18n.tr("in %1m") |
62 | - .arg(timeObject.minutes) |
63 | - } |
64 | - |
65 | - alarmSubtitle.text = alarmETA |
66 | + if(alarmStatus.checked && type === Alarm.Repeating) { |
67 | + alarmSubtitle.text = alarmUtils.get_time_to_next_alarm(model.date - localTime) |
68 | _internalTimerLoader.sourceComponent = _internalTimerComponent |
69 | } |
70 | } |
71 | |
72 | === modified file 'app/alarm/AlarmList.qml' |
73 | --- app/alarm/AlarmList.qml 2014-09-25 11:26:43 +0000 |
74 | +++ app/alarm/AlarmList.qml 2014-10-05 15:35:22 +0000 |
75 | @@ -21,13 +21,13 @@ |
76 | import Ubuntu.Components.ListItems 1.0 as ListItem |
77 | import "../components" |
78 | import "../upstreamcomponents" |
79 | -import "../components/Utils.js" as Utils |
80 | |
81 | MultipleSelectionListView { |
82 | id: alarmListView |
83 | objectName: "alarmListView" |
84 | |
85 | property var _currentSwipedItem: null |
86 | + property var localTime |
87 | |
88 | function _updateSwipeState(item) |
89 | { |
90 | @@ -51,15 +51,12 @@ |
91 | clip: true |
92 | anchors.fill: parent |
93 | |
94 | - AlarmUtils { |
95 | - id: alarmUtils |
96 | - } |
97 | - |
98 | listDelegate: AlarmDelegate { |
99 | id: alarmDelegate |
100 | objectName: "alarm" + index |
101 | |
102 | property var removalAnimation |
103 | + localTime: alarmListView.localTime |
104 | |
105 | function remove() { |
106 | removalAnimation.start() |
107 | |
108 | === modified file 'app/alarm/AlarmPage.qml' |
109 | --- app/alarm/AlarmPage.qml 2014-09-26 12:24:11 +0000 |
110 | +++ app/alarm/AlarmPage.qml 2014-10-05 15:35:22 +0000 |
111 | @@ -23,10 +23,12 @@ |
112 | Page { |
113 | id: alarmPage |
114 | |
115 | + property var alarmModel |
116 | + |
117 | title: i18n.tr("Alarms") |
118 | objectName: 'AlarmPage' |
119 | |
120 | - Component.onCompleted: console.log("Alarm Page loaded") |
121 | + Component.onCompleted: console.log("[LOG]: Alarm Page loaded") |
122 | |
123 | flickable: null |
124 | |
125 | @@ -127,6 +129,7 @@ |
126 | id: alarmListView |
127 | listModel: alarmModel |
128 | anchors.fill: parent |
129 | + localTime: clockTime |
130 | } |
131 | |
132 | EmptyState { |
133 | |
134 | === modified file 'app/alarm/AlarmUtils.qml' |
135 | --- app/alarm/AlarmUtils.qml 2014-09-03 22:36:07 +0000 |
136 | +++ app/alarm/AlarmUtils.qml 2014-10-05 15:35:22 +0000 |
137 | @@ -50,21 +50,65 @@ |
138 | } |
139 | } |
140 | |
141 | - function get_time_to_next_alarm ( datetime_offset ) { |
142 | - // increase by a minute, so we could make a nicer time |
143 | - // to the next alarm, otherwise a minute always missing |
144 | - // which makes it look odd |
145 | - datetime_offset += 60000; |
146 | - |
147 | - var days_in_offset = Math.floor( datetime_offset / ( 3600000 * 24 ) ); |
148 | - var hours_in_offset = Math.floor( datetime_offset / 3600000 % 24 ); |
149 | - var minutes_in_offset = Math.floor( datetime_offset / 60000 % 60 ); |
150 | - |
151 | - return { |
152 | - days : days_in_offset, |
153 | - hours : hours_in_offset, |
154 | - minutes : minutes_in_offset |
155 | - } |
156 | + // Function to set the bottom edge title with "Next Active in..." |
157 | + function set_bottom_edge_title(alarmModel, clockTime) { |
158 | + var bottom_edge_title = i18n.tr("No active alarms") |
159 | + |
160 | + /* |
161 | + Check if alarm model received is valid and has saved alarms and only |
162 | + then proceed to find the next active alarm. |
163 | + */ |
164 | + if (alarmModel && alarmModel.count) { |
165 | + var activeAlarmDate = _get_next_active_alarm(alarmModel, clockTime) |
166 | + |
167 | + // Return immediately if there are no active alarms found |
168 | + if (!activeAlarmDate) { |
169 | + return bottom_edge_title |
170 | + } |
171 | + } |
172 | + |
173 | + else { |
174 | + return bottom_edge_title |
175 | + } |
176 | + |
177 | + bottom_edge_title = i18n.tr("Next Alarm %1").arg(get_time_to_next_alarm(activeAlarmDate - clockTime)) |
178 | + return bottom_edge_title |
179 | + } |
180 | + |
181 | + // Function to format the time to next alarm into a string |
182 | + function get_time_to_next_alarm(totalTime) { |
183 | + if(totalTime < 0) { |
184 | + return i18n.tr("Alarm Passed") |
185 | + } |
186 | + |
187 | + var timeObject = _split_time(totalTime); |
188 | + var alarmETA |
189 | + |
190 | + // TRANSLATORS: the first argument is the number of days, |
191 | + // followed by hour and minute (eg. in 1d 20h 3m) |
192 | + if(timeObject.days) { |
193 | + alarmETA = i18n.tr("in %1d %2h %3m") |
194 | + .arg(timeObject.days) |
195 | + .arg(timeObject.hours) |
196 | + .arg(timeObject.minutes) |
197 | + } |
198 | + |
199 | + // TRANSLATORS: the first argument is the number of |
200 | + // hours followed by the minutes (eg. in 4h 3m) |
201 | + else if (timeObject.hours) { |
202 | + alarmETA = i18n.tr("in %1h %2m") |
203 | + .arg(timeObject.hours) |
204 | + .arg(timeObject.minutes) |
205 | + } |
206 | + |
207 | + // TRANSLATORS: the argument is the number of |
208 | + // minutes to the alarm (eg. in 3m) |
209 | + else { |
210 | + alarmETA = i18n.tr("in %1m") |
211 | + .arg(timeObject.minutes) |
212 | + } |
213 | + |
214 | + return alarmETA; |
215 | } |
216 | |
217 | // Function return the alarm dayOfWeek according to the day provided |
218 | @@ -84,6 +128,45 @@ |
219 | INTERNAL FUNCTIONS |
220 | */ |
221 | |
222 | + /* |
223 | + Function to get the next active alarm. This function ignores alarms in the |
224 | + past and also iteratively looks through every alarm since the alarm model |
225 | + does not always list the active alarms in chronological order. |
226 | + */ |
227 | + function _get_next_active_alarm(alarmModel, clockTime) { |
228 | + var activeAlarmDate = undefined |
229 | + |
230 | + for (var i=0; i<alarmModel.count; i++) { |
231 | + var currentAlarm = alarmModel.get(i) |
232 | + if (currentAlarm.enabled && currentAlarm.date > clockTime) { |
233 | + if (activeAlarmDate === undefined || |
234 | + currentAlarm.date < activeAlarmDate) { |
235 | + activeAlarmDate = currentAlarm.date |
236 | + } |
237 | + } |
238 | + } |
239 | + |
240 | + return activeAlarmDate |
241 | + } |
242 | + |
243 | + // Function to split time (in ms) into days, hours and minutes |
244 | + function _split_time(totalTime) { |
245 | + // increase by a minute, so we could make a nicer time |
246 | + // to the next alarm, otherwise a minute always missing |
247 | + // which makes it look odd |
248 | + totalTime += 60000; |
249 | + |
250 | + var days_in_offset = Math.floor(totalTime / ( 3600000 * 24)); |
251 | + var hours_in_offset = Math.floor(totalTime / 3600000 % 24); |
252 | + var minutes_in_offset = Math.floor(totalTime / 60000 % 60); |
253 | + |
254 | + return { |
255 | + days : days_in_offset, |
256 | + hours : hours_in_offset, |
257 | + minutes : minutes_in_offset |
258 | + } |
259 | + } |
260 | + |
261 | // Function to determine the locale's weekdays value |
262 | function _get_weekdays() { |
263 | var weekDays = 0 |
264 | |
265 | === modified file 'app/alarm/EditAlarmPage.qml' |
266 | --- app/alarm/EditAlarmPage.qml 2014-09-25 11:26:43 +0000 |
267 | +++ app/alarm/EditAlarmPage.qml 2014-10-05 15:35:22 +0000 |
268 | @@ -23,7 +23,6 @@ |
269 | import Ubuntu.Components.Pickers 1.0 |
270 | import Ubuntu.Components.ListItems 1.0 as ListItem |
271 | import "../components" |
272 | -import "../components/Utils.js" as Utils |
273 | |
274 | Page { |
275 | id: _addAlarmPage |
276 | @@ -177,7 +176,7 @@ |
277 | |
278 | onErrorChanged: { |
279 | if (error !== Alarm.NoError) { |
280 | - Utils.log(debugMode, "Error saving alarm, code: " + error) |
281 | + console.log("[LOG]: Error saving alarm, code: " + error) |
282 | } |
283 | } |
284 | |
285 | |
286 | === modified file 'app/clock/ClockPage.qml' |
287 | --- app/clock/ClockPage.qml 2014-09-26 11:36:12 +0000 |
288 | +++ app/clock/ClockPage.qml 2014-10-05 15:35:22 +0000 |
289 | @@ -17,12 +17,11 @@ |
290 | */ |
291 | |
292 | import QtQuick 2.3 |
293 | -import U1db 1.0 as U1db |
294 | import Ubuntu.Components 1.1 |
295 | +import "../alarm" |
296 | import "../components" |
297 | import "../upstreamcomponents" |
298 | import "../worldclock" |
299 | -import "../components/Utils.js" as Utils |
300 | |
301 | PageWithBottomEdge { |
302 | id: _clockPage |
303 | @@ -31,15 +30,32 @@ |
304 | // Property to keep track of the clock mode |
305 | property alias isDigital: clock.isDigital |
306 | |
307 | + // Property to keep track of the clock time |
308 | + property alias clockTime: clock.analogTime |
309 | + |
310 | + property var alarmModel |
311 | + |
312 | flickable: null |
313 | - |
314 | - Component.onCompleted: Utils.log(debugMode, "Clock Page loaded") |
315 | + bottomEdgeTitle: alarmUtils.set_bottom_edge_title(alarmModel, clockTime) |
316 | + |
317 | + Component.onCompleted: { |
318 | + console.log("[LOG]: Clock Page loaded") |
319 | + _clockPage.setBottomEdgePage(Qt.resolvedUrl("../alarm/AlarmPage.qml"), { alarmModel: alarmModel }) |
320 | + } |
321 | + |
322 | + AlarmUtils { |
323 | + id: alarmUtils |
324 | + } |
325 | |
326 | Flickable { |
327 | id: _flickable |
328 | |
329 | Component.onCompleted: otherElementsStartUpAnimation.start() |
330 | |
331 | + onFlickStarted: { |
332 | + forceActiveFocus() |
333 | + } |
334 | + |
335 | anchors.fill: parent |
336 | contentWidth: parent.width |
337 | contentHeight: clock.height + date.height + locationRow.height |
338 | |
339 | === modified file 'app/clock/MainClock.qml' |
340 | --- app/clock/MainClock.qml 2014-08-24 12:47:50 +0000 |
341 | +++ app/clock/MainClock.qml 2014-10-05 15:35:22 +0000 |
342 | @@ -17,7 +17,6 @@ |
343 | */ |
344 | |
345 | import QtQuick 2.3 |
346 | -import DateTime 1.0 |
347 | import Ubuntu.Components 1.1 |
348 | import "../components" |
349 | |
350 | @@ -30,44 +29,6 @@ |
351 | |
352 | isMainClock: true |
353 | |
354 | - Connections { |
355 | - target: clockApp |
356 | - onApplicationStateChanged: { |
357 | - localTimeSource.update() |
358 | - } |
359 | - } |
360 | - |
361 | - DateTime { |
362 | - id: localTimeSource |
363 | - updateInterval: isDigital ? 1000 : 10 |
364 | - } |
365 | - |
366 | - /* |
367 | - Create a new Date() object and pass the date, month, year, hour, minute |
368 | - and second received from the DateTime plugin manually to ensure the |
369 | - timezone info is set correctly. |
370 | - |
371 | - Javascript Month is 0-11 while QDateTime month is 1-12. Hence the -1 |
372 | - is required. |
373 | - */ |
374 | - |
375 | - /* |
376 | - FIXME: When the upstream QT bug at |
377 | - https://bugreports.qt-project.org/browse/QTBUG-40275 is fixed it will be |
378 | - possible to receive a datetime object directly instead of using this hack. |
379 | - */ |
380 | - analogTime: new Date |
381 | - ( |
382 | - localTimeSource.localDateString.split(":")[0], |
383 | - localTimeSource.localDateString.split(":")[1]-1, |
384 | - localTimeSource.localDateString.split(":")[2], |
385 | - localTimeSource.localTimeString.split(":")[0], |
386 | - localTimeSource.localTimeString.split(":")[1], |
387 | - localTimeSource.localTimeString.split(":")[2], |
388 | - localTimeSource.localTimeString.split(":")[3] |
389 | - ) |
390 | - time: Qt.formatTime(analogTime) |
391 | - |
392 | isDigital: clockModeDocument.contents.digitalMode ? true : false |
393 | |
394 | Component.onCompleted: { |
395 | |
396 | === modified file 'app/components/Clock.qml' |
397 | --- app/components/Clock.qml 2014-09-06 15:16:43 +0000 |
398 | +++ app/components/Clock.qml 2014-10-05 15:35:22 +0000 |
399 | @@ -18,7 +18,6 @@ |
400 | |
401 | import QtQuick 2.3 |
402 | import Ubuntu.Components 1.1 |
403 | -import "Utils.js" as Utils |
404 | |
405 | /* |
406 | Generic clock component which has a digital and analog mode. A flip animation |
407 | @@ -148,6 +147,7 @@ |
408 | enabled: isMainClock |
409 | anchors.fill: parent |
410 | onClicked: { |
411 | + forceActiveFocus() |
412 | clockFlipAnimation.start() |
413 | } |
414 | } |
415 | |
416 | === removed file 'app/components/Utils.js' |
417 | --- app/components/Utils.js 2014-07-17 12:21:45 +0000 |
418 | +++ app/components/Utils.js 1970-01-01 00:00:00 +0000 |
419 | @@ -1,25 +0,0 @@ |
420 | -/* |
421 | - * Copyright (C) 2014 Canonical Ltd |
422 | - * |
423 | - * This file is part of Ubuntu Clock App |
424 | - * |
425 | - * Ubuntu Clock App is free software: you can redistribute it and/or modify |
426 | - * it under the terms of the GNU General Public License version 3 as |
427 | - * published by the Free Software Foundation. |
428 | - * |
429 | - * Ubuntu Clock App is distributed in the hope that it will be useful, |
430 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
431 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
432 | - * GNU General Public License for more details. |
433 | - * |
434 | - * You should have received a copy of the GNU General Public License |
435 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
436 | - */ |
437 | - |
438 | -.pragma library |
439 | - |
440 | -function log (debugMode, message) { |
441 | - if (debugMode) { |
442 | - console.log("[LOG]" + message) |
443 | - } |
444 | -} |
445 | |
446 | === modified file 'app/ubuntu-clock-app.qml' |
447 | --- app/ubuntu-clock-app.qml 2014-08-26 20:20:37 +0000 |
448 | +++ app/ubuntu-clock-app.qml 2014-10-05 15:35:22 +0000 |
449 | @@ -17,12 +17,11 @@ |
450 | */ |
451 | |
452 | import QtQuick 2.3 |
453 | +import DateTime 1.0 |
454 | import U1db 1.0 as U1db |
455 | import Ubuntu.Components 1.1 |
456 | import "clock" |
457 | -import "alarm" |
458 | import "components" |
459 | -import "components/Utils.js" as Utils |
460 | |
461 | MainView { |
462 | id: clockApp |
463 | @@ -30,9 +29,6 @@ |
464 | // Property to store the state of an application (active or suspended) |
465 | property bool applicationState: Qt.application.active |
466 | |
467 | - // Property to enable/disable the debug mode to show more console output |
468 | - property bool debugMode: true |
469 | - |
470 | // objectName for functional testing purposes (autopilot-qt5) |
471 | objectName: "clock" |
472 | |
473 | @@ -77,7 +73,16 @@ |
474 | |
475 | AlarmModel { |
476 | id: alarmModel |
477 | - Component.onCompleted: Utils.log(debugMode, "Alarm Database loaded") |
478 | + Component.onCompleted: console.log("[LOG]: Alarm Database loaded") |
479 | + } |
480 | + |
481 | + DateTime { |
482 | + id: localTimeSource |
483 | + updateInterval: clockModeDocument.contents.digitalMode ? 1000 : 10 |
484 | + } |
485 | + |
486 | + onApplicationStateChanged: { |
487 | + localTimeSource.update() |
488 | } |
489 | |
490 | PageStack { |
491 | @@ -97,11 +102,31 @@ |
492 | */ |
493 | |
494 | /* |
495 | - #TODO: The bottom edge title should reflect the time to the next |
496 | - alarm. For instance it should read "Next alarm in 9h23m". |
497 | - */ |
498 | - bottomEdgeTitle: i18n.tr("Alarms") |
499 | - bottomEdgePageComponent: AlarmPage {} |
500 | + Create a new Date() object and pass the date, month, year, hour, minute |
501 | + and second received from the DateTime plugin manually to ensure the |
502 | + timezone info is set correctly. |
503 | + |
504 | + Javascript Month is 0-11 while QDateTime month is 1-12. Hence the -1 |
505 | + is required. |
506 | + */ |
507 | + |
508 | + /* |
509 | + FIXME: When the upstream QT bug at |
510 | + https://bugreports.qt-project.org/browse/QTBUG-40275 is fixed it will be |
511 | + possible to receive a datetime object directly instead of using this hack. |
512 | + */ |
513 | + |
514 | + alarmModel: alarmModel |
515 | + clockTime: new Date |
516 | + ( |
517 | + localTimeSource.localDateString.split(":")[0], |
518 | + localTimeSource.localDateString.split(":")[1]-1, |
519 | + localTimeSource.localDateString.split(":")[2], |
520 | + localTimeSource.localTimeString.split(":")[0], |
521 | + localTimeSource.localTimeString.split(":")[1], |
522 | + localTimeSource.localTimeString.split(":")[2], |
523 | + localTimeSource.localTimeString.split(":")[3] |
524 | + ) |
525 | } |
526 | } |
527 | } |
528 | |
529 | === modified file 'app/upstreamcomponents/PageWithBottomEdge.qml' |
530 | --- app/upstreamcomponents/PageWithBottomEdge.qml 2014-09-20 10:47:21 +0000 |
531 | +++ app/upstreamcomponents/PageWithBottomEdge.qml 2014-10-05 15:35:22 +0000 |
532 | @@ -62,7 +62,7 @@ |
533 | |
534 | */ |
535 | |
536 | -import QtQuick 2.3 |
537 | +import QtQuick 2.2 |
538 | import Ubuntu.Components 1.1 |
539 | |
540 | Page { |
541 | @@ -71,7 +71,7 @@ |
542 | property alias bottomEdgePageComponent: edgeLoader.sourceComponent |
543 | property alias bottomEdgePageSource: edgeLoader.source |
544 | property alias bottomEdgeTitle: tipLabel.text |
545 | - property alias bottomEdgeEnabled: bottomEdge.visible |
546 | + property bool bottomEdgeEnabled: true |
547 | property int bottomEdgeExpandThreshold: page.height * 0.2 |
548 | property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded |
549 | property bool reloadBottomEdgePage: true |
550 | @@ -87,6 +87,9 @@ |
551 | signal bottomEdgeReleased() |
552 | signal bottomEdgeDismissed() |
553 | |
554 | + function giveFocus() { |
555 | + tip.forceActiveFocus() |
556 | + } |
557 | |
558 | function showBottomEdgePage(source, properties) |
559 | { |
560 | @@ -142,13 +145,123 @@ |
561 | z: 1 |
562 | } |
563 | |
564 | - Timer { |
565 | - id: hideIndicator |
566 | - |
567 | - interval: 3000 |
568 | - running: true |
569 | - repeat: false |
570 | - onTriggered: tip.hiden = true |
571 | + UbuntuShape { |
572 | + id: tip |
573 | + objectName: "bottomEdgeTip" |
574 | + |
575 | + property bool hiden: (activeFocus === false) || |
576 | + ((bottomEdge.y - units.gu(1)) < tip.y) |
577 | + |
578 | + enabled: mouseArea.enabled |
579 | + visible: page.bottomEdgeEnabled |
580 | + anchors { |
581 | + bottom: parent.bottom |
582 | + horizontalCenter: bottomEdge.horizontalCenter |
583 | + bottomMargin: hiden ? - height + units.gu(1) : -units.gu(1) |
584 | + Behavior on bottomMargin { |
585 | + SequentialAnimation { |
586 | + // wait some msecs in case of the focus change again, to avoid flickering |
587 | + PauseAnimation { |
588 | + duration: 300 |
589 | + } |
590 | + UbuntuNumberAnimation { |
591 | + duration: UbuntuAnimation.SnapDuration |
592 | + } |
593 | + } |
594 | + } |
595 | + } |
596 | + |
597 | + z: 1 |
598 | + width: tipLabel.paintedWidth + units.gu(6) |
599 | + height: bottomEdge.tipHeight + units.gu(1) |
600 | + color: Theme.palette.normal.overlay |
601 | + Label { |
602 | + id: tipLabel |
603 | + |
604 | + anchors { |
605 | + top: parent.top |
606 | + left: parent.left |
607 | + right: parent.right |
608 | + } |
609 | + height: bottomEdge.tipHeight |
610 | + verticalAlignment: Text.AlignVCenter |
611 | + horizontalAlignment: Text.AlignHCenter |
612 | + opacity: tip.hiden ? 0.0 : 1.0 |
613 | + Behavior on opacity { |
614 | + UbuntuNumberAnimation { |
615 | + duration: UbuntuAnimation.SnapDuration |
616 | + } |
617 | + } |
618 | + } |
619 | + } |
620 | + |
621 | + Rectangle { |
622 | + id: shadow |
623 | + |
624 | + anchors { |
625 | + left: parent.left |
626 | + right: parent.right |
627 | + bottom: parent.bottom |
628 | + } |
629 | + height: units.gu(1) |
630 | + z: 1 |
631 | + opacity: 0.0 |
632 | + gradient: Gradient { |
633 | + GradientStop { position: 0.0; color: "transparent" } |
634 | + GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) } |
635 | + } |
636 | + } |
637 | + |
638 | + MouseArea { |
639 | + id: mouseArea |
640 | + |
641 | + property real previousY: -1 |
642 | + property string dragDirection: "None" |
643 | + |
644 | + preventStealing: true |
645 | + drag { |
646 | + axis: Drag.YAxis |
647 | + target: bottomEdge |
648 | + minimumY: bottomEdge.pageStartY |
649 | + maximumY: page.height |
650 | + } |
651 | + enabled: edgeLoader.status == Loader.Ready |
652 | + visible: page.bottomEdgeEnabled |
653 | + |
654 | + anchors { |
655 | + left: parent.left |
656 | + right: parent.right |
657 | + bottom: parent.bottom |
658 | + } |
659 | + height: bottomEdge.tipHeight |
660 | + z: 1 |
661 | + |
662 | + onReleased: { |
663 | + page.bottomEdgeReleased() |
664 | + if ((dragDirection === "BottomToTop") && |
665 | + bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) { |
666 | + bottomEdge.state = "expanded" |
667 | + } else { |
668 | + bottomEdge.state = "collapsed" |
669 | + } |
670 | + previousY = -1 |
671 | + dragDirection = "None" |
672 | + } |
673 | + |
674 | + onPressed: { |
675 | + previousY = mouse.y |
676 | + tip.forceActiveFocus() |
677 | + } |
678 | + |
679 | + onMouseYChanged: { |
680 | + var yOffset = previousY - mouseY |
681 | + // skip if was a small move |
682 | + if (Math.abs(yOffset) <= units.gu(2)) { |
683 | + return |
684 | + } |
685 | + previousY = mouseY |
686 | + dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom" |
687 | + } |
688 | } |
689 | |
690 | FakeHeader { |
691 | @@ -177,7 +290,7 @@ |
692 | |
693 | z: 1 |
694 | color: Theme.palette.normal.background |
695 | - parent: page |
696 | + clip: true |
697 | anchors { |
698 | left: parent.left |
699 | right: parent.right |
700 | @@ -185,99 +298,7 @@ |
701 | height: page.height |
702 | y: height |
703 | |
704 | - UbuntuShape { |
705 | - id: tip |
706 | - objectName: "bottomEdgeTip" |
707 | - |
708 | - property bool hiden: false |
709 | - |
710 | - readonly property double visiblePosition: (page.height - bottomEdge.y) < units.gu(1) ? -bottomEdge.tipHeight + (page.height - bottomEdge.y) : 0 |
711 | - readonly property double invisiblePosition: (page.height - bottomEdge.y) < units.gu(1) ? -units.gu(1) : 0 |
712 | - |
713 | - z: -1 |
714 | - anchors.horizontalCenter: parent.horizontalCenter |
715 | - y: hiden ? invisiblePosition : visiblePosition |
716 | - |
717 | - width: tipLabel.paintedWidth + units.gu(6) |
718 | - height: bottomEdge.tipHeight + units.gu(1) |
719 | - color: Theme.palette.normal.overlay |
720 | - Label { |
721 | - id: tipLabel |
722 | - |
723 | - anchors { |
724 | - top: parent.top |
725 | - left: parent.left |
726 | - right: parent.right |
727 | - } |
728 | - height: bottomEdge.tipHeight |
729 | - verticalAlignment: Text.AlignVCenter |
730 | - horizontalAlignment: Text.AlignHCenter |
731 | - opacity: tip.hiden ? 0.0 : 1.0 |
732 | - Behavior on opacity { |
733 | - UbuntuNumberAnimation { |
734 | - duration: UbuntuAnimation.SnapDuration |
735 | - } |
736 | - } |
737 | - } |
738 | - Behavior on y { |
739 | - UbuntuNumberAnimation { |
740 | - duration: UbuntuAnimation.SnapDuration |
741 | - } |
742 | - } |
743 | - } |
744 | - |
745 | - Rectangle { |
746 | - id: shadow |
747 | - |
748 | - anchors { |
749 | - left: parent.left |
750 | - right: parent.right |
751 | - } |
752 | - height: units.gu(1) |
753 | - y: -height |
754 | - z: -2 |
755 | - opacity: 0.0 |
756 | - gradient: Gradient { |
757 | - GradientStop { position: 0.0; color: "transparent" } |
758 | - GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) } |
759 | - } |
760 | - } |
761 | - |
762 | - MouseArea { |
763 | - id: mouseArea |
764 | - |
765 | - preventStealing: true |
766 | - drag { |
767 | - axis: Drag.YAxis |
768 | - target: bottomEdge |
769 | - minimumY: bottomEdge.pageStartY |
770 | - maximumY: page.height |
771 | - threshold: 100 |
772 | - } |
773 | - |
774 | - anchors { |
775 | - left: parent.left |
776 | - right: parent.right |
777 | - } |
778 | - height: bottomEdge.tipHeight |
779 | - y: -height |
780 | - |
781 | - onReleased: { |
782 | - page.bottomEdgeReleased() |
783 | - if (bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) { |
784 | - bottomEdge.state = "expanded" |
785 | - } else { |
786 | - bottomEdge.state = "collapsed" |
787 | - bottomEdge.y = bottomEdge.height |
788 | - } |
789 | - } |
790 | - |
791 | - onClicked: { |
792 | - tip.hiden = false |
793 | - hideIndicator.restart() |
794 | - } |
795 | - } |
796 | - |
797 | + visible: !page.isCollapsed |
798 | state: "collapsed" |
799 | states: [ |
800 | State { |
801 | @@ -290,14 +311,6 @@ |
802 | target: fakeHeader |
803 | y: -fakeHeader.height |
804 | } |
805 | - PropertyChanges { |
806 | - target: tip |
807 | - opacity: 1.0 |
808 | - } |
809 | - PropertyChanges { |
810 | - target: hideIndicator |
811 | - running: true |
812 | - } |
813 | }, |
814 | State { |
815 | name: "expanded" |
816 | @@ -309,10 +322,6 @@ |
817 | target: fakeHeader |
818 | y: 0 |
819 | } |
820 | - PropertyChanges { |
821 | - target: hideIndicator |
822 | - running: false |
823 | - } |
824 | }, |
825 | State { |
826 | name: "floating" |
827 | @@ -321,14 +330,6 @@ |
828 | target: shadow |
829 | opacity: 1.0 |
830 | } |
831 | - PropertyChanges { |
832 | - target: hideIndicator |
833 | - running: false |
834 | - } |
835 | - PropertyChanges { |
836 | - target: tip |
837 | - hiden: false |
838 | - } |
839 | } |
840 | ] |
841 | |
842 | @@ -336,18 +337,35 @@ |
843 | Transition { |
844 | to: "expanded" |
845 | SequentialAnimation { |
846 | + alwaysRunToEnd: true |
847 | ParallelAnimation { |
848 | - UbuntuNumberAnimation { |
849 | + SmoothedAnimation { |
850 | target: bottomEdge |
851 | property: "y" |
852 | - duration: UbuntuAnimation.SlowDuration |
853 | + duration: UbuntuAnimation.FastDuration |
854 | + easing.type: Easing.Linear |
855 | } |
856 | - UbuntuNumberAnimation { |
857 | + SmoothedAnimation { |
858 | target: fakeHeader |
859 | property: "y" |
860 | - duration: UbuntuAnimation.SlowDuration |
861 | + duration: UbuntuAnimation.FastDuration |
862 | + easing.type: Easing.Linear |
863 | } |
864 | } |
865 | + SmoothedAnimation { |
866 | + target: edgeLoader |
867 | + property: "anchors.topMargin" |
868 | + to: - units.gu(4) |
869 | + duration: UbuntuAnimation.FastDuration |
870 | + easing.type: Easing.Linear |
871 | + } |
872 | + SmoothedAnimation { |
873 | + target: edgeLoader |
874 | + property: "anchors.topMargin" |
875 | + to: 0 |
876 | + duration: UbuntuAnimation.FastDuration |
877 | + easing: UbuntuAnimation.StandardEasing |
878 | + } |
879 | ScriptAction { |
880 | script: page._pushPage() |
881 | } |
882 | @@ -357,6 +375,8 @@ |
883 | from: "expanded" |
884 | to: "collapsed" |
885 | SequentialAnimation { |
886 | + alwaysRunToEnd: true |
887 | + |
888 | ScriptAction { |
889 | script: { |
890 | Qt.inputMethod.hide() |
891 | @@ -366,12 +386,12 @@ |
892 | } |
893 | } |
894 | ParallelAnimation { |
895 | - UbuntuNumberAnimation { |
896 | + SmoothedAnimation { |
897 | target: bottomEdge |
898 | property: "y" |
899 | duration: UbuntuAnimation.SlowDuration |
900 | } |
901 | - UbuntuNumberAnimation { |
902 | + SmoothedAnimation { |
903 | target: fakeHeader |
904 | property: "y" |
905 | duration: UbuntuAnimation.SlowDuration |
906 | @@ -382,14 +402,14 @@ |
907 | // destroy current bottom page |
908 | if (page.reloadBottomEdgePage) { |
909 | edgeLoader.active = false |
910 | + } else { |
911 | + tip.forceActiveFocus() |
912 | } |
913 | |
914 | // notify |
915 | page.bottomEdgeDismissed() |
916 | |
917 | edgeLoader.active = true |
918 | - tip.hiden = false |
919 | - hideIndicator.restart() |
920 | } |
921 | } |
922 | } |
923 | @@ -404,30 +424,23 @@ |
924 | } |
925 | ] |
926 | |
927 | - Item { |
928 | + Loader { |
929 | + id: edgeLoader |
930 | + |
931 | + asynchronous: true |
932 | anchors.fill: parent |
933 | - clip: true |
934 | - |
935 | - Loader { |
936 | - id: edgeLoader |
937 | - |
938 | - z: 1 |
939 | - active: true |
940 | - asynchronous: true |
941 | - anchors.fill: parent |
942 | - |
943 | - //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging |
944 | - Binding { |
945 | - target: edgeLoader.status === Loader.Ready ? edgeLoader : null |
946 | - property: "anchors.topMargin" |
947 | - value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0 |
948 | - when: !page.isReady |
949 | - } |
950 | - |
951 | - onLoaded: { |
952 | - if (page.isReady && edgeLoader.item.active !== true) { |
953 | - page._pushPage() |
954 | - } |
955 | + //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging |
956 | + Binding { |
957 | + target: edgeLoader.status === Loader.Ready ? edgeLoader : null |
958 | + property: "anchors.topMargin" |
959 | + value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0 |
960 | + when: !page.isReady |
961 | + } |
962 | + |
963 | + onLoaded: { |
964 | + tip.forceActiveFocus() |
965 | + if (page.isReady && edgeLoader.item.active !== true) { |
966 | + page._pushPage() |
967 | } |
968 | } |
969 | } |
970 | |
971 | === modified file 'app/worldclock/UserWorldCityList.qml' |
972 | --- app/worldclock/UserWorldCityList.qml 2014-08-24 12:47:50 +0000 |
973 | +++ app/worldclock/UserWorldCityList.qml 2014-10-05 15:35:22 +0000 |
974 | @@ -21,8 +21,6 @@ |
975 | import U1db 1.0 as U1db |
976 | import Ubuntu.Components 1.1 |
977 | |
978 | -import "../components/Utils.js" as Utils |
979 | - |
980 | Column { |
981 | id: worldCityColumn |
982 | |
983 | |
984 | === modified file 'tests/autopilot/ubuntu_clock_app/emulators.py' |
985 | --- tests/autopilot/ubuntu_clock_app/emulators.py 2014-09-12 19:44:18 +0000 |
986 | +++ tests/autopilot/ubuntu_clock_app/emulators.py 2014-10-05 15:35:22 +0000 |
987 | @@ -91,6 +91,8 @@ |
988 | self.bottomEdgePageLoaded.wait_for(True) |
989 | try: |
990 | action_item = self.wait_select_single(objectName='bottomEdgeTip') |
991 | + action_item.hiden.wait_for(False) |
992 | + action_item.enabled.wait_for(True) |
993 | start_x = (action_item.globalRect.x + |
994 | (action_item.globalRect.width * 0.5)) |
995 | start_y = (action_item.globalRect.y + |
996 | |
997 | === modified file 'tests/unit/tst_alarm.qml' |
998 | --- tests/unit/tst_alarm.qml 2014-09-25 22:10:27 +0000 |
999 | +++ tests/unit/tst_alarm.qml 2014-10-05 15:35:22 +0000 |
1000 | @@ -18,6 +18,7 @@ |
1001 | |
1002 | import QtQuick 2.3 |
1003 | import QtTest 1.0 |
1004 | +import DateTime 1.0 |
1005 | import Ubuntu.Test 1.0 |
1006 | import Ubuntu.Components 1.1 |
1007 | import "../../app/alarm" |
1008 | @@ -29,10 +30,26 @@ |
1009 | height: units.gu(70) |
1010 | useDeprecatedToolbar: false |
1011 | |
1012 | + property var clockTime: new Date |
1013 | + ( |
1014 | + localTimeSource.localDateString.split(":")[0], |
1015 | + localTimeSource.localDateString.split(":")[1]-1, |
1016 | + localTimeSource.localDateString.split(":")[2], |
1017 | + localTimeSource.localTimeString.split(":")[0], |
1018 | + localTimeSource.localTimeString.split(":")[1], |
1019 | + localTimeSource.localTimeString.split(":")[2], |
1020 | + localTimeSource.localTimeString.split(":")[3] |
1021 | + ) |
1022 | + |
1023 | AlarmModel { |
1024 | id: alarmModel |
1025 | } |
1026 | |
1027 | + DateTime { |
1028 | + id: localTimeSource |
1029 | + updateInterval: 1000 |
1030 | + } |
1031 | + |
1032 | PageStack { |
1033 | id: pageStack |
1034 | Component.onCompleted: push(alarmPage) |