Merge lp:~music-app-dev/music-app/prototype-now-playing-side-panel into lp:music-app/remix

Proposed by Victor Thompson
Status: Superseded
Proposed branch: lp:~music-app-dev/music-app/prototype-now-playing-side-panel
Merge into: lp:music-app/remix
Diff against target: 5322 lines (+2426/-1833) (has conflicts)
55 files modified
CMakeLists.txt (+13/-7)
app/CMakeLists.txt (+10/-0)
app/components/BlurredBackground.qml (+1/-1)
app/components/CMakeLists.txt (+6/-2)
app/components/CoverGrid.qml (+2/-2)
app/components/Dialog/CMakeLists.txt (+4/-0)
app/components/Dialog/ContentHubErrorDialog.qml (+38/-0)
app/components/Dialog/ContentHubNotFoundDialog.qml (+46/-0)
app/components/Dialog/ContentHubWaitDialog.qml (+37/-0)
app/components/Dialog/EditPlaylistDialog.qml (+80/-0)
app/components/Dialog/NewPlaylistDialog.qml (+75/-0)
app/components/Dialog/RemovePlaylistDialog.qml (+59/-0)
app/components/Flickables/CMakeLists.txt (+4/-0)
app/components/Flickables/CardView.qml (+1/-0)
app/components/Flickables/MultiSelectListView.qml (+51/-0)
app/components/Flickables/MusicListView.qml (+32/-0)
app/components/HeadState/CMakeLists.txt (+4/-0)
app/components/HeadState/MultiSelectHeadState.qml (+108/-0)
app/components/HeadState/PlaylistsHeadState.qml (+47/-0)
app/components/HeadState/SearchableHeadState.qml (+34/-0)
app/components/LibraryListModel.qml (+2/-2)
app/components/ListItemActions/AddToPlaylist.qml (+2/-8)
app/components/ListItemActions/AddToQueue.qml (+1/-1)
app/components/MusicPage.qml (+2/-2)
app/components/MusicRow.qml (+1/-1)
app/components/MusicToolbar.qml (+244/-314)
app/components/NowPlaying.qml (+550/-0)
app/components/PlaylistsEmptyState.qml (+58/-0)
app/components/ViewButton/CMakeLists.txt (+4/-0)
app/components/ViewButton/PlayAllButton.qml (+34/-0)
app/components/ViewButton/QueueAllButton.qml (+46/-0)
app/components/ViewButton/ShuffleButton.qml (+46/-0)
app/components/Walkthrough/Slide1.qml (+1/-1)
app/components/Walkthrough/Slide2.qml (+1/-1)
app/components/Walkthrough/Slide3.qml (+1/-1)
app/components/Walkthrough/Walkthrough.qml (+1/-1)
app/components/WorkerModelLoader.qml (+1/-1)
app/logic/CMakeLists.txt (+4/-0)
app/music-app.qml (+352/-155)
app/ui/AddToPlaylist.qml (+22/-23)
app/ui/Albums.qml (+22/-27)
app/ui/ArtistView.qml (+29/-73)
app/ui/Artists.qml (+19/-25)
app/ui/CMakeLists.txt (+4/-0)
app/ui/Genres.qml (+21/-26)
app/ui/LibraryEmptyState.qml (+123/-0)
app/ui/NowPlayingView.qml (+23/-624)
app/ui/Playlists.qml (+31/-39)
app/ui/Recent.qml (+18/-23)
app/ui/Songs.qml (+23/-118)
app/ui/SongsView.qml (+40/-283)
click/CMakeLists.txt (+0/-15)
tests/autopilot/music_app/__init__.py (+38/-44)
tests/autopilot/music_app/tests/__init__.py (+2/-2)
tests/autopilot/music_app/tests/test_music.py (+8/-11)
Text conflict in app/music-app.qml
To merge this branch: bzr merge lp:~music-app-dev/music-app/prototype-now-playing-side-panel
Reviewer Review Type Date Requested Status
Music App Developers Pending
Review via email: mp+249143@code.launchpad.net

This proposal has been superseded by a proposal from 2015-02-10.

Commit message

* Conditional layouts for the music app

Description of the change

DO NOT MERGE, just want to keep this MP to see a diff of what's been added.

To post a comment you must log in.
842. By Victor Thompson

Add ItemLayout to the Layouts.

Unmerged revisions

842. By Victor Thompson

Add ItemLayout to the Layouts.

841. By Victor Thompson

start conditional layout prototype

840. By Andrew Hayzen

* Create generic MultiSelectListView.qml for handling multiselect
* Create MusicListView.qml with our patches to ListView
* Move Flickables from components into their own Flickables folder.

Approved by Victor Thompson, Ubuntu Phone Apps Jenkins Bot.

839. By Andrew Hayzen

* Do not allow LibraryEmptyState loader to be active if content-hub is running. Fixes: https://bugs.launchpad.net/bugs/1418366.

Approved by Victor Thompson, Ubuntu Phone Apps Jenkins Bot.

838. By Andrew Hayzen

* Move all Dialog components into their own files in components/Dialog.

Approved by Victor Thompson, Ubuntu Phone Apps Jenkins Bot.

837. By Andrew Hayzen

* Create common components for Play All, Queue All and Shuffle buttons within 'Views'.

Approved by Victor Thompson, Ubuntu Phone Apps Jenkins Bot.

836. By Andrew Hayzen

* Split EmptyStates into a page (for noMusic) and a component (for noPlaylists) and load in async.

Approved by Ubuntu Phone Apps Jenkins Bot, Victor Thompson.

835. By Andrew Hayzen

* Move existing head states into HeadState folder
* Create MultiSelectHeadState SearchableHeadState PlaylistHeadState
* Migrate AddToPlaylist Albums Artists Genres NowPlaying Playlists Songs SongsView to use the new HeadState.

Approved by Ubuntu Phone Apps Jenkins Bot, Victor Thompson.

834. By Andrew Hayzen

* Make use of SDK PageStack.push(url, properties) instead of creating the component, object and pushing ourselves.

Approved by Victor Thompson, Ubuntu Phone Apps Jenkins Bot.

833. By Andrew Hayzen

* Rename common to components
* Move all qml components in components
* Move any JS scripts into logic.

