From 122e57d8acb29d1d9211865be75995c7ea574938 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sat, 14 Sep 2024 08:09:51 +0200 Subject: [PATCH] Restore ScrollToTop on timeline tab --- IceCubesApp/App/Main/AppView.swift | 11 ++++ .../Env/Sources/Env/CustomEnvValues.swift | 1 + .../Sources/Timeline/View/TimelineView.swift | 61 +++++++++++-------- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/IceCubesApp/App/Main/AppView.swift b/IceCubesApp/App/Main/AppView.swift index 28c526c9..01e35488 100644 --- a/IceCubesApp/App/Main/AppView.swift +++ b/IceCubesApp/App/Main/AppView.swift @@ -26,6 +26,7 @@ struct AppView: View { @State var iosTabs = iOSTabs.shared @State var sidebarTabs = SidebarTabs.shared + @State var selectedTabScrollToTop: Int = -1 var body: some View { #if os(visionOS) @@ -75,6 +76,7 @@ struct AppView: View { } .id(appAccountsManager.currentClient.id) .withSheetDestinations(sheetDestinations: $appRouterPath.presentedSheet) + .environment(\.selectedTabScrollToTop, selectedTabScrollToTop) } private func updateTab(with newTab: AppTab) { @@ -90,6 +92,15 @@ struct AppView: View { HapticManager.shared.fireHaptic(.tabSelection) SoundEffectManager.shared.playSound(.tabSelection) + if selectedTab == newTab { + selectedTabScrollToTop = newTab.rawValue + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + selectedTabScrollToTop = -1 + } + } else { + selectedTabScrollToTop = -1 + } + selectedTab = newTab } diff --git a/Packages/Env/Sources/Env/CustomEnvValues.swift b/Packages/Env/Sources/Env/CustomEnvValues.swift index 5e6b88ac..cbf4cda7 100644 --- a/Packages/Env/Sources/Env/CustomEnvValues.swift +++ b/Packages/Env/Sources/Env/CustomEnvValues.swift @@ -12,4 +12,5 @@ extension EnvironmentValues { @Entry public var isStatusFocused: Bool = false @Entry public var isHomeTimeline: Bool = false @Entry public var indentationLevel: UInt = 0 + @Entry public var selectedTabScrollToTop: Int = -1 } diff --git a/Packages/Timeline/Sources/Timeline/View/TimelineView.swift b/Packages/Timeline/Sources/Timeline/View/TimelineView.swift index 901b2164..40303980 100644 --- a/Packages/Timeline/Sources/Timeline/View/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/View/TimelineView.swift @@ -11,6 +11,8 @@ import SwiftUIIntrospect @MainActor public struct TimelineView: View { @Environment(\.scenePhase) private var scenePhase + @Environment(\.selectedTabScrollToTop) private var selectedTabScrollToTop + @Environment(Theme.self) private var theme @Environment(CurrentAccount.self) private var account @Environment(StreamWatcher.self) private var watcher @@ -45,33 +47,42 @@ public struct TimelineView: View { public var body: some View { ZStack(alignment: .top) { - List { - scrollToTopView - TimelineTagGroupheaderView(group: $selectedTagGroup, timeline: $timeline) - TimelineTagHeaderView(tag: $viewModel.tag) - switch viewModel.timeline { - case .remoteLocal: - StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath, isRemote: true) - default: - StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath) - .environment(\.isHomeTimeline, timeline == .home) - } - } - .id(client.id) - .environment(\.defaultMinListRowHeight, 1) - .listStyle(.plain) - #if !os(visionOS) - .scrollContentBackground(.hidden) - .background(theme.primaryBackgroundColor) - #endif - .introspect(.list, on: .iOS(.v17, .v18)) { (collectionView: UICollectionView) in - DispatchQueue.main.async { - self.collectionView = collectionView + ScrollViewReader { proxy in + List { + scrollToTopView + TimelineTagGroupheaderView(group: $selectedTagGroup, timeline: $timeline) + TimelineTagHeaderView(tag: $viewModel.tag) + switch viewModel.timeline { + case .remoteLocal: + StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath, isRemote: true) + default: + StatusesListView(fetcher: viewModel, client: client, routerPath: routerPath) + .environment(\.isHomeTimeline, timeline == .home) } - prefetcher.viewModel = viewModel - collectionView.isPrefetchingEnabled = true - collectionView.prefetchDataSource = prefetcher } + .id(client.id) + .environment(\.defaultMinListRowHeight, 1) + .listStyle(.plain) + .onChange(of: selectedTabScrollToTop) { _, newValue in + if newValue == 0, routerPath.path.isEmpty { + withAnimation { + proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top) + } + } + } + #if !os(visionOS) + .scrollContentBackground(.hidden) + .background(theme.primaryBackgroundColor) + #endif + .introspect(.list, on: .iOS(.v17, .v18)) { (collectionView: UICollectionView) in + DispatchQueue.main.async { + self.collectionView = collectionView + } + prefetcher.viewModel = viewModel + collectionView.isPrefetchingEnabled = true + collectionView.prefetchDataSource = prefetcher + } + } if viewModel.timeline.supportNewestPagination { TimelineUnreadStatusesView(observer: viewModel.pendingStatusesObserver) }