on xenial help sign in
Added synchronous/confirmation notification support to unity8. Fixes: 1232633, 1373404, 1378618
9c6d98eMirco Müller
qml/Notifications/Notification.qml+72−8
@@ -31,6 +31,7 @@ Item {
31 property alias secondaryIconSource: secondaryIcon.source
31 property alias secondaryIconSource: secondaryIcon.source
32 property alias summary: summaryLabel.text
32 property alias summary: summaryLabel.text
33 property alias body: bodyLabel.text
33 property alias body: bodyLabel.text
34+ property alias value: valueIndicator.value
34 property var actions
35 property var actions
35 property var notificationId
36 property var notificationId
36 property var type
37 property var type
@@ -40,6 +41,7 @@ Item {
40 property bool fullscreen: false
41 property bool fullscreen: false
41 property int maxHeight
42 property int maxHeight
42 property int margins
43 property int margins
44+ readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
43 readonly property color red: "#fc4949"
45 readonly property color red: "#fc4949"
44 readonly property color green: "#3fb24f"
46 readonly property color green: "#3fb24f"
45 readonly property color sdLightGrey: "#eaeaea"
47 readonly property color sdLightGrey: "#eaeaea"
@@ -48,9 +50,9 @@ Item {
48 readonly property real contentSpacing: units.gu(2)
50 readonly property real contentSpacing: units.gu(2)
49
51
50 objectName: "background"
52 objectName: "background"
51− implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height + contentSpacing * 2) : 0
53+ implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
52
54
53− color: type == Notification.SnapDecision ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
55+ color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
54 opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
56 opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
55
57
56 state: {
58 state: {
@@ -78,6 +80,7 @@ Item {
78 Audio {
80 Audio {
79 id: sound
81 id: sound
80 objectName: "sound"
82 objectName: "sound"
83+ audioRole: MediaPlayer.alert
81 source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
84 source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
82 }
85 }
83
86
@@ -88,6 +91,12 @@ Item {
88 }
91 }
89 }
92 }
90
93
94+ onHintsChanged: {
95+ if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
96+ sound.play();
97+ }
98+ }
99+
91 Behavior on height {
100 Behavior on height {
92 id: normalHeightBehavior
101 id: normalHeightBehavior
93
102
@@ -126,6 +135,7 @@ Item {
126 fill: parent
135 fill: parent
127 leftMargin: notification.margins
136 leftMargin: notification.margins
128 rightMargin: notification.margins
137 rightMargin: notification.margins
138+ topMargin: type === Notification.Confirmation ? units.gu(.5) : 0
129 }
139 }
130 color: parent.color
140 color: parent.color
131 opacity: parent.opacity
141 opacity: parent.opacity
@@ -206,10 +216,10 @@ Item {
206 right: parent.right
216 right: parent.right
207 top: parent.top
217 top: parent.top
208 margins: 0
218 margins: 0
209− topMargin: fullscreen ? 0 : units.gu(2)
219+ topMargin: fullscreen ? 0 : type === Notification.Confirmation ? units.gu(1) : units.gu(2)
210 }
220 }
211
221
212− spacing: units.gu(2)
222+ spacing: type === Notification.Confirmation ? units.gu(1) : units.gu(2)
213
223
214 Row {
224 Row {
215 id: topRow
225 id: topRow
@@ -228,7 +238,7 @@ Item {
228 width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
238 width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
229 height: width
239 height: width
230 shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
240 shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
231− visible: iconSource !== undefined && iconSource != ""
241+ visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
232 }
242 }
233
243
234 Column {
244 Column {
@@ -245,8 +255,9 @@ Item {
245 left: parent.left
255 left: parent.left
246 right: parent.right
256 right: parent.right
247 }
257 }
258+ visible: type !== Notification.Confirmation
248 fontSize: "medium"
259 fontSize: "medium"
249− color: type == Notification.SnapDecision ? sdFontColor : Theme.palette.selected.backgroundText
260+ color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
250 elide: Text.ElideRight
261 elide: Text.ElideRight
251 textFormat: Text.PlainText
262 textFormat: Text.PlainText
252 }
263 }
@@ -259,9 +270,9 @@ Item {
259 left: parent.left
270 left: parent.left
260 right: parent.right
271 right: parent.right
261 }
272 }
262− visible: body != ""
273+ visible: body != "" && type !== Notification.Confirmation
263 fontSize: "small"
274 fontSize: "small"
264− color: type == Notification.SnapDecision ? sdFontColor : Theme.palette.selected.backgroundText
275+ color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
265 wrapMode: Text.WordWrap
276 wrapMode: Text.WordWrap
266 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
277 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
267 elide: Text.ElideRight
278 elide: Text.ElideRight
@@ -284,6 +295,59 @@ Item {
284 visible: type == Notification.SnapDecision
295 visible: type == Notification.SnapDecision
285 }
296 }
286
297
298+ ShapedIcon {
299+ id: centeredIcon
300+ objectName: "centeredIcon"
301+ width: units.gu(5)
302+ height: width
303+ shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
304+ fileSource: icon.fileSource
305+ visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
306+ anchors.horizontalCenter: parent.horizontalCenter
307+ }
308+
309+ Label {
310+ id: valueLabel
311+ objectName: "valueLabel"
312+ text: body
313+ anchors.horizontalCenter: parent.horizontalCenter
314+ visible: type === Notification.Confirmation && body !== ""
315+ fontSize: "medium"
316+ color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
317+ wrapMode: Text.WordWrap
318+ maximumLineCount: 1
319+ elide: Text.ElideRight
320+ textFormat: Text.PlainText
321+ }
322+
323+ UbuntuShape {
324+ id: valueIndicator
325+ objectName: "valueIndicator"
326+ visible: type === Notification.Confirmation
327+ property double value
328+
329+ anchors {
330+ left: parent.left
331+ right: parent.right
332+ margins: contentSpacing
333+ }
334+
335+ height: units.gu(1)
336+ color: darkOnBright ? UbuntuColors.darkGrey : UbuntuColors.lightGrey
337+ borderSource: "none"
338+ radius: "small"
339+
340+ UbuntuShape {
341+ id: innerBar
342+ objectName: "innerBar"
343+ width: valueIndicator.width * valueIndicator.value / 100
344+ height: units.gu(1)
345+ color: notification.hints["x-canonical-value-bar-tint"] === "true" ? UbuntuColors.orange : darkOnBright ? UbuntuColors.lightGrey : "white"
346+ borderSource: "none"
347+ radius: "small"
348+ }
349+ }
350+
287 Column {
351 Column {
288 id: dialogColumn
352 id: dialogColumn
289 objectName: "dialogListView"
353 objectName: "dialogListView"
qml/Notifications/Notifications.qml+1
@@ -56,6 +56,7 @@ Flickables.ListView {
56 secondaryIconSource: model.secondaryIcon
56 secondaryIconSource: model.secondaryIcon
57 summary: model.summary
57 summary: model.summary
58 body: model.body
58 body: model.body
59+ value: model.value
59 actions: model.actions
60 actions: model.actions
60 notificationId: model.id
61 notificationId: model.id
61 notification: notificationList.model.getRaw(notificationId)
62 notification: notificationList.model.getRaw(notificationId)
tests/qmltests/Notifications/tst_Notifications.qml+172−41
@@ -53,6 +53,7 @@ Row {
53 summary: "",
53 summary: "",
54 body: "",
54 body: "",
55 icon: "",
55 icon: "",
56+ value: 0,
56 secondaryIcon: "",
57 secondaryIcon: "",
57 actions: []
58 actions: []
58 }
59 }
@@ -125,10 +126,11 @@ Row {
125 function addEphemeralIconSummaryNotification() {
126 function addEphemeralIconSummaryNotification() {
126 var n = {
127 var n = {
127 type: Notification.Ephemeral,
128 type: Notification.Ephemeral,
129+ hints: {"x-canonical-non-shaped-icon": "false"},
128 summary: "Photo upload completed",
130 summary: "Photo upload completed",
129 body: "",
131 body: "",
130− icon: "",
131− secondaryIcon: "../graphics/applicationIcons/facebook.png",
132+ icon: "../graphics/applicationIcons/facebook.png",
133+ secondaryIcon: "",
132 actions: []
134 actions: []
133 }
135 }
134
136
@@ -148,20 +150,53 @@ Row {
148 mockModel.append(n)
150 mockModel.append(n)
149 }
151 }
150
152
153+ function addConfirmationNotification() {
154+ var n = {
155+ type: Notification.Confirmation,
156+ hints: {"x-canonical-non-shaped-icon": "true"},
157+ summary: "Confirmation notification",
158+ body: "",
159+ icon: "image://theme/audio-volume-medium",
160+ secondaryIcon: "",
161+ value: 50,
162+ actions: [],
163+ }
164+
165+ mockModel.append(n)
166+ }
167+
168+ function add2ndConfirmationNotification() {
169+ var n = {
170+ type: Notification.Confirmation,
171+ hints: {"x-canonical-non-shaped-icon": "true",
172+ "x-canonical-value-bar-tint": "true"},
173+ summary: "Confirmation notification",
174+ body: "High Volume",
175+ icon: "image://theme/audio-volume-high",
176+ secondaryIcon: "",
177+ value: 85,
178+ actions: [],
179+ }
180+
181+ mockModel.append(n)
182+ }
183+
151 function clearNotifications() {
184 function clearNotifications() {
152− mockModel.clear()
185+ while(mockModel.count > 1) {
186+ remove1stNotification()
187+ }
153 }
188 }
154
189
155 function remove1stNotification() {
190 function remove1stNotification() {
156− if (mockModel.count > 0)
191+ if (mockModel.count > 1)
157− mockModel.remove(0)
192+ mockModel.remove(1)
158 }
193 }
159
194
160 Rectangle {
195 Rectangle {
161 id: notificationsRect
196 id: notificationsRect
162
197
163 width: units.gu(40)
198 width: units.gu(40)
164− height: units.gu(71)
199+ height: units.gu(115)
165
200
166 MouseArea{
201 MouseArea{
167 id: clickThroughCatcher
202 id: clickThroughCatcher
@@ -183,7 +218,7 @@ Row {
183 id: interactiveControls
218 id: interactiveControls
184
219
185 width: units.gu(30)
220 width: units.gu(30)
186− height: units.gu(81)
221+ height: units.gu(115)
187 color: "grey"
222 color: "grey"
188
223
189 Column {
224 Column {
@@ -227,6 +262,18 @@ Row {
227 onClicked: addInteractiveNotification()
262 onClicked: addInteractiveNotification()
228 }
263 }
229
264
265+ Button {
266+ width: parent.width
267+ text: "add a confirmation"
268+ onClicked: addConfirmationNotification()
269+ }
270+
271+ Button {
272+ width: parent.width
273+ text: "add a 2nd confirmation"
274+ onClicked: add2ndConfirmationNotification()
275+ }
276+
230 Button {
277 Button {
231 width: parent.width
278 width: parent.width
232 text: "remove 1st notification"
279 text: "remove 1st notification"
@@ -264,12 +311,16 @@ Row {
264 summaryVisible: true,
311 summaryVisible: true,
265 bodyVisible: true,
312 bodyVisible: true,
266 iconVisible: true,
313 iconVisible: true,
267− shapedIcon: true,
314+ centeredIconVisible: false,
268− nonShapedIcon: false,
315+ shaped: true,
316+ nonShaped: false,
269 secondaryIconVisible: true,
317 secondaryIconVisible: true,
270 buttonRowVisible: true,
318 buttonRowVisible: true,
271 buttonTinted: true,
319 buttonTinted: true,
272− hasSound: false
320+ hasSound: false,
321+ valueVisible: false,
322+ valueLabelVisible: false,
323+ valueTinted: false
273 },
324 },
274 {
325 {
275 tag: "2-over-1 Snap Decision with button-tint",
326 tag: "2-over-1 Snap Decision with button-tint",
@@ -285,31 +336,37 @@ Row {
285 summaryVisible: true,
336 summaryVisible: true,
286 bodyVisible: true,
337 bodyVisible: true,
287 iconVisible: false,
338 iconVisible: false,
288− shapedIcon: false,
339+ centeredIconVisible: false,
289− nonShapedIcon: false,
340+ shaped: false,
290 secondaryIconVisible: false,
341 secondaryIconVisible: false,
291 buttonRowVisible: false,
342 buttonRowVisible: false,
292 buttonTinted: true,
343 buttonTinted: true,
293− hasSound: false
344+ hasSound: false,
345+ valueVisible: false,
346+ valueLabelVisible: false,
347+ valueTinted: false
294 },
348 },
295 {
349 {
296 tag: "Ephemeral notification - icon-summary layout",
350 tag: "Ephemeral notification - icon-summary layout",
297 type: Notification.Ephemeral,
351 type: Notification.Ephemeral,
298− hints: {"x-canonical-private-affirmative-tint": "false"},
352+ hints: {},
299 summary: "Photo upload completed",
353 summary: "Photo upload completed",
300 body: "",
354 body: "",
301− icon: "",
302− secondaryIcon: "../graphics/applicationIcons/facebook.png",
355+ icon: "../graphics/applicationIcons/facebook.png",
356+ secondaryIcon: "",
303 actions: [],
357 actions: [],
304 summaryVisible: true,
358 summaryVisible: true,
305 bodyVisible: false,
359 bodyVisible: false,
306− iconVisible: false,
360+ iconVisible: true,
307− shapedIcon: false,
361+ centeredIconVisible: false,
308− nonShapedIcon: false,
362+ shaped: true,
309− secondaryIconVisible: true,
363+ secondaryIconVisible: false,
310 buttonRowVisible: false,
364 buttonRowVisible: false,
311 buttonTinted: false,
365 buttonTinted: false,
312− hasSound: false
366+ hasSound: false,
367+ valueVisible: false,
368+ valueLabelVisible: false,
369+ valueTinted: false
313 },
370 },
314 {
371 {
315 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
372 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
@@ -326,12 +383,15 @@ Row {
326 bodyVisible: false,
383 bodyVisible: false,
327 interactiveAreaEnabled: false,
384 interactiveAreaEnabled: false,
328 iconVisible: false,
385 iconVisible: false,
329− shapedIcon: false,
386+ centeredIconVisible: false,
330− nonShapedIcon: false,
387+ shaped: false,
331 secondaryIconVisible: true,
388 secondaryIconVisible: true,
332 buttonRowVisible: false,
389 buttonRowVisible: false,
333 buttonTinted: false,
390 buttonTinted: false,
334− hasSound: false
391+ hasSound: false,
392+ valueVisible: false,
393+ valueLabelVisible: false,
394+ valueTinted: false
335 },
395 },
336 {
396 {
337 tag: "Interactive notification",
397 tag: "Interactive notification",
@@ -346,12 +406,15 @@ Row {
346 summaryVisible: true,
406 summaryVisible: true,
347 bodyVisible: true,
407 bodyVisible: true,
348 iconVisible: true,
408 iconVisible: true,
349− shapedIcon: true,
409+ centeredIconVisible: false,
350− nonShapedIcon: false,
410+ shaped: true,
351 secondaryIconVisible: false,
411 secondaryIconVisible: false,
352 buttonRowVisible: false,
412 buttonRowVisible: false,
353 buttonTinted: false,
413 buttonTinted: false,
354− hasSound: true
414+ hasSound: true,
415+ valueVisible: false,
416+ valueLabelVisible: false,
417+ valueTinted: false
355 },
418 },
356 {
419 {
357 tag: "Snap Decision without secondary icon and no button-tint",
420 tag: "Snap Decision without secondary icon and no button-tint",
@@ -367,12 +430,15 @@ Row {
367 summaryVisible: true,
430 summaryVisible: true,
368 bodyVisible: true,
431 bodyVisible: true,
369 iconVisible: true,
432 iconVisible: true,
370− shapedIcon: true,
433+ centeredIconVisible: false,
371− nonShapedIcon: false,
434+ shaped: true,
372 secondaryIconVisible: false,
435 secondaryIconVisible: false,
373 buttonRowVisible: true,
436 buttonRowVisible: true,
374 buttonTinted: false,
437 buttonTinted: false,
375− hasSound: true
438+ hasSound: true,
439+ valueVisible: false,
440+ valueLabelVisible: false,
441+ valueTinted: false
376 },
442 },
377 {
443 {
378 tag: "Ephemeral notification",
444 tag: "Ephemeral notification",
@@ -387,12 +453,15 @@ Row {
387 summaryVisible: true,
453 summaryVisible: true,
388 bodyVisible: true,
454 bodyVisible: true,
389 iconVisible: true,
455 iconVisible: true,
390− shapedIcon: true,
456+ centeredIconVisible: false,
391− nonShapedIcon: false,
457+ shaped: true,
392 secondaryIconVisible: true,
458 secondaryIconVisible: true,
393 buttonRowVisible: false,
459 buttonRowVisible: false,
394 buttonTinted: false,
460 buttonTinted: false,
395− hasSound: true
461+ hasSound: true,
462+ valueVisible: false,
463+ valueLabelVisible: false,
464+ valueTinted: false
396 },
465 },
397 {
466 {
398 tag: "Ephemeral notification with non-shaped icon",
467 tag: "Ephemeral notification with non-shaped icon",
@@ -401,18 +470,68 @@ Row {
401 "x-canonical-non-shaped-icon": "true"},
470 "x-canonical-non-shaped-icon": "true"},
402 summary: "Contacts",
471 summary: "Contacts",
403 body: "Synchronised contacts-database with cloud-storage.",
472 body: "Synchronised contacts-database with cloud-storage.",
404− icon: "../graphics/applicationIcons/contacts-app.png",
473+ icon: "image://theme/contacts-app",
405 secondaryIcon: "",
474 secondaryIcon: "",
406 actions: [],
475 actions: [],
407 summaryVisible: true,
476 summaryVisible: true,
408 bodyVisible: true,
477 bodyVisible: true,
409 iconVisible: true,
478 iconVisible: true,
410− shapedIcon: false,
479+ centeredIconVisible: false,
411− nonShapedIcon: true,
480+ shaped: false,
481+ secondaryIconVisible: false,
482+ buttonRowVisible: false,
483+ buttonTinted: false,
484+ hasSound: false,
485+ valueVisible: false,
486+ valueLabelVisible: false,
487+ valueTinted: false
488+ },
489+ {
490+ tag: "Confirmation notification with value",
491+ type: Notification.Confirmation,
492+ hints: {"x-canonical-non-shaped-icon": "true"},
493+ summary: "",
494+ body: "",
495+ icon: "image://theme/audio-volume-medium",
496+ secondaryIcon: "",
497+ value: 50,
498+ actions: [],
499+ summaryVisible: false,
500+ bodyVisible: false,
501+ iconVisible: false,
502+ centeredIconVisible: true,
503+ shaped: false,
412 secondaryIconVisible: false,
504 secondaryIconVisible: false,
413 buttonRowVisible: false,
505 buttonRowVisible: false,
414 buttonTinted: false,
506 buttonTinted: false,
415− hasSound: false
507+ hasSound: false,
508+ valueVisible: true,
509+ valueLabelVisible: false,
510+ valueTinted: false
511+ },
512+ {
513+ tag: "Confirmation notification with value, label and tint",
514+ type: Notification.Confirmation,
515+ hints: {"x-canonical-non-shaped-icon": "true",
516+ "x-canonical-value-bar-tint" : "true"},
517+ summary: "",
518+ body: "High Volume",
519+ icon: "image://theme/audio-volume-high",
520+ secondaryIcon: "",
521+ value: 85,
522+ actions: [],
523+ summaryVisible: false,
524+ bodyVisible: false,
525+ iconVisible: false,
526+ centeredIconVisible: true,
527+ shaped: false,
528+ secondaryIconVisible: false,
529+ buttonRowVisible: false,
530+ buttonTinted: false,
531+ hasSound: false,
532+ valueVisible: true,
533+ valueLabelVisible: true,
534+ valueTinted: true
416 }
535 }
417 ]
536 ]
418 }
537 }
@@ -450,17 +569,29 @@ Row {
450 waitForRendering(notification);
569 waitForRendering(notification);
451
570
452 var icon = findChild(notification, "icon")
571 var icon = findChild(notification, "icon")
453− var shapedIcon = findChild(notification, "shapedIcon")
454− var nonShapedIcon = findChild(notification, "nonShapedIcon")
572+ var centeredIcon = findChild(notification, "centeredIcon")
455 var interactiveArea = findChild(notification, "interactiveArea")
573 var interactiveArea = findChild(notification, "interactiveArea")
456 var secondaryIcon = findChild(notification, "secondaryIcon")
574 var secondaryIcon = findChild(notification, "secondaryIcon")
457 var summaryLabel = findChild(notification, "summaryLabel")
575 var summaryLabel = findChild(notification, "summaryLabel")
458 var bodyLabel = findChild(notification, "bodyLabel")
576 var bodyLabel = findChild(notification, "bodyLabel")
459 var buttonRow = findChild(notification, "buttonRow")
577 var buttonRow = findChild(notification, "buttonRow")
578+ var valueIndicator = findChild(notification, "valueIndicator")
579+ var valueLabel = findChild(notification, "valueLabel")
580+ var innerBar = findChild(notification, "innerBar")
460
581
461 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
582 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
583+ if (icon.visible) {
584+ compare(icon.shaped, data.shaped, "shaped-status is incorrect")
585+ }
462− compare(shapedIcon.visible, data.shapedIcon, "shaped-icon visibility is incorrect")
586+ compare(centeredIcon.visible, data.centeredIconVisible, "centered-icon visibility is incorrect")
463− compare(nonShapedIcon.visible, data.nonShapedIcon, "non-shaped-icon visibility is incorrect")
587+ if (centeredIcon.visible) {
588+ compare(centeredIcon.shaped, data.shaped, "shaped-status is incorrect")
589+ }
590+ compare(valueIndicator.visible, data.valueVisible, "value-indicator visibility is incorrect")
591+ if (valueIndicator.visible) {
592+ verify(innerBar.color === data.valueTinted ? UbuntuColors.orange : "white", "value-bar has the wrong color-tint")
593+ }
594+ compare(valueLabel.visible, data.valueLabelVisible, "value-label visibility is incorrect")
464
595
465 // test input does not fall through
596 // test input does not fall through
466 mouseClick(notification, notification.width / 2, notification.height / 2)
597 mouseClick(notification, notification.width / 2, notification.height / 2)