Merge lp:~macslow/unity/phablet-notification-renderer into lp:unity/phablet

Proposed by Mirco Müller
Status: Merged
Approved by: Mirco Müller
Approved revision: no longer in the source branch.
Merged at revision: 675
Proposed branch: lp:~macslow/unity/phablet-notification-renderer
Merge into: lp:unity/phablet
Diff against target: 645 lines (+591/-0)
7 files modified
CMakeLists.txt (+1/-0)
Notifications/Notification.qml (+153/-0)
Notifications/Notifications.qml (+77/-0)
Notifications/timings.js (+23/-0)
debian/qml-phone-shell.install (+1/-0)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Notifications/tst_Notifications.qml (+335/-0)
To merge this branch: bzr merge lp:~macslow/unity/phablet-notification-renderer
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michał Sawicz Approve
Michael Zanetti (community) Approve
Review via email: mp+155512@code.launchpad.net

Commit message

First basic NotificationRenderer (Frontend) for NotifyOSD NG. Ephemeral and 2-button snap-decision layout are support for a start. Using mocked up notification-data for the moment.

Description of the change

First basic NotificationRenderer (Frontend) for NotifyOSD NG. Ephemeral and 2-button snap-decision layout are support for a start. Using mocked up notification-data for the moment.

To post a comment you must log in.
Revision history for this message
Michał Sawicz (saviq) wrote :

* the change to .bzrignore seems unrelated, please don't submit it
* I'd rename Bubble to Notification
* I don't see a reason to separate Bubble.qml into a separate dir - unless really difficult, there should only be a single Notification component that lays itself out according to the properties set
* "property bool snapDecision: actions != null" - this should depend on the type of the notification, not the presence of actions
* the whole bubble is 0.85 opaque, is that per-design? shouldn't just the background be transparent and the text fully opaque?
* please separate the components with newlines
* "visible: secondaryIconSource ? true : false" you can use just "source" here, secondaryIconSource is an alias anyway
* "left: parent.left; leftMargin: units.gu(8)" you should anchor to icon.right instead
* "fillMode: Image.PreserveAspectCrop" for secondaryIcon seems unnecessary, the icon should always be 2x2 GUs
* "leftMargin: secondaryIcon.visible ? units.gu(11) : units.gu(8)" anchor to icon or secondaryIcon instead
* "width: secondaryIcon.visible ? units.gu (25) : units.gu (28)" anchor to parent.right instead
* "font.family: "Ubuntu"" is default
* "opacity: 1.0" is default, and does _not_ mean that the text itself will be fully opaque, because the parent item is not opaque [1]
* "maximumLineCount: 1" is unnecessary when wrapping is disabled
* the above applies to bodyLabel, too, except that bodyLabel should anchor to summaryLabel's left and bottom
* you should elide the body, too, in case it overflows
* "width: units.gu (17.5)" is arbitrary, calculate the width based on parent.width
* "text: actions.get(1).action_label" you should almost never need Model.get() - use Views or Positioners with Repeaters [2] [3]
* the Loader is unnecessary, if you use a Repeater, it will not create anything if the model is empty
* use add/move/delete transitions

* in this case I'd rather see the layout to use Positioners [2] extensively for better readability and flexibility, like so:

Column {
    Row {
        UbuntuShape { id: icon }
        Image { id: secondaryIcon}
        Column {
            Label { id: summary }
            Label { id: body }
        }
    }
    Flow {
        Repeater { id: buttons }
    }
}

[1] http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick2-item.html#opacity-prop
[2] http://qt-project.org/doc/qt-5.0/qtquick/qtquick-qmltypereference.html#positioning
[3] http://qt-project.org/doc/qt-5.0/qtquick/qtquick-qmltypereference.html#model-view-types-and-data-storage-and-access

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

The Columns might be overkill, but you need to wrap the Labels in an Item anyway for the Row to not try and position them next to each other.

Revision history for this message
Mirco Müller (macslow) wrote :

Ah, ok. I'll fix all those issues.

Revision history for this message
kevin gunn (kgunn72) wrote :

wrt >> I'd rename Bubble to Notification
I worry a little that we are overloading "notification"
e.g. if back end is also called notification it might get confusing
am i being a nut? ultimately i'll defer to Saviq/Mirco

Revision history for this message
Michał Sawicz (saviq) wrote :

Sure, that's a valid concern. QML Notification component would end up as a QQmlNotification in C++ world, and anyway namespacing is helping here - it's going to be Unity::Notifications::Notification in C++ world. And even in QML it's recommended to used named imports (import Unity.Notifications 0.1 as Notifications) and then the components exposed from that plugin are available as Notifications.Notification, for example.

What's more, we'll never use (title case) Notification as exposed from the QML Unity.Notifications plugin, at least not in the shell. That object will only be created C++ side.

So, all in all I don't foresee naming conflicts, but to avoid confusion, I could agree to a different name ;). I'd like to avoid "Bubble" nevertheless ;D

Revision history for this message
Mirco Müller (macslow) wrote :

All mentioned suggestions were addressed, with two variations:

