From a2e6af5aa3a871b03d7510011b8bb5ff0753dad7 Mon Sep 17 00:00:00 2001 From: ludelanier Date: Thu, 6 Jun 2024 17:14:18 +0200 Subject: [PATCH] bind images and fix ranking row --- .../AllInApp/AllIn/Components/Friend.swift | 2 +- .../AllIn/Components/RankingRow.swift | 8 +-- .../AllInApp/AllIn/Components/UserInfo.swift | 7 +- .../AllIn/Components/userPicture.swift | 53 +++++++------- .../AllIn/Utils/asyncCachedImage.swift | 71 +++++++++++++++++++ .../AllInApp/AllIn/Views/DetailsView.swift | 3 +- Sources/AllInApp/AllIn/Views/MainView.swift | 2 +- .../AllInApp/AllIn/Views/RankingView.swift | 7 +- .../AllInApp.xcodeproj/project.pbxproj | 12 ++++ 9 files changed, 128 insertions(+), 37 deletions(-) create mode 100644 Sources/AllInApp/AllIn/Utils/asyncCachedImage.swift diff --git a/Sources/AllInApp/AllIn/Components/Friend.swift b/Sources/AllInApp/AllIn/Components/Friend.swift index 42ad66a..11e4641 100644 --- a/Sources/AllInApp/AllIn/Components/Friend.swift +++ b/Sources/AllInApp/AllIn/Components/Friend.swift @@ -29,7 +29,7 @@ struct Friend: View { var body: some View { HStack{ - UserPicture(picture: nil,username: user.username, size: 45) + UserPicture(picture: user.image,username: user.username, size: 45) Text(user.username) .fontWeight(.medium) .padding(.leading, 5) diff --git a/Sources/AllInApp/AllIn/Components/RankingRow.swift b/Sources/AllInApp/AllIn/Components/RankingRow.swift index 40e3e68..31469a0 100644 --- a/Sources/AllInApp/AllIn/Components/RankingRow.swift +++ b/Sources/AllInApp/AllIn/Components/RankingRow.swift @@ -10,7 +10,7 @@ import SwiftUI struct RankingRow: View { var number: Int - var image: String + var image: String? var pseudo: String var allCoins: Int @@ -18,12 +18,12 @@ struct RankingRow: View { HStack(){ Text(number.description) .textStyle(weight: .bold, color: AllInColors.lightPurpleColor, size: 18) - .padding(.leading, 15) - UserInfo(username: pseudo, value: allCoins) + UserInfo(username: pseudo, picture: image, value: allCoins) } + .padding(10) + .padding(.horizontal, 5) .background(AllInColors.componentBackgroundColor) .cornerRadius(8) - .padding([.leading,.trailing],20) .frame(maxWidth: 750) } } diff --git a/Sources/AllInApp/AllIn/Components/UserInfo.swift b/Sources/AllInApp/AllIn/Components/UserInfo.swift index 56b2411..abfa5b7 100644 --- a/Sources/AllInApp/AllIn/Components/UserInfo.swift +++ b/Sources/AllInApp/AllIn/Components/UserInfo.swift @@ -10,10 +10,11 @@ import Model struct UserInfo: View { var username: String + var picture: String? var value: Int var body: some View { HStack { - UserPicture(username: username, size: 35) + UserPicture(picture: picture, username: username, size: 35) .padding(.trailing, 7) Text(username) .font(.system(size: 15)) @@ -24,10 +25,10 @@ struct UserInfo: View { .font(.system(size: 18)) .foregroundStyle(AllInColors.lightPurpleColor) .fontWeight(.bold) - .padding(.trailing, 8) + .padding(.trailing, 4) Image("PurpleAllCoin") .resizable() - .frame(width: 11, height: 12) + .frame(width: 15, height: 16) } } } diff --git a/Sources/AllInApp/AllIn/Components/userPicture.swift b/Sources/AllInApp/AllIn/Components/userPicture.swift index 396f06e..98ac158 100644 --- a/Sources/AllInApp/AllIn/Components/userPicture.swift +++ b/Sources/AllInApp/AllIn/Components/userPicture.swift @@ -6,41 +6,46 @@ // import SwiftUI - struct UserPicture: View { var picture: String? var username: String var size: CGFloat + var body: some View { ZStack { - if picture != nil { - AsyncImage( - url: URL(string:picture!), - content: { image in - image - .resizable() - .aspectRatio(contentMode: .fit) - .clipShape(Circle()) - }, - placeholder: { - ProgressView() - } - ) + if let pictureURL = picture { + userImage(url: pictureURL) } else { - Circle() - .foregroundColor(.gray) - .frame(width: size, height: size) - .overlay( - Text(username.prefix(2).uppercased()) - .fontWeight(.medium) - .foregroundStyle(.white) - .font(.system(size: fontSize(for: size))) - ) + placeholderImage } } } - func fontSize(for diameter: CGFloat) -> CGFloat { + @MainActor private func userImage(url: String) -> some View { + AsyncCachedImage(url: URL(string: url)) { image in + image + .resizable() + .frame(width: size, height: size) + .clipShape(Circle()) + } placeholder: { + placeholderImage + } + } + + + private var placeholderImage: some View { + Circle() + .foregroundColor(.gray) + .frame(width: size, height: size) + .overlay( + Text(username.prefix(2).uppercased()) + .fontWeight(.medium) + .foregroundStyle(.white) + .font(.system(size: fontSize(for: size))) + ) + } + + private func fontSize(for diameter: CGFloat) -> CGFloat { let fontScaleFactor: CGFloat = 0.45 return diameter * fontScaleFactor } diff --git a/Sources/AllInApp/AllIn/Utils/asyncCachedImage.swift b/Sources/AllInApp/AllIn/Utils/asyncCachedImage.swift new file mode 100644 index 0000000..923d524 --- /dev/null +++ b/Sources/AllInApp/AllIn/Utils/asyncCachedImage.swift @@ -0,0 +1,71 @@ +// +// asyncCachedImage.swift +// AllIn +// +// Created by Lucas Delanier on 06/06/2024. +// + +import SwiftUI + +@MainActor +struct AsyncCachedImage: View { + // Input dependencies + var url: URL? + @ViewBuilder var content: (Image) -> ImageView + @ViewBuilder var placeholder: () -> PlaceholderView + + // Downloaded image + @State var image: UIImage? = nil + + init( + url: URL?, + @ViewBuilder content: @escaping (Image) -> ImageView, + @ViewBuilder placeholder: @escaping () -> PlaceholderView + ) { + self.url = url + self.content = content + self.placeholder = placeholder + } + + var body: some View { + VStack { + if let uiImage = image { + content(Image(uiImage: uiImage)) + } else { + placeholder() + .onAppear { + Task { + image = await downloadPhoto() + } + } + } + } + } + + // Downloads if the image is not cached already + // Otherwise returns from the cache + private func downloadPhoto() async -> UIImage? { + do { + guard let url else { return nil } + + // Check if the image is cached already + if let cachedResponse = URLCache.shared.cachedResponse(for: .init(url: url)) { + return UIImage(data: cachedResponse.data) + } else { + let (data, response) = try await URLSession.shared.data(from: url) + + // Save returned image data into the cache + URLCache.shared.storeCachedResponse(.init(response: response, data: data), for: .init(url: url)) + + guard let image = UIImage(data: data) else { + return nil + } + + return image + } + } catch { + print("Error downloading: \(error)") + return nil + } + } +} diff --git a/Sources/AllInApp/AllIn/Views/DetailsView.swift b/Sources/AllInApp/AllIn/Views/DetailsView.swift index 53e97dd..09bcb2b 100644 --- a/Sources/AllInApp/AllIn/Views/DetailsView.swift +++ b/Sources/AllInApp/AllIn/Views/DetailsView.swift @@ -122,7 +122,8 @@ struct DetailsView: View { .padding(.bottom, 10) ScrollView(showsIndicators: false) { ForEach(viewModel.betDetail?.participations ?? []) { participation in - UserInfo(username: participation.username, value: participation.stake).padding(.horizontal, 10) + // TODO + UserInfo(username: participation.username, picture: nil , value: participation.stake).padding(.horizontal, 10) } } .padding(.bottom, geometry.safeAreaInsets.bottom + 28) diff --git a/Sources/AllInApp/AllIn/Views/MainView.swift b/Sources/AllInApp/AllIn/Views/MainView.swift index 6d07610..d4efc7c 100644 --- a/Sources/AllInApp/AllIn/Views/MainView.swift +++ b/Sources/AllInApp/AllIn/Views/MainView.swift @@ -60,8 +60,8 @@ struct MainView: View { if self.showMenu { Menu() - .frame(width: geometry.size.width*0.83) .transition(.move(edge: .leading)) + .frame(width: geometry.size.width*0.83) } } diff --git a/Sources/AllInApp/AllIn/Views/RankingView.swift b/Sources/AllInApp/AllIn/Views/RankingView.swift index 85bb797..cb00a24 100644 --- a/Sources/AllInApp/AllIn/Views/RankingView.swift +++ b/Sources/AllInApp/AllIn/Views/RankingView.swift @@ -51,7 +51,7 @@ struct RankingView: View { .cornerRadius(41.5, corners: .topLeft) .cornerRadius(8, corners: .topRight) .cornerRadius(15, corners: [.bottomLeft, .bottomRight]) - UserPicture(picture: nil, username: viewModel.friends[0].username, size: 70) + UserPicture(picture: viewModel.friends[0].image, username: viewModel.friends[0].username, size: 70) .offset(x: 0, y: -55) Text("1") @@ -95,7 +95,7 @@ struct RankingView: View { .cornerRadius(8, corners: .topLeft) .cornerRadius(15, corners: [.bottomLeft, .bottomRight]) - UserPicture(picture: nil, username: viewModel.friends[1].username, size: 50) + UserPicture(picture: viewModel.friends[1].image, username: viewModel.friends[1].username, size: 50) .offset(x: 0, y: -50) Text("2") @@ -122,13 +122,14 @@ struct RankingView: View { let friend = viewModel.friends[index] RankingRow( number: index + 1, - image: "defaultUserImage", + image: friend.image, pseudo: friend.username, allCoins: friend.nbCoins ) } } .padding(.top, 10) + .padding(.horizontal, 20) } Spacer() } diff --git a/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj b/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj index 37e36fc..09d030d 100644 --- a/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj +++ b/Sources/AllInApp/AllInApp.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 123590B62B5537E200F7AEBD /* ResultBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123590B52B5537E200F7AEBD /* ResultBanner.swift */; }; 123590B82B5541BA00F7AEBD /* ParticipateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123590B72B5541BA00F7AEBD /* ParticipateButton.swift */; }; 123F31DB2C0F26E8009B6D65 /* userPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123F31DA2C0F26E8009B6D65 /* userPicture.swift */; }; + 123F31E02C11E99E009B6D65 /* asyncCachedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123F31DF2C11E99E009B6D65 /* asyncCachedImage.swift */; }; 1244EF602B4EC31E00374ABF /* HistoricBetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1244EF5F2B4EC31E00374ABF /* HistoricBetView.swift */; }; 1244EF622B4EC67000374ABF /* ReviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1244EF612B4EC67000374ABF /* ReviewCard.swift */; }; 129D051D2B6E7FF0003D3E08 /* OddCapsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129D051C2B6E7FF0003D3E08 /* OddCapsule.swift */; }; @@ -154,6 +155,7 @@ 123590B52B5537E200F7AEBD /* ResultBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultBanner.swift; sourceTree = ""; }; 123590B72B5541BA00F7AEBD /* ParticipateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateButton.swift; sourceTree = ""; }; 123F31DA2C0F26E8009B6D65 /* userPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = userPicture.swift; sourceTree = ""; }; + 123F31DF2C11E99E009B6D65 /* asyncCachedImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = asyncCachedImage.swift; sourceTree = ""; }; 1244EF5F2B4EC31E00374ABF /* HistoricBetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoricBetView.swift; sourceTree = ""; }; 1244EF612B4EC67000374ABF /* ReviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewCard.swift; sourceTree = ""; }; 129D051C2B6E7FF0003D3E08 /* OddCapsule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OddCapsule.swift; sourceTree = ""; }; @@ -274,6 +276,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 123F31DE2C11E979009B6D65 /* Utils */ = { + isa = PBXGroup; + children = ( + 123F31DF2C11E99E009B6D65 /* asyncCachedImage.swift */, + ); + path = Utils; + sourceTree = ""; + }; EC1D15402B715A7A0094833E /* Protocols */ = { isa = PBXGroup; children = ( @@ -309,6 +319,7 @@ EC6B969A2B24B4CC00FC1C58 /* AllIn */ = { isa = PBXGroup; children = ( + 123F31DE2C11E979009B6D65 /* Utils */, ECF872852B93B9D900F9D240 /* AllIn.entitlements */, EC7EF7462B87E3470022B5D9 /* QuickActions */, ECB7BC662B2F1AAD002A6654 /* ViewModels */, @@ -676,6 +687,7 @@ EC6B969C2B24B4CC00FC1C58 /* AllInApp.swift in Sources */, 123590B42B51792000F7AEBD /* DetailsView.swift in Sources */, ECB26A192B40744F00FE06B3 /* RankingViewModel.swift in Sources */, + 123F31E02C11E99E009B6D65 /* asyncCachedImage.swift in Sources */, EC650A502B2793D5003AFCAD /* TextCapsule.swift in Sources */, EC650A482B25DCFF003AFCAD /* UsersPreview.swift in Sources */, EC17A15E2B6A955E008A8679 /* CurrentBetView.swift in Sources */, -- 2.36.3