Unity 8
 All Classes Functions
Notification.qml
1 /*
2  * Copyright (C) 2013 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  */
16 
17 import QtQuick 2.0
18 import QtMultimedia 5.0
19 import Ubuntu.Components 1.1
20 import Unity.Notifications 1.0
21 import QMenuModel 0.1
22 import Utils 0.1
23 
24 import Ubuntu.Components.ListItems 0.1 as ListItem
25 
26 Item {
27  id: notification
28 
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 darkOnBright: panel.indicators.shown || type === Notification.SnapDecision
44  readonly property color red: "#fc4949"
45  readonly property color green: "#3fb24f"
46  readonly property color sdLightGrey: "#eaeaea"
47  readonly property color sdDarkGrey: "#dddddd"
48  readonly property color sdFontColor: "#5d5d5d"
49  readonly property real contentSpacing: units.gu(2)
50 
51  objectName: "background"
52  implicitHeight: type !== Notification.PlaceHolder ? (fullscreen ? maxHeight : outterColumn.height - shapedBack.anchors.topMargin + contentSpacing * 2) : 0
53 
54  color: (type === Notification.Confirmation && notificationList.useModal && !greeter.shown) || darkOnBright ? sdLightGrey : Qt.rgba(0.132, 0.117, 0.109, 0.97)
55  opacity: 1 // FIXME: 1 because of LP: #1354406 workaround, has to be 0 really
56 
57  state: {
58  var result = "";
59 
60  if (type == Notification.SnapDecision) {
61  if (ListView.view.currentIndex == index) {
62  result = "expanded";
63  } else {
64  if (ListView.view.count > 2) {
65  if (ListView.view.currentIndex == -1 && index == 1) {
66  result = "expanded";
67  } else {
68  result = "contracted";
69  }
70  } else {
71  result = "expanded";
72  }
73  }
74  }
75 
76  return result;
77  }
78 
79  Audio {
80  id: sound
81  objectName: "sound"
82  audioRole: MediaPlayer.alert
83  source: hints["suppress-sound"] != "true" && hints["sound-file"] != undefined ? hints["sound-file"] : ""
84  }
85 
86  // FIXME: using onCompleted because of LP: #1354406 workaround, has to be onOpacityChanged really
87  Component.onCompleted: {
88  if (opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
89  sound.play();
90  }
91  }
92 
93  onHintsChanged: {
94  if (type === Notification.Confirmation && opacity == 1.0 && hints["suppress-sound"] != "true" && sound.source != "") {
95  sound.play();
96  }
97  }
98 
99  Behavior on height {
100  id: normalHeightBehavior
101 
102  //enabled: menuItemFactory.progress == 1
103  enabled: true
104  UbuntuNumberAnimation {
105  duration: UbuntuAnimation.SnapDuration
106  }
107  }
108 
109  states:[
110  State {
111  name: "contracted"
112  PropertyChanges {target: notification; height: units.gu(10)}
113  },
114  State {
115  name: "expanded"
116  PropertyChanges {target: notification; height: implicitHeight}
117  }
118  ]
119 
120  clip: fullscreen ? false : true
121 
122  visible: type != Notification.PlaceHolder
123 
124  UbuntuShape {
125  id: shapedBack
126 
127  visible: !fullscreen
128  anchors {
129  fill: parent
130  leftMargin: notification.margins
131  rightMargin: notification.margins
132  topMargin: type === Notification.Confirmation ? units.gu(.5) : 0
133  }
134  color: parent.color
135  opacity: parent.opacity
136  radius: "medium"
137  borderSource: "none"
138  }
139 
140  Rectangle {
141  id: nonShapedBack
142 
143  visible: fullscreen
144  anchors.fill: parent
145  color: parent.color
146  opacity: parent.opacity
147  }
148 
149  Item {
150  id: contents
151  anchors.fill: fullscreen ? nonShapedBack : shapedBack
152 
153  UnityMenuModelPaths {
154  id: paths
155 
156  source: hints["x-canonical-private-menu-model"]
157 
158  busNameHint: "busName"
159  actionsHint: "actions"
160  menuObjectPathHint: "menuPath"
161  }
162 
163  UnityMenuModel {
164  id: unityMenuModel
165 
166  property string lastNameOwner: ""
167 
168  busName: paths.busName
169  actions: paths.actions
170  menuObjectPath: paths.menuObjectPath
171  onNameOwnerChanged: {
172  if (lastNameOwner != "" && nameOwner == "" && notification.notification != undefined) {
173  notification.notification.close()
174  }
175  lastNameOwner = nameOwner
176  }
177  }
178 
179  MouseArea {
180  id: interactiveArea
181 
182  anchors.fill: parent
183  objectName: "interactiveArea"
184  onClicked: {
185  if (notification.type == Notification.Interactive) {
186  notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
187  } else {
188  notificationList.currentIndex = index;
189  }
190  }
191  }
192 
193  Column {
194  id: outterColumn
195 
196  anchors {
197  left: parent.left
198  right: parent.right
199  top: parent.top
200  margins: 0
201  topMargin: fullscreen ? 0 : type === Notification.Confirmation ? units.gu(1) : units.gu(2)
202  }
203 
204  spacing: type === Notification.Confirmation ? units.gu(1) : units.gu(2)
205 
206  Row {
207  id: topRow
208 
209  spacing: contentSpacing
210  anchors {
211  left: parent.left
212  right: parent.right
213  margins: contentSpacing
214  }
215 
216  ShapedIcon {
217  id: icon
218 
219  objectName: "icon"
220  width: type == Notification.Ephemeral && !bodyLabel.visible ? units.gu(3) : units.gu(6)
221  height: width
222  shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
223  visible: iconSource !== undefined && iconSource !== "" && type !== Notification.Confirmation
224  }
225 
226  Column {
227  id: labelColumn
228  width: secondaryIcon.visible ? parent.width - x - units.gu(4.5) : parent.width - x
229 
230  anchors.verticalCenter: (icon.visible && !bodyLabel.visible) ? icon.verticalCenter : undefined
231 
232  Label {
233  id: summaryLabel
234 
235  objectName: "summaryLabel"
236  anchors {
237  left: parent.left
238  right: parent.right
239  }
240  visible: type !== Notification.Confirmation
241  fontSize: "medium"
242  color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
243  elide: Text.ElideRight
244  textFormat: Text.PlainText
245  }
246 
247  Label {
248  id: bodyLabel
249 
250  objectName: "bodyLabel"
251  anchors {
252  left: parent.left
253  right: parent.right
254  }
255  visible: body != "" && type !== Notification.Confirmation
256  fontSize: "small"
257  color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
258  wrapMode: Text.WordWrap
259  maximumLineCount: type == Notification.SnapDecision ? 12 : 2
260  elide: Text.ElideRight
261  textFormat: Text.PlainText
262  }
263  }
264 
265  Image {
266  id: secondaryIcon
267 
268  objectName: "secondaryIcon"
269  width: units.gu(3)
270  height: units.gu(3)
271  visible: status === Image.Ready
272  fillMode: Image.PreserveAspectCrop
273  }
274  }
275 
276  ListItem.ThinDivider {
277  visible: type == Notification.SnapDecision
278  }
279 
280  ShapedIcon {
281  id: centeredIcon
282  objectName: "centeredIcon"
283  width: units.gu(5)
284  height: width
285  shaped: notification.hints["x-canonical-non-shaped-icon"] == "true" ? false : true
286  fileSource: icon.fileSource
287  visible: fileSource !== undefined && fileSource !== "" && type === Notification.Confirmation
288  anchors.horizontalCenter: parent.horizontalCenter
289  }
290 
291  Label {
292  id: valueLabel
293  objectName: "valueLabel"
294  text: body
295  anchors.horizontalCenter: parent.horizontalCenter
296  visible: type === Notification.Confirmation && body !== ""
297  fontSize: "medium"
298  color: darkOnBright ? sdFontColor : Theme.palette.selected.backgroundText
299  wrapMode: Text.WordWrap
300  maximumLineCount: 1
301  elide: Text.ElideRight
302  textFormat: Text.PlainText
303  }
304 
305  UbuntuShape {
306  id: valueIndicator
307  objectName: "valueIndicator"
308  visible: type === Notification.Confirmation
309  property double value
310 
311  anchors {
312  left: parent.left
313  right: parent.right
314  margins: contentSpacing
315  }
316 
317  height: units.gu(1)
318  color: darkOnBright ? UbuntuColors.darkGrey : UbuntuColors.lightGrey
319  borderSource: "none"
320  radius: "small"
321 
322  UbuntuShape {
323  id: innerBar
324  objectName: "innerBar"
325  width: valueIndicator.width * valueIndicator.value / 100
326  height: units.gu(1)
327  color: notification.hints["x-canonical-value-bar-tint"] === "true" ? UbuntuColors.orange : darkOnBright ? UbuntuColors.lightGrey : "white"
328  borderSource: "none"
329  radius: "small"
330  }
331  }
332 
333  Column {
334  id: dialogColumn
335  objectName: "dialogListView"
336  spacing: units.gu(2)
337 
338  visible: count > 0
339 
340  anchors {
341  left: parent.left
342  right: parent.right
343  top: fullscreen ? parent.top : undefined
344  bottom: fullscreen ? parent.bottom : undefined
345  }
346 
347  Repeater {
348  model: unityMenuModel
349 
350  NotificationMenuItemFactory {
351  id: menuItemFactory
352 
353  anchors {
354  left: dialogColumn.left
355  right: dialogColumn.right
356  }
357 
358  menuModel: unityMenuModel
359  menuData: model
360  menuIndex: index
361  maxHeight: notification.maxHeight
362 
363  onLoaded: {
364  notification.fullscreen = Qt.binding(function() { return fullscreen; });
365  }
366  onAccepted: {
367  notification.notification.invokeAction(actionRepeater.itemAt(0).actionId)
368  }
369  }
370  }
371  }
372 
373  Column {
374  id: oneOverTwoCase
375 
376  anchors {
377  left: parent.left
378  right: parent.right
379  margins: contentSpacing
380  }
381 
382  spacing: contentSpacing
383 
384  visible: notification.type === Notification.SnapDecision && oneOverTwoRepeaterTop.count === 3
385 
386  Repeater {
387  id: oneOverTwoRepeaterTop
388 
389  model: notification.actions
390  delegate: Loader {
391  id: oneOverTwoLoaderTop
392 
393  property string actionId: id
394  property string actionLabel: label
395 
396  Component {
397  id: oneOverTwoButtonTop
398 
399  Button {
400  objectName: "notify_oot_button" + index
401  width: oneOverTwoCase.width
402  text: oneOverTwoLoaderTop.actionLabel
403  color: notification.hints["x-canonical-private-affirmative-tint"] == "true" ? green : sdDarkGrey
404  onClicked: notification.notification.invokeAction(oneOverTwoLoaderTop.actionId)
405  }
406  }
407  sourceComponent: index == 0 ? oneOverTwoButtonTop : undefined
408  }
409  }
410 
411  Row {
412  spacing: contentSpacing
413 
414  Repeater {
415  id: oneOverTwoRepeaterBottom
416 
417  model: notification.actions
418  delegate: Loader {
419  id: oneOverTwoLoaderBottom
420 
421  property string actionId: id
422  property string actionLabel: label
423 
424  Component {
425  id: oneOverTwoButtonBottom
426 
427  Button {
428  objectName: "notify_oot_button" + index
429  width: oneOverTwoCase.width / 2 - spacing * 2
430  text: oneOverTwoLoaderBottom.actionLabel
431  color: index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true" ? red : sdDarkGrey
432  onClicked: notification.notification.invokeAction(oneOverTwoLoaderBottom.actionId)
433  }
434  }
435  sourceComponent: (index == 1 || index == 2) ? oneOverTwoButtonBottom : undefined
436  }
437  }
438  }
439  }
440 
441  Row {
442  id: buttonRow
443 
444  objectName: "buttonRow"
445  anchors {
446  left: parent.left
447  right: parent.right
448  margins: contentSpacing
449  }
450  visible: notification.type == Notification.SnapDecision && actionRepeater.count > 0 && !oneOverTwoCase.visible
451  spacing: units.gu(2)
452  layoutDirection: Qt.RightToLeft
453 
454  Loader {
455  id: notifySwipeButtonLoader
456  active: notification.hints["x-canonical-snap-decisions-swipe"] === "true"
457 
458  sourceComponent: SwipeToAct {
459  objectName: "notify_swipe_button"
460  width: buttonRow.width
461  leftIconName: "call-end"
462  rightIconName: "call-start"
463  onRightTriggered: {
464  notification.notification.invokeAction(notification.actions.data(0, ActionModel.RoleActionId))
465  }
466 
467  onLeftTriggered: {
468  notification.notification.invokeAction(notification.actions.data(1, ActionModel.RoleActionId))
469  }
470  }
471  }
472 
473  Repeater {
474  id: actionRepeater
475  model: notification.actions
476  delegate: Loader {
477  id: loader
478 
479  property string actionId: id
480  property string actionLabel: label
481  active: !notifySwipeButtonLoader.active
482 
483  Component {
484  id: actionButton
485 
486  Button {
487  objectName: "notify_button" + index
488  width: buttonRow.width / 2 - spacing * 2
489  text: loader.actionLabel
490  color: {
491  var result = sdDarkGrey;
492  if (index == 0 && notification.hints["x-canonical-private-affirmative-tint"] == "true") {
493  result = green;
494  }
495  if (index == 1 && notification.hints["x-canonical-private-rejection-tint"] == "true") {
496  result = red;
497  }
498  return result;
499  }
500  onClicked: notification.notification.invokeAction(loader.actionId)
501  }
502  }
503  sourceComponent: (index == 0 || index == 1) ? actionButton : undefined
504  }
505  }
506  }
507 
508  OptionToggle {
509  id: optionToggle
510  objectName: "notify_button2"
511  width: parent.width
512  anchors {
513  left: parent.left
514  right: parent.right
515  margins: contentSpacing
516  }
517 
518  visible: notification.type == Notification.SnapDecision && actionRepeater.count > 3 && !oneOverTwoCase.visible
519  model: notification.actions
520  expanded: false
521  startIndex: 2
522  onTriggered: {
523  notification.notification.invokeAction(id)
524  }
525  }
526  }
527  }
528 }