Skip to content

Commit 4ac5999

Browse files
authored
Add preference-controlled full timeline fetch option (#2351)
* Add optional full timeline fetching * Cleanup
1 parent 3332cef commit 4ac5999

File tree

4 files changed

+75
-15
lines changed

4 files changed

+75
-15
lines changed

IceCubesApp/App/Tabs/Settings/SettingsTab.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,12 @@ struct SettingsTabs: View {
235235
Label("Stream home timeline", systemImage: "antenna.radiowaves.left.and.right")
236236
.symbolVariant(preferences.streamHomeTimeline ? .none : .slash)
237237
}
238+
Toggle(isOn: $preferences.fullTimelineFetch) {
239+
Label("Full timeline fetch", systemImage: "arrow.triangle.2.circlepath")
240+
.symbolVariant(preferences.fullTimelineFetch ? .none : .slash)
241+
}
238242
} header: {
239243
Text("settings.section.other")
240-
} footer: {
241-
Text("settings.section.other.footer")
242244
}
243245
#if !os(visionOS)
244246
.listRowBackground(theme.primaryBackgroundColor)

Packages/Env/Sources/Env/UserPreferences.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import SwiftUI
7373
@AppStorage("sidebar_expanded") public var isSidebarExpanded: Bool = false
7474

7575
@AppStorage("stream_home_timeline") public var streamHomeTimeline: Bool = false
76+
@AppStorage("full_timeline_fetch") public var fullTimelineFetch: Bool = false
7677

7778
// Notifications
7879
@AppStorage("notifications-truncate-status-content")
@@ -369,6 +370,12 @@ import SwiftUI
369370
}
370371
}
371372

373+
public var fullTimelineFetch: Bool {
374+
didSet {
375+
storage.fullTimelineFetch = fullTimelineFetch
376+
}
377+
}
378+
372379
// Notifications
373380
public var notificationsTruncateStatusContent: Bool {
374381
didSet {
@@ -554,6 +561,7 @@ import SwiftUI
554561
isSidebarExpanded = storage.isSidebarExpanded
555562
notificationsTruncateStatusContent = storage.notificationsTruncateStatusContent
556563
streamHomeTimeline = storage.streamHomeTimeline
564+
fullTimelineFetch = storage.fullTimelineFetch
557565
}
558566
}
559567

Packages/Timeline/Sources/Timeline/View/TimelineViewModel.swift

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ import SwiftUI
6666
@ObservationIgnored
6767
private let cache = TimelineCache()
6868

69+
private enum Constants {
70+
static let fullTimelineFetchLimit = 800
71+
static let fullTimelineFetchMaxPages = fullTimelineFetchLimit / 40
72+
}
73+
74+
private var isFullTimelineFetchEnabled: Bool {
75+
UserPreferences.shared.fullTimelineFetch
76+
}
77+
6978
private var isCacheEnabled: Bool {
7079
canFilterTimeline && timeline.supportNewestPagination && client?.isAuth == true
7180
}
@@ -296,20 +305,58 @@ extension TimelineViewModel: GapLoadingFetcher {
296305
return
297306
}
298307

308+
var statusesToInsert = actuallyNewStatuses
309+
310+
if isFullTimelineFetchEnabled, statusesToInsert.count < Constants.fullTimelineFetchLimit {
311+
let additionalStatuses: [Status] = try await statusFetcher.fetchNewPages(
312+
client: client,
313+
timeline: timeline,
314+
minId: latestStatus,
315+
maxPages: Constants.fullTimelineFetchMaxPages)
316+
317+
if !additionalStatuses.isEmpty {
318+
var knownIds = Set(currentIds)
319+
knownIds.formUnion(statusesToInsert.map(\.id))
320+
321+
let filteredAdditional = additionalStatuses.filter { status in
322+
guard status.id > latestStatus else { return false }
323+
if knownIds.contains(status.id) {
324+
return false
325+
}
326+
knownIds.insert(status.id)
327+
return true
328+
}
329+
330+
if !filteredAdditional.isEmpty {
331+
let remainingCapacity = max(0, Constants.fullTimelineFetchLimit - statusesToInsert.count)
332+
if remainingCapacity > 0 {
333+
statusesToInsert.append(contentsOf: filteredAdditional.prefix(remainingCapacity))
334+
}
335+
}
336+
}
337+
}
338+
339+
statusesToInsert.sort { $0.id > $1.id }
340+
341+
if statusesToInsert.count > Constants.fullTimelineFetchLimit {
342+
statusesToInsert = Array(statusesToInsert.prefix(Constants.fullTimelineFetchLimit))
343+
}
344+
299345
StatusDataControllerProvider.shared.updateDataControllers(
300-
for: actuallyNewStatuses, client: client)
346+
for: statusesToInsert, client: client)
301347