Approved by Ubuntu Phone Apps Jenkins Bot, Victor Thompson.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-09-08 08:08:13 +0000
3+++ CMakeLists.txt 2015-02-10 03:13:44 +0000
4@@ -13,10 +13,10 @@
5 set(DESKTOP_FILE "${PROJECT_NAME}_${APP_NAME}.desktop")
6 set(URLS_FILE "${PROJECT_NAME}_${APP_NAME}.url-dispatcher")
7 set(APP_HARDCODE music-app)
8-set(MAIN_QML music-app.qml)
9-set(ICON_FILE images/music-app@30.png)
10+set(MAIN_QML app/music-app.qml)
11+set(ICON_FILE app/graphics/music-app@30.png)
12 set(AUTOPILOT_DIR music_app)
13-set(UBUNTU_MANIFEST_PATH "click/manifest.json.in" CACHE INTERNAL "Tell QtCreator location and name of the manifest file")
14+set(UBUNTU_MANIFEST_PATH "manifest.json.in" CACHE INTERNAL "Tell QtCreator location and name of the manifest file")
15
16 if(CLICK_MODE)
17 if(NOT DEFINED BZR_SOURCE)
18@@ -30,6 +30,14 @@
19 set(EXEC "qmlscene -qt5 ${MAIN_QML} --url=%u -I ./plugins")
20 set(DESKTOP_DIR ${DATA_DIR})
21 set(URLS_DIR ${DATA_DIR})
22+
23+ if(NOT BZR_REVNO)
24+ set(BZR_REVNO "latest")
25+ endif(NOT BZR_REVNO)
26+
27+ configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json)
28+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json apparmor.json
29+ music-app-content.json DESTINATION ${CMAKE_INSTALL_PREFIX})
30 else(CLICK_MODE)
31 set(DATA_DIR ${CMAKE_INSTALL_DATADIR}/${APP_HARDCODE})
32 set(EXEC ${APP_HARDCODE})
33@@ -53,7 +61,7 @@
34 file(GLOB SRC_FILES
35 RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
36 *.qml *.js *.png *.js *.json)
37-install(DIRECTORY common images DESTINATION ${DATA_DIR})
38+install(DIRECTORY app DESTINATION ${DATA_DIR})
39 install(FILES ${SRC_FILES} ${ICON_FILE} DESTINATION ${DATA_DIR})
40
41 configure_file(${DESKTOP_FILE}.in.in ${DESKTOP_FILE}.in)
42@@ -71,9 +79,7 @@
43 # Tests
44 enable_testing()
45
46-add_subdirectory(click)
47-add_subdirectory(common)
48-add_subdirectory(images)
49+add_subdirectory(app)
50 add_subdirectory(po)
51 add_subdirectory(tests)
52
53
54=== added directory 'app'
55=== added file 'app/CMakeLists.txt'
56--- app/CMakeLists.txt 1970-01-01 00:00:00 +0000
57+++ app/CMakeLists.txt 2015-02-10 03:13:44 +0000
58@@ -0,0 +1,10 @@
59+add_subdirectory(components)
60+add_subdirectory(graphics)
61+add_subdirectory(logic)
62+add_subdirectory(ui)
63+
64+# make the qml files visible on qtcreator
65+file(GLOB APP_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
66+*.qml *.js *.png *.js *.json)
67+
68+add_custom_target(com_ubuntu_music_APP_Files ALL SOURCES ${APP_FILES})
69
70=== renamed directory 'common' => 'app/components'
71=== modified file 'app/components/BlurredBackground.qml'
72--- common/BlurredBackground.qml 2014-11-11 02:46:42 +0000
73+++ app/components/BlurredBackground.qml 2015-02-10 03:13:44 +0000
74@@ -25,7 +25,7 @@
75 Item {
76 width: parent.width
77
78- property string art // : player.currentMetaFile === "" ? Qt.resolvedUrl("../images/music-app-cover@30.png") : player.currentMetaArt
79+ property string art // : player.currentMetaFile === "" ? Qt.resolvedUrl("../graphics/music-app-cover@30.png") : player.currentMetaArt
80
81 // dark layer
82 Rectangle {
83
84=== modified file 'app/components/CMakeLists.txt'
85--- common/CMakeLists.txt 2015-01-20 17:47:30 +0000
86+++ app/components/CMakeLists.txt 2015-02-10 03:13:44 +0000
87@@ -1,7 +1,11 @@
88+add_subdirectory(Dialog)
89+add_subdirectory(HeadState)
90+add_subdirectory(Flickables)
91 add_subdirectory(ListItemActions)
92+add_subdirectory(ViewButton)
93 add_subdirectory(Walkthrough)
94
95 # make the qml files visible on qtcreator
96-file(GLOB COMMON_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
97+file(GLOB COMPONENTS_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
98
99-add_custom_target(com_ubuntu_music_COMMON_QMLFiles ALL SOURCES ${COMMON_QML_FILES})
100+add_custom_target(com_ubuntu_music_COMPONENTS_QMLFiles ALL SOURCES ${COMPONENTS_QML_FILES})
101
102=== modified file 'app/components/CoverGrid.qml'
103--- common/CoverGrid.qml 2015-01-11 16:34:22 +0000
104+++ app/components/CoverGrid.qml 2015-02-10 03:13:44 +0000
105@@ -60,7 +60,7 @@
106 ? (coverGrid.covers[index].art !== undefined
107 ? coverGrid.covers[index].art
108 : "image://albumart/artist=" + coverGrid.covers[index].author + "&album=" + coverGrid.covers[index].album)
109- : Qt.resolvedUrl("../images/music-app-cover@30.png")
110+ : Qt.resolvedUrl("../graphics/music-app-cover@30.png")
111
112 // TODO: This should be investigated once http://pad.lv/1391368
113 // is resolved. Once it is, these can either be set to
114@@ -73,7 +73,7 @@
115
116 onStatusChanged: {
117 if (status === Image.Error) {
118- source = Qt.resolvedUrl("../images/music-app-cover@30.png")
119+ source = Qt.resolvedUrl("../graphics/music-app-cover@30.png")
120 } else if (status === Image.Ready && index === 0) {
121 firstSource = source
122 }
123
124=== added directory 'app/components/Dialog'
125=== added file 'app/components/Dialog/CMakeLists.txt'
126--- app/components/Dialog/CMakeLists.txt 1970-01-01 00:00:00 +0000
127+++ app/components/Dialog/CMakeLists.txt 2015-02-10 03:13:44 +0000
128@@ -0,0 +1,4 @@
129+# make the qml files visible on qtcreator
130+file(GLOB DIALOG_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
131+
132+add_custom_target(com_ubuntu_music_DIALOG_QMLFiles ALL SOURCES ${DIALOG_QML_FILES})
133
134=== added file 'app/components/Dialog/ContentHubErrorDialog.qml'
135--- app/components/Dialog/ContentHubErrorDialog.qml 1970-01-01 00:00:00 +0000
136+++ app/components/Dialog/ContentHubErrorDialog.qml 2015-02-10 03:13:44 +0000
137@@ -0,0 +1,38 @@
138+/*
139+ * Copyright (C) 2013, 2014, 2015
140+ * Andrew Hayzen <ahayzen@gmail.com>
141+ * Daniel Holm <d.holmen@gmail.com>
142+ * Victor Thompson <victor.thompson@gmail.com>
143+ *
144+ * This program is free software; you can redistribute it and/or modify
145+ * it under the terms of the GNU General Public License as published by
146+ * the Free Software Foundation; version 3.
147+ *
148+ * This program is distributed in the hope that it will be useful,
149+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
150+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
151+ * GNU General Public License for more details.
152+ *
153+ * You should have received a copy of the GNU General Public License
154+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
155+ */
156+
157+import QtQuick 2.3
158+import Ubuntu.Components 1.1
159+import Ubuntu.Components.Popups 1.0
160+
161+Dialog {
162+ id: dialogContentHubError
163+
164+ property alias errorText: errorLabel.text
165+
166+ Label {
167+ id: errorLabel
168+ color: styleMusic.common.black
169+ }
170+
171+ Button {
172+ text: i18n.tr("OK")
173+ onClicked: PopupUtils.close(dialogContentHubError)
174+ }
175+}
176
177=== added file 'app/components/Dialog/ContentHubNotFoundDialog.qml'
178--- app/components/Dialog/ContentHubNotFoundDialog.qml 1970-01-01 00:00:00 +0000
179+++ app/components/Dialog/ContentHubNotFoundDialog.qml 2015-02-10 03:13:44 +0000
180@@ -0,0 +1,46 @@
181+/*
182+ * Copyright (C) 2013, 2014, 2015
183+ * Andrew Hayzen <ahayzen@gmail.com>
184+ * Daniel Holm <d.holmen@gmail.com>
185+ * Victor Thompson <victor.thompson@gmail.com>
186+ *
187+ * This program is free software; you can redistribute it and/or modify
188+ * it under the terms of the GNU General Public License as published by
189+ * the Free Software Foundation; version 3.
190+ *
191+ * This program is distributed in the hope that it will be useful,
192+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
193+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
194+ * GNU General Public License for more details.
195+ *
196+ * You should have received a copy of the GNU General Public License
197+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
198+ */
199+
200+import QtQuick 2.3
201+import Ubuntu.Components 1.1
202+import Ubuntu.Components.Popups 1.0
203+
204+Dialog {
205+ id: dialogContentHubNotFound
206+
207+ Label {
208+ color: styleMusic.common.black
209+ text: i18n.tr("Imported file not found")
210+ }
211+
212+ Button {
213+ text: i18n.tr("Wait")
214+ onClicked: {
215+ PopupUtils.close(dialogContentHubNotFound)
216+
217+ contentHubWaitForFile.dialog = PopupUtils.open(Qt.resolvedUrl("ContentHubWaitDialog.qml"), mainView)
218+ contentHubWaitForFile.start();
219+ }
220+ }
221+
222+ Button {
223+ text: i18n.tr("Cancel")
224+ onClicked: PopupUtils.close(dialogContentHubNotFound)
225+ }
226+}
227
228=== added file 'app/components/Dialog/ContentHubWaitDialog.qml'
229--- app/components/Dialog/ContentHubWaitDialog.qml 1970-01-01 00:00:00 +0000
230+++ app/components/Dialog/ContentHubWaitDialog.qml 2015-02-10 03:13:44 +0000
231@@ -0,0 +1,37 @@
232+/*
233+ * Copyright (C) 2013, 2014, 2015
234+ * Andrew Hayzen <ahayzen@gmail.com>
235+ * Daniel Holm <d.holmen@gmail.com>
236+ * Victor Thompson <victor.thompson@gmail.com>
237+ *
238+ * This program is free software; you can redistribute it and/or modify
239+ * it under the terms of the GNU General Public License as published by
240+ * the Free Software Foundation; version 3.
241+ *
242+ * This program is distributed in the hope that it will be useful,
243+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
244+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
245+ * GNU General Public License for more details.
246+ *
247+ * You should have received a copy of the GNU General Public License
248+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
249+ */
250+
251+import QtQuick 2.3
252+import Ubuntu.Components 1.1
253+import Ubuntu.Components.Popups 1.0
254+import "../"
255+
256+Dialog {
257+ id: dialogContentHubWait
258+
259+ LoadingSpinnerComponent {
260+ anchors {
261+ horizontalCenter: parent.horizontalCenter
262+ top: undefined
263+ margins: units.gu(0)
264+ }
265+ loadingText: i18n.tr("Waiting for file(s)...")
266+ visible: true
267+ }
268+}
269
270=== added file 'app/components/Dialog/EditPlaylistDialog.qml'
271--- app/components/Dialog/EditPlaylistDialog.qml 1970-01-01 00:00:00 +0000
272+++ app/components/Dialog/EditPlaylistDialog.qml 2015-02-10 03:13:44 +0000
273@@ -0,0 +1,80 @@
274+/*
275+ * Copyright (C) 2013, 2014, 2015
276+ * Andrew Hayzen <ahayzen@gmail.com>
277+ * Daniel Holm <d.holmen@gmail.com>
278+ * Victor Thompson <victor.thompson@gmail.com>
279+ *
280+ * This program is free software; you can redistribute it and/or modify
281+ * it under the terms of the GNU General Public License as published by
282+ * the Free Software Foundation; version 3.
283+ *
284+ * This program is distributed in the hope that it will be useful,
285+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
286+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
287+ * GNU General Public License for more details.
288+ *
289+ * You should have received a copy of the GNU General Public License
290+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
291+ */
292+
293+import QtQuick 2.3
294+import Ubuntu.Components 1.1
295+import Ubuntu.Components.Popups 1.0
296+import QtQuick.LocalStorage 2.0
297+import "../../logic/meta-database.js" as Library
298+import "../../logic/playlists.js" as Playlists
299+
300+Dialog {
301+ id: dialogEditPlaylist
302+ // TRANSLATORS: this is a title of a dialog with a prompt to rename a playlist
303+ title: i18n.tr("Rename playlist")
304+
305+ property string oldPlaylistName: ""
306+
307+ TextField {
308+ id: playlistName
309+ inputMethodHints: Qt.ImhNoPredictiveText
310+ placeholderText: i18n.tr("Enter playlist name")
311+ }
312+ Label {
313+ id: editplaylistoutput
314+ color: "red"
315+ visible: false
316+ }
317+
318+ Button {
319+ text: i18n.tr("Change")
320+ color: styleMusic.dialog.confirmButtonColor
321+ onClicked: {
322+ editplaylistoutput.visible = true
323+
324+ if (playlistName.text.length > 0) { // make sure something is acually inputed
325+ console.debug("Debug: User changed name from "+oldPlaylistName+" to "+playlistName.text)
326+
327+ if (Playlists.renamePlaylist(oldPlaylistName, playlistName.text) === true) {
328+
329+ if (Library.recentContainsPlaylist(oldPlaylistName)) {
330+ Library.recentRenamePlaylist(oldPlaylistName, playlistName.text)
331+ }
332+
333+ line2 = playlistName.text
334+
335+ playlistChangedHelper() // update recent/playlist models
336+
337+ PopupUtils.close(dialogEditPlaylist)
338+ }
339+ else {
340+ editplaylistoutput.text = i18n.tr("Playlist already exists")
341+ }
342+ }
343+ else {
344+ editplaylistoutput.text = i18n.tr("Please type in a name.")
345+ }
346+ }
347+ }
348+ Button {
349+ text: i18n.tr("Cancel")
350+ color: styleMusic.dialog.cancelButtonColor
351+ onClicked: PopupUtils.close(dialogEditPlaylist)
352+ }
353+}
354
355=== added file 'app/components/Dialog/NewPlaylistDialog.qml'
356--- app/components/Dialog/NewPlaylistDialog.qml 1970-01-01 00:00:00 +0000
357+++ app/components/Dialog/NewPlaylistDialog.qml 2015-02-10 03:13:44 +0000
358@@ -0,0 +1,75 @@
359+/*
360+ * Copyright (C) 2013, 2014, 2015
361+ * Andrew Hayzen <ahayzen@gmail.com>
362+ * Daniel Holm <d.holmen@gmail.com>
363+ * Victor Thompson <victor.thompson@gmail.com>
364+ *
365+ * This program is free software; you can redistribute it and/or modify
366+ * it under the terms of the GNU General Public License as published by
367+ * the Free Software Foundation; version 3.
368+ *
369+ * This program is distributed in the hope that it will be useful,
370+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
371+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
372+ * GNU General Public License for more details.
373+ *
374+ * You should have received a copy of the GNU General Public License
375+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
376+ */
377+
378+import QtQuick 2.3
379+import Ubuntu.Components 1.1
380+import Ubuntu.Components.Popups 1.0
381+import QtQuick.LocalStorage 2.0
382+import "../../logic/playlists.js" as Playlists
383+
384+
385+Dialog {
386+ id: dialogNewPlaylist
387+ objectName: "dialogNewPlaylist"
388+ title: i18n.tr("New playlist")
389+ TextField {
390+ id: playlistName
391+ objectName: "playlistNameTextField"
392+ placeholderText: i18n.tr("Enter playlist name")
393+ inputMethodHints: Qt.ImhNoPredictiveText
394+ }
395+ Label {
396+ id: newplaylistoutput
397+ color: "red"
398+ visible: false // should only be visible when an error is made.
399+ }
400+
401+ Button {
402+ text: i18n.tr("Create")
403+ color: styleMusic.dialog.confirmButtonColor
404+ objectName: "newPlaylistDialogCreateButton"
405+ onClicked: {
406+ newplaylistoutput.visible = false // make sure its hidden now if there was an error last time
407+ if (playlistName.text.length > 0) { // make sure something is acually inputed
408+ if (Playlists.addPlaylist(playlistName.text) === true) {
409+ console.debug("Debug: User created a new playlist named: ", playlistName.text)
410+
411+ playlistModel.filterPlaylists(); // reload model
412+
413+ PopupUtils.close(dialogNewPlaylist)
414+ }
415+ else {
416+ console.debug("Debug: Playlist already exists")
417+ newplaylistoutput.visible = true
418+ newplaylistoutput.text = i18n.tr("Playlist already exists")
419+ }
420+ }
421+ else {
422+ newplaylistoutput.visible = true
423+ newplaylistoutput.text = i18n.tr("Please type in a name.")
424+ }
425+ }
426+ }
427+
428+ Button {
429+ text: i18n.tr("Cancel")
430+ color: styleMusic.dialog.cancelButtonColor
431+ onClicked: PopupUtils.close(dialogNewPlaylist)
432+ }
433+}
434
435=== added file 'app/components/Dialog/RemovePlaylistDialog.qml'
436--- app/components/Dialog/RemovePlaylistDialog.qml 1970-01-01 00:00:00 +0000
437+++ app/components/Dialog/RemovePlaylistDialog.qml 2015-02-10 03:13:44 +0000
438@@ -0,0 +1,59 @@
439+/*
440+ * Copyright (C) 2013, 2014, 2015
441+ * Andrew Hayzen <ahayzen@gmail.com>
442+ * Daniel Holm <d.holmen@gmail.com>
443+ * Victor Thompson <victor.thompson@gmail.com>
444+ *
445+ * This program is free software; you can redistribute it and/or modify
446+ * it under the terms of the GNU General Public License as published by
447+ * the Free Software Foundation; version 3.
448+ *
449+ * This program is distributed in the hope that it will be useful,
450+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
451+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
452+ * GNU General Public License for more details.
453+ *
454+ * You should have received a copy of the GNU General Public License
455+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
456+ */
457+
458+import QtQuick 2.3
459+import Ubuntu.Components 1.1
460+import Ubuntu.Components.Popups 1.0
461+import QtQuick.LocalStorage 2.0
462+import "../../logic/meta-database.js" as Library
463+import "../../logic/playlists.js" as Playlists
464+
465+Dialog {
466+ id: dialogRemovePlaylist
467+ // TRANSLATORS: this is a title of a dialog with a prompt to delete a playlist
468+ title: i18n.tr("Permanently delete playlist?")
469+ text: "("+i18n.tr("This cannot be undone")+")"
470+
471+ property string oldPlaylistName
472+
473+ Button {
474+ text: i18n.tr("Remove")
475+ color: styleMusic.dialog.confirmRemoveButtonColor
476+ onClicked: {
477+ // removing playlist
478+ Playlists.removePlaylist(dialogRemovePlaylist.oldPlaylistName)
479+
480+ if (Library.recentContainsPlaylist(dialogRemovePlaylist.oldPlaylistName)) {
481+ Library.recentRemovePlaylist(dialogRemovePlaylist.oldPlaylistName)
482+ }
483+
484+ playlistChangedHelper(true) // update recent/playlist models
485+
486+ songStackPage.page = undefined
487+ PopupUtils.close(dialogRemovePlaylist)
488+
489+ mainPageStack.goBack()
490+ }
491+ }
492+ Button {
493+ text: i18n.tr("Cancel")
494+ color: styleMusic.dialog.cancelButtonColor
495+ onClicked: PopupUtils.close(dialogRemovePlaylist)
496+ }
497+}
498
499=== added directory 'app/components/Flickables'
500=== added file 'app/components/Flickables/CMakeLists.txt'
501--- app/components/Flickables/CMakeLists.txt 1970-01-01 00:00:00 +0000
502+++ app/components/Flickables/CMakeLists.txt 2015-02-10 03:13:44 +0000
503@@ -0,0 +1,4 @@
504+# make the qml files visible on qtcreator
505+file(GLOB FLICKABLES_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
506+
507+add_custom_target(com_ubuntu_music_FLICKABLES_QMLFiles ALL SOURCES ${FLICKABLES_QML_FILES})
508
509=== renamed file 'common/CardView.qml' => 'app/components/Flickables/CardView.qml'
510--- common/CardView.qml 2014-11-23 03:50:40 +0000
511+++ app/components/Flickables/CardView.qml 2015-02-10 03:13:44 +0000
512@@ -17,6 +17,7 @@
513
514 import QtQuick 2.3
515 import Ubuntu.Components 1.1
516+import "../"
517
518
519 Flickable {
520
521=== added file 'app/components/Flickables/MultiSelectListView.qml'
522--- app/components/Flickables/MultiSelectListView.qml 1970-01-01 00:00:00 +0000
523+++ app/components/Flickables/MultiSelectListView.qml 2015-02-10 03:13:44 +0000
524@@ -0,0 +1,51 @@
525+/*
526+ * Copyright (C) 2013, 2014, 2015
527+ * Andrew Hayzen <ahayzen@gmail.com>
528+ * Daniel Holm <d.holmen@gmail.com>
529+ * Victor Thompson <victor.thompson@gmail.com>
530+ *
531+ * This program is free software; you can redistribute it and/or modify
532+ * it under the terms of the GNU General Public License as published by
533+ * the Free Software Foundation; version 3.
534+ *
535+ * This program is distributed in the hope that it will be useful,
536+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
537+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
538+ * GNU General Public License for more details.
539+ *
540+ * You should have received a copy of the GNU General Public License
541+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
542+ */
543+
544+import QtQuick 2.3
545+import Ubuntu.Components 1.1
546+
547+MusicListView {
548+ property var selectedItems: []
549+
550+ signal clearSelection()
551+ signal closeSelection()
552+ signal selectAll()
553+
554+ onClearSelection: selectedItems = []
555+ onCloseSelection: {
556+ clearSelection()
557+ state = "normal"
558+ }
559+ onSelectAll: {
560+ var tmp = selectedItems
561+
562+ for (var i=0; i < model.count; i++) {
563+ if (tmp.indexOf(i) === -1) {
564+ tmp.push(i)
565+ }
566+ }
567+
568+ selectedItems = tmp
569+ }
570+ onVisibleChanged: {
571+ if (!visible) {
572+ closeSelection()
573+ }
574+ }
575+}
576
577=== added file 'app/components/Flickables/MusicListView.qml'
578--- app/components/Flickables/MusicListView.qml 1970-01-01 00:00:00 +0000
579+++ app/components/Flickables/MusicListView.qml 2015-02-10 03:13:44 +0000
580@@ -0,0 +1,32 @@
581+/*
582+ * Copyright (C) 2013, 2014, 2015
583+ * Andrew Hayzen <ahayzen@gmail.com>
584+ * Daniel Holm <d.holmen@gmail.com>
585+ * Victor Thompson <victor.thompson@gmail.com>
586+ *
587+ * This program is free software; you can redistribute it and/or modify
588+ * it under the terms of the GNU General Public License as published by
589+ * the Free Software Foundation; version 3.
590+ *
591+ * This program is distributed in the hope that it will be useful,
592+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
593+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
594+ * GNU General Public License for more details.
595+ *
596+ * You should have received a copy of the GNU General Public License
597+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
598+ */
599+
600+import QtQuick 2.3
601+import Ubuntu.Components 1.1
602+
603+
604+ListView {
605+ Component.onCompleted: {
606+ // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
607+ // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
608+ var scaleFactor = units.gridUnit / 8;
609+ maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
610+ flickDeceleration = flickDeceleration * scaleFactor;
611+ }
612+}
613
614=== added directory 'app/components/HeadState'
615=== added file 'app/components/HeadState/CMakeLists.txt'
616--- app/components/HeadState/CMakeLists.txt 1970-01-01 00:00:00 +0000
617+++ app/components/HeadState/CMakeLists.txt 2015-02-10 03:13:44 +0000
618@@ -0,0 +1,4 @@
619+# make the qml files visible on qtcreator
620+file(GLOB HEAD_STATE_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
621+
622+add_custom_target(com_ubuntu_music_HEAD_STATE_QMLFiles ALL SOURCES ${HEAD_STATE_QML_FILES})
623
624=== added file 'app/components/HeadState/MultiSelectHeadState.qml'
625--- app/components/HeadState/MultiSelectHeadState.qml 1970-01-01 00:00:00 +0000
626+++ app/components/HeadState/MultiSelectHeadState.qml 2015-02-10 03:13:44 +0000
627@@ -0,0 +1,108 @@
628+/*
629+ * Copyright (C) 2015
630+ * Andrew Hayzen <ahayzen@gmail.com>
631+ * Victor Thompson <victor.thompson@gmail.com>
632+ *
633+ * This program is free software; you can redistribute it and/or modify
634+ * it under the terms of the GNU General Public License as published by
635+ * the Free Software Foundation; version 3.
636+ *
637+ * This program is distributed in the hope that it will be useful,
638+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
639+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
640+ * GNU General Public License for more details.
641+ *
642+ * You should have received a copy of the GNU General Public License
643+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
644+ */
645+
646+import QtQuick 2.3
647+import Ubuntu.Components 1.1
648+
649+PageHeadState {
650+ id: selectionState
651+ actions: [
652+ Action {
653+ iconName: "select"
654+ text: i18n.tr("Select All")
655+ onTriggered: {
656+ if (listview.selectedItems.length === listview.model.count) {
657+ listview.clearSelection()
658+ } else {
659+ listview.selectAll()
660+ }
661+ }
662+ },
663+ Action {
664+ enabled: listview.selectedItems.length !== 0
665+ iconName: "add-to-playlist"
666+ text: i18n.tr("Add to playlist")
667+ onTriggered: {
668+ var items = []
669+
670+ for (var i=0; i < listview.selectedItems.length; i++) {
671+ items.push(makeDict(listview.model.get(listview.selectedItems[i], listview.model.RoleModelData)));
672+ }
673+
674+ mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
675+ {"chosenElements": items})
676+
677+ listview.closeSelection()
678+ }
679+ },
680+ Action {
681+ enabled: listview.selectedItems.length > 0
682+ iconName: "add"
683+ text: i18n.tr("Add to queue")
684+ visible: addToQueue
685+
686+ onTriggered: {
687+ var items = []
688+
689+ for (var i=0; i < listview.selectedItems.length; i++) {
690+ items.push(listview.model.get(listview.selectedItems[i], listview.model.RoleModelData));
691+ }
692+
693+ trackQueue.appendList(items)
694+
695+ listview.closeSelection()
696+ }
697+ },
698+ Action {
699+ enabled: listview.selectedItems.length > 0
700+ iconName: "delete"
701+ text: i18n.tr("Delete")
702+ visible: removable
703+
704+ onTriggered: {
705+ removed(listview.selectedItems)
706+
707+ listview.closeSelection()
708+ }
709+ }
710+
711+ ]
712+ backAction: Action {
713+ text: i18n.tr("Cancel selection")
714+ iconName: "back"
715+ onTriggered: {
716+ listview.clearSelection()
717+ listview.state = "normal"
718+ }
719+ }
720+ head: thisPage.head
721+ name: "selection"
722+
723+ PropertyChanges {
724+ target: thisPage.head
725+ backAction: selectionState.backAction
726+ actions: selectionState.actions
727+ }
728+
729+ property bool addToQueue: true
730+ property ListView listview
731+ property bool removable: false
732+ property Page thisPage
733+
734+ signal removed(var selectedItems)
735+}
736
737=== added file 'app/components/HeadState/PlaylistsHeadState.qml'
738--- app/components/HeadState/PlaylistsHeadState.qml 1970-01-01 00:00:00 +0000
739+++ app/components/HeadState/PlaylistsHeadState.qml 2015-02-10 03:13:44 +0000
740@@ -0,0 +1,47 @@
741+/*
742+ * Copyright (C) 2015
743+ * Andrew Hayzen <ahayzen@gmail.com>
744+ * Victor Thompson <victor.thompson@gmail.com>
745+ *
746+ * This program is free software; you can redistribute it and/or modify
747+ * it under the terms of the GNU General Public License as published by
748+ * the Free Software Foundation; version 3.
749+ *
750+ * This program is distributed in the hope that it will be useful,
751+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
752+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
753+ * GNU General Public License for more details.
754+ *
755+ * You should have received a copy of the GNU General Public License
756+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
757+ */
758+
759+import QtQuick 2.3
760+import Ubuntu.Components 1.1
761+import Ubuntu.Components.Popups 1.0
762+
763+
764+PageHeadState {
765+ name: "default"
766+ head: thisPage.head
767+ actions: [
768+ Action {
769+ id: newPlaylistAction
770+ objectName: "newPlaylistButton"
771+ iconName: "add"
772+ onTriggered: {
773+ customdebug("New playlist.")
774+ PopupUtils.open(Qt.resolvedUrl("../Dialog/NewPlaylistDialog.qml"), mainView)
775+ }
776+ },
777+ Action {
778+ id: searchAction
779+ iconName: "search"
780+ onTriggered: thisPage.state = "search"
781+ }
782+ ]
783+
784+ property alias newPlaylistEnabled: newPlaylistAction.enabled
785+ property alias searchEnabled: searchAction.enabled
786+ property Page thisPage
787+}
788
789=== renamed file 'common/SearchHeadState.qml' => 'app/components/HeadState/SearchHeadState.qml'
790=== added file 'app/components/HeadState/SearchableHeadState.qml'
791--- app/components/HeadState/SearchableHeadState.qml 1970-01-01 00:00:00 +0000
792+++ app/components/HeadState/SearchableHeadState.qml 2015-02-10 03:13:44 +0000
793@@ -0,0 +1,34 @@
794+/*
795+ * Copyright (C) 2015
796+ * Andrew Hayzen <ahayzen@gmail.com>
797+ * Victor Thompson <victor.thompson@gmail.com>
798+ *
799+ * This program is free software; you can redistribute it and/or modify
800+ * it under the terms of the GNU General Public License as published by
801+ * the Free Software Foundation; version 3.
802+ *
803+ * This program is distributed in the hope that it will be useful,
804+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
805+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
806+ * GNU General Public License for more details.
807+ *
808+ * You should have received a copy of the GNU General Public License
809+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
810+ */
811+
812+import QtQuick 2.3
813+import Ubuntu.Components 1.1
814+
815+
816+PageHeadState {
817+ name: "default"
818+ head: thisPage.head
819+ actions: Action {
820+ id: searchAction
821+ iconName: "search"
822+ onTriggered: thisPage.state = "search"
823+ }
824+
825+ property alias searchEnabled: searchAction.enabled
826+ property Page thisPage
827+}
828
829=== renamed file 'LibraryListModel.qml' => 'app/components/LibraryListModel.qml'
830--- LibraryListModel.qml 2015-01-20 23:58:27 +0000
831+++ app/components/LibraryListModel.qml 2015-02-10 03:13:44 +0000
832@@ -19,8 +19,8 @@
833
834 import QtQuick 2.3
835 import QtQuick.LocalStorage 2.0
836-import "meta-database.js" as Library
837-import "playlists.js" as Playlists
838+import "../logic/meta-database.js" as Library
839+import "../logic/playlists.js" as Playlists
840
841 Item {
842 id: libraryListModelItem
843
844=== modified file 'app/components/ListItemActions/AddToPlaylist.qml'
845--- common/ListItemActions/AddToPlaylist.qml 2015-01-11 15:54:04 +0000
846+++ app/components/ListItemActions/AddToPlaylist.qml 2015-02-10 03:13:44 +0000
847@@ -30,13 +30,7 @@
848 onTriggered: {
849 console.debug("Debug: Add track to playlist");
850
851- var comp = Qt.createComponent("../../MusicaddtoPlaylist.qml")
852- var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": [makeDict(model)]});
853-
854- if (addToPlaylist == null) { // Error Handling
855- console.log("Error creating object");
856- }
857-
858- mainPageStack.push(addToPlaylist)
859+ mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
860+ {"chosenElements": [makeDict(model)]})
861 }
862 }
863
864=== modified file 'app/components/ListItemActions/AddToQueue.qml'
865--- common/ListItemActions/AddToQueue.qml 2014-10-28 00:00:26 +0000
866+++ app/components/ListItemActions/AddToQueue.qml 2015-02-10 03:13:44 +0000
867@@ -19,7 +19,7 @@
868 import QtQuick 2.3
869 import QtQuick.LocalStorage 2.0
870 import Ubuntu.Components 1.1
871-import "../../meta-database.js" as Library
872+import "../../logic/meta-database.js" as Library
873
874 Action {
875 iconName: "add"
876
877=== modified file 'app/components/MusicPage.qml'
878--- common/MusicPage.qml 2015-01-10 19:22:08 +0000
879+++ app/components/MusicPage.qml 2015-02-10 03:13:44 +0000
880@@ -25,7 +25,7 @@
881 Page {
882 id: thisPage
883 anchors {
884- bottomMargin: musicToolbar.visible ? musicToolbar.currentHeight : 0
885+ bottomMargin: musicToolbar.visible ? musicToolbar.height : 0
886 fill: parent
887 }
888
889@@ -56,7 +56,7 @@
890
891 onVisibleChanged: {
892 if (visible) {
893- musicToolbar.setPage(thisPage);
894+ mainPageStack.setPage(thisPage);
895 }
896 }
897 }
898
899=== modified file 'app/components/MusicRow.qml'
900--- common/MusicRow.qml 2014-10-23 17:40:04 +0000
901+++ app/components/MusicRow.qml 2015-02-10 03:13:44 +0000
902@@ -55,7 +55,7 @@
903
904 onStatusChanged: {
905 if (status === Image.Error) {
906- source = Qt.resolvedUrl("../images/music-app-cover@30.png")
907+ source = Qt.resolvedUrl("../graphics/music-app-cover@30.png")
908 }
909 }
910 visible: imageSource !== undefined
911
912=== renamed file 'MusicToolbar.qml' => 'app/components/MusicToolbar.qml'
913--- MusicToolbar.qml 2015-01-23 07:22:52 +0000
914+++ app/components/MusicToolbar.qml 2015-02-10 03:13:44 +0000
915@@ -18,335 +18,265 @@
916 */
917
918 import QtQuick 2.3
919-import QtQuick.LocalStorage 2.0
920 import QtMultimedia 5.0
921 import Ubuntu.Components 1.1
922-import Ubuntu.Components.Popups 1.0
923-import "common"
924
925-Item {
926+Rectangle {
927 anchors {
928 bottom: parent.bottom
929 left: parent.left
930 right: parent.right
931 }
932-
933- // Properties storing the current page info
934- property var currentPage: null
935- property var currentTab: null
936-
937- // Properties and signals for the toolbar
938- property alias currentHeight: musicToolbarPanel.height
939- property alias opened: musicToolbarPanel.opened
940-
941- property bool popping: false
942-
943- /* Helper functions */
944-
945- // Back button has been pressed, jump up pageStack or back to parent page
946- function goBack()
947- {
948- if (mainPageStack !== null && mainPageStack.depth > 1) {
949- mainPageStack.pop(currentPage)
950- }
951- }
952-
953- // Pop a specific page in the stack
954- function popPage(page)
955- {
956- var tmpPages = []
957-
958- popping = true
959-
960- while (mainPageStack.currentPage !== page && mainPageStack.depth > 0) {
961- tmpPages.push(mainPageStack.currentPage)
962- mainPageStack.pop()
963- }
964-
965- if (mainPageStack.depth > 0) {
966- mainPageStack.pop()
967- }
968-
969- for (var i=tmpPages.length - 1; i > -1; i--) {
970- mainPageStack.push(tmpPages[i])
971- }
972-
973- popping = false
974- }
975-
976- // Set the current page, and any parent/stacks
977- function setPage(childPage)
978- {
979- if (!popping) {
980- currentPage = childPage;
981- }
982- }
983-
984- Panel {
985- id: musicToolbarPanel
986+ color: styleMusic.common.black
987+ height: units.gu(7.25)
988+ objectName: "musicToolbarObject"
989+
990+ // Hack for autopilot otherwise MusicToolbar appears as QQuickRectangle
991+ // due to bug 1341671 it is required that there is a property so that
992+ // qml doesn't optimise using the parent type
993+ property bool bug1341671workaround: true
994+
995+ /* Toolbar controls */
996+ Item {
997+ id: toolbarControls
998 anchors {
999- left: parent.left
1000- right: parent.right
1001- bottom: parent.bottom
1002- }
1003- height: units.gu(7.25)
1004- locked: true
1005- opened: true
1006-
1007- /* Expanded toolbar */
1008- Item {
1009- id: musicToolbarExpandedContainer
1010- anchors {
1011- fill: parent
1012- }
1013+ fill: parent
1014+ }
1015+ state: trackQueue.model.count === 0 ? "disabled" : "enabled"
1016+ states: [
1017+ State {
1018+ name: "disabled"
1019+ PropertyChanges {
1020+ target: disabledPlayerControlsGroup
1021+ visible: true
1022+ }
1023+ PropertyChanges {
1024+ target: enabledPlayerControlsGroup
1025+ visible: false
1026+ }
1027+ },
1028+ State {
1029+ name: "enabled"
1030+ PropertyChanges {
1031+ target: disabledPlayerControlsGroup
1032+ visible: false
1033+ }
1034+ PropertyChanges {
1035+ target: enabledPlayerControlsGroup
1036+ visible: true
1037+ }
1038+ }
1039+ ]
1040+
1041+ /* Disabled (empty state) controls */
1042+ Item {
1043+ id: disabledPlayerControlsGroup
1044+ anchors {
1045+ bottom: playerControlsProgressBar.top
1046+ left: parent.left
1047+ right: parent.right
1048+ top: parent.top
1049+ }
1050+
1051+ Label {
1052+ id: noSongsInQueueLabel
1053+ anchors {
1054+ left: parent.left
1055+ leftMargin: units.gu(2)
1056+ right: disabledPlayerControlsPlayButton.left
1057+ rightMargin: units.gu(2)
1058+ verticalCenter: parent.verticalCenter
1059+ }
1060+ color: styleMusic.playerControls.labelColor
1061+ text: i18n.tr("Tap to shuffle music")
1062+ fontSize: "large"
1063+ visible: !emptyPageLoader.noMusic
1064+ wrapMode: Text.WordWrap
1065+ maximumLineCount: 2
1066+ }
1067+
1068+ /* Play/Pause button */
1069+ Icon {
1070+ id: disabledPlayerControlsPlayButton
1071+ anchors {
1072+ right: parent.right
1073+ rightMargin: units.gu(3)
1074+ verticalCenter: parent.verticalCenter
1075+ }
1076+ color: "#FFF"
1077+ height: units.gu(2.5)
1078+ name: player.playbackState === MediaPlayer.PlayingState ?
1079+ "media-playback-pause" : "media-playback-start"
1080+ objectName: "disabledSmallPlayShape"
1081+ width: height
1082+ }
1083+
1084+ /* Click to shuffle music */
1085+ MouseArea {
1086+ anchors {
1087+ fill: parent
1088+ }
1089+ onClicked: {
1090+ if (emptyPageLoader.noMusic) {
1091+ return;
1092+ }
1093+
1094+ if (trackQueue.model.count === 0) {
1095+ playRandomSong();
1096+ }
1097+ else {
1098+ player.toggle();
1099+ }
1100+ }
1101+ }
1102+ }
1103+
1104+ /* Enabled (queue > 0) controls */
1105+ Item {
1106+ id: enabledPlayerControlsGroup
1107+ anchors {
1108+ bottom: playerControlsProgressBar.top
1109+ left: parent.left
1110+ right: parent.right
1111+ top: parent.top
1112+ }
1113+
1114+ /* Album art in player controls */
1115+ CoverGrid {
1116+ id: playerControlsImage
1117+ anchors {
1118+ bottom: parent.bottom
1119+ left: parent.left
1120+ top: parent.top
1121+ }
1122+ covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]
1123+ size: parent.height
1124+ }
1125+
1126+ /* Column of meta labels */
1127+ Column {
1128+ id: playerControlsLabels
1129+ anchors {
1130+ left: playerControlsImage.right
1131+ leftMargin: units.gu(1.5)
1132+ right: playerControlsPlayButton.left
1133+ rightMargin: units.gu(1)
1134+ verticalCenter: parent.verticalCenter
1135+ }
1136+
1137+ /* Title of track */
1138+ Label {
1139+ id: playerControlsTitle
1140+ anchors {
1141+ left: parent.left
1142+ right: parent.right
1143+ }
1144+ color: "#FFF"
1145+ elide: Text.ElideRight
1146+ fontSize: "small"
1147+ font.weight: Font.DemiBold
1148+ text: player.currentMetaTitle === ""
1149+ ? player.source : player.currentMetaTitle
1150+ }
1151+
1152+ /* Artist of track */
1153+ Label {
1154+ id: playerControlsArtist
1155+ anchors {
1156+ left: parent.left
1157+ right: parent.right
1158+ }
1159+ color: "#FFF"
1160+ elide: Text.ElideRight
1161+ fontSize: "small"
1162+ opacity: 0.4
1163+ text: player.currentMetaArtist
1164+ }
1165+ }
1166+
1167+ /* Play/Pause button */
1168+ Icon {
1169+ id: playerControlsPlayButton
1170+ anchors {
1171+ right: parent.right
1172+ rightMargin: units.gu(3)
1173+ verticalCenter: parent.verticalCenter
1174+ }
1175+ color: "#FFF"
1176+ height: units.gu(2.5)
1177+ name: player.playbackState === MediaPlayer.PlayingState ?
1178+ "media-playback-pause" : "media-playback-start"
1179+ objectName: "playShape"
1180+ width: height
1181+ }
1182+
1183+ /* Mouse area to jump to now playing */
1184+ MouseArea {
1185+ anchors {
1186+ fill: parent
1187+ }
1188+ objectName: "jumpNowPlaying"
1189+
1190+ onClicked: tabs.pushNowPlaying()
1191+ }
1192+
1193+ /* Mouse area for the play button (ontop of the jump to now playing) */
1194+ MouseArea {
1195+ anchors {
1196+ bottom: parent.bottom
1197+ horizontalCenter: playerControlsPlayButton.horizontalCenter
1198+ top: parent.top
1199+ }
1200+ onClicked: player.toggle()
1201+ width: units.gu(8)
1202+
1203+ Rectangle {
1204+ anchors {
1205+ fill: parent
1206+ }
1207+ color: "#FFF"
1208+ opacity: parent.pressed ? 0.1 : 0
1209+
1210+ Behavior on opacity {
1211+ UbuntuNumberAnimation {
1212+ duration: UbuntuAnimation.FastDuration
1213+ }
1214+ }
1215+ }
1216+ }
1217+ }
1218+
1219+ /* Object which provides the progress bar when toolbar is minimized */
1220+ Rectangle {
1221+ id: playerControlsProgressBar
1222+ anchors {
1223+ bottom: parent.bottom
1224+ left: parent.left
1225+ right: parent.right
1226+ }
1227+ color: styleMusic.common.black
1228+ height: units.gu(0.25)
1229
1230 Rectangle {
1231- id: musicToolbarPlayerControls
1232+ id: playerControlsProgressBarHint
1233 anchors {
1234- fill: parent
1235- }
1236- color: "#000"
1237- state: trackQueue.model.count === 0 ? "disabled" : "enabled"
1238- states: [
1239- State {
1240- name: "disabled"
1241- PropertyChanges {
1242- target: disabledPlayerControlsGroup
1243- visible: true
1244- }
1245- PropertyChanges {
1246- target: enabledPlayerControlsGroup
1247- visible: false
1248- }
1249- },
1250- State {
1251- name: "enabled"
1252- PropertyChanges {
1253- target: disabledPlayerControlsGroup
1254- visible: false
1255- }
1256- PropertyChanges {
1257- target: enabledPlayerControlsGroup
1258- visible: true
1259- }
1260- }
1261- ]
1262-
1263- /* Disabled (empty state) controls */
1264- Item {
1265- id: disabledPlayerControlsGroup
1266- anchors {
1267- bottom: playerControlsProgressBar.top
1268- left: parent.left
1269- right: parent.right
1270- top: parent.top
1271- }
1272-
1273- Label {
1274- id: noSongsInQueueLabel
1275- anchors {
1276- left: parent.left
1277- leftMargin: units.gu(2)
1278- right: disabledPlayerControlsPlayButton.left
1279- rightMargin: units.gu(2)
1280- verticalCenter: parent.verticalCenter
1281- }
1282- color: styleMusic.playerControls.labelColor
1283- text: i18n.tr("Tap to shuffle music")
1284- fontSize: "large"
1285- visible: !emptyPage.noMusic
1286- wrapMode: Text.WordWrap
1287- maximumLineCount: 2
1288- }
1289-
1290- /* Play/Pause button */
1291- Icon {
1292- id: disabledPlayerControlsPlayButton
1293- anchors {
1294- right: parent.right
1295- rightMargin: units.gu(3)
1296- verticalCenter: parent.verticalCenter
1297- }
1298- color: "#FFF"
1299- height: units.gu(2.5)
1300- name: player.playbackState === MediaPlayer.PlayingState ?
1301- "media-playback-pause" : "media-playback-start"
1302- objectName: "disabledSmallPlayShape"
1303- width: height
1304- }
1305-
1306- /* Click to shuffle music */
1307- MouseArea {
1308- anchors {
1309- fill: parent
1310- }
1311- onClicked: {
1312- if (emptyPage.noMusic) {
1313- return;
1314- }
1315-
1316- if (trackQueue.model.count === 0) {
1317- playRandomSong();
1318- }
1319- else {
1320- player.toggle();
1321- }
1322- }
1323- }
1324- }
1325-
1326- /* Enabled (queue > 0) controls */
1327- Item {
1328- id: enabledPlayerControlsGroup
1329- anchors {
1330- bottom: playerControlsProgressBar.top
1331- left: parent.left
1332- right: parent.right
1333- top: parent.top
1334- }
1335-
1336- /* Album art in player controls */
1337- CoverGrid {
1338- id: playerControlsImage
1339- anchors {
1340- bottom: parent.bottom
1341- left: parent.left
1342- top: parent.top
1343- }
1344- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]
1345- size: parent.height
1346- }
1347-
1348- /* Column of meta labels */
1349- Column {
1350- id: playerControlsLabels
1351- anchors {
1352- left: playerControlsImage.right
1353- leftMargin: units.gu(1.5)
1354- right: playerControlsPlayButton.left
1355- rightMargin: units.gu(1)
1356- verticalCenter: parent.verticalCenter
1357- }
1358-
1359- /* Title of track */
1360- Label {
1361- id: playerControlsTitle
1362- anchors {
1363- left: parent.left
1364- right: parent.right
1365- }
1366- color: "#FFF"
1367- elide: Text.ElideRight
1368- fontSize: "small"
1369- font.weight: Font.DemiBold
1370- text: player.currentMetaTitle === ""
1371- ? player.source : player.currentMetaTitle
1372- }
1373-
1374- /* Artist of track */
1375- Label {
1376- id: playerControlsArtist
1377- anchors {
1378- left: parent.left
1379- right: parent.right
1380- }
1381- color: "#FFF"
1382- elide: Text.ElideRight
1383- fontSize: "small"
1384- opacity: 0.4
1385- text: player.currentMetaArtist
1386- }
1387- }
1388-
1389- /* Play/Pause button */
1390- Icon {
1391- id: playerControlsPlayButton
1392- anchors {
1393- right: parent.right
1394- rightMargin: units.gu(3)
1395- verticalCenter: parent.verticalCenter
1396- }
1397- color: "#FFF"
1398- height: units.gu(2.5)
1399- name: player.playbackState === MediaPlayer.PlayingState ?
1400- "media-playback-pause" : "media-playback-start"
1401- objectName: "playShape"
1402- width: height
1403- }
1404-
1405- MouseArea {
1406- anchors {
1407- bottom: parent.bottom
1408- horizontalCenter: playerControlsPlayButton.horizontalCenter
1409- top: parent.top
1410- }
1411- onClicked: player.toggle()
1412- width: units.gu(8)
1413-
1414- Rectangle {
1415- anchors {
1416- fill: parent
1417- }
1418- color: "#FFF"
1419- opacity: parent.pressed ? 0.1 : 0
1420-
1421- Behavior on opacity {
1422- UbuntuNumberAnimation {
1423- duration: UbuntuAnimation.FastDuration
1424- }
1425- }
1426- }
1427- }
1428-
1429- /* Mouse area to jump to now playing */
1430- Item {
1431- anchors {
1432- bottom: parent.bottom
1433- left: parent.left
1434- right: playerControlsLabels.right
1435- top: parent.top
1436- }
1437- objectName: "jumpNowPlaying"
1438- function trigger() {
1439- tabs.pushNowPlaying();
1440- }
1441- }
1442- }
1443-
1444- /* Object which provides the progress bar when toolbar is minimized */
1445- Rectangle {
1446- id: playerControlsProgressBar
1447- anchors {
1448- bottom: parent.bottom
1449- left: parent.left
1450- right: parent.right
1451- }
1452- color: styleMusic.common.black
1453- height: units.gu(0.25)
1454-
1455- Rectangle {
1456- id: playerControlsProgressBarHint
1457- anchors {
1458- left: parent.left
1459- top: parent.top
1460- }
1461- color: UbuntuColors.blue
1462- height: parent.height
1463- width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
1464-
1465- Connections {
1466- target: player
1467- onPositionChanged: {
1468- playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
1469- }
1470- onStopped: {
1471- playerControlsProgressBarHint.width = 0;
1472- }
1473- }
1474+ left: parent.left
1475+ top: parent.top
1476+ }
1477+ color: UbuntuColors.blue
1478+ height: parent.height
1479+ width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
1480+
1481+ Connections {
1482+ target: player
1483+ onPositionChanged: {
1484+ playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
1485+ }
1486+ onStopped: {
1487+ playerControlsProgressBarHint.width = 0;
1488 }
1489 }
1490 }
1491 }
1492 }
1493 }
1494-
1495
1496=== added file 'app/components/NowPlaying.qml'
1497--- app/components/NowPlaying.qml 1970-01-01 00:00:00 +0000
1498+++ app/components/NowPlaying.qml 2015-02-10 03:13:44 +0000
1499@@ -0,0 +1,550 @@
1500+/*
1501+ * Copyright (C) 2013, 2014, 2015
1502+ * Andrew Hayzen <ahayzen@gmail.com>
1503+ * Daniel Holm <d.holmen@gmail.com>
1504+ * Victor Thompson <victor.thompson@gmail.com>
1505+ *
1506+ * This program is free software; you can redistribute it and/or modify
1507+ * it under the terms of the GNU General Public License as published by
1508+ * the Free Software Foundation; version 3.
1509+ *
1510+ * This program is distributed in the hope that it will be useful,
1511+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1512+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1513+ * GNU General Public License for more details.
1514+ *
1515+ * You should have received a copy of the GNU General Public License
1516+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1517+ */
1518+
1519+import QtMultimedia 5.0
1520+import QtQuick 2.3
1521+import QtQuick.LocalStorage 2.0
1522+import Ubuntu.Components 1.1
1523+import Ubuntu.Thumbnailer 0.1
1524+import "../components"
1525+import "../components/Flickables"
1526+import "../components/HeadState"
1527+import "../components/ListItemActions"
1528+import "../components/Themes/Ambiance"
1529+import "../logic/meta-database.js" as Library
1530+import "../logic/playlists.js" as Playlists
1531+
1532+Item {
1533+ id: nowPlayingView
1534+ objectName: "nowPlayingView"
1535+
1536+ anchors {
1537+ fill: parent
1538+ }
1539+
1540+ property bool isListView: false
1541+
1542+ Item {
1543+ id: fullview
1544+ anchors {
1545+ top: parent.top
1546+ topMargin: mainView.header.height
1547+ }
1548+ height: parent.height - mainView.header.height - units.gu(9.5)
1549+ visible: !isListView || wideAspect
1550+ width: parent.width
1551+
1552+ BlurredBackground {
1553+ id: blurredBackground
1554+ anchors {
1555+ left: parent.left
1556+ right: parent.right
1557+ top: parent.top
1558+ }
1559+ art: albumImage.firstSource
1560+ height: parent.height - units.gu(7)
1561+
1562+ Item {
1563+ id: albumImageContainer
1564+ anchors {
1565+ horizontalCenter: parent.horizontalCenter
1566+ top: parent.top
1567+ }
1568+ height: parent.height
1569+ width: parent.width
1570+
1571+ CoverGrid {
1572+ id: albumImage
1573+ anchors.centerIn: parent
1574+ covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
1575+ size: parent.width > parent.height ? parent.height : parent.width
1576+ }
1577+ }
1578+
1579+ Rectangle {
1580+ id: nowPlayingWideAspectLabelsBackground
1581+ anchors.bottom: parent.bottom
1582+ color: styleMusic.common.black
1583+ height: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(10) : units.gu(13)
1584+ opacity: 0.8
1585+ width: parent.width
1586+ }
1587+
1588+ /* Column for labels in wideAspect */
1589+ Column {
1590+ id: nowPlayingWideAspectLabels
1591+ spacing: units.gu(1)
1592+ anchors {
1593+ left: parent.left
1594+ leftMargin: units.gu(2)
1595+ right: parent.right
1596+ rightMargin: units.gu(2)
1597+ top: nowPlayingWideAspectLabelsBackground.top
1598+ topMargin: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(2) : units.gu(1.5)
1599+ }
1600+
1601+ /* Title of track */
1602+ Label {
1603+ id: nowPlayingWideAspectTitle
1604+ anchors {
1605+ left: parent.left
1606+ leftMargin: units.gu(1)
1607+ right: parent.right
1608+ rightMargin: units.gu(1)
1609+ }
1610+ color: styleMusic.playerControls.labelColor
1611+ elide: Text.ElideRight
1612+ fontSize: "x-large"
1613+ maximumLineCount: 2
1614+ objectName: "playercontroltitle"
1615+ text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
1616+ wrapMode: Text.WordWrap
1617+ }
1618+
1619+ /* Artist of track */
1620+ Label {
1621+ id: nowPlayingWideAspectArtist
1622+ anchors {
1623+ left: parent.left
1624+ leftMargin: units.gu(1)
1625+ right: parent.right
1626+ rightMargin: units.gu(1)
1627+ }
1628+ color: styleMusic.nowPlaying.labelSecondaryColor
1629+ elide: Text.ElideRight
1630+ fontSize: "small"
1631+ text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
1632+ }
1633+ }
1634+
1635+ /* Detect cover art swipe */
1636+ MouseArea {
1637+ anchors.fill: parent
1638+ property string direction: "None"
1639+ property real lastX: -1
1640+
1641+ onPressed: lastX = mouse.x
1642+
1643+ onReleased: {
1644+ var diff = mouse.x - lastX
1645+ if (Math.abs(diff) < units.gu(4)) {
1646+ return;
1647+ } else if (diff < 0) {
1648+ player.nextSong()
1649+ } else if (diff > 0) {
1650+ player.previousSong()
1651+ }
1652+ }
1653+ }
1654+ }
1655+
1656+ /* Background for progress bar component */
1657+ Rectangle {
1658+ id: musicToolbarFullProgressBackground
1659+ anchors {
1660+ bottom: parent.bottom
1661+ left: parent.left
1662+ right: parent.right
1663+ top: blurredBackground.bottom
1664+ }
1665+ color: styleMusic.common.black
1666+ }
1667+
1668+ /* Progress bar component */
1669+ Item {
1670+ id: musicToolbarFullProgressContainer
1671+ anchors.left: parent.left
1672+ anchors.leftMargin: units.gu(3)
1673+ anchors.right: parent.right
1674+ anchors.rightMargin: units.gu(3)
1675+ anchors.top: blurredBackground.bottom
1676+ anchors.topMargin: units.gu(1)
1677+ height: units.gu(3)
1678+ width: parent.width
1679+
1680+ /* Position label */
1681+ Label {
1682+ id: musicToolbarFullPositionLabel
1683+ anchors.top: progressSliderMusic.bottom
1684+ anchors.topMargin: units.gu(-2)
1685+ anchors.left: parent.left
1686+ color: styleMusic.nowPlaying.labelSecondaryColor
1687+ fontSize: "small"
1688+ height: parent.height
1689+ horizontalAlignment: Text.AlignHCenter
1690+ text: durationToString(player.position)
1691+ verticalAlignment: Text.AlignVCenter
1692+ width: units.gu(3)
1693+ }
1694+
1695+ Slider {
1696+ id: progressSliderMusic
1697+ anchors.left: parent.left
1698+ anchors.right: parent.right
1699+ maximumValue: player.duration // load value at startup
1700+ objectName: "progressSliderShape"
1701+ style: UbuntuBlueSliderStyle {}
1702+ value: player.position // load value at startup
1703+
1704+ function formatValue(v) {
1705+ if (seeking) { // update position label while dragging
1706+ musicToolbarFullPositionLabel.text = durationToString(v)
1707+ }
1708+
1709+ return durationToString(v)
1710+ }
1711+
1712+ property bool seeking: false
1713+ property bool seeked: false
1714+
1715+ onSeekingChanged: {
1716+ if (seeking === false) {
1717+ musicToolbarFullPositionLabel.text = durationToString(player.position)
1718+ }
1719+ }
1720+
1721+ onPressedChanged: {
1722+ seeking = pressed
1723+
1724+ if (!pressed) {
1725+ seeked = true
1726+ player.seek(value)
1727+
1728+ musicToolbarFullPositionLabel.text = durationToString(value)
1729+ }
1730+ }
1731+
1732+ Connections {
1733+ target: player
1734+ onPositionChanged: {
1735+ // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
1736+ if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
1737+ musicToolbarFullPositionLabel.text = durationToString(player.position)
1738+ musicToolbarFullDurationLabel.text = durationToString(player.duration)
1739+
1740+ progressSliderMusic.value = player.position
1741+ progressSliderMusic.maximumValue = player.duration
1742+ }
1743+
1744+ progressSliderMusic.seeked = false;
1745+ }
1746+ onStopped: {
1747+ musicToolbarFullPositionLabel.text = durationToString(0);
1748+ musicToolbarFullDurationLabel.text = durationToString(0);
1749+ }
1750+ }
1751+ }
1752+
1753+ /* Duration label */
1754+ Label {
1755+ id: musicToolbarFullDurationLabel
1756+ anchors.top: progressSliderMusic.bottom
1757+ anchors.topMargin: units.gu(-2)
1758+ anchors.right: parent.right
1759+ color: styleMusic.nowPlaying.labelSecondaryColor
1760+ fontSize: "small"
1761+ height: parent.height
1762+ horizontalAlignment: Text.AlignHCenter
1763+ text: durationToString(player.duration)
1764+ verticalAlignment: Text.AlignVCenter
1765+ width: units.gu(3)
1766+ }
1767+ }
1768+ }
1769+
1770+ Loader {
1771+ id: queueListLoader
1772+ anchors {
1773+ fill: parent
1774+ }
1775+ asynchronous: true
1776+ sourceComponent: MultiSelectListView {
1777+ id: queueList
1778+ anchors {
1779+ bottomMargin: musicToolbarFullContainer.height + units.gu(2)
1780+ fill: parent
1781+ topMargin: units.gu(2)
1782+ }
1783+ delegate: queueDelegate
1784+ footer: Item {
1785+ height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
1786+ }
1787+ model: trackQueue.model
1788+ objectName: "nowPlayingqueueList"
1789+
1790+ property int normalHeight: units.gu(6)
1791+ property int transitionDuration: 250 // transition length of animations
1792+
1793+ onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
1794+
1795+ Component {
1796+ id: queueDelegate
1797+ ListItemWithActions {
1798+ id: queueListItem
1799+ color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
1800+ height: queueList.normalHeight
1801+ objectName: "nowPlayingListItem" + index
1802+ state: ""
1803+
1804+ leftSideAction: Remove {
1805+ onTriggered: trackQueue.removeQueueList([index])
1806+ }
1807+ multiselectable: true
1808+ reorderable: true
1809+ rightSideActions: [
1810+ AddToPlaylist{
1811+
1812+ }
1813+ ]
1814+
1815+ onItemClicked: {
1816+ customdebug("File: " + model.filename) // debugger
1817+ trackQueueClick(index); // toggle track state
1818+ }
1819+ onReorder: {
1820+ console.debug("Move: ", from, to);
1821+
1822+ trackQueue.model.move(from, to, 1);
1823+ Library.moveQueueItem(from, to);
1824+
1825+ // Maintain currentIndex with current song
1826+ if (from === player.currentIndex) {
1827+ player.currentIndex = to;
1828+ }
1829+ else if (from < player.currentIndex && to >= player.currentIndex) {
1830+ player.currentIndex -= 1;
1831+ }
1832+ else if (from > player.currentIndex && to <= player.currentIndex) {
1833+ player.currentIndex += 1;
1834+ }
1835+
1836+ queueIndex = player.currentIndex
1837+ }
1838+
1839+ Item {
1840+ id: trackContainer;
1841+ anchors {
1842+ fill: parent
1843+ }
1844+
1845+ NumberAnimation {
1846+ id: trackContainerReorderAnimation
1847+ target: trackContainer;
1848+ property: "anchors.leftMargin";
1849+ duration: queueList.transitionDuration;
1850+ to: units.gu(2)
1851+ }
1852+
1853+ NumberAnimation {
1854+ id: trackContainerResetAnimation
1855+ target: trackContainer;
1856+ property: "anchors.leftMargin";
1857+ duration: queueList.transitionDuration;
1858+ to: units.gu(0.5)
1859+ }
1860+
1861+ MusicRow {
1862+ id: musicRow
1863+ height: parent.height
1864+ column: Column {
1865+ Label {
1866+ id: trackTitle
1867+ color: player.currentIndex === index ? UbuntuColors.blue
1868+ : styleMusic.common.music
1869+ fontSize: "small"
1870+ objectName: "titleLabel"
1871+ text: model.title
1872+ }
1873+
1874+ Label {
1875+ id: trackArtist
1876+ color: styleMusic.common.subtitle
1877+ fontSize: "x-small"
1878+ objectName: "artistLabel"
1879+ text: model.author
1880+ }
1881+ }
1882+ }
1883+ }
1884+ }
1885+ }
1886+ }
1887+ visible: isListView || wideAspect
1888+ }
1889+
1890+ /* Full toolbar */
1891+ Rectangle {
1892+ id: musicToolbarFullContainer
1893+ anchors.bottom: parent.bottom
1894+ color: styleMusic.common.black
1895+ height: units.gu(10)
1896+ width: parent.width
1897+
1898+ /* Repeat button */
1899+ MouseArea {
1900+ id: nowPlayingRepeatButton
1901+ anchors.right: nowPlayingPreviousButton.left
1902+ anchors.rightMargin: units.gu(1)
1903+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1904+ height: units.gu(6)
1905+ opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
1906+ width: height
1907+ onClicked: player.repeat = !player.repeat
1908+
1909+ Icon {
1910+ id: repeatIcon
1911+ height: units.gu(3)
1912+ width: height
1913+ anchors.verticalCenter: parent.verticalCenter
1914+ anchors.horizontalCenter: parent.horizontalCenter
1915+ color: "white"
1916+ name: "media-playlist-repeat"
1917+ objectName: "repeatShape"
1918+ opacity: player.repeat && !emptyPageLoader.noMusic ? 1 : .4
1919+ }
1920+ }
1921+
1922+ /* Previous button */
1923+ MouseArea {
1924+ id: nowPlayingPreviousButton
1925+ anchors.right: nowPlayingPlayButton.left
1926+ anchors.rightMargin: units.gu(1)
1927+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1928+ height: units.gu(6)
1929+ opacity: trackQueue.model.count === 0 ? .4 : 1
1930+ width: height
1931+ onClicked: player.previousSong()
1932+
1933+ Icon {
1934+ id: nowPlayingPreviousIndicator
1935+ height: units.gu(3)
1936+ width: height
1937+ anchors.verticalCenter: parent.verticalCenter
1938+ anchors.horizontalCenter: parent.horizontalCenter
1939+ color: "white"
1940+ name: "media-skip-backward"
1941+ objectName: "previousShape"
1942+ opacity: 1
1943+ }
1944+ }
1945+
1946+ /* Play/Pause button */
1947+ MouseArea {
1948+ id: nowPlayingPlayButton
1949+ anchors.centerIn: parent
1950+ height: units.gu(10)
1951+ width: height
1952+ onClicked: player.toggle()
1953+
1954+ Icon {
1955+ id: nowPlayingPlayIndicator
1956+ height: units.gu(6)
1957+ width: height
1958+ anchors.verticalCenter: parent.verticalCenter
1959+ anchors.horizontalCenter: parent.horizontalCenter
1960+ opacity: emptyPageLoader.noMusic ? .4 : 1
1961+ color: "white"
1962+ name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
1963+ objectName: "playShape"
1964+ }
1965+ }
1966+
1967+ /* Next button */
1968+ MouseArea {
1969+ id: nowPlayingNextButton
1970+ anchors.left: nowPlayingPlayButton.right
1971+ anchors.leftMargin: units.gu(1)
1972+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1973+ height: units.gu(6)
1974+ opacity: trackQueue.model.count === 0 ? .4 : 1
1975+ width: height
1976+ onClicked: player.nextSong()
1977+
1978+ Icon {
1979+ id: nowPlayingNextIndicator
1980+ height: units.gu(3)
1981+ width: height
1982+ anchors.verticalCenter: parent.verticalCenter
1983+ anchors.horizontalCenter: parent.horizontalCenter
1984+ color: "white"
1985+ name: "media-skip-forward"
1986+ objectName: "forwardShape"
1987+ opacity: 1
1988+ }
1989+ }
1990+
1991+ /* Shuffle button */
1992+ MouseArea {
1993+ id: nowPlayingShuffleButton
1994+ anchors.left: nowPlayingNextButton.right
1995+ anchors.leftMargin: units.gu(1)
1996+ anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
1997+ height: units.gu(6)
1998+ opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
1999+ width: height
2000+ onClicked: player.shuffle = !player.shuffle
2001+
2002+ Icon {
2003+ id: shuffleIcon
2004+ height: units.gu(3)
2005+ width: height
2006+ anchors.verticalCenter: parent.verticalCenter
2007+ anchors.horizontalCenter: parent.horizontalCenter
2008+ color: "white"
2009+ name: "media-playlist-shuffle"
2010+ objectName: "shuffleShape"
2011+ opacity: player.shuffle && !emptyPageLoader.noMusic ? 1 : .4
2012+ }
2013+ }
2014+
2015+ /* Object which provides the progress bar when in the queue */
2016+ Rectangle {
2017+ id: playerControlsProgressBar
2018+ anchors {
2019+ bottom: parent.bottom
2020+ left: parent.left
2021+ right: parent.right
2022+ }
2023+ color: styleMusic.common.black
2024+ height: units.gu(0.25)
2025+ visible: isListView || wideAspect
2026+
2027+ Rectangle {
2028+ id: playerControlsProgressBarHint
2029+ anchors {
2030+ left: parent.left
2031+ bottom: parent.bottom
2032+ }
2033+ color: UbuntuColors.blue
2034+ height: parent.height
2035+ width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
2036+
2037+ Connections {
2038+ target: player
2039+ onPositionChanged: {
2040+ playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
2041+ }
2042+ onStopped: {
2043+ playerControlsProgressBarHint.width = 0;
2044+ }
2045+ }
2046+ }
2047+ }
2048+ }
2049+}
2050
2051=== renamed file 'Player.qml' => 'app/components/Player.qml'
2052=== added file 'app/components/PlaylistsEmptyState.qml'
2053--- app/components/PlaylistsEmptyState.qml 1970-01-01 00:00:00 +0000
2054+++ app/components/PlaylistsEmptyState.qml 2015-02-10 03:13:44 +0000
2055@@ -0,0 +1,58 @@
2056+/*
2057+ * Copyright (C) 2013, 2014, 2015
2058+ * Andrew Hayzen <ahayzen@gmail.com>
2059+ * Daniel Holm <d.holmen@gmail.com>
2060+ * Victor Thompson <victor.thompson@gmail.com>
2061+ *
2062+ * This program is free software; you can redistribute it and/or modify
2063+ * it under the terms of the GNU General Public License as published by
2064+ * the Free Software Foundation; version 3.
2065+ *
2066+ * This program is distributed in the hope that it will be useful,
2067+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2068+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2069+ * GNU General Public License for more details.
2070+ *
2071+ * You should have received a copy of the GNU General Public License
2072+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2073+ */
2074+
2075+import QtQuick 2.3
2076+import Ubuntu.Components 1.1
2077+
2078+
2079+Rectangle {
2080+ id: playlistsEmptyState
2081+ anchors {
2082+ fill: parent
2083+ }
2084+ color: mainView.backgroundColor
2085+
2086+ Column {
2087+ anchors.centerIn: parent
2088+ spacing: units.gu(4)
2089+ width: units.gu(36)
2090+
2091+ Label {
2092+ color: styleMusic.libraryEmpty.labelColor
2093+ elide: Text.ElideRight
2094+ fontSize: "x-large"
2095+ horizontalAlignment: Text.AlignLeft
2096+ maximumLineCount: 2
2097+ text: i18n.tr("No playlists found")
2098+ width: parent.width
2099+ wrapMode: Text.WordWrap
2100+ }
2101+
2102+ Label {
2103+ color: styleMusic.libraryEmpty.labelColor
2104+ elide: Text.ElideRight
2105+ fontSize: "large"
2106+ horizontalAlignment: Text.AlignLeft
2107+ maximumLineCount: 4
2108+ text: i18n.tr("Get more out of Music by tapping the %1 icon to start making playlists for every mood and occasion.").arg('"+"')
2109+ width: parent.width
2110+ wrapMode: Text.WordWrap
2111+ }
2112+ }
2113+}
2114
2115=== renamed file 'Style.qml' => 'app/components/Style.qml'
2116=== added directory 'app/components/ViewButton'
2117=== added file 'app/components/ViewButton/CMakeLists.txt'
2118--- app/components/ViewButton/CMakeLists.txt 1970-01-01 00:00:00 +0000
2119+++ app/components/ViewButton/CMakeLists.txt 2015-02-10 03:13:44 +0000
2120@@ -0,0 +1,4 @@
2121+# make the qml files visible on qtcreator
2122+file(GLOB VIEW_BUTTON_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
2123+
2124+add_custom_target(com_ubuntu_music_VIEW_BUTTON_QMLFiles ALL SOURCES ${VIEW_BUTTON_QML_FILES})
2125
2126=== added file 'app/components/ViewButton/PlayAllButton.qml'
2127--- app/components/ViewButton/PlayAllButton.qml 1970-01-01 00:00:00 +0000
2128+++ app/components/ViewButton/PlayAllButton.qml 2015-02-10 03:13:44 +0000
2129@@ -0,0 +1,34 @@
2130+/*
2131+ * Copyright (C) 2013, 2014, 2015
2132+ * Andrew Hayzen <ahayzen@gmail.com>
2133+ * Daniel Holm <d.holmen@gmail.com>
2134+ * Victor Thompson <victor.thompson@gmail.com>
2135+ *
2136+ * This program is free software; you can redistribute it and/or modify
2137+ * it under the terms of the GNU General Public License as published by
2138+ * the Free Software Foundation; version 3.
2139+ *
2140+ * This program is distributed in the hope that it will be useful,
2141+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2142+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2143+ * GNU General Public License for more details.
2144+ *
2145+ * You should have received a copy of the GNU General Public License
2146+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2147+ */
2148+
2149+import QtQuick 2.3
2150+import Ubuntu.Components 1.1
2151+
2152+
2153+Button {
2154+ color: UbuntuColors.green
2155+ height: units.gu(4)
2156+ // TRANSLATORS: this appears in a button with limited space (around 14 characters)
2157+ text: i18n.tr("Play all")
2158+ width: units.gu(15)
2159+
2160+ property var model
2161+
2162+ onClicked: trackClicked(model, 0) // play track
2163+}
2164
2165=== added file 'app/components/ViewButton/QueueAllButton.qml'
2166--- app/components/ViewButton/QueueAllButton.qml 1970-01-01 00:00:00 +0000
2167+++ app/components/ViewButton/QueueAllButton.qml 2015-02-10 03:13:44 +0000
2168@@ -0,0 +1,46 @@
2169+/*
2170+ * Copyright (C) 2013, 2014, 2015
2171+ * Andrew Hayzen <ahayzen@gmail.com>
2172+ * Daniel Holm <d.holmen@gmail.com>
2173+ * Victor Thompson <victor.thompson@gmail.com>
2174+ *
2175+ * This program is free software; you can redistribute it and/or modify
2176+ * it under the terms of the GNU General Public License as published by
2177+ * the Free Software Foundation; version 3.
2178+ *
2179+ * This program is distributed in the hope that it will be useful,
2180+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2181+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2182+ * GNU General Public License for more details.
2183+ *
2184+ * You should have received a copy of the GNU General Public License
2185+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2186+ */
2187+
2188+import QtQuick 2.3
2189+import Ubuntu.Components 1.1
2190+
2191+
2192+Button {
2193+ height: units.gu(4)
2194+ strokeColor: UbuntuColors.green
2195+ width: units.gu(15)
2196+
2197+ property var model
2198+
2199+ onClicked: addQueueFromModel(model)
2200+
2201+ Text {
2202+ anchors {
2203+ centerIn: parent
2204+ }
2205+ color: "white"
2206+ elide: Text.ElideRight
2207+ height: parent.height
2208+ horizontalAlignment: Text.AlignHCenter
2209+ // TRANSLATORS: this appears in a button with limited space (around 14 characters)
2210+ text: i18n.tr("Queue all")
2211+ verticalAlignment: Text.AlignVCenter
2212+ width: parent.width - units.gu(2)
2213+ }
2214+}
2215
2216=== added file 'app/components/ViewButton/ShuffleButton.qml'
2217--- app/components/ViewButton/ShuffleButton.qml 1970-01-01 00:00:00 +0000
2218+++ app/components/ViewButton/ShuffleButton.qml 2015-02-10 03:13:44 +0000
2219@@ -0,0 +1,46 @@
2220+/*
2221+ * Copyright (C) 2013, 2014, 2015
2222+ * Andrew Hayzen <ahayzen@gmail.com>
2223+ * Daniel Holm <d.holmen@gmail.com>
2224+ * Victor Thompson <victor.thompson@gmail.com>
2225+ *
2226+ * This program is free software; you can redistribute it and/or modify
2227+ * it under the terms of the GNU General Public License as published by
2228+ * the Free Software Foundation; version 3.
2229+ *
2230+ * This program is distributed in the hope that it will be useful,
2231+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2232+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2233+ * GNU General Public License for more details.
2234+ *
2235+ * You should have received a copy of the GNU General Public License
2236+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2237+ */
2238+
2239+import QtQuick 2.3
2240+import Ubuntu.Components 1.1
2241+
2242+
2243+Button {
2244+ height: units.gu(4)
2245+ strokeColor: UbuntuColors.green
2246+ width: units.gu(15)
2247+
2248+ property var model
2249+
2250+ onClicked: shuffleModel(model)
2251+
2252+ Text {
2253+ anchors {
2254+ centerIn: parent
2255+ }
2256+ color: "white"
2257+ elide: Text.ElideRight
2258+ height: parent.height
2259+ horizontalAlignment: Text.AlignHCenter
2260+ // TRANSLATORS: this appears in a button with limited space (around 14 characters)
2261+ text: i18n.tr("Shuffle")
2262+ verticalAlignment: Text.AlignVCenter
2263+ width: parent.width - units.gu(2)
2264+ }
2265+}
2266
2267=== modified file 'app/components/Walkthrough/Slide1.qml'
2268--- common/Walkthrough/Slide1.qml 2015-02-02 14:03:12 +0000
2269+++ app/components/Walkthrough/Slide1.qml 2015-02-10 03:13:44 +0000
2270@@ -39,7 +39,7 @@
2271 height: (parent.height - bodyText.contentHeight - introductionText.height - 4*units.gu(4))/2
2272 image: Image {
2273 id: centerImage
2274- source: Qt.resolvedUrl("../../images/music-app@30.png")
2275+ source: Qt.resolvedUrl("../../graphics/music-app@30.png")
2276 }
2277 radius: "medium"
2278 width: height
2279
2280=== modified file 'app/components/Walkthrough/Slide2.qml'
2281--- common/Walkthrough/Slide2.qml 2015-02-02 13:37:23 +0000
2282+++ app/components/Walkthrough/Slide2.qml 2015-02-10 03:13:44 +0000
2283@@ -38,7 +38,7 @@
2284 }
2285 height: (parent.height - bodyText.contentHeight - introductionText.height - 4*units.gu(4))/2
2286 fillMode: Image.PreserveAspectFit
2287- source: Qt.resolvedUrl("../../images/sd_phone_icon.png")
2288+ source: Qt.resolvedUrl("../../graphics/sd_phone_icon.png")
2289 }
2290
2291 Label {
2292
2293=== modified file 'app/components/Walkthrough/Slide3.qml'
2294--- common/Walkthrough/Slide3.qml 2015-02-02 13:37:23 +0000
2295+++ app/components/Walkthrough/Slide3.qml 2015-02-10 03:13:44 +0000
2296@@ -38,7 +38,7 @@
2297 }
2298 height: (parent.height - introductionText.height - finalMessage.contentHeight - 4.5*units.gu(4))/2
2299 fillMode: Image.PreserveAspectFit
2300- source: Qt.resolvedUrl("../../images/music_download_icon.png")
2301+ source: Qt.resolvedUrl("../../graphics/music_download_icon.png")
2302 }
2303
2304 Label {
2305
2306=== modified file 'app/components/Walkthrough/Walkthrough.qml'
2307--- common/Walkthrough/Walkthrough.qml 2015-01-31 02:47:16 +0000
2308+++ app/components/Walkthrough/Walkthrough.qml 2015-02-10 03:13:44 +0000
2309@@ -134,7 +134,7 @@
2310 anchors.verticalCenter: parent.verticalCenter
2311 antialiasing: true
2312 height: width
2313- source: listView.currentIndex == index ? "../../images/Ellipse@27.png" : "../../images/Ellipse_15_opacity@27.png"
2314+ source: listView.currentIndex == index ? "../../graphics/Ellipse@27.png" : "../../graphics/Ellipse_15_opacity@27.png"
2315 width: units.gu(1.5)
2316 }
2317 }
2318
2319=== renamed file 'WorkerModelLoader.qml' => 'app/components/WorkerModelLoader.qml'
2320--- WorkerModelLoader.qml 2015-02-01 03:35:07 +0000
2321+++ app/components/WorkerModelLoader.qml 2015-02-10 03:13:44 +0000
2322@@ -21,7 +21,7 @@
2323
2324 WorkerScript {
2325 id: worker
2326- source: "worker-library-loader.js"
2327+ source: "../logic/worker-library-loader.js"
2328
2329 property bool canLoad: true
2330 property bool completed: false
2331
2332=== renamed directory 'images' => 'app/graphics'
2333=== added directory 'app/logic'
2334=== added file 'app/logic/CMakeLists.txt'
2335--- app/logic/CMakeLists.txt 1970-01-01 00:00:00 +0000
2336+++ app/logic/CMakeLists.txt 2015-02-10 03:13:44 +0000
2337@@ -0,0 +1,4 @@
2338+# make the qml files visible on qtcreator
2339+file(GLOB LOGIC_JS_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.js)
2340+
2341+add_custom_target(com_ubuntu_music_LOGIC_JSFiles ALL SOURCES ${LOGIC_JS_FILES})
2342
2343=== renamed file 'meta-database.js' => 'app/logic/meta-database.js'
2344=== renamed file 'playlists.js' => 'app/logic/playlists.js'
2345=== renamed file 'worker-library-loader.js' => 'app/logic/worker-library-loader.js'
2346=== renamed file 'music-app.qml' => 'app/music-app.qml'
2347--- music-app.qml 2015-02-05 22:40:14 +0000
2348+++ app/music-app.qml 2015-02-10 03:13:44 +0000
2349@@ -22,15 +22,17 @@
2350 import Ubuntu.Components.Popups 1.0
2351 import Ubuntu.Components.ListItems 1.0 as ListItem
2352 import Ubuntu.Content 0.1
2353+import Ubuntu.Layouts 1.0
2354 import Ubuntu.MediaScanner 0.1
2355 import Qt.labs.settings 1.0
2356 import QtMultimedia 5.0
2357 import QtQuick.LocalStorage 2.0
2358 import QtGraphicalEffects 1.0
2359 import UserMetrics 0.1
2360-import "meta-database.js" as Library
2361-import "playlists.js" as Playlists
2362-import "common"
2363+import "logic/meta-database.js" as Library
2364+import "logic/playlists.js" as Playlists
2365+import "components"
2366+import "ui"
2367
2368 MainView {
2369 objectName: "music"
2370@@ -55,10 +57,10 @@
2371 focus: true
2372 Keys.onPressed: {
2373 if(event.key === Qt.Key_Escape) {
2374- if (musicToolbar.currentPage.searchable && musicToolbar.currentPage.state === "search") {
2375- musicToolbar.currentPage.state = "default"
2376+ if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "search") {
2377+ mainPageStack.currentMusicPage.state = "default"
2378 } else {
2379- musicToolbar.goBack(); // Esc Go back
2380+ mainPageStack.goBack(); // Esc Go back
2381 }
2382 }
2383 else if(event.modifiers === Qt.AltModifier) {
2384@@ -95,8 +97,8 @@
2385 player.repeat = !player.repeat
2386 break;
2387 case Qt.Key_F: // Ctrl+F Show Search popup
2388- if (musicToolbar.currentPage.searchable && musicToolbar.currentPage.state === "default") {
2389- musicToolbar.currentPage.state = "search"
2390+ if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {
2391+ mainPageStack.currentMusicPage.state = "search"
2392 header.show()
2393 }
2394
2395@@ -159,7 +161,7 @@
2396 id: backAction
2397 text: i18n.tr("Back")
2398 keywords: i18n.tr("Go back to last page")
2399- onTriggered: musicToolbar.goBack();
2400+ onTriggered: mainPageStack.goBack();
2401 }
2402
2403 // With a default Quit action only the first 4 actions are displayed
2404@@ -304,7 +306,7 @@
2405
2406 if (success === true) {
2407 if (contentHubWaitForFile.processId === -1) {
2408- contentHubWaitForFile.dialog = PopupUtils.open(contentHubWait, mainView)
2409+ contentHubWaitForFile.dialog = PopupUtils.open(Qt.resolvedUrl("components/Dialog/ContentHubWaitDialog.qml"), mainView)
2410 contentHubWaitForFile.searchPaths = contentHub.searchPaths;
2411 contentHubWaitForFile.processId = processId;
2412 contentHubWaitForFile.start();
2413@@ -318,7 +320,7 @@
2414 }
2415 }
2416 else {
2417- var errordialog = PopupUtils.open(contentHubError, mainView)
2418+ var errordialog = PopupUtils.open(Qt.resolvedUrl("components/Dialog/ContentHubErrorDialog.qml"), mainView)
2419 errordialog.errorText = err.join("\n")
2420 }
2421 }
2422@@ -416,7 +418,7 @@
2423 stopTimer();
2424
2425 console.debug("File(s) were not found", JSON.stringify(searchPaths))
2426- PopupUtils.open(contentHubNotFound, mainView)
2427+ PopupUtils.open(Qt.resolvedUrl("components/Dialog/ContentHubNotFoundDialog.qml"), mainView)
2428 }
2429 }
2430 else {
2431@@ -435,67 +437,6 @@
2432 }
2433 }
2434
2435- Component {
2436- id: contentHubWait
2437- Dialog {
2438- id: dialogContentHubWait
2439-
2440- LoadingSpinnerComponent {
2441- anchors {
2442- margins: units.gu(0)
2443- }
2444- loadingText: i18n.tr("Waiting for file(s)...")
2445- visible: true
2446- }
2447- }
2448- }
2449-
2450- Component {
2451- id: contentHubError
2452- Dialog {
2453- id: dialogContentHubError
2454-
2455- property alias errorText: errorLabel.text
2456-
2457- Label {
2458- id: errorLabel
2459- color: styleMusic.common.black
2460- }
2461-
2462- Button {
2463- text: i18n.tr("OK")
2464- onClicked: PopupUtils.close(dialogContentHubError)
2465- }
2466- }
2467- }
2468-
2469- Component {
2470- id: contentHubNotFound
2471- Dialog {
2472- id: dialogContentHubNotFound
2473-
2474- Label {
2475- color: styleMusic.common.black
2476- text: i18n.tr("Imported file not found")
2477- }
2478-
2479- Button {
2480- text: i18n.tr("Wait")
2481- onClicked: {
2482- PopupUtils.close(dialogContentHubNotFound)
2483-
2484- contentHubWaitForFile.dialog = PopupUtils.open(contentHubWait, mainView)
2485- contentHubWaitForFile.start();
2486- }
2487- }
2488-
2489- Button {
2490- text: i18n.tr("Cancel")
2491- onClicked: PopupUtils.close(dialogContentHubNotFound)
2492- }
2493- }
2494- }
2495-
2496 // UserMetrics to show Music stuff on welcome screen
2497 Metric {
2498 id: songsMetric
2499@@ -607,9 +548,7 @@
2500
2501 // Display walkthrough on first run, even if the user has music
2502 if (firstRun) {
2503- var comp = Qt.createComponent("common/Walkthrough/FirstRunWalkthrough.qml")
2504- var walkthrough = comp.createObject(mainPageStack, {});
2505- mainPageStack.push(walkthrough)
2506+ mainPageStack.push(Qt.resolvedUrl("components/Walkthrough/FirstRunWalkthrough.qml"), {})
2507 }
2508
2509 if (args.values.url) {
2510@@ -808,10 +747,10 @@
2511
2512 // TODO: improve in refactoring to be able detect when a track is removed
2513 // Update playlists page
2514- if (musicPlaylistPage.visible) {
2515+ if (playlistsPage.visible) {
2516 playlistModel.filterPlaylists()
2517 } else {
2518- musicPlaylistPage.changed = true
2519+ playlistsPage.changed = true
2520 }
2521 }
2522 }
2523@@ -847,10 +786,10 @@
2524 console.debug("Removed recent:", JSON.stringify(removed))
2525 Library.recentRemoveAlbums(removed)
2526
2527- if (musicStartPage.visible) {
2528+ if (recentPage.visible) {
2529 recentModel.filterRecent()
2530 } else {
2531- musicStartPage.changed = true
2532+ recentPage.changed = true
2533 }
2534 }
2535 }
2536@@ -965,7 +904,7 @@
2537 if (trackQueue.model.count === 0) {
2538 // Nothing in the queue so stop and pop the queue
2539 player.stop()
2540- musicToolbar.goBack()
2541+ mainPageStack.goBack()
2542 } else if (items.indexOf(player.currentIndex) > -1) {
2543 // Current track was removed
2544
2545@@ -1022,6 +961,7 @@
2546 if (preLoadComplete)
2547 {
2548 loading.visible = false
2549+<<<<<<< TREE
2550 playlistTab.loading = false
2551 playlistTab.populated = true
2552 }
2553@@ -1401,82 +1341,339 @@
2554
2555 Image {
2556 id: imageEmptySD
2557+=======
2558+ playlistsTab.loading = false
2559+ playlistsTab.populated = true
2560+ }
2561+ }
2562+ }
2563+
2564+ Layouts {
2565+ objectName: "musicLayout"
2566+ id: layouts
2567+ anchors.fill: parent
2568+ layouts: [
2569+ ConditionalLayout {
2570+ name: "wideAspect"
2571+ when: wideAspect
2572+ Rectangle {
2573+ anchors.fill: parent
2574+ color: "transparent"
2575+ Rectangle {
2576+ width: layouts.width*0.375
2577+>>>>>>> MERGE-SOURCE
2578 anchors {
2579- verticalCenter: parent.verticalCenter
2580- }
2581- antialiasing: true
2582- fillMode: Image.PreserveAspectFit
2583- height: units.gu(7)
2584- smooth: true
2585- source: "images/music_empty_SD.png"
2586- }
2587- }
2588-
2589- Label {
2590- color: styleMusic.libraryEmpty.labelColor
2591- elide: Text.ElideRight
2592- fontSize: "x-large"
2593- horizontalAlignment: Text.AlignLeft
2594- maximumLineCount: 2
2595- text: i18n.tr("No music found")
2596- width: parent.width
2597- wrapMode: Text.WordWrap
2598- }
2599-
2600- Label {
2601- color: styleMusic.libraryEmpty.labelColor
2602- elide: Text.ElideRight
2603- fontSize: "large"
2604- horizontalAlignment: Text.AlignLeft
2605- maximumLineCount: 4
2606- text: i18n.tr("Connect your device to any computer and simply drag files to the Music folder or insert removable media with music.")
2607- width: parent.width
2608- wrapMode: Text.WordWrap
2609- }
2610- }
2611- }
2612-
2613- // Overlay to show when no playlists are on the device
2614- Rectangle {
2615- id: playlistsEmpty
2616+ top: parent.top
2617+ bottom:parent.bottom
2618+ right: parent.right
2619+ }
2620+ color: "transparent"
2621+
2622+ NowPlaying {
2623+ id: nowPlaying
2624+ Layouts.item: "nowPlaying"
2625+ }
2626+ }
2627+ }
2628+ }
2629+ ]
2630+
2631+ Loader {
2632+ id: musicToolbar
2633+ anchors {
2634+ bottom: parent.bottom
2635+ left: parent.left
2636+ right: parent.right
2637+ }
2638+ asynchronous: true
2639+ source: "components/MusicToolbar.qml"
2640+ visible: mainPageStack.currentPage.title !== i18n.tr("Now playing") &&
2641+ mainPageStack.currentPage.title !== i18n.tr("Queue") &&
2642+ mainPageStack.currentPage.title !== i18n.tr("Select playlist") &&
2643+ !firstRun &&
2644+ !wideAspect
2645+ z: 200 // put on top of everything else
2646+ }
2647+
2648+ PageStack {
2649+ id: mainPageStack
2650+
2651+ // Properties storing the current page info
2652+ property Page currentMusicPage: null // currentPage can be Tabs
2653+ property bool popping: false
2654+
2655+ /* Helper functions */
2656+
2657+ // Go back up the stack if possible
2658+ function goBack() {
2659+ if (depth > 1) {
2660+ pop()
2661+ }
2662+ }
2663+
2664+ // Pop a specific page in the stack
2665+ function popPage(page) {
2666+ var tmpPages = []
2667+
2668+ popping = true
2669+
2670+ while (currentPage !== page && depth > 0) {
2671+ tmpPages.push(currentPage)
2672+ pop()
2673+ }
2674+
2675+ if (depth > 0) {
2676+ pop()
2677+ }
2678+
2679+ for (var i=tmpPages.length - 1; i > -1; i--) {
2680+ push(tmpPages[i])
2681+ }
2682+
2683+ popping = false
2684+ }
2685+
2686+ // Set the current page, and any parent/stacks
2687+ function setPage(childPage) {
2688+ if (!popping) {
2689+ currentMusicPage = childPage;
2690+ }
2691+ }
2692+
2693+ Tabs {
2694+ id: tabs
2695+ anchors {
2696+ fill: parent
2697+ }
2698+
2699+ property Tab lastTab: selectedTab
2700+
2701+ onSelectedTabChanged: {
2702+ // pause loading of the models in the old tab
2703+ if (lastTab !== null && lastTab !== selectedTab) {
2704+ allowLoading(lastTab, false);
2705+ }
2706+
2707+ lastTab = selectedTab;
2708+
2709+ ensurePopulated(selectedTab);
2710+ }
2711+
2712+ onSelectedTabIndexChanged: {
2713+ if (loadedUI) { // store the tab index if changed by the user
2714+ startupSettings.tabIndex = selectedTabIndex
2715+ }
2716+ }
2717+
2718+ // Use a repeater to 'hide' the recent tab when the model is empty
2719+ // A repeater is used because the Tabs component respects adds and
2720+ // removes. Whereas replacing the list tabChildren does not appear
2721+ // to respect removes and setting the page as active: false causes
2722+ // the page to be blank but the action to still in the overflow
2723+ Repeater {
2724+ id: recentTabRepeater
2725+ // If the model has not loaded and at startup the db was not empty
2726+ // then show recent or
2727+ // If the workerlist has been set and it has values then show recent
2728+ model: (!recentModel.preLoadComplete && !startupRecentEmpty) ||
2729+ (recentModel.workerList !== undefined &&
2730+ recentModel.workerList.length > 0) ? 1 : 0
2731+ delegate: Component {
2732+ // First tab is all music
2733+ Tab {
2734+ property bool populated: recentTabRepeater.populated
2735+ property var loader: [recentModel.filterRecent]
2736+ property bool loading: recentTabRepeater.loading
2737+ property var model: [recentModel, albumTracksModel]
2738+ id: recentTab
2739+ objectName: "recentTab"
2740+ anchors.fill: parent
2741+ title: page.title
2742+
2743+ // Tab content begins here
2744+ page: Recent {
2745+ id: recentPage
2746+ }
2747+ }
2748+ }
2749+
2750+ // Store the startup state of the db separately otherwise
2751+ // it breaks the binding of model
2752+ property bool startupRecentEmpty: Library.isRecentEmpty()
2753+
2754+ // cached values of the recent model that are copied when
2755+ // the tab is created
2756+ property bool loading: false
2757+ property bool populated: false
2758+
2759+ onCountChanged: {
2760+ if (count === 0 && loadedUI) {
2761+ // Jump to the albums tab when recent is empty
2762+ tabs.selectedTabIndex = albumsTab.index
2763+ } else if (count > 0 && !loadedUI) {
2764+ // UI is still loading and recent tab has been inserted
2765+ // so move the selected index 'down' as the value is
2766+ // not auto updated - this is for the case of loading
2767+ // directly to the recent tab (otherwise the content
2768+ // appears as the second tab but the tabs think they are
2769+ // on the first tab)
2770+ tabs.selectedTabIndex -= 1
2771+ } else if (count > 0 && loadedUI) {
2772+ // tab inserted while the app is running so move the
2773+ // selected index 'up' to keep the same position
2774+ tabs.selectedTabIndex += 1
2775+ }
2776+ }
2777+ }
2778+
2779+ // Second tab is arists
2780+ Tab {
2781+ property bool populated: true
2782+ property var loader: []
2783+ property bool loading: false
2784+ property var model: []
2785+ id: artistsTab
2786+ objectName: "artistsTab"
2787+ anchors.fill: parent
2788+ title: page.title
2789+
2790+ // tab content
2791+ page: Artists {
2792+ id: artistsPage
2793+ }
2794+ }
2795+
2796+ // third tab is albums
2797+ Tab {
2798+ property bool populated: true
2799+ property var loader: []
2800+ property bool loading: false
2801+ property var model: []
2802+ id: albumsTab
2803+ objectName: "albumsTab"
2804+ anchors.fill: parent
2805+ title: page.title
2806+
2807+ // Tab content begins here
2808+ page: Albums {
2809+ id: albumsPage
2810+ }
2811+ }
2812+
2813+ // forth tab is genres
2814+ Tab {
2815+ property bool populated: true
2816+ property var loader: []
2817+ property bool loading: false
2818+ property var model: []
2819+ id: genresTab
2820+ objectName: "genresTab"
2821+ anchors.fill: parent
2822+ title: page.title
2823+
2824+ // Tab content begins here
2825+ page: Genres {
2826+ id: genresPage
2827+ }
2828+ }
2829+
2830+ // fourth tab is all songs
2831+ Tab {
2832+ property bool populated: true
2833+ property var loader: []
2834+ property bool loading: false
2835+ property var model: []
2836+ id: songsTab
2837+ objectName: "songsTab"
2838+ anchors.fill: parent
2839+ title: page.title
2840+
2841+ // Tab content begins here
2842+ page: Songs {
2843+ id: tracksPage
2844+ }
2845+ }
2846+
2847+
2848+ // fifth tab is the playlists
2849+ Tab {
2850+ property bool populated: false
2851+ property var loader: [playlistModel.filterPlaylists]
2852+ property bool loading: false
2853+ property var model: [playlistModel, albumTracksModel]
2854+ id: playlistsTab
2855+ objectName: "playlistsTab"
2856+ anchors.fill: parent
2857+ title: page.title
2858+
2859+ // Tab content begins here
2860+ page: Playlists {
2861+ id: playlistsPage
2862+ }
2863+ }
2864+
2865+ // Set the models in the tab to allow/disallow loading
2866+ function allowLoading(tabToLoad, state)
2867+ {
2868+ if (tabToLoad !== undefined && tabToLoad.model !== undefined)
2869+ {
2870+ for (var i=0; i < tabToLoad.model.length; i++)
2871+ {
2872+ tabToLoad.model[i].canLoad = state;
2873+ }
2874+ }
2875+ }
2876+
2877+ function ensurePopulated(selectedTab)
2878+ {
2879+ allowLoading(selectedTab, true); // allow loading of the models
2880+
2881+ if (!selectedTab.populated && !selectedTab.loading && loadedUI) {
2882+ loading.visible = true
2883+ selectedTab.loading = true
2884+
2885+ if (selectedTab.loader !== undefined)
2886+ {
2887+ for (var i=0; i < selectedTab.loader.length; i++)
2888+ {
2889+ selectedTab.loader[i]();
2890+ }
2891+ }
2892+ }
2893+ loading.visible = selectedTab.loading || !selectedTab.populated
2894+ }
2895+
2896+ function pushNowPlaying()
2897+ {
2898+ // only push if on a different page
2899+ if (mainPageStack.currentPage.title !== i18n.tr("Now playing")
2900+ && mainPageStack.currentPage.title !== i18n.tr("Queue")) {
2901+ mainPageStack.push(Qt.resolvedUrl("components/NowPlaying.qml"), {})
2902+ }
2903+
2904+ if (mainPageStack.currentPage.title === i18n.tr("Queue")) {
2905+ mainPageStack.currentPage.isListView = false; // ensure full view
2906+ }
2907+ }
2908+ } // end of tabs
2909+ }
2910+
2911+ Loader {
2912+ id: emptyPageLoader
2913+ // Do not be active if content-hub is importing due to the models resetting
2914+ // this then causes the empty page loader to partially run then showing a blank header
2915+ active: noMusic && !firstRun && contentHubWaitForFile.processId === -1
2916 anchors {
2917 fill: parent
2918- topMargin: -emptyPage.header.height
2919- }
2920- color: mainView.backgroundColor
2921- visible: emptyPage.noPlaylists && !emptyPage.noMusic && (playlistTab.index === tabs.selectedTab.index || mainPageStack.currentPage.title === i18n.tr("Select playlist"))
2922-
2923- Column {
2924- anchors.centerIn: parent
2925- spacing: units.gu(4)
2926- width: units.gu(36)
2927-
2928- Label {
2929- color: styleMusic.libraryEmpty.labelColor
2930- elide: Text.ElideRight
2931- fontSize: "x-large"
2932- horizontalAlignment: Text.AlignLeft
2933- maximumLineCount: 2
2934- text: i18n.tr("No playlists found")
2935- width: parent.width
2936- wrapMode: Text.WordWrap
2937- }
2938-
2939- Label {
2940- color: styleMusic.libraryEmpty.labelColor
2941- elide: Text.ElideRight
2942- fontSize: "large"
2943- horizontalAlignment: Text.AlignLeft
2944- maximumLineCount: 4
2945- text: i18n.tr("Get more out of Music by tapping the %1 icon to start making playlists for every mood and occasion.").arg('"+"')
2946- width: parent.width
2947- wrapMode: Text.WordWrap
2948- }
2949- }
2950- }
2951- }
2952-
2953- LoadingSpinnerComponent {
2954- id: loading
2955+ }
2956+ source: "ui/LibraryEmptyState.qml"
2957+ visible: active
2958+
2959+ property bool noMusic: allSongsModel.rowCount === 0 && allSongsModelModel.status === SongsModel.Ready && loadedUI
2960+ }
2961+
2962+ LoadingSpinnerComponent {
2963+ id: loading
2964+ }
2965 }
2966 } // end of main view
2967
2968=== added directory 'app/ui'
2969=== renamed file 'MusicaddtoPlaylist.qml' => 'app/ui/AddToPlaylist.qml'
2970--- MusicaddtoPlaylist.qml 2015-01-28 02:27:52 +0000
2971+++ app/ui/AddToPlaylist.qml 2015-02-10 03:13:44 +0000
2972@@ -23,9 +23,11 @@
2973 import Ubuntu.Components.ListItems 1.0 as ListItem
2974 import Ubuntu.Components.Popups 1.0
2975 import QtQuick.LocalStorage 2.0
2976-import "meta-database.js" as Library
2977-import "playlists.js" as Playlists
2978-import "common"
2979+import "../logic/meta-database.js" as Library
2980+import "../logic/playlists.js" as Playlists
2981+import "../components"
2982+import "../components/Flickables"
2983+import "../components/HeadState"
2984
2985
2986 /* NOTE:
2987@@ -45,25 +47,10 @@
2988 searchResultsCount: addToPlaylistModelFilter.count
2989 state: "default"
2990 states: [
2991- PageHeadState {
2992- name: "default"
2993- head: addToPlaylistPage.head
2994- actions: [
2995- Action {
2996- enabled: allSongsModel.count > 0
2997- objectName: "newPlaylistButton"
2998- iconName: "add"
2999- onTriggered: {
3000- customdebug("New playlist.")
3001- PopupUtils.open(newPlaylistDialog, mainView)
3002- }
3003- },
3004- Action {
3005- enabled: playlistModel.model.count > 0 && allSongsModel.count > 0
3006- iconName: "search"
3007- onTriggered: addToPlaylistPage.state = "search"
3008- }
3009- ]
3010+ PlaylistsHeadState {
3011+ newPlaylistEnabled: allSongsModel.count > 0
3012+ searchEnabled: playlistModel.model.count > 0 && allSongsModel.count > 0
3013+ thisPage: addToPlaylistPage
3014 },
3015 SearchHeadState {
3016 id: searchHeader
3017@@ -129,8 +116,20 @@
3018 page.covers = Playlists.getPlaylistCovers(name)
3019 }
3020
3021- musicToolbar.goBack(); // go back to the previous page
3022+ mainPageStack.goBack(); // go back to the previous page
3023 }
3024 }
3025 }
3026+
3027+ // Overlay to show when no playlists are on the device
3028+ Loader {
3029+ anchors {
3030+ fill: parent
3031+ topMargin: -playlistsPage.header.height
3032+ }
3033+ active: playlistModel.model.count === 0 && playlistModel.workerComplete
3034+ asynchronous: true
3035+ source: "../components/PlaylistsEmptyState.qml"
3036+ visible: active
3037+ }
3038 }
3039
3040=== renamed file 'MusicAlbums.qml' => 'app/ui/Albums.qml'
3041--- MusicAlbums.qml 2015-01-20 12:02:52 +0000
3042+++ app/ui/Albums.qml 2015-02-10 03:13:44 +0000
3043@@ -20,7 +20,9 @@
3044 import QtQuick 2.3
3045 import Ubuntu.Components 1.1
3046 import Ubuntu.MediaScanner 0.1
3047-import "common"
3048+import "../components"
3049+import "../components/Flickables"
3050+import "../components/HeadState"
3051
3052
3053 MusicPage {
3054@@ -31,14 +33,9 @@
3055 searchResultsCount: albumsModelFilter.count
3056 state: "default"
3057 states: [
3058- PageHeadState {
3059- name: "default"
3060- head: albumsPage.head
3061- actions: Action {
3062- enabled: albumsModelFilter.count > 0
3063- iconName: "search"
3064- onTriggered: albumsPage.state = "search"
3065- }
3066+ SearchableHeadState {
3067+ thisPage: albumsPage
3068+ searchEnabled: albumsModelFilter.count > 0
3069 },
3070 SearchHeadState {
3071 id: searchHeader
3072@@ -46,6 +43,11 @@
3073 }
3074 ]
3075
3076+ // Hack for autopilot otherwise Albums appears as MusicPage
3077+ // due to bug 1341671 it is required that there is a property so that
3078+ // qml doesn't optimise using the parent type
3079+ property bool bug1341671workaround: true
3080+
3081 CardView {
3082 id: albumCardView
3083 model: SortFilterModel {
3084@@ -70,24 +72,17 @@
3085 secondaryText: model.artist != "" ? model.artist : i18n.tr("Unknown Artist")
3086
3087 onClicked: {
3088- var comp = Qt.createComponent("common/SongsPage.qml")
3089- var songsPage = comp.createObject(mainPageStack,
3090- {
3091- "album": model.title,
3092- "artist": model.artist,
3093- "covers": [{art: model.art}],
3094- "isAlbum": true,
3095- "genre": undefined,
3096- "title": i18n.tr("Album"),
3097- "line1": model.artist,
3098- "line2": model.title,
3099- });
3100-
3101- if (songsPage == null) { // Error Handling
3102- console.log("Error creating object");
3103- }
3104-
3105- mainPageStack.push(songsPage)
3106+ mainPageStack.push(Qt.resolvedUrl("SongsView.qml"),
3107+ {
3108+ "album": model.title,
3109+ "artist": model.artist,
3110+ "covers": [{art: model.art}],
3111+ "isAlbum": true,
3112+ "genre": undefined,
3113+ "title": i18n.tr("Album"),
3114+ "line1": model.artist,
3115+ "line2": model.title
3116+ })
3117 }
3118 }
3119 }
3120
3121=== renamed file 'common/AlbumsPage.qml' => 'app/ui/ArtistView.qml'
3122--- common/AlbumsPage.qml 2015-01-30 01:23:39 +0000
3123+++ app/ui/ArtistView.qml 2015-02-10 03:13:44 +0000
3124@@ -24,11 +24,14 @@
3125 import Ubuntu.MediaScanner 0.1
3126 import Ubuntu.Thumbnailer 0.1
3127 import QtQuick.LocalStorage 2.0
3128-import "../meta-database.js" as Library
3129+import "../logic/meta-database.js" as Library
3130+import "../components"
3131+import "../components/Flickables"
3132+import "../components/ViewButton"
3133
3134 MusicPage {
3135- id: albumStackPage
3136- objectName: "albumsArtistPage"
3137+ id: artistViewPage
3138+ objectName: "artistViewPage"
3139 visible: false
3140
3141 property string artist: ""
3142@@ -50,57 +53,17 @@
3143 header: BlurredHeader {
3144 rightColumn: Column {
3145 spacing: units.gu(2)
3146- Button {
3147- id: shuffleRow
3148- height: units.gu(4)
3149- strokeColor: UbuntuColors.green
3150- width: units.gu(15)
3151- Text {
3152- anchors {
3153- centerIn: parent
3154- }
3155- color: "white"
3156- elide: Text.ElideRight
3157- height: parent.height
3158- horizontalAlignment: Text.AlignHCenter
3159- // TRANSLATORS: this appears in a button with limited space (around 14 characters)
3160- text: i18n.tr("Shuffle")
3161- verticalAlignment: Text.AlignVCenter
3162- width: parent.width - units.gu(2)
3163- }
3164- onClicked: shuffleModel(songArtistModel)
3165- }
3166- Button {
3167- id: queueAllRow
3168- height: units.gu(4)
3169- strokeColor: UbuntuColors.green
3170- width: units.gu(15)
3171- Text {
3172- anchors {
3173- centerIn: parent
3174- }
3175- color: "white"
3176- elide: Text.ElideRight
3177- height: parent.height
3178- horizontalAlignment: Text.AlignHCenter
3179- // TRANSLATORS: this appears in a button with limited space (around 14 characters)
3180- text: i18n.tr("Queue all")
3181- verticalAlignment: Text.AlignVCenter
3182- width: parent.width - units.gu(2)
3183- }
3184- onClicked: addQueueFromModel(songArtistModel)
3185- }
3186- Button {
3187- id: playRow
3188- color: UbuntuColors.green
3189- height: units.gu(4)
3190- // TRANSLATORS: this appears in a button with limited space (around 14 characters)
3191- text: i18n.tr("Play all")
3192- width: units.gu(15)
3193- onClicked: trackClicked(songArtistModel, 0, true)
3194+ ShuffleButton {
3195+ model: songArtistModel
3196+ }
3197+ QueueAllButton {
3198+ model: songArtistModel
3199+ }
3200+ PlayAllButton {
3201+ model: songArtistModel
3202 }
3203 }
3204- coverSources: albumStackPage.covers
3205+ coverSources: artistViewPage.covers
3206 height: units.gu(30)
3207 bottomColumn: Column {
3208 Label {
3209@@ -139,18 +102,18 @@
3210
3211 SongsModel {
3212 id: songArtistModel
3213- albumArtist: albumStackPage.artist
3214+ albumArtist: artistViewPage.artist
3215 store: musicStore
3216 }
3217 }
3218 itemWidth: units.gu(12)
3219 model: AlbumsModel {
3220 id: albumsModel
3221- albumArtist: albumStackPage.artist
3222+ albumArtist: artistViewPage.artist
3223 store: musicStore
3224 onStatusChanged: {
3225 if (albumsModel.status === SongsModel.Ready && loaded && albumsModel.count === 0) {
3226- musicToolbar.popPage(albumStackPage)
3227+ mainPageStack.popPage(artistViewPage)
3228 }
3229 }
3230 }
3231@@ -162,24 +125,17 @@
3232 secondaryTextVisible: false
3233
3234 onClicked: {
3235- var comp = Qt.createComponent("SongsPage.qml")
3236- var songsPage = comp.createObject(mainPageStack,
3237- {
3238- "album": model.title,
3239- "artist": model.artist,
3240- "covers": [{art: model.art}],
3241- "isAlbum": true,
3242- "genre": undefined,
3243- "title": i18n.tr("Album"),
3244- "line1": model.artist != "" ? model.artist : i18n.tr("Unknown Artist"),
3245- "line2": model.title != "" ? model.title : i18n.tr("Unknown Album"),
3246- });
3247-
3248- if (songsPage == null) { // Error Handling
3249- console.log("Error creating object");
3250- }
3251-
3252- mainPageStack.push(songsPage)
3253+ mainPageStack.push(Qt.resolvedUrl("SongsView.qml"),
3254+ {
3255+ "album": model.title,
3256+ "artist": model.artist,
3257+ "covers": [{art: model.art}],
3258+ "isAlbum": true,
3259+ "genre": undefined,
3260+ "title": i18n.tr("Album"),
3261+ "line1": model.artist != "" ? model.artist : i18n.tr("Unknown Artist"),
3262+ "line2": model.title != "" ? model.title : i18n.tr("Unknown Album")
3263+ })
3264 }
3265 }
3266 }
3267
3268=== renamed file 'MusicArtists.qml' => 'app/ui/Artists.qml'
3269--- MusicArtists.qml 2015-01-20 12:02:52 +0000
3270+++ app/ui/Artists.qml 2015-02-10 03:13:44 +0000
3271@@ -24,9 +24,11 @@
3272 import Ubuntu.MediaScanner 0.1
3273 import Ubuntu.Thumbnailer 0.1
3274 import QtQuick.LocalStorage 2.0
3275-import "meta-database.js" as Library
3276-import "playlists.js" as Playlists
3277-import "common"
3278+import "../logic/meta-database.js" as Library
3279+import "../logic/playlists.js" as Playlists
3280+import "../components"
3281+import "../components/Flickables"
3282+import "../components/HeadState"
3283
3284
3285 MusicPage {
3286@@ -37,14 +39,9 @@
3287 searchResultsCount: artistsModelFilter.count
3288 state: "default"
3289 states: [
3290- PageHeadState {
3291- name: "default"
3292- head: artistsPage.head
3293- actions: Action {
3294- enabled: artistsModelFilter.count > 0
3295- iconName: "search"
3296- onTriggered: artistsPage.state = "search"
3297- }
3298+ SearchableHeadState {
3299+ thisPage: artistsPage
3300+ searchEnabled: artistsModelFilter.count > 0
3301 },
3302 SearchHeadState {
3303 id: searchHeader
3304@@ -52,6 +49,11 @@
3305 }
3306 ]
3307
3308+ // Hack for autopilot otherwise Artists appears as MusicPage
3309+ // due to bug 1341671 it is required that there is a property so that
3310+ // qml doesn't optimise using the parent type
3311+ property bool bug1341671workaround: true
3312+
3313 CardView {
3314 id: artistCardView
3315 itemWidth: units.gu(12)
3316@@ -97,21 +99,13 @@
3317 }
3318 }
3319
3320-
3321 onClicked: {
3322- var comp = Qt.createComponent("common/AlbumsPage.qml")
3323- var albumsPage = comp.createObject(mainPageStack,
3324- {
3325- "artist": model.artist,
3326- "covers": artistCard.coverSources,
3327- "title": i18n.tr("Artist"),
3328- });
3329-
3330- if (albumsPage == null) { // Error Handling
3331- console.log("Error creating object");
3332- }
3333-
3334- mainPageStack.push(albumsPage)
3335+ mainPageStack.push(Qt.resolvedUrl("ArtistView.qml"),
3336+ {
3337+ "artist": model.artist,
3338+ "covers": artistCard.coverSources,
3339+ "title": i18n.tr("Artist")
3340+ })
3341 }
3342 }
3343 }
3344
3345=== added file 'app/ui/CMakeLists.txt'
3346--- app/ui/CMakeLists.txt 1970-01-01 00:00:00 +0000
3347+++ app/ui/CMakeLists.txt 2015-02-10 03:13:44 +0000
3348@@ -0,0 +1,4 @@
3349+# make the qml files visible on qtcreator
3350+file(GLOB UI_QML_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml)
3351+
3352+add_custom_target(com_ubuntu_music_UI_QMLFiles ALL SOURCES ${UI_QML_FILES})
3353
3354=== renamed file 'MusicGenres.qml' => 'app/ui/Genres.qml'
3355--- MusicGenres.qml 2015-01-20 12:02:52 +0000
3356+++ app/ui/Genres.qml 2015-02-10 03:13:44 +0000
3357@@ -20,7 +20,9 @@
3358 import QtQuick 2.3
3359 import Ubuntu.Components 1.1
3360 import Ubuntu.MediaScanner 0.1
3361-import "common"
3362+import "../components"
3363+import "../components/Flickables"
3364+import "../components/HeadState"
3365
3366
3367 MusicPage {
3368@@ -31,14 +33,9 @@
3369 searchResultsCount: genresModelFilter.count
3370 state: "default"
3371 states: [
3372- PageHeadState {
3373- name: "default"
3374- head: genresPage.head
3375- actions: Action {
3376- enabled: genresModelFilter.count > 0
3377- iconName: "search"
3378- onTriggered: genresPage.state = "search"
3379- }
3380+ SearchableHeadState {
3381+ thisPage: genresPage
3382+ searchEnabled: genresModelFilter.count > 0
3383 },
3384 SearchHeadState {
3385 id: searchHeader
3386@@ -46,6 +43,11 @@
3387 }
3388 ]
3389
3390+ // Hack for autopilot otherwise Albums appears as MusicPage
3391+ // due to bug 1341671 it is required that there is a property so that
3392+ // qml doesn't optimise using the parent type
3393+ property bool bug1341671workaround: true
3394+
3395 CardView {
3396 id: genreCardView
3397 itemWidth: units.gu(12)
3398@@ -98,23 +100,16 @@
3399 }
3400
3401 onClicked: {
3402- var comp = Qt.createComponent("common/SongsPage.qml")
3403- var songsPage = comp.createObject(mainPageStack,
3404- {
3405- "covers": genreCard.coverSources,
3406- "album": undefined,
3407- "isAlbum": true,
3408- "genre": model.genre,
3409- "title": i18n.tr("Genre"),
3410- "line2": model.genre,
3411- "line1": i18n.tr("Genre")
3412- });
3413-
3414- if (songsPage == null) { // Error Handling
3415- console.log("Error creating object");
3416- }
3417-
3418- mainPageStack.push(songsPage)
3419+ mainPageStack.push(Qt.resolvedUrl("SongsView.qml"),
3420+ {
3421+ "covers": genreCard.coverSources,
3422+ "album": undefined,
3423+ "isAlbum": true,
3424+ "genre": model.genre,
3425+ "title": i18n.tr("Genre"),
3426+ "line2": model.genre,
3427+ "line1": i18n.tr("Genre")
3428+ })
3429 }
3430 }
3431 }
3432
3433=== added file 'app/ui/LibraryEmptyState.qml'
3434--- app/ui/LibraryEmptyState.qml 1970-01-01 00:00:00 +0000
3435+++ app/ui/LibraryEmptyState.qml 2015-02-10 03:13:44 +0000
3436@@ -0,0 +1,123 @@
3437+/*
3438+ * Copyright (C) 2013, 2014, 2015
3439+ * Andrew Hayzen <ahayzen@gmail.com>
3440+ * Daniel Holm <d.holmen@gmail.com>
3441+ * Victor Thompson <victor.thompson@gmail.com>
3442+ *
3443+ * This program is free software; you can redistribute it and/or modify
3444+ * it under the terms of the GNU General Public License as published by
3445+ * the Free Software Foundation; version 3.
3446+ *
3447+ * This program is distributed in the hope that it will be useful,
3448+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3449+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3450+ * GNU General Public License for more details.
3451+ *
3452+ * You should have received a copy of the GNU General Public License
3453+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3454+ */
3455+
3456+import QtQuick 2.3
3457+import Ubuntu.Components 1.1
3458+
3459+Page {
3460+ id: libraryEmptyPage
3461+ anchors {
3462+ fill: parent
3463+ }
3464+
3465+ // Overlay to show when no tracks detected on the device
3466+ Rectangle {
3467+ id: libraryEmpty
3468+ anchors {
3469+ fill: parent
3470+ topMargin: -libraryEmptyPage.header.height
3471+ }
3472+ color: mainView.backgroundColor
3473+
3474+ Column {
3475+ id: noMusicTextColumn
3476+ anchors {
3477+ centerIn: parent
3478+ }
3479+ spacing: units.gu(4)
3480+ width: units.gu(36)
3481+
3482+ Row {
3483+ anchors {
3484+ horizontalCenter: parent.horizontalCenter
3485+ }
3486+
3487+ Item {
3488+ height: parent.height
3489+ width: imageEmptyDownload.width + units.gu(2)
3490+
3491+ Image {
3492+ id: imageEmptyDownload
3493+ anchors {
3494+ horizontalCenter: parent.horizontalCenter
3495+ verticalCenter: parent.verticalCenter
3496+ }
3497+ antialiasing: true
3498+ fillMode: Image.PreserveAspectFit
3499+ height: units.gu(10)
3500+ smooth: true
3501+ source: "../graphics/music_empty_download.png"
3502+ }
3503+ }
3504+
3505+ Item {
3506+ height: parent.height
3507+ width: units.gu(7)
3508+
3509+ Image {
3510+ id: imageSep
3511+ anchors {
3512+ horizontalCenter: parent.horizontalCenter
3513+ verticalCenter: parent.verticalCenter
3514+ }
3515+ antialiasing: true
3516+ fillMode: Image.PreserveAspectFit
3517+ height: units.gu(6)
3518+ smooth: true
3519+ source: "../graphics/div.png"
3520+ }
3521+ }
3522+
3523+ Image {
3524+ id: imageEmptySD
3525+ anchors {
3526+ verticalCenter: parent.verticalCenter
3527+ }
3528+ antialiasing: true
3529+ fillMode: Image.PreserveAspectFit
3530+ height: units.gu(7)
3531+ smooth: true
3532+ source: "../graphics/music_empty_SD.png"
3533+ }
3534+ }
3535+
3536+ Label {
3537+ color: styleMusic.libraryEmpty.labelColor
3538+ elide: Text.ElideRight
3539+ fontSize: "x-large"
3540+ horizontalAlignment: Text.AlignLeft
3541+ maximumLineCount: 2
3542+ text: i18n.tr("No music found")
3543+ width: parent.width
3544+ wrapMode: Text.WordWrap
3545+ }
3546+
3547+ Label {
3548+ color: styleMusic.libraryEmpty.labelColor
3549+ elide: Text.ElideRight
3550+ fontSize: "large"
3551+ horizontalAlignment: Text.AlignLeft
3552+ maximumLineCount: 4
3553+ text: i18n.tr("Connect your device to any computer and simply drag files to the Music folder or insert removable media with music.")
3554+ width: parent.width
3555+ wrapMode: Text.WordWrap
3556+ }
3557+ }
3558+ }
3559+}
3560
3561=== renamed file 'MusicNowPlaying.qml' => 'app/ui/NowPlayingView.qml'
3562--- MusicNowPlaying.qml 2015-01-28 02:30:43 +0000
3563+++ app/ui/NowPlayingView.qml 2015-02-10 03:13:44 +0000
3564@@ -22,11 +22,13 @@
3565 import QtQuick.LocalStorage 2.0
3566 import Ubuntu.Components 1.1
3567 import Ubuntu.Thumbnailer 0.1
3568-import "common"
3569-import "common/ListItemActions"
3570-import "common/Themes/Ambiance"
3571-import "meta-database.js" as Library
3572-import "playlists.js" as Playlists
3573+import "../components"
3574+import "../components/Flickables"
3575+import "../components/HeadState"
3576+import "../components/ListItemActions"
3577+import "../components/Themes/Ambiance"
3578+import "../logic/meta-database.js" as Library
3579+import "../logic/playlists.js" as Playlists
3580
3581 MusicPage {
3582 id: nowPlaying
3583@@ -35,7 +37,7 @@
3584 title: isListView ? queueTitle : nowPlayingTitle
3585 visible: false
3586
3587- property bool isListView: false
3588+ property alias isListView: nowPlaying.isListView
3589 // TRANSLATORS: this appears in the header with limited space (around 20 characters)
3590 property string nowPlayingTitle: i18n.tr("Now playing")
3591 // TRANSLATORS: this appears in the header with limited space (around 20 characters)
3592@@ -98,14 +100,8 @@
3593
3594 items.push(makeDict(trackQueue.model.get(player.currentIndex)));
3595
3596- var comp = Qt.createComponent("MusicaddtoPlaylist.qml")
3597- var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": items});
3598-
3599- if (addToPlaylist == null) { // Error Handling
3600- console.log("Error creating object");
3601- }
3602-
3603- mainPageStack.push(addToPlaylist)
3604+ mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),
3605+ {"chosenElements": items})
3606 }
3607 },
3608 Action {
3609@@ -126,619 +122,22 @@
3610 actions: defaultState.actions
3611 }
3612 },
3613- PageHeadState {
3614- id: selectionState
3615-
3616- name: "selection"
3617- backAction: Action {
3618- text: i18n.tr("Cancel selection")
3619- iconName: "back"
3620- onTriggered: {
3621- queueListLoader.item.clearSelection()
3622- queueListLoader.item.state = "normal"
3623- }
3624- }
3625- actions: [
3626- Action {
3627- text: i18n.tr("Select All")
3628- iconName: "select"
3629- onTriggered: {
3630- if (queueListLoader.item.selectedItems.length === trackQueue.model.count) {
3631- queueListLoader.item.clearSelection()
3632- } else {
3633- queueListLoader.item.selectAll()
3634- }
3635- }
3636- },
3637- Action {
3638- enabled: queueListLoader.item.selectedItems.length > 0
3639- iconName: "add-to-playlist"
3640- text: i18n.tr("Add to playlist")
3641- onTriggered: {
3642- var items = []
3643-
3644- for (var i=0; i < queueListLoader.item.selectedItems.length; i++) {
3645- items.push(makeDict(trackQueue.model.get(queueListLoader.item.selectedItems[i])));
3646- }
3647-
3648- var comp = Qt.createComponent("MusicaddtoPlaylist.qml")
3649- var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": items});
3650-
3651- if (addToPlaylist == null) { // Error Handling
3652- console.log("Error creating object");
3653- }
3654-
3655- mainPageStack.push(addToPlaylist)
3656-
3657- queueListLoader.item.closeSelection()
3658- }
3659- },
3660- Action {
3661- enabled: queueListLoader.item.selectedItems.length > 0
3662- iconName: "delete"
3663- text: i18n.tr("Delete")
3664- onTriggered: {
3665- // Remove the tracks from the queue
3666- // Use slice() to copy the list
3667- // so that the indexes don't change as they are removed
3668- trackQueue.removeQueueList(queueListLoader.item.selectedItems.slice())
3669-
3670- queueListLoader.item.closeSelection()
3671- }
3672- }
3673- ]
3674- PropertyChanges {
3675- target: nowPlaying.head
3676- backAction: selectionState.backAction
3677- actions: selectionState.actions
3678+ MultiSelectHeadState {
3679+ addToQueue: false
3680+ listview: queueListLoader.item
3681+ removable: true
3682+ thisPage: nowPlaying
3683+
3684+ onRemoved: {
3685+ // Remove the tracks from the queue
3686+ // Use slice() to copy the list
3687+ // so that the indexes don't change as they are removed
3688+ trackQueue.removeQueueList(selectedItems.slice())
3689 }
3690 }
3691 ]
3692
3693- Item {
3694- id: fullview
3695- anchors {
3696- top: parent.top
3697- topMargin: mainView.header.height
3698- }
3699- height: parent.height - mainView.header.height - units.gu(9.5)
3700- visible: !isListView
3701- width: parent.width
3702-
3703- BlurredBackground {
3704- id: blurredBackground
3705- anchors {
3706- left: parent.left
3707- right: parent.right
3708- top: parent.top
3709- }
3710- art: albumImage.firstSource
3711- height: parent.height - units.gu(7)
3712-
3713- Item {
3714- id: albumImageContainer
3715- anchors {
3716- horizontalCenter: parent.horizontalCenter
3717- top: parent.top
3718- }
3719- height: parent.height
3720- width: parent.width
3721-
3722- CoverGrid {
3723- id: albumImage
3724- anchors.centerIn: parent
3725- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
3726- size: parent.width > parent.height ? parent.height : parent.width
3727- }
3728- }
3729-
3730- Rectangle {
3731- id: nowPlayingWideAspectLabelsBackground
3732- anchors.bottom: parent.bottom
3733- color: styleMusic.common.black
3734- height: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(10) : units.gu(13)
3735- opacity: 0.8
3736- width: parent.width
3737- }
3738-
3739- /* Column for labels in wideAspect */
3740- Column {
3741- id: nowPlayingWideAspectLabels
3742- spacing: units.gu(1)
3743- anchors {
3744- left: parent.left
3745- leftMargin: units.gu(2)
3746- right: parent.right
3747- rightMargin: units.gu(2)
3748- top: nowPlayingWideAspectLabelsBackground.top
3749- topMargin: nowPlayingWideAspectTitle.lineCount === 1 ? units.gu(2) : units.gu(1.5)
3750- }
3751-
3752- /* Title of track */
3753- Label {
3754- id: nowPlayingWideAspectTitle
3755- anchors {
3756- left: parent.left
3757- leftMargin: units.gu(1)
3758- right: parent.right
3759- rightMargin: units.gu(1)
3760- }
3761- color: styleMusic.playerControls.labelColor
3762- elide: Text.ElideRight
3763- fontSize: "x-large"
3764- maximumLineCount: 2
3765- objectName: "playercontroltitle"
3766- text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
3767- wrapMode: Text.WordWrap
3768- }
3769-
3770- /* Artist of track */
3771- Label {
3772- id: nowPlayingWideAspectArtist
3773- anchors {
3774- left: parent.left
3775- leftMargin: units.gu(1)
3776- right: parent.right
3777- rightMargin: units.gu(1)
3778- }
3779- color: styleMusic.nowPlaying.labelSecondaryColor
3780- elide: Text.ElideRight
3781- fontSize: "small"
3782- text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
3783- }
3784- }
3785-
3786- /* Detect cover art swipe */
3787- MouseArea {
3788- anchors.fill: parent
3789- property string direction: "None"
3790- property real lastX: -1
3791-
3792- onPressed: lastX = mouse.x
3793-
3794- onReleased: {
3795- var diff = mouse.x - lastX
3796- if (Math.abs(diff) < units.gu(4)) {
3797- return;
3798- } else if (diff < 0) {
3799- player.nextSong()
3800- } else if (diff > 0) {
3801- player.previousSong()
3802- }
3803- }
3804- }
3805- }
3806-
3807- /* Background for progress bar component */
3808- Rectangle {
3809- id: musicToolbarFullProgressBackground
3810- anchors {
3811- bottom: parent.bottom
3812- left: parent.left
3813- right: parent.right
3814- top: blurredBackground.bottom
3815- }
3816- color: styleMusic.common.black
3817- }
3818-
3819- /* Progress bar component */
3820- Item {
3821- id: musicToolbarFullProgressContainer
3822- anchors.left: parent.left
3823- anchors.leftMargin: units.gu(3)
3824- anchors.right: parent.right
3825- anchors.rightMargin: units.gu(3)
3826- anchors.top: blurredBackground.bottom
3827- anchors.topMargin: units.gu(1)
3828- height: units.gu(3)
3829- width: parent.width
3830-
3831- /* Position label */
3832- Label {
3833- id: musicToolbarFullPositionLabel
3834- anchors.top: progressSliderMusic.bottom
3835- anchors.topMargin: units.gu(-2)
3836- anchors.left: parent.left
3837- color: styleMusic.nowPlaying.labelSecondaryColor
3838- fontSize: "small"
3839- height: parent.height
3840- horizontalAlignment: Text.AlignHCenter
3841- text: durationToString(player.position)
3842- verticalAlignment: Text.AlignVCenter
3843- width: units.gu(3)
3844- }
3845-
3846- Slider {
3847- id: progressSliderMusic
3848- anchors.left: parent.left
3849- anchors.right: parent.right
3850- maximumValue: player.duration // load value at startup
3851- objectName: "progressSliderShape"
3852- style: UbuntuBlueSliderStyle {}
3853- value: player.position // load value at startup
3854-
3855- function formatValue(v) {
3856- if (seeking) { // update position label while dragging
3857- musicToolbarFullPositionLabel.text = durationToString(v)
3858- }
3859-
3860- return durationToString(v)
3861- }
3862-
3863- property bool seeking: false
3864- property bool seeked: false
3865-
3866- onSeekingChanged: {
3867- if (seeking === false) {
3868- musicToolbarFullPositionLabel.text = durationToString(player.position)
3869- }
3870- }
3871-
3872- onPressedChanged: {
3873- seeking = pressed
3874-
3875- if (!pressed) {
3876- seeked = true
3877- player.seek(value)
3878-
3879- musicToolbarFullPositionLabel.text = durationToString(value)
3880- }
3881- }
3882-
3883- Connections {
3884- target: player
3885- onPositionChanged: {
3886- // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
3887- if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
3888- musicToolbarFullPositionLabel.text = durationToString(player.position)
3889- musicToolbarFullDurationLabel.text = durationToString(player.duration)
3890-
3891- progressSliderMusic.value = player.position
3892- progressSliderMusic.maximumValue = player.duration
3893- }
3894-
3895- progressSliderMusic.seeked = false;
3896- }
3897- onStopped: {
3898- musicToolbarFullPositionLabel.text = durationToString(0);
3899- musicToolbarFullDurationLabel.text = durationToString(0);
3900- }
3901- }
3902- }
3903-
3904- /* Duration label */
3905- Label {
3906- id: musicToolbarFullDurationLabel
3907- anchors.top: progressSliderMusic.bottom
3908- anchors.topMargin: units.gu(-2)
3909- anchors.right: parent.right
3910- color: styleMusic.nowPlaying.labelSecondaryColor
3911- fontSize: "small"
3912- height: parent.height
3913- horizontalAlignment: Text.AlignHCenter
3914- text: durationToString(player.duration)
3915- verticalAlignment: Text.AlignVCenter
3916- width: units.gu(3)
3917- }
3918- }
3919- }
3920-
3921- Loader {
3922- id: queueListLoader
3923- anchors {
3924- fill: parent
3925- }
3926- asynchronous: true
3927- sourceComponent: ListView {
3928- id: queueList
3929- anchors {
3930- bottomMargin: musicToolbarFullContainer.height + units.gu(2)
3931- fill: parent
3932- topMargin: units.gu(2)
3933- }
3934- delegate: queueDelegate
3935- footer: Item {
3936- height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
3937- }
3938- model: trackQueue.model
3939- objectName: "nowPlayingqueueList"
3940-
3941- property int normalHeight: units.gu(6)
3942- property int transitionDuration: 250 // transition length of animations
3943-
3944- onCountChanged: {
3945- customdebug("Queue: Now has: " + queueList.count + " tracks")
3946- }
3947-
3948- // Requirements for ListItemWithActions
3949- property var selectedItems: []
3950-
3951- signal clearSelection()
3952- signal closeSelection()
3953- signal selectAll()
3954-
3955- onClearSelection: selectedItems = []
3956- onCloseSelection: {
3957- clearSelection()
3958- state = "normal"
3959- }
3960- onSelectAll: {
3961- var tmp = selectedItems
3962-
3963- for (var i=0; i < model.count; i++) {
3964- if (tmp.indexOf(i) === -1) {
3965- tmp.push(i)
3966- }
3967- }
3968-
3969- selectedItems = tmp
3970- }
3971- onVisibleChanged: {
3972- if (!visible) {
3973- closeSelection()
3974- }
3975- }
3976-
3977- Component.onCompleted: {
3978- // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
3979- // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
3980- var scaleFactor = units.gridUnit / 8;
3981- maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
3982- flickDeceleration = flickDeceleration * scaleFactor;
3983- }
3984-
3985- Component {
3986- id: queueDelegate
3987- ListItemWithActions {
3988- id: queueListItem
3989- color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
3990- height: queueList.normalHeight
3991- objectName: "nowPlayingListItem" + index
3992- state: ""
3993-
3994- leftSideAction: Remove {
3995- onTriggered: trackQueue.removeQueueList([index])
3996- }
3997- multiselectable: true
3998- reorderable: true
3999- rightSideActions: [
4000- AddToPlaylist{
4001-
4002- }
4003- ]
4004-
4005- onItemClicked: {
4006- customdebug("File: " + model.filename) // debugger
4007- trackQueueClick(index); // toggle track state
4008- }
4009- onReorder: {
4010- console.debug("Move: ", from, to);
4011-
4012- trackQueue.model.move(from, to, 1);
4013- Library.moveQueueItem(from, to);
4014-
4015- // Maintain currentIndex with current song
4016- if (from === player.currentIndex) {
4017- player.currentIndex = to;
4018- }
4019- else if (from < player.currentIndex && to >= player.currentIndex) {
4020- player.currentIndex -= 1;
4021- }
4022- else if (from > player.currentIndex && to <= player.currentIndex) {
4023- player.currentIndex += 1;
4024- }
4025-
4026- queueIndex = player.currentIndex
4027- }
4028-
4029- Item {
4030- id: trackContainer;
4031- anchors {
4032- fill: parent
4033- }
4034-
4035- NumberAnimation {
4036- id: trackContainerReorderAnimation
4037- target: trackContainer;
4038- property: "anchors.leftMargin";
4039- duration: queueList.transitionDuration;
4040- to: units.gu(2)
4041- }
4042-
4043- NumberAnimation {
4044- id: trackContainerResetAnimation
4045- target: trackContainer;
4046- property: "anchors.leftMargin";
4047- duration: queueList.transitionDuration;
4048- to: units.gu(0.5)
4049- }
4050-
4051- MusicRow {
4052- id: musicRow
4053- height: parent.height
4054- column: Column {
4055- Label {
4056- id: trackTitle
4057- color: player.currentIndex === index ? UbuntuColors.blue
4058- : styleMusic.common.music
4059- fontSize: "small"
4060- objectName: "titleLabel"
4061- text: model.title
4062- }
4063-
4064- Label {
4065- id: trackArtist
4066- color: styleMusic.common.subtitle
4067- fontSize: "x-small"
4068- objectName: "artistLabel"
4069- text: model.author
4070- }
4071- }
4072- }
4073- }
4074- }
4075- }
4076- }
4077- visible: isListView
4078- }
4079-
4080- /* Full toolbar */
4081- Rectangle {
4082- id: musicToolbarFullContainer
4083- anchors.bottom: parent.bottom
4084- color: styleMusic.common.black
4085- height: units.gu(10)
4086- width: parent.width
4087-
4088- /* Repeat button */
4089- MouseArea {
4090- id: nowPlayingRepeatButton
4091- anchors.right: nowPlayingPreviousButton.left
4092- anchors.rightMargin: units.gu(1)
4093- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
4094- height: units.gu(6)
4095- opacity: player.repeat && !emptyPage.noMusic ? 1 : .4
4096- width: height
4097- onClicked: player.repeat = !player.repeat
4098-
4099- Icon {
4100- id: repeatIcon
4101- height: units.gu(3)
4102- width: height
4103- anchors.verticalCenter: parent.verticalCenter
4104- anchors.horizontalCenter: parent.horizontalCenter
4105- color: "white"
4106- name: "media-playlist-repeat"
4107- objectName: "repeatShape"
4108- opacity: player.repeat && !emptyPage.noMusic ? 1 : .4
4109- }
4110- }
4111-
4112- /* Previous button */
4113- MouseArea {
4114- id: nowPlayingPreviousButton
4115- anchors.right: nowPlayingPlayButton.left
4116- anchors.rightMargin: units.gu(1)
4117- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
4118- height: units.gu(6)
4119- opacity: trackQueue.model.count === 0 ? .4 : 1
4120- width: height
4121- onClicked: player.previousSong()
4122-
4123- Icon {
4124- id: nowPlayingPreviousIndicator
4125- height: units.gu(3)
4126- width: height
4127- anchors.verticalCenter: parent.verticalCenter
4128- anchors.horizontalCenter: parent.horizontalCenter
4129- color: "white"
4130- name: "media-skip-backward"
4131- objectName: "previousShape"
4132- opacity: 1
4133- }
4134- }
4135-
4136- /* Play/Pause button */
4137- MouseArea {
4138- id: nowPlayingPlayButton
4139- anchors.centerIn: parent
4140- height: units.gu(10)
4141- width: height
4142- onClicked: player.toggle()
4143-
4144- Icon {
4145- id: nowPlayingPlayIndicator
4146- height: units.gu(6)
4147- width: height
4148- anchors.verticalCenter: parent.verticalCenter
4149- anchors.horizontalCenter: parent.horizontalCenter
4150- opacity: emptyPage.noMusic ? .4 : 1
4151- color: "white"
4152- name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
4153- objectName: "playShape"
4154- }
4155- }
4156-
4157- /* Next button */
4158- MouseArea {
4159- id: nowPlayingNextButton
4160- anchors.left: nowPlayingPlayButton.right
4161- anchors.leftMargin: units.gu(1)
4162- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
4163- height: units.gu(6)
4164- opacity: trackQueue.model.count === 0 ? .4 : 1
4165- width: height
4166- onClicked: player.nextSong()
4167-
4168- Icon {
4169- id: nowPlayingNextIndicator
4170- height: units.gu(3)
4171- width: height
4172- anchors.verticalCenter: parent.verticalCenter
4173- anchors.horizontalCenter: parent.horizontalCenter
4174- color: "white"
4175- name: "media-skip-forward"
4176- objectName: "forwardShape"
4177- opacity: 1
4178- }
4179- }
4180-
4181- /* Shuffle button */
4182- MouseArea {
4183- id: nowPlayingShuffleButton
4184- anchors.left: nowPlayingNextButton.right
4185- anchors.leftMargin: units.gu(1)
4186- anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
4187- height: units.gu(6)
4188- opacity: player.shuffle && !emptyPage.noMusic ? 1 : .4
4189- width: height
4190- onClicked: player.shuffle = !player.shuffle
4191-
4192- Icon {
4193- id: shuffleIcon
4194- height: units.gu(3)
4195- width: height
4196- anchors.verticalCenter: parent.verticalCenter
4197- anchors.horizontalCenter: parent.horizontalCenter
4198- color: "white"
4199- name: "media-playlist-shuffle"
4200- objectName: "shuffleShape"
4201- opacity: player.shuffle && !emptyPage.noMusic ? 1 : .4
4202- }
4203- }
4204-
4205- /* Object which provides the progress bar when in the queue */
4206- Rectangle {
4207- id: playerControlsProgressBar
4208- anchors {
4209- bottom: parent.bottom
4210- left: parent.left
4211- right: parent.right
4212- }
4213- color: styleMusic.common.black
4214- height: units.gu(0.25)
4215- visible: isListView
4216-
4217- Rectangle {
4218- id: playerControlsProgressBarHint
4219- anchors {
4220- left: parent.left
4221- bottom: parent.bottom
4222- }
4223- color: UbuntuColors.blue
4224- height: parent.height
4225- width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
4226-
4227- Connections {
4228- target: player
4229- onPositionChanged: {
4230- playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
4231- }
4232- onStopped: {
4233- playerControlsProgressBarHint.width = 0;
4234- }
4235- }
4236- }
4237- }
4238+ NowPlaying {
4239+ id: view
4240 }
4241 }
4242
4243=== renamed file 'MusicPlaylists.qml' => 'app/ui/Playlists.qml'
4244--- MusicPlaylists.qml 2015-01-21 00:10:33 +0000
4245+++ app/ui/Playlists.qml 2015-02-10 03:13:44 +0000
4246@@ -23,8 +23,10 @@
4247 import Ubuntu.Components.Popups 1.0
4248 import QtMultimedia 5.0
4249 import QtQuick.LocalStorage 2.0
4250-import "playlists.js" as Playlists
4251-import "common"
4252+import "../logic/playlists.js" as Playlists
4253+import "../components"
4254+import "../components/Flickables"
4255+import "../components/HeadState"
4256
4257 // page for the playlists
4258 MusicPage {
4259@@ -37,25 +39,10 @@
4260 searchResultsCount: playlistModelFilter.count
4261 state: "default"
4262 states: [
4263- PageHeadState {
4264- name: "default"
4265- head: playlistsPage.head
4266- actions: [
4267- Action {
4268- enabled: allSongsModel.count > 0
4269- objectName: "newPlaylistButton"
4270- iconName: "add"
4271- onTriggered: {
4272- customdebug("New playlist.")
4273- PopupUtils.open(newPlaylistDialog, mainView)
4274- }
4275- },
4276- Action {
4277- enabled: playlistModel.model.count > 0 && allSongsModel.count > 0
4278- iconName: "search"
4279- onTriggered: playlistsPage.state = "search"
4280- }
4281- ]
4282+ PlaylistsHeadState {
4283+ newPlaylistEnabled: allSongsModel.count > 0
4284+ searchEnabled: playlistModel.model.count > 0 && allSongsModel.count > 0
4285+ thisPage: playlistsPage
4286 },
4287 SearchHeadState {
4288 id: searchHeader
4289@@ -99,25 +86,30 @@
4290 onClicked: {
4291 albumTracksModel.filterPlaylistTracks(model.name)
4292
4293- var comp = Qt.createComponent("common/SongsPage.qml")
4294- var songsPage = comp.createObject(mainPageStack,
4295- {
4296- "album": undefined,
4297- "covers": coverSources,
4298- "isAlbum": false,
4299- "genre": undefined,
4300- "page": playlistsPage,
4301- "title": i18n.tr("Playlist"),
4302- "line1": i18n.tr("Playlist"),
4303- "line2": model.name,
4304- });
4305-
4306- if (songsPage == null) { // Error Handling
4307- console.log("Error creating object");
4308- }
4309-
4310- mainPageStack.push(songsPage)
4311+ mainPageStack.push(Qt.resolvedUrl("SongsView.qml"),
4312+ {
4313+ "album": undefined,
4314+ "covers": coverSources,
4315+ "isAlbum": false,
4316+ "genre": undefined,
4317+ "page": playlistsPage,
4318+ "title": i18n.tr("Playlist"),
4319+ "line1": i18n.tr("Playlist"),
4320+ "line2": model.name,
4321+ })
4322 }
4323 }
4324 }
4325+
4326+ // Overlay to show when no playlists are on the device
4327+ Loader {
4328+ anchors {
4329+ fill: parent
4330+ topMargin: -playlistsPage.header.height
4331+ }
4332+ active: playlistModel.model.count === 0 && playlistModel.workerComplete
4333+ asynchronous: true
4334+ source: "../components/PlaylistsEmptyState.qml"
4335+ visible: active
4336+ }
4337 }
4338
4339=== renamed file 'MusicStart.qml' => 'app/ui/Recent.qml'
4340--- MusicStart.qml 2014-11-10 22:20:24 +0000
4341+++ app/ui/Recent.qml 2015-02-10 03:13:44 +0000
4342@@ -25,12 +25,14 @@
4343 import Ubuntu.Thumbnailer 0.1
4344 import QtMultimedia 5.0
4345 import QtQuick.LocalStorage 2.0
4346-import "meta-database.js" as Library
4347-import "playlists.js" as Playlists
4348-import "common"
4349+import "../logic/meta-database.js" as Library
4350+import "../logic/playlists.js" as Playlists
4351+import "../components"
4352+import "../components/Flickables"
4353
4354 MusicPage {
4355- id: mainpage
4356+ id: recentPage
4357+ objectName: "recentPage"
4358 title: i18n.tr("Recent")
4359
4360 property bool changed: false
4361@@ -83,25 +85,18 @@
4362 albumTracksModel.filterPlaylistTracks(model.data)
4363 }
4364
4365- var comp = Qt.createComponent("common/SongsPage.qml")
4366- var songsPage = comp.createObject(mainPageStack,
4367- {
4368- "album": model.type !== "playlist" ? model.data : undefined,
4369- "artist": model.type !== "playlist" ? recentAlbumSongs.get(0, SongsModel.RoleModelData).artist : undefined,
4370- "covers": coverSources,
4371- "isAlbum": (model.type === "album"),
4372- "genre": undefined,
4373- "page": mainpage,
4374- "title": (model.type === "album") ? i18n.tr("Album") : i18n.tr("Playlist"),
4375- "line1": secondaryText,
4376- "line2": primaryText,
4377- });
4378-
4379- if (songsPage == null) { // Error Handling
4380- console.log("Error creating object");
4381- }
4382-
4383- mainPageStack.push(songsPage)
4384+ mainPageStack.push(Qt.resolvedUrl("SongsView.qml"),
4385+ {
4386+ "album": model.type !== "playlist" ? model.data : undefined,
4387+ "artist": model.type !== "playlist" ? recentAlbumSongs.get(0, SongsModel.RoleModelData).artist : undefined,
4388+ "covers": coverSources,
4389+ "isAlbum": (model.type === "album"),
4390+ "genre": undefined,
4391+ "page": recentPage,
4392+ "title": (model.type === "album") ? i18n.tr("Album") : i18n.tr("Playlist"),
4393+ "line1": secondaryText,
4394+ "line2": primaryText,
4395+ })
4396 }
4397 }
4398 }
4399
4400=== renamed file 'MusicTracks.qml' => 'app/ui/Songs.qml'
4401--- MusicTracks.qml 2015-01-26 03:20:37 +0000
4402+++ app/ui/Songs.qml 2015-02-10 03:13:44 +0000
4403@@ -23,99 +23,41 @@
4404 import Ubuntu.Thumbnailer 0.1
4405 import QtMultimedia 5.0
4406 import QtQuick.LocalStorage 2.0
4407-import "playlists.js" as Playlists
4408-import "common"
4409-import "common/ListItemActions"
4410+import "../logic/playlists.js" as Playlists
4411+import "../components"
4412+import "../components/Flickables"
4413+import "../components/HeadState"
4414+import "../components/ListItemActions"
4415
4416
4417 MusicPage {
4418- id: tracksPage
4419- objectName: "tracksPage"
4420+ id: songsPage
4421+ objectName: "songsPage"
4422 title: i18n.tr("Songs")
4423 searchable: true
4424 searchResultsCount: songsModelFilter.count
4425 state: "default"
4426 states: [
4427- PageHeadState {
4428- name: "default"
4429- head: tracksPage.head
4430- actions: Action {
4431- iconName: "search"
4432- onTriggered: tracksPage.state = "search"
4433- }
4434+ SearchableHeadState {
4435+ thisPage: songsPage
4436+ searchEnabled: songsModelFilter.count > 0
4437 },
4438- PageHeadState {
4439- id: selectionState
4440- name: "selection"
4441- backAction: Action {
4442- text: i18n.tr("Cancel selection")
4443- iconName: "back"
4444- onTriggered: {
4445- tracklist.clearSelection()
4446- tracklist.state = "normal"
4447- }
4448- }
4449- head: tracksPage.head
4450- actions: [
4451- Action {
4452- iconName: "select"
4453- text: i18n.tr("Select All")
4454- onTriggered: {
4455- if (tracklist.selectedItems.length === tracklist.model.count) {
4456- tracklist.clearSelection()
4457- } else {
4458- tracklist.selectAll()
4459- }
4460- }
4461- },
4462- Action {
4463- enabled: tracklist.selectedItems.length !== 0
4464- iconName: "add-to-playlist"
4465- text: i18n.tr("Add to playlist")
4466- onTriggered: {
4467- var items = []
4468-
4469- for (var i=0; i < tracklist.selectedItems.length; i++) {
4470- items.push(makeDict(tracklist.model.get(tracklist.selectedItems[i], tracklist.model.RoleModelData)));
4471- }
4472-
4473- var comp = Qt.createComponent("MusicaddtoPlaylist.qml")
4474- var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": items});
4475-
4476- if (addToPlaylist == null) { // Error Handling
4477- console.log("Error creating object");
4478- }
4479-
4480- mainPageStack.push(addToPlaylist)
4481-
4482- tracklist.closeSelection()
4483- }
4484- },
4485- Action {
4486- enabled: tracklist.selectedItems.length > 0
4487- iconName: "add"
4488- text: i18n.tr("Add to queue")
4489- onTriggered: {
4490- var items = []
4491-
4492- for (var i=0; i < tracklist.selectedItems.length; i++) {
4493- items.push(tracklist.model.get(tracklist.selectedItems[i], tracklist.model.RoleModelData));
4494- }
4495-
4496- trackQueue.appendList(items)
4497-
4498- tracklist.closeSelection()
4499- }
4500- }
4501- ]
4502+ MultiSelectHeadState {
4503+ listview: tracklist
4504+ thisPage: songsPage
4505 },
4506 SearchHeadState {
4507 id: searchHeader
4508- thisPage: tracksPage
4509+ thisPage: songsPage
4510 }
4511 ]
4512
4513- ListView {
4514+ // Hack for autopilot otherwise Albums appears as MusicPage
4515+ // due to bug 1341671 it is required that there is a property so that
4516+ // qml doesn't optimise using the parent type
4517+ property bool bug1341671workaround: true
4518+
4519+ MultiSelectListView {
4520 id: tracklist
4521 anchors {
4522 bottomMargin: units.gu(2)
4523@@ -139,49 +81,12 @@
4524 filterCaseSensitivity: Qt.CaseInsensitive
4525 }
4526
4527- Component.onCompleted: {
4528- // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
4529- // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
4530- var scaleFactor = units.gridUnit / 8;
4531- maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
4532- flickDeceleration = flickDeceleration * scaleFactor;
4533- }
4534-
4535- // Requirements for ListItemWithActions
4536- property var selectedItems: []
4537-
4538- signal clearSelection()
4539- signal closeSelection()
4540- signal selectAll()
4541-
4542- onClearSelection: selectedItems = []
4543- onCloseSelection: {
4544- clearSelection()
4545- state = "normal"
4546- }
4547 onStateChanged: {
4548 if (state === "multiselectable") {
4549- tracksPage.state = "selection"
4550+ songsPage.state = "selection"
4551 } else {
4552 searchHeader.query = "" // force query back to default
4553- tracksPage.state = "default"
4554- }
4555- }
4556-
4557- onSelectAll: {
4558- var tmp = selectedItems
4559-
4560- for (var i=0; i < model.count; i++) {
4561- if (tmp.indexOf(i) === -1) {
4562- tmp.push(i)
4563- }
4564- }
4565-
4566- selectedItems = tmp
4567- }
4568- onVisibleChanged: {
4569- if (!visible) {
4570- closeSelection()
4571+ songsPage.state = "default"
4572 }
4573 }
4574
4575@@ -204,7 +109,7 @@
4576 ]
4577
4578 onItemClicked: {
4579- if (tracksPage.state === "search") { // only play single track when searching
4580+ if (songsPage.state === "search") { // only play single track when searching
4581 trackQueue.clear()
4582 trackQueue.append(songsModelFilter.get(index))
4583 trackQueueClick(0)
4584
4585=== renamed file 'common/SongsPage.qml' => 'app/ui/SongsView.qml'
4586--- common/SongsPage.qml 2015-01-30 01:23:39 +0000
4587+++ app/ui/SongsView.qml 2015-02-10 03:13:44 +0000
4588@@ -24,9 +24,13 @@
4589 import Ubuntu.MediaScanner 0.1
4590 import Ubuntu.Thumbnailer 0.1
4591 import QtQuick.LocalStorage 2.0
4592-import "../meta-database.js" as Library
4593-import "../playlists.js" as Playlists
4594-import "ListItemActions"
4595+import "../logic/meta-database.js" as Library
4596+import "../logic/playlists.js" as Playlists
4597+import "../components"
4598+import "../components/Flickables"
4599+import "../components/HeadState"
4600+import "../components/ListItemActions"
4601+import "../components/ViewButton"
4602
4603 MusicPage {
4604 id: songStackPage
4605@@ -121,7 +125,7 @@
4606 objectName: "editPlaylist"
4607 iconName: "edit"
4608 onTriggered: {
4609- var dialog = PopupUtils.open(editPlaylistDialog, mainView)
4610+ var dialog = PopupUtils.open(Qt.resolvedUrl("../components/Dialog/EditPlaylistDialog.qml"), mainView)
4611 dialog.oldPlaylistName = line2
4612 }
4613 },
4614@@ -129,7 +133,7 @@
4615 objectName: "deletePlaylist"
4616 iconName: "delete"
4617 onTriggered: {
4618- var dialog = PopupUtils.open(removePlaylistDialog, mainView)
4619+ var dialog = PopupUtils.open(Qt.resolvedUrl("../components/Dialog/RemovePlaylistDialog.qml"), mainView)
4620 dialog.oldPlaylistName = line2
4621 }
4622 }
4623@@ -140,100 +144,29 @@
4624 actions: playlistState.actions
4625 }
4626 },
4627- PageHeadState {
4628- id: selectionState
4629- name: "selection"
4630- backAction: Action {
4631- text: i18n.tr("Cancel selection")
4632- iconName: "back"
4633- onTriggered: {
4634- albumtrackslist.clearSelection()
4635- albumtrackslist.state = "normal"
4636- }
4637- }
4638- actions: [
4639- Action {
4640- iconName: "select"
4641- text: i18n.tr("Select All")
4642- onTriggered: {
4643- if (albumtrackslist.selectedItems.length === albumtrackslist.model.count) {
4644- albumtrackslist.clearSelection()
4645- } else {
4646- albumtrackslist.selectAll()
4647- }
4648- }
4649- },
4650- Action {
4651- enabled: albumtrackslist.selectedItems.length > 0
4652- iconName: "add-to-playlist"
4653- text: i18n.tr("Add to playlist")
4654- onTriggered: {
4655- var items = []
4656-
4657- for (var i=0; i < albumtrackslist.selectedItems.length; i++) {
4658- items.push(makeDict(albumtrackslist.model.get(albumtrackslist.selectedItems[i], albumtrackslist.model.RoleModelData)));
4659- }
4660-
4661- var comp = Qt.createComponent("../MusicaddtoPlaylist.qml")
4662- var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": items, "page": songStackPage});
4663-
4664- if (addToPlaylist == null) { // Error Handling
4665- console.log("Error creating object");
4666- }
4667-
4668- mainPageStack.push(addToPlaylist)
4669-
4670- albumtrackslist.closeSelection()
4671- }
4672- },
4673- Action {
4674- enabled: albumtrackslist.selectedItems.length > 0
4675- iconName: "add"
4676- text: i18n.tr("Add to queue")
4677- onTriggered: {
4678- var items = []
4679-
4680- for (var i=0; i < albumtrackslist.selectedItems.length; i++) {
4681- items.push(albumtrackslist.model.get(albumtrackslist.selectedItems[i], albumtrackslist.model.RoleModelData));
4682- }
4683-
4684- trackQueue.appendList(items)
4685-
4686- albumtrackslist.closeSelection()
4687- }
4688- },
4689- Action {
4690- enabled: albumtrackslist.selectedItems.length > 0
4691- iconName: "delete"
4692- text: i18n.tr("Delete")
4693- visible: songStackPage.line1 === i18n.tr("Playlist")
4694- onTriggered: {
4695- for (var i=0; i < albumtrackslist.selectedItems.length; i++) {
4696- Playlists.removeFromPlaylist(songStackPage.line2, albumtrackslist.selectedItems[i])
4697-
4698- // Update indexes as an index has been removed
4699- for (var j=i + 1; j < albumtrackslist.selectedItems.length; j++) {
4700- if (albumtrackslist.selectedItems[j] > albumtrackslist.selectedItems[i]) {
4701- albumtrackslist.selectedItems[j]--;
4702- }
4703- }
4704- }
4705-
4706- albumtrackslist.closeSelection()
4707-
4708- playlistChangedHelper() // update recent/playlist models
4709-
4710- albumTracksModel.filterPlaylistTracks(songStackPage.line2)
4711-
4712- // refresh cover art
4713- songStackPage.covers = Playlists.getPlaylistCovers(songStackPage.line2)
4714- }
4715- }
4716- ]
4717- PropertyChanges {
4718- target: songStackPage.head
4719- backAction: selectionState.backAction
4720- actions: selectionState.actions
4721+ MultiSelectHeadState {
4722+ listview: albumtrackslist
4723+ removable: songStackPage.line1 === i18n.tr("Playlist")
4724+ thisPage: songStackPage
4725+
4726+ onRemoved: {
4727+ for (var i=0; i < selectedItems.length; i++) {
4728+ Playlists.removeFromPlaylist(songStackPage.line2, selectedItems[i])
4729+
4730+ // Update indexes as an index has been removed
4731+ for (var j=i + 1; j < selectedItems.length; j++) {
4732+ if (selectedItems[j] > selectedItems[i]) {
4733+ selectedItems[j]--;
4734+ }
4735+ }
4736+ }
4737+
4738+ playlistChangedHelper() // update recent/playlist models
4739+
4740+ albumTracksModel.filterPlaylistTracks(songStackPage.line2)
4741+
4742+ // refresh cover art
4743+ songStackPage.covers = Playlists.getPlaylistCovers(songStackPage.line2)
4744 }
4745 }
4746 ]
4747@@ -243,12 +176,12 @@
4748 store: musicStore
4749 onStatusChanged: {
4750 if (songsModel.status === SongsModel.Ready && loaded && songsModel.count === 0) {
4751- musicToolbar.popPage(songStackPage)
4752+ mainPageStack.popPage(songStackPage)
4753 }
4754 }
4755 }
4756
4757- ListView {
4758+ MultiSelectListView {
4759 id: albumtrackslist
4760 anchors {
4761 fill: parent
4762@@ -258,67 +191,12 @@
4763 objectName: "songspage-listview"
4764 width: parent.width
4765
4766- // Requirements for ListItemWithActions
4767- property var selectedItems: []
4768-
4769- signal clearSelection()
4770- signal closeSelection()
4771- signal selectAll()
4772-
4773- onClearSelection: selectedItems = []
4774- onCloseSelection: {
4775- clearSelection()
4776- state = "normal"
4777- }
4778- onSelectAll: {
4779- var tmp = selectedItems
4780-
4781- for (var i=0; i < model.count; i++) {
4782- if (tmp.indexOf(i) === -1) {
4783- tmp.push(i)
4784- }
4785- }
4786-
4787- selectedItems = tmp
4788- }
4789- onVisibleChanged: {
4790- if (!visible) {
4791- closeSelection()
4792- }
4793- }
4794-
4795- Component.onCompleted: {
4796- // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
4797- // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
4798- var scaleFactor = units.gridUnit / 8;
4799- maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
4800- flickDeceleration = flickDeceleration * scaleFactor;
4801- }
4802-
4803 header: BlurredHeader {
4804 rightColumn: Column {
4805 spacing: units.gu(2)
4806- Button {
4807- id: shuffleRow
4808- height: units.gu(4)
4809- strokeColor: UbuntuColors.green
4810- width: units.gu(15)
4811- Text {
4812- anchors {
4813- centerIn: parent
4814- }
4815- color: "white"
4816- elide: Text.ElideRight
4817- height: parent.height
4818- horizontalAlignment: Text.AlignHCenter
4819- // TRANSLATORS: this appears in a button with limited space (around 14 characters)
4820- text: i18n.tr("Shuffle")
4821- verticalAlignment: Text.AlignVCenter
4822- width: parent.width - units.gu(2)
4823- }
4824+ ShuffleButton {
4825+ model: albumtrackslist.model
4826 onClicked: {
4827- shuffleModel(albumtrackslist.model) // play track
4828-
4829 if (isAlbum && songStackPage.line1 !== i18n.tr("Genre")) {
4830 Library.addRecent(albumtrackslist.model.get(0, albumtrackslist.model.RoleModelData).album, "album")
4831 } else if (songStackPage.line1 === i18n.tr("Playlist")) {
4832@@ -330,36 +208,12 @@
4833 recentModel.filterRecent()
4834 }
4835 }
4836- Button {
4837- id: queueAllRow
4838- height: units.gu(4)
4839- strokeColor: UbuntuColors.green
4840- width: units.gu(15)
4841- Text {
4842- anchors {
4843- centerIn: parent
4844- }
4845- color: "white"
4846- elide: Text.ElideRight
4847- height: parent.height
4848- horizontalAlignment: Text.AlignHCenter
4849- // TRANSLATORS: this appears in a button with limited space (around 14 characters)
4850- text: i18n.tr("Queue all")
4851- verticalAlignment: Text.AlignVCenter
4852- width: parent.width - units.gu(2)
4853- }
4854- onClicked: addQueueFromModel(albumtrackslist.model)
4855+ QueueAllButton {
4856+ model: albumtrackslist.model
4857 }
4858- Button {
4859- id: playRow
4860- color: UbuntuColors.green
4861- height: units.gu(4)
4862- // TRANSLATORS: this appears in a button with limited space (around 14 characters)
4863- text: i18n.tr("Play all")
4864- width: units.gu(15)
4865+ PlayAllButton {
4866+ model: albumtrackslist.model
4867 onClicked: {
4868- trackClicked(albumtrackslist.model, 0) // play track
4869-
4870 if (isAlbum && songStackPage.line1 !== i18n.tr("Genre")) {
4871 Library.addRecent(albumtrackslist.model.get(0, albumtrackslist.model.RoleModelData).album, "album")
4872 } else if (songStackPage.line1 === i18n.tr("Playlist")) {
4873@@ -528,101 +382,4 @@
4874 }
4875
4876 Component.onCompleted: loaded = true
4877-
4878- // Edit name of playlist dialog
4879- Component {
4880- id: editPlaylistDialog
4881- Dialog {
4882- id: dialogEditPlaylist
4883- // TRANSLATORS: this is a title of a dialog with a prompt to rename a playlist
4884- title: i18n.tr("Rename playlist")
4885-
4886- property string oldPlaylistName: ""
4887-
4888- TextField {
4889- id: playlistName
4890- inputMethodHints: Qt.ImhNoPredictiveText
4891- placeholderText: i18n.tr("Enter playlist name")
4892- }
4893- Label {
4894- id: editplaylistoutput
4895- color: "red"
4896- visible: false
4897- }
4898-
4899- Button {
4900- text: i18n.tr("Change")
4901- color: styleMusic.dialog.confirmButtonColor
4902- onClicked: {
4903- editplaylistoutput.visible = true
4904-
4905- if (playlistName.text.length > 0) { // make sure something is acually inputed
4906- console.debug("Debug: User changed name from "+oldPlaylistName+" to "+playlistName.text)
4907-
4908- if (Playlists.renamePlaylist(oldPlaylistName, playlistName.text) === true) {
4909-
4910- if (Library.recentContainsPlaylist(oldPlaylistName)) {
4911- Library.recentRenamePlaylist(oldPlaylistName, playlistName.text)
4912- }
4913-
4914- line2 = playlistName.text
4915-
4916- playlistChangedHelper() // update recent/playlist models
4917-
4918- PopupUtils.close(dialogEditPlaylist)
4919- }
4920- else {
4921- editplaylistoutput.text = i18n.tr("Playlist already exists")
4922- }
4923- }
4924- else {
4925- editplaylistoutput.text = i18n.tr("Please type in a name.")
4926- }
4927- }
4928- }
4929- Button {
4930- text: i18n.tr("Cancel")
4931- color: styleMusic.dialog.cancelButtonColor
4932- onClicked: PopupUtils.close(dialogEditPlaylist)
4933- }
4934- }
4935- }
4936-
4937- // Remove playlist dialog
4938- Component {
4939- id: removePlaylistDialog
4940- Dialog {
4941- id: dialogRemovePlaylist
4942- // TRANSLATORS: this is a title of a dialog with a prompt to delete a playlist
4943- title: i18n.tr("Permanently delete playlist?")
4944- text: "("+i18n.tr("This cannot be undone")+")"
4945-
4946- property string oldPlaylistName
4947-
4948- Button {
4949- text: i18n.tr("Remove")
4950- color: styleMusic.dialog.confirmRemoveButtonColor
4951- onClicked: {
4952- // removing playlist
4953- Playlists.removePlaylist(dialogRemovePlaylist.oldPlaylistName)
4954-
4955- if (Library.recentContainsPlaylist(dialogRemovePlaylist.oldPlaylistName)) {
4956- Library.recentRemovePlaylist(dialogRemovePlaylist.oldPlaylistName)
4957- }
4958-
4959- playlistChangedHelper(true) // update recent/playlist models
4960-
4961- songStackPage.page = undefined
4962- PopupUtils.close(dialogRemovePlaylist)
4963-
4964- musicToolbar.goBack()
4965- }
4966- }
4967- Button {
4968- text: i18n.tr("Cancel")
4969- color: styleMusic.dialog.cancelButtonColor
4970- onClicked: PopupUtils.close(dialogRemovePlaylist)
4971- }
4972- }
4973- }
4974 }
4975
4976=== renamed file 'click/apparmor.json' => 'apparmor.json'
4977=== removed directory 'click'
4978=== removed file 'click/CMakeLists.txt'
4979--- click/CMakeLists.txt 2014-07-01 23:13:50 +0000
4980+++ click/CMakeLists.txt 1970-01-01 00:00:00 +0000
4981@@ -1,15 +0,0 @@
4982-if(CLICK_MODE)
4983- if(NOT BZR_REVNO)
4984- set(BZR_REVNO "latest")
4985- endif(NOT BZR_REVNO)
4986- configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json)
4987- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json apparmor.json
4988- music-app-content.json DESTINATION ${CMAKE_INSTALL_PREFIX})
4989-endif(CLICK_MODE)
4990-
4991-# make the click files visible on qtcreator
4992-file(GLOB CLICK_FILES
4993- RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
4994- *.json *.json.in)
4995-
4996-add_custom_target(com_ubuntu_music_CLICKFiles ALL SOURCES ${CLICK_FILES})
4997
4998=== renamed file 'click/manifest.json.in' => 'manifest.json.in'
4999=== renamed file 'click/music-app-content.json' => 'music-app-content.json'
5000=== modified file 'tests/autopilot/music_app/__init__.py'
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches