Merge lp:~ogra/junk/alternate-webapp-container into lp:junk
- alternate-webapp-container
- Merge into main
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
David Futcher | Pending | ||
Review via email: mp+259843@code.launchpad.net |
Commit message
Description of the change
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' |
99 | Binary 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' |
831 | Binary files sounds/Click.wav 1970-01-01 00:00:00 +0000 and sounds/Click.wav 2015-05-21 19:43:33 +0000 differ |