mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-01-25 23:38:06 +00:00
Add timeline content filter
This commit is contained in:
parent
801b6c5682
commit
2f5307bfc7
14 changed files with 757 additions and 164 deletions
|
@ -114,6 +114,8 @@ extension View {
|
||||||
ActivityView(image: image, status: status)
|
ActivityView(image: image, status: status)
|
||||||
case let .editTagGroup(tagGroup, onSaved):
|
case let .editTagGroup(tagGroup, onSaved):
|
||||||
EditTagGroupView(tagGroup: tagGroup, onSaved: onSaved)
|
EditTagGroupView(tagGroup: tagGroup, onSaved: onSaved)
|
||||||
|
case .timelineContentFilter:
|
||||||
|
NavigationSheet { TimelineContentFilterView() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.withEnvironments()
|
.withEnvironments()
|
||||||
|
|
|
@ -6,24 +6,18 @@ import Network
|
||||||
import NukeUI
|
import NukeUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
import Timeline
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct ContentSettingsView: View {
|
struct ContentSettingsView: View {
|
||||||
@Environment(UserPreferences.self) private var userPreferences
|
@Environment(UserPreferences.self) private var userPreferences
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
|
@State private var contentFilter = TimelineContentFilter.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@Bindable var userPreferences = userPreferences
|
@Bindable var userPreferences = userPreferences
|
||||||
Form {
|
Form {
|
||||||
Section("settings.content.boosts") {
|
|
||||||
Toggle(isOn: $userPreferences.suppressDupeReblogs) {
|
|
||||||
Text("settings.content.hide-repeated-boosts")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if !os(visionOS)
|
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Section("settings.content.media") {
|
Section("settings.content.media") {
|
||||||
Toggle(isOn: $userPreferences.autoPlayVideo) {
|
Toggle(isOn: $userPreferences.autoPlayVideo) {
|
||||||
Text("settings.other.autoplay-video")
|
Text("settings.other.autoplay-video")
|
||||||
|
@ -122,6 +116,24 @@ struct ContentSettingsView: View {
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Section("timeline.content-filter.title") {
|
||||||
|
Toggle(isOn: $contentFilter.showBoosts) {
|
||||||
|
Label("timeline.filter.show-boosts", image: "Rocket")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $contentFilter.showReplies) {
|
||||||
|
Label("timeline.filter.show-replies", systemImage: "bubble.left.and.bubble.right")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $contentFilter.showThreads) {
|
||||||
|
Label("timeline.filter.show-threads", systemImage: "bubble.left.and.text.bubble.right")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $contentFilter.showQuotePosts) {
|
||||||
|
Label("timeline.filter.show-quote", systemImage: "quote.bubble")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.navigationTitle("settings.content.navigation-title")
|
.navigationTitle("settings.content.navigation-title")
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
|
|
|
@ -124,7 +124,10 @@ struct TimelineTab: View {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var timelineFilterButton: some View {
|
private var timelineFilterButton: some View {
|
||||||
latestOrResumeButtons
|
latestOrResumeButtons
|
||||||
|
contentFilterButton
|
||||||
|
Divider()
|
||||||
pinMenuButton
|
pinMenuButton
|
||||||
|
Divider()
|
||||||
timelineFiltersButtons
|
timelineFiltersButtons
|
||||||
listsFiltersButons
|
listsFiltersButons
|
||||||
tagsFiltersButtons
|
tagsFiltersButtons
|
||||||
|
@ -203,7 +206,6 @@ struct TimelineTab: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Divider()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,8 +227,6 @@ struct TimelineTab: View {
|
||||||
Label("status.action.pin", systemImage: "pin")
|
Label("status.action.pin", systemImage: "pin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var timelineFiltersButtons: some View {
|
private var timelineFiltersButtons: some View {
|
||||||
|
@ -312,6 +312,14 @@ struct TimelineTab: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var contentFilterButton: some View {
|
||||||
|
Button(action: {
|
||||||
|
routerPath.presentedSheet = .timelineContentFilter
|
||||||
|
}, label: {
|
||||||
|
Label("timeline.content-filter.title", systemSymbol: .line3HorizontalDecrease)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func resetTimelineFilter() {
|
private func resetTimelineFilter() {
|
||||||
if client.isAuth, canFilterTimeline {
|
if client.isAuth, canFilterTimeline {
|
||||||
timeline = lastTimelineFilter
|
timeline = lastTimelineFilter
|
||||||
|
|
|
@ -40122,6 +40122,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings.content.boosts" : {
|
"settings.content.boosts" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"be" : {
|
"be" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
|
@ -41066,6 +41067,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings.content.hide-repeated-boosts" : {
|
"settings.content.hide-repeated-boosts" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"be" : {
|
"be" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
|
@ -73453,6 +73455,124 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timeline.content-filter.title" : {
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Content Filter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"timeline.federated" : {
|
"timeline.federated" : {
|
||||||
"comment" : "MARK: Package: Timeline",
|
"comment" : "MARK: Package: Timeline",
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
|
@ -74163,6 +74283,482 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timeline.filter.show-boosts" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Boosts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeline.filter.show-quote" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Quotes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeline.filter.show-replies" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Replies"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeline.filter.show-threads" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Show Threads"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"timeline.filter.tag-groups" : {
|
"timeline.filter.tag-groups" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"be" : {
|
"be" : {
|
||||||
|
|
|
@ -57,6 +57,7 @@ public enum SheetDestination: Identifiable {
|
||||||
case report(status: Status)
|
case report(status: Status)
|
||||||
case shareImage(image: UIImage, status: Status)
|
case shareImage(image: UIImage, status: Status)
|
||||||
case editTagGroup(tagGroup: TagGroup, onSaved: ((TagGroup) -> Void)?)
|
case editTagGroup(tagGroup: TagGroup, onSaved: ((TagGroup) -> Void)?)
|
||||||
|
case timelineContentFilter
|
||||||
|
|
||||||
public var id: String {
|
public var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -85,6 +86,8 @@ public enum SheetDestination: Identifiable {
|
||||||
"editTagGroup"
|
"editTagGroup"
|
||||||
case .settings, .support, .about, .accountPushNotficationsSettings:
|
case .settings, .support, .about, .accountPushNotficationsSettings:
|
||||||
"settings"
|
"settings"
|
||||||
|
case .timelineContentFilter:
|
||||||
|
"timelineContentFilter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,6 @@ import SwiftUI
|
||||||
@AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true
|
@AppStorage("user_deepl_api_free") public var userDeeplAPIFree = true
|
||||||
@AppStorage("auto_detect_post_language") public var autoDetectPostLanguage = true
|
@AppStorage("auto_detect_post_language") public var autoDetectPostLanguage = true
|
||||||
|
|
||||||
@AppStorage("suppress_dupe_reblogs") public var suppressDupeReblogs: Bool = false
|
|
||||||
|
|
||||||
@AppStorage("inAppBrowserReaderView") public var inAppBrowserReaderView = false
|
@AppStorage("inAppBrowserReaderView") public var inAppBrowserReaderView = false
|
||||||
|
|
||||||
@AppStorage("haptic_tab") public var hapticTabSelectionEnabled = true
|
@AppStorage("haptic_tab") public var hapticTabSelectionEnabled = true
|
||||||
|
@ -197,12 +195,6 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var suppressDupeReblogs: Bool {
|
|
||||||
didSet {
|
|
||||||
storage.suppressDupeReblogs = suppressDupeReblogs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var inAppBrowserReaderView: Bool {
|
public var inAppBrowserReaderView: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
storage.inAppBrowserReaderView = inAppBrowserReaderView
|
storage.inAppBrowserReaderView = inAppBrowserReaderView
|
||||||
|
@ -478,7 +470,6 @@ import SwiftUI
|
||||||
alwaysUseDeepl = storage.alwaysUseDeepl
|
alwaysUseDeepl = storage.alwaysUseDeepl
|
||||||
userDeeplAPIFree = storage.userDeeplAPIFree
|
userDeeplAPIFree = storage.userDeeplAPIFree
|
||||||
autoDetectPostLanguage = storage.autoDetectPostLanguage
|
autoDetectPostLanguage = storage.autoDetectPostLanguage
|
||||||
suppressDupeReblogs = storage.suppressDupeReblogs
|
|
||||||
inAppBrowserReaderView = storage.inAppBrowserReaderView
|
inAppBrowserReaderView = storage.inAppBrowserReaderView
|
||||||
hapticTabSelectionEnabled = storage.hapticTabSelectionEnabled
|
hapticTabSelectionEnabled = storage.hapticTabSelectionEnabled
|
||||||
hapticTimelineEnabled = storage.hapticTimelineEnabled
|
hapticTimelineEnabled = storage.hapticTimelineEnabled
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
import Env
|
|
||||||
import Foundation
|
|
||||||
import LRUCache
|
|
||||||
import Models
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
public class ReblogCache: @unchecked Sendable {
|
|
||||||
struct CacheEntry: Codable {
|
|
||||||
var reblogId: String
|
|
||||||
var postId: String
|
|
||||||
var seen: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
public static let shared = ReblogCache()
|
|
||||||
var statusCache = LRUCache<String, CacheEntry>()
|
|
||||||
private var needsWrite = false
|
|
||||||
|
|
||||||
init() {
|
|
||||||
statusCache.countLimit = 300 // can tune the cache here, 100 is super conservative
|
|
||||||
|
|
||||||
// read any existing cache from disk
|
|
||||||
if FileManager.default.fileExists(atPath: cacheFile.path()) {
|
|
||||||
do {
|
|
||||||
let data = try Data(contentsOf: cacheFile)
|
|
||||||
let cacheData = try JSONDecoder().decode([CacheEntry].self, from: data)
|
|
||||||
for entry in cacheData {
|
|
||||||
statusCache.setValue(entry, forKey: entry.reblogId)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Error reading cache from disc")
|
|
||||||
}
|
|
||||||
print("Starting cache has \(statusCache.count) items")
|
|
||||||
}
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 30.0) { [weak self] in
|
|
||||||
self?.saveCache()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func saveCache() {
|
|
||||||
if needsWrite {
|
|
||||||
do {
|
|
||||||
let data = try JSONEncoder().encode(statusCache.allValues)
|
|
||||||
try data.write(to: cacheFile)
|
|
||||||
} catch {
|
|
||||||
print("Error writing cache to disc")
|
|
||||||
}
|
|
||||||
needsWrite = false
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 30.0) { [weak self] in
|
|
||||||
self?.saveCache()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var cacheFile: URL {
|
|
||||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
|
||||||
let documentsDirectory = paths[0]
|
|
||||||
|
|
||||||
return URL(fileURLWithPath: documentsDirectory.path()).appendingPathComponent("reblog.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor public func removeDuplicateReblogs(_ statuses: inout [Status]) {
|
|
||||||
if !UserPreferences.shared.suppressDupeReblogs {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var i = statuses.count
|
|
||||||
|
|
||||||
for status in statuses.reversed() {
|
|
||||||
// go backwards through the status list
|
|
||||||
// so that we can remove items without
|
|
||||||
// borking the array
|
|
||||||
|
|
||||||
i -= 1
|
|
||||||
if let reblog = status.reblog {
|
|
||||||
if let cached = statusCache.value(forKey: reblog.id) {
|
|
||||||
// this is already cached
|
|
||||||
if cached.postId != status.id, cached.seen {
|
|
||||||
// This was posted by someone other than the person we have in the cache
|
|
||||||
// and we have seen the items at some point, so we might want to suppress it
|
|
||||||
|
|
||||||
if status.account.id != CurrentAccount.shared.account?.id {
|
|
||||||
// just a quick check to makes sure that this wasn't boosted by the current
|
|
||||||
// user. Hiding that would be confusing
|
|
||||||
// But assuming it isn't then we can suppress this boost
|
|
||||||
print("suppressing: \(reblog.id)/ \(String(describing: reblog.account.displayName)) by \(String(describing: status.account.displayName))")
|
|
||||||
statuses.remove(at: i)
|
|
||||||
// assert(statuses.count == (ct-1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cache(status, seen: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func cache(_ status: Status, seen: Bool) {
|
|
||||||
var wasSeen = false
|
|
||||||
var postToCache = status.id
|
|
||||||
|
|
||||||
if let reblog = status.reblog {
|
|
||||||
// only caching boosts at the moment.
|
|
||||||
|
|
||||||
if let cached = statusCache.value(forKey: reblog.id) {
|
|
||||||
// every time we see it, we refresh it in the list
|
|
||||||
// so poplular things are kept in the cache
|
|
||||||
|
|
||||||
wasSeen = cached.seen
|
|
||||||
|
|
||||||
if wasSeen {
|
|
||||||
postToCache = cached.postId
|
|
||||||
// if we have seen a particular version of the post
|
|
||||||
// that's the one we keep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statusCache.setValue(CacheEntry(reblogId: reblog.id, postId: postToCache, seen: seen || wasSeen), forKey: reblog.id)
|
|
||||||
needsWrite = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -106,7 +106,6 @@ public struct StatusRowView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.markSeen()
|
|
||||||
if !reasons.contains(.placeholder) {
|
if !reasons.contains(.placeholder) {
|
||||||
if !isCompact, viewModel.embeddedStatus == nil {
|
if !isCompact, viewModel.embeddedStatus == nil {
|
||||||
Task {
|
Task {
|
||||||
|
|
|
@ -151,19 +151,6 @@ import SwiftUI
|
||||||
recalcCollapse()
|
recalcCollapse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func markSeen() {
|
|
||||||
// called in on appear so we can cache that the status has been seen.
|
|
||||||
if UserPreferences.shared.suppressDupeReblogs, !seen {
|
|
||||||
DispatchQueue.global().async { [weak self] in
|
|
||||||
guard let self else { return }
|
|
||||||
ReblogCache.shared.cache(status, seen: true)
|
|
||||||
Task { @MainActor in
|
|
||||||
self.seen = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func navigateToDetail() {
|
func navigateToDetail() {
|
||||||
if isRemote, let url = URL(string: finalStatus.url ?? "") {
|
if isRemote, let url = URL(string: finalStatus.url ?? "") {
|
||||||
routerPath.navigate(to: .remoteStatusDetail(url: url))
|
routerPath.navigate(to: .remoteStatusDetail(url: url))
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
@Observable public class TimelineContentFilter {
|
||||||
|
class Storage {
|
||||||
|
@AppStorage("timeline_show_boosts") var showBoosts: Bool = true
|
||||||
|
@AppStorage("timeline_show_replies") var showReplies: Bool = true
|
||||||
|
@AppStorage("timeline_show_threads") var showThreads: Bool = true
|
||||||
|
@AppStorage("timeline_quote_posts") var showQuotePosts: Bool = true
|
||||||
|
}
|
||||||
|
|
||||||
|
public static let shared = TimelineContentFilter()
|
||||||
|
private let storage = Storage()
|
||||||
|
|
||||||
|
public var showBoosts: Bool {
|
||||||
|
didSet {
|
||||||
|
storage.showBoosts = showBoosts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var showReplies: Bool {
|
||||||
|
didSet {
|
||||||
|
storage.showReplies = showReplies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var showThreads: Bool {
|
||||||
|
didSet {
|
||||||
|
storage.showThreads = showThreads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var showQuotePosts: Bool {
|
||||||
|
didSet {
|
||||||
|
storage.showQuotePosts = showQuotePosts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
showBoosts = storage.showBoosts
|
||||||
|
showReplies = storage.showReplies
|
||||||
|
showThreads = storage.showThreads
|
||||||
|
showQuotePosts = storage.showQuotePosts
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import SwiftUI
|
||||||
|
import DesignSystem
|
||||||
|
|
||||||
|
public struct TimelineContentFilterView: View {
|
||||||
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
|
@State private var contentFilter = TimelineContentFilter.shared
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $contentFilter.showBoosts) {
|
||||||
|
Label("timeline.filter.show-boosts", image: "Rocket")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $contentFilter.showReplies) {
|
||||||
|
Label("timeline.filter.show-replies", systemImage: "bubble.left.and.bubble.right")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $contentFilter.showThreads) {
|
||||||
|
Label("timeline.filter.show-threads", systemImage: "bubble.left.and.text.bubble.right")
|
||||||
|
}
|
||||||
|
Toggle(isOn: $contentFilter.showQuotePosts) {
|
||||||
|
Label("timeline.filter.show-quote", systemImage: "quote.bubble")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.navigationTitle("timeline.content-filter.title")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
#if !os(visionOS)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.secondaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ public struct TimelineView: View {
|
||||||
|
|
||||||
@State private var viewModel = TimelineViewModel()
|
@State private var viewModel = TimelineViewModel()
|
||||||
@State private var prefetcher = TimelineMediaPrefetcher()
|
@State private var prefetcher = TimelineMediaPrefetcher()
|
||||||
|
@State private var contentFilter = TimelineContentFilter.shared
|
||||||
|
|
||||||
@State private var wasBackgrounded: Bool = false
|
@State private var wasBackgrounded: Bool = false
|
||||||
@State private var collectionView: UICollectionView?
|
@State private var collectionView: UICollectionView?
|
||||||
|
@ -166,6 +167,18 @@ public struct TimelineView: View {
|
||||||
.onChange(of: viewModel.timeline) { _, newValue in
|
.onChange(of: viewModel.timeline) { _, newValue in
|
||||||
timeline = newValue
|
timeline = newValue
|
||||||
}
|
}
|
||||||
|
.onChange(of: contentFilter.showReplies) { _, _ in
|
||||||
|
Task { await viewModel.refreshTimelineContentFilter() }
|
||||||
|
}
|
||||||
|
.onChange(of: contentFilter.showBoosts) { _, _ in
|
||||||
|
Task { await viewModel.refreshTimelineContentFilter() }
|
||||||
|
}
|
||||||
|
.onChange(of: contentFilter.showThreads) { _, _ in
|
||||||
|
Task { await viewModel.refreshTimelineContentFilter() }
|
||||||
|
}
|
||||||
|
.onChange(of: contentFilter.showQuotePosts) { _, _ in
|
||||||
|
Task { await viewModel.refreshTimelineContentFilter() }
|
||||||
|
}
|
||||||
.onChange(of: scenePhase) { _, newValue in
|
.onChange(of: scenePhase) { _, newValue in
|
||||||
switch newValue {
|
switch newValue {
|
||||||
case .active:
|
case .active:
|
||||||
|
|
|
@ -205,6 +205,14 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refreshTimelineContentFilter() async {
|
||||||
|
timelineTask?.cancel()
|
||||||
|
let statuses = await datasource.getFiltered()
|
||||||
|
withAnimation {
|
||||||
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fetchStatuses(from: Marker.Content) async throws {
|
func fetchStatuses(from: Marker.Content) async throws {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
statusesState = .loading
|
statusesState = .loading
|
||||||
|
@ -213,7 +221,6 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
minId: nil,
|
minId: nil,
|
||||||
offset: 0))
|
offset: 0))
|
||||||
|
|
||||||
ReblogCache.shared.removeDuplicateReblogs(&statuses)
|
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||||
|
|
||||||
await datasource.set(statuses)
|
await datasource.set(statuses)
|
||||||
|
@ -284,7 +291,6 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
minId: nil,
|
minId: nil,
|
||||||
offset: 0))
|
offset: 0))
|
||||||
|
|
||||||
ReblogCache.shared.removeDuplicateReblogs(&statuses)
|
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||||
|
|
||||||
await datasource.set(statuses)
|
await datasource.set(statuses)
|
||||||
|
@ -309,7 +315,6 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
!ids.contains(where: { $0 == status.id })
|
!ids.contains(where: { $0 == status.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
ReblogCache.shared.removeDuplicateReblogs(&newStatuses)
|
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
||||||
|
|
||||||
// If no new statuses, resume streaming and exit.
|
// If no new statuses, resume streaming and exit.
|
||||||
|
@ -401,7 +406,6 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
{
|
{
|
||||||
pagesLoaded += 1
|
pagesLoaded += 1
|
||||||
|
|
||||||
ReblogCache.shared.removeDuplicateReblogs(&newStatuses)
|
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
||||||
|
|
||||||
allStatuses.insert(contentsOf: newStatuses, at: 0)
|
allStatuses.insert(contentsOf: newStatuses, at: 0)
|
||||||
|
@ -424,7 +428,6 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
minId: nil,
|
minId: nil,
|
||||||
offset: statuses.count))
|
offset: statuses.count))
|
||||||
|
|
||||||
ReblogCache.shared.removeDuplicateReblogs(&newStatuses)
|
|
||||||
|
|
||||||
await datasource.append(contentOf: newStatuses)
|
await datasource.append(contentOf: newStatuses)
|
||||||
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
StatusDataControllerProvider.shared.updateDataControllers(for: newStatuses, client: client)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Models
|
import Models
|
||||||
|
import Env
|
||||||
|
|
||||||
actor TimelineDatasource {
|
actor TimelineDatasource {
|
||||||
private var statuses: [Status] = []
|
private var statuses: [Status] = []
|
||||||
|
@ -12,8 +13,22 @@ actor TimelineDatasource {
|
||||||
statuses
|
statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFiltered() -> [Status] {
|
func getFiltered() async -> [Status] {
|
||||||
statuses.filter{ !$0.isHidden }
|
let contentFilter = TimelineContentFilter.shared
|
||||||
|
let showReplies = await contentFilter.showReplies
|
||||||
|
let showBoosts = await contentFilter.showBoosts
|
||||||
|
let showThreads = await contentFilter.showThreads
|
||||||
|
let showQuotePosts = await contentFilter.showQuotePosts
|
||||||
|
return statuses.filter { status in
|
||||||
|
if status.isHidden ||
|
||||||
|
!showReplies && status.inReplyToId != nil && status.inReplyToAccountId != status.account.id ||
|
||||||
|
!showBoosts && status.reblog != nil ||
|
||||||
|
!showThreads && status.inReplyToAccountId == status.account.id ||
|
||||||
|
!showQuotePosts && !status.content.statusesURLs.isEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func count() -> Int {
|
func count() -> Int {
|
||||||
|
|
Loading…
Reference in a new issue