Cleanup scrollToTop

This commit is contained in:
Thomas Ricouard 2024-07-09 16:04:24 +02:00
parent 7c2e8d6e80
commit 1ffb4ab901
12 changed files with 79 additions and 139 deletions

View file

@ -19,9 +19,9 @@ extension View {
navigationDestination(for: RouterDestination.self) { destination in navigationDestination(for: RouterDestination.self) { destination in
switch destination { switch destination {
case let .accountDetail(id): case let .accountDetail(id):
AccountDetailView(accountId: id, scrollToTopSignal: .constant(0)) AccountDetailView(accountId: id)
case let .accountDetailWithAccount(account): case let .accountDetailWithAccount(account):
AccountDetailView(account: account, scrollToTopSignal: .constant(0)) AccountDetailView(account: account)
case let .accountSettingsWithAccount(account, appAccount): case let .accountSettingsWithAccount(account, appAccount):
AccountSettingsView(account: account, appAccount: appAccount) AccountSettingsView(account: account, appAccount: appAccount)
case let .statusDetail(id): case let .statusDetail(id):
@ -36,19 +36,16 @@ extension View {
TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)), TimelineView(timeline: .constant(.hashtag(tag: tag, accountId: accountId)),
pinnedFilters: .constant([]), pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil), selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false) canFilterTimeline: false)
case let .list(list): case let .list(list):
TimelineView(timeline: .constant(.list(list: list)), TimelineView(timeline: .constant(.list(list: list)),
pinnedFilters: .constant([]), pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil), selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false) canFilterTimeline: false)
case let .linkTimeline(url, title): case let .linkTimeline(url, title):
TimelineView(timeline: .constant(.link(url: url, title: title)), TimelineView(timeline: .constant(.link(url: url, title: title)),
pinnedFilters: .constant([]), pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil), selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false) canFilterTimeline: false)
case let .following(id): case let .following(id):
AccountsListView(mode: .following(accountId: id)) AccountsListView(mode: .following(accountId: id))
@ -64,7 +61,6 @@ extension View {
TimelineView(timeline: .constant(.trending), TimelineView(timeline: .constant(.trending),
pinnedFilters: .constant([]), pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil), selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false) canFilterTimeline: false)
case let .trendingLinks(cards): case let .trendingLinks(cards):
TrendingLinksListView(cards: cards) TrendingLinksListView(cards: cards)
@ -74,8 +70,7 @@ extension View {
NotificationsRequestsListView() NotificationsRequestsListView()
case let .notificationForAccount(accountId): case let .notificationForAccount(accountId):
NotificationsListView(lockedType: nil, NotificationsListView(lockedType: nil,
lockedAccountId: accountId, lockedAccountId: accountId)
scrollToTopSignal: .constant(0))
case .blockedAccounts: case .blockedAccounts:
AccountsListView(mode: .blocked) AccountsListView(mode: .blocked)
case .mutedAccounts: case .mutedAccounts:

View file

@ -70,8 +70,10 @@ struct AppView: View {
selectedTab = newTab selectedTab = newTab
})) { })) {
ForEach(availableTabs) { tab in ForEach(availableTabs) { tab in
Tab(value: tab) {
tab.makeContentView(selectedTab: $selectedTab) tab.makeContentView(selectedTab: $selectedTab)
.tabItem { .toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .tabBar)
} label: {
if userPreferences.showiPhoneTabLabel { if userPreferences.showiPhoneTabLabel {
tab.label tab.label
.environment(\.symbolVariants, tab == selectedTab ? .fill : .none) .environment(\.symbolVariants, tab == selectedTab ? .fill : .none)
@ -79,9 +81,7 @@ struct AppView: View {
Image(systemName: tab.iconName) Image(systemName: tab.iconName)
} }
} }
.tag(tab)
.badge(badgeFor(tab: tab)) .badge(badgeFor(tab: tab))
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .tabBar)
} }
} }
.id(appAccountsManager.currentClient.id) .id(appAccountsManager.currentClient.id)

View file

