diff --git a/Spottie/Backend/Types/API/SearchResultsResponse.swift b/Spottie/Backend/Types/API/SearchResultsResponse.swift index e8d135f..2d03330 100644 --- a/Spottie/Backend/Types/API/SearchResultsResponse.swift +++ b/Spottie/Backend/Types/API/SearchResultsResponse.swift @@ -9,7 +9,7 @@ import Foundation struct SearchResultsResponse: Decodable { let albums: WebAPIPagingObject - let artists: WebAPIPagingObject + let artists: WebAPIPagingObject let tracks: WebAPIPagingObject let playlists: WebAPIPagingObject } diff --git a/Spottie/Backend/Types/WebAPI/WebAPIArtistObject.swift b/Spottie/Backend/Types/WebAPI/WebAPIArtistObject.swift index 045676f..ca059f8 100644 --- a/Spottie/Backend/Types/WebAPI/WebAPIArtistObject.swift +++ b/Spottie/Backend/Types/WebAPI/WebAPIArtistObject.swift @@ -5,10 +5,20 @@ // Created by Lee Jun Kit on 24/5/21. // +import Foundation + struct WebAPIArtistObject: Codable { var id: String var uri: String var name: String var images: [WebAPIImageObject] var popularity: Int + + func getArtworkURL() -> URL { + if (self.images.isEmpty) { + return URL(string: "https://misc.scdn.co/liked-songs/liked-songs-640.png")! + } + + return URL(string: self.images[0].url)! + } } diff --git a/Spottie/Backend/Types/WebAPI/WebAPIPlaylistObject.swift b/Spottie/Backend/Types/WebAPI/WebAPIPlaylistObject.swift index 7df6705..46d0725 100644 --- a/Spottie/Backend/Types/WebAPI/WebAPIPlaylistObject.swift +++ b/Spottie/Backend/Types/WebAPI/WebAPIPlaylistObject.swift @@ -15,4 +15,12 @@ struct WebAPIPlaylistObject: Decodable { var description: String? var images: [WebAPIImageObject] var tracks: WebAPIPlaylistTracksRefObject + + func getArtworkURL() -> URL { + if (self.images.isEmpty) { + return URL(string: "https://misc.scdn.co/liked-songs/liked-songs-640.png")! + } + + return URL(string: self.images[0].url)! + } } diff --git a/Spottie/Backend/Types/WebAPI/WebAPISimplifiedAlbumObject.swift b/Spottie/Backend/Types/WebAPI/WebAPISimplifiedAlbumObject.swift index 3d716ac..100087a 100644 --- a/Spottie/Backend/Types/WebAPI/WebAPISimplifiedAlbumObject.swift +++ b/Spottie/Backend/Types/WebAPI/WebAPISimplifiedAlbumObject.swift @@ -30,7 +30,11 @@ struct WebAPISimplifiedAlbumObject: Codable { var releaseDatePrecision: ReleaseDatePrecision var totalTracks: Int - func getArtworkURL() -> URL? { - return URL(string: self.images[0].url) + func getArtworkURL() -> URL { + if (self.images.isEmpty) { + return URL(string: "https://misc.scdn.co/liked-songs/liked-songs-640.png")! + } + + return URL(string: self.images[0].url)! } } diff --git a/Spottie/Views/Home.swift b/Spottie/Views/Home.swift index ef80b84..7230ccb 100644 --- a/Spottie/Views/Home.swift +++ b/Spottie/Views/Home.swift @@ -24,6 +24,8 @@ struct Home: View { var body: some View { GeometryReader { reader in + let numItemsToShow = numberOfItemsToShowInRow(reader) + ScrollView(.vertical) { if searchText.isEmpty { LazyVStack(alignment: .leading) { @@ -35,7 +37,6 @@ struct Home: View { ) .padding() } else if vm.items.count > 0 { - let numItemsToShow = numberOfItemsToShowInRow(reader) CarouselRow( viewModel: vm, onItemPressed: viewModel.load, @@ -46,7 +47,10 @@ struct Home: View { } } } else { - Search(viewModel: Search.ViewModel(searchTermPublisher: debouncedPublisher)) + Search( + viewModel: Search.ViewModel(searchTermPublisher: debouncedPublisher), + numItemsPerRow: numItemsToShow + ) } } } @@ -100,16 +104,16 @@ extension Home { case let .album(album): title = album.name subtitle = album.artists[0].name - artworkURL = album.getArtworkURL()! + artworkURL = album.getArtworkURL() case let .artist(artist): title = artist.name subtitle = "Artist" - artworkURL = URL(string: artist.images[0].url)! + artworkURL = artist.getArtworkURL() artworkIsCircle = true case let .playlist(playlist): title = playlist.name subtitle = playlist.description ?? "" - artworkURL = URL(string: playlist.images[0].url)! + artworkURL = playlist.getArtworkURL() case let .link(link): title = link.name subtitle = "" diff --git a/Spottie/Views/Search.swift b/Spottie/Views/Search.swift index 15e808b..c9692e5 100644 --- a/Spottie/Views/Search.swift +++ b/Spottie/Views/Search.swift @@ -10,10 +10,14 @@ import Combine struct Search: View { @StateObject var viewModel: ViewModel + var numItemsPerRow: Int + var body: some View { - ScrollView { - LazyVStack(alignment: .leading) { - + LazyVStack(alignment: .leading) { + ForEach(viewModel.results) { vm in + CarouselRow(viewModel: vm, onItemPressed: { id in + + }, numItemsToShow: numItemsPerRow) } } } @@ -24,7 +28,8 @@ extension Search { // is to create a publisher from onChange of searchText // https://rhonabwy.com/2021/02/07/integrating-swiftui-bindings-and-combine/ class ViewModel: ObservableObject { - @Published var results: SearchResultsResponse? + @Published var results: [CarouselRow.ViewModel] = [] + private var cancellables = [AnyCancellable]() init(searchTermPublisher: AnyPublisher) { searchTermPublisher @@ -35,8 +40,63 @@ extension Search { } .switchToLatest() .receive(on: DispatchQueue.main) - - .assign(to: &$results) + .sink { response in + if let r = response { + let artistsRow = CarouselRow.ViewModel( + id: "artists", + title: "Artists", + subtitle: nil, + items: r.artists.items.map { artist in + let id = artist.id + let title = artist.name + let artworkURL = artist.getArtworkURL() + return CarouselRowItem.ViewModel( + id: id, + title: title, + subtitle: "Artist", + artworkURL: artworkURL, + artworkIsCircle: true + ) + }) + + let albumsRow = CarouselRow.ViewModel( + id: "albums", + title: "Albums", + subtitle: nil, + items: r.albums.items.map { album in + let id = album.id + let title = album.name + let subtitle = album.artists[0].name + let artworkURL = album.getArtworkURL() + return CarouselRowItem.ViewModel( + id: id, + title: title, + subtitle: subtitle, + artworkURL: artworkURL + ) + }) + + let playlistsRow = CarouselRow.ViewModel( + id: "playlists", + title: "Playlists", + subtitle: nil, + items: r.playlists.items.map { playlist in + let id = playlist.id + let title = playlist.name + let subtitle = "\(playlist.tracks.total) tracks, by \(playlist.owner.displayName ?? "an unnamed user")" + let artworkURL = playlist.getArtworkURL() + return CarouselRowItem.ViewModel( + id: id, + title: title, + subtitle: subtitle, + artworkURL: artworkURL + ) + }) + + self.results = [artistsRow, albumsRow, playlistsRow] + } + } + .store(in: &cancellables) } } } @@ -44,6 +104,6 @@ extension Search { struct Search_Previews: PreviewProvider { static let vm = Search.ViewModel(searchTermPublisher: Just("Hello").eraseToAnyPublisher()) static var previews: some View { - Search(viewModel: vm) + Search(viewModel: vm, numItemsPerRow: 4) } }