Merge lp:~ogra/junk/alternate-webapp-container into lp:junk

Proposed by Matti Rinta-Nikkola
Status: Needs review
Proposed branch: lp:~ogra/junk/alternate-webapp-container
Merge into: lp:junk
Diff against target: 831 lines (+764/-0)
12 files modified
README (+39/-0)
app.desktop (+7/-0)
app.json (+11/-0)
config.js (+20/-0)
manifest.json (+14/-0)
qml/ContentPickerDialog.qml (+116/-0)
qml/Main.qml (+173/-0)
qml/MimeTypeMapper.js (+43/-0)
qml/ThinProgressBar.qml (+35/-0)
qml/UCSComponents/EmptyState.qml (+43/-0)
qml/UCSComponents/RadialAction.qml (+12/-0)
qml/UCSComponents/RadialBottomEdge.qml (+251/-0)
To merge this branch: bzr merge lp:~ogra/junk/alternate-webapp-container
Reviewer Review Type Date Requested Status
David Futcher Pending
Review via email: mp+259843@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

16. By Oliver Grawert

add check for length of url-handler args

15. By Oliver Grawert

fix location of soundeffect wav file

14. By Oliver Grawert

oops, add the right imports to make HapticsEffect actually work

13. By Oliver Grawert

add support for audible links (click sound when website links are tapped)

12. By Oliver Grawert

add support for haptic feedback when links are clicked, add proper comments to config.js

11. By Oliver Grawert

add url handler support

10. By Oliver Grawert

make sure we actually ship ContentPicker and MimeTypeMapper

9. By Oliver Grawert

allow opening urls in the webapp domain from arguments (if called via url-dispatcher)

8. By Oliver Grawert

add support to override the User Agent

7. By Oliver Grawert

