From ed0bfb7d943679ca7d6854f13d9df160f439fe12 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 31 Jan 2023 17:43:06 +0100 Subject: [PATCH] Even better refresh / timeline position management --- IceCubesApp/App/IceCubesApp.swift | 4 +++- .../Sources/Timeline/TimelineView.swift | 6 ++--- .../Sources/Timeline/TimelineViewModel.swift | 23 ++++++++++++------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/IceCubesApp/App/IceCubesApp.swift b/IceCubesApp/App/IceCubesApp.swift index 4c358648..d60d23b9 100644 --- a/IceCubesApp/App/IceCubesApp.swift +++ b/IceCubesApp/App/IceCubesApp.swift @@ -166,7 +166,9 @@ struct IceCubesApp: App { private func handleScenePhase(scenePhase: ScenePhase) { switch scenePhase { case .background: - watcher.stopWatching() + if !ProcessInfo.processInfo.isiOSAppOnMac { + watcher.stopWatching() + } case .active: watcher.watch(streams: [.user, .direct]) UIApplication.shared.applicationIconBadgeNumber = 0 diff --git a/Packages/Timeline/Sources/Timeline/TimelineView.swift b/Packages/Timeline/Sources/Timeline/TimelineView.swift index 4954b8ec..acb113b1 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineView.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineView.swift @@ -68,13 +68,13 @@ public struct TimelineView: View { viewModel.timeline = timeline } else { Task { - await viewModel.fetchStatuses(userIntent: false) + await viewModel.fetchStatuses() } } } .refreshable { feedbackGenerator.impactOccurred(intensity: 0.3) - await viewModel.fetchStatuses(userIntent: true) + await viewModel.fetchStatuses() feedbackGenerator.impactOccurred(intensity: 0.7) } .onChange(of: watcher.latestEvent?.id) { _ in @@ -102,7 +102,7 @@ public struct TimelineView: View { if wasBackgrounded { wasBackgrounded = false Task { - await viewModel.fetchStatuses(userIntent: false) + await viewModel.fetchStatuses() } } case .background: diff --git a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift index 031f2da1..f2381c8d 100644 --- a/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift +++ b/Packages/Timeline/Sources/Timeline/TimelineViewModel.swift @@ -18,6 +18,8 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { private var statuses: [Status] = [] private var visibileStatusesIds = Set() + private var canStreamEvents: Bool = true + var scrollProxy: ScrollViewProxy? var pendingStatusesObserver: PendingStatusesObserver = .init() @@ -31,7 +33,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { pendingStatusesObserver.pendingStatuses = [] tag = nil } - await fetchStatuses(userIntent: false) + await fetchStatuses() switch timeline { case let .hashtag(tag, _): await fetchTag(id: tag) @@ -54,10 +56,6 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { } func fetchStatuses() async { - await fetchStatuses(userIntent: false) - } - - func fetchStatuses(userIntent: Bool) async { guard let client else { return } do { if statuses.isEmpty { @@ -71,19 +69,24 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) } } else if let first = statuses.first { + canStreamEvents = false var newStatuses: [Status] = await fetchNewPages(minId: first.id, maxPages: 20) - if userIntent || !pendingStatusesEnabled { + if !pendingStatusesEnabled { statuses.insert(contentsOf: newStatuses, at: 0) pendingStatusesObserver.pendingStatuses = [] withAnimation { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) + canStreamEvents = true } } else { newStatuses = newStatuses.filter { status in !statuses.contains(where: { $0.id == status.id }) } - guard !newStatuses.isEmpty else { return } + guard !newStatuses.isEmpty else { + canStreamEvents = true + return + } pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map{ $0.id }, at: 0) pendingStatusesObserver.feedbackGenerator.impactOccurred() @@ -94,21 +97,24 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { pendingStatusesObserver.disableUpdate = true withAnimation { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) - DispatchQueue.main.async { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { self.scrollProxy?.scrollTo(firstStatusId) self.pendingStatusesObserver.disableUpdate = false + self.canStreamEvents = true } } } else { statuses.insert(contentsOf: newStatuses, at: 0) withAnimation { statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage) + canStreamEvents = true } } } } } catch { statusesState = .error(error: error) + canStreamEvents = true print("timeline parse error: \(error)") } } @@ -161,6 +167,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher { func handleEvent(event: any StreamEvent, currentAccount: CurrentAccount) { if let event = event as? StreamEventUpdate, + canStreamEvents, pendingStatusesEnabled, !statuses.contains(where: { $0.id == event.status.id }) {