Merge lp:~verzegnassi-stefano/openstore/redesign into lp:openstore
- redesign
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 23 |
Proposed branch: | lp:~verzegnassi-stefano/openstore/redesign |
Merge into: | lp:openstore |
Diff against target: |
2042 lines (+1377/-433) 13 files modified
manifest.json.in (+1/-1) openstore/AppDetailsPage.qml (+542/-387) openstore/CategoriesPage.qml (+60/-0) openstore/DiscoverTab.qml (+165/-0) openstore/EmptyState.qml (+74/-0) openstore/FilteredAppView.qml (+108/-0) openstore/Main.qml (+188/-44) openstore/SearchPage.qml (+87/-0) openstore/SectionDivider.qml (+52/-0) openstore/TextualButtonStyle.qml (+67/-0) openstore/appmodel.cpp (+15/-0) openstore/appmodel.h (+11/-1) openstore/openstore.qrc (+7/-0) |
To merge this branch: | bzr merge lp:~verzegnassi-stefano/openstore/redesign |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Zanetti | Needs Information | ||
Review via email: mp+323619@code.launchpad.net |
Commit message
Full UI redesign of the application:
* Added a 'Discover' tab
* Added a 'My Apps' tab
* Search and categories
* Redesigned AppDetails page
Description of the change
Full UI redesign of the application:
* Added a 'Discover' tab
* Added a 'My Apps' tab
* Search and categories
* Redesigned AppDetails page
Please note in DiscoverTab.qml that data are fetched from:
https:/
- 29. By Stefano Verzegnassi
-
Bump SDK version, we're using now 'automaticHeight' property in UITK Headers
- 30. By Stefano Verzegnassi
-
DiscoverTab app tiles: Show label if application is already installed or an update is available
- 31. By Stefano Verzegnassi
-
Fixed a few visual issues in AppDetails page, and be sure we don't get false positives for the 'unconfined' alert
Stefano Verzegnassi (verzegnassi-stefano) wrote : | # |
Ok, first 2 questions are up to you guys. I just kept the file I uploaded on my gist since I got no other input.
Answering to the third question, well, that's something I forgot to remove when I've been testing the app on the desktop. I'm on xenial and click tools are a bit broken: I did a few changes to the code in order to make OpenStore believe I have some package installed. Forgot to remove those few bits. :P
- 32. By Stefano Verzegnassi
-
Remove i18n from printSize
- 33. By Stefano Verzegnassi
-
Remove dirty bits
- 34. By Stefano Verzegnassi
-
Remove 'Packager/
Publisher' entry from AppDetailsPage - 35. By Stefano Verzegnassi
-
Use the brand new Discover API from the OpenStore server
- 36. By Stefano Verzegnassi
-
Use the brand new Categories API from the OpenStore server
- 37. By Stefano Verzegnassi
-
Move categories GET request in Main.qml, since it makes no sense to download definitions every time we open the hamburger menu. Also, there's the risk that categories are not in sync with the repo manifest
- 38. By Stefano Verzegnassi
-
The icon is now called 'find', not 'search'...
- 39. By Stefano Verzegnassi
-
Re-add 'packager/
publisher' field
Preview Diff
1 | === modified file 'manifest.json.in' |
2 | --- manifest.json.in 2016-08-24 13:25:35 +0000 |
3 | +++ manifest.json.in 2017-05-08 18:42:06 +0000 |
4 | @@ -13,5 +13,5 @@ |
5 | }, |
6 | "version": "0.103", |
7 | "maintainer": "OpenStore Team <openstore-team@lists.launchpad.net>", |
8 | - "framework": "ubuntu-sdk-15.04.3" |
9 | + "framework": "ubuntu-sdk-15.04.6" |
10 | } |
11 | |
12 | === modified file 'openstore/AppDetailsPage.qml' |
13 | --- openstore/AppDetailsPage.qml 2016-08-24 13:25:35 +0000 |
14 | +++ openstore/AppDetailsPage.qml 2017-05-08 18:42:06 +0000 |
15 | @@ -22,417 +22,572 @@ |
16 | |
17 | |
18 | Page { |
19 | + id: appDetailsPage |
20 | header: PageHeader { |
21 | title: app ? app.name : "App details" |
22 | + automaticHeight: false |
23 | } |
24 | |
25 | property var app: null |
26 | |
27 | |
28 | - Flickable { |
29 | + ScrollView { |
30 | + id: scrollView |
31 | anchors.fill: parent |
32 | anchors.topMargin: parent.header.height |
33 | - contentHeight: mainColumn.height + units.gu(2) |
34 | - interactive: contentHeight > height - topMargin |
35 | |
36 | Column { |
37 | id: mainColumn |
38 | - anchors { left: parent.left; top: parent.top; right: parent.right } |
39 | - anchors.margins: units.gu(2) |
40 | - spacing: units.gu(1) |
41 | - height: childrenRect.height |
42 | - |
43 | - RowLayout { |
44 | - anchors { left: parent.left; right: parent.right } |
45 | - height: units.gu(10) |
46 | - spacing: units.gu(1) |
47 | - |
48 | - UbuntuShape { |
49 | - Layout.fillHeight: true |
50 | - Layout.preferredWidth: height |
51 | - |
52 | - image: Image { |
53 | + width: scrollView.width |
54 | + //spacing: units.gu(1) |
55 | + |
56 | + ListItem { |
57 | + height: units.gu(16) |
58 | + |
59 | + ListItemLayout { |
60 | + anchors.fill: parent |
61 | + title.text: app.name |
62 | + subtitle.text: app.author |
63 | + summary.text: printSize(app.fileSize) |
64 | + |
65 | + UbuntuShape { |
66 | + SlotsLayout.position: SlotsLayout.Leading |
67 | + width: units.gu(12); height: width |
68 | + aspect: UbuntuShape.Flat |
69 | + |
70 | + image: Image { |
71 | + height: parent.height |
72 | + width: parent.width |
73 | + source: app ? app.icon : "" |
74 | + } |
75 | + } |
76 | + } |
77 | + } |
78 | + |
79 | + ListItem { |
80 | + height: units.gu(8) |
81 | + |
82 | + RowLayout { |
83 | + id: buttonsRow |
84 | + anchors.fill: parent |
85 | + anchors.margins: units.gu(2) |
86 | + spacing: units.gu(2) |
87 | + visible: !appModel.installer.busy |
88 | + |
89 | + Button { |
90 | + Layout.fillWidth: true |
91 | + text: app.installed ? i18n.tr("Upgrade") : i18n.tr("Install") |
92 | + visible: !app.installed || (app.installed && app.installedVersion < app.version) |
93 | + color: UbuntuColors.green |
94 | + onClicked: { |
95 | + appModel.installer.installPackage(app.packageUrl) |
96 | + } |
97 | + } |
98 | + |
99 | + Button { |
100 | + Layout.fillWidth: true |
101 | + text: i18n.tr("Remove") |
102 | + visible: app.installed |
103 | + color: UbuntuColors.red |
104 | + onClicked: { |
105 | + appModel.installer.removePackage(app.appId, app.version) |
106 | + } |
107 | + } |
108 | + } |
109 | + |
110 | + ProgressBar { |
111 | + id: installerProgressBar |
112 | + anchors { |
113 | + left: parent.left; leftMargin: units.gu(2) |
114 | + right: parent.right; rightMargin: units.gu(2) |
115 | + verticalCenter: parent.verticalCenter |
116 | + } |
117 | + maximumValue: app ? app.fileSize : 0 |
118 | + value: appModel.installer.downloadProgress |
119 | + visible: appModel.installer.busy |
120 | + indeterminate: appModel.installer.downloadProgress == 0 |
121 | + } |
122 | + } |
123 | + |
124 | + ListItem { |
125 | + visible: { |
126 | + for (var i=0; i<app.hooksCount; ++i) { |
127 | + if (includesUnconfinedLocations(app.readPaths(i))) |
128 | + return true |
129 | + if (includesUnconfinedLocations(app.writePaths(i))) |
130 | + return true |
131 | + if (app.apparmorTemplate(i).indexOf("unconfined") >= 0) |
132 | + return true |
133 | + } |
134 | + return false |
135 | + } |
136 | + ListItemLayout { |
137 | + anchors.centerIn: parent |
138 | + subtitle.text: i18n.tr("This software requires extra privileges. See below for details.") |
139 | + subtitle.color: UbuntuColors.red |
140 | + subtitle.maximumLineCount: 2 |
141 | + subtitle.wrapMode: Text.WordWrap |
142 | + |
143 | + Icon { |
144 | + SlotsLayout.position: SlotsLayout.Leading |
145 | + width: units.gu(4); height: width |
146 | + name: "security-alert" |
147 | + } |
148 | + } |
149 | + } |
150 | + |
151 | + ListItem { |
152 | + height: units.gu(32) |
153 | + visible: screenshotsView.count |
154 | + |
155 | + ListView { |
156 | + id: screenshotsView |
157 | + anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } |
158 | + leftMargin: units.gu(2) |
159 | + rightMargin: units.gu(2) |
160 | + clip: true |
161 | + height: count > 0 ? units.gu(24) : 0 |
162 | + visible: count > 0 |
163 | + spacing: units.gu(1) |
164 | + orientation: ListView.Horizontal |
165 | + model: app.screenshots |
166 | + delegate: UbuntuShape { |
167 | height: parent.height |
168 | - width: parent.width |
169 | - source: app ? app.icon : "" |
170 | - } |
171 | - } |
172 | - |
173 | - ColumnLayout { |
174 | - Layout.fillWidth: true |
175 | - Layout.fillHeight: true |
176 | - spacing: units.gu(1) |
177 | - Label { |
178 | - text: app.name |
179 | - Layout.fillWidth: true |
180 | - fontSize: "large" |
181 | - } |
182 | - Label { |
183 | - text: app.author |
184 | - Layout.fillWidth: true |
185 | - } |
186 | - } |
187 | - } |
188 | - ThinDivider { } |
189 | - Label { |
190 | - anchors { left: parent.left; right: parent.right } |
191 | - text: app.tagline |
192 | - wrapMode: Text.WordWrap |
193 | - } |
194 | - |
195 | - ListView { |
196 | - anchors { left: parent.left; right: parent.right; margins: -units.gu(2) } |
197 | - leftMargin: units.gu(2) |
198 | - rightMargin: units.gu(2) |
199 | - clip: true |
200 | - height: count > 0 ? units.gu(20) : 0 |
201 | - visible: count > 0 |
202 | - spacing: units.gu(1) |
203 | - orientation: ListView.Horizontal |
204 | - model: app.screenshots |
205 | - delegate: UbuntuShape { |
206 | - height: parent.height |
207 | - // sh : lv.h = sw : x |
208 | - width: screenshot.sourceSize.width * height / screenshot.sourceSize.height |
209 | - sourceFillMode: UbuntuShape.PreserveAspectFit |
210 | - source: Image { |
211 | - id: screenshot |
212 | - source: modelData |
213 | - } |
214 | - |
215 | - AbstractButton { |
216 | - id: screenShotButton |
217 | - anchors.fill: parent |
218 | - onClicked: { |
219 | - print("opening at:", screenShotButton.mapToItem(root, 0, 0).x) |
220 | - zoomIn.createObject(root, {x: screenShotButton.mapToItem(root, 0, 0).x, y: screenShotButton.mapToItem(root, 0, 0).y, itemScale: screenShotButton.height / root.height, imageSource: modelData}); |
221 | -// zoomIn.createObject(root, {x: 100, y: 100}); |
222 | - } |
223 | - } |
224 | - |
225 | - Component { |
226 | - id: zoomIn |
227 | - Rectangle { |
228 | - id: zI |
229 | - width: parent.width |
230 | - height: parent.height |
231 | - color: "black" |
232 | - |
233 | - property real itemScale: 1 |
234 | - property string imageSource |
235 | - transform: Scale { |
236 | - origin.x: 0 |
237 | - origin.y: 0 |
238 | - xScale: zI.itemScale |
239 | - yScale: zI.itemScale |
240 | - } |
241 | - |
242 | - ParallelAnimation { |
243 | - id: scaleInAnimation |
244 | - onStarted: { |
245 | - hideAnimation.initialScale = itemScale; |
246 | - hideAnimation.initialX = x; |
247 | - hideAnimation.initialY = y; |
248 | - } |
249 | - |
250 | - UbuntuNumberAnimation { target: zI; property: "itemScale"; to: 1 } |
251 | - UbuntuNumberAnimation { target: zI; properties: "x,y"; to: 0 } |
252 | - } |
253 | - |
254 | - |
255 | - Component.onCompleted: { |
256 | - scaleInAnimation.start(); |
257 | - } |
258 | - |
259 | - Image { |
260 | - anchors.fill: parent |
261 | - source: zI.imageSource |
262 | - fillMode: Image.PreserveAspectFit |
263 | - } |
264 | - |
265 | - AbstractButton { |
266 | - anchors.fill: parent |
267 | - onClicked: { |
268 | - hideAnimation.start() |
269 | - } |
270 | - } |
271 | - |
272 | - ParallelAnimation { |
273 | - id: hideAnimation |
274 | - property real initialScale: 1 |
275 | - property int initialX: 0 |
276 | - property int initialY: 0 |
277 | - |
278 | - |
279 | - UbuntuNumberAnimation { target: zI; property: "itemScale"; to: hideAnimation.initialScale } |
280 | - UbuntuNumberAnimation { target: zI; property: "x"; to: hideAnimation.initialX } |
281 | - UbuntuNumberAnimation { target: zI; property: "y"; to: hideAnimation.initialY } |
282 | - onStopped: { |
283 | - script: zI.destroy() |
284 | - } |
285 | - |
286 | - } |
287 | - } |
288 | - } |
289 | - } |
290 | - } |
291 | - |
292 | - Label { |
293 | - anchors { left: parent.left; right: parent.right } |
294 | - text: app.description |
295 | - wrapMode: Text.WordWrap |
296 | - } |
297 | - |
298 | - ThinDivider { } |
299 | - |
300 | - Label { |
301 | - text: "<b>Packager/Publisher:</b> " + (app.maintainer ? app.maintainer : "Openstore team") |
302 | - } |
303 | - |
304 | - Label { |
305 | - anchors { left: parent.left; right: parent.right } |
306 | - text: "Installed version: " + (app.installedVersion ? app.installedVersion : "None") |
307 | - wrapMode: Text.WordWrap |
308 | - } |
309 | - |
310 | - Label { |
311 | - anchors { left: parent.left; right: parent.right } |
312 | - text: "Latest available version: " + app.version |
313 | - wrapMode: Text.WordWrap |
314 | - } |
315 | - |
316 | - Label { |
317 | - anchors { left: parent.left; right: parent.right } |
318 | - text: "Changelog:" |
319 | - wrapMode: Text.WordWrap |
320 | - visible: app.changelog |
321 | - } |
322 | - |
323 | - Label { |
324 | - anchors { left: parent.left; right: parent.right } |
325 | - text: app.changelog |
326 | - wrapMode: Text.WordWrap |
327 | - visible: app.changelog |
328 | - } |
329 | - |
330 | - ThinDivider {} |
331 | - |
332 | - ProgressBar { |
333 | - Layout.fillWidth: true |
334 | - maximumValue: app ? app.fileSize : 0 |
335 | - value: appModel.installer.downloadProgress |
336 | - visible: appModel.installer.busy |
337 | - indeterminate: appModel.installer.downloadProgress == 0 |
338 | - } |
339 | - |
340 | - RowLayout { |
341 | - width: parent.width |
342 | - spacing: units.gu(1) |
343 | - visible: !appModel.installer.busy |
344 | - |
345 | - Button { |
346 | - text: app.installed ? "Upgrade" : "Install" |
347 | - visible: !app.installed || (app.installed && app.installedVersion < app.version) |
348 | - color: "#DD4814" |
349 | - onClicked: { |
350 | - appModel.installer.installPackage(app.packageUrl) |
351 | - } |
352 | - } |
353 | - |
354 | - Button { |
355 | - text: "Remove" |
356 | - visible: app.installed |
357 | - color: UbuntuColors.red |
358 | - onClicked: { |
359 | - appModel.installer.removePackage(app.appId, app.version) |
360 | - } |
361 | - } |
362 | - } |
363 | - |
364 | - ThinDivider {} |
365 | - |
366 | - Label { |
367 | - anchors { left: parent.left; right: parent.right } |
368 | - text: "Package contents:" |
369 | - font.bold: true |
370 | + // sh : lv.h = sw : x |
371 | + width: screenshot.sourceSize.width * height / screenshot.sourceSize.height |
372 | + aspect: UbuntuShape.Flat |
373 | + sourceFillMode: UbuntuShape.PreserveAspectFit |
374 | + source: Image { |
375 | + id: screenshot |
376 | + source: modelData |
377 | + smooth: true |
378 | + antialiasing: true |
379 | + } |
380 | + |
381 | + AbstractButton { |
382 | + id: screenShotButton |
383 | + anchors.fill: parent |
384 | + onClicked: { |
385 | + print("opening at:", screenShotButton.mapToItem(root, 0, 0).x) |
386 | + zoomIn.createObject(root, {x: screenShotButton.mapToItem(root, 0, 0).x, y: screenShotButton.mapToItem(root, 0, 0).y, itemScale: screenShotButton.height / root.height, imageSource: modelData}); |
387 | + // zoomIn.createObject(root, {x: 100, y: 100}); |
388 | + } |
389 | + } |
390 | + |
391 | + Component { |
392 | + id: zoomIn |
393 | + Rectangle { |
394 | + id: zI |
395 | + width: parent.width |
396 | + height: parent.height |
397 | + color: "black" |
398 | + |
399 | + property real itemScale: 1 |
400 | + property string imageSource |
401 | + transform: Scale { |
402 | + origin.x: 0 |
403 | + origin.y: 0 |
404 | + xScale: zI.itemScale |
405 | + yScale: zI.itemScale |
406 | + } |
407 | + |
408 | + ParallelAnimation { |
409 | + id: scaleInAnimation |
410 | + onStarted: { |
411 | + hideAnimation.initialScale = itemScale; |
412 | + hideAnimation.initialX = x; |
413 | + hideAnimation.initialY = y; |
414 | + } |
415 | + |
416 | + UbuntuNumberAnimation { target: zI; property: "itemScale"; to: 1 } |
417 | + UbuntuNumberAnimation { target: zI; properties: "x,y"; to: 0 } |
418 | + } |
419 | + |
420 | + |
421 | + Component.onCompleted: { |
422 | + scaleInAnimation.start(); |
423 | + } |
424 | + |
425 | + Image { |
426 | + anchors.fill: parent |
427 | + source: zI.imageSource |
428 | + fillMode: Image.PreserveAspectFit |
429 | + } |
430 | + |
431 | + AbstractButton { |
432 | + anchors.fill: parent |
433 | + onClicked: { |
434 | + hideAnimation.start() |
435 | + } |
436 | + } |
437 | + |
438 | + ParallelAnimation { |
439 | + id: hideAnimation |
440 | + property real initialScale: 1 |
441 | + property int initialX: 0 |
442 | + property int initialY: 0 |
443 | + |
444 | + |
445 | + UbuntuNumberAnimation { target: zI; property: "itemScale"; to: hideAnimation.initialScale } |
446 | + UbuntuNumberAnimation { target: zI; property: "x"; to: hideAnimation.initialX } |
447 | + UbuntuNumberAnimation { target: zI; property: "y"; to: hideAnimation.initialY } |
448 | + onStopped: { |
449 | + script: zI.destroy() |
450 | + } |
451 | + |
452 | + } |
453 | + } |
454 | + } |
455 | + } |
456 | + } |
457 | + } |
458 | + |
459 | + ListItem { |
460 | + height: descLayout.height |
461 | + onClicked: descLayout.showAll = !descLayout.showAll |
462 | + ListItemLayout { |
463 | + id: descLayout |
464 | + property bool showAll: false |
465 | + title.text: app.tagline || i18n.tr("Description") |
466 | + subtitle.text: app.description |
467 | + subtitle.textSize: Label.Small |
468 | + subtitle.wrapMode: Text.WordWrap |
469 | + subtitle.maximumLineCount: showAll ? Number.MAX_VALUE : 5 |
470 | + |
471 | + Icon { |
472 | + width: units.gu(2); height: width |
473 | + SlotsLayout.position: SlotsLayout.Last |
474 | + name: descLayout.showAll ? "go-up" : "go-down" |
475 | + } |
476 | + } |
477 | + } |
478 | + |
479 | + ListItem { |
480 | + height: changelogLayout.height |
481 | + visible: app.changelog |
482 | + onClicked: changelogLayout.showAll = !changelogLayout.showAll |
483 | + ListItemLayout { |
484 | + id: changelogLayout |
485 | + property bool showAll: false |
486 | + title.text: i18n.tr("What's New") |
487 | + subtitle.text: app.changelog |
488 | + subtitle.textSize: Label.Small |
489 | + subtitle.wrapMode: Text.WordWrap |
490 | + subtitle.maximumLineCount: showAll ? Number.MAX_VALUE : 5 |
491 | + |
492 | + Icon { |
493 | + width: units.gu(2); height: width |
494 | + SlotsLayout.position: SlotsLayout.Last |
495 | + name: changelogLayout.showAll ? "go-up" : "go-down" |
496 | + } |
497 | + } |
498 | + } |
499 | + |
500 | + ListItem { |
501 | + divider.visible: false |
502 | + ListItemLayout { |
503 | + anchors.centerIn: parent |
504 | + title.text: i18n.tr("Packager/Publisher") |
505 | + subtitle.text: app.maintainer || i18n.tr("OpenStore team") |
506 | + } |
507 | + } |
508 | + |
509 | + ListItem { |
510 | + divider.visible: false |
511 | + ListItemLayout { |
512 | + anchors.centerIn: parent |
513 | + title.text: i18n.tr("Installed version") |
514 | + subtitle.text: app.installedVersion || "None" |
515 | + } |
516 | + } |
517 | + |
518 | + ListItem { |
519 | + divider.visible: false |
520 | + ListItemLayout { |
521 | + anchors.centerIn: parent |
522 | + |
523 | + title.text: i18n.tr("Latest available version") |
524 | + subtitle.text: app.version |
525 | + } |
526 | + } |
527 | + |
528 | + ListItem { |
529 | + divider.visible: false |
530 | + ListItemLayout { |
531 | + anchors.centerIn: parent |
532 | + title.text: i18n.tr("License") || i18n.tr("<i>N/A</i>") |
533 | + subtitle.text: app.license |
534 | + } |
535 | + } |
536 | + |
537 | + ListItem { |
538 | + onClicked: Qt.openUrlExternally(app.source) |
539 | + ListItemLayout { |
540 | + anchors.centerIn: parent |
541 | + title.text: i18n.tr("Source Code") || i18n.tr("<i>N/A</i>") |
542 | + subtitle.text: app.source |
543 | + ProgressionSlot { visible: app.source } |
544 | + } |
545 | + } |
546 | + |
547 | + ListItem { |
548 | + onClicked: { |
549 | + // FIXME: I don't like this heuristic, but there's no other way to get a reference |
550 | + // to the page that pushed 'appDetailsPage' into the stack. |
551 | + // The parent node is actually the PageWrapper that created this page, 'parentPage' is one of its properties. |
552 | + //var realParentPage = appDetailsPage.parentNode.parentPage |
553 | + |
554 | + var pageProps = { |
555 | + title: app.author, |
556 | + filterPattern: new RegExp(app.author), |
557 | + filterProperty: "author" |
558 | + } |
559 | + |
560 | + appDetailsPage.pageStack.addPageToCurrentColumn(/*realParentPage*/ appDetailsPage, filteredAppPageComponent, pageProps) |
561 | + } |
562 | + ListItemLayout { |
563 | + anchors.centerIn: parent |
564 | + title.text: i18n.tr("More from %1").arg(app.author) |
565 | + ProgressionSlot {} |
566 | + } |
567 | + } |
568 | + |
569 | + ListItem { |
570 | + onClicked: { |
571 | + // FIXME: I don't like this heuristic, but there's no other way to get a reference |
572 | + // to the page that pushed 'appDetailsPage' into the stack. |
573 | + // The parent node is actually the PageWrapper that created this page, 'parentPage' is one of its properties. |
574 | + //var realParentPage = appDetailsPage.parentNode.parentPage |
575 | + |
576 | + var pageProps = { |
577 | + title: app.category, |
578 | + filterPattern: new RegExp(app.category), |
579 | + filterProperty: "category" |
580 | + } |
581 | + |
582 | + appDetailsPage.pageStack.addPageToCurrentColumn(/*realParentPage*/ appDetailsPage, filteredAppPageComponent, pageProps) |
583 | + } |
584 | + ListItemLayout { |
585 | + anchors.centerIn: parent |
586 | + // FIXME: app.category is not localized. |
587 | + title.text: i18n.tr("Other apps in %1").arg(app.category) |
588 | + ProgressionSlot {} |
589 | + } |
590 | + } |
591 | + |
592 | + SectionDivider { |
593 | + text: i18n.tr("Package contents") |
594 | } |
595 | |
596 | Repeater { |
597 | model: app.hooksCount |
598 | |
599 | - delegate: Column { |
600 | - width: parent.width |
601 | + delegate: ListItem { |
602 | + height: hookDelLayout.height + units.gu(3) |
603 | + |
604 | property var hooks: app.hooks(index) |
605 | property string permissions: app.permissions(index) |
606 | property string readpaths: app.readPaths(index) |
607 | property string writepaths: app.writePaths(index) |
608 | property string hookName: app.hookName(index) |
609 | property string apparmorTemplate: app.apparmorTemplate(index) |
610 | - spacing: units.gu(1) |
611 | - |
612 | - RowLayout { |
613 | - width: parent.width |
614 | - |
615 | - Label { |
616 | - text: hookName |
617 | - Layout.fillWidth: true |
618 | - font.bold: true |
619 | - } |
620 | - HookIcon { |
621 | - Layout.preferredHeight: units.gu(4) |
622 | - Layout.preferredWidth: units.gu(4) |
623 | - name: "stock_application" |
624 | - visible: (hooks & ApplicationItem.HookDesktop) |
625 | - } |
626 | - HookIcon { |
627 | - Layout.preferredHeight: units.gu(4) |
628 | - Layout.preferredWidth: units.gu(4) |
629 | - name: "search" |
630 | - visible: (hooks & ApplicationItem.HookScope) |
631 | - } |
632 | - HookIcon { |
633 | - Layout.preferredHeight: units.gu(4) |
634 | - Layout.preferredWidth: units.gu(4) |
635 | - name: "stock_website" |
636 | - visible: (hooks & ApplicationItem.HookUrls) |
637 | - } |
638 | - HookIcon { |
639 | - Layout.preferredHeight: units.gu(4) |
640 | - Layout.preferredWidth: units.gu(4) |
641 | - name: "share" |
642 | - visible: (hooks & ApplicationItem.HookContentHub) |
643 | - } |
644 | - HookIcon { |
645 | - Layout.preferredHeight: units.gu(4) |
646 | - Layout.preferredWidth: units.gu(4) |
647 | - name: "notification" |
648 | - visible: (hooks & ApplicationItem.HookPushHelper) |
649 | - } |
650 | - HookIcon { |
651 | - Layout.preferredHeight: units.gu(4) |
652 | - Layout.preferredWidth: units.gu(4) |
653 | - name: "contact-group" |
654 | - visible: (hooks & ApplicationItem.HookAccountService) |
655 | - } |
656 | - } |
657 | - RowLayout { |
658 | - anchors { left: parent.left; right: parent.right } |
659 | - spacing: units.gu(1) |
660 | - Icon { |
661 | - Layout.preferredHeight: units.gu(3) |
662 | - Layout.preferredWidth: units.gu(3) |
663 | - implicitHeight: height |
664 | - implicitWidth: width |
665 | - name: "security-alert" |
666 | - visible: apparmorTemplate.indexOf("unconfined") >= 0 |
667 | - } |
668 | - |
669 | - Label { |
670 | - id: templateLabel |
671 | - Layout.fillWidth: true |
672 | - text: "Apparmor profile: " + apparmorTemplate |
673 | - visible: apparmorTemplate |
674 | - color: apparmorTemplate.indexOf("unconfined") >= 0 ? UbuntuColors.red : permissionLabel.color |
675 | - anchors.verticalCenter: parent.verticalCenter |
676 | - } |
677 | - } |
678 | - |
679 | - |
680 | - Row { |
681 | - anchors { left: parent.left; right: parent.right } |
682 | - spacing: units.gu(1) |
683 | - visible: permissions.length > 0 |
684 | - |
685 | - Icon { |
686 | - Layout.preferredHeight: units.gu(3) |
687 | - Layout.preferredWidth: units.gu(3) |
688 | - implicitHeight: height |
689 | - implicitWidth: width |
690 | - name: "security-alert" |
691 | - } |
692 | - |
693 | - Label { |
694 | - id: permissionLabel |
695 | - text: "Permissions: " + (permissions ? permissions : "<i>none</i>") |
696 | + |
697 | + Column { |
698 | + id: hookDelLayout |
699 | + anchors { left: parent.left; right: parent.right; margins: units.gu(2) } |
700 | + y: units.gu(1) |
701 | + spacing: units.gu(1) |
702 | + |
703 | + RowLayout { |
704 | width: parent.width |
705 | - wrapMode: Text.WordWrap |
706 | - anchors.verticalCenter: parent.verticalCenter |
707 | - } |
708 | - } |
709 | - |
710 | - RowLayout { |
711 | - anchors { left: parent.left; right: parent.right } |
712 | - spacing: units.gu(1) |
713 | - visible: readpaths.length > 0 |
714 | - Icon { |
715 | - Layout.preferredHeight: units.gu(3) |
716 | - Layout.preferredWidth: units.gu(3) |
717 | - implicitHeight: height |
718 | - implicitWidth: width |
719 | - name: "security-alert" |
720 | - } |
721 | - Label { |
722 | - text: "Read paths: " + readpaths |
723 | - Layout.fillWidth: true |
724 | - wrapMode: Text.WordWrap |
725 | - anchors.verticalCenter: parent.verticalCenter |
726 | - } |
727 | - } |
728 | - RowLayout { |
729 | - anchors { left: parent.left; right: parent.right } |
730 | - spacing: units.gu(1) |
731 | - visible: writepaths.length > 0 |
732 | - Icon { |
733 | - Layout.preferredHeight: units.gu(3) |
734 | - Layout.preferredWidth: units.gu(3) |
735 | - implicitHeight: height |
736 | - implicitWidth: width |
737 | - name: "security-alert" |
738 | - } |
739 | - |
740 | - Label { |
741 | - text: "Write paths: " + writepaths |
742 | - Layout.fillWidth: true |
743 | - wrapMode: Text.WordWrap |
744 | - anchors.verticalCenter: parent.verticalCenter |
745 | - } |
746 | - } |
747 | - |
748 | - Button { |
749 | - anchors { left: parent.left } |
750 | - text: "Open" |
751 | - color: UbuntuColors.green |
752 | - visible: app.installed && (hooks & ApplicationItem.HookDesktop) |
753 | - onClicked: Qt.openUrlExternally("appid://" + app.appId + "/" + hookName + "/" + app.installedVersion) |
754 | - } |
755 | - } |
756 | - } |
757 | - ThinDivider { } |
758 | - |
759 | - Label { |
760 | - anchors { left: parent.left; right: parent.right } |
761 | - text: "License: " + app.license |
762 | - wrapMode: Text.WordWrap |
763 | - } |
764 | - Label { |
765 | - anchors { left: parent.left; right: parent.right } |
766 | - text: "Source code:" |
767 | - wrapMode: Text.WordWrap |
768 | - } |
769 | - AbstractButton { |
770 | - anchors { left: parent.left; right: parent.right } |
771 | - height: linkLabel.implicitHeight |
772 | - Label { |
773 | - id: linkLabel |
774 | - anchors { left: parent.left; right: parent.right } |
775 | - text: app.source |
776 | - wrapMode: Text.WordWrap |
777 | - color: "blue" |
778 | - } |
779 | - onClicked: { |
780 | - Qt.openUrlExternally(app.source) |
781 | - } |
782 | - } |
783 | - } |
784 | + height: units.gu(4) |
785 | + |
786 | + Label { |
787 | + text: hookName |
788 | + Layout.fillWidth: true |
789 | + } |
790 | + |
791 | + HookIcon { |
792 | + Layout.preferredHeight: units.gu(4) |
793 | + Layout.preferredWidth: units.gu(4) |
794 | + name: "stock_application" |
795 | + visible: (hooks & ApplicationItem.HookDesktop) |
796 | + } |
797 | + HookIcon { |
798 | + Layout.preferredHeight: units.gu(4) |
799 | + Layout.preferredWidth: units.gu(4) |
800 | + name: "search" |
801 | + visible: (hooks & ApplicationItem.HookScope) |
802 | + } |
803 | + HookIcon { |
804 | + Layout.preferredHeight: units.gu(4) |
805 | + Layout.preferredWidth: units.gu(4) |
806 | + name: "stock_website" |
807 | + visible: (hooks & ApplicationItem.HookUrls) |
808 | + } |
809 | + HookIcon { |
810 | + Layout.preferredHeight: units.gu(4) |
811 | + Layout.preferredWidth: units.gu(4) |
812 | + name: "share" |
813 | + visible: (hooks & ApplicationItem.HookContentHub) |
814 | + } |
815 | + HookIcon { |
816 | + Layout.preferredHeight: units.gu(4) |
817 | + Layout.preferredWidth: units.gu(4) |
818 | + name: "notification" |
819 | + visible: (hooks & ApplicationItem.HookPushHelper) |
820 | + } |
821 | + HookIcon { |
822 | + Layout.preferredHeight: units.gu(4) |
823 | + Layout.preferredWidth: units.gu(4) |
824 | + name: "contact-group" |
825 | + visible: (hooks & ApplicationItem.HookAccountService) |
826 | + } |
827 | + } |
828 | + |
829 | + ListItemLayout { |
830 | + anchors { left: parent.left; right: parent.right } |
831 | + anchors.leftMargin: units.gu(-2) |
832 | + height: units.gu(6) |
833 | + Icon { |
834 | + SlotsLayout.position: SlotsLayout.Leading |
835 | + width: units.gu(4); height: width |
836 | + name: "security-alert" |
837 | + visible: apparmorTemplate.indexOf("unconfined") >= 0 |
838 | + } |
839 | + |
840 | + title.text: i18n.tr("AppArmor profile") |
841 | + subtitle.text: apparmorTemplate || "Ubuntu confined app" |
842 | + subtitle.color: apparmorTemplate.indexOf("unconfined") >= 0 ? UbuntuColors.red : theme.palette.normal.backgroundSecondaryText |
843 | + subtitle.maximumLineCount: Number.MAX_VALUE |
844 | + } |
845 | + |
846 | + |
847 | + ListItemLayout { |
848 | + anchors { left: parent.left; right: parent.right } |
849 | + anchors.leftMargin: units.gu(-2) |
850 | + height: units.gu(6) |
851 | + visible: permissions.length > 0 |
852 | + |
853 | + Icon { |
854 | + property var restrictedPerms: ["bluetooth", "calendar", "contacts", "debug", "history", "music_files", "picture_files", "video_files"] |
855 | + SlotsLayout.position: SlotsLayout.Leading |
856 | + width: units.gu(4); height: width |
857 | + name: "security-alert" |
858 | + visible: { |
859 | + var length = restrictedPerms.length; |
860 | + while(length--) { |
861 | + if (permissions.indexOf(restrictedPerms[length]) > -1) |
862 | + return true |
863 | + } |
864 | + return false |
865 | + } |
866 | + } |
867 | + |
868 | + title.text: i18n.tr("Permissions") |
869 | + subtitle.maximumLineCount: Number.MAX_VALUE |
870 | + subtitle.text: { |
871 | + if (permissions) { |
872 | + return permissions.replace("bluetooth", "<font color=\"#ED3146\">bluetooth</font>") |
873 | + .replace("calendar", "<font color=\"#ED3146\">calendar</font>") |
874 | + .replace("contacts", "<font color=\"#ED3146\">contacts</font>") |
875 | + .replace("debug", "<font color=\"#ED3146\">debug</font>") |
876 | + .replace("history", "<font color=\"#ED3146\">history</font>") |
877 | + .replace("music_files_read", "<font color=\"#ED3146\">music_files_read</font>") |
878 | + .replace("picture_files_read", "<font color=\"#ED3146\">music_files_read</font>") |
879 | + .replace("video_files_read", "<font color=\"#ED3146\">music_files_read</font>") |
880 | + .replace("music_files", "<font color=\"#ED3146\">music_files_read</font>") |
881 | + .replace("picture_files", "<font color=\"#ED3146\">music_files_read</font>") |
882 | + .replace("video_files", "<font color=\"#ED3146\">music_files_read</font>") |
883 | + } |
884 | + |
885 | + return i18n.tr("<i>none required</i>") |
886 | + } |
887 | + } |
888 | + |
889 | + ListItemLayout { |
890 | + anchors { left: parent.left; right: parent.right } |
891 | + anchors.leftMargin: units.gu(-2) |
892 | + height: units.gu(6) |
893 | + visible: readpaths.length > 0 |
894 | + |
895 | + Icon { |
896 | + SlotsLayout.position: SlotsLayout.Leading |
897 | + width: units.gu(4); height: width |
898 | + name: "security-alert" |
899 | + visible: includesUnconfinedLocations(readpaths) |
900 | + } |
901 | + |
902 | + title.text: i18n.tr("Read paths") |
903 | + subtitle.text: readpaths || i18n.tr("<i>none</i>") |
904 | + subtitle.maximumLineCount: Number.MAX_VALUE |
905 | + } |
906 | + |
907 | + ListItemLayout { |
908 | + anchors { left: parent.left; right: parent.right } |
909 | + anchors.leftMargin: units.gu(-2) |
910 | + height: units.gu(6) |
911 | + visible: writepaths.length > 0 |
912 | + Icon { |
913 | + SlotsLayout.position: SlotsLayout.Leading |
914 | + width: units.gu(4); height: width |
915 | + name: "security-alert" |
916 | + visible: includesUnconfinedLocations(writepaths) |
917 | + } |
918 | + |
919 | + title.text: i18n.tr("Write paths") |
920 | + subtitle.text: writepaths || i18n.tr("<i>none</i>") |
921 | + subtitle.maximumLineCount: Number.MAX_VALUE |
922 | + } |
923 | + |
924 | + Button { |
925 | + anchors { right: parent.right } |
926 | + text: "Open" |
927 | + color: UbuntuColors.green |
928 | + visible: app.installed && (hooks & ApplicationItem.HookDesktop) |
929 | + onClicked: Qt.openUrlExternally("appid://" + app.appId + "/" + hookName + "/" + app.installedVersion) |
930 | + } |
931 | + } |
932 | + } |
933 | + } |
934 | + } |
935 | + } |
936 | + |
937 | + function printSize(size) { |
938 | + var s |
939 | + |
940 | + s = 1024 * 1024 * 1024 |
941 | + if (size >= s) |
942 | + // TRANSLATORS: %1 is the size of a file, expressed in GB |
943 | + return i18n.tr("%1 GB").arg((size / s).toFixed(2)); |
944 | + |
945 | + s = 1024 * 1024 |
946 | + if (size >= s) |
947 | + // TRANSLATORS: %1 is the size of a file, expressed in MB |
948 | + return i18n.tr("%1 MB").arg((size / s).toFixed(2)); |
949 | + |
950 | + s = 1024 |
951 | + if (size >= s) |
952 | + // TRANSLATORS: %1 is the size of a file, expressed in kB |
953 | + return i18n.tr("%1 kB").arg(parseInt(size / s)); |
954 | + |
955 | + // TRANSLATORS: %1 is the size of a file, expressed in byte |
956 | + return i18n.tr("%1 byte").arg(size); |
957 | + } |
958 | + |
959 | + function includesUnconfinedLocations(paths) { |
960 | + var p = paths.split(",") |
961 | + var j = 0 |
962 | + |
963 | + for (var i=0; i < p.length; ++i) { |
964 | + var x = p[i] |
965 | + if (x.match(/[^\w\s]/)) { |
966 | + if (x.indexOf("/home/phablet/.cache/" + app.appId) == -1 && x.indexOf("/home/phablet/.config/" + app.appId) == -1) { |
967 | + ++j |
968 | + } |
969 | + } |
970 | + } |
971 | + |
972 | + return (j > 0) |
973 | } |
974 | } |
975 | |
976 | === added file 'openstore/CategoriesPage.qml' |
977 | --- openstore/CategoriesPage.qml 1970-01-01 00:00:00 +0000 |
978 | +++ openstore/CategoriesPage.qml 2017-05-08 18:42:06 +0000 |
979 | @@ -0,0 +1,60 @@ |
980 | +/* |
981 | + * Copyright (C) 2017 - Stefano Verzegnassi <verzegnassi.stefano@gmail.com> |
982 | + * |
983 | + * This program is free software; you can redistribute it and/or modify |
984 | + * it under the terms of the GNU General Public License as published by |
985 | + * the Free Software Foundation; version 3. |
986 | + * |
987 | + * This program is distributed in the hope that it will be useful, |
988 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
989 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
990 | + * GNU General Public License for more details. |
991 | + * |
992 | + * You should have received a copy of the GNU General Public License |
993 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
994 | + */ |
995 | + |
996 | +import QtQuick 2.4 |
997 | +import Ubuntu.Components 1.3 |
998 | + |
999 | +Page { |
1000 | + id: categoryPage |
1001 | + |
1002 | + header: PageHeader { |
1003 | + title: i18n.tr("Categories") |
1004 | + leadingActionBar.actions: Action { |
1005 | + iconName: "close" |
1006 | + onTriggered: categoryPage.pageStack.removePages(categoryPage) |
1007 | + } |
1008 | + } |
1009 | + |
1010 | + signal categoryClicked(var name, var code) |
1011 | + |
1012 | + onCategoryClicked: { |
1013 | + var pageProps = { |
1014 | + title: name, |
1015 | + filterPattern: new RegExp(code.toString()), |
1016 | + filterProperty: "category" |
1017 | + } |
1018 | + categoryPage.pageStack.addPageToNextColumn(categoryPage, filteredAppPageComponent, pageProps) |
1019 | + } |
1020 | + |
1021 | + ScrollView { |
1022 | + anchors.fill: parent |
1023 | + anchors.topMargin: categoryPage.header.height |
1024 | + |
1025 | + ListView { |
1026 | + id: categoryView |
1027 | + anchors.fill: parent |
1028 | + model: categories.list |
1029 | + delegate: ListItem { |
1030 | + onClicked: categoryPage.categoryClicked(modelData, modelData) |
1031 | + ListItemLayout { |
1032 | + anchors.centerIn: parent |
1033 | + title.text: modelData |
1034 | + ProgressionSlot {} |
1035 | + } |
1036 | + } |
1037 | + } |
1038 | + } |
1039 | +} |
1040 | |
1041 | === added file 'openstore/DiscoverTab.qml' |
1042 | --- openstore/DiscoverTab.qml 1970-01-01 00:00:00 +0000 |
1043 | +++ openstore/DiscoverTab.qml 2017-05-08 18:42:06 +0000 |
1044 | @@ -0,0 +1,165 @@ |
1045 | +/* |
1046 | + * Copyright (C) 2017 - Stefano Verzegnassi <verzegnassi.stefano@gmail.com> |
1047 | + * |
1048 | + * This program is free software; you can redistribute it and/or modify |
1049 | + * it under the terms of the GNU General Public License as published by |
1050 | + * the Free Software Foundation; version 3. |
1051 | + * |
1052 | + * This program is distributed in the hope that it will be useful, |
1053 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1054 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1055 | + * GNU General Public License for more details. |
1056 | + * |
1057 | + * You should have received a copy of the GNU General Public License |
1058 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1059 | + */ |
1060 | + |
1061 | +import QtQuick 2.4 |
1062 | +import Ubuntu.Components 1.3 |
1063 | +import OpenStore 1.0 |
1064 | + |
1065 | +ScrollView { |
1066 | + id: rootItem |
1067 | + anchors.fill: parent |
1068 | + anchors.topMargin: parent.header ? parent.header.height : 0 |
1069 | + |
1070 | + property var discoverData |
1071 | + property string discoverApiEndPoint: "https://open.uappexplorer.com/api/v1/apps/discover" |
1072 | + property AppModel storeModel |
1073 | + |
1074 | + signal appDetailsRequired(var appId) |
1075 | + signal categoryViewRequired(var name, var categoryCode) |
1076 | + |
1077 | + Component.onCompleted: { |
1078 | + var doc = new XMLHttpRequest(); |
1079 | + doc.onreadystatechange = function() { |
1080 | + if (doc.readyState == 4 && doc.status == 200) { |
1081 | + var reply = JSON.parse(doc.responseText) |
1082 | + |
1083 | + if (reply.success) { |
1084 | + rootItem.discoverData = reply.data |
1085 | + } else { |
1086 | + console.log("Unable to fetch discover data from server (success = false).") |
1087 | + } |
1088 | + } |
1089 | + } |
1090 | + |
1091 | + doc.open("GET", discoverApiEndPoint, true); |
1092 | + doc.send(); |
1093 | + } |
1094 | + |
1095 | + ListView { |
1096 | + id: view |
1097 | + anchors.fill: parent |
1098 | + |
1099 | + header: AbstractButton { |
1100 | + id: highlightAppControl |
1101 | + property var appItem: storeModel.app(storeModel.findApp(discoverData.highlighted_id)) |
1102 | + width: parent.width |
1103 | + height: Math.min(units.gu(28), width * 9 / 16) |
1104 | + |
1105 | + onClicked: rootItem.appDetailsRequired(discoverData.highlighted_id) |
1106 | + |
1107 | + Image { |
1108 | + anchors.fill: parent |
1109 | + anchors.bottomMargin: units.gu(4) |
1110 | + source: highlightAppControl.appItem.icon |
1111 | + fillMode: Image.PreserveAspectCrop |
1112 | + } |
1113 | + |
1114 | + ListItemLayout { |
1115 | + anchors.centerIn: parent |
1116 | + |
1117 | + title.text: highlightAppControl.appItem.name |
1118 | + title.font.pixelSize: units.gu(3) |
1119 | + title.color: "white" |
1120 | + |
1121 | + subtitle.text: highlightAppControl.appItem.tagline || highlightAppControl.appItem.description |
1122 | + subtitle.font.pixelSize: units.gu(1.5) |
1123 | + subtitle.color: "white" |
1124 | + } |
1125 | + } |
1126 | + |
1127 | + model: discoverData ? rootItem.discoverData.categories : null |
1128 | + delegate: Column { |
1129 | + width: parent.width |
1130 | + spacing: units.gu(1) |
1131 | + |
1132 | + ListItem { |
1133 | + divider.visible: false |
1134 | + onClicked: { |
1135 | + if (modelData.referral) { |
1136 | + rootItem.categoryViewRequired(modelData.name, modelData.referral) |
1137 | + } |
1138 | + } |
1139 | + |
1140 | + ListItemLayout { |
1141 | + anchors.centerIn: parent |
1142 | + title.text: modelData.name |
1143 | + subtitle.text: modelData.tagline |
1144 | + |
1145 | + ProgressionSlot { |
1146 | + visible: modelData.referral != "" |
1147 | + } |
1148 | + } |
1149 | + } |
1150 | + |
1151 | + ListView { |
1152 | + anchors { left: parent.left; right: parent.right } |
1153 | + leftMargin: units.gu(2) |
1154 | + rightMargin: units.gu(2) |
1155 | + clip: true |
1156 | + height: count > 0 ? units.gu(24) : 0 |
1157 | + visible: count > 0 |
1158 | + spacing: units.gu(2) |
1159 | + orientation: ListView.Horizontal |
1160 | + model: modelData.ids |
1161 | + delegate: AbstractButton { |
1162 | + id: appDel |
1163 | + property var appItem: storeModel.app(storeModel.findApp(modelData)) |
1164 | + height: parent.height |
1165 | + width: units.gu(16) |
1166 | + |
1167 | + onClicked: rootItem.appDetailsRequired(modelData) |
1168 | + |
1169 | + Column { |
1170 | + anchors.fill: parent |
1171 | + |
1172 | + UbuntuShape { |
1173 | + width: parent.width |
1174 | + height: width |
1175 | + aspect: UbuntuShape.Flat |
1176 | + sourceFillMode: UbuntuShape.PreserveAspectFit |
1177 | + source: Image { |
1178 | + source: appDel.appItem.icon |
1179 | + } |
1180 | + } |
1181 | + |
1182 | + ListItemLayout { |
1183 | + anchors { |
1184 | + left: parent.left; leftMargin: units.gu(-1) |
1185 | + right: parent.right |
1186 | + } |
1187 | + |
1188 | + height: units.gu(4) |
1189 | + title { |
1190 | + text: appDel.appItem.name |
1191 | + textSize: Label.Small |
1192 | + wrapMode: Text.WrapAtWordBoundaryOrAnywhere |
1193 | + maximumLineCount: 2 |
1194 | + } |
1195 | + |
1196 | + subtitle { |
1197 | + text: appDel.appItem.author |
1198 | + textSize: Label.XSmall |
1199 | + } |
1200 | + |
1201 | + summary.text: appDel.appItem.installed ? appDel.appItem.updateAvailable ? i18n.tr("Update available") : i18n.tr("Installed") : "" |
1202 | + summary.textSize: Label.XSmall |
1203 | + } |
1204 | + } |
1205 | + } |
1206 | + } |
1207 | + } |
1208 | + } |
1209 | +} |
1210 | |
1211 | === added file 'openstore/EmptyState.qml' |
1212 | --- openstore/EmptyState.qml 1970-01-01 00:00:00 +0000 |
1213 | +++ openstore/EmptyState.qml 2017-05-08 18:42:06 +0000 |
1214 | @@ -0,0 +1,74 @@ |
1215 | +/* |
1216 | + * Copyright (C) 2014, 2015, 2016 Canonical Ltd |
1217 | + * |
1218 | + * This file is part of Ubuntu Clock App |
1219 | + * |
1220 | + * Ubuntu Clock App is free software: you can redistribute it and/or modify |
1221 | + * it under the terms of the GNU General Public License version 3 as |
1222 | + * published by the Free Software Foundation. |
1223 | + * |
1224 | + * Ubuntu Clock App is distributed in the hope that it will be useful, |
1225 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1226 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1227 | + * GNU General Public License for more details. |
1228 | + * |
1229 | + * You should have received a copy of the GNU General Public License |
1230 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1231 | + */ |
1232 | + |
1233 | +import QtQuick 2.4 |
1234 | +import Ubuntu.Components 1.3 |
1235 | + |
1236 | +/* |
1237 | + Component which displays an empty state (approved by design). It offers an |
1238 | + icon, title and subtitle to describe the empty state. |
1239 | +*/ |
1240 | + |
1241 | +Column { |
1242 | + id: emptyState |
1243 | + spacing: units.gu(2) |
1244 | + width: units.gu(36) |
1245 | + |
1246 | + // Public APIs |
1247 | + default property alias iconPlaceholder: iconContainer.data |
1248 | + property alias iconName: emptyIcon.name |
1249 | + property alias title: emptyLabel.text |
1250 | + property alias subTitle: emptySublabel.text |
1251 | + |
1252 | + Item { |
1253 | + width: childrenRect.width |
1254 | + height: childrenRect.height + units.gu(2) |
1255 | + Icon { |
1256 | + id: emptyIcon |
1257 | + height: visible ? units.gu(10) : 0 |
1258 | + width: visible ? height : 0 |
1259 | + color: "#BBBBBB" |
1260 | + visible: name || source |
1261 | + } |
1262 | + Row { |
1263 | + id: iconContainer |
1264 | + anchors.horizontalCenter: parent.horizontalCenter |
1265 | + } |
1266 | + } |
1267 | + |
1268 | + Label { |
1269 | + id: emptyLabel |
1270 | + width: parent.width |
1271 | + horizontalAlignment: Text.AlignLeft |
1272 | + textSize: Label.XLarge |
1273 | + |
1274 | + elide: Text.ElideRight |
1275 | + wrapMode: Text.WordWrap |
1276 | + maximumLineCount: 2 |
1277 | + } |
1278 | + |
1279 | + Label { |
1280 | + id: emptySublabel |
1281 | + width: parent.width |
1282 | + horizontalAlignment: Text.AlignLeft |
1283 | + textSize: Label.Medium |
1284 | + |
1285 | + elide: Text.ElideRight |
1286 | + wrapMode: Text.WordWrap |
1287 | + } |
1288 | +} |
1289 | |
1290 | === added file 'openstore/FilteredAppView.qml' |
1291 | --- openstore/FilteredAppView.qml 1970-01-01 00:00:00 +0000 |
1292 | +++ openstore/FilteredAppView.qml 2017-05-08 18:42:06 +0000 |
1293 | @@ -0,0 +1,108 @@ |
1294 | +/* |
1295 | + * Copyright (C) 2015 Michael Zanetti <michael.zanetti@ubuntu.com> |
1296 | + * Copyright (C) 2017 Stefano Verzegnassi <verzegnassi.stefano@gmail.com> |
1297 | + * |
1298 | + * This program is free software; you can redistribute it and/or modify |
1299 | + * it under the terms of the GNU General Public License as published by |
1300 | + * the Free Software Foundation; version 3. |
1301 | + * |
1302 | + * This program is distributed in the hope that it will be useful, |
1303 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1304 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1305 | + * GNU General Public License for more details. |
1306 | + * |
1307 | + * You should have received a copy of the GNU General Public License |
1308 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1309 | + */ |
1310 | + |
1311 | +import QtQuick 2.4 |
1312 | +import Ubuntu.Components 1.3 |
1313 | + |
1314 | +ScrollView { |
1315 | + id: rootItem |
1316 | + anchors.fill: parent |
1317 | + anchors.topMargin: parent.header ? parent.header.height : 0 |
1318 | + |
1319 | + property var filterPattern: new RegExp() |
1320 | + property string filterProperty |
1321 | + |
1322 | + property string sortProperty |
1323 | + property int sortOrder |
1324 | + |
1325 | + property alias model: sortedFilteredAppModel.model |
1326 | + property alias view: view |
1327 | + |
1328 | + property bool showTicks: true |
1329 | + |
1330 | + signal appDetailsRequired(var appId) |
1331 | + |
1332 | + ListView { |
1333 | + id: view |
1334 | + model: SortFilterModel { |
1335 | + id: sortedFilteredAppModel |
1336 | + |
1337 | + filter.pattern: rootItem.filterPattern |
1338 | + filter.property: rootItem.filterProperty |
1339 | + |
1340 | + sort.property: rootItem.sortProperty |
1341 | + sort.order: rootItem.sortOrder |
1342 | + } |
1343 | + |
1344 | + // TODO: Move it in Main.qml or elsewhere |
1345 | + onCountChanged: { |
1346 | + if (count > 0 && root.appIdToOpen != "") { |
1347 | + var index = appModel.findApp(root.appIdToOpen) |
1348 | + if (index >= 0) { |
1349 | + pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("AppDetailsPage.qml"), {app: appModel.app(index)}) |
1350 | + root.appIdToOpen = ""; |
1351 | + } |
1352 | + } |
1353 | + } |
1354 | + |
1355 | + delegate: ListItem { |
1356 | + height: layout.height + divider.height |
1357 | + |
1358 | + ListItemLayout { |
1359 | + id: layout |
1360 | + title.text: model.name |
1361 | + summary.text: model.tagline |
1362 | + |
1363 | + UbuntuShape { |
1364 | + SlotsLayout.position: SlotsLayout.Leading |
1365 | + aspect: UbuntuShape.Flat |
1366 | + image: Image { |
1367 | + source: model.icon |
1368 | + height: parent.height |
1369 | + width: parent.width |
1370 | + } |
1371 | + } |
1372 | + Icon { |
1373 | + SlotsLayout.position: SlotsLayout.Trailing |
1374 | + height: units.gu(2) |
1375 | + width: height |
1376 | + implicitHeight: height |
1377 | + implicitWidth: width |
1378 | + visible: model.installed && rootItem.showTicks |
1379 | + name: "tick" |
1380 | + color: model.updateAvailable ? UbuntuColors.orange : UbuntuColors.green |
1381 | + } |
1382 | + |
1383 | + ProgressionSlot {} |
1384 | + } |
1385 | + onClicked: { |
1386 | + rootItem.appDetailsRequired(model.appId) |
1387 | + } |
1388 | + } |
1389 | + |
1390 | + Loader { |
1391 | + anchors.centerIn: parent |
1392 | + active: view.count == 0 |
1393 | + sourceComponent: EmptyState { |
1394 | + title: rootItem.filterProperty == "category" ? i18n.tr("Nothing here yet") : i18n.tr("No result found.").arg(rootItem.filterPattern) |
1395 | + subTitle: rootItem.filterProperty == "category" ? i18n.tr("No app has been released in this department yet.") : i18n.tr("Try with a different search.") |
1396 | + iconName: rootItem.filterProperty == "category" ? "ubuntu-store-symbolic" : "search" |
1397 | + anchors.centerIn: parent |
1398 | + } |
1399 | + } |
1400 | + } |
1401 | +} |
1402 | |
1403 | === modified file 'openstore/Main.qml' |
1404 | --- openstore/Main.qml 2016-08-21 10:29:12 +0000 |
1405 | +++ openstore/Main.qml 2017-05-08 18:42:06 +0000 |
1406 | @@ -20,6 +20,7 @@ |
1407 | import QtQuick.Layouts 1.1 |
1408 | import Qt.labs.settings 1.0 |
1409 | import Ubuntu.Content 1.3 |
1410 | +import QtQml.Models 2.1 |
1411 | |
1412 | MainView { |
1413 | id: root |
1414 | @@ -82,6 +83,7 @@ |
1415 | AppModel { |
1416 | id: appModel |
1417 | installer: installer |
1418 | + onRepositoryListFetched: repoFetchingIndicator.visible = false |
1419 | } |
1420 | |
1421 | ServiceRegistry { |
1422 | @@ -89,6 +91,31 @@ |
1423 | clickInstaller: installer |
1424 | } |
1425 | |
1426 | + QtObject { |
1427 | + id: categories |
1428 | + |
1429 | + property string categoriesApiEndPoint: "https://open.uappexplorer.com/api/v1/categories" |
1430 | + property var list |
1431 | + |
1432 | + Component.onCompleted: { |
1433 | + var doc = new XMLHttpRequest(); |
1434 | + doc.onreadystatechange = function() { |
1435 | + if (doc.readyState == 4 && doc.status == 200) { |
1436 | + var reply = JSON.parse(doc.responseText) |
1437 | + if (reply.success) { |
1438 | + list = reply.data |
1439 | + } else { |
1440 | + console.log("Unable to fetch categories from server (success = false).") |
1441 | + } |
1442 | + } |
1443 | + } |
1444 | + |
1445 | + doc.open("GET", categoriesApiEndPoint, true); |
1446 | + doc.send(); |
1447 | + } |
1448 | + |
1449 | + } |
1450 | + |
1451 | property bool contentHubInstallInProgress: false |
1452 | Connections { |
1453 | target: ContentHub |
1454 | @@ -113,54 +140,171 @@ |
1455 | id: mainPage |
1456 | header: PageHeader { |
1457 | title: i18n.tr("Open Store") |
1458 | + automaticHeight: false |
1459 | + |
1460 | + leadingActionBar.actions: Action { |
1461 | + iconName: "navigation-menu" |
1462 | + text: i18n.tr("Categories") |
1463 | + onTriggered: mainPage.pageStack.addPageToCurrentColumn(mainPage, Qt.resolvedUrl("CategoriesPage.qml"), {}) |
1464 | + } |
1465 | + |
1466 | + trailingActionBar.actions: Action { |
1467 | + iconName: "find" |
1468 | + text: i18n.tr("Search") |
1469 | + onTriggered: { |
1470 | + mainPage.pageStack.addPageToCurrentColumn(mainPage, searchPageComponent, {}) |
1471 | + } |
1472 | + } |
1473 | + |
1474 | + sections { |
1475 | + model: [ i18n.tr("Discover"), i18n.tr("My Apps") ] |
1476 | + selectedIndex: 0 // Should always match "Discover" |
1477 | + onSelectedIndexChanged: { |
1478 | + // Current section has changed, if there was an opened page |
1479 | + // in the second column, it is not anymore related to the |
1480 | + // new current section. Remove it. |
1481 | + mainPage.pageStack.removePages(mainPage) |
1482 | + } |
1483 | + } |
1484 | } |
1485 | |
1486 | - |
1487 | ListView { |
1488 | - anchors.fill: parent |
1489 | - anchors.topMargin: mainPage.header.height |
1490 | + id: view |
1491 | + anchors { |
1492 | + top: mainPage.header.bottom |
1493 | + bottom: parent.bottom |
1494 | + left: parent.left |
1495 | + right: parent.right |
1496 | + } |
1497 | + |
1498 | + clip: true |
1499 | + orientation: ListView.Horizontal |
1500 | + interactive: false |
1501 | + snapMode: ListView.SnapOneItem |
1502 | + highlightMoveDuration: 0 |
1503 | + currentIndex: mainPage.header.sections.selectedIndex |
1504 | + |
1505 | + model: ObjectModel { |
1506 | + Loader { |
1507 | + id: discoverTabLoader |
1508 | + width: view.width |
1509 | + height: view.height |
1510 | + asynchronous: true |
1511 | + source: Qt.resolvedUrl("DiscoverTab.qml") |
1512 | + |
1513 | + active: false |
1514 | + Connections { |
1515 | + target: appModel |
1516 | + onRepositoryListFetched: discoverTabLoader.active = true |
1517 | + } |
1518 | + |
1519 | + onLoaded: { |
1520 | + item.storeModel = appModel |
1521 | + |
1522 | + item.appDetailsRequired.connect(function(appId) { |
1523 | + var pageProps = { |
1524 | + app: appModel.app(appModel.findApp(appId)) |
1525 | + } |
1526 | + mainPage.pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("AppDetailsPage.qml"), pageProps) |
1527 | + }) |
1528 | + |
1529 | + item.categoryViewRequired.connect(function(name, code) { |
1530 | + var pageProps = { |
1531 | + title: name, |
1532 | + filterPattern: new RegExp(code.toString()), |
1533 | + filterProperty: "category" |
1534 | + } |
1535 | + |
1536 | + mainPage.pageStack.removePages(mainPage) |
1537 | + mainPage.pageStack.addPageToCurrentColumn(mainPage, filteredAppPageComponent, pageProps) |
1538 | + }) |
1539 | + } |
1540 | + } |
1541 | + Loader { |
1542 | + width: view.width |
1543 | + height: view.height |
1544 | + asynchronous: true |
1545 | + source: Qt.resolvedUrl("FilteredAppView.qml") |
1546 | + |
1547 | + onLoaded: { |
1548 | + item.model = appModel |
1549 | + |
1550 | + item.filterProperty = "installed" |
1551 | + item.filterPattern = new RegExp("true") |
1552 | + |
1553 | + item.sortProperty = "updateAvailable" |
1554 | + item.sortOrder = Qt.DescendingOrder |
1555 | + |
1556 | + item.view.section.property = "updateAvailable" |
1557 | + item.view.section.delegate = updateDivider |
1558 | + |
1559 | + item.showTicks = false |
1560 | + |
1561 | + item.appDetailsRequired.connect(function(appId) { |
1562 | + var pageProps = { |
1563 | + app: appModel.app(appModel.findApp(appId)) |
1564 | + } |
1565 | + mainPage.pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("AppDetailsPage.qml"), pageProps) |
1566 | + }) |
1567 | + } |
1568 | + |
1569 | + Component { |
1570 | + id: updateDivider |
1571 | + SectionDivider { |
1572 | + text: section == "true" ? i18n.tr("Available updates") : i18n.tr("Installed apps") |
1573 | + } |
1574 | + } |
1575 | + } |
1576 | + } |
1577 | + } |
1578 | + |
1579 | + Column { |
1580 | + id: repoFetchingIndicator |
1581 | + anchors.centerIn: parent |
1582 | + anchors.verticalCenterOffset: mainPage.header.height * 0.4 |
1583 | + spacing: units.gu(1) |
1584 | + ActivityIndicator { |
1585 | + anchors.horizontalCenter: parent.horizontalCenter |
1586 | + running: visible |
1587 | + } |
1588 | + Label { |
1589 | + textSize: Label.Small |
1590 | + text: i18n.tr("Fetching package list...") |
1591 | + } |
1592 | + } |
1593 | + } |
1594 | + } |
1595 | + |
1596 | + Component { |
1597 | + id: searchPageComponent |
1598 | + |
1599 | + SearchPage { |
1600 | + id: searchPage |
1601 | + model: appModel |
1602 | + |
1603 | + onAppDetailsRequired: { |
1604 | + var pageProps = { app: appModel.app(appModel.findApp(appId)) } |
1605 | + searchPage.pageStack.addPageToNextColumn(searchPage, Qt.resolvedUrl("AppDetailsPage.qml"), pageProps) |
1606 | + } |
1607 | + } |
1608 | + } |
1609 | + |
1610 | + Component { |
1611 | + id: filteredAppPageComponent |
1612 | + Page { |
1613 | + id: filteredAppPage |
1614 | + property alias filterPattern: filteredAppView.filterPattern |
1615 | + property alias filterProperty: filteredAppView.filterProperty |
1616 | + header: PageHeader { |
1617 | + title: filteredAppPage.title |
1618 | + automaticHeight: false |
1619 | + } |
1620 | + FilteredAppView { |
1621 | + id: filteredAppView |
1622 | model: appModel |
1623 | - |
1624 | - onCountChanged: { |
1625 | - if (count > 0 && root.appIdToOpen != "") { |
1626 | - var index = appModel.findApp(root.appIdToOpen) |
1627 | - if (index >= 0) { |
1628 | - pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("AppDetailsPage.qml"), {app: appModel.app(index)}) |
1629 | - root.appIdToOpen = ""; |
1630 | - } |
1631 | - } |
1632 | - } |
1633 | - |
1634 | - delegate: ListItem { |
1635 | - height: layout.height + divider.height |
1636 | - |
1637 | - ListItemLayout { |
1638 | - id: layout |
1639 | - title.text: model.name |
1640 | - summary.text: model.tagline |
1641 | - |
1642 | - UbuntuShape { |
1643 | - SlotsLayout.position: SlotsLayout.Leading |
1644 | - image: Image { |
1645 | - source: model.icon |
1646 | - height: parent.height |
1647 | - width: parent.width |
1648 | - } |
1649 | - } |
1650 | - Icon { |
1651 | - SlotsLayout.position: SlotsLayout.Trailing |
1652 | - height: units.gu(2) |
1653 | - width: height |
1654 | - implicitHeight: height |
1655 | - implicitWidth: width |
1656 | - visible: model.installed |
1657 | - name: "tick" |
1658 | - color: model.installedVersion >= model.version ? UbuntuColors.green : UbuntuColors.orange |
1659 | - } |
1660 | - } |
1661 | - onClicked: { |
1662 | - pageStack.addPageToNextColumn(mainPage, Qt.resolvedUrl("AppDetailsPage.qml"), {app: appModel.app(index)}) |
1663 | - } |
1664 | + onAppDetailsRequired: { |
1665 | + var pageProps = { app: appModel.app(appModel.findApp(appId)) } |
1666 | + filteredAppPage.pageStack.addPageToNextColumn(filteredAppPage, Qt.resolvedUrl("AppDetailsPage.qml"), pageProps) |
1667 | } |
1668 | } |
1669 | } |
1670 | |
1671 | === added file 'openstore/SearchPage.qml' |
1672 | --- openstore/SearchPage.qml 1970-01-01 00:00:00 +0000 |
1673 | +++ openstore/SearchPage.qml 2017-05-08 18:42:06 +0000 |
1674 | @@ -0,0 +1,87 @@ |
1675 | +/* |
1676 | + * Copyright (C) 2017 - Stefano Verzegnassi <verzegnassi.stefano@gmail.com> |
1677 | + * |
1678 | + * This program is free software; you can redistribute it and/or modify |
1679 | + * it under the terms of the GNU General Public License as published by |
1680 | + * the Free Software Foundation; version 3. |
1681 | + * |
1682 | + * This program is distributed in the hope that it will be useful, |
1683 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1684 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1685 | + * GNU General Public License for more details. |
1686 | + * |
1687 | + * You should have received a copy of the GNU General Public License |
1688 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1689 | + */ |
1690 | + |
1691 | +import QtQuick 2.4 |
1692 | +import Ubuntu.Components 1.3 |
1693 | + |
1694 | +Page { |
1695 | + id: searchPage |
1696 | + |
1697 | + property alias model: view.model |
1698 | + property alias query: searchField.text |
1699 | + |
1700 | + signal appDetailsRequired(var appId) |
1701 | + |
1702 | + header: PageHeader { |
1703 | + title: i18n.tr("Search") |
1704 | + automaticHeight: false |
1705 | + leadingActionBar.actions: null |
1706 | + trailingActionBar { |
1707 | + anchors.rightMargin: 0 |
1708 | + delegate: TextualButtonStyle {} |
1709 | + |
1710 | + actions: Action { |
1711 | + text: i18n.tr("Cancel") |
1712 | + |
1713 | + onTriggered: { |
1714 | + // Clear the search |
1715 | + searchField.text = "" |
1716 | + searchPage.pageStack.removePages(searchPage) |
1717 | + } |
1718 | + } |
1719 | + } |
1720 | + |
1721 | + contents: TextField { |
1722 | + id: searchField |
1723 | + anchors { |
1724 | + left: parent.left |
1725 | + right: parent.right |
1726 | + verticalCenter: parent.verticalCenter |
1727 | + } |
1728 | + |
1729 | + primaryItem: Icon { |
1730 | + height: units.gu(2); width: height |
1731 | + name: "search" |
1732 | + } |
1733 | + |
1734 | + placeholderText: i18n.tr("search in OpenStore...") |
1735 | + onTextChanged: view.search(text) |
1736 | + Component.onCompleted: view.search(text) |
1737 | + |
1738 | + // Disable predictive text |
1739 | + inputMethodHints: Qt.ImhNoPredictiveText |
1740 | + |
1741 | + onVisibleChanged: forceActiveFocus() |
1742 | + } |
1743 | + } |
1744 | + |
1745 | + FilteredAppView { |
1746 | + id: view |
1747 | + |
1748 | + // FIXME: TODO: Use "number of downloads" or "last updated" when they'll be available |
1749 | + sortOrder: Qt.AscendingOrder |
1750 | + sortProperty: "name" |
1751 | + filterProperty: "searchHackishString" |
1752 | + //filterPattern: new RegExp("$a") // A kind way to say SortFilterModel not to match anything until searchField is filled. |
1753 | + |
1754 | + function search(text) { |
1755 | + //view.filterPattern = text ? new RegExp(text, 'i') : new RegExp("$a") |
1756 | + view.filterPattern = new RegExp(text, 'i') |
1757 | + } |
1758 | + |
1759 | + onAppDetailsRequired: searchPage.appDetailsRequired(appId) |
1760 | + } |
1761 | +} |
1762 | |
1763 | === added file 'openstore/SectionDivider.qml' |
1764 | --- openstore/SectionDivider.qml 1970-01-01 00:00:00 +0000 |
1765 | +++ openstore/SectionDivider.qml 2017-05-08 18:42:06 +0000 |
1766 | @@ -0,0 +1,52 @@ |
1767 | +import QtQuick 2.4 |
1768 | +import Ubuntu.Components 1.3 |
1769 | + |
1770 | +Rectangle { |
1771 | + id: rootItem |
1772 | + |
1773 | + property alias text: sectionLabel.text |
1774 | + property alias iconName: icon.name |
1775 | + |
1776 | + anchors { left: parent.left; right: parent.right } |
1777 | + height: units.gu(4) |
1778 | + |
1779 | + color: theme.palette.normal.foreground |
1780 | + |
1781 | + Row { |
1782 | + anchors { |
1783 | + left: parent.left |
1784 | + right: parent.right |
1785 | + margins: units.gu(2) |
1786 | + verticalCenter: parent.verticalCenter |
1787 | + } |
1788 | + spacing: units.gu(1) |
1789 | + |
1790 | + Icon { |
1791 | + id: icon |
1792 | + height: units.gu(2) |
1793 | + width: name ? units.gu(2) : 0 |
1794 | + anchors.verticalCenter: parent.verticalCenter |
1795 | + } |
1796 | + |
1797 | + Label { |
1798 | + id: sectionLabel |
1799 | + anchors.verticalCenter: parent.verticalCenter |
1800 | + } |
1801 | + } |
1802 | + |
1803 | + Rectangle { |
1804 | + anchors { |
1805 | + left: parent.left |
1806 | + right: parent.right |
1807 | + bottom: parent.bottom |
1808 | + } |
1809 | + |
1810 | + height: units.dp(2) |
1811 | + gradient: Gradient { |
1812 | + GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0.1) } |
1813 | + GradientStop { position: 0.49; color: Qt.rgba(0, 0, 0, 0.1) } |
1814 | + GradientStop { position: 0.5; color: Qt.rgba(1, 1, 1, 0.4) } |
1815 | + GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0.4) } |
1816 | + } |
1817 | + } |
1818 | +} |
1819 | |
1820 | === added file 'openstore/TextualButtonStyle.qml' |
1821 | --- openstore/TextualButtonStyle.qml 1970-01-01 00:00:00 +0000 |
1822 | +++ openstore/TextualButtonStyle.qml 2017-05-08 18:42:06 +0000 |
1823 | @@ -0,0 +1,67 @@ |
1824 | +/* |
1825 | + * Copyright (C) 2016 Canonical, Ltd. |
1826 | + * |
1827 | + * This program is free software; you can redistribute it and/or modify |
1828 | + * it under the terms of the GNU General Public License as published by |
1829 | + * the Free Software Foundation; version 3. |
1830 | + * |
1831 | + * This program is distributed in the hope that it will be useful, |
1832 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1833 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1834 | + * GNU General Public License for more details. |
1835 | + * |
1836 | + * You should have received a copy of the GNU General Public License |
1837 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1838 | + */ |
1839 | + |
1840 | +import QtQuick 2.4 |
1841 | +import Ubuntu.Components 1.3 |
1842 | + |
1843 | +Component { |
1844 | + id: textualButton |
1845 | + AbstractButton { |
1846 | + id: button |
1847 | + action: modelData |
1848 | + width: layout.width + units.gu(4) |
1849 | + height: parent.height |
1850 | + Rectangle { |
1851 | + color: UbuntuColors.slate |
1852 | + opacity: 0.1 |
1853 | + anchors.fill: parent |
1854 | + visible: button.pressed |
1855 | + } |
1856 | + Row { |
1857 | + id: layout |
1858 | + anchors.centerIn: parent |
1859 | + spacing: units.gu(1) |
1860 | + Icon { |
1861 | + anchors.verticalCenter: parent.verticalCenter |
1862 | + width: visible ? units.gu(2) : 0 |
1863 | + height: width |
1864 | + name: action.iconName |
1865 | + source: action.iconSource |
1866 | + visible: (name != "") || (source != "") |
1867 | + color: { |
1868 | + if (button.enabled) |
1869 | + return text === i18n.tr("Pick") ? theme.palette.selected.backgroundText : theme.palette.normal.backgroundText |
1870 | + |
1871 | + return theme.palette.disabled.backgroundText |
1872 | + } |
1873 | + } |
1874 | + Label { |
1875 | + anchors.verticalCenter: parent.verticalCenter |
1876 | + text: action.text |
1877 | + font.weight: text === i18n.tr("Pick") ? Font.Normal : Font.Light |
1878 | + // Hide text from overflow button of ActionBar |
1879 | + visible: text !== "More" |
1880 | + width: visible ? paintedWidth : 0 |
1881 | + color: { |
1882 | + if (button.enabled) |
1883 | + return text === i18n.tr("Pick") ? theme.palette.selected.backgroundText : theme.palette.normal.backgroundText |
1884 | + |
1885 | + return theme.palette.disabled.backgroundText |
1886 | + } |
1887 | + } |
1888 | + } |
1889 | + } |
1890 | +} |
1891 | |
1892 | === modified file 'openstore/appmodel.cpp' |
1893 | --- openstore/appmodel.cpp 2016-08-21 10:29:12 +0000 |
1894 | +++ openstore/appmodel.cpp 2017-05-08 18:42:06 +0000 |
1895 | @@ -49,6 +49,8 @@ |
1896 | switch (role) { |
1897 | case RoleName: |
1898 | return m_list.at(index.row())->name(); |
1899 | + case RoleAppId: |
1900 | + return m_list.at(index.row())->appId(); |
1901 | case RoleIcon: |
1902 | return m_list.at(index.row())->icon(); |
1903 | case RoleAuthor: |
1904 | @@ -57,6 +59,8 @@ |
1905 | return m_list.at(index.row())->tagline(); |
1906 | case RoleDescription: |
1907 | return m_list.at(index.row())->description(); |
1908 | + case RoleCategory: |
1909 | + return m_list.at(index.row())->category(); |
1910 | case RoleScreenshots: |
1911 | return m_list.at(index.row())->screenshots(); |
1912 | case RoleChangelog: |
1913 | @@ -69,8 +73,12 @@ |
1914 | return m_list.at(index.row())->installed(); |
1915 | case RoleInstalledVersion: |
1916 | return m_list.at(index.row())->installedVersion(); |
1917 | + case RoleUpdateAvailable: |
1918 | + return bool(m_list.at(index.row())->installedVersion() < m_list.at(index.row())->version()); |
1919 | case RoleMaintainer: |
1920 | return m_list.at(index.row())->maintainer(); |
1921 | + case RoleSearchHackishString: |
1922 | + return QString(m_list.at(index.row())->name()) + QString(m_list.at(index.row())->appId()) + QString(m_list.at(index.row())->author()); |
1923 | } |
1924 | return QVariant(); |
1925 | } |
1926 | @@ -79,17 +87,21 @@ |
1927 | { |
1928 | QHash<int, QByteArray> roles; |
1929 | roles.insert(RoleName, "name"); |
1930 | + roles.insert(RoleAppId, "appId"); |
1931 | roles.insert(RoleIcon, "icon"); |
1932 | roles.insert(RoleAuthor, "author"); |
1933 | roles.insert(RoleTagline, "tagline"); |
1934 | roles.insert(RoleDescription, "description"); |
1935 | + roles.insert(RoleCategory, "category"); |
1936 | roles.insert(RoleScreenshots, "screenshots"); |
1937 | roles.insert(RoleChangelog, "changelog"); |
1938 | roles.insert(RolePackageUrl, "packageUrl"); |
1939 | roles.insert(RoleVersion, "version"); |
1940 | roles.insert(RoleInstalled, "installed"); |
1941 | roles.insert(RoleInstalledVersion, "installedVersion"); |
1942 | + roles.insert(RoleUpdateAvailable, "updateAvailable"); |
1943 | roles.insert(RoleMaintainer, "maintainer"); |
1944 | + roles.insert(RoleSearchHackishString, "searchHackishString"); |
1945 | return roles; |
1946 | } |
1947 | |
1948 | @@ -199,6 +211,7 @@ |
1949 | item->setMaintainer(packageMap.value("maintainer_name").toString()); |
1950 | item->setTagline(packageMap.value("tagline").toString()); |
1951 | item->setDescription(packageMap.value("description").toString()); |
1952 | + item->setCategory(packageMap.value("category").toString()); |
1953 | item->setScreenshots(packageMap.value("screenshots").toStringList()); |
1954 | item->setChangelog(packageMap.value("changelog").toString()); |
1955 | item->setVersion(packageMap.value("version").toString()); |
1956 | @@ -257,6 +270,8 @@ |
1957 | m_list.append(item); |
1958 | } |
1959 | endResetModel(); |
1960 | + |
1961 | + Q_EMIT repositoryListFetched(); |
1962 | } |
1963 | |
1964 | void AppModel::buildInstalledClickList() |
1965 | |
1966 | === modified file 'openstore/appmodel.h' |
1967 | --- openstore/appmodel.h 2016-08-21 10:29:12 +0000 |
1968 | +++ openstore/appmodel.h 2017-05-08 18:42:06 +0000 |
1969 | @@ -16,6 +16,7 @@ |
1970 | Q_PROPERTY(QString author READ author CONSTANT) |
1971 | Q_PROPERTY(QString tagline READ tagline CONSTANT) |
1972 | Q_PROPERTY(QString description READ description CONSTANT) |
1973 | + Q_PROPERTY(QString category READ category CONSTANT) |
1974 | Q_PROPERTY(QStringList screenshots READ screenshots CONSTANT) |
1975 | Q_PROPERTY(QString changelog READ changelog CONSTANT) |
1976 | Q_PROPERTY(QString version READ version CONSTANT) |
1977 | @@ -74,6 +75,9 @@ |
1978 | QString description() const { return m_description; } |
1979 | void setDescription(const QString &description) { m_description = description; } |
1980 | |
1981 | + QString category() const { return m_category; } |
1982 | + void setCategory(const QString &category) { m_category = category; } |
1983 | + |
1984 | QStringList screenshots() const { return m_screenshots; } |
1985 | void setScreenshots(const QStringList &screenshots) { m_screenshots = screenshots; } |
1986 | |
1987 | @@ -123,6 +127,7 @@ |
1988 | QString m_author; |
1989 | QString m_tagline; |
1990 | QString m_description; |
1991 | + QString m_category; |
1992 | QStringList m_screenshots; |
1993 | QString m_changelog; |
1994 | QString m_packageUrl; |
1995 | @@ -144,17 +149,21 @@ |
1996 | public: |
1997 | enum Roles { |
1998 | RoleName, |
1999 | + RoleAppId, |
2000 | RoleIcon, |
2001 | RoleAuthor, |
2002 | RoleTagline, |
2003 | RoleDescription, |
2004 | + RoleCategory, |
2005 | RoleScreenshots, |
2006 | RoleChangelog, |
2007 | RolePackageUrl, |
2008 | RoleVersion, |
2009 | RoleInstalled, |
2010 | RoleInstalledVersion, |
2011 | - RoleMaintainer |
2012 | + RoleUpdateAvailable, |
2013 | + RoleMaintainer, |
2014 | + RoleSearchHackishString |
2015 | }; |
2016 | |
2017 | explicit AppModel(QObject *parent = 0); |
2018 | @@ -182,6 +191,7 @@ |
2019 | |
2020 | Q_SIGNALS: |
2021 | void installerChanged(); |
2022 | + void repositoryListFetched(); |
2023 | |
2024 | private: |
2025 | QList<ApplicationItem*> m_list; |
2026 | |
2027 | === modified file 'openstore/openstore.qrc' |
2028 | --- openstore/openstore.qrc 2015-09-08 21:41:14 +0000 |
2029 | +++ openstore/openstore.qrc 2017-05-08 18:42:06 +0000 |
2030 | @@ -3,5 +3,12 @@ |
2031 | <file>Main.qml</file> |
2032 | <file>AppDetailsPage.qml</file> |
2033 | <file>HookIcon.qml</file> |
2034 | + <file>DiscoverTab.qml</file> |
2035 | + <file>CategoriesPage.qml</file> |
2036 | + <file>FilteredAppView.qml</file> |
2037 | + <file>EmptyState.qml</file> |
2038 | + <file>SectionDivider.qml</file> |
2039 | + <file>SearchPage.qml</file> |
2040 | + <file>TextualButtonStyle.qml</file> |
2041 | </qresource> |
2042 | </RCC> |
The CategoryModel.qml has high potential to drift apart from the server categories. I'd vote for adding an API to the server that allows grabbing the categories.
======= ======= ======
+ var dataUrl = "https:/ /gist.githubuse rcontent. com/sverzegnass i/e6cdcfc44785c e90e5904c5fa1f9 441f/raw/ ddb35eb91186d36 ea131ab8b99604b 8a40890939/ DiscoverData. json"
This looks very odd... I guess the intention is to have a file defining the "featured items" etc, that contributors can modify/update. That's fair, but please keep it somewhere on the infrastructure. E.g. put this Json file into the app repository here, or host it somewhere on the openstore server.
==================
1974 + // TODO: Is this really necessary? lickList( );
1975 + // buildInstalledC
what's up with this?
Apart from that, code looks ok to me!