add support for geolocation, add a file picker for apps that support upload of files, set a bunch of defaults for the webview (most importantly cache images)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'README'
2--- README 1970-01-01 00:00:00 +0000
3+++ README 2015-05-21 19:43:33 +0000
4@@ -0,0 +1,39 @@
5+This is a new approach of an alternate webapp container that
6+does not use a toolbar at the top but instead provides a bottom
7+menu with navigation objects, similar to the one the dekko mail
8+app uses. This way you do not have a jiggly toolbar that always
9+expands and collapses in your app.
10+
11+To make use of this with your own webapp just follow the steps below.
12+
13+Fist of all branch this tree locally ...
14+
15+ bzr branch lp:~ogra/junk/alternate-webapp-container
16+
17+== Building ==
18+
19+Edit the value of applicationName at the top of qml/Main.qml to match
20+your application name to work around https://launchpad.net/bugs/1435778
21+
22+Edit config.js to add webappName, webappUrl and webappUrlPattern
23+similar to how you would use them as commandline options to webapp-container.
24+
25+Optionally you can set the webappUA variable in config.js to override
26+the User Agent string.
27+
28+Edit app.desktop and manifest.json to your liking.
29+Replace icon.png with your own icon.
30+
31+Now run:
32+ click build .
33+
34+This will create a .click package with you webapp.
35+
36+== Installing ==
37+
38+Push the click to your device via adb, then:
39+
40+ adb shell pkcon install-local --allow-untrusted /path/to/click
41+
42+You will see a bunch of progress bars ... once they are done,
43+pull down the app scope to refresh it and find your new app in there.
44
45=== added file 'app.desktop'
46--- app.desktop 1970-01-01 00:00:00 +0000
47+++ app.desktop 2015-05-21 19:43:33 +0000
48@@ -0,0 +1,7 @@
49+[Desktop Entry]
50+Name=Google Plus
51+Type=Application
52+Icon=icon.png
53+Exec=qmlscene %u qml/Main.qml
54+Terminal=false
55+X-Ubuntu-Touch=true
56
57=== added file 'app.json'
58--- app.json 1970-01-01 00:00:00 +0000
59+++ app.json 2015-05-21 19:43:33 +0000
60@@ -0,0 +1,11 @@
61+{
62+ "policy_groups": [
63+ "networking",
64+ "webview",
65+ "audio",
66+ "video",
67+ "location",
68+ "content_exchange"
69+ ],
70+ "policy_version": 1.1
71+}
72
73=== added file 'config.js'
74--- config.js 1970-01-01 00:00:00 +0000
75+++ config.js 2015-05-21 19:43:33 +0000
76@@ -0,0 +1,20 @@
77+// the name of your app as used in the click package (required)
78+var webappName = "google-plus.ogra"
79+
80+// the start url of your app (required)
81+var webappUrl = "https://plus.google.com/"
82+
83+// the the pattern that defines which links are considered
84+// local and which are opened in an external browser (required)
85+var webappUrlPattern = "https?://plus.google.*/*,https?://accounts.google.*/*,https?://accounts.google.co.*/*,https?://www.google.*/accounts/*"
86+
87+// a user agent override (optional)
88+// var webappUA = "uncomment this line and set your User Agent string here between these quotes, if you need to override it"
89+
90+// Haptic feedback for links (note, this does not work if your site
91+// uses javascript functions to open links) (optional)
92+// var hapticLinks = "true"
93+
94+// Audible feedback when clicking links (the same constraints as
95+// for hapticLinks apply) (optional)
96+// var audibleLinks = "true"
97
98=== added file 'icon.png'
99Binary files icon.png 1970-01-01 00:00:00 +0000 and icon.png 2015-05-21 19:43:33 +0000 differ
100=== added file 'manifest.json'
101--- manifest.json 1970-01-01 00:00:00 +0000
102+++ manifest.json 2015-05-21 19:43:33 +0000
103@@ -0,0 +1,14 @@
104+{
105+ "description": "Another G+ WebApp",
106+ "framework": "ubuntu-sdk-14.04",
107+ "hooks": {
108+ "google-plus": {
109+ "apparmor": "app.json",
110+ "desktop": "app.desktop"
111+ }
112+},
113+ "maintainer": "Oliver Grawert <ogra@ubuntu.com>",
114+ "name": "google-plus.ogra",
115+ "title": "Google Plus",
116+ "version": "0.1"
117+}
118
119=== added directory 'qml'
120=== added file 'qml/ContentPickerDialog.qml'
121--- qml/ContentPickerDialog.qml 1970-01-01 00:00:00 +0000
122+++ qml/ContentPickerDialog.qml 2015-05-21 19:43:33 +0000
123@@ -0,0 +1,116 @@
124+/*
125+ * Copyright 2014 Canonical Ltd.
126+ *
127+ * This file is part of webbrowser-app.
128+ *
129+ * webbrowser-app is free software; you can redistribute it and/or modify
130+ * it under the terms of the GNU General Public License as published by
131+ * the Free Software Foundation; version 3.
132+ *
133+ * webbrowser-app is distributed in the hope that it will be useful,
134+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
135+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
136+ * GNU General Public License for more details.
137+ *
138+ * You should have received a copy of the GNU General Public License
139+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
140+ */
141+
142+import QtQuick 2.0
143+import Ubuntu.Components 1.1
144+import Ubuntu.Components.Popups 1.0 as Popups
145+import Ubuntu.Content 0.1
146+import "MimeTypeMapper.js" as MimeTypeMapper
147+
148+Component {
149+ Popups.PopupBase {
150+ id: picker
151+ objectName: "contentPickerDialog"
152+
153+ // Set the parent at construction time, instead of letting show()
154+ // set it later on, which for some reason results in the size of
155+ // the dialog not being updated.
156+ parent: QuickUtils.rootItem(this)
157+
158+ property var activeTransfer
159+ property var selectedItems
160+
161+ Rectangle {
162+ anchors.fill: parent
163+
164+ ContentTransferHint {
165+ anchors.fill: parent
166+ activeTransfer: picker.activeTransfer
167+ }
168+
169+ ContentPeerPicker {
170+ id: peerPicker
171+ anchors.fill: parent
172+ visible: true
173+ contentType: ContentType.All
174+ handler: ContentHandler.Source
175+
176+ onPeerSelected: {
177+ if (model.allowMultipleFiles) {
178+ peer.selectionType = ContentTransfer.Multiple
179+ } else {
180+ peer.selectionType = ContentTransfer.Single
181+ }
182+ picker.activeTransfer = peer.request()
183+ stateChangeConnection.target = picker.activeTransfer
184+ }
185+
186+ onCancelPressed: {
187+ webview.focus = true
188+ model.reject()
189+ }
190+ }
191+ }
192+
193+ Connections {
194+ id: stateChangeConnection
195+ onStateChanged: {
196+ if (picker.activeTransfer.state === ContentTransfer.Charged) {
197+ selectedItems = []
198+ for(var i in picker.activeTransfer.items) {
199+ selectedItems.push(String(picker.activeTransfer.items[i].url).replace("file://", ""))
200+ }
201+ acceptTimer.running = true
202+ }
203+ }
204+ }
205+
206+ // FIXME: Work around for browser becoming insensitive to touch events
207+ // if the dialog is dismissed while the application is inactive.
208+ // Just listening for changes to Qt.application.active doesn't appear
209+ // to be enough to resolve this, so it seems that something else needs
210+ // to be happening first. As such there's a potential for a race
211+ // condition here, although as yet no problem has been encountered.
212+ Timer {
213+ id: acceptTimer
214+ interval: 100
215+ repeat: true
216+ onTriggered: {
217+ if(Qt.application.active) {
218+ webview.focus = true
219+ model.accept(selectedItems)
220+ }
221+ }
222+ }
223+
224+ Component.onCompleted: {
225+ if(acceptTypes.length === 1) {
226+ var contentType = MimeTypeMapper.mimeTypeToContentType(acceptTypes[0])
227+ if(contentType == ContentType.Unknown) {
228+ // If we don't recognise the type, allow uploads from any app
229+ contentType = ContentType.All
230+ }
231+ peerPicker.contentType = contentType
232+ } else {
233+ peerPicker.contentType = ContentType.All
234+ }
235+ show()
236+ }
237+ }
238+}
239+
240
241=== added file 'qml/Main.qml'
242--- qml/Main.qml 1970-01-01 00:00:00 +0000
243+++ qml/Main.qml 2015-05-21 19:43:33 +0000
244@@ -0,0 +1,173 @@
245+import QtQuick 2.2
246+import Ubuntu.Web 0.2
247+import Ubuntu.Components 1.1
248+import com.canonical.Oxide 1.0 as Oxide
249+import "UCSComponents"
250+import Ubuntu.Content 1.1
251+import QtMultimedia 5.0
252+import QtFeedback 5.0
253+import "."
254+import "../config.js" as Conf
255+
256+MainView {
257+ objectName: "mainView"
258+
259+ applicationName: "google-plus.ogra"
260+
261+ useDeprecatedToolbar: false
262+ anchorToKeyboard: true
263+ automaticOrientation: true
264+
265+ property string myUrl: Conf.webappUrl
266+ property string myPattern: Conf.webappUrlPattern
267+
268+ property string myUA: Conf.webappUA ? Conf.webappUA : "Mozilla/5.0 (Linux; Android 5.0; Nexus 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.102 Mobile Safari/537.36"
269+
270+ Page {
271+ id: page
272+ anchors {
273+ fill: parent
274+ bottom: parent.bottom
275+ }
276+ width: parent.width
277+ height: parent.height
278+
279+ HapticsEffect {
280+ id: vibration
281+ attackIntensity: 0.0
282+ attackTime: 50
283+ intensity: 1.0
284+ duration: 10
285+ fadeTime: 50
286+ fadeIntensity: 0.0
287+ }
288+
289+ SoundEffect {
290+ id: clicksound
291+ source: "../sounds/Click.wav"
292+ }
293+
294+ WebContext {
295+ id: webcontext
296+ userAgent: myUA
297+ }
298+ WebView {
299+ id: webview
300+ anchors {
301+ fill: parent
302+ bottom: parent.bottom
303+ }
304+ width: parent.width
305+ height: parent.height
306+
307+ context: webcontext
308+ url: myUrl
309+ preferences.localStorageEnabled: true
310+ preferences.allowFileAccessFromFileUrls: true
311+ preferences.allowUniversalAccessFromFileUrls: true
312+ preferences.appCacheEnabled: true
313+ preferences.javascriptCanAccessClipboard: true
314+ filePicker: filePickerLoader.item
315+
316+ function navigationRequestedDelegate(request) {
317+ var url = request.url.toString();
318+ var pattern = myPattern.split(',');
319+ var isvalid = false;
320+
321+ if (Conf.hapticLinks) {
322+ vibration.start()
323+ }
324+
325+ if (Conf.audibleLinks) {
326+ clicksound.play()
327+ }
328+
329+ for (var i=0; i<pattern.length; i++) {
330+ var tmpsearch = pattern[i].replace(/\*/g,'(.*)')
331+ var search = tmpsearch.replace(/^https\?:\/\//g, '(http|https):\/\/');
332+ if (url.match(search)) {
333+ isvalid = true;
334+ break
335+ }
336+ }
337+ if(isvalid == false) {
338+ console.warn("Opening remote: " + url);
339+ Qt.openUrlExternally(url)
340+ request.action = Oxide.NavigationRequest.ActionReject
341+ }
342+ }
343+ Component.onCompleted: {
344+ preferences.localStorageEnabled = true
345+ if (Qt.application.arguments[1].toString().indexOf(myUrl) > -1) {
346+ console.warn("got argument: " + Qt.application.arguments[1])
347+ url = Qt.application.arguments[1]
348+ }
349+ console.warn("url is: " + url)
350+ }
351+ onGeolocationPermissionRequested: { request.accept() }
352+ Loader {
353+ id: filePickerLoader
354+ source: "ContentPickerDialog.qml"
355+ asynchronous: true
356+ }
357+ }
358+ ThinProgressBar {
359+ webview: webview
360+ anchors {
361+ left: parent.left
362+ right: parent.right
363+ top: parent.top
364+ }
365+ }
366+ RadialBottomEdge {
367+ id: nav
368+ visible: true
369+ actions: [
370+ RadialAction {
371+ id: reload
372+ iconName: "reload"
373+ onTriggered: {
374+ webview.reload()
375+ }
376+ text: qsTr("Reload")
377+ },
378+ RadialAction {
379+ id: forward
380+ enabled: webview.canGoForward
381+ iconName: "go-next"
382+ onTriggered: {
383+ webview.goForward()
384+ }
385+ text: qsTr("Forward")
386+ },
387+ RadialAction {
388+ id: back
389+ enabled: webview.canGoBack
390+ iconName: "go-previous"
391+ onTriggered: {
392+ webview.goBack()
393+ }
394+ text: qsTr("Back")
395+ }
396+ ]
397+ }
398+ }
399+ Connections {
400+ target: Qt.inputMethod
401+ onVisibleChanged: nav.visible = !nav.visible
402+ }
403+ Connections {
404+ target: webview
405+ onFullscreenChanged: nav.visible = !webview.fullscreen
406+ }
407+ Connections {
408+ target: UriHandler
409+ onOpened: {
410+ if (uris.length === 0 ) {
411+ return;
412+ }
413+ webview.url = uris[0]
414+ console.warn("uri-handler request")
415+ }
416+ }
417+}
418
419=== added file 'qml/MimeTypeMapper.js'
420--- qml/MimeTypeMapper.js 1970-01-01 00:00:00 +0000
421+++ qml/MimeTypeMapper.js 2015-05-21 19:43:33 +0000
422@@ -0,0 +1,43 @@
423+/*
424+ * Copyright 2014 Canonical Ltd.
425+ *
426+ * This file is part of webbrowser-app.
427+ *
428+ * webbrowser-app is free software; you can redistribute it and/or modify
429+ * it under the terms of the GNU General Public License as published by
430+ * the Free Software Foundation; version 3.
431+ *
432+ * webbrowser-app is distributed in the hope that it will be useful,
433+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
434+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
435+ * GNU General Public License for more details.
436+ *
437+ * You should have received a copy of the GNU General Public License
438+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
439+*/
440+
441+
442+function mimeTypeToContentType(mimeType) {
443+ if(mimeType.search("image/") === 0) {
444+ return ContentType.Pictures;
445+ } else if(mimeType.search("audio/") === 0) {
446+ return ContentType.Music;
447+ } else if(mimeType.search("video/") === 0) {
448+ return ContentType.Videos;
449+ } else if(mimeType.search("text/x-vcard") === 0) {
450+ return ContentType.Contacts;
451+ } else if(mimeType.search("application/epub[+]zip") === 0
452+ || mimeType.search("application/vnd\.amazon\.ebook") === 0
453+ || mimeType.search("application/x-mobipocket-ebook") === 0
454+ || mimeType.search("application/x-fictionbook+xml") === 0
455+ || mimeType.search("application/x-ms-reader") === 0) {
456+ return ContentType.EBooks;
457+ } else if(mimeType.search("text/") === 0
458+ || mimeType.search("application/pdf") === 0
459+ || mimeType.search("application/x-pdf") === 0
460+ || mimeType.search("application/vnd\.pdf") === 0) {
461+ return ContentType.Documents;
462+ } else {
463+ return ContentType.Unknown;
464+ }
465+}
466
467=== added file 'qml/ThinProgressBar.qml'
468--- qml/ThinProgressBar.qml 1970-01-01 00:00:00 +0000
469+++ qml/ThinProgressBar.qml 2015-05-21 19:43:33 +0000
470@@ -0,0 +1,35 @@
471+/*
472+ * Copyright 2014 Canonical Ltd.
473+ *
474+ * This file is part of webbrowser-app.
475+ *
476+ * webbrowser-app is free software; you can redistribute it and/or modify
477+ * it under the terms of the GNU General Public License as published by
478+ * the Free Software Foundation; version 3.
479+ *
480+ * webbrowser-app is distributed in the hope that it will be useful,
481+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
482+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
483+ * GNU General Public License for more details.
484+ *
485+ * You should have received a copy of the GNU General Public License
486+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
487+ */
488+
489+import QtQuick 2.0
490+import Ubuntu.Components 1.1
491+
492+ProgressBar {
493+ property var webview
494+
495+ height: units.dp(3)
496+
497+ showProgressPercentage: false
498+ value: webview ? webview.loadProgress / 100 : 0.0
499+ visible: webview ? webview.loading
500+ // Workaround for https://bugs.launchpad.net/oxide/+bug/1290821.
501+ // Note: this also works with a QtWebKit webview by chance,
502+ // because !undefined evaluates to true.
503+ && !webview.lastLoadStopped
504+ : false
505+}
506
507=== added directory 'qml/UCSComponents'
508=== added file 'qml/UCSComponents/EmptyState.qml'
509--- qml/UCSComponents/EmptyState.qml 1970-01-01 00:00:00 +0000
510+++ qml/UCSComponents/EmptyState.qml 2015-05-21 19:43:33 +0000
511@@ -0,0 +1,43 @@
512+import QtQuick 2.0
513+import Ubuntu.Components 1.1
514+
515+/*
516+ Component which displays an empty state (approved by design). It offers an
517+ icon, title and subtitle to describe the empty state.
518+*/
519+
520+Item {
521+ id: emptyState
522+
523+ // Public APIs
524+ property alias iconName: emptyIcon.name
525+ property alias iconSource: emptyIcon.source
526+ property alias iconColor: emptyIcon.color
527+ property alias title: emptyLabel.text
528+ property alias subTitle: emptySublabel.text
529+
530+ height: childrenRect.height
531+
532+ Icon {
533+ id: emptyIcon
534+ anchors.horizontalCenter: parent.horizontalCenter
535+ height: units.gu(10)
536+ width: height
537+ color: "#BBBBBB"
538+ }
539+
540+ Label {
541+ id: emptyLabel
542+ anchors.top: emptyIcon.bottom
543+ anchors.topMargin: units.gu(5)
544+ anchors.horizontalCenter: parent.horizontalCenter
545+ fontSize: "large"
546+ font.bold: true
547+ }
548+
549+ Label {
550+ id: emptySublabel
551+ anchors.top: emptyLabel.bottom
552+ anchors.horizontalCenter: parent.horizontalCenter
553+ }
554+}
555
556=== added file 'qml/UCSComponents/RadialAction.qml'
557--- qml/UCSComponents/RadialAction.qml 1970-01-01 00:00:00 +0000
558+++ qml/UCSComponents/RadialAction.qml 2015-05-21 19:43:33 +0000
559@@ -0,0 +1,12 @@
560+import QtQuick 2.0
561+import Ubuntu.Components 1.1
562+
563+Action {
564+ property string iconName: "add"
565+ property string iconSource
566+ property color iconColor: "Black"
567+ property color backgroundColor: "White"
568+ property string text
569+ property bool top: false
570+ property bool enabled: true
571+}
572
573=== added file 'qml/UCSComponents/RadialBottomEdge.qml'
574--- qml/UCSComponents/RadialBottomEdge.qml 1970-01-01 00:00:00 +0000
575+++ qml/UCSComponents/RadialBottomEdge.qml 2015-05-21 19:43:33 +0000
576@@ -0,0 +1,251 @@
577+import QtQuick 2.0
578+import QtFeedback 5.0
579+import Ubuntu.Components 1.1
580+import QtGraphicalEffects 1.0
581+
582+Item {
583+ id: bottomEdge
584+
585+ property int hintSize: units.gu(8)
586+ property color hintColor: Theme.palette.normal.overlay
587+ property string hintIconName: "view-grid-symbolic"
588+ property alias hintIconSource: hintIcon.source
589+ property color hintIconColor: UbuntuColors.coolGrey
590+ property bool bottomEdgeEnabled: true
591+
592+ property real expandedPosition: 0.6 * height
593+ property real collapsedPosition: height - hintSize/2
594+
595+ property list<RadialAction> actions
596+ property real actionButtonSize: units.gu(7)
597+ property real actionButtonDistance: 1.5* hintSize
598+
599+ anchors.fill: parent
600+
601+ HapticsEffect {
602+ id: clickEffect
603+ attackIntensity: 0.0
604+ attackTime: 50
605+ intensity: 1.0
606+ duration: 10
607+ fadeTime: 50
608+ fadeIntensity: 0.0
609+ }
610+
611+ Rectangle {
612+ id: bgVisual
613+
614+ z: 1
615+ visible: bottomEdgeHint.y !== collapsedPosition
616+ color: "black"
617+ anchors.fill: parent
618+ opacity: 0.9 * (((bottomEdge.height - bottomEdgeHint.y) / bottomEdge.height) * 2)
619+
620+ MouseArea {
621+ anchors.fill: parent
622+ enabled: bgVisual.visible
623+ onClicked: bottomEdgeHint.state = "collapsed"
624+ z: 1
625+ }
626+
627+ }
628+
629+ Rectangle {
630+ id: bottomEdgeHint
631+
632+ color: hintColor
633+ width: hintSize
634+ height: width
635+ radius: width/2
636+ visible: bottomEdgeEnabled
637+
638+ anchors.horizontalCenter: parent.horizontalCenter
639+ y: collapsedPosition
640+ z: parent.z + 1
641+
642+ Rectangle {
643+ id: dropShadow
644+ width: parent.width
645+ height: parent.height
646+ border.color: "#B3B3B3"
647+ color: "Transparent"
648+ radius: parent.radius + 1
649+ z: -1
650+ anchors {
651+ centerIn: parent
652+ verticalCenterOffset: -1 //units.gu(-0.3)
653+ }
654+ }
655+
656+ Icon {
657+ id: hintIcon
658+ width: hintSize/4
659+ height: width
660+ name: hintIconName
661+ color: hintIconColor
662+ anchors {
663+ centerIn: parent
664+ verticalCenterOffset: width * ((bottomEdgeHint.y - expandedPosition)
665+ /(expandedPosition - collapsedPosition))
666+ }
667+ }
668+
669+ property real actionListDistance: -actionButtonDistance * ((bottomEdgeHint.y - collapsedPosition)
670+ /(collapsedPosition - expandedPosition))
671+
672+ Repeater {
673+ id: actionList
674+ model: actions
675+ delegate: Rectangle {
676+ id: actionDelegate
677+ readonly property real radAngle: (index % actionList.count * (360/actionList.count)) * Math.PI / 180
678+ property real distance: bottomEdgeHint.actionListDistance
679+ z: -1
680+ width: actionButtonSize
681+ height: width
682+ radius: width/2
683+ anchors.centerIn: parent
684+ color: modelData.backgroundColor
685+ transform: Translate {
686+ x: distance * Math.sin(radAngle)
687+ y: -distance * Math.cos(radAngle)
688+ }
689+
690+ Icon {
691+ id: icon
692+ anchors.centerIn: parent
693+ width: parent.width/2
694+ height: width
695+// name: !modelData.iconSource ? modelData.iconName : undefined
696+// source: modelData.iconSource ? Qt.resolvedUrl(modelData.iconSource) : undefined
697+ color: modelData.iconColor
698+ opacity: modelData.enabled ? 1.0 : 0.2
699+ Component.onCompleted: modelData.iconSource ? source = Qt.resolvedUrl(modelData.iconSource) : name = modelData.iconName
700+ }
701+
702+ Label {
703+ visible: text && bottomEdgeHint.state == "expanded"
704+ text: modelData.text
705+ anchors {
706+ top: !modelData.top ? icon.bottom : undefined
707+ topMargin: !modelData.top ? units.gu(3) : undefined
708+ bottom: modelData.top ? icon.top : undefined
709+ bottomMargin: modelData.top ? units.gu(3) : undefined
710+ horizontalCenter: icon.horizontalCenter
711+ }
712+ color: Theme.palette.normal.foregroundText
713+ font.bold: true
714+ fontSize: "medium"
715+
716+ }
717+
718+ MouseArea {
719+ anchors.fill: parent
720+ enabled: modelData.enabled
721+ onClicked: {
722+ clickEffect.start()
723+ bottomEdgeHint.state = "collapsed"
724+ modelData.triggered(null)
725+ }
726+ }
727+ }
728+ }
729+
730+ MouseArea {
731+ id: mouseArea
732+
733+ property real previousY: -1
734+ property string dragDirection: "None"
735+
736+ z: 1
737+ anchors.fill: parent
738+ visible: bottomEdgeEnabled
739+
740+ preventStealing: true
741+ drag {
742+ axis: Drag.YAxis
743+ target: bottomEdgeHint
744+ minimumY: expandedPosition
745+ maximumY: collapsedPosition
746+ }
747+
748+ onReleased: {
749+ if ((dragDirection === "BottomToTop") &&
750+ bottomEdgeHint.y < collapsedPosition) {
751+ bottomEdgeHint.state = "expanded"
752+ } else {
753+ if (bottomEdgeHint.state === "collapsed") {
754+ bottomEdgeHint.y = collapsedPosition
755+ }
756+ bottomEdgeHint.state = "collapsed"
757+ }
758+ previousY = -1
759+ dragDirection = "None"
760+ }
761+
762+ onClicked: {
763+ if (bottomEdgeHint.y === collapsedPosition)
764+ bottomEdgeHint.state = "expanded"
765+ else
766+ bottomEdgeHint.state = "collapsed"
767+ }
768+
769+ onPressed: {
770+ previousY = bottomEdgeHint.y
771+ }
772+
773+ onMouseYChanged: {
774+ var yOffset = previousY - bottomEdgeHint.y
775+ if (Math.abs(yOffset) <= units.gu(2)) {
776+ return
777+ }
778+ previousY = bottomEdgeHint.y
779+ dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
780+ }
781+ }
782+
783+ state: "collapsed"
784+ states: [
785+ State {
786+ name: "collapsed"
787+ PropertyChanges {
788+ target: bottomEdgeHint
789+ y: collapsedPosition
790+ }
791+ },
792+ State {
793+ name: "expanded"
794+ PropertyChanges {
795+ target: bottomEdgeHint
796+ y: expandedPosition
797+ }
798+ },
799+
800+ State {
801+ name: "floating"
802+ when: mouseArea.drag.active
803+ }
804+ ]
805+
806+ transitions: [
807+ Transition {
808+ to: "expanded"
809+ SpringAnimation {
810+ target: bottomEdgeHint
811+ property: "y"
812+ spring: 2
813+ damping: 0.2
814+ }
815+ },
816+
817+ Transition {
818+ to: "collapsed"
819+ SmoothedAnimation {
820+ target: bottomEdgeHint
821+ property: "y"
822+ duration: UbuntuAnimation.BriskDuration
823+ }
824+ }
825+ ]
826+ }
827+}
828
829=== added directory 'sounds'
830=== added file 'sounds/Click.wav'
831Binary files sounds/Click.wav 1970-01-01 00:00:00 +0000 and sounds/Click.wav 2015-05-21 19:43:33 +0000 differ

Subscribers

People subscribed via source and target branches

to status/vote changes: