From fc77dd14fe0bc5e34b0dec14708b94c2527997af Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Thu, 22 Dec 2022 12:26:11 +0100 Subject: [PATCH] Account detail: Add also followed by section --- .../Sources/Account/AccountDetailView.swift | 29 ++++++++++- .../Account/AccountDetailViewModel.swift | 30 +++++++---- .../DesignSystem/Views/AvatarView.swift | 50 ++++++++++++++----- Packages/Models/Sources/Models/Account.swift | 5 ++ .../Sources/Network/Endpoint/Accounts.swift | 5 ++ 5 files changed, 94 insertions(+), 25 deletions(-) diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index 8ce07aed..4c6d99ce 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -34,8 +34,10 @@ public struct AccountDetailView: View { ScrollViewOffsetReader { offset in self.scrollOffset = offset } content: { - LazyVStack { + LazyVStack(alignment: .leading) { headerView + familliarFollowers + .offset(y: -36) featuredTagsView .offset(y: -36) if isCurrentUser { @@ -151,6 +153,31 @@ public struct AccountDetailView: View { } } + @ViewBuilder + private var familliarFollowers: some View { + if !viewModel.familliarFollowers.isEmpty { + VStack(alignment: .leading, spacing: 0) { + Text("Also followed by") + .font(.headline) + .padding(.leading, DS.Constants.layoutPadding) + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 0) { + ForEach(viewModel.familliarFollowers) { account in + AvatarView(url: account.avatar, size: .badge) + .onTapGesture { + routeurPath.navigate(to: .accountDetailWithAccount(account: account)) + } + .padding(.leading, -4) + } + } + .padding(.leading, DS.Constants.layoutPadding + 4) + } + } + .padding(.top, 2) + .padding(.bottom, 12) + } + } + private var fieldSheetView: some View { NavigationStack { List { diff --git a/Packages/Account/Sources/Account/AccountDetailViewModel.swift b/Packages/Account/Sources/Account/AccountDetailViewModel.swift index d4f55ebf..9aaebc12 100644 --- a/Packages/Account/Sources/Account/AccountDetailViewModel.swift +++ b/Packages/Account/Sources/Account/AccountDetailViewModel.swift @@ -50,6 +50,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { @Published var followedTags: [Tag] = [] @Published var featuredTags: [FeaturedTag] = [] @Published var fields: [Account.Field] = [] + @Published var familliarFollowers: [Account] = [] @Published var selectedTab = Tab.statuses { didSet { reloadTabState() @@ -77,18 +78,25 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher { func fetchAccount() async { guard let client else { return } do { - let account: Account = try await client.get(endpoint: Accounts.accounts(id: accountId)) - self.fields = account.fields - if isCurrentUser { - self.followedTags = try await client.get(endpoint: Accounts.followedTags) - } else { - let relationships: [Relationshionship] = try await client.get(endpoint: Accounts.relationships(id: accountId)) - self.relationship = relationships.first - } - self.featuredTags = try await client.get(endpoint: Accounts.featuredTags(id: accountId)) + async let account: Account = client.get(endpoint: Accounts.accounts(id: accountId)) + async let followedTags: [Tag] = client.get(endpoint: Accounts.followedTags) + async let relationships: [Relationshionship] = client.get(endpoint: Accounts.relationships(id: accountId)) + async let featuredTags: [FeaturedTag] = client.get(endpoint: Accounts.featuredTags(id: accountId)) + async let familliarFollowers: [FamilliarAccounts] = client.get(endpoint: Accounts.familiarFollowers(withAccount: accountId)) + let loadedAccount = try await account + self.featuredTags = try await featuredTags self.featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt } - self.title = account.displayName - accountState = .data(account: account) + self.fields = loadedAccount.fields + self.title = loadedAccount.displayName + if isCurrentUser { + self.followedTags = try await followedTags + } else { + let relationships = try await relationships + self.relationship = relationships.first + self.familliarFollowers = try await familliarFollowers.first?.accounts ?? [] + } + self.account = loadedAccount + accountState = .data(account: loadedAccount) } catch { accountState = .error(error: error) } diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift index fa706430..6c5ab986 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift @@ -1,32 +1,56 @@ import SwiftUI public struct AvatarView: View { + public enum Size { + case profile, badge + + var size: CGSize { + switch self { + case .profile: + return .init(width: 40, height: 40) + case .badge: + return .init(width: 28, height: 28) + } + } + } + @Environment(\.redactionReasons) private var reasons public let url: URL + public let size: Size - public init(url: URL) { + public init(url: URL, size: Size = .profile) { self.url = url + self.size = size } public var body: some View { if reasons == .placeholder { - RoundedRectangle(cornerRadius: 4) + RoundedRectangle(cornerRadius: size == .profile ? 4 : size.size.width / 2) .fill(.gray) - .frame(maxWidth: 40, maxHeight: 40) + .frame(maxWidth: size.size.width, maxHeight: size.size.height) } else { - AsyncImage( - url: url, - content: { image in + AsyncImage(url: url) { phase in + switch phase { + case .empty: + if size == .badge { + Circle() + .fill(.gray) + .frame(maxWidth: size.size.width, maxHeight: size.size.height) + } else { + ProgressView() + .frame(maxWidth: size.size.width, maxHeight: size.size.height) + } + case let .success(image): image.resizable() .aspectRatio(contentMode: .fit) - .cornerRadius(4) - .frame(maxWidth: 40, maxHeight: 40) - }, - placeholder: { - ProgressView() - .frame(maxWidth: 40, maxHeight: 40) + .cornerRadius(size == .profile ? 4 : size.size.width / 2) + .frame(maxWidth: size.size.width, maxHeight: size.size.height) + case .failure: + EmptyView() + @unknown default: + EmptyView() } - ) + } } } } diff --git a/Packages/Models/Sources/Models/Account.swift b/Packages/Models/Sources/Models/Account.swift index 57c9a618..85446dec 100644 --- a/Packages/Models/Sources/Models/Account.swift +++ b/Packages/Models/Sources/Models/Account.swift @@ -49,3 +49,8 @@ public struct Account: Codable, Identifiable, Equatable, Hashable { emojis: []) } } + +public struct FamilliarAccounts: Codable { + public let id: String + public let accounts: [Account] +} diff --git a/Packages/Network/Sources/Network/Endpoint/Accounts.swift b/Packages/Network/Sources/Network/Endpoint/Accounts.swift index 8f2dfd08..c57d66bd 100644 --- a/Packages/Network/Sources/Network/Endpoint/Accounts.swift +++ b/Packages/Network/Sources/Network/Endpoint/Accounts.swift @@ -10,6 +10,7 @@ public enum Accounts: Endpoint { case relationships(id: String) case follow(id: String) case unfollow(id: String) + case familiarFollowers(withAccount: String) public func path() -> String { switch self { @@ -31,6 +32,8 @@ public enum Accounts: Endpoint { return "accounts/\(id)/follow" case .unfollow(let id): return "accounts/\(id)/unfollow" + case .familiarFollowers: + return "accounts/familiar_followers" } } @@ -47,6 +50,8 @@ public enum Accounts: Endpoint { return params case let .relationships(id): return [.init(name: "id", value: id)] + case let .familiarFollowers(withAccount): + return [.init(name: "id[]", value: withAccount)] default: return nil }