diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index c804bdcd..a0b33eb5 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -39,6 +39,7 @@ struct SettingsTabs: View { accountsSection generalSection otherSections + postStreamingSection AISection cacheSection } @@ -248,6 +249,23 @@ struct SettingsTabs: View { #endif } + @ViewBuilder + private var postStreamingSection: some View { + @Bindable var preferences = preferences + Section { + Toggle(isOn: $preferences.isPostsStreamingEnabled) { + Label("Posts streaming", systemImage: "clock.badge") + } + } header: { + Text("Streaming") + } footer: { + Text("Enabling post streaming will automatically add new posts at the top of your home timeline. Disable if you get performance issues.") + } + #if !os(visionOS) + .listRowBackground(theme.primaryBackgroundColor) + #endif + } + @ViewBuilder private var AISection: some View { @Bindable var preferences = preferences diff --git a/IceCubesApp/Resources/Localization/Localizable.xcstrings b/IceCubesApp/Resources/Localization/Localizable.xcstrings index 651c1233..c832ea76 100644 --- a/IceCubesApp/Resources/Localization/Localizable.xcstrings +++ b/IceCubesApp/Resources/Localization/Localizable.xcstrings @@ -23845,6 +23845,9 @@ }, "Disable to hide AI assisted tools options such as copywritting and alt image description generated from AI. Use OpenAI API. See our Privacy Policy for more information." : { + }, + "Enabling post streaming will automatically add new posts at the top of your home timeline. Disable if you get performance issues." : { + }, "enum.avatar-position.leading" : { "comment" : "MARK: Enums", @@ -42055,6 +42058,9 @@ } } } + }, + "Posts streaming" : { + }, "Privacy Policy" : { "localizations" : { @@ -77147,6 +77153,9 @@ } } } + }, + "Streaming" : { + }, "Tab" : { "localizations" : { diff --git a/Packages/Env/Sources/Env/UserPreferences.swift b/Packages/Env/Sources/Env/UserPreferences.swift index 2a1ae9f2..6fe323c5 100644 --- a/Packages/Env/Sources/Env/UserPreferences.swift +++ b/Packages/Env/Sources/Env/UserPreferences.swift @@ -62,6 +62,8 @@ import SwiftUI @AppStorage("show_account_popover") public var showAccountPopover: Bool = true @AppStorage("sidebar_expanded") public var isSidebarExpanded: Bool = false + + @AppStorage("stream_new_posts") public var isPostsStreamingEnabled: Bool = false init() { prepareTranslationType() @@ -357,6 +359,12 @@ import SwiftUI storage.isSidebarExpanded = isSidebarExpanded } } + + public var isPostsStreamingEnabled: Bool { + didSet { + storage.isPostsStreamingEnabled = isPostsStreamingEnabled + } + } public func getRealMaxIndent() -> UInt { showReplyIndentation ? maxReplyIndentation : 0 @@ -533,6 +541,7 @@ import SwiftUI showAccountPopover = storage.showAccountPopover muteVideo = storage.muteVideo isSidebarExpanded = storage.isSidebarExpanded + isPostsStreamingEnabled = storage.isPostsStreamingEnabled } } diff --git a/Packages/Timeline/Sources/Timeline/View/TimelineViewModel.swift b/Packages/Timeline/Sources/Timeline/View/TimelineViewModel.swift index 5777156f..12b03c3d 100644 --- a/Packages/Timeline/Sources/Timeline/View/TimelineViewModel.swift +++ b/Packages/Timeline/Sources/Timeline/View/TimelineViewModel.swift @@ -391,9 +391,8 @@ extension TimelineViewModel { guard let client = client, canStreamEvents, isTimelineVisible else { return } switch event { - case _ as StreamEventUpdate: - // Removed automatic stream for events. - break + case let updateEvent as StreamEventUpdate: + await handleUpdateEvent(updateEvent, client: client) case let deleteEvent as StreamEventDelete: await handleDeleteEvent(deleteEvent) case let statusUpdateEvent as StreamEventStatusUpdate: @@ -402,6 +401,18 @@ extension TimelineViewModel { break } } + + private func handleUpdateEvent(_ event: StreamEventUpdate, client: Client) async { + guard timeline == .home, + UserPreferences.shared.isPostsStreamingEnabled, + await !datasource.contains(statusId: event.status.id) else { return } + + pendingStatusesObserver.pendingStatuses.insert(event.status.id, at: 0) + await datasource.insert(event.status, at: 0) + await cache() + StatusDataControllerProvider.shared.updateDataControllers(for: [event.status], client: client) + await updateStatusesState() + } private func handleDeleteEvent(_ event: StreamEventDelete) async { if let _ = await datasource.remove(event.status) {