Account detail: Add also followed by section

This commit is contained in:
Thomas Ricouard 2022-12-22 12:26:11 +01:00
parent 084dd18362
commit fc77dd14fe
5 changed files with 94 additions and 25 deletions

View file

@ -34,8 +34,10 @@ public struct AccountDetailView: View {
ScrollViewOffsetReader { offset in ScrollViewOffsetReader { offset in
self.scrollOffset = offset self.scrollOffset = offset
} content: { } content: {
LazyVStack { LazyVStack(alignment: .leading) {
headerView headerView
familliarFollowers
.offset(y: -36)
featuredTagsView featuredTagsView
.offset(y: -36) .offset(y: -36)
if isCurrentUser { 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 { private var fieldSheetView: some View {
NavigationStack { NavigationStack {
List { List {

View file

@ -50,6 +50,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
@Published var followedTags: [Tag] = [] @Published var followedTags: [Tag] = []
@Published var featuredTags: [FeaturedTag] = [] @Published var featuredTags: [FeaturedTag] = []
@Published var fields: [Account.Field] = [] @Published var fields: [Account.Field] = []
@Published var familliarFollowers: [Account] = []
@Published var selectedTab = Tab.statuses { @Published var selectedTab = Tab.statuses {
didSet { didSet {
reloadTabState() reloadTabState()
@ -77,18 +78,25 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
func fetchAccount() async { func fetchAccount() async {
guard let client else { return } guard let client else { return }
do { do {
let account: Account = try await client.get(endpoint: Accounts.accounts(id: accountId)) async let account: Account = client.get(endpoint: Accounts.accounts(id: accountId))
self.fields = account.fields async let followedTags: [Tag] = client.get(endpoint: Accounts.followedTags)
if isCurrentUser { async let relationships: [Relationshionship] = client.get(endpoint: Accounts.relationships(id: accountId))
self.followedTags = try await client.get(endpoint: Accounts.followedTags) async let featuredTags: [FeaturedTag] = client.get(endpoint: Accounts.featuredTags(id: accountId))
} else { async let familliarFollowers: [FamilliarAccounts] = client.get(endpoint: Accounts.familiarFollowers(withAccount: accountId))
let relationships: [Relationshionship] = try await client.get(endpoint: Accounts.relationships(id: accountId)) let loadedAccount = try await account
self.relationship = relationships.first self.featuredTags = try await featuredTags
}
self.featuredTags = try await client.get(endpoint: Accounts.featuredTags(id: accountId))
self.featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt } self.featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt }
self.title = account.displayName self.fields = loadedAccount.fields
accountState = .data(account: account) 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 { } catch {
accountState = .error(error: error) accountState = .error(error: error)
} }

View file

@ -1,32 +1,56 @@
import SwiftUI import SwiftUI
public struct AvatarView: View { 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 @Environment(\.redactionReasons) private var reasons
public let url: URL public let url: URL
public let size: Size
public init(url: URL) { public init(url: URL, size: Size = .profile) {
self.url = url self.url = url
self.size = size
} }
public var body: some View { public var body: some View {
if reasons == .placeholder { if reasons == .placeholder {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: size == .profile ? 4 : size.size.width / 2)
.fill(.gray) .fill(.gray)
.frame(maxWidth: 40, maxHeight: 40) .frame(maxWidth: size.size.width, maxHeight: size.size.height)
} else { } else {
AsyncImage( AsyncImage(url: url) { phase in
url: url, switch phase {
content: { image in 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() image.resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.cornerRadius(4) .cornerRadius(size == .profile ? 4 : size.size.width / 2)
.frame(maxWidth: 40, maxHeight: 40) .frame(maxWidth: size.size.width, maxHeight: size.size.height)
}, case .failure:
placeholder: { EmptyView()
ProgressView() @unknown default:
.frame(maxWidth: 40, maxHeight: 40) EmptyView()
} }
) }
} }
} }
} }

View file

@ -49,3 +49,8 @@ public struct Account: Codable, Identifiable, Equatable, Hashable {
emojis: []) emojis: [])
} }
} }
public struct FamilliarAccounts: Codable {
public let id: String
public let accounts: [Account]
}

View file

@ -10,6 +10,7 @@ public enum Accounts: Endpoint {
case relationships(id: String) case relationships(id: String)
case follow(id: String) case follow(id: String)
case unfollow(id: String) case unfollow(id: String)
case familiarFollowers(withAccount: String)
public func path() -> String { public func path() -> String {
switch self { switch self {
@ -31,6 +32,8 @@ public enum Accounts: Endpoint {
return "accounts/\(id)/follow" return "accounts/\(id)/follow"
case .unfollow(let id): case .unfollow(let id):
return "accounts/\(id)/unfollow" return "accounts/\(id)/unfollow"
case .familiarFollowers:
return "accounts/familiar_followers"
} }
} }
@ -47,6 +50,8 @@ public enum Accounts: Endpoint {
return params return params
case let .relationships(id): case let .relationships(id):
return [.init(name: "id", value: id)] return [.init(name: "id", value: id)]
case let .familiarFollowers(withAccount):
return [.init(name: "id[]", value: withAccount)]
default: default:
return nil return nil
} }