Merge lp:~ahayzen/music-app/add-sdk-search-support-002 into lp:music-app/remix
- add-sdk-search-support-002
- Merge into remix
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 | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Victor Thompson | Approve | ||
Ubuntu Phone Apps Jenkins Bot | continuous-integration | Approve | |
Andrew Hayzen | Abstain | ||
Review via email:
|
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 MusicaddtoPlayl
* 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 MusicaddtoPlayl
* 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/
- 769. By Andrew Hayzen
-
* Tidy comments
* Strip anything todo with sheets as they have been removed
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:768
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:769
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 770. By Andrew Hayzen
-
* Force the focus onto the search field when search activated
* Attempt at ap fixes
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:770
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:772
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 MusicaddtoPlayl
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:773
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:774
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 775. By Andrew Hayzen
-
* Fix for addToPlaylists not working on second viewing
* Fix for queueIndex becoming out of sync with clear; append; trackQueueClick(0);
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:775
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 776. By Andrew Hayzen
-
* Fixes for autopilot
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:776
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 777. By Andrew Hayzen
-
* Ensure that searching on MusicGenres.qml is case insensitive
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:777
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andrew Hayzen (ahayzen) wrote : | # |
From my point of view this is now ready :)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
- 778. By Andrew Hayzen
-
* Add searching to MusicaddtoPlayl
ist.qml
* Disable searching if there are no playlists
* Various fixes and tidy ups
* Add copyright 2015
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:778
http://
Executed test runs:
UNSTABLE: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 779. By Andrew Hayzen
-
* Fix for objectName typo causing autopilot failure
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:779
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 780. By Andrew Hayzen
-
* Add empty state label for search
* Add Ctrl+F support to search
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andrew Hayzen (ahayzen) wrote : | # |
1) DONE
4) DONE
Just point 2) left.
- 781. By Andrew Hayzen
-
* Force the header to show on Ctrl+F
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:781
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:782
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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/
--- common/
+++ common/
@@ -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.
+ placeholderText: i18n.tr("Search music")
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.
+
property Page thisPage
property alias query: searchField.text
}
- 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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:783
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 784. By Andrew Hayzen
-
* Fix for blank MusicaddtoPlayl
ist.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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:784
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:786
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:787
http://
Executed test runs:
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
Preview Diff
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 |
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?