302348
// Pass the original count to determine if we need a gap
303349
await updateTimelineWithNewStatuses(
304-
actuallyNewStatuses,
350+
statusesToInsert,
305351
latestStatus: latestStatus,
306-
fetchedCount: newestStatuses.count
352+
fetchedCount: newestStatuses.count,
353+
shouldCreateGap: !isFullTimelineFetchEnabled
307354
)
308355
canStreamEvents = true
309356
}
310357

311358
private func updateTimelineWithNewStatuses(
312-
_ newStatuses: [Status], latestStatus: String, fetchedCount: Int
359+
_ newStatuses: [Status], latestStatus: String, fetchedCount: Int, shouldCreateGap: Bool
313360
) async {
314361
let topStatus = await datasource.getFiltered().first
315362

@@ -319,7 +366,11 @@ extension TimelineViewModel: GapLoadingFetcher {
319366
// Only create a gap if:
320367
// 1. We fetched a full page (suggesting there might be more)
321368
// 2. AND we have a significant number of actually new statuses
322-
if fetchedCount >= 40 && newStatuses.count >= 40, let oldestNewStatus = newStatuses.last {
369+
if shouldCreateGap,
370+
fetchedCount >= 40,
371+
newStatuses.count >= 40,
372+
let oldestNewStatus = newStatuses.last
373+
{
323374
// Create a gap to load statuses between the oldest new status and our previous top
324375
let gap = TimelineGap(sinceId: latestStatus, maxId: oldestNewStatus.id)
325376
// Insert the gap after all the new statuses
@@ -471,6 +522,7 @@ extension TimelineViewModel: GapLoadingFetcher {
471522

472523
private func createGapForOlderStatuses(sinceId: String? = nil, maxId: String, at index: Int) async
473524
{
525+
guard !isFullTimelineFetchEnabled else { return }
474526
let gap = TimelineGap(sinceId: sinceId, maxId: maxId)
475527
await datasource.insertGap(gap, at: index)
476528
}

Packages/Timeline/Sources/Timeline/actors/TimelineStatusFetcher.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,25 @@ struct TimelineStatusFetcher: TimelineStatusFetching {
4141
async throws -> [Status]
4242
{
4343
guard let client = client else { throw StatusFetcherError.noClientAvailable }
44+
guard maxPages > 0 else { return [] }
45+
46+
var pagesLoaded = 0
4447
var allStatuses: [Status] = []
4548
var latestMinId = minId
46-
let targetCount = 50
47-
48-
for _ in 1...maxPages {
49-
if Task.isCancelled { break }
50-
51-
// If we already have enough statuses, stop fetching
52-
if allStatuses.count >= targetCount { break }
5349

50+
while !Task.isCancelled, pagesLoaded < maxPages {
5451
let newStatuses: [Status] = try await client.get(
5552
endpoint: timeline.endpoint(
5653
sinceId: nil,
5754
maxId: nil,
5855
minId: latestMinId,
5956
offset: nil,
60-
limit: min(40, targetCount - allStatuses.count)
57+
limit: 40
6158
))
6259

6360
if newStatuses.isEmpty { break }
6461

62+
pagesLoaded += 1
6563
allStatuses.insert(contentsOf: newStatuses, at: 0)
6664
latestMinId = newStatuses.first?.id ?? latestMinId
6765
}

0 commit comments

Comments
 (0)