@ -13,11 +13,10 @@ struct ExploreTab: View {
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@Environment(Client.self) private var client @Environment(Client.self) private var client
@State private var routerPath = RouterPath() @State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
var body: some View { var body: some View {
NavigationStack(path: $routerPath.path) { NavigationStack(path: $routerPath.path) {
ExploreView(scrollToTopSignal: $scrollToTopSignal) ExploreView()
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar) .toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)

View file

@ -15,11 +15,10 @@ struct MessagesTab: View {
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@Environment(AppAccountsManager.self) private var appAccount @Environment(AppAccountsManager.self) private var appAccount
@State private var routerPath = RouterPath() @State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
var body: some View { var body: some View {
NavigationStack(path: $routerPath.path) { NavigationStack(path: $routerPath.path) {
ConversationsListView(scrollToTopSignal: $scrollToTopSignal) ConversationsListView()
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbar { .toolbar {

View file

@ -20,7 +20,6 @@ struct NotificationsTab: View {
@Environment(UserPreferences.self) private var userPreferences @Environment(UserPreferences.self) private var userPreferences
@Environment(PushNotificationsService.self) private var pushNotificationsService @Environment(PushNotificationsService.self) private var pushNotificationsService
@State private var routerPath = RouterPath() @State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
@Binding var selectedTab: AppTab @Binding var selectedTab: AppTab
@ -28,7 +27,7 @@ struct NotificationsTab: View {
var body: some View { var body: some View {
NavigationStack(path: $routerPath.path) { NavigationStack(path: $routerPath.path) {
NotificationsListView(lockedType: lockedType, scrollToTopSignal: $scrollToTopSignal) NotificationsListView(lockedType: lockedType)
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbar { .toolbar {

View file

@ -14,18 +14,17 @@ struct ProfileTab: View {
@Environment(Client.self) private var client @Environment(Client.self) private var client
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@State private var routerPath = RouterPath() @State private var routerPath = RouterPath()
@State private var scrollToTopSignal: Int = 0
var body: some View { var body: some View {
NavigationStack(path: $routerPath.path) { NavigationStack(path: $routerPath.path) {
if let account = currentAccount.account { if let account = currentAccount.account {
AccountDetailView(account: account, scrollToTopSignal: $scrollToTopSignal) AccountDetailView(account: account)
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
.toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar) .toolbarBackground(theme.primaryBackgroundColor.opacity(0.30), for: .navigationBar)
.id(account.id) .id(account.id)
} else { } else {
AccountDetailView(account: .placeholder(), scrollToTopSignal: $scrollToTopSignal) AccountDetailView(account: .placeholder())
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.allowsHitTesting(false) .allowsHitTesting(false)
} }

View file

@ -22,7 +22,6 @@ struct TimelineTab: View {
@State private var didAppear: Bool = false @State private var didAppear: Bool = false
@State private var timeline: TimelineFilter = .home @State private var timeline: TimelineFilter = .home
@State private var selectedTagGroup: TagGroup? @State private var selectedTagGroup: TagGroup?
@State private var scrollToTopSignal: Int = 0
@Query(sort: \LocalTimeline.creationDate, order: .reverse) var localTimelines: [LocalTimeline] @Query(sort: \LocalTimeline.creationDate, order: .reverse) var localTimelines: [LocalTimeline]
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup] @Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
@ -42,7 +41,6 @@ struct TimelineTab: View {
TimelineView(timeline: $timeline, TimelineView(timeline: $timeline,
pinnedFilters: $pinnedFilters, pinnedFilters: $pinnedFilters,
selectedTagGroup: $selectedTagGroup, selectedTagGroup: $selectedTagGroup,
scrollToTopSignal: $scrollToTopSignal,
canFilterTimeline: canFilterTimeline) canFilterTimeline: canFilterTimeline)
.withAppRouter() .withAppRouter()
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)

View file

@ -28,18 +28,14 @@ public struct AccountDetailView: View {
@State private var displayTitle: Bool = false @State private var displayTitle: Bool = false
@Binding var scrollToTopSignal: Int
/// When coming from a URL like a mention tap in a status. /// When coming from a URL like a mention tap in a status.
public init(accountId: String, scrollToTopSignal: Binding<Int>) { public init(accountId: String) {
_viewModel = .init(initialValue: .init(accountId: accountId)) _viewModel = .init(initialValue: .init(accountId: accountId))
_scrollToTopSignal = scrollToTopSignal
} }
/// When the account is already fetched by the parent caller. /// When the account is already fetched by the parent caller.
public init(account: Account, scrollToTopSignal: Binding<Int>) { public init(account: Account) {
_viewModel = .init(initialValue: .init(account: account)) _viewModel = .init(initialValue: .init(account: account))
_scrollToTopSignal = scrollToTopSignal
} }
public var body: some View { public var body: some View {
@ -89,11 +85,6 @@ public struct AccountDetailView: View {
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor) .background(theme.primaryBackgroundColor)
#endif #endif
.onChange(of: scrollToTopSignal) {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
} }
.onAppear { .onAppear {
guard reasons != .placeholder else { return } guard reasons != .placeholder else { return }
@ -403,6 +394,6 @@ extension View {
struct AccountDetailView_Previews: PreviewProvider { struct AccountDetailView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
AccountDetailView(account: .placeholder(), scrollToTopSignal: .constant(0)) AccountDetailView(account: .placeholder())
} }
} }

View file

@ -14,11 +14,7 @@ public struct ConversationsListView: View {
@State private var viewModel = ConversationsListViewModel() @State private var viewModel = ConversationsListViewModel()
@Binding var scrollToTopSignal: Int public init() { }
public init(scrollToTopSignal: Binding<Int>) {
_scrollToTopSignal = scrollToTopSignal
}
private var conversations: Binding<[Conversation]> { private var conversations: Binding<[Conversation]> {
if viewModel.isLoadingFirstPage { if viewModel.isLoadingFirstPage {
@ -89,11 +85,6 @@ public struct ConversationsListView: View {
viewModel.handleEvent(event: latestEvent) viewModel.handleEvent(event: latestEvent)
} }
} }
.onChange(of: scrollToTopSignal) {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
.refreshable { .refreshable {
// note: this Task wrapper should not be necessary, but it reportedly crashes without it // note: this Task wrapper should not be necessary, but it reportedly crashes without it
// when refreshing on an empty list // when refreshing on an empty list

View file

@ -14,11 +14,7 @@ public struct ExploreView: View {
@State private var viewModel = ExploreViewModel() @State private var viewModel = ExploreViewModel()
@Binding var scrollToTopSignal: Int public init() { }
public init(scrollToTopSignal: Binding<Int>) {
_scrollToTopSignal = scrollToTopSignal
}
public var body: some View { public var body: some View {
ScrollViewReader { proxy in ScrollViewReader { proxy in
@ -111,15 +107,6 @@ public struct ExploreView: View {
.task(id: viewModel.searchQuery) { .task(id: viewModel.searchQuery) {
await viewModel.search() await viewModel.search()
} }
.onChange(of: scrollToTopSignal) {
if viewModel.scrollToTopVisible {
viewModel.isSearchPresented.toggle()
} else {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
}
} }
} }

View file

@ -17,18 +17,15 @@ public struct NotificationsListView: View {
@State private var viewModel = NotificationsViewModel() @State private var viewModel = NotificationsViewModel()
@State private var isNotificationsPolicyPresented: Bool = false @State private var isNotificationsPolicyPresented: Bool = false
@Binding var scrollToTopSignal: Int
let lockedType: Models.Notification.NotificationType? let lockedType: Models.Notification.NotificationType?
let lockedAccountId: String? let lockedAccountId: String?
public init(lockedType: Models.Notification.NotificationType? = nil, public init(lockedType: Models.Notification.NotificationType? = nil,
lockedAccountId: String? = nil, lockedAccountId: String? = nil)
scrollToTopSignal: Binding<Int>)
{ {
self.lockedType = lockedType self.lockedType = lockedType
self.lockedAccountId = lockedAccountId self.lockedAccountId = lockedAccountId
_scrollToTopSignal = scrollToTopSignal
} }
public var body: some View { public var body: some View {
@ -44,11 +41,6 @@ public struct NotificationsListView: View {
.id(account.account?.id) .id(account.account?.id)
.environment(\.defaultMinListRowHeight, 1) .environment(\.defaultMinListRowHeight, 1)
.listStyle(.plain) .listStyle(.plain)
.onChange(of: scrollToTopSignal) {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
} }
.toolbar { .toolbar {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {

View file

@ -27,7 +27,6 @@ public struct TimelineView: View {
@Binding var timeline: TimelineFilter @Binding var timeline: TimelineFilter
@Binding var pinnedFilters: [TimelineFilter] @Binding var pinnedFilters: [TimelineFilter]
@Binding var selectedTagGroup: TagGroup? @Binding var selectedTagGroup: TagGroup?
@Binding var scrollToTopSignal: Int
@Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup] @Query(sort: \TagGroup.creationDate, order: .reverse) var tagGroups: [TagGroup]
@ -36,18 +35,15 @@ public struct TimelineView: View {
public init(timeline: Binding<TimelineFilter>, public init(timeline: Binding<TimelineFilter>,
pinnedFilters: Binding<[TimelineFilter]>, pinnedFilters: Binding<[TimelineFilter]>,
selectedTagGroup: Binding<TagGroup?>, selectedTagGroup: Binding<TagGroup?>,
scrollToTopSignal: Binding<Int>,
canFilterTimeline: Bool) canFilterTimeline: Bool)
{ {
_timeline = timeline _timeline = timeline
_pinnedFilters = pinnedFilters _pinnedFilters = pinnedFilters
_selectedTagGroup = selectedTagGroup _selectedTagGroup = selectedTagGroup
_scrollToTopSignal = scrollToTopSignal
self.canFilterTimeline = canFilterTimeline self.canFilterTimeline = canFilterTimeline
} }
public var body: some View { public var body: some View {
ScrollViewReader { proxy in
ZStack(alignment: .top) { ZStack(alignment: .top) {
List { List {
scrollToTopView scrollToTopView
@ -108,12 +104,6 @@ public struct TimelineView: View {
viewModel.scrollToIndex = nil viewModel.scrollToIndex = nil
} }
} }
.onChange(of: scrollToTopSignal) {
withAnimation {
proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
}
}
}
.toolbar { .toolbar {
toolbarTitleView toolbarTitleView
toolbarTagGroupButton toolbarTagGroupButton