diff --git a/firstfm.xcodeproj/project.pbxproj b/firstfm.xcodeproj/project.pbxproj index 0b09a98..7a47708 100644 --- a/firstfm.xcodeproj/project.pbxproj +++ b/firstfm.xcodeproj/project.pbxproj @@ -73,6 +73,9 @@ 82A006C526797C3E0009BD71 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82A006C426797C3E0009BD71 /* SearchView.swift */; }; 82B6CCDF26AB1795008AFDEF /* ProfileLoggedOutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B6CCDE26AB1795008AFDEF /* ProfileLoggedOutView.swift */; }; 82B6CCE126AB2832008AFDEF /* SrobblesLoggedOutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B6CCE026AB2832008AFDEF /* SrobblesLoggedOutView.swift */; }; + 82B6CCE326AB800B008AFDEF /* LastUserScrobblesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B6CCE226AB800B008AFDEF /* LastUserScrobblesView.swift */; }; + 82B6CCE526AB80B4008AFDEF /* TopUserArtistsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B6CCE426AB80B4008AFDEF /* TopUserArtistsView.swift */; }; + 82B6CCE726AB8B00008AFDEF /* ViewLoad.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B6CCE626AB8B00008AFDEF /* ViewLoad.swift */; }; 82BBD2462698B13B009B42FC /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BBD2452698B13B009B42FC /* Country.swift */; }; 82BBD2482698B37F009B42FC /* TrendsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BBD2472698B37F009B42FC /* TrendsViewModel.swift */; }; 82BBD24A2698C33D009B42FC /* TopCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BBD2492698C33D009B42FC /* TopCountryView.swift */; }; @@ -157,6 +160,9 @@ 82A006C426797C3E0009BD71 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; 82B6CCDE26AB1795008AFDEF /* ProfileLoggedOutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileLoggedOutView.swift; sourceTree = ""; }; 82B6CCE026AB2832008AFDEF /* SrobblesLoggedOutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SrobblesLoggedOutView.swift; sourceTree = ""; }; + 82B6CCE226AB800B008AFDEF /* LastUserScrobblesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastUserScrobblesView.swift; sourceTree = ""; }; + 82B6CCE426AB80B4008AFDEF /* TopUserArtistsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopUserArtistsView.swift; sourceTree = ""; }; + 82B6CCE626AB8B00008AFDEF /* ViewLoad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLoad.swift; sourceTree = ""; }; 82BBD2452698B13B009B42FC /* Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Country.swift; sourceTree = ""; }; 82BBD2472698B37F009B42FC /* TrendsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendsViewModel.swift; sourceTree = ""; }; 82BBD2492698C33D009B42FC /* TopCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopCountryView.swift; sourceTree = ""; }; @@ -303,6 +309,7 @@ children = ( 82C282E126A31CFD000E5F41 /* StringMD5.swift */, 825F0F8A26A5B6E1007BA84B /* NumberFormat.swift */, + 82B6CCE626AB8B00008AFDEF /* ViewLoad.swift */, ); path = Extension; sourceTree = ""; @@ -315,6 +322,8 @@ 82C282E926A3354E000E5F41 /* FriendRow.swift */, 825F0FA826A5F122007BA84B /* LogoutButton.swift */, 82B6CCDE26AB1795008AFDEF /* ProfileLoggedOutView.swift */, + 82B6CCE226AB800B008AFDEF /* LastUserScrobblesView.swift */, + 82B6CCE426AB80B4008AFDEF /* TopUserArtistsView.swift */, ); path = Profile; sourceTree = ""; @@ -556,6 +565,7 @@ 825F0F9526A5E938007BA84B /* ArtistStats.swift in Sources */, 825F0F9126A5E8F5007BA84B /* SimilarArtist.swift in Sources */, 82C282EC26A370D2000E5F41 /* ScobbledTrackViewModel.swift in Sources */, + 82B6CCE326AB800B008AFDEF /* LastUserScrobblesView.swift in Sources */, 82505CE02675074E00CCCB58 /* ChartListView.swift in Sources */, 825F0F9E26A5EAE7007BA84B /* SpotifyAlbum.swift in Sources */, 82D5B4D42696DC7800716931 /* RecentTracksResponse.swift in Sources */, @@ -582,6 +592,7 @@ 82A006B8267960D90009BD71 /* Track.swift in Sources */, 825F0FA026A5EC82007BA84B /* Session.swift in Sources */, 822E9A9026A9788900C5307B /* TagView.swift in Sources */, + 82B6CCE526AB80B4008AFDEF /* TopUserArtistsView.swift in Sources */, 82505CE72675300400CCCB58 /* SpotifyArtistSearchResponse.swift in Sources */, 82BBD2482698B37F009B42FC /* TrendsViewModel.swift in Sources */, 82F0D54D2697AAC7007CEA98 /* SearchViewModel.swift in Sources */, @@ -599,6 +610,7 @@ 825F0FC526A74539007BA84B /* TopArtistAlbumsView.swift in Sources */, 4A8C296126983B7300F55ECC /* LastFMAPI.swift in Sources */, 822E9A9626A97B7200C5307B /* TagViewModel.swift in Sources */, + 82B6CCE726AB8B00008AFDEF /* ViewLoad.swift in Sources */, 82BBD24A2698C33D009B42FC /* TopCountryView.swift in Sources */, 82B6CCDF26AB1795008AFDEF /* ProfileLoggedOutView.swift in Sources */, 820455DC267AA8E70009A418 /* LoginView.swift in Sources */, diff --git a/firstfm/ViewModel/ProfileViewModel.swift b/firstfm/ViewModel/ProfileViewModel.swift index 9df694b..1ea0a2d 100644 --- a/firstfm/ViewModel/ProfileViewModel.swift +++ b/firstfm/ViewModel/ProfileViewModel.swift @@ -7,6 +7,7 @@ class ProfileViewModel: ObservableObject { @Published var friends: [Friend] = [] @Published var scrobbles: [ScrobbledTrack] = [] @Published var topArtists: [Artist] = [] + @Published var scrobblesPeriodPicked: Int = 5 var isFriendsLoading = true // var isLoading = true @AppStorage("lastfm_username") var storedUsername: String? @@ -17,7 +18,7 @@ class ProfileViewModel: ObservableObject { self.getProfile(username) self.getUserScrobbles() - self.getTopArtists() + self.getTopArtists(period: "overall") } func getProfile(_ username: String) { @@ -114,9 +115,28 @@ class ProfileViewModel: ObservableObject { } } - func getTopArtists() { + func getTopArtistsForPeriodTag(tag: Int) { + // Reset artist to show placeholder + self.topArtists = [] + + switch tag { + case 0: + self.getTopArtists(period: "7day") + case 1: + self.getTopArtists(period: "1month") + case 2: + self.getTopArtists(period: "3month") + case 3: + self.getTopArtists(period: "6month") + case 4: + self.getTopArtists(period: "12month") + default: + self.getTopArtists(period: "overall") + } + } - LastFMAPI.request(lastFMMethod: "user.getTopArtists", args: ["user": storedUsername ?? "", "limit": "6", "period": "overall"]) { (data: UserTopArtistsResponse?, error) -> Void in + func getTopArtists(period: String) { + LastFMAPI.request(lastFMMethod: "user.getTopArtists", args: ["user": storedUsername ?? "", "limit": "6", "period": period]) { (data: UserTopArtistsResponse?, error) -> Void in if error != nil { DispatchQueue.main.async { @@ -133,21 +153,21 @@ class ProfileViewModel: ObservableObject { artists[index].image[0].url = "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp" } - DispatchQueue.main.async { - self.topArtists = artists -// self.isLoading = false - } - for (index, artist) in data.topartists.artist.enumerated() { // Get image URL for each artist and trigger a View update through the observed object SpotifyImage.findImage(type: "artist", name: artist.name) { imageURL in if let imageURL = imageURL { - DispatchQueue.main.async { - self.topArtists[index].image[0].url = imageURL - } +// DispatchQueue.main.async { + artists[index].image[0].url = imageURL +// } } } } + + DispatchQueue.main.async { + self.topArtists = artists +// self.isLoading = false + } } } } diff --git a/firstfm/Views/Profile/LastUserScrobblesView.swift b/firstfm/Views/Profile/LastUserScrobblesView.swift new file mode 100644 index 0000000..01cee00 --- /dev/null +++ b/firstfm/Views/Profile/LastUserScrobblesView.swift @@ -0,0 +1,59 @@ +import SwiftUI + +struct LastUserScrobblesView: View { + + var scrobbles: [ScrobbledTrack] = [] + + var body: some View { + List { + Section { + Text("Last scrobbles").font(.headline).unredacted() + if !scrobbles.isEmpty { + ForEach(scrobbles, id: \.name) {track in + NavigationLink( + destination: TrackView(track: Track(name: track.name, playcount: "0", listeners: "", url: "", artist: nil, image: track.image)), + label: { + ScrobbledTrackRow(track: track) + }) + } + } else { + // Placeholder for redacted + ForEach((1...5), id: \.self) {_ in + NavigationLink( + destination: Color(.red), + label: { + ScrobbledTrackRow(track: ScrobbledTrack( + name: "toto", + url: "123", + image: [ + LastFMImage( + url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", + size: "lol" + ), + LastFMImage( + url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", + size: "lol" + ), + LastFMImage( + url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", + size: "lol" + ), + LastFMImage( + url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", + size: "lol" + ) + ], + artist: ScrobbledArtist( + mbid: "", + name: "Artist" + ), + date: nil + )) + }) + } + } + } + } + .redacted(reason: scrobbles.isEmpty ? .placeholder : []) + } +} diff --git a/firstfm/Views/Profile/ProfileView.swift b/firstfm/Views/Profile/ProfileView.swift index 91e81ed..7f5e7a2 100644 --- a/firstfm/Views/Profile/ProfileView.swift +++ b/firstfm/Views/Profile/ProfileView.swift @@ -8,6 +8,7 @@ struct ProfileView: View { @State private var showingSettings = false @State private var showingFriends = false @State private var showingLovedTracks = false + @State private var scrobblesPeriodPicked: Int = 5 @ViewBuilder var body: some View { @@ -74,68 +75,24 @@ struct ProfileView: View { } .redacted(reason: profile.user == nil ? .placeholder : []) .frame(width: 350) - List { - Section { - Text("Last scrobbles").font(.headline).unredacted() - if !profile.scrobbles.isEmpty { - ForEach(profile.scrobbles, id: \.name) {track in - NavigationLink( - destination: TrackView(track: Track(name: track.name, playcount: "0", listeners: "", url: "", artist: nil, image: track.image)), - label: { - ScrobbledTrackRow(track: track) - }) - } - } else { - // Placeholder for redacted - ForEach((1...5), id: \.self) {_ in - NavigationLink( - destination: Color(.red), - label: { - ScrobbledTrackRow(track: ScrobbledTrack( - name: "toto", - url: "123", - image: [ - LastFMImage( - url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", - size: "lol" - ), - LastFMImage( - url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", - size: "lol" - ), - LastFMImage( - url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", - size: "lol" - ), - LastFMImage( - url: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp", - size: "lol" - ) - ], - artist: ScrobbledArtist( - mbid: "", - name: "Artist" - ), - date: nil - )) - }) - } - } - } - } - .redacted(reason: profile.scrobbles.isEmpty ? .placeholder : []) + + LastUserScrobblesView(scrobbles: profile.scrobbles) .frame( width: g.size.width - 5, height: g.size.height * 0.7, alignment: .center ) .offset(y: -70) + + TopUserArtistsView(artists: profile.topArtists) + .environmentObject(profile) + .offset(y: -70) } .edgesIgnoringSafeArea(.top) }.edgesIgnoringSafeArea(.top) .navigationBarTitle("") } - .onAppear { + .onLoad { if let username = storedUsername { self.profile.getAll(username) } diff --git a/firstfm/Views/Profile/TopUserArtistsView.swift b/firstfm/Views/Profile/TopUserArtistsView.swift new file mode 100644 index 0000000..50b503f --- /dev/null +++ b/firstfm/Views/Profile/TopUserArtistsView.swift @@ -0,0 +1,80 @@ +import SwiftUI +import Kingfisher + +struct TopUserArtistsView: View { + var artists: [Artist] +// @State private var scrobblesPeriodPicked: Int = 5 + @EnvironmentObject var vm: ProfileViewModel + + var body: some View { + Section { + HStack { + Text("Top artists").font(.headline) + Spacer() + + Menu { + Picker(selection: $vm.scrobblesPeriodPicked, label: Text("Period")) { + Text("Last 7 days").tag(0) + Text("Last 30 days").tag(1) + Text("Last 90 days").tag(2) + Text("Last 180 days").tag(3) + Text("Last 365 days").tag(4) + Text("All time").tag(5) + }.onChange(of: vm.scrobblesPeriodPicked) { + tag in vm.getTopArtistsForPeriodTag(tag: tag) + } + } + label: { + Label("", systemImage: "calendar").foregroundColor(.white) + } + }.unredacted() + LazyVGrid(columns: [ + GridItem(.flexible(minimum: 50, maximum: 200)), + GridItem(.flexible(minimum: 50, maximum: 200)) + ], spacing: 30 ) { + if !artists.isEmpty { + ForEach(artists, id: \.name) { artist in + NavigationLink( + destination: ArtistView(artist: artist), + label: { + VStack { + KFImage.url(URL(string: artist.image[0].url != "" ? artist.image[0].url : "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp" )!) + .resizable() + .loadImmediately() + .fade(duration: 0.5) + .cornerRadius(5) + .aspectRatio(contentMode: .fill) + Text(artist.name).font(.headline).lineLimit(1).foregroundColor(.white) + Text("\(Int(artist.playcount ?? "0")?.formatted() ?? "0") scrobbles") + .font(.subheadline) + .foregroundColor(.gray).lineLimit(1) + } + }) + + } + } else { + // Placeholder for redacted + ForEach((1...6), id: \.self) {_ in + NavigationLink( + destination: Color(.red), + label: { + VStack { + KFImage.url(URL(string: "https://lastfm.freetls.fastly.net/i/u/64s/4128a6eb29f94943c9d206c08e625904.webp" )!) + .resizable() + .loadImmediately() + .cornerRadius(5) + .aspectRatio(contentMode: .fill) + Text("Album name").font(.headline).lineLimit(1).foregroundColor(.white) + Text("\(String(format: "%ld", locale: Locale.current, 0) ) listeners") + .font(.subheadline) + .foregroundColor(.gray).lineLimit(1) + } + }) + } + + } + } + }.padding() + .redacted(reason: artists.isEmpty ? .placeholder : []) + } +}