* I left the explicit sizes for icon and secondary-icon in place as a "safty-net", for those cases when rogue applications pass in too small or too large icons, thus the layout of the notification does not break away from the design-guideline.

* With the way anchors are used in summayLabel and bodyLabel now, there's no danger of them being accidentally placed next to each other.

Good to merge with trunk?

Revision history for this message
Michał Sawicz (saviq) wrote :

Yeah, explicit sizes of icons / assets is correct, of course.

Revision history for this message
Michał Sawicz (saviq) wrote :

* please order the properties on the component in a more structured way (at least public properties first, and secondaryIcon source just after iconSource)
* components should not have their dimensions predefined, unless they depend (as in this case the height) on their child objects
    width: parent.width
    height: childrenRect.height + units.gu (1)
* don't expose private properties on root objects of components - in this case I don't see the need for that separate property, you can simply use the condition itself
    property bool snapDecision: type == "Notifications.Type.SnapDecision"
* components should not define their overall animations - and you're not changing the opacity of it anyway - you're going to that in the transitions
    Behavior on opacity {NumberAnimation{}}
* I would prefer using a Row for the icons and summary+body, all the margin: units.gu(1) make it hard to read, and it would handle the invisible secondaryIcon automatically
* explicitly check for source !== undefined && source != "", you could also check for status == Image.Ready [1], but that might result in some movement while the image is being loaded
    visible: source
* set sourceSize [2] on Images that come from outside to reduce memory
* this is not correct, use bodyLabel.y + bodyLabel.height - height of items does not include anchor margins - and if you use a Row above, you will just anchor to the row's bottom anyway
  top: (icon.height >= summaryLabel.height + bodyLabel.height) ? icon.bottom : bodyLabel.bottom
* why this??
    parent: notification
* set visibility instead
    topMargin: snapDecision ? units.gu (1) : 0
* there's a binding loop - should go away when you move to using a Row, too
    Notifications.qml:48:19: QML Notification: Binding loop detected for property "height"
* don't use space between function name and parenthesis [units.gu(1), not units.gu (1)]
* use camelCase instead of underscores (action_label)
* no need to prefix action's values with action_
* Notifications should not have its own queue or the displayNotification method, it should be a ListView{}, not a Showable{ListView{}}
* Notifications - see above about predefined dimensions
    anchors.fill: parent
* don't add Notifications to Shell, yet, since there's no model to supply to it
* the tests are not really tests - there's no way they would fail, is there?

[1] http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick2-image.html#status-prop
[2] http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick2-image.html#sourceSize-prop

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

=========

48 + color: Qt.rgba(0, 0, 0, 0.85)
with that, shouldn't you be able to just drop the Item?

=========

54 + top: parent.top
unnecessary

=========

56 + height: childrenRect.height + contentColumn.anchors.margins * 2
since spacing / margins are units.gu(1) everywhere, let's just use contentColumn.spacing everywhere?

=========

81 + width: (body == "") ? units.gu(2) : units.gu(6)
82 + height: (body == "") ? units.gu(2) : units.gu(6)
we need to be able to collapse items that _do_ have body text, no? actually, it's the secondaryIcon that should stay on screen in collapsed state, even if there is a primary icon, no? IMO it's a perfect place to use States and Transitions [1]

expanded => collapsed transition as follows:
SequentialAnimation {
  ParallelAnimation {
    icon shrinks
    icon fades out
    labelColumn.height shrinks to summaryLabel.height
    labelColumn fades out
  }
  PropertyAction {
    icon becomes invisible
    bodyLabel becomes invisible
  }
}

collapsed => expanded transition as follows:
SequentialAnimation {
  PropertyAction {
    icon becomes visible
    bodyLabel becomes visible
  }
  ParallelAnimation {
    icon grows
    icon fades in
    labelColumn.height grows to labelColumn.implicitHeight
    labelColumn fades in
  }
}

you also need to bind labelColumn.clip to Transition.running [2] of the two above.

=========

107 + height: childrenRect.height
this shouldn't be needed in Positioners - they get sized by their children implicitly

=========

113 + width: parent.width - x
this should only be like that in labelColumn, the Labels themselves should anchor to parent.left/right

=========

124 + visible: body != ""
as mentioned above, use States and Transitions for that

=========

141 + visible: type == "Notifications.Type.SnapDecision"
145 + model: actions
reference by object - notification.type, notification.actions

=========

74 + width: parent.width
142 + width: childrenRect.width
you should anchor the Rows to parent.left/right instead

=========

because of the quality issues, let's drop sourceSize for now (sorry)

=========

46 + width: parent.width
191 + anchors.fill: parent
these need to go away (component dimensions should generally be handled in their parents, unless they depend on content dimensions):

=========

225 +
rogue newline

=========

