IceCubesApp/Packages/Account/Sources/Account/AccountDetailView.swift

228 lines
7.2 KiB
Swift
Raw Normal View History

2022-11-29 11:18:06 +00:00
import SwiftUI
import Models
import Network
2022-12-18 19:30:19 +00:00
import Status
import Shimmer
import DesignSystem
2022-12-22 09:53:36 +00:00
import Env
2022-11-29 11:18:06 +00:00
public struct AccountDetailView: View {
2022-12-20 19:33:45 +00:00
@Environment(\.redactionReasons) private var reasons
@EnvironmentObject private var watcher: StreamWatcher
@EnvironmentObject private var currentAccount: CurrentAccount
2022-12-24 14:09:17 +00:00
@EnvironmentObject private var theme: Theme
2022-11-29 11:18:06 +00:00
@EnvironmentObject private var client: Client
@EnvironmentObject private var routeurPath: RouterPath
2022-11-29 11:18:06 +00:00
@StateObject private var viewModel: AccountDetailViewModel
2022-12-20 08:37:07 +00:00
@State private var scrollOffset: CGFloat = 0
2022-12-21 19:53:23 +00:00
@State private var isFieldsSheetDisplayed: Bool = false
2022-12-27 12:49:54 +00:00
@State private var isCurrentUser: Bool = false
2022-12-20 15:08:09 +00:00
/// When coming from a URL like a mention tap in a status.
2022-11-29 11:18:06 +00:00
public init(accountId: String) {
_viewModel = StateObject(wrappedValue: .init(accountId: accountId))
}
/// When the account is already fetched by the parent caller.
2022-12-27 12:49:54 +00:00
public init(account: Account) {
_viewModel = StateObject(wrappedValue: .init(account: account))
2022-12-17 12:37:46 +00:00
}
2022-11-29 11:18:06 +00:00
public var body: some View {
2022-12-27 08:11:12 +00:00
ScrollViewReader { proxy in
ScrollViewOffsetReader { offset in
self.scrollOffset = offset
} content: {
LazyVStack(alignment: .leading) {
makeHeaderView(proxy: proxy)
familliarFollowers
.offset(y: -36)
featuredTagsView
.offset(y: -36)
Group {
if isCurrentUser {
Picker("", selection: $viewModel.selectedTab) {
ForEach(AccountDetailViewModel.Tab.allCases, id: \.self) { tab in
Text(tab.title).tag(tab)
}
}
.pickerStyle(.segmented)
.padding(.horizontal, DS.Constants.layoutPadding)
.offset(y: -20)
} else {
Divider()
.offset(y: -20)
}
}
2022-12-27 08:11:12 +00:00
.id("status")
switch viewModel.tabState {
case .statuses:
StatusesListView(fetcher: viewModel)
case let .followedTags(tags):
makeTagsListView(tags: tags)
}
}
2022-11-29 11:18:06 +00:00
}
.background(theme.primaryBackgroundColor)
2022-11-29 11:18:06 +00:00
}
.onAppear {
Task {
guard reasons != .placeholder else { return }
isCurrentUser = currentAccount.account?.id == viewModel.accountId
viewModel.isCurrentUser = isCurrentUser
viewModel.client = client
await viewModel.fetchAccount()
if viewModel.statuses.isEmpty {
await viewModel.fetchStatuses()
}
2022-12-20 15:08:09 +00:00
}
}
.refreshable {
Task {
await viewModel.fetchAccount()
await viewModel.fetchStatuses()
}
2022-12-18 19:30:19 +00:00
}
.onChange(of: watcher.latestEvent?.id) { id in
if let latestEvent = watcher.latestEvent,
viewModel.accountId == currentAccount.account?.id {
viewModel.handleEvent(event: latestEvent, currentAccount: currentAccount)
}
}
2022-12-20 08:37:07 +00:00
.edgesIgnoringSafeArea(.top)
.navigationTitle(Text(scrollOffset < -200 ? viewModel.title : ""))
2022-12-18 19:30:19 +00:00
}
@ViewBuilder
2022-12-27 08:11:12 +00:00
private func makeHeaderView(proxy: ScrollViewProxy?) -> some View {
switch viewModel.accountState {
2022-12-18 19:30:19 +00:00
case .loading:
2022-12-20 16:11:12 +00:00
AccountDetailHeaderView(isCurrentUser: isCurrentUser,
account: .placeholder(),
2022-12-23 15:21:31 +00:00
relationship: .placeholder(),
2022-12-27 08:11:12 +00:00
scrollViewProxy: proxy,
2022-12-21 11:47:07 +00:00
scrollOffset: $scrollOffset)
2022-12-18 19:30:19 +00:00
.redacted(reason: .placeholder)
case let .data(account):
2022-12-20 16:11:12 +00:00
AccountDetailHeaderView(isCurrentUser: isCurrentUser,
account: account,
2022-12-23 15:21:31 +00:00
relationship: viewModel.relationship,
2022-12-27 08:11:12 +00:00
scrollViewProxy: proxy,
2022-12-21 11:47:07 +00:00
scrollOffset: $scrollOffset)
2022-12-18 19:30:19 +00:00
case let .error(error):
Text("Error: \(error.localizedDescription)")
}
}
2022-12-21 19:53:23 +00:00
2022-12-21 19:26:38 +00:00
@ViewBuilder
private var featuredTagsView: some View {
2022-12-21 19:53:23 +00:00
if !viewModel.featuredTags.isEmpty || !viewModel.fields.isEmpty {
2022-12-21 19:26:38 +00:00
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 4) {
2022-12-21 19:53:23 +00:00
if !viewModel.fields.isEmpty {
2022-12-21 19:26:38 +00:00
Button {
2022-12-21 19:53:23 +00:00
isFieldsSheetDisplayed.toggle()
2022-12-21 19:26:38 +00:00
} label: {
VStack(alignment: .leading, spacing: 0) {
2022-12-21 19:53:23 +00:00
Text("About")
2022-12-21 19:26:38 +00:00
.font(.callout)
2022-12-21 19:53:23 +00:00
Text("\(viewModel.fields.count) fields")
2022-12-21 19:26:38 +00:00
.font(.caption2)
}
2022-12-21 19:53:23 +00:00
}
.buttonStyle(.bordered)
.sheet(isPresented: $isFieldsSheetDisplayed) {
fieldSheetView
}
}
if !viewModel.featuredTags.isEmpty {
ForEach(viewModel.featuredTags) { tag in
Button {
routeurPath.navigate(to: .hashTag(tag: tag.name, account: viewModel.accountId))
} label: {
VStack(alignment: .leading, spacing: 0) {
Text("#\(tag.name)")
.font(.callout)
Text("\(tag.statusesCount) posts")
.font(.caption2)
}
}.buttonStyle(.bordered)
}
2022-12-21 19:26:38 +00:00
}
}
.padding(.leading, DS.Constants.layoutPadding)
}
}
}
@ViewBuilder
private var familliarFollowers: some View {
if !viewModel.familliarFollowers.isEmpty {
2022-12-23 15:21:31 +00:00
VStack(alignment: .leading, spacing: 2) {
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)
}
}
2022-12-21 19:53:23 +00:00
private var fieldSheetView: some View {
NavigationStack {
List {
ForEach(viewModel.fields) { field in
VStack(alignment: .leading, spacing: 2) {
Text(field.name)
.font(.headline)
HStack {
if field.verifiedAt != nil {
Image(systemName: "checkmark.seal")
.foregroundColor(Color.green.opacity(0.80))
}
Text(field.value.asSafeAttributedString)
2022-12-24 14:09:17 +00:00
.foregroundColor(theme.tintColor)
2022-12-21 19:53:23 +00:00
}
.font(.body)
}
2022-12-29 09:39:34 +00:00
.listRowBackground(field.verifiedAt != nil ? Color.green.opacity(0.15) : theme.primaryBackgroundColor)
2022-12-21 19:53:23 +00:00
}
}
2022-12-29 09:39:34 +00:00
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
2022-12-21 19:53:23 +00:00
.navigationTitle("About")
}
}
private func makeTagsListView(tags: [Tag]) -> some View {
Group {
ForEach(tags) { tag in
TagRowView(tag: tag)
.padding(.horizontal, DS.Constants.layoutPadding)
.padding(.vertical, 8)
}
}
}
2022-12-17 12:37:46 +00:00
}
struct AccountDetailView_Previews: PreviewProvider {
static var previews: some View {
AccountDetailView(account: .placeholder())
2022-11-29 11:18:06 +00:00
}
}
2022-12-17 12:37:46 +00:00