Merge lp:~ahayzen/music-app/add-sdk-search-support-002 into lp:music-app/remix

Proposed by Andrew Hayzen
Status: Merged
Approved by: Victor Thompson
Approved revision: 787
Merged at revision: 775
Proposed branch: lp:~ahayzen/music-app/add-sdk-search-support-002
Merge into: lp:music-app/remix
Diff against target: 1253 lines (+450/-320)
15 files modified
MusicAlbums.qml (+21/-1)
MusicArtists.qml (+22/-2)
MusicGenres.qml (+21/-3)
MusicPlaylists.qml (+38/-15)
MusicToolbar.qml (+2/-22)
MusicTracks.qml (+39/-10)
MusicaddtoPlaylist.qml (+43/-20)
common/ColumnFlow.qml (+122/-34)
common/ListItemActions/AddToPlaylist.qml (+5/-5)
common/MusicPage.qml (+27/-1)
common/SearchHeadState.qml (+71/-0)
common/SongsPage.qml (+2/-2)
images/search.svg (+0/-153)
music-app.qml (+18/-21)
tests/autopilot/music_app/__init__.py (+19/-31)
To merge this branch: bzr merge lp:~ahayzen/music-app/add-sdk-search-support-002
Reviewer Review Type Date Requested Status
Victor Thompson Approve
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Andrew Hayzen Abstain
Review via email: mp+245787@code.launchpad.net

Commit message

* Refactor ColumnFlow so that it uses insert/removes instead of resetting the view
* Add search support to MusicAlbums.qml MusicArtists.qml MusicGenres.qml MusicPlaylists.qml MusicTracks.qml MusicaddtoPlaylist.qml
* Remove any references to sheets as they have been removed

Description of the change

* Refactor ColumnFlow so that it uses insert/removes instead of resetting the view
* Add search support to MusicAlbums.qml MusicArtists.qml MusicGenres.qml MusicPlaylists.qml MusicTracks.qml MusicaddtoPlaylist.qml
* Remove any references to sheets as they have been removed

Please test the ColumnFlow adding/removing (both by searching and adding/removing media) and bug 1341814.

I'm not sure whether searching is required/useful on the AlbumsPage.qml as you are likely to have < 10 albums per artist/

To post a comment you must log in.
769. By Andrew Hayzen

* Tidy comments
* Strip anything todo with sheets as they have been removed

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

I wonder if there is a way of making search + multiselect work on MusicTracks, also when you search on MusicTracks and click a track what should it add to the queue and play? The whole of the model? The filtered search model? Just the track?

review: Needs Information
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
770. By Andrew Hayzen

* Force the focus onto the search field when search activated
* Attempt at ap fixes

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
771. By Andrew Hayzen

* Fix issue of not page specific header actions when there is search
* Create generic search state
* Allow multiselect and search in trackspage
* Revert ap changes

772. By Andrew Hayzen

* Fix going back from search

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

So I've fixed multiselect + searching so the remaining question is.

When you search on MusicTracks and click a track what should it add to the queue and play? The whole of the model? The filtered search model? Just the track?

I'm leaning towards just playing that single track.

review: Needs Information
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Spotted an issue where after navigating to a page with more than 1 action it causes the search bar to not fill the whole header, reported bug 1408481, however I don't feel this is a blocker as it is minor.

Revision history for this message
Victor Thompson (vthompson) wrote :

I agree, I don't think we should block on lp:1408481.

One behavioral thing I've noticed is that I think that if the user does the following:
1) clicks search (say from the Artists tab)
2) selects an artist
3) taps the back button to return to Artists
When the user returns to the Artists tab the search bar is still shown and the keyboard is activated. I think the search action should be cancelled when the user leaves the view. Same thing happens when you search for a song and play it. When you return from the Now Playing page the search function is active. I think it should move from the "search" state to the "default" state.

Why have you left out Genres from search? Maybe Playlists would be nice as well--but I see some difficulty there (but it might be nice to search for the playlist you want to add a track to via MusicaddtoPlaylist).

review: Needs Fixing
Revision history for this message
Victor Thompson (vthompson) wrote :

Also, as discussed, it seems like adding to a playlist has broken in this MP. Via multi-select or via swipe to the left. When the user taps the action the "Select playlist" page is empty--maybe filtered?

773. By Andrew Hayzen

* Add MusicGenres.qml MusicPlaylists.qml to searching
* Change behaviour so that when 'going back' you always go back to the "default" state
* Fix for ColumnFlow issues

774. By Andrew Hayzen

* Only play single track when searching

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

I think there are still issues with adding to a playlist.

1) Navigate to the Songs tab and swipe a track to the left
2) Click the "Add to playlist" action
3) Tap the back button
4) Swipe a track to the left and click the "Add to playlist" button

Step 4 shows no available playlists to choose from.

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
775. By Andrew Hayzen

* Fix for addToPlaylists not working on second viewing
* Fix for queueIndex becoming out of sync with clear; append; trackQueueClick(0);

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
776. By Andrew Hayzen

* Fixes for autopilot

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
777. By Andrew Hayzen

* Ensure that searching on MusicGenres.qml is case insensitive

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

From my point of view this is now ready :)

review: Abstain
Revision history for this message
Victor Thompson (vthompson) wrote :

A few functional things:

1. I think we should re-enable the ability to do a search via the "Ctrl+F" action on the desktop
2. Perhaps we don't want to overthink which characters we want to escape in the regex, but currently the user can't search for any of the following special characters: .^$*+?()[{\| It might be useful to search for some of these including ? and +.
3. I think the search action should be hidden in the Playlists tab when the user has no playlists.
4. It would be useful to have an empty state that says something like "No items found" so the user doesn't think the action is still underway.
5. It seems like it'd make sense to also add search to MusicaddtoPlaylist as well so the user can quickly find the playlist he/she wants to add a track to.

Ok, now I'll start reviewing the code.

review: Needs Fixing
Revision history for this message
Victor Thompson (vthompson) wrote :

I've added 9 inline comments. Most are pretty trivial. I still want to make another attempt at reviewing the ColumnFlow changes in greater detail.

review: Needs Fixing
778. By Andrew Hayzen

* Add searching to MusicaddtoPlaylist.qml
* Disable searching if there are no playlists
* Various fixes and tidy ups
* Add copyright 2015

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

1) TODO - should this search the current page, or jump to MusicTracks etc?
2) TODO - What do you propose to use as the regexp?
3) I've disabled the button if there are no playlists
4) TODO - Figuring out the best way todo this...
5) I've added support to addToPlaylists

I've removed the placeholder for now, as i'm not sure why that wasn't working.

From the inline comments
'Would this fix alone be enough to workaround the SDK bug that's been fixed (lp:1341814)? If so, we could remove the "SDK hack"'

