From 88092bbb97c42b6025e4adba1d2e2894656fb995 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 9 Jul 2021 00:18:15 +0200 Subject: [PATCH] feat: init artist search --- firstfm.xcodeproj/project.pbxproj | 8 ++ firstfm/Data/Entities/API/ArtistSearch.swift | 22 ++++++ firstfm/Data/Entities/Artist.swift | 2 +- firstfm/Data/ViewModel/ChartViewModel.swift | 23 +++++- .../Data/ViewModel/ScrobblesViewModel.swift | 1 - firstfm/Data/ViewModel/SearchViewModel.swift | 79 +++++++++++++++++++ firstfm/Views/SearchView.swift | 18 ++++- 7 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 firstfm/Data/Entities/API/ArtistSearch.swift create mode 100644 firstfm/Data/ViewModel/SearchViewModel.swift diff --git a/firstfm.xcodeproj/project.pbxproj b/firstfm.xcodeproj/project.pbxproj index ea9ea79..ce92755 100644 --- a/firstfm.xcodeproj/project.pbxproj +++ b/firstfm.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ 82D5B4D42696DC7800716931 /* RecentTracks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82D5B4D32696DC7800716931 /* RecentTracks.swift */; }; 82D5B4D62696E0E100716931 /* ScrobbledTrackRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82D5B4D52696E0E100716931 /* ScrobbledTrackRowView.swift */; }; 82E80C39269777C60098DC3C /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = 82E80C38269777C60098DC3C /* SwiftUIRefresh */; }; + 82F0D54D2697AAC7007CEA98 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F0D54C2697AAC7007CEA98 /* SearchViewModel.swift */; }; + 82F0D54F2697AC66007CEA98 /* ArtistSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F0D54E2697AC66007CEA98 /* ArtistSearch.swift */; }; 82FBAE472674B8FC000D8E29 /* Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82FBAE462674B8FC000D8E29 /* Artist.swift */; }; 82FBAE492674BB55000D8E29 /* ChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82FBAE482674BB55000D8E29 /* ChartViewModel.swift */; }; 82FBAE4C2674BCB7000D8E29 /* ArtistResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82FBAE4B2674BCB7000D8E29 /* ArtistResponse.swift */; }; @@ -76,6 +78,8 @@ 82D5B4D12696DBD700716931 /* ScrobblesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrobblesViewModel.swift; sourceTree = ""; }; 82D5B4D32696DC7800716931 /* RecentTracks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentTracks.swift; sourceTree = ""; }; 82D5B4D52696E0E100716931 /* ScrobbledTrackRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrobbledTrackRowView.swift; sourceTree = ""; }; + 82F0D54C2697AAC7007CEA98 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = ""; }; + 82F0D54E2697AC66007CEA98 /* ArtistSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistSearch.swift; sourceTree = ""; }; 82FBAE462674B8FC000D8E29 /* Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Artist.swift; sourceTree = ""; }; 82FBAE482674BB55000D8E29 /* ChartViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartViewModel.swift; sourceTree = ""; }; 82FBAE4B2674BCB7000D8E29 /* ArtistResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistResponse.swift; sourceTree = ""; }; @@ -192,6 +196,7 @@ 82A006BB2679631A0009BD71 /* TopTracksResponseContainer.swift */, 821493A32694C427007A21C8 /* UserInfoResponse.swift */, 82D5B4D32696DC7800716931 /* RecentTracks.swift */, + 82F0D54E2697AC66007CEA98 /* ArtistSearch.swift */, ); path = API; sourceTree = ""; @@ -203,6 +208,7 @@ 820455DF267ABC930009A418 /* AuthViewModel.swift */, 8214939F2694C349007A21C8 /* UserViewModel.swift */, 82D5B4D12696DBD700716931 /* ScrobblesViewModel.swift */, + 82F0D54C2697AAC7007CEA98 /* SearchViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -319,7 +325,9 @@ 82A006B8267960D90009BD71 /* Track.swift in Sources */, 82A006BC2679631A0009BD71 /* TopTracksResponseContainer.swift in Sources */, 82505CE72675300400CCCB58 /* ArtistSearchResponse.swift in Sources */, + 82F0D54D2697AAC7007CEA98 /* SearchViewModel.swift in Sources */, 82D5B4D22696DBD700716931 /* ScrobblesViewModel.swift in Sources */, + 82F0D54F2697AC66007CEA98 /* ArtistSearch.swift in Sources */, 82A006BE2679636F0009BD71 /* TrackSearchResponse.swift in Sources */, 820455E0267ABC930009A418 /* AuthViewModel.swift in Sources */, 820455DC267AA8E70009A418 /* LoginView.swift in Sources */, diff --git a/firstfm/Data/Entities/API/ArtistSearch.swift b/firstfm/Data/Entities/API/ArtistSearch.swift new file mode 100644 index 0000000..b99e81c --- /dev/null +++ b/firstfm/Data/Entities/API/ArtistSearch.swift @@ -0,0 +1,22 @@ +// +// ArtistSearch.swift +// firstfm +// +// Created by Stanislas Lange on 08/07/2021. +// + +import Foundation + +struct ArtistSearchResponse: Codable { + let results: ArtistSearchResult +} + +// MARK: - Results +struct ArtistSearchResult: Codable { + let artistmatches: Artistmatches +} + +// MARK: - Artistmatches +struct Artistmatches: Codable { + let artist: [Artist] +} diff --git a/firstfm/Data/Entities/Artist.swift b/firstfm/Data/Entities/Artist.swift index 7ab8494..128751e 100644 --- a/firstfm/Data/Entities/Artist.swift +++ b/firstfm/Data/Entities/Artist.swift @@ -11,7 +11,7 @@ struct Artist: Codable, Identifiable { var mbid: String var name: String - var playcount: String + var playcount: String? var listeners: String var image: [LastFMImage] } diff --git a/firstfm/Data/ViewModel/ChartViewModel.swift b/firstfm/Data/ViewModel/ChartViewModel.swift index cf81c3c..a010f6c 100644 --- a/firstfm/Data/ViewModel/ChartViewModel.swift +++ b/firstfm/Data/ViewModel/ChartViewModel.swift @@ -48,8 +48,16 @@ class ChartViewModel: ObservableObject { do{ let jsonResponse = try JSONDecoder().decode(ArtistResponse.self, from: data) + var artists = jsonResponse.artists.artist + + for (index, _) in artists.enumerated() { + if artists[index].image[0].url == "" { + artists[index].image[0].url = "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp" + } + } + DispatchQueue.main.async { - self.artists = jsonResponse.artists.artist + self.artists = artists // Let's stop the loader, the images will be loaded aynchronously self.isLoading = false } @@ -105,7 +113,18 @@ class ChartViewModel: ObservableObject { let jsonResponse = try JSONDecoder().decode(SpotifyArtistSearchResponse.self, from: data) // TODO: match image sizes - completion(jsonResponse.artists.items[0].images[0].url) + if jsonResponse.artists.items.count > 0 { + if jsonResponse.artists.items[0].images.count > 0 { + print(jsonResponse.artists.items[0].images[0].url) + completion(jsonResponse.artists.items[0].images[0].url) + } else { + completion("https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp") + } + } else { + completion("https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp") + } + + } } } diff --git a/firstfm/Data/ViewModel/ScrobblesViewModel.swift b/firstfm/Data/ViewModel/ScrobblesViewModel.swift index 1a6e6da..d306a7b 100644 --- a/firstfm/Data/ViewModel/ScrobblesViewModel.swift +++ b/firstfm/Data/ViewModel/ScrobblesViewModel.swift @@ -122,7 +122,6 @@ class ScrobblesViewModel: ObservableObject { // TODO: match image sizes if jsonResponse.tracks.items.count > 0 { if jsonResponse.tracks.items[0].album.images.count > 0 { - print("ok") print(jsonResponse.tracks.items[0].album.images[0].url) completion(jsonResponse.tracks.items[0].album.images[0].url) } diff --git a/firstfm/Data/ViewModel/SearchViewModel.swift b/firstfm/Data/ViewModel/SearchViewModel.swift new file mode 100644 index 0000000..ec69621 --- /dev/null +++ b/firstfm/Data/ViewModel/SearchViewModel.swift @@ -0,0 +1,79 @@ +// +// SearchViewModel.swift +// firstfm +// +// Created by Stanislas Lange on 08/07/2021. +// + +import Foundation + +class SearchViewModel: ObservableObject { + @Published var artists: [Artist] = [] + var isLoading = false + + // swiftlint:disable force_cast + let lastFMAPIKey = Bundle.main.object(forInfoDictionaryKey: "LastFMAPIKey") as! String + + func searchForArtist(artist: String) { + self.isLoading = true + + var request = URLRequest(url: URL(string: "https://ws.audioscrobbler.com/2.0/?format=json")!) + + let data : Data = "api_key=\(lastFMAPIKey)&method=artist.search&artist=\(artist)".data(using: .utf8)! + + request.httpMethod = "POST" + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type"); + request.httpBody = data + + URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in + do { + if let response = response { + let nsHTTPResponse = response as? HTTPURLResponse + if let statusCode = nsHTTPResponse?.statusCode { + print ("status code = \(statusCode)") + } + // TODO + } + if let error = error { + print (error) + // TODO + } + if let data = data { + do{ + let jsonResponse = try JSONDecoder().decode(ArtistSearchResponse.self, from: data) + + + var artists = jsonResponse.results.artistmatches.artist + + for (index, _) in artists.enumerated() { + if artists[index].image[0].url == "" { + artists[index].image[0].url = "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp" + } + } + + DispatchQueue.main.async { + self.artists = artists + // Let's stop the loader, the images will be loaded aynchronously + self.isLoading = false + } + + for (index, artist) in jsonResponse.results.artistmatches.artist.enumerated() { + // Get image URL for each artist and trigger a View update through the observed object + ChartViewModel().getImageForArtist(artistName: artist.name) { imageURL in + if let imageURL = imageURL { + DispatchQueue.main.async { + self.artists[index].image[0].url = imageURL + } + } + } + } + } + } + } + catch { + print(error) + // TODO + } + }.resume() + } +} diff --git a/firstfm/Views/SearchView.swift b/firstfm/Views/SearchView.swift index b073d0a..9245629 100644 --- a/firstfm/Views/SearchView.swift +++ b/firstfm/Views/SearchView.swift @@ -8,7 +8,7 @@ import SwiftUI struct SearchView: View { - + @ObservedObject var search = SearchViewModel() @State var searchString: String = "" var body: some View { @@ -22,12 +22,24 @@ struct SearchView: View { Image(systemName: "magnifyingglass") } }.padding() - Spacer() + List { + ForEach(search.artists) { artist in + ZStack { + Button("") {} + NavigationLink( + destination: ArtistView(artist: artist), + label: { + ArtistRow(artist: artist) + }) + } + } + } } } func performSearch() { - + print("searching for \(searchString)") + self.search.searchForArtist(artist: searchString) } }