Transition profile to List container
This commit is contained in:
parent
ad7ca63c3b
commit
a959ea3606
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,16 +39,16 @@ 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())
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
||||||
Picker("", selection: $viewModel.selectedTab) {
|
Picker("", selection: $viewModel.selectedTab) {
|
||||||
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
|
ForEach(isCurrentUser ? AccountDetailViewModel.Tab.currentAccountTabs : AccountDetailViewModel.Tab.accountTabs,
|
||||||
id: \.self) { tab in
|
id: \.self) { tab in
|
||||||
|
@ -58,24 +57,28 @@ public struct AccountDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
.padding(.horizontal, .layoutPadding)
|
.padding(.layoutPadding)
|
||||||
.offset(y: -20)
|
.listRowSeparator(.hidden)
|
||||||
}
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
.listRowInsets(.init())
|
||||||
.id("status")
|
.id("status")
|
||||||
|
|
||||||
switch viewModel.tabState {
|
switch viewModel.tabState {
|
||||||
case .statuses:
|
case .statuses:
|
||||||
if viewModel.selectedTab == .statuses {
|
if viewModel.selectedTab == .statuses {
|
||||||
pinnedPostsView
|
pinnedPostsView
|
||||||
|
.listRowInsets(.init())
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
}
|
}
|
||||||
StatusesListView(fetcher: viewModel, isEmbdedInList: false)
|
StatusesListView(fetcher: viewModel)
|
||||||
case .followedTags:
|
case .followedTags:
|
||||||
tagsListView
|
tagsListView
|
||||||
case .lists:
|
case .lists:
|
||||||
listsListView
|
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue