on 555cc15 help sign in
unity8/qml/Notifications/Notification.qml
573 lines 21 kB history raw
1/*
2 * Copyright (C) 2013, 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17import QtQuick 2.0
18import QtMultimedia 5.0
19import Powerd 0.1
20import Ubuntu.Components 1.2
21import Unity.Notifications 1.0
22import QMenuModel 0.1
23import Utils 0.1
24import Ubuntu.Components.ListItems 0.1 as ListItem
26Item {
27 id: notification
29 property alias iconSource: icon.fileSource
30 property alias secondaryIconSource: secondaryIcon.source
31 property alias summary: summaryLabel.text
32 property alias body: bodyLabel.text
33 property alias value: valueIndicator.value
34 property var actions
35 property var notificationId
36 property var type
37 property var hints
38 property var notification
39 property color color
40 property bool fullscreen: false
41 property int maxHeight
42 property int margins
43 readonly property bool draggable: (type === Notification.SnapDecision && state === "contracted") || type === Notification.Interactive || type === Notification.Ephemeral
44 readonly property bool darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
45 readonly property color red: "#fc4949"
46 readonly property color green: "#3fb24f"
47 readonly property color sdLightGrey: "#eaeaea"
48 readonly property color sdDarkGrey: "#dddddd"
49 readonly property color sdFontColor: "#5d5d5d"
50 readonly property real contentSpacing: units.gu(2)
51 readonly property bool canBeClosed: type === Notification.Ephemeral
52 property bool hasMouse
53 property url background: ""
55 objectName: "background"
56 implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
58 color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
59 opacity: 1 - (x / notification.width) // FIXME: non-zero initially because of LP: #1354406 workaround, we want this to start at 0 upon creation eventually
61 state: {
62 var result = "";
64 if (type == Notification.SnapDecision) {
65 if (ListView.view.currentIndex == index) {
66 result = "expanded";
67 } else {
68 if (ListView.view.count > 2) {
69 if (ListView.view.currentIndex == -1 && index == 1) {
70 result = "expanded";
71 } else {
72 result = "contracted";
73 }
74 } else {
75 result = "expanded";
76 }
77 }
78 }
80 return result;
81 }
83 Audio {
84 id: sound
85 objectName: "sound"
86 audioRole: MediaPlayer.alert
87 source: hints["suppress-sound"] !== "true" && hints["sound-file"] !== undefined ? hints["sound-file"] : ""
88 }
90 Component.onCompleted: {
91 // Turn on screen as needed (Powerd.Notification means the screen
92 // stays on for a shorter amount of time)
93 if (type == Notification.SnapDecision) {
94 Powerd.setStatus(Powerd.On, Powerd.SnapDecision);
95 } else if (type != Notification.Confirmation) {
96 Powerd.setStatus(Powerd.On, Powerd.Notification);
97 }
99 // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
100 if (opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
101 sound.play();
102 }
103 }
105 Behavior on x {
106 id: normalXBehavior
108 enabled: draggable
109 UbuntuNumberAnimation {
110 duration: UbuntuAnimation.FastDuration
111 easing.type: Easing.OutBounce
112 }
113 }
115 onHintsChanged: {
116 if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] !== "true" && sound.source !== "") {
117 sound.play();
118 }
119 }
121 Behavior on height {
122 id: normalHeightBehavior
124 //enabled: menuItemFactory.progress == 1
125 enabled: true
126 UbuntuNumberAnimation {
127 duration: UbuntuAnimation.SnapDuration
128 }
129 }
131 states:[
132 State {
133 name: "contracted"
134 PropertyChanges {target: notification; height: units.gu(10)}
135 },
136 State {
137 name: "expanded"
138 PropertyChanges {target: notification; height: implicitHeight}
139 }
140 ]
142 clip: fullscreen ? false : true
144 visible: type != Notification.PlaceHolder
146 UbuntuShape {
147 id: shapedBack
149 visible: !fullscreen
150 anchors {
151 fill: parent
152 leftMargin: notification.margins
153 rightMargin: notification.margins
154 topMargin: type === Notification.Confirmation ? units.gu(.5) : 0
155 }
156 color: parent.color
157 opacity: parent.opacity
158 radius: "medium"
159 borderSource: "none"
160 }
162 Rectangle {
163 id: nonShapedBack
165 visible: fullscreen
166 anchors.fill: parent
167 color: parent.color
168 opacity: parent.opacity
169 }
171 onXChanged: {
172 if (draggable && notification.x > 0.75 * notification.width) {
173 notification.notification.close()
174 }
175 }
177 Item {
178 id: contents
179 anchors.fill: fullscreen ? nonShapedBack : shapedBack
181 UnityMenuModelPaths {
182 id: paths
184 source: hints["x-canonical-private-menu-model"]
186 busNameHint: "busName"
187 actionsHint: "actions"
188 menuObjectPathHint: "menuPath"
189 }
191 UnityMenuModel {
192 id: unityMenuModel
194 property string lastNameOwner: ""
196 busName: paths.busName
197 actions: paths.actions
198 menuObjectPath: paths.menuObjectPath
199 onNameOwnerChanged: {
200 if (lastNameOwner !== "" && nameOwner === "" && notification.notification !== undefined) {
201 notification.notification.close()
202 }
203 lastNameOwner = nameOwner
204 }
205 }
207 MouseArea {
208 id: interactiveArea
210 anchors.fill: parent
211 objectName: "interactiveArea"
213 drag.target: draggable ? notification : undefined
214 drag.axis: Drag.XAxis
215 drag.minimumX: 0
216 drag.maximumX: notification.width
218 onClicked: {
219 if (notification.type == Notification.Interactive) {
220 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
221 } else if (hasMouse && canBeClosed) {
222 notification.notification.close()
223 } else {
224 notificationList.currentIndex = index;
225 }
226 }
227 onReleased: {
228 if (notification.x < notification.width / 2) {
229 notification.x = 0
230 } else {
231 notification.x = notification.width
232 }
233 }
234 }
236 Column {
237 id: outterColumn
239 anchors {
240 left: parent.left
241 right: parent.right
242 top: parent.top
243 margins: 0
244 topMargin: fullscreen ? 0 : type === Notification.Confirmation ? units.gu(1) : units.gu(2)
245 }
247 spacing: type === Notification.Confirmation ? units.gu(1) : units.gu(2)
249 Row {
250 id: topRow
252 spacing: contentSpacing
253 anchors {
254 left: parent.left
255 right: parent.right
256 margins: contentSpacing
257 }
259 ShapedIcon {
260 id: icon
262 objectName: "icon"
263 width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
264 height: width
265 shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
266 visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
267 }
269 Column {
270 id: labelColumn
271 width: secondaryIcon.visible ? parent.width - x - units.gu(4.5) : parent.width - x
273 anchors.verticalCenter: (icon.visible && !bodyLabel.visible) ? icon.verticalCenter : undefined
275 Label {
276 id: summaryLabel
278 objectName: "summaryLabel"
279 anchors {
280 left: parent.left
281 right: parent.right
282 }
283 visible: type !== Notification.Confirmation
284 fontSize: "medium"
285 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
286 elide: Text.ElideRight
287 textFormat: Text.PlainText
288 }
290 Label {
291 id: bodyLabel
293 objectName: "bodyLabel"
294 anchors {
295 left: parent.left
296 right: parent.right
297 }
298 visible: body != "" && type !== Notification.Confirmation
299 fontSize: "small"
300 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
301 wrapMode: Text.WordWrap
302 maximumLineCount: type == Notification.SnapDecision ? 12 : 2
303 elide: Text.ElideRight
304 textFormat: Text.PlainText
305 }
306 }
308 Image {
309 id: secondaryIcon
311 objectName: "secondaryIcon"
312 width: units.gu(3)
313 height: units.gu(3)
314 visible: status === Image.Ready
315 fillMode: Image.PreserveAspectCrop
316 }
317 }
319 ListItem.ThinDivider {
320 visible: type == Notification.SnapDecision
321 }
323 ShapedIcon {
324 id: centeredIcon
325 objectName: "centeredIcon"
326 width: units.gu(5)
327 height: width
328 shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
329 fileSource: icon.fileSource
330 visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
331 anchors.horizontalCenter: parent.horizontalCenter
332 }
334 Label {
335 id: valueLabel
336 objectName: "valueLabel"
337 text: body
338 anchors.horizontalCenter: parent.horizontalCenter
339 visible: type === Notification.Confirmation && body !== ""
340 fontSize: "medium"
341 color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
342 wrapMode: Text.WordWrap
343 maximumLineCount: 1
344 elide: Text.ElideRight
345 textFormat: Text.PlainText
346 }
348 UbuntuShape {
349 id: valueIndicator
350 objectName: "valueIndicator"
351 visible: type === Notification.Confirmation
352 property double value
354 anchors {
355 left: parent.left
356 right: parent.right
357 margins: contentSpacing
358 }
360 height: units.gu(1)
361 color: darkOnBright ? UbuntuColors.darkGrey : UbuntuColors.lightGrey
362 borderSource: "none"
363 radius: "small"
365 UbuntuShape {
366 id: innerBar
367 objectName: "innerBar"
368 width: valueIndicator.width * valueIndicator.value / 100
369 height: units.gu(1)
370 color: notification.hints["x-canonical-value-bar-tint"] === "true" ? UbuntuColors.orange : darkOnBright ? UbuntuColors.lightGrey : "white"
371 borderSource: "none"
372 radius: "small"
373 }
374 }
376 Column {
377 id: dialogColumn
378 objectName: "dialogListView"
379 spacing: units.gu(2)
381 visible: count > 0
383 anchors {
384 left: parent.left
385 right: parent.right
386 top: fullscreen ? parent.top : undefined
387 bottom: fullscreen ? parent.bottom : undefined
388 }
390 Repeater {
391 model: unityMenuModel
393 NotificationMenuItemFactory {
394 id: menuItemFactory
396 anchors {
397 left: dialogColumn.left
398 right: dialogColumn.right
399 }
401 menuModel: unityMenuModel
402 menuData: model
403 menuIndex: index
404 maxHeight: notification.maxHeight
405 background: notification.background
407 onLoaded: {
408 notification.fullscreen = Qt.binding(function() { return fullscreen; });
409 }
410 onAccepted: {
411 notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
412 }
413 }
414 }
415 }
417 Column {
418 id: oneOverTwoCase
420 anchors {
421 left: parent.left
422 right: parent.right
423 margins: contentSpacing
424 }
426 spacing: contentSpacing
428 visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
430 Repeater {
431 id: oneOverTwoRepeaterTop
433 model: notification.actions
434 delegate: Loader {
435 id: oneOverTwoLoaderTop
437 property string actionId: id
438 property string actionLabel: label
440 Component {
441 id: oneOverTwoButtonTop
443 Button {
444 objectName: "notify_oot_button" + index
445 width: oneOverTwoCase.width
446 text: oneOverTwoLoaderTop.actionLabel
447 color: notification.hints["x-canonical-private-affirmative-tint"] == "true" ? green : sdDarkGrey
448 onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
449 }
450 }
451 sourceComponent: index == 0 ? oneOverTwoButtonTop : undefined
452 }
453 }
455 Row {
456 spacing: contentSpacing
458 Repeater {
459 id: oneOverTwoRepeaterBottom
461 model: notification.actions
462 delegate: Loader {
463 id: oneOverTwoLoaderBottom
465 property string actionId: id
466 property string actionLabel: label
468 Component {
469 id: oneOverTwoButtonBottom
471 Button {
472 objectName: "notify_oot_button" + index
473 width: oneOverTwoCase.width / 2 - spacing * 2
474 text: oneOverTwoLoaderBottom.actionLabel
475 color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true" ? red : sdDarkGrey
476 onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
477 }
478 }
479 sourceComponent: (index == 1 || index == 2) ? oneOverTwoButtonBottom : undefined
480 }
481 }
482 }
483 }
485 Row {
486 id: buttonRow
488 objectName: "buttonRow"
489 anchors {
490 left: parent.left
491 right: parent.right
492 margins: contentSpacing
493 }
494 visible: notification.type === Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
495 spacing: contentSpacing
496 layoutDirection: Qt.RightToLeft
498 Loader {
499 id: notifySwipeButtonLoader
500 active: notification.hints["x-canonical-snap-decisions-swipe"] === "true"
502 sourceComponent: SwipeToAct {
503 objectName: "notify_swipe_button"
504 width: buttonRow.width
505 leftIconName: "call-end"
506 rightIconName: "call-start"
507 clickToAct: notification.hasMouse
508 onRightTriggered: {
509 notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
510 }
512 onLeftTriggered: {
513 notification.notification.invokeAction(notification.actions.data(1, ActionModel.RoleActionId))
514 }
515 }
516 }
518 Repeater {
519 id: actionRepeater
520 model: notification.actions
521 delegate: Loader {
522 id: loader
524 property string actionId: id
525 property string actionLabel: label
526 active: !notifySwipeButtonLoader.active
528 Component {
529 id: actionButton
531 Button {
532 objectName: "notify_button" + index
533 width: buttonRow.width / 2 - spacing * 2
534 text: loader.actionLabel
535 color: {
536 var result = sdDarkGrey;
537 if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] == "true") {
538 result = green;
539 }
540 if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true") {
541 result = red;
542 }
543 return result;
544 }
545 onClicked: notification.notification.invokeAction(loader.actionId)
546 }
547 }
548 sourceComponent: (index == 0 || index == 1) ? actionButton : undefined
549 }
550 }
551 }
553 OptionToggle {
554 id: optionToggle
555 objectName: "notify_button2"
556 width: parent.width
557 anchors {
558 left: parent.left
559 right: parent.right
560 margins: contentSpacing
561 }
563 visible: notification.type == Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible
564 model: notification.actions
565 expanded: false
566 startIndex: 2
567 onTriggered: {
568 notification.notification.invokeAction(id)
569 }
570 }
571 }
572 }