1/*
2 * Copyright (C) 2019
3 *      Jean-Luc Barriere <jlbarriere68@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18import QtQuick 2.9
19import QtQuick.Controls 2.2
20import QtQml.Models 2.3
21import NosonApp 1.0
22import NosonMediaScanner 1.0
23import "../components"
24import "../components/Delegates"
25import "../components/Flickables"
26import "../components/Dialog"
27
28MusicPage {
29    id: songStackPage
30    objectName: "songsPage"
31    visible: false
32    pageFlickable: songList
33    isListView: true
34    listview: songList
35
36    property string line1: ""
37    property string line2: ""
38    property var covers: []
39    property int coverFlow: 1
40    property bool isAlbum: false
41    property string year: ""
42    property string album: ""
43    property string artist: ""
44    property string genre: ""
45    property string composer: ""
46
47    // enable selection toolbar
48    selectable: true
49
50    onStateChanged: {
51        if (state === "selection")
52            songList.state = "selection"
53        else
54            songList.state = "default"
55    }
56
57    TrackList {
58        id: tracks
59        album: songStackPage.album
60        artist: songStackPage.artist
61        genre: songStackPage.genre
62        composer: songStackPage.composer
63        Component.onCompleted: init()
64    }
65
66    SortFilterModel {
67        id: songsModel
68        model: tracks
69        sort.property: isAlbum ? "albumTrackNo" : "normalized"
70        sort.order: Qt.AscendingOrder
71        sortCaseSensitivity: Qt.CaseInsensitive
72    }
73
74    function makeFileCoverSource(modelItem) {
75        var art = "";
76        if (modelItem.hasArt)
77            art = player.makeFilePictureLocalURL(modelItem.filePath);
78        return makeCoverSource(art, modelItem.author, modelItem.album);
79    }
80
81    function makeItemPayload(modelItem) {
82        return player.makeFileStreamItem(modelItem.filePath, modelItem.codec, modelItem.title, modelItem.album,
83                                         modelItem.author, modelItem.duration.toString(), modelItem.hasArt);
84    }
85
86    function makeContainerPayloads(model) {
87        var items = [];
88        for (var i = 0; i < model.count; i++) {
89            var item = model.get(i);
90            items.push({id: item.id, payload: makeItemPayload(item)});
91        }
92        return items;
93    }
94
95    Repeater {
96        id: songModelRepeater
97        model: songsModel
98
99        delegate: Item {
100            property string art: model.art
101            property string artist: model.author
102            property string album: model.album
103            property string filePath: model.filePath
104            property bool hasArt: model.hasArt
105        }
106        property bool hasCover: covers.length ? true : false
107
108        onItemAdded: {
109            if (!hasCover) {
110                if (item.art !== "") {
111                    songStackPage.covers = [{art: item.art}];
112                    hasCover = true;
113                } else if (item.hasArt) {
114                    item.art = player.makeFilePictureLocalURL(item.filePath);
115                    songStackPage.covers = [{art: item.art}];
116                    hasCover = true;
117                }
118            }
119        }
120    }
121
122    BlurredBackground {
123        id: blurredBackground
124        height: parent.height
125    }
126
127    Component {
128        id: dragDelegate
129
130        SelectMusicListItem {
131            id: listItem
132            listview: songList
133            reorderable: false
134            selectable: true
135
136            onClick: {
137                var item = {
138                    id: model.id, title: model.title, album: model.album, author: model.author, albumTrackNo: model.albumTrackNo,
139                    payload: makeItemPayload(model)
140                };
141                var arts = [];
142                if (isAlbum)
143                    arts = covers; // header covers
144                else
145                    arts = [{art: imageSource}]; // item cover
146
147                dialogSongInfo.open(item, arts,
148                                    "qrc:/controls2/ThisDevice/ArtistView.qml",
149                                    {
150                                        "artist": model.author,
151                                        "covers": makeCoverSource(undefined, model.author, undefined),
152                                        "pageTitle": qsTr("Artist")
153                                    },
154                                    true,   // force show more
155                                    true,   // can play
156                                    true    // can queue
157                                    );
158            }
159
160            color: "transparent"
161
162            noCover: "qrc:/images/no_cover.png"
163            rowNumber: songStackPage.isAlbum ? model.albumTrackNo > 0 ? model.albumTrackNo.toString() : "#" : ""
164            imageSources: !songStackPage.isAlbum ? makeFileCoverSource(model) : []
165            description: qsTr("Song")
166
167            onImageError: model.art = "" // reset invalid url from model
168            onActionPressed: {
169                var payload = makeItemPayload(model);
170                trackClicked({id: model.id, payload: payload}, true);
171            }
172            actionVisible: true
173            actionIconSource: "qrc:/images/media-preview-start.svg"
174            menuVisible: true
175
176            menuItems: [
177                AddToQueue {
178                    modelItem: model
179                }
180            ]
181
182            coverSize: units.gu(5)
183
184            column: Column {
185                Label {
186                    id: trackTitle
187                    color: styleMusic.view.primaryColor
188                    font.pointSize: units.fs("medium")
189                    text: model.title
190                }
191
192                Label {
193                    id: trackArtist
194                    color: styleMusic.view.secondaryColor
195                    font.pointSize: units.fs("x-small")
196                    text: model.author
197                    visible: !isAlbum
198                }
199
200                Label {
201                    id: trackInfo
202                    color: styleMusic.view.secondaryColor
203                    font.pointSize: units.fs(isAlbum ? "small" : "x-small")
204                    text: model.codec + " " + (model.bitRate > 999999 ? model.sampleRate : model.bitRate > 0 ? Math.round(model.bitRate/1000) + "k" : "")
205                }
206            }
207
208            Component.onCompleted: {
209                if (model.year > 0) {
210                    songStackPage.year = model.year
211                }
212            }
213        }
214    }
215
216    MultiSelectListView {
217        id: songList
218        anchors.fill: parent
219
220        header: MusicHeader {
221            id: blurredHeader
222            rightColumn: Column {
223                spacing: units.gu(1)
224                ShuffleButton {
225                    model: songsModel
226                    width: units.gu(24)
227                }
228                QueueAllButton {
229                    model: songsModel
230                    width: units.gu(24)
231                }
232                PlayAllButton {
233                    model: songsModel
234                    width: units.gu(24)
235                }
236            }
237            height: contentHeight
238            coverSources: songStackPage.covers
239            coverFlow: songStackPage.coverFlow
240            titleColumn: Column {
241                spacing: units.gu(1)
242
243                Label {
244                    id: albumLabel
245                    anchors {
246                        left: parent.left
247                        right: parent.right
248                    }
249                    color: styleMusic.view.foregroundColor
250                    elide: Text.ElideRight
251                    font.pointSize: units.fs("x-large")
252                    maximumLineCount: 1
253                    text: line2
254                    wrapMode: Text.NoWrap
255                }
256
257                Label {
258                    id: albumArtist
259                    anchors {
260                        left: parent.left
261                        right: parent.right
262                    }
263                    color: styleMusic.view.secondaryColor
264                    elide: Text.ElideRight
265                    font.pointSize: units.fs("small")
266                    maximumLineCount: 1
267                    text: line1
268                    visible: line1 !== ""
269                    wrapMode: Text.NoWrap
270                }
271
272                Label {
273                    id: albumYear
274                    anchors {
275                        left: parent.left
276                        right: parent.right
277                    }
278                    color: styleMusic.view.secondaryColor
279                    elide: Text.ElideRight
280                    font.pointSize: units.fs("small")
281                    maximumLineCount: 1
282                    text: isAlbum
283                          ? (year !== "" ? year + " | " : "") + qsTr("%n song(s)", "", songsModel.count)
284                          : qsTr("%n song(s)", "", songsModel.count)
285                    wrapMode: Text.NoWrap
286                }
287
288                Item {
289                    id: spacer
290                    width: parent.width
291                    height: units.gu(1)
292                }
293            }
294
295            onFirstSourceChanged: {
296                blurredBackground.art = firstSource
297            }
298        }
299
300        model: DelegateModel {
301            id: visualModel
302            model: songsModel
303            delegate: dragDelegate
304        }
305
306        Connections {
307            target: songStackPage
308            function onSelectAllClicked() { songList.selectAll() }
309            function onSelectNoneClicked() { songList.selectNone() }
310            function onAddToQueueClicked() {
311                var indicies = songList.getSelectedIndices();
312                var items = [];
313                for (var i = 0; i < indicies.length; i++) {
314                    var itemModel = songsModel.get(indicies[i]);
315                    var payload = makeItemPayload(itemModel);
316                    items.push({id: itemModel.id, payload: payload});
317                }
318                if (addMultipleItemsToQueue(items)) {
319                    songList.selectNone()
320                    songStackPage.state = "default"
321                }
322            }
323        }
324
325        onHasSelectionChanged: {
326            songStackPage.selectAllVisible = !hasSelection
327            songStackPage.selectNoneVisible = hasSelection
328            songStackPage.addToQueueVisible = hasSelection
329            songStackPage.addToPlaylistVisible = false
330            songStackPage.removeSelectedVisible = false
331
332        }
333
334        Component.onCompleted: {
335            songStackPage.selectAllVisible = !hasSelection
336            songStackPage.selectNoneVisible = hasSelection
337            songStackPage.addToQueueVisible = hasSelection
338            songStackPage.addToPlaylistVisible = false
339            songStackPage.removeSelectedVisible = false
340        }
341    }
342}
343