237 +<<<<<<< TREE
238 +=======
239 +add_qml_test(DashPreview)
240 +add_qml_test(NotificationRenderer)
241 +>>>>>>> MERGE-SOURCE
conflict (DashPreview shouldn't be touched)

=========

add some controls next to the view so that you can manually drive the test (see other tests in qmluitests for examples)

=========

[1] http://qt-project.org/doc/qt-5.0/qtquick/qtquick-statesanimations-states.html
[2] http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick2-transition.html#running-prop

review: Needs Fixing
Revision history for this message
Mirco Müller (macslow) wrote :

> 81 + width: (body == "") ? units.gu(2) : units.gu(6)
> 82 + height: (body == "") ? units.gu(2) : units.gu(6)
> we need to be able to collapse items that _do_ have body text, no? actually,
> it's the secondaryIcon that should stay on screen in collapsed state, even if
> there is a primary icon, no?

No, this here is meant for the special "icon+summary" layout case Design wants. This is different from a collapsing snap-decision, which is only meant to be used on large screens.

> 141 + visible: type == "Notifications.Type.SnapDecision"
> 145 + model: actions
> reference by object - notification.type, notification.actions

That does not work. How can I make the object known? Do I need to change the way data is passed in the test?

> 46 + width: parent.width
> 191 + anchors.fill: parent
> these need to go away (component dimensions should generally be handled in
> their parents, unless they depend on content dimensions):

I could get rid of "anchors.fill: parent", but I have to keep "width: parent.width". That way the width of the ListView holding the notifications follows any embedding parent-item. Otherwise there's no way to tell the ListView what horizontal space it may occupy.

Revision history for this message
Michał Sawicz (saviq) wrote :

W dniu 09.04.2013 17:07, Mirco Müller pisze:
>> 81 + width: (body == "") ? units.gu(2) : units.gu(6)
>> > 82 + height: (body == "") ? units.gu(2) : units.gu(6)
>> > we need to be able to collapse items that _do_ have body text, no? actually,
>> > it's the secondaryIcon that should stay on screen in collapsed state, even if
>> > there is a primary icon, no?
> No, this here is meant for the special "icon+summary" layout case Design wants. This is different from a collapsing snap-decision, which is only meant to be used on large screens.

Right, but since the icon is that small, do we not want to get rid of
the UbuntuShape for it? We could easily switch the source of the icons
instead of the dimensions of them? Is the lack of a body the only
determinant of whether it's a icon+summary layout? Does that mean body
is required for the generic case? It doesn't make sense for the icon to
be an avatar, for example, when it's just 2x2 GUs?

>> > 141 + visible: type == "Notifications.Type.SnapDecision"
>> > 145 + model: actions
>> > reference by object - notification.type, notification.actions
> That does not work. How can I make the object known? Do I need to change the way data is passed in the test?

Hum? The top level UbuntuShape has «id: notification» and type and
actions properties, can you not access those for some reason?

>> > 46 + width: parent.width
>> > 191 + anchors.fill: parent
>> > these need to go away (component dimensions should generally be handled in
>> > their parents, unless they depend on content dimensions):
> I could get rid of "anchors.fill: parent", but I have to keep "width: parent.width". That way the width of the ListView holding the notifications follows any embedding parent-item. Otherwise there's no way to tell the ListView what horizontal space it may occupy.

That's the «width: parent.width» in Notification.qml:29, it should be in
Notifications.qml:29 - it's in the delegate where you should define the
dimensions (again, unless they depend on their content). I think the
rule of thumb is: top-level component in a file should not have its
dimensions defined, unless they depend on the contents.

Case in point: it's Shell.qml that will decide on the dimensions of
Notifications {}, it's Notifications.qml that will decide on the
dimensions of Notification {} - it's delegate. And you should use
anchors there, too.

Cheers,
--
Michał Sawicz <email address hidden>
Canonical Services Ltd.

Revision history for this message
Michał Sawicz (saviq) wrote :

Hey, make sure to merge trunk, soon, there's conflicts.

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

if possible remove wait()s from the tests. They take ~8 secs now while I think they could run in < 0.5 secs. (Makes a huge difference in Jenkins where all 300 tests we have are summed up)

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

I'd like to see tests for the different mouse interactions:

- notifcation is click-through or not (interactive/not interactive?)
- click buttons in notifications

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

604 + wait(100)

really required? I know only because view.count == model.count it doesn't mean that the delegates are created already. I think still wait(0) should be enough. (Fine as long it is set to WIP - just wanted to mention it for completemess)

* Regarding the if() around the signal spys: I think it would be better to add the conditions to the test data. Because if you decide on interactiveArea.visible, your test wouldn't catch a bug where interactiveArea is already visible by mistake. Better extending testdata with data like "isInteractive: true; hasButtons: false"

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

debian/qml-phone-shell.install needs to be fixed:

dh_install: usr/share/qml-phone-shell/Notifications/Notification.qml exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/Notifications.qml exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/timings.js exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/graphics/tile.png exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/graphics/avatar3.jpg exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/graphics/avatar1.jpg exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/graphics/icon_phone.png exists in debian/tmp but is not installed to anywhere
dh_install: usr/share/qml-phone-shell/Notifications/graphics/avatar2.jpg exists in debian/tmp but is not installed to anywhere
dh_install: missing files, aborting

review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

I don't think those images should be installed with the shell. They should rather be moved into tests/mocks or the like

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

Hey, no need to put it as Work In Progress when you go and fix review comments. Leaving it at Needs Review is fine.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

224 + easing.type: Timings.easing
288 +var easing = "OutQuint";

Interesting... I just thought easings.type is a enum instead of a string. Looking it up in the docs it seems using strings is possible. Easing.OutQuint the preferred one though (I guess because it gives us code completion in QtCreator while strings don't):

<snip>

QML Basic Type: enumeration
An enumeration type consists of a set of named values.
An enumeration value may be specifed as either a string:

 Text { horizontalAlignment: "AlignRight" }

or as <Element>.<value>:

 Text { horizontalAlignment: Text.AlignRight }

The second form is preferred.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :
Download full text (3.2 KiB)

Tests
========================

627 + wait(0)

Looks unnecessary?

========

643 + interactiveAreaSpy.clear()
644 + clickThroughSpy.clear()

651 + clickThroughSpy.clear()

You should do that in a « function cleanup() » instead - it's called after every test case.

========

646 + interactiveAreaSpy.wait()
647 + verify(interactiveAreaSpy.count == 1)

653 + clickThroughSpy.wait()
654 + verify(clickThroughSpy.count == 1)

SignalSpy.wait() already verifies the count is 1.

========

648 + verify(clickThroughSpy.count == 0)

Should use compare().

========

649 + }
650 + else {

Please keep in one line.

========

638 + compare(icon.visible, data.iconVisible, "check for icon of notification" + (mockModel.count - 1))
639 + compare(interactiveArea.visible, data.interactiveAreaVisible, "check for interactive notification" + (mockModel.count - 1))

657 + compare(secondaryIcon.visible, data.secondaryIconVisible, "check for secondary icon" + (mockModel.count - 1))
658 + compare(summaryLabel.visible, data.summaryVisible, "check for summary-label" + (mockModel.count - 1))
659 + compare(bodyLabel.visible, data.bodyVisible, "check for body-label" + (mockModel.count - 1))
660 + compare(buttonRow.visible, data.buttonRowVisible, "check for buttons" + (mockModel.count - 1))

The messages could be improved, and also "notification1", "notification2" are not really useful - the tag: from _data() shows which test is being run.

========

662 + if(data.buttonRowVisible) {
663 + var buttonCancel = findChild(buttonRow, "button0")
664 + var buttonAccept = findChild(buttonRow, "button1")
665 +
666 + buttonSpy.target = buttonCancel
667 + buttonSpy.clear()
668 + mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
669 + buttonSpy.wait()
670 + verify(buttonSpy.count == 1)
671 +
672 + buttonSpy.target = buttonAccept
673 + buttonSpy.clear()
674 + mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2)
675 + buttonSpy.wait()
676 + verify(buttonSpy.count == 1)
677 + }

This only tests that Button.clicked signal is working. You should instead create an actionInvoked(string) signal on Notification and spy on this signal, making sure that SignalSpy.signalArguments are as expected.

Another run through the code itself
===================================

 46 + width: parent.width
 47 + height: childrenRect.height

Again, top-level components in a file should not have width or height defined. Use implicitHeight and get rid of width altogether - it's determined by the delegate definition in Notifications.qml.

========

 57 + visible: type == "Notifications.Type.Interactive"

MouseAreas are not graphical, use "enabled" instead.

========

 58 + onClicked: print("clicked " + actions.get(0).id)

Forward through a...

Read more...

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :
Download full text (3.3 KiB)

There's still some questions from https://code.launchpad.net/~macslow/unity/phablet-notification-renderer/+merge/155512/comments/346145 unanswered.

=========

 50 + visible: opacity > 0

Is that needed?

=========

 57 + visible: type == "Notifications.Type.Interactive"

Where is type defined?

=========

 68 + margins: units.gu(1)
 69 + }
 70 + height: childrenRect.height + contentColumn.anchors.margins
 71 + spacing: units.gu(1)

 76 + spacing: units.gu(1)