No that fix was to make the header state go back to default after popping from the pagestack, not fixing the header/contents becoming out of sync. Also note that the 'hack' that is used for the SDK bug is the same code that was actually put into the SDK just from our application.

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
779. By Andrew Hayzen

* Fix for objectName typo causing autopilot failure

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
780. By Andrew Hayzen

* Add empty state label for search
* Add Ctrl+F support to search

Revision history for this message
Andrew Hayzen (ahayzen) wrote :

1) DONE
4) DONE

Just point 2) left.

781. By Andrew Hayzen

* Force the header to show on Ctrl+F

Revision history for this message
Victor Thompson (vthompson) wrote :

Point 2 isn't that big of a deal. Maybe it's worth detecting a question mark and escaping it--but at that point it might be worth detecting all special characters. I don't want to hold this up just for that.

782. By Andrew Hayzen

* Esc now jumps you out of search mode

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

A few things I'd like to figure out or do prior to landing:

1. We should make the input text color black (supposed issue with our dark theme).
2. We really should figure out what's causing the placeholderText issue (supposed issue with our dark theme).
3. I think we need more space on the rightMargin of the searchField.

I have fixes for all 3 in the attached diff, however, for the first 2 we should find the bug that is causing this. I assume one already exists. Then we can add a FIXME or TODO for the workarounds.

Please implement something like the following:

=== modified file 'common/SearchHeadState.qml'
--- common/SearchHeadState.qml 2015-01-10 19:40:52 +0000
+++ common/SearchHeadState.qml 2015-01-10 20:43:18 +0000
@@ -37,10 +37,12 @@
         anchors {
             left: parent ? parent.left : undefined
             right: parent ? parent.right : undefined
- rightMargin: units.gu(1)
+ rightMargin: units.gu(5)
         }
+ color: styleMusic.common.black
         hasClearButton: true
         inputMethodHints: Qt.ImhNoPredictiveText
+ placeholderText: i18n.tr("Search music")

         onParentChanged: {
             if (parent === null) {
@@ -67,6 +69,8 @@
         }
     }

+ // TODO: Workaround for pad.lv/#####, force the theme's backgroundText color
+ // to work with the app's backgroundColor
+ Component.onCompleted: Theme.palette.normal.backgroundText = "#81888888"
+
     property Page thisPage
     property alias query: searchField.text
 }

review: Needs Fixing
783. By Andrew Hayzen

* Fixes for TextField text colours and margins
* Fix for backgroundText color
* Fix for ColumnFlow.qml not loading if the model changes and then nothing extra is inserted
* Fix to ensure the playlistModel can load when on the addToPlaylist page

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
784. By Andrew Hayzen

* Fix for blank MusicaddtoPlaylist.qml due to delegate it was generated from being destroyed when the search is cleared as the page is made invisible

785. By Andrew Hayzen

* Extra protection to filtering the playlistModel

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
786. By Andrew Hayzen

* Disable SortFilterModel sorting as it is sometimes incorrect for playlistModel and SQL sorts the data correctly anyway

787. By Andrew Hayzen

