Transition profile to List container

This commit is contained in:
Thomas Ricouard 2023-02-12 16:13:57 +01:00
parent ad7ca63c3b
commit a959ea3606
3 changed files with 73 additions and 92 deletions

View File

@ -7,6 +7,10 @@ import Shimmer
import SwiftUI import SwiftUI
struct AccountDetailHeaderView: View { struct AccountDetailHeaderView: View {
enum Constants {
static let headerHeight: CGFloat = 200
}
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var quickLook: QuickLook
@EnvironmentObject private var routerPath: RouterPath @EnvironmentObject private var routerPath: RouterPath
@ -17,16 +21,10 @@ struct AccountDetailHeaderView: View {
let account: Account let account: Account
let scrollViewProxy: ScrollViewProxy? let scrollViewProxy: ScrollViewProxy?
@Binding var scrollOffset: CGFloat
private var bannerHeight: CGFloat {
200 + (scrollOffset > 0 ? scrollOffset * 2 : 0)
}
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Rectangle() Rectangle()
.frame(height: 200) .frame(height: Constants.headerHeight)
.overlay { .overlay {
headerImageView headerImageView
} }
@ -39,7 +37,7 @@ struct AccountDetailHeaderView: View {
if reasons.contains(.placeholder) { if reasons.contains(.placeholder) {
Rectangle() Rectangle()
.foregroundColor(theme.secondaryBackgroundColor) .foregroundColor(theme.secondaryBackgroundColor)
.frame(height: bannerHeight) .frame(height: Constants.headerHeight)
} else { } else {
LazyImage(url: account.header) { state in LazyImage(url: account.header) { state in
if let image = state.image { if let image = state.image {
@ -48,14 +46,14 @@ struct AccountDetailHeaderView: View {
.overlay(account.haveHeader ? .black.opacity(0.50) : .clear) .overlay(account.haveHeader ? .black.opacity(0.50) : .clear)
} else if state.isLoading { } else if state.isLoading {
theme.secondaryBackgroundColor theme.secondaryBackgroundColor
.frame(height: bannerHeight) .frame(height: Constants.headerHeight)
.shimmering() .shimmering()
} else { } else {
theme.secondaryBackgroundColor theme.secondaryBackgroundColor
.frame(height: bannerHeight) .frame(height: Constants.headerHeight)
} }
} }
.frame(height: bannerHeight) .frame(height: Constants.headerHeight)
} }
if viewModel.relationship?.followedBy == true { if viewModel.relationship?.followedBy == true {
@ -69,8 +67,7 @@ struct AccountDetailHeaderView: View {
} }
} }
.background(theme.secondaryBackgroundColor) .background(theme.secondaryBackgroundColor)
.frame(height: bannerHeight) .frame(height: Constants.headerHeight)
.offset(y: scrollOffset > 0 ? -scrollOffset : 0)
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture { .onTapGesture {
guard account.haveHeader else { guard account.haveHeader else {
@ -102,16 +99,26 @@ struct AccountDetailHeaderView: View {
} label: { } label: {
makeCustomInfoLabel(title: "account.posts", count: account.statusesCount) makeCustomInfoLabel(title: "account.posts", count: account.statusesCount)
} }
NavigationLink(value: RouterDestinations.following(id: account.id)) { .buttonStyle(.borderless)
Button {
routerPath.navigate(to: .following(id: account.id))
} label: {
makeCustomInfoLabel(title: "account.following", count: account.followingCount) makeCustomInfoLabel(title: "account.following", count: account.followingCount)
} }
NavigationLink(value: RouterDestinations.followers(id: account.id)) { .buttonStyle(.borderless)
Button {
routerPath.navigate(to: .followers(id: account.id))
} label: {
makeCustomInfoLabel( makeCustomInfoLabel(
title: "account.followers", title: "account.followers",
count: account.followersCount, count: account.followersCount,
needsBadge: currentAccount.account?.id == account.id && !currentAccount.followRequests.isEmpty needsBadge: currentAccount.account?.id == account.id && !currentAccount.followRequests.isEmpty
) )
} }
.buttonStyle(.borderless)
}.offset(y: 20) }.offset(y: 20)
} }
} }
@ -123,6 +130,12 @@ struct AccountDetailHeaderView: View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis) EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
.font(.scaledHeadline) .font(.scaledHeadline)
.onDisappear {
print("DISPEAR")
}
.onAppear {
print("APPEAR")
}
Text("@\(account.acct)") Text("@\(account.acct)")
.font(.scaledCallout) .font(.scaledCallout)
.foregroundColor(.gray) .foregroundColor(.gray)
@ -189,7 +202,6 @@ struct AccountDetailHeaderView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
AccountDetailHeaderView(viewModel: .init(account: .placeholder()), AccountDetailHeaderView(viewModel: .init(account: .placeholder()),
account: .placeholder(), account: .placeholder(),
scrollViewProxy: nil, scrollViewProxy: nil)
scrollOffset: .constant(0))
} }
} }

