2022-12-18 19:30:19 +00:00
|
|
|
import DesignSystem
|
2023-01-17 10:36:01 +00:00
|
|
|
import EmojiText
|
2022-12-22 09:53:36 +00:00
|
|
|
import Env
|
2023-01-17 10:36:01 +00:00
|
|
|
import Models
|
2022-12-25 06:43:02 +00:00
|
|
|
import NukeUI
|
2023-01-17 10:36:01 +00:00
|
|
|
import Shimmer
|
|
|
|
import SwiftUI
|
2022-12-17 12:37:46 +00:00
|
|
|
|
|
|
|
struct AccountDetailHeaderView: View {
|
2023-02-12 15:13:57 +00:00
|
|
|
enum Constants {
|
|
|
|
static let headerHeight: CGFloat = 200
|
|
|
|
}
|
2023-02-12 15:29:41 +00:00
|
|
|
|
2022-12-24 14:09:17 +00:00
|
|
|
@EnvironmentObject private var theme: Theme
|
2022-12-22 09:56:24 +00:00
|
|
|
@EnvironmentObject private var quickLook: QuickLook
|
2023-01-17 14:14:50 +00:00
|
|
|
@EnvironmentObject private var routerPath: RouterPath
|
2023-01-25 12:02:28 +00:00
|
|
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
2022-12-17 12:37:46 +00:00
|
|
|
@Environment(\.redactionReasons) private var reasons
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-12 06:36:19 +00:00
|
|
|
@ObservedObject var viewModel: AccountDetailViewModel
|
2022-12-20 08:37:07 +00:00
|
|
|
let account: Account
|
2022-12-27 08:11:12 +00:00
|
|
|
let scrollViewProxy: ScrollViewProxy?
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-17 12:37:46 +00:00
|
|
|
var body: some View {
|
|
|
|
VStack(alignment: .leading) {
|
2023-01-30 06:13:38 +00:00
|
|
|
Rectangle()
|
2023-02-12 15:13:57 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
2023-01-30 06:13:38 +00:00
|
|
|
.overlay {
|
2023-01-30 06:27:06 +00:00
|
|
|
headerImageView
|
2023-01-30 06:13:38 +00:00
|
|
|
}
|
2022-12-17 12:37:46 +00:00
|
|
|
accountInfoView
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-17 12:37:46 +00:00
|
|
|
private var headerImageView: some View {
|
2023-01-19 07:41:45 +00:00
|
|
|
ZStack(alignment: .bottomTrailing) {
|
|
|
|
if reasons.contains(.placeholder) {
|
|
|
|
Rectangle()
|
2023-01-30 06:15:18 +00:00
|
|
|
.foregroundColor(theme.secondaryBackgroundColor)
|
2023-02-12 15:13:57 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
2023-01-19 07:41:45 +00:00
|
|
|
} else {
|
|
|
|
LazyImage(url: account.header) { state in
|
|
|
|
if let image = state.image {
|
|
|
|
image
|
2023-02-18 06:25:10 +00:00
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fill)
|
2023-01-30 06:15:18 +00:00
|
|
|
.overlay(account.haveHeader ? .black.opacity(0.50) : .clear)
|
2023-02-18 06:25:10 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
|
|
|
.clipped()
|
2023-01-19 07:41:45 +00:00
|
|
|
} else if state.isLoading {
|
2023-01-30 06:15:18 +00:00
|
|
|
theme.secondaryBackgroundColor
|
2023-02-12 15:13:57 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
2023-01-19 07:41:45 +00:00
|
|
|
.shimmering()
|
|
|
|
} else {
|
2023-01-30 06:15:18 +00:00
|
|
|
theme.secondaryBackgroundColor
|
2023-02-12 15:13:57 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
2023-01-19 07:41:45 +00:00
|
|
|
}
|
2022-12-25 06:43:02 +00:00
|
|
|
}
|
2023-02-12 15:13:57 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
2023-01-19 07:41:45 +00:00
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-19 07:41:45 +00:00
|
|
|
if viewModel.relationship?.followedBy == true {
|
2023-01-19 17:14:08 +00:00
|
|
|
Text("account.relation.follows-you")
|
2023-01-19 07:41:45 +00:00
|
|
|
.font(.scaledFootnote)
|
|
|
|
.fontWeight(.semibold)
|
|
|
|
.padding(4)
|
|
|
|
.background(.ultraThinMaterial)
|
|
|
|
.cornerRadius(4)
|
|
|
|
.padding(8)
|
2022-12-20 16:11:12 +00:00
|
|
|
}
|
2022-12-20 08:37:07 +00:00
|
|
|
}
|
2023-01-30 06:15:18 +00:00
|
|
|
.background(theme.secondaryBackgroundColor)
|
2023-02-12 15:13:57 +00:00
|
|
|
.frame(height: Constants.headerHeight)
|
2022-12-20 08:37:07 +00:00
|
|
|
.contentShape(Rectangle())
|
|
|
|
.onTapGesture {
|
2023-01-30 06:15:18 +00:00
|
|
|
guard account.haveHeader else {
|
|
|
|
return
|
|
|
|
}
|
2022-12-22 09:56:24 +00:00
|
|
|
Task {
|
|
|
|
await quickLook.prepareFor(urls: [account.header], selectedURL: account.header)
|
|
|
|
}
|
2022-12-20 08:37:07 +00:00
|
|
|
}
|
2022-12-17 12:37:46 +00:00
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-17 12:37:46 +00:00
|
|
|
private var accountAvatarView: some View {
|
|
|
|
HStack {
|
2022-12-23 09:41:55 +00:00
|
|
|
AvatarView(url: account.avatar, size: .account)
|
2023-01-17 10:36:01 +00:00
|
|
|
.onTapGesture {
|
2023-01-30 06:15:18 +00:00
|
|
|
guard account.haveAvatar else {
|
|
|
|
return
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
Task {
|
|
|
|
await quickLook.prepareFor(urls: [account.avatar], selectedURL: account.avatar)
|
|
|
|
}
|
2022-12-22 09:56:24 +00:00
|
|
|
}
|
2022-12-17 12:37:46 +00:00
|
|
|
Spacer()
|
|
|
|
Group {
|
2022-12-27 08:11:12 +00:00
|
|
|
Button {
|
|
|
|
withAnimation {
|
|
|
|
scrollViewProxy?.scrollTo("status", anchor: .top)
|
|
|
|
}
|
|
|
|
} label: {
|
2023-01-19 17:14:08 +00:00
|
|
|
makeCustomInfoLabel(title: "account.posts", count: account.statusesCount)
|
2022-12-27 08:11:12 +00:00
|
|
|
}
|
2023-02-12 15:13:57 +00:00
|
|
|
.buttonStyle(.borderless)
|
2023-02-12 15:29:41 +00:00
|
|
|
|
2023-02-12 15:13:57 +00:00
|
|
|
Button {
|
|
|
|
routerPath.navigate(to: .following(id: account.id))
|
|
|
|
} label: {
|
2023-01-19 17:14:08 +00:00
|
|
|
makeCustomInfoLabel(title: "account.following", count: account.followingCount)
|
2022-12-23 17:47:19 +00:00
|
|
|
}
|
2023-02-12 15:13:57 +00:00
|
|
|
.buttonStyle(.borderless)
|
|
|
|
|
|
|
|
Button {
|
|
|
|
routerPath.navigate(to: .followers(id: account.id))
|
|
|
|
} label: {
|
2023-01-25 12:02:28 +00:00
|
|
|
makeCustomInfoLabel(
|
|
|
|
title: "account.followers",
|
|
|
|
count: account.followersCount,
|
|
|
|
needsBadge: currentAccount.account?.id == account.id && !currentAccount.followRequests.isEmpty
|
|
|
|
)
|
2022-12-23 17:47:19 +00:00
|
|
|
}
|
2023-02-12 15:13:57 +00:00
|
|
|
.buttonStyle(.borderless)
|
2023-02-12 15:29:41 +00:00
|
|
|
|
2022-12-17 12:37:46 +00:00
|
|
|
}.offset(y: 20)
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-17 12:37:46 +00:00
|
|
|
private var accountInfoView: some View {
|
|
|
|
Group {
|
|
|
|
accountAvatarView
|
2023-01-23 17:50:59 +00:00
|
|
|
HStack(alignment: .firstTextBaseline) {
|
2022-12-20 16:11:12 +00:00
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
2023-01-20 17:27:00 +00:00
|
|
|
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
|
2023-01-17 18:41:46 +00:00
|
|
|
.font(.scaledHeadline)
|
2022-12-24 12:41:25 +00:00
|
|
|
Text("@\(account.acct)")
|
2023-01-17 18:41:46 +00:00
|
|
|
.font(.scaledCallout)
|
2023-01-17 10:36:01 +00:00
|
|
|
.foregroundColor(.gray)
|
2023-01-23 17:50:59 +00:00
|
|
|
joinedAtView
|
2022-12-20 16:11:12 +00:00
|
|
|
}
|
|
|
|
Spacer()
|
2023-01-12 06:36:19 +00:00
|
|
|
if let relationship = viewModel.relationship, !viewModel.isCurrentUser {
|
|
|
|
HStack {
|
|
|
|
FollowButton(viewModel: .init(accountId: account.id,
|
2023-01-12 07:30:47 +00:00
|
|
|
relationship: relationship,
|
2023-01-20 17:53:07 +00:00
|
|
|
shouldDisplayNotify: true,
|
|
|
|
relationshipUpdated: { relationship in
|
2023-01-22 05:38:30 +00:00
|
|
|
viewModel.relationship = relationship
|
|
|
|
}))
|
2023-01-12 06:36:19 +00:00
|
|
|
}
|
2022-12-20 16:11:12 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-18 17:28:16 +00:00
|
|
|
|
|
|
|
if let note = viewModel.relationship?.note, !note.isEmpty,
|
|
|
|
!viewModel.isCurrentUser {
|
|
|
|
makeNoteView(note)
|
|
|
|
}
|
|
|
|
|
2023-01-20 17:27:00 +00:00
|
|
|
EmojiTextApp(account.note, emojis: account.emojis)
|
2023-01-17 18:41:46 +00:00
|
|
|
.font(.scaledBody)
|
2022-12-17 12:37:46 +00:00
|
|
|
.padding(.top, 8)
|
2022-12-23 14:28:22 +00:00
|
|
|
.environment(\.openURL, OpenURLAction { url in
|
2023-01-17 14:14:50 +00:00
|
|
|
routerPath.handle(url: url)
|
2022-12-23 14:28:22 +00:00
|
|
|
})
|
2023-02-19 10:35:46 +00:00
|
|
|
|
|
|
|
fieldsView
|
2022-12-17 12:37:46 +00:00
|
|
|
}
|
2023-01-03 06:41:29 +00:00
|
|
|
.padding(.horizontal, .layoutPadding)
|
2022-12-17 12:37:46 +00:00
|
|
|
.offset(y: -40)
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-25 12:02:28 +00:00
|
|
|
private func makeCustomInfoLabel(title: LocalizedStringKey, count: Int, needsBadge: Bool = false) -> some View {
|
2022-12-17 12:37:46 +00:00
|
|
|
VStack {
|
2022-12-18 19:30:19 +00:00
|
|
|
Text("\(count)")
|
2023-01-17 18:41:46 +00:00
|
|
|
.font(.scaledHeadline)
|
2022-12-24 14:09:17 +00:00
|
|
|
.foregroundColor(theme.tintColor)
|
2023-01-25 12:02:28 +00:00
|
|
|
.overlay(alignment: .trailing) {
|
|
|
|
if needsBadge {
|
|
|
|
Circle()
|
|
|
|
.fill(Color.red)
|
|
|
|
.frame(width: 9, height: 9)
|
|
|
|
.offset(x: 12)
|
|
|
|
}
|
|
|
|
}
|
2022-12-17 12:37:46 +00:00
|
|
|
Text(title)
|
2023-01-17 18:41:46 +00:00
|
|
|
.font(.scaledFootnote)
|
2022-12-17 12:37:46 +00:00
|
|
|
.foregroundColor(.gray)
|
|
|
|
}
|
|
|
|
}
|
2023-01-23 17:50:59 +00:00
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
private var joinedAtView: some View {
|
|
|
|
if let joinedAt = viewModel.account?.createdAt.asDate {
|
|
|
|
HStack(spacing: 4) {
|
|
|
|
Image(systemName: "calendar")
|
|
|
|
Text("account.joined")
|
|
|
|
Text(joinedAt, style: .date)
|
|
|
|
}
|
|
|
|
.foregroundColor(.gray)
|
|
|
|
.font(.footnote)
|
|
|
|
.padding(.top, 6)
|
|
|
|
}
|
|
|
|
}
|
2023-02-18 17:28:16 +00:00
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
private func makeNoteView(_ note: String) -> some View {
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
Text("account.relation.note.label")
|
|
|
|
.foregroundColor(.gray)
|
|
|
|
Text(note)
|
2023-02-18 20:25:45 +00:00
|
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
2023-02-18 17:28:16 +00:00
|
|
|
.padding(8)
|
|
|
|
.background(theme.secondaryBackgroundColor)
|
|
|
|
.cornerRadius(4)
|
|
|
|
.overlay(
|
|
|
|
RoundedRectangle(cornerRadius: 4)
|
|
|
|
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-02-19 10:35:46 +00:00
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
private var fieldsView: some View {
|
|
|
|
if !viewModel.fields.isEmpty {
|
|
|
|
VStack(alignment: .leading) {
|
|
|
|
ForEach(viewModel.fields) { field in
|
|
|
|
HStack {
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
|
|
Text(field.name)
|
|
|
|
.font(.scaledHeadline)
|
|
|
|
HStack {
|
|
|
|
if field.verifiedAt != nil {
|
|
|
|
Image(systemName: "checkmark.seal")
|
|
|
|
.foregroundColor(Color.green.opacity(0.80))
|
|
|
|
}
|
|
|
|
EmojiTextApp(field.value, emojis: viewModel.account?.emojis ?? [])
|
|
|
|
.foregroundColor(theme.tintColor)
|
|
|
|
.environment(\.openURL, OpenURLAction { url in
|
|
|
|
UIApplication.shared.open(url)
|
|
|
|
return .handled
|
|
|
|
})
|
|
|
|
}
|
|
|
|
.font(.scaledBody)
|
|
|
|
if viewModel.fields.last != field {
|
|
|
|
Divider()
|
|
|
|
.padding(.vertical, 4)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Spacer()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.padding(8)
|
|
|
|
.background(theme.secondaryBackgroundColor)
|
|
|
|
.cornerRadius(4)
|
|
|
|
.overlay(
|
|
|
|
RoundedRectangle(cornerRadius: 4)
|
|
|
|
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-12-17 12:37:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct AccountDetailHeaderView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
2023-01-12 06:36:19 +00:00
|
|
|
AccountDetailHeaderView(viewModel: .init(account: .placeholder()),
|
2022-12-20 16:11:12 +00:00
|
|
|
account: .placeholder(),
|
2023-02-12 15:13:57 +00:00
|
|
|
scrollViewProxy: nil)
|
2022-12-17 12:37:46 +00:00
|
|
|
}
|
|
|
|
}
|