147 + spacing: units.gu(1)

163 + width: (topRow.width - units.gu(1)) / 2

I mentioned earlier a common spacing could be useful, go with the below instead:

 68 + margins: spacing
 69 + }
 70 + height: childrenRect.height + spacing
 71 + spacing: units.gu(1)

 76 + spacing: contentColumn.spacing

147 + spacing: contentColumn.spacing

163 + width: (topRow.width - buttonRow.spacing) / 2

=========

 81 + height: childrenRect.height

That's unnecessary - default in Positioners.

=========

 83 + UbuntuShape {
 84 + id: icon
 85 +
 86 + objectName: "icon"
 87 + width: (body == "") ? units.gu(2) : units.gu(6)
 88 + height: (body == "") ? units.gu(2) : units.gu(6)
 89 + image: Image {
 90 + id: avatarIcon
 91 +
 92 + fillMode: Image.PreserveAspectCrop
 93 + }
 94 + }
 95 +
 96 + Image {
 97 + id: secondaryIcon
 98 +
 99 + objectName: "secondaryIcon"
100 + width: units.gu(2)
101 + height: units.gu(2)
102 + visible: source !== undefined && source != "" && bodyLabel.visible
103 + fillMode: Image.PreserveAspectCrop
104 + }

I'm still not sure we want a 2x2 GU UbuntuShape, see the comment mentioned at the top of this one.