View File

@ -19,7 +19,6 @@ public struct AccountDetailView: View {
@EnvironmentObject private var routerPath: RouterPath @EnvironmentObject private var routerPath: RouterPath
@StateObject private var viewModel: AccountDetailViewModel @StateObject private var viewModel: AccountDetailViewModel
@State private var scrollOffset: CGFloat = 0
@State private var isFieldsSheetDisplayed: Bool = false @State private var isFieldsSheetDisplayed: Bool = false
@State private var isCurrentUser: Bool = false @State private var isCurrentUser: Bool = false
@State private var isCreateListAlertPresented: Bool = false @State private var isCreateListAlertPresented: Bool = false
@ -40,42 +39,46 @@ public struct AccountDetailView: View {
public var body: some View { public var body: some View {
ScrollViewReader { proxy in ScrollViewReader { proxy in
ScrollViewOffsetReader { offset in List {
self.scrollOffset = offset Group {
} content: {
LazyVStack(alignment: .leading) {
makeHeaderView(proxy: proxy) makeHeaderView(proxy: proxy)
familiarFollowers familiarFollowers
.offset(y: -36)
featuredTagsView featuredTagsView
.offset(y: -36) }
Group { .listRowInsets(.init())
Picker("", selection: $viewModel.selectedTab) { .listRowSeparator(.hidden)
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs, .listRowBackground(theme.primaryBackgroundColor)
id: \.self) { tab in
Image(systemName: tab.iconName) Picker("", selection: $viewModel.selectedTab) {
.tag(tab) ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
} id: \.self) { tab in
} Image(systemName: tab.iconName)
.pickerStyle(.segmented) .tag(tab)
.padding(.horizontal, .layoutPadding)
.offset(y: -20)
}
.id("status")
switch viewModel.tabState {
case .statuses:
if viewModel.selectedTab == .statuses {
pinnedPostsView
}
StatusesListView(fetcher: viewModel, isEmbdedInList: false)
case .followedTags:
tagsListView
case .lists:
listsListView
} }
} }
.pickerStyle(.segmented)
.padding(.layoutPadding)
.listRowSeparator(.hidden)
.listRowBackground(theme.primaryBackgroundColor)
.listRowInsets(.init())
.id("status")
switch viewModel.tabState {
case .statuses:
if viewModel.selectedTab == .statuses {
pinnedPostsView
.listRowInsets(.init())
.listRowSeparator(.hidden)
.listRowBackground(theme.primaryBackgroundColor)
}
StatusesListView(fetcher: viewModel)
case .followedTags:
tagsListView
case .lists:
listsListView
}
} }
.listStyle(.plain)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
} }
@ -138,14 +141,12 @@ public struct AccountDetailView: View {
case .loading: case .loading:
AccountDetailHeaderView(viewModel: viewModel, AccountDetailHeaderView(viewModel: viewModel,
account: .placeholder(), account: .placeholder(),
scrollViewProxy: proxy, scrollViewProxy: proxy)
scrollOffset: $scrollOffset)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
case let .data(account): case let .data(account):
AccountDetailHeaderView(viewModel: viewModel, AccountDetailHeaderView(viewModel: viewModel,
account: account, account: account,
scrollViewProxy: proxy, scrollViewProxy: proxy)
scrollOffset: $scrollOffset)
case let .error(error): case let .error(error):
Text("Error: \(error.localizedDescription)") Text("Error: \(error.localizedDescription)")
} }
@ -270,8 +271,7 @@ public struct AccountDetailView: View {
Spacer() Spacer()
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
} }
.padding(.horizontal, .layoutPadding) .listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8)
} }
}.task { }.task {
await currentAccount.fetchFollowedTags() await currentAccount.fetchFollowedTags()
@ -282,16 +282,11 @@ public struct AccountDetailView: View {
Group { Group {
ForEach(currentAccount.sortedLists) { list in ForEach(currentAccount.sortedLists) { list in
NavigationLink(value: RouterDestinations.list(list: list)) { NavigationLink(value: RouterDestinations.list(list: list)) {
HStack { Text(list.title)
Text(list.title)
Spacer()
Image(systemName: "chevron.right")
}
.padding(.vertical, 8)
.padding(.horizontal, .layoutPadding)
.font(.scaledHeadline) .font(.scaledHeadline)
.foregroundColor(theme.labelColor) .foregroundColor(theme.labelColor)
} }
.listRowBackground(theme.primaryBackgroundColor)
.contextMenu { .contextMenu {
Button("account.list.delete", role: .destructive) { Button("account.list.delete", role: .destructive) {
Task { Task {
@ -303,7 +298,9 @@ public struct AccountDetailView: View {
Button("account.list.create") { Button("account.list.create") {
isCreateListAlertPresented = true isCreateListAlertPresented = true
} }
.padding(.horizontal, .layoutPadding) .tint(theme.tintColor)
.buttonStyle(.borderless)
.listRowBackground(theme.primaryBackgroundColor)
} }
.task { .task {
await currentAccount.fetchLists() await currentAccount.fetchLists()
@ -348,18 +345,6 @@ public struct AccountDetailView: View {
@ToolbarContentBuilder @ToolbarContentBuilder
private var toolbarContent: some ToolbarContent { private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .principal) {
if scrollOffset < -170 {
switch viewModel.accountState {
case let .data(account):
EmojiTextApp(.init(stringValue: account.safeDisplayName), emojis: account.emojis)
.font(.scaledHeadline)
default:
EmptyView()
}
}
}
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Menu { Menu {
if let account = viewModel.account { if let account = viewModel.account {
@ -544,11 +529,7 @@ public struct AccountDetailView: View {
} }
} }
} label: { } label: {
if scrollOffset < -5 { Image(systemName: "ellipsis.circle")
Image(systemName: "ellipsis.circle")
} else {
Image(systemName: "ellipsis.circle.fill")
}
} }
} }
} }

View File

@ -9,12 +9,10 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
@ObservedObject private var fetcher: Fetcher @ObservedObject private var fetcher: Fetcher
private let isRemote: Bool private let isRemote: Bool
private let isEmbdedInList: Bool
public init(fetcher: Fetcher, isRemote: Bool = false, isEmbdedInList: Bool = true) { public init(fetcher: Fetcher, isRemote: Bool = false) {
self.fetcher = fetcher self.fetcher = fetcher
self.isRemote = isRemote self.isRemote = isRemote
self.isEmbdedInList = isEmbdedInList
} }
public var body: some View { public var body: some View {
@ -22,12 +20,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
case .loading: case .loading:
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isCompact: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.padding(.horizontal, isEmbdedInList ? 0 : .layoutPadding)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
if !isEmbdedInList {
Divider()
.padding(.vertical, .dividerPadding)
}
} }
case .error: case .error:
ErrorView(title: "status.error.title", ErrorView(title: "status.error.title",
@ -45,7 +38,6 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
let viewModel = StatusRowViewModel(status: status, isCompact: false, isRemote: isRemote) let viewModel = StatusRowViewModel(status: status, isCompact: false, isRemote: isRemote)
if viewModel.filter?.filter.filterAction != .hide { if viewModel.filter?.filter.filterAction != .hide {
StatusRowView(viewModel: viewModel) StatusRowView(viewModel: viewModel)
.padding(.horizontal, isEmbdedInList ? 0 : .layoutPadding)
.id(status.id) .id(status.id)
.onAppear { .onAppear {
fetcher.statusDidAppear(status: status) fetcher.statusDidAppear(status: status)
@ -53,10 +45,6 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
.onDisappear { .onDisappear {
fetcher.statusDidDisappear(status: status) fetcher.statusDidDisappear(status: status)
} }
if !isEmbdedInList {
Divider()
.padding(.vertical, .dividerPadding)
}
} }
} }
switch nextPageState { switch nextPageState {