* Update copyright on missed touched files

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Approved! This looks great. Thanks for reintroducing this feature! I'm sure many people will love to see this land--I know I will.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MusicAlbums.qml'
2--- MusicAlbums.qml 2014-11-26 02:33:29 +0000
3+++ MusicAlbums.qml 2015-01-11 16:25:30 +0000
4@@ -1,5 +1,5 @@
5 /*
6- * Copyright (C) 2013, 2014
7+ * Copyright (C) 2013, 2014, 2015
8 * Andrew Hayzen <ahayzen@gmail.com>
9 * Daniel Holm <d.holmen@gmail.com>
10 * Victor Thompson <victor.thompson@gmail.com>
11@@ -27,6 +27,23 @@
12 id: albumsPage
13 objectName: "albumsPage"
14 title: i18n.tr("Albums")
15+ searchable: true
16+ searchResultsCount: albumsModelFilter.count
17+ state: "default"
18+ states: [
19+ PageHeadState {
20+ name: "default"
21+ head: albumsPage.head
22+ actions: Action {
23+ iconName: "search"
24+ onTriggered: albumsPage.state = "search"
25+ }
26+ },
27+ SearchHeadState {
28+ id: searchHeader
29+ thisPage: albumsPage
30+ }
31+ ]
32
33 CardView {
34 id: albumCardView
35@@ -40,6 +57,9 @@
36 sort.property: "title"
37 sort.order: Qt.AscendingOrder
38 sortCaseSensitivity: Qt.CaseInsensitive
39+ filter.property: "title"
40+ filter.pattern: new RegExp(searchHeader.query, "i")
41+ filterCaseSensitivity: Qt.CaseInsensitive
42 }
43 delegate: Card {
44 id: albumCard
45
46=== modified file 'MusicArtists.qml'
47--- MusicArtists.qml 2014-11-23 19:36:28 +0000
48+++ MusicArtists.qml 2015-01-11 16:25:30 +0000
49@@ -1,5 +1,5 @@
50 /*
51- * Copyright (C) 2013, 2014
52+ * Copyright (C) 2013, 2014, 2015
53 * Andrew Hayzen <ahayzen@gmail.com>
54 * Daniel Holm <d.holmen@gmail.com>
55 * Victor Thompson <victor.thompson@gmail.com>
56@@ -30,9 +30,26 @@
57
58
59 MusicPage {
60- id: mainpage
61+ id: artistsPage
62 objectName: "artistsPage"
63 title: i18n.tr("Artists")
64+ searchable: true
65+ searchResultsCount: artistsModelFilter.count
66+ state: "default"
67+ states: [
68+ PageHeadState {
69+ name: "default"
70+ head: artistsPage.head
71+ actions: Action {
72+ iconName: "search"
73+ onTriggered: artistsPage.state = "search"
74+ }
75+ },
76+ SearchHeadState {
77+ id: searchHeader
78+ thisPage: artistsPage
79+ }
80+ ]
81
82 CardView {
83 id: artistCardView
84@@ -48,6 +65,9 @@
85 sort.property: "artist"
86 sort.order: Qt.AscendingOrder
87 sortCaseSensitivity: Qt.CaseInsensitive
88+ filter.property: "artist"
89+ filter.pattern: new RegExp(searchHeader.query, "i")
90+ filterCaseSensitivity: Qt.CaseInsensitive
91 }
92 delegate: Card {
93 id: artistCard
94
95=== modified file 'MusicGenres.qml'
96--- MusicGenres.qml 2014-11-23 19:44:39 +0000
97+++ MusicGenres.qml 2015-01-11 16:25:30 +0000
98@@ -1,5 +1,5 @@
99 /*
100- * Copyright (C) 2013, 2014
101+ * Copyright (C) 2013, 2014, 2015
102 * Andrew Hayzen <ahayzen@gmail.com>
103 * Daniel Holm <d.holmen@gmail.com>
104 * Victor Thompson <victor.thompson@gmail.com>
105@@ -24,9 +24,26 @@
106
107
108 MusicPage {
109- id: mainpage
110+ id: genresPage
111 objectName: "genresPage"
112 title: i18n.tr("Genres")
113+ searchable: true
114+ searchResultsCount: genresModelFilter.count
115+ state: "default"
116+ states: [
117+ PageHeadState {
118+ name: "default"
119+ head: genresPage.head
120+ actions: Action {
121+ iconName: "search"
122+ onTriggered: genresPage.state = "search"
123+ }
124+ },
125+ SearchHeadState {
126+ id: searchHeader
127+ thisPage: genresPage
128+ }
129+ ]
130
131 CardView {
132 id: genreCardView
133@@ -38,7 +55,8 @@
134 store: musicStore
135 }
136 filter.property: "genre"
137- filter.pattern: /\S+/
138+ filter.pattern: searchHeader.query === "" ? /\S+/ : new RegExp(searchHeader.query, "i")
139+ filterCaseSensitivity: Qt.CaseInsensitive
140 sort.property: "genre"
141 sort.order: Qt.AscendingOrder
142 sortCaseSensitivity: Qt.CaseInsensitive
143
144=== modified file 'MusicPlaylists.qml'
145--- MusicPlaylists.qml 2014-11-10 22:20:24 +0000
146+++ MusicPlaylists.qml 2015-01-11 16:25:30 +0000
147@@ -1,5 +1,5 @@
148 /*
149- * Copyright (C) 2013, 2014
150+ * Copyright (C) 2013, 2014, 2015
151 * Andrew Hayzen <ahayzen@gmail.com>
152 * Daniel Holm <d.holmen@gmail.com>
153 * Victor Thompson <victor.thompson@gmail.com>
154@@ -33,6 +33,34 @@
155 // TRANSLATORS: this is the name of the playlists page shown in the tab header.
156 // Remember to keep the translation short to fit the screen width
157 title: i18n.tr("Playlists")
158+ searchable: true
159+ searchResultsCount: playlistModelFilter.count
160+ state: "default"
161+ states: [
162+ PageHeadState {
163+ name: "default"
164+ head: playlistsPage.head
165+ actions: [
166+ Action {
167+ objectName: "newPlaylistButton"
168+ iconName: "add"
169+ onTriggered: {
170+ customdebug("New playlist.")
171+ PopupUtils.open(newPlaylistDialog, mainView)
172+ }
173+ },
174+ Action {
175+ enabled: playlistModel.model.count > 0
176+ iconName: "search"
177+ onTriggered: playlistsPage.state = "search"
178+ }
179+ ]
180+ },
181+ SearchHeadState {
182+ id: searchHeader
183+ thisPage: playlistsPage
184+ }
185+ ]
186
187 property bool changed: false
188
189@@ -49,22 +77,17 @@
190 onTriggered: playlistModel.filterPlaylists()
191 }
192
193- head {
194- actions: [
195- Action {
196- objectName: "newplaylistButton"
197- iconName: "add"
198- onTriggered: {
199- customdebug("New playlist.")
200- PopupUtils.open(newPlaylistDialog, mainView)
201- }
202- }
203- ]
204- }
205-
206 CardView {
207 id: playlistslist
208- model: playlistModel.model
209+ model: SortFilterModel {
210+ // Sorting disabled as it is incorrect on first run (due to workers?)
211+ // and SQL sorts the data correctly
212+ id: playlistModelFilter
213+ model: playlistModel.model
214+ filter.property: "name"
215+ filter.pattern: new RegExp(searchHeader.query, "i")
216+ filterCaseSensitivity: Qt.CaseInsensitive
217+ }
218 objectName: "playlistsCardView"
219 delegate: Card {
220 id: playlistCard
221
222=== modified file 'MusicToolbar.qml'
223--- MusicToolbar.qml 2014-12-12 02:34:50 +0000
224+++ MusicToolbar.qml 2015-01-11 16:25:30 +0000
225@@ -1,5 +1,5 @@
226 /*
227- * Copyright (C) 2013, 2014
228+ * Copyright (C) 2013, 2014, 2015
229 * Andrew Hayzen <ahayzen@gmail.com>
230 * Daniel Holm <d.holmen@gmail.com>
231 * Victor Thompson <victor.thompson@gmail.com>
232@@ -33,7 +33,6 @@
233
234 // Properties storing the current page info
235 property var currentPage: null
236- property var currentSheet: []
237 property var currentTab: null
238
239 // Properties and signals for the toolbar
240@@ -45,36 +44,17 @@
241 // Back button has been pressed, jump up pageStack or back to parent page
242 function goBack()
243 {
244- if (currentSheet.length > 0) {
245- PopupUtils.close(currentSheet[currentSheet.length - 1])
246- return; // don't change toolbar state when going back from sheet
247- }
248- else if (mainPageStack !== null && mainPageStack.depth > 1) {
249+ if (mainPageStack !== null && mainPageStack.depth > 1) {
250 mainPageStack.pop(currentPage)
251 }
252 }
253
254- // Remove sheet as it has been closed
255- function removeSheet(sheet)
256- {
257- var index = currentSheet.lastIndexOf(sheet);
258-
259- if (index > -1) {
260- currentSheet.splice(index, 1);
261- }
262- }
263-
264 // Set the current page, and any parent/stacks
265 function setPage(childPage)
266 {
267 currentPage = childPage;
268 }
269
270- // Set the current sheet (overrides page)
271- function setSheet(sheet) {
272- currentSheet.push(sheet)
273- }
274-
275 Panel {
276 id: musicToolbarPanel
277 anchors {
278
279=== modified file 'MusicTracks.qml'
280--- MusicTracks.qml 2015-01-08 00:44:57 +0000
281+++ MusicTracks.qml 2015-01-11 16:25:30 +0000
282@@ -1,5 +1,5 @@
283 /*
284- * Copyright (C) 2013, 2014
285+ * Copyright (C) 2013, 2014, 2015
286 * Andrew Hayzen <ahayzen@gmail.com>
287 * Daniel Holm <d.holmen@gmail.com>
288 * Victor Thompson <victor.thompson@gmail.com>
289@@ -29,13 +29,22 @@
290
291
292 MusicPage {
293- id: mainpage
294+ id: tracksPage
295 objectName: "tracksPage"
296 title: i18n.tr("Songs")
297-
298- state: tracklist.state === "multiselectable" ? "selection" : "default"
299+ searchable: true
300+ searchResultsCount: songsModelFilter.count
301+ state: "default"
302 states: [
303 PageHeadState {
304+ name: "default"
305+ head: tracksPage.head
306+ actions: Action {
307+ iconName: "search"
308+ onTriggered: tracksPage.state = "search"
309+ }
310+ },
311+ PageHeadState {
312 id: selectionState
313 name: "selection"
314 backAction: Action {
315@@ -46,6 +55,7 @@
316 tracklist.state = "normal"
317 }
318 }
319+ head: tracksPage.head
320 actions: [
321 Action {
322 iconName: "select"
323@@ -94,11 +104,10 @@
324 }
325 }
326 ]
327- PropertyChanges {
328- target: mainpage.head
329- backAction: selectionState.backAction
330- actions: selectionState.actions
331- }
332+ },
333+ SearchHeadState {
334+ id: searchHeader
335+ thisPage: tracksPage
336 }
337 ]
338
339@@ -121,6 +130,9 @@
340 sort.property: "title"
341 sort.order: Qt.AscendingOrder
342 sortCaseSensitivity: Qt.CaseInsensitive
343+ filter.property: "title"
344+ filter.pattern: new RegExp(searchHeader.query, "i")
345+ filterCaseSensitivity: Qt.CaseInsensitive
346 }
347
348 Component.onCompleted: {
349@@ -143,6 +155,15 @@
350 clearSelection()
351 state = "normal"
352 }
353+ onStateChanged: {
354+ if (state === "multiselectable") {
355+ tracksPage.state = "selection"
356+ } else {
357+ searchHeader.query = "" // force query back to default
358+ tracksPage.state = "default"
359+ }
360+ }
361+
362 onSelectAll: {
363 var tmp = selectedItems
364
365@@ -178,7 +199,15 @@
366 }
367 ]
368
369- onItemClicked: trackClicked(tracklist.model, index) // play track
370+ onItemClicked: {
371+ if (tracksPage.state === "search") { // only play single track when searching
372+ trackQueue.clear()
373+ trackQueue.append(songsModelFilter.get(index))
374+ trackQueueClick(0)
375+ } else {
376+ trackClicked(songsModelFilter, index) // play track
377+ }
378+ }
379
380 MusicRow {
381 id: musicRow
382
383=== modified file 'MusicaddtoPlaylist.qml'
384--- MusicaddtoPlaylist.qml 2014-10-29 02:59:35 +0000
385+++ MusicaddtoPlaylist.qml 2015-01-11 16:25:30 +0000
386@@ -1,5 +1,5 @@
387 /*
388- * Copyright (C) 2013, 2014
389+ * Copyright (C) 2013, 2014, 2015
390 * Andrew Hayzen <ahayzen@gmail.com>
391 * Daniel Holm <d.holmen@gmail.com>
392 * Victor Thompson <victor.thompson@gmail.com>
393@@ -37,38 +37,61 @@
394
395 // Page that will be used when adding tracks to playlists
396 MusicPage {
397- id: addtoPlaylist
398+ id: addToPlaylistPage
399 objectName: "addToPlaylistPage"
400 title: i18n.tr("Select playlist")
401- visible: false
402+ searchable: true
403+ searchResultsCount: addToPlaylistModelFilter.count
404+ state: "default"
405+ states: [
406+ PageHeadState {
407+ name: "default"
408+ head: addToPlaylistPage.head
409+ actions: [
410+ Action {
411+ objectName: "newPlaylistButton"
412+ iconName: "add"
413+ onTriggered: {
414+ customdebug("New playlist.")
415+ PopupUtils.open(newPlaylistDialog, mainView)
416+ }
417+ },
418+ Action {
419+ enabled: playlistModel.model.count > 0
420+ iconName: "search"
421+ onTriggered: addToPlaylistPage.state = "search"
422+ }
423+ ]
424+ },
425+ SearchHeadState {
426+ id: searchHeader
427+ thisPage: addToPlaylistPage
428+ }
429+ ]
430
431 property var chosenElements: []
432 property var page
433
434- head {
435- actions: [
436- Action {
437- objectName: "newPlaylistButton"
438- text: i18n.tr("New playlist")
439- iconName: "add"
440- onTriggered: {
441- customdebug("New playlist.")
442- PopupUtils.open(newPlaylistDialog, mainView)
443- }
444- }
445- ]
446- }
447-
448 onVisibleChanged: {
449- if (visible) {
450- tabs.ensurePopulated(playlistTab)
451+ // Load the playlistmodel if it hasn't loaded or is empty
452+ if (visible && (!playlistModel.completed || playlistModel.model.count === 0)) {
453+ playlistModel.canLoad = true // ensure the model canLoad
454+ playlistModel.filterPlaylists()
455 }
456 }
457
458 CardView {
459 id: addtoPlaylistView
460 itemWidth: units.gu(12)
461- model: playlistModel.model
462+ model: SortFilterModel {
463+ // Sorting disabled as it is incorrect on first run (due to workers?)
464+ // and SQL sorts the data correctly
465+ id: addToPlaylistModelFilter
466+ model: playlistModel.model
467+ filter.property: "name"
468+ filter.pattern: new RegExp(searchHeader.query, "i")
469+ filterCaseSensitivity: Qt.CaseInsensitive
470+ }
471 objectName: "addToPlaylistCardView"
472 delegate: Card {
473 id: playlist
474
475=== modified file 'common/ColumnFlow.qml'
476--- common/ColumnFlow.qml 2014-11-23 03:50:40 +0000
477+++ common/ColumnFlow.qml 2015-01-11 16:25:30 +0000
478@@ -1,5 +1,5 @@
479 /*
480- * Copyright (C) 2014
481+ * Copyright (C) 2014, 2015
482 * Andrew Hayzen <ahayzen@gmail.com>
483 *
484 * This program is free software; you can redistribute it and/or modify
485@@ -32,10 +32,12 @@
486 property int columnWidth: parent.width / columns
487 property int contentHeight: 0
488 property int count: model === undefined ? 0 : model.count
489+ property int delayRebuildIndex: -1
490 property var incubating: ({}) // incubating objects
491 property var items: ({})
492 property var itemToColumn: ({}) // cache of the columns of indexes
493 property int lastIndex: 0 // the furtherest index loaded
494+ property bool removing: false
495 property bool restoring: false // is the view restoring?
496 property var restoreItems: ({}) // when rebuilding items are stored here temporarily
497
498@@ -59,41 +61,94 @@
499 }
500 }
501
502- onCountChanged: {
503- if (!visible) { // store changes for when visible
504- if (count === 0 && lastIndex > -1) {
505- lastIndex = -1;
506- } else if (lastIndex > -1) {
507- lastIndex = -(lastIndex + 2); // save the index to restore later
508- }
509- } else if (count === 0) { // likely the model is been reset so reset the view
510- reset()
511- } else { // likely new items in the model check if any can be shown
512- append()
513- }
514+ onModelChanged: { // reload the model when it is set
515+ reset()
516+ append(true, model.count - 1)
517 }
518
519 onVisibleChanged: {
520- if (columns != columnHeights.length && visible) { // number of columns has changed while invisible so reset
521- if (!restoring) {
522- rebuildColumns()
523- }
524- } else if (lastIndex < 0 && visible) { // restore from count change
525- if (lastIndex === -1) {
526- reset()
527- } else {
528- lastIndex = (-lastIndex) - 2
529- }
530-
531- append()
532+ if (visible && delayRebuildIndex !== -1) { // restore from count change
533+ if (delayRebuildIndex === 0) {
534+ reset()
535+ } else {
536+ removeIndex(delayRebuildIndex)
537+ }
538+
539+ delayRebuildIndex = -1
540+ append(true)
541+ }
542+
543+ // number of columns has changed while invisible so reset if not already restoring
544+ if (visible && !restoring && columns != columnHeights.length) {
545+ rebuildColumns()
546+ }
547+ }
548+
549+ ListModel { // fakemodel for connections to link to when there is no model
550+ id: fakeModel
551+ }
552+
553+ Connections {
554+ target: model === undefined ? fakeModel : model
555+ onModelReset: {
556+ if (!visible) {
557+ delayRebuildIndex = 0
558+ } else {
559+ reset()
560+ append()
561+ }
562+ }
563+ onRowsInserted: {
564+ if (!visible) {
565+ setDelayRebuildIndex(first)
566+ } else {
567+ if (first <= lastIndex) {
568+ if (first === 0) {
569+ reset()
570+ } else {
571+ removeIndex(first) // remove earliest index and all items after
572+ }
573+ }
574+
575+ // Supply last index if larger as count is not updated until after insertion
576+ append(true, last > count ? last : count)
577+ }
578+ }
579+ onRowsRemoved: {
580+ if (!visible) {
581+ setDelayRebuildIndex(first)
582+ } else {
583+ if (first <= lastIndex) {
584+ if (first === 0) {
585+ reset()
586+ } else {
587+ removeIndex(first) // remove earliest index and all items after
588+ }
589+
590+ // count is not updated until after removal, so send insertMax
591+ // insertMax is count - removal region inclusive - 1 (lastIndex is 1 infront)
592+
593+ append(true, count - (1 + last - first) - 1) // rebuild any items on screen or before
594+ }
595+ }
596+ }
597+ }
598+
599+
600+ Connections {
601+ target: flickable
602+ onContentYChanged: {
603+ append() // Append any new items (scrolling down)
604+
605+ ensureItemsVisible()
606 }
607 }
608
609 // Append a new row of items if possible
610- function append()
611+ function append(loadBefore, insertMax)
612 {
613 // Do not allow append to run if incubating
614- if (isIncubating() || restoring) {
615+ if (isIncubating() || restoring || removing) {
616 return;
617 }
618
619@@ -106,7 +161,12 @@
620 var y = columnHeightsMax[columnsByHeight[i]];
621
622 // build new object in column if possible
623- if (count > 0 && lastIndex < count && inViewport(y, 0)) {
624+ // if insertMax is undefined then allow if there is work todo (from the count in the model)
625+ // otherwise use the insertMax as the count to compare with the lastIndex added to the columnFlow
626+ // and
627+ // allow if the y position is within the viewport
628+ // or if loadBefore is true then allow if the y position is before the viewport
629+ if (((count > 0 && lastIndex < count && insertMax === undefined) || (insertMax !== undefined && lastIndex <= insertMax)) && (inViewport(y, 0) || (loadBefore === true && beforeViewport(y)))) {
630 incubateObject(lastIndex++, columnsByHeight[i], getMaxInColumn(columnsByHeight[i]), append);
631 workDone = true
632 } else {
633@@ -119,6 +179,12 @@
634 }
635 }
636
637+ // Detect if a loaded object is before the viewport with a buffer
638+ function beforeViewport(y)
639+ {
640+ return y <= flickable.contentY - buffer;
641+ }
642+
643 // Cache the size of the columns for use later
644 function cacheColumnHeights()
645 {
646@@ -319,6 +385,29 @@
647 }
648 }
649
650+ // Remove an index from the model (invalidating anything after)
651+ function removeIndex(index)
652+ {
653+ removing = true
654+
655+ forceIncubationCompletion()
656+
657+ for (var i in items) {
658+ if (i >= index && items.hasOwnProperty(i)) {
659+ delete columnHeights[itemToColumn[i]][i]
660+ delete itemToColumn[i]
661+
662+ items[i].destroy()
663+ delete items[i]
664+ }
665+ }
666+
667+ lastIndex = index
668+ removing = false
669+
670+ cacheColumnHeights()
671+ }
672+
673 // Restores existing items into potentially new positions
674 function restoreExisting()
675 {
676@@ -385,6 +474,7 @@
677
678 // Reset and rebuild the variables
679 items = ({})
680+ itemToColumn = ({})
681 lastIndex = 0
682
683 columnHeights = []
684@@ -398,12 +488,10 @@
685 contentHeight = 0
686 }
687
688- Connections {
689- target: flickable
690- onContentYChanged: {
691- append() // Append any new items (scrolling down)
692-
693- ensureItemsVisible()
694+ function setDelayRebuildIndex(index)
695+ {
696+ if (delayRebuildIndex === -1 || index < lastIndex) {
697+ delayRebuildIndex = index
698 }
699 }
700 }
701
702=== modified file 'common/ListItemActions/AddToPlaylist.qml'
703--- common/ListItemActions/AddToPlaylist.qml 2014-10-29 02:29:52 +0000
704+++ common/ListItemActions/AddToPlaylist.qml 2015-01-11 16:25:30 +0000
705@@ -1,7 +1,8 @@
706 /*
707- * Copyright (C) 2014 Andrew Hayzen <ahayzen@gmail.com>
708- * Daniel Holm <d.holmen@gmail.com>
709- * Victor Thompson <victor.thompson@gmail.com>
710+ * Copyright (C) 2014, 2015
711+ * Andrew Hayzen <ahayzen@gmail.com>
712+ * Daniel Holm <d.holmen@gmail.com>
713+ * Victor Thompson <victor.thompson@gmail.com>
714 *
715 * This program is free software; you can redistribute it and/or modify
716 * it under the terms of the GNU General Public License as published by
717@@ -25,13 +26,12 @@
718 text: i18n.tr("Add to playlist")
719
720 property bool primed: false
721- property var page
722
723 onTriggered: {
724 console.debug("Debug: Add track to playlist");
725
726 var comp = Qt.createComponent("../../MusicaddtoPlaylist.qml")
727- var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": [makeDict(model)], "page": page});
728+ var addToPlaylist = comp.createObject(mainPageStack, {"chosenElements": [makeDict(model)]});
729
730 if (addToPlaylist == null) { // Error Handling
731 console.log("Error creating object");
732
733=== modified file 'common/MusicPage.qml'
734--- common/MusicPage.qml 2014-10-06 02:18:23 +0000
735+++ common/MusicPage.qml 2015-01-11 16:25:30 +0000
736@@ -1,5 +1,5 @@
737 /*
738- * Copyright (C) 2013, 2014
739+ * Copyright (C) 2013, 2014, 2015
740 * Andrew Hayzen <ahayzen@gmail.com>
741 * Daniel Holm <d.holmen@gmail.com>
742 * Victor Thompson <victor.thompson@gmail.com>
743@@ -17,6 +17,7 @@
744 * along with this program. If not, see <http://www.gnu.org/licenses/>.
745 */
746
747+import QtQuick 2.3
748 import Ubuntu.Components 1.1
749
750
751@@ -28,6 +29,31 @@
752 fill: parent
753 }
754
755+ property bool searchable: false
756+ property int searchResultsCount
757+
758+ Label {
759+ anchors {
760+ centerIn: parent
761+ }
762+ text: i18n.tr("No items found")
763+ visible: parent.state === "search" && searchResultsCount === 0
764+ }
765+
766+ // FIXME: hack is a workaround for SDK bug pad.lv/1341814
767+ // which causes the header and contents of the page to become out of sync
768+ property Item __oldContents: null
769+
770+ Connections {
771+ target: thisPage.head
772+ onContentsChanged: {
773+ if (thisPage.__oldContents) {
774+ thisPage.__oldContents.parent = null;
775+ }
776+ thisPage.__oldContents = thisPage.head.contents;
777+ }
778+ }
779+
780 onVisibleChanged: {
781 if (visible) {
782 musicToolbar.setPage(thisPage);
783
784=== added file 'common/SearchHeadState.qml'
785--- common/SearchHeadState.qml 1970-01-01 00:00:00 +0000
786+++ common/SearchHeadState.qml 2015-01-11 16:25:30 +0000
787@@ -0,0 +1,71 @@
788+/*
789+ * Copyright (C) 2015
790+ * Andrew Hayzen <ahayzen@gmail.com>
791+ * Victor Thompson <victor.thompson@gmail.com>
792+ *
793+ * This program is free software; you can redistribute it and/or modify
794+ * it under the terms of the GNU General Public License as published by
795+ * the Free Software Foundation; version 3.
796+ *
797+ * This program is distributed in the hope that it will be useful,
798+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
799+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
800+ * GNU General Public License for more details.
801+ *
802+ * You should have received a copy of the GNU General Public License
803+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
804+ */
805+
806+import QtQuick 2.3
807+import Ubuntu.Components 1.1
808+
809+PageHeadState {
810+ id: headerState
811+ name: "search"
812+ head: thisPage.head
813+ backAction: Action {
814+ id: leaveSearchAction
815+ text: "back"
816+ iconName: "back"
817+ onTriggered: {
818+ thisPage.state = "default"
819+ searchField.text = ""
820+ }
821+ }
822+ contents: TextField {
823+ id: searchField
824+ anchors {
825+ left: parent ? parent.left : undefined
826+ right: parent ? parent.right : undefined
827+ rightMargin: units.gu(2)
828+ }
829+ color: styleMusic.common.black
830+ hasClearButton: true
831+ inputMethodHints: Qt.ImhNoPredictiveText
832+ placeholderText: i18n.tr("Search music")
833+
834+ onVisibleChanged: {
835+ if (visible) {
836+ forceActiveFocus()
837+ }
838+ }
839+
840+ // Use the page onVisible as the text field goes visible=false when switching states
841+ // This is used when popping from the pageStack and returning back to a page with search
842+ Connections {
843+ target: thisPage
844+ onVisibleChanged: {
845+ // clear when the page becomes visible not invisible
846+ // if invisible is used the delegates can be destroyed which
847+ // have created the pushed component
848+ if (visible) {
849+ searchField.text = ""
850+ thisPage.state = "default"
851+ }
852+ }
853+ }
854+ }
855+
856+ property Page thisPage
857+ property alias query: searchField.text
858+}
859
860=== modified file 'common/SongsPage.qml'
861--- common/SongsPage.qml 2015-01-08 00:48:43 +0000
862+++ common/SongsPage.qml 2015-01-11 16:25:30 +0000
863@@ -1,5 +1,5 @@
864 /*
865- * Copyright (C) 2013, 2014
866+ * Copyright (C) 2013, 2014, 2015
867 * Andrew Hayzen <ahayzen@gmail.com>
868 * Daniel Holm <d.holmen@gmail.com>
869 * Victor Thompson <victor.thompson@gmail.com>
870@@ -416,7 +416,7 @@
871
872 },
873 AddToPlaylist {
874- page: songStackPage
875+
876 }
877 ]
878
879
880=== removed file 'images/search.svg'
881--- images/search.svg 2014-02-20 01:25:57 +0000
882+++ images/search.svg 1970-01-01 00:00:00 +0000
883@@ -1,153 +0,0 @@
884-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
885-<!-- Created with Inkscape (http://www.inkscape.org/) -->
886-
887-<svg
888- xmlns:dc="http://purl.org/dc/elements/1.1/"
889- xmlns:cc="http://creativecommons.org/ns#"
890- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
891- xmlns:svg="http://www.w3.org/2000/svg"
892- xmlns="http://www.w3.org/2000/svg"
893- xmlns:xlink="http://www.w3.org/1999/xlink"
894- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
895- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
896- width="90"
897- height="90.000015"
898- id="svg3133"
899- version="1.1"
900- inkscape:version="0.48+devel r12262"
901- sodipodi:docname="search.svg">
902- <defs
903- id="defs3135">
904- <linearGradient
905- inkscape:collect="always"
906- id="linearGradient3803">
907- <stop
908- style="stop-color:#e7e5e5;stop-opacity:1;"
909- offset="0"
910- id="stop3805" />
911- <stop
912- style="stop-color:#e2dfdf;stop-opacity:1"
913- offset="1"
914- id="stop3807" />
915- </linearGradient>
916- <linearGradient
917- inkscape:collect="always"
918- xlink:href="#linearGradient3803"
919- id="linearGradient3809"
920- x1="53.012165"
921- y1="-102.79017"
922- x2="53.012165"
923- y2="-66.661224"
924- gradientUnits="userSpaceOnUse" />
925- <linearGradient
926- inkscape:collect="always"
927- xlink:href="#linearGradient3803"
928- id="linearGradient3813"
929- gradientUnits="userSpaceOnUse"
930- x1="53.012165"
931- y1="-102.79017"
932- x2="53.012165"
933- y2="-66.661224"
934- gradientTransform="translate(-625,0)" />
935- </defs>
936- <sodipodi:namedview
937- id="base"
938- pagecolor="#ffffff"
939- bordercolor="#666666"
940- borderopacity="1.0"
941- inkscape:pageopacity="0.0"
942- inkscape:pageshadow="2"
943- inkscape:zoom="7.9580781"
944- inkscape:cx="47.57328"
945- inkscape:cy="40.531144"
946- inkscape:document-units="px"
947- inkscape:current-layer="g3842"
948- showgrid="true"
949- inkscape:window-width="1920"
950- inkscape:window-height="1029"
951- inkscape:window-x="0"
952- inkscape:window-y="24"
953- inkscape:window-maximized="1"
954- inkscape:snap-grids="true"
955- inkscape:snap-global="true"
956- fit-margin-top="0"
957- fit-margin-left="0"
958- fit-margin-right="0"
959- fit-margin-bottom="0"
960- inkscape:snap-bbox="true"
961- inkscape:bbox-paths="true"
962- inkscape:bbox-nodes="true"
963- inkscape:snap-bbox-edge-midpoints="true"
964- inkscape:snap-bbox-midpoints="true"
965- inkscape:object-paths="true"
966- inkscape:snap-intersection-paths="true"
967- inkscape:snap-midpoints="true"
968- inkscape:snap-smooth-nodes="true"
969- inkscape:object-nodes="true"
970- inkscape:snap-object-midpoints="true"
971- inkscape:snap-center="true">
972- <inkscape:grid
973- type="xygrid"
974- id="grid3016"
975- empspacing="6"
976- visible="true"
977- enabled="true"
978- snapvisiblegridlinesonly="true"
979- originx="-2.98e-06px"
980- originy="2.6171874e-06px" />
981- </sodipodi:namedview>
982- <metadata
983- id="metadata3138">
984- <rdf:RDF>
985- <cc:Work
986- rdf:about="">
987- <dc:format>image/svg+xml</dc:format>
988- <dc:type
989- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
990- <dc:title />
991- </cc:Work>
992- </rdf:RDF>
993- </metadata>
994- <g
995- inkscape:label="Layer 1"
996- inkscape:groupmode="layer"
997- id="layer1"
998- transform="translate(-2.98e-6,-962.36219)"
999- style="display:inline">
1000- <g
1001- transform="matrix(0.99934414,0,0,1,-106.92982,549.00002)"
1002- id="g3842"
1003- style="display:inline">
1004- <rect
1005- style="opacity:0.05;color:#000000;fill:none;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
1006- id="rect3844"
1007- width="90.059067"
1008- height="90.000015"
1009- x="107"
1010- y="-503.36218"
1011- transform="scale(1,-1)" />
1012- <path
1013- sodipodi:type="arc"
1014- style="color:#000000;fill:none;stroke:#808080;stroke-width:10.49972248;stroke-miterlimit:4;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
1015- id="path2995"
1016- sodipodi:cx="35.999996"
1017- sodipodi:cy="36.500011"
1018- sodipodi:rx="33"
1019- sodipodi:ry="33.5"
1020- d="m 68.999996,36.500011 a 33,33.5 0 1 1 -65.9999998,0 A 33,33.5 0 1 1 68.999996,36.500011 Z"
1021- transform="matrix(0.86331274,0,0,0.85161915,120.95028,427.27807)" />
1022- <path
1023- style="font-size:xx-small;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#808080;fill-opacity:1;stroke:none;stroke-width:6;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
1024- d="m 175.1697,477.23717 -4.25279,4.25 12.00787,12 L 187.17757,489.23717 Z"
1025- id="path3765"
1026- inkscape:connector-curvature="0"
1027- sodipodi:nodetypes="ccccc" />
1028- <path
1029- style="font-size:xx-small;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#808080;fill-opacity:1;stroke:none;stroke-width:11.80387211;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
1030- d="m 180.97203,481.40625 c -2.32437,0.012 -4.57033,1.53501 -5.44162,3.68994 -0.87128,2.15492 -0.31496,4.81092 1.34787,6.43506 l 8.79632,8.79991 c 4.02735,4.27401 4.89575,3.69726 8.46955,0.1258 3.57381,-3.57146 4.15747,-4.18909 -0.1258,-8.46955 l -8.79632,-8.79991 C 184.12024,482.05781 182.55003,481.39971 180.97203,481.40625 Z"
1031- id="path3767"
1032- inkscape:connector-curvature="0"
1033- sodipodi:nodetypes="cscczccc" />
1034- </g>
1035- </g>
1036-</svg>
1037
1038=== modified file 'music-app.qml'
1039--- music-app.qml 2014-12-19 22:51:19 +0000
1040+++ music-app.qml 2015-01-11 16:25:30 +0000
1041@@ -1,5 +1,5 @@
1042 /*
1043- * Copyright (C) 2013, 2014
1044+ * Copyright (C) 2013, 2014, 2015
1045 * Andrew Hayzen <ahayzen@gmail.com>
1046 * Daniel Holm <d.holmen@gmail.com>
1047 * Victor Thompson <victor.thompson@gmail.com>
1048@@ -54,7 +54,11 @@
1049 focus: true
1050 Keys.onPressed: {
1051 if(event.key === Qt.Key_Escape) {
1052- musicToolbar.goBack(); // Esc Go back
1053+ if (musicToolbar.currentPage.searchable && musicToolbar.currentPage.state === "search") {
1054+ musicToolbar.currentPage.state = "default"
1055+ } else {
1056+ musicToolbar.goBack(); // Esc Go back
1057+ }
1058 }
1059 else if(event.modifiers === Qt.AltModifier) {
1060 var position;
1061@@ -90,10 +94,11 @@
1062 player.repeat = !player.repeat
1063 break;
1064 case Qt.Key_F: // Ctrl+F Show Search popup
1065- if (!searchSheet.sheetVisible) {
1066- PopupUtils.open(searchSheet.sheet, mainView,
1067- { title: i18n.tr("Search") })
1068+ if (musicToolbar.currentPage.searchable && musicToolbar.currentPage.state === "default") {
1069+ musicToolbar.currentPage.state = "search"
1070+ header.show()
1071 }
1072+
1073 break;
1074 case Qt.Key_J: // Ctrl+J Jump to playing song
1075 tabs.pushNowPlaying()
1076@@ -135,20 +140,6 @@
1077 }
1078 }
1079
1080- // HUD Actions
1081- Action {
1082- id: searchAction
1083- text: i18n.tr("Search")
1084- keywords: i18n.tr("Search Track")
1085- onTriggered: {
1086- if (!searchSheet.sheetVisible) {
1087- PopupUtils.open(searchSheet.sheet, mainView,
1088- {
1089- title: i18n.tr("Search")
1090- } )
1091- }
1092- }
1093- }
1094 Action {
1095 id: nextAction
1096 text: i18n.tr("Next")
1097@@ -185,7 +176,7 @@
1098 onTriggered: player.stop()
1099 }
1100
1101- actions: [searchAction, nextAction, playsAction, prevAction, stopAction, backAction]
1102+ actions: [nextAction, playsAction, prevAction, stopAction, backAction]
1103
1104 // signal to open new URIs
1105 Connections {
1106@@ -578,6 +569,10 @@
1107 if (args.values.url) {
1108 uriHandler.process(args.values.url, true);
1109 }
1110+
1111+ // TODO: Workaround for pad.lv/1356779, force the theme's backgroundText color
1112+ // to work with the app's backgroundColor
1113+ Theme.palette.normal.backgroundText = "#81888888"
1114 }
1115
1116 // VARIABLES
1117@@ -808,13 +803,15 @@
1118 function append(listElement)
1119 {
1120 model.append(makeDict(listElement))
1121- Library.addQueueItem(trackQueue.model.count,listElement.filename)
1122+ Library.addQueueItem(trackQueue.model.count, listElement.filename)
1123 }
1124
1125 function clear()
1126 {
1127 model.clear()
1128 Library.clearQueue()
1129+
1130+ queueIndex = 0 // reset otherwise when you append and play 1 track it doesn't update correctly
1131 }
1132 }
1133
1134
1135=== modified file 'tests/autopilot/music_app/__init__.py'
1136--- tests/autopilot/music_app/__init__.py 2014-11-07 22:54:20 +0000
1137+++ tests/autopilot/music_app/__init__.py 2015-01-11 16:25:30 +0000
1138@@ -1,5 +1,5 @@
1139 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1140-# Copyright 2013, 2014 Canonical
1141+# Copyright 2013, 2014, 2015 Canonical
1142 #
1143 # This program is free software: you can redistribute it and/or modify it
1144 # under the terms of the GNU General Public License version 3, as published
1145@@ -59,7 +59,7 @@
1146 self.main_view.switch_to_tab('albumsTab')
1147
1148 return self.main_view.wait_select_single(
1149- Page11, objectName='albumsPage')
1150+ MusicPage, objectName='albumsPage')
1151
1152 def get_albums_artist_page(self):
1153 return self.main_view.wait_select_single(
1154@@ -69,7 +69,7 @@
1155 self.main_view.switch_to_tab('artistsTab')
1156
1157 return self.main_view.wait_select_single(
1158- Page11, objectName='artistsPage')
1159+ MusicPage, objectName='artistsPage')
1160
1161 def get_new_playlist_dialog(self):
1162 return self.main_view.wait_select_single(
1163@@ -105,7 +105,7 @@
1164 self.main_view.switch_to_tab('tracksTab')
1165
1166 return self.main_view.wait_select_single(
1167- Page11, objectName='tracksPage')
1168+ MusicPage, objectName='tracksPage')
1169
1170 @property
1171 def loaded(self):
1172@@ -137,16 +137,9 @@
1173 self.main_view = self.get_root_instance().select_single(MainView)
1174
1175
1176-class MusicPage(Page):
1177- def __init__(self, *args):
1178- super(Page, self).__init__(*args)
1179-
1180-
1181-class MusicAlbums(MusicPage):
1182+class MusicAlbums():
1183 """ Autopilot helper for the albums page """
1184 def __init__(self, *args):
1185- super(MusicPage, self).__init__(*args)
1186-
1187 self.visible.wait_for(True)
1188
1189 @click_object
1190@@ -155,11 +148,9 @@
1191 objectName="albumsPageGridItem" + str(i)))
1192
1193
1194-class MusicArtists(MusicPage):
1195+class MusicArtists():
1196 """ Autopilot helper for the artists page """
1197 def __init__(self, *args):
1198- super(MusicPage, self).__init__(*args)
1199-
1200 self.visible.wait_for(True)
1201
1202 @click_object
1203@@ -168,11 +159,9 @@
1204 objectName="artistsPageGridItem" + str(i)))
1205
1206
1207-class MusicTracks(MusicPage):
1208+class MusicTracks():
1209 """ Autopilot helper for the tracks page """
1210 def __init__(self, *args):
1211- super(MusicPage, self).__init__(*args)
1212-
1213 self.visible.wait_for(True)
1214
1215 def get_track(self, i):
1216@@ -180,6 +169,18 @@
1217 objectName="tracksPageListItem" + str(i)))
1218
1219
1220+class MusicPage(Page, MusicAlbums, MusicArtists, MusicTracks):
1221+ """
1222+ FIXME: Represents MusicTracks MusicArtists MusicAlbums
1223+ due to bug 1341671 and bug 1337004 they all appear as MusicPage
1224+ Therefore this class 'contains' all of them for now
1225+ Once the bugs are fixed MusicPage should be swapped for MusicTracks etc
1226+ and MusicTracks should be changed to inherit MusicPage
1227+ """
1228+ def __init__(self, *args):
1229+ super(MusicPage, self).__init__(*args)
1230+
1231+
1232 class MusicPlaylists(MusicPage):
1233 """ Autopilot helper for the playlists page """
1234 def __init__(self, *args):
1235@@ -215,19 +216,6 @@
1236 objectName="addToPlaylistCardItem" + str(i)))
1237
1238
1239-class Page11(MusicAlbums, MusicArtists, MusicTracks):
1240- """
1241- FIXME: Represents MusicTracks MusicArtists MusicAlbums
1242- due to bug 1341671 and bug 1337004 they all appear as Page11
1243- Therefore this class 'contains' all of them for now
1244- Once the bugs are fixed Page11 should be swapped for MusicTracks etc
1245- """
1246- def __init__(self, *args):
1247- super(MusicAlbums, self).__init__(*args)
1248- super(MusicArtists, self).__init__(*args)
1249- super(MusicTracks, self).__init__(*args)
1250-
1251-
1252 class Player(UbuntuUIToolkitCustomProxyObjectBase):
1253 """Autopilot helper for Player"""
1254

Subscribers

People subscribed via source and target branches