=========

149 + visible: type == "Notifications.Type.SnapDecision"

156 + model: actions

See the comment at the top.

=========

159 + id: button
160 +

Looks unused.

=========

163 + width: (topRow.width - units.gu(1)) / 2

Why topRow? Shouldn't this be buttonRow.width?

=========

166 + onClicked: print("clicked " + id)

Same as before - forward via a actionInvoked(string) signal.

=========

208 + delegate: Notification {
209 + objectName: "notification" + index
210 + iconSource: model.icon
211 + secondaryIconSource: model.secondaryIcon
212 + summary: model.summary
213 + body: model.body
214 + actions: model.actions
215 + }

Use anchors { left: parent.left; right: parent.right } to make the delegates fill Notifications horizontally. Also, "model" here is unnecessary - you can access the roles directly.

=========

309 +add_qml_test(Notifications NotificationRenderer)

326 === added file 'tests/qmltests/Notifications/tst_NotificationRenderer.qml'
327 --- tests/qmltests/Notifications/tst_NotificationRenderer.qml 1970-01-01 ...

Read more...

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

Ah one more thing, please reduce the set of test assets to a minimum. You can use those from /graphics instead.

Obviously we need to clean those up, but let's not add more instead.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Mirco Müller (macslow) wrote :

Hm... CI of my branch failed because of a failure with test_hide_hud_click.

Revision history for this message
Mirco Müller (macslow) wrote :

qml_phone_shell.tests.testconfigurations.TestNexus10.test_hide_hud_click() to be precise

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

Looks good to me now.

review: Approve
Revision history for this message
Michał Sawicz (saviq) wrote :

Hey, some last still-remaining tweaks:

=====

 77 + spacing: units.gu(1)

Should be contentColumn.spacing

=====

 58 + enabled: type == "Notifications.Type.Interactive"

150 + visible: type == "Notifications.Type.SnapDecision"

157 + model: actions

Should be notification.type ==, notification.actions

=====

162 + width: (topRow.width - buttonRow.spacing) / 2

Why topRow? Shouldn't this be buttonRow.width?

=====

That's it.

review: Needs Fixing
Revision history for this message
Mirco Müller (macslow) wrote :

Did those requested tweaks.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Yup!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2013-04-26 14:54:51 +0000
+++ CMakeLists.txt 2013-05-15 15:17:27 +0000
@@ -183,6 +183,7 @@
183 Panel183 Panel
184 Launcher184 Launcher
185 SideStage185 SideStage
186 Notifications
186 )187 )
187188
188install(DIRECTORY ${QML_DIRS}189install(DIRECTORY ${QML_DIRS}
189190
=== added directory 'Notifications'
=== added file 'Notifications/Notification.qml'
--- Notifications/Notification.qml 1970-01-01 00:00:00 +0000
+++ Notifications/Notification.qml 2013-05-15 15:17:27 +0000
@@ -0,0 +1,153 @@
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
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19
20UbuntuShape {
21 id: notification
22
23 property string type
24 property alias iconSource: avatarIcon.source
25 property alias secondaryIconSource: secondaryIcon.source
26 property alias summary: summaryLabel.text
27 property alias body: bodyLabel.text
28 property var actions
29
30 signal actionInvoked(string buttonId)
31
32 implicitHeight: childrenRect.height
33 color: Qt.rgba(0, 0, 0, 0.85)
34 opacity: 0
35
36 MouseArea {
37 id: interactiveArea
38
39 anchors.fill: contentColumn
40 objectName: "interactiveArea"
41 enabled: notification.type == "Notifications.Type.Interactive"
42 onClicked: notification.actionInvoked(actions.get(0).id)
43 }
44
45 Column {
46 id: contentColumn
47
48 anchors {
49 left: parent.left
50 right: parent.right
51 top: parent.top
52 margins: spacing
53 }
54 height: childrenRect.height + spacing
55 spacing: units.gu(1)
56
57 Row {
58 id: topRow
59
60 spacing: contentColumn.spacing
61 anchors {
62 left: parent.left
63 right: parent.right
64 }
65
66 UbuntuShape {
67 id: icon
68
69 objectName: "icon"
70 width: units.gu(6)
71 height: units.gu(6)
72 visible: iconSource !== undefined && iconSource != ""
73 image: Image {
74 id: avatarIcon
75
76 fillMode: Image.PreserveAspectCrop
77 }
78 }
79
80 Image {
81 id: secondaryIcon
82
83 objectName: "secondaryIcon"
84 width: units.gu(2)
85 height: units.gu(2)
86 visible: source !== undefined && source != ""
87 fillMode: Image.PreserveAspectCrop
88 }
89
90 Column {
91 id: labelColumn
92 width: parent.width - x
93
94 Label {
95 id: summaryLabel
96
97 objectName: "summaryLabel"
98 anchors {
99 left: parent.left
100 right: parent.right
101 }
102 fontSize: "medium"
103 font.bold: true
104 color: "#f3f3e7"
105 elide: Text.ElideRight
106 }
107
108 Label {
109 id: bodyLabel
110
111 objectName: "bodyLabel"
112 anchors {
113 left: parent.left
114 right: parent.right
115 }
116 visible: body != ""
117 fontSize: "small"
118 color: "#f3f3e7"
119 opacity: 0.6
120 wrapMode: Text.WordWrap
121 maximumLineCount: 10
122 elide: Text.ElideRight
123 }
124 }
125 }
126
127 Row {
128 id: buttonRow
129
130 objectName: "buttonRow"
131 spacing: contentColumn.spacing
132 layoutDirection: Qt.RightToLeft
133 visible: notification.type == "Notifications.Type.SnapDecision"
134 anchors {
135 left: parent.left
136 right: parent.right
137 }
138
139 Repeater {
140 model: notification.actions
141
142 Button {
143 objectName: "button" + index
144 color: Positioner.isFirstItem ? "#d85317" : "#cdcdcb"
145 width: (buttonRow.width - buttonRow.spacing) / 2
146 height: units.gu(4)
147 text: label
148 onClicked: notification.actionInvoked(id)
149 }
150 }
151 }
152 }
153}
0154
=== added file 'Notifications/Notifications.qml'
--- Notifications/Notifications.qml 1970-01-01 00:00:00 +0000
+++ Notifications/Notifications.qml 2013-05-15 15:17:27 +0000
@@ -0,0 +1,77 @@
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
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import "../Components"
20import "timings.js" as Timings
21
22ListView {
23 id: notificationRenderer
24
25 objectName: "notificationRenderer"
26 interactive: false
27
28 spacing: units.gu(.5)
29 delegate: Notification {
30 objectName: "notification" + index
31 anchors {
32 left: parent.left
33 right: parent.right
34 }
35 type: model.type
36 iconSource: model.icon
37 secondaryIconSource: model.secondaryIcon
38 summary: model.summary
39 body: model.body
40 actions: model.actions
41 }
42
43 populate: Transition {
44 NumberAnimation {
45 property: "opacity"
46 to: 1
47 duration: Timings.snapBeat
48 easing.type: Timings.easing
49 }
50 }
51
52 add: Transition {
53 NumberAnimation {
54 property: "opacity"
55 to: 1
56 duration: Timings.snapBeat
57 easing.type: Timings.easing
58 }
59 }
60
61 remove: Transition {
62 NumberAnimation {
63 property: "opacity"
64 to: 0
65 duration: Timings.fastBeat
66 easing.type: Timings.easing
67 }
68 }
69
70 displaced: Transition {
71 NumberAnimation {
72 properties: "x,y"
73 duration: Timings.fastBeat
74 easing.type: Timings.easing
75 }
76 }
77}
078
=== added file 'Notifications/timings.js'
--- Notifications/timings.js 1970-01-01 00:00:00 +0000
+++ Notifications/timings.js 2013-05-15 15:17:27 +0000
@@ -0,0 +1,23 @@
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
17var rhythm = 700;
18var fastBeat = 0.5 * rhythm;
19var snapBeat = 0.5 * fastBeat;
20var slowBeat = rhythm;
21var sleepyBeat = slowBeat * 2;
22
23var easing = Easing.OutQuint;
024
=== modified file 'debian/qml-phone-shell.install'
--- debian/qml-phone-shell.install 2013-04-23 15:58:17 +0000
+++ debian/qml-phone-shell.install 2013-05-15 15:17:27 +0000
@@ -7,6 +7,7 @@
7/usr/share/qml-phone-shell/Greeter/*7/usr/share/qml-phone-shell/Greeter/*
8/usr/share/qml-phone-shell/Hud/*8/usr/share/qml-phone-shell/Hud/*
9/usr/share/qml-phone-shell/Launcher/*9/usr/share/qml-phone-shell/Launcher/*
10/usr/share/qml-phone-shell/Notifications/*
10/usr/share/qml-phone-shell/Panel/*11/usr/share/qml-phone-shell/Panel/*
11/usr/share/qml-phone-shell/Shell.qml12/usr/share/qml-phone-shell/Shell.qml
12/usr/share/qml-phone-shell/SideStage/*13/usr/share/qml-phone-shell/SideStage/*
1314
=== modified file 'tests/qmltests/CMakeLists.txt'
--- tests/qmltests/CMakeLists.txt 2013-05-14 13:42:33 +0000
+++ tests/qmltests/CMakeLists.txt 2013-05-15 15:17:27 +0000
@@ -48,6 +48,7 @@
48add_qml_test(Hud Hud)48add_qml_test(Hud Hud)
49add_qml_test(Hud Result)49add_qml_test(Hud Result)
50add_qml_test(Launcher Launcher)50add_qml_test(Launcher Launcher)
51add_qml_test(Notifications Notifications)
51add_qml_test(Panel IndicatorRow)52add_qml_test(Panel IndicatorRow)
52add_qml_test(Panel Indicators)53add_qml_test(Panel Indicators)
53add_qml_test(Panel MenuContent)54add_qml_test(Panel MenuContent)
5455
=== added directory 'tests/qmltests/Notifications'
=== added file 'tests/qmltests/Notifications/tst_Notifications.qml'
--- tests/qmltests/Notifications/tst_Notifications.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Notifications/tst_Notifications.qml 2013-05-15 15:17:27 +0000
@@ -0,0 +1,335 @@
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
17import QtQuick 2.0
18import QtTest 1.0
19import ".."
20import "../../../Notifications"
21import Ubuntu.Components 0.1
22import Unity.Test 0.1
23
24Row {
25 id: rootRow
26
27 ListModel {
28 id: mockModel
29 }
30
31 function addSnapDecisionNotification() {
32 var n = {
33 type: "Notifications.Type.SnapDecision",
34 summary: "Tom Ato",
35 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
36 icon: "../graphics/avatars/funky.png",
37 secondaryIcon: "../graphics/applicationIcons/facebook.png",
38 actions: [{ id: "ok_id", label: "Ok"},
39 { id: "cancel_id", label: "Cancel"}]
40 }
41
42 mockModel.append(n)
43 }
44
45 function addEphemeralNotification() {
46 var n = {
47 type: "Notifications.Type.Ephemeral",
48 summary: "Cole Raby",
49 body: "I did not expect it to be that late.",
50 icon: "../graphics/avatars/amanda.png",
51 secondaryIcon: "../graphics/applicationIcons/facebook.png",
52 actions: []
53 }
54
55 mockModel.append(n)
56 }
57
58 function addEphemeralIconSummaryNotification() {
59 var n = {
60 type: "Notifications.Type.Ephemeral",
61 summary: "Photo upload completed",
62 body: "",
63 icon: "",
64 secondaryIcon: "../graphics/applicationIcons/facebook.png",
65 actions: []
66 }
67
68 mockModel.append(n)
69 }
70
71 function addInteractiveNotification() {
72 var n = {
73 type: "Notifications.Type.Interactive",
74 summary: "Interactive notification",
75 body: "This is a notification that can be clicked",
76 icon: "../graphics/avatars/anna_olsson.png",
77 secondaryIcon: "",
78 actions: [{ id: "reply_id", label: "Dummy"}],
79 }
80
81 mockModel.append(n)
82 }
83
84 function clearNotifications() {
85 mockModel.clear()
86 }
87
88 function remove1stNotification() {
89 if (mockModel.count > 0)
90 mockModel.remove(0)
91 }
92
93 Rectangle {
94 id: notificationsRect
95
96 width: units.gu(40)
97 height: units.gu(71)
98
99 MouseArea{
100 id: clickThroughCatcher
101
102 anchors.fill: parent
103 }
104
105 Notifications {
106 id: notifications
107
108 anchors.fill: parent
109 anchors.margins: units.gu(1)
110 model: mockModel
111 }
112 }
113
114 Rectangle {
115 id: interactiveControls
116
117 width: units.gu(30)
118 height: units.gu(71)
119 color: "grey"
120
121 Column {
122 spacing: units.gu(1)
123 anchors.fill: parent
124 anchors.margins: units.gu(1)
125
126 Button {
127 width: parent.width
128 text: "add a snap-decision"
129 onClicked: addSnapDecisionNotification()
130 }
131
132 Button {
133 width: parent.width
134 text: "add an ephemeral"
135 onClicked: addEphemeralNotification()
136 }
137
138 Button {
139 width: parent.width
140 text: "add an icon-summary"
141 onClicked: addEphemeralIconSummaryNotification()
142 }
143
144 Button {
145 width: parent.width
146 text: "add an interactive"
147 onClicked: addInteractiveNotification()
148 }
149
150 Button {
151 width: parent.width
152 text: "remove 1st notification"
153 onClicked: remove1stNotification()
154 }
155
156 Button {
157 width: parent.width
158 text: "clear model"
159 onClicked: clearNotifications()
160 }
161 }
162 }
163
164 UnityTestCase {
165 id: root
166 name: "NotificationRendererTest"
167 when: windowShown
168
169 function test_NotificationRenderer_data() {
170 return [
171 {
172 tag: "Snap Decision with secondary icon",
173 type: "Notifications.Type.SnapDecision",
174 summary: "Tom Ato",
175 body: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.",
176 icon: "../graphics/avatars/funky.png",
177 secondaryIcon: "../graphics/applicationIcons/facebook.png",
178 actions: [{ id: "ok_id", label: "Ok"},
179 { id: "cancel_id", label: "Cancel"}],
180 summaryVisible: true,
181 bodyVisible: true,
182 interactiveAreaEnabled: false,
183 iconVisible: true,
184 secondaryIconVisible: true,
185 buttonRowVisible: true
186 },
187 {
188 tag: "Ephemeral notification - icon-summary layout",
189 type: "Notifications.Type.Ephemeral",
190 summary: "Photo upload completed",
191 body: "",
192 icon: "",
193 secondaryIcon: "../graphics/applicationIcons/facebook.png",
194 actions: [],
195 summaryVisible: true,
196 bodyVisible: false,
197 interactiveAreaEnabled: false,
198 iconVisible: false,
199 secondaryIconVisible: true,
200 buttonRowVisible: false
201 },
202 {
203 tag: "Ephemeral notification - check suppression of secondary icon for icon-summary layout",
204 type: "Notifications.Type.Ephemeral",
205 summary: "New comment successfully published",
206 body: "",
207 icon: "",
208 secondaryIcon: "../graphics/applicationIcons/facebook.png",
209 actions: [],
210 summaryVisible: true,
211 bodyVisible: false,
212 interactiveAreaEnabled: false,
213 iconVisible: false,
214 secondaryIconVisible: true,
215 buttonRowVisible: false
216 },
217 {
218 tag: "Interactive notification",
219 type: "Notifications.Type.Interactive",
220 summary: "Interactive notification",
221 body: "This is a notification that can be clicked",
222 icon: "../graphics/avatars/amanda.png",
223 secondaryIcon: "",
224 actions: [{ id: "reply_id", label: "Dummy"}],
225 summaryVisible: true,
226 bodyVisible: true,
227 interactiveAreaEnabled: true,
228 iconVisible: true,
229 secondaryIconVisible: false,
230 buttonRowVisible: false
231 },
232 {
233 tag: "Snap Decision without secondary icon",
234 type: "Notifications.Type.SnapDecision",
235 summary: "Bro Coly",
236 body: "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
237 icon: "../graphics/avatars/anna_olsson.png",
238 secondaryIcon: "",
239 actions: [{ id: "accept_id", label: "Accept"},
240 { id: "reject_id", label: "Reject"}],
241 summaryVisible: true,
242 bodyVisible: true,
243 interactiveAreaEnabled: false,
244 iconVisible: true,
245 secondaryIconVisible: false,
246 buttonRowVisible: true
247 },
248 {
249 tag: "Ephemeral notification",
250 type: "Notifications.Type.Ephemeral",
251 summary: "Cole Raby",
252 body: "I did not expect it to be that late.",
253 icon: "../graphics/avatars/funky.png",
254 secondaryIcon: "../graphics/applicationIcons/facebook.png",
255 actions: [],
256 summaryVisible: true,
257 bodyVisible: true,
258 interactiveAreaEnabled: false,
259 iconVisible: true,
260 secondaryIconVisible: true,
261 buttonRowVisible: false
262 }
263 ]
264 }
265
266 SignalSpy {
267 id: clickThroughSpy
268
269 target: clickThroughCatcher
270 signalName: "clicked"
271 }
272
273 SignalSpy {
274 id: actionSpy
275 signalName: "actionInvoked"
276 }
277
278 function cleanup() {
279 clickThroughSpy.clear()
280 actionSpy.clear()
281 }
282
283 function test_NotificationRenderer(data) {
284 // populate model with some mock notifications
285 mockModel.append(data)
286
287 // make sure the model is properly updated before going on
288 tryCompare(notifications, "count", mockModel.count)
289
290 var notification = findChild(notifications, "notification" + (mockModel.count - 1))
291 var icon = findChild(notification, "icon")
292 var interactiveArea = findChild(notification, "interactiveArea")
293 var secondaryIcon = findChild(notification, "secondaryIcon")
294 var summaryLabel = findChild(notification, "summaryLabel")
295 var bodyLabel = findChild(notification, "bodyLabel")
296 var buttonRow = findChild(notification, "buttonRow")
297 waitForRendering(buttonRow)
298
299 compare(icon.visible, data.iconVisible, "avatar-icon visibility is incorrect")
300 compare(interactiveArea.enabled, data.interactiveAreaEnabled, "check for interactive area")
301
302 if(data.interactiveAreaEnabled) {
303 actionSpy.target = notification
304 mouseClick(notification, notification.width / 2, notification.height / 2)
305 actionSpy.wait()
306 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id for interactive action")
307 compare(clickThroughSpy.count, 0, "click on interactive notification fell through")
308 } else {
309 mouseClick(notification, notification.width / 2, notification.height / 2)
310 clickThroughSpy.wait()
311 }
312
313 compare(secondaryIcon.visible, data.secondaryIconVisible, "secondary-icon visibility is incorrect")
314 compare(summaryLabel.visible, data.summaryVisible, "summary-text visibility is incorrect")
315 compare(bodyLabel.visible, data.bodyVisible, "body-text visibility is incorrect")
316 compare(buttonRow.visible, data.buttonRowVisible, "button visibility is incorrect")
317
318 if(data.buttonRowVisible) {
319 var buttonCancel = findChild(buttonRow, "button1")
320 var buttonAccept = findChild(buttonRow, "button0")
321
322 waitForRendering(notification)
323 actionSpy.target = notification
324 mouseClick(buttonCancel, buttonCancel.width / 2, buttonCancel.height / 2)
325 actionSpy.wait()
326 compare(actionSpy.signalArguments[0][0], data.actions[1]["id"], "got wrong id for negative action")
327 actionSpy.clear()
328
329 mouseClick(buttonAccept, buttonAccept.width / 2, buttonAccept.height / 2)
330 actionSpy.wait()
331 compare(actionSpy.signalArguments[0][0], data.actions[0]["id"], "got wrong id positive action")
332 }
333 }
334 }
335}

Subscribers

People subscribed via source and target branches