mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-29 03:31:02 +00:00
Add supports for notifications filter API
This commit is contained in:
parent
bb56047ee2
commit
5c32c24ae5
19 changed files with 1260 additions and 39 deletions
|
@ -11,6 +11,7 @@ import Models
|
||||||
import StatusKit
|
import StatusKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Timeline
|
import Timeline
|
||||||
|
import Notifications
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension View {
|
extension View {
|
||||||
|
@ -63,6 +64,12 @@ extension View {
|
||||||
TrendingLinksListView(cards: cards)
|
TrendingLinksListView(cards: cards)
|
||||||
case let .tagsList(tags):
|
case let .tagsList(tags):
|
||||||
TagsListView(tags: tags)
|
TagsListView(tags: tags)
|
||||||
|
case .notificationsRequests:
|
||||||
|
NotificationsRequestsListView()
|
||||||
|
case let .notificationForAccount(accountId):
|
||||||
|
NotificationsListView(lockedType: nil ,
|
||||||
|
lockedAccountId: accountId,
|
||||||
|
scrollToTopSignal: .constant(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,7 @@ struct NavigationSheet<Content: View>: View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
content()
|
content()
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
CloseToolbarItem()
|
||||||
Button {
|
|
||||||
dismiss()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "xmark.circle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34599,6 +34599,839 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"notifications.content-filter.newAccounts" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "New accounts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications.content-filter.peopleNotFollowingYou" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People not following you"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications.content-filter.peopleYouDontFollow" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "People you don't follow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications.content-filter.privateMentions" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Unsolicited private mentions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications.content-filter.requests.title" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filtered notifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications.content-filter.title" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Notifications Filter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications.content-filter.title-inline" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Filter out notifications from…"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications.empty.message" : {
|
"notifications.empty.message" : {
|
||||||
"comment" : "MARK: Package: Notifications",
|
"comment" : "MARK: Package: Notifications",
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
|
@ -38197,7 +39030,7 @@
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Toutes les notifications"
|
"value" : "Notifications"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"it" : {
|
"it" : {
|
||||||
|
|
|
@ -56,11 +56,9 @@ public struct ConversationsListView: View {
|
||||||
message: "conversations.error.message",
|
message: "conversations.error.message",
|
||||||
buttonTitle: "conversations.error.button")
|
buttonTitle: "conversations.error.button")
|
||||||
{
|
{
|
||||||
Task {
|
|
||||||
await viewModel.fetchConversations()
|
await viewModel.fetchConversations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if viewModel.nextPage != nil {
|
if viewModel.nextPage != nil {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct CloseToolbarItem: ToolbarContent {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public var body: some ToolbarContent {
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
Button(action: {
|
||||||
|
dismiss()
|
||||||
|
}, label: {
|
||||||
|
Image(systemName: "xmark.circle")
|
||||||
|
})
|
||||||
|
.keyboardShortcut(.cancelAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ public struct ErrorView: View {
|
||||||
public let title: LocalizedStringKey
|
public let title: LocalizedStringKey
|
||||||
public let message: LocalizedStringKey
|
public let message: LocalizedStringKey
|
||||||
public let buttonTitle: LocalizedStringKey
|
public let buttonTitle: LocalizedStringKey
|
||||||
public let onButtonPress: () -> Void
|
public let onButtonPress: (() async -> Void)
|
||||||
|
|
||||||
public init(title: LocalizedStringKey, message: LocalizedStringKey, buttonTitle: LocalizedStringKey, onButtonPress: @escaping (() -> Void)) {
|
public init(title: LocalizedStringKey, message: LocalizedStringKey, buttonTitle: LocalizedStringKey, onButtonPress: @escaping (() async -> Void) ) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.message = message
|
self.message = message
|
||||||
self.buttonTitle = buttonTitle
|
self.buttonTitle = buttonTitle
|
||||||
|
@ -29,7 +29,9 @@ public struct ErrorView: View {
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Button {
|
Button {
|
||||||
onButtonPress()
|
Task {
|
||||||
|
await onButtonPress()
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Text(buttonTitle)
|
Text(buttonTitle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,10 @@ import Observation
|
||||||
version >= 4.1
|
version >= 4.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var isNotificationsFilterSupported: Bool {
|
||||||
|
version >= 4.3
|
||||||
|
}
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
public func setClient(client: Client) {
|
public func setClient(client: Client) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ public enum RouterDestination: Hashable {
|
||||||
case trendingTimeline
|
case trendingTimeline
|
||||||
case trendingLinks(cards: [Card])
|
case trendingLinks(cards: [Card])
|
||||||
case tagsList(tags: [Tag])
|
case tagsList(tags: [Tag])
|
||||||
|
case notificationsRequests
|
||||||
|
case notificationForAccount(accountId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WindowDestinationEditor: Hashable, Codable {
|
public enum WindowDestinationEditor: Hashable, Codable {
|
||||||
|
|
14
Packages/Models/Sources/Models/NotificationsPolicy.swift
Normal file
14
Packages/Models/Sources/Models/NotificationsPolicy.swift
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct NotificationsPolicy: Codable, Sendable {
|
||||||
|
public var filterNotFollowing: Bool
|
||||||
|
public var filterNotFollowers: Bool
|
||||||
|
public var filterNewAccounts: Bool
|
||||||
|
public var filterPrivateMentions: Bool
|
||||||
|
public let summary: Summary
|
||||||
|
|
||||||
|
public struct Summary: Codable, Sendable {
|
||||||
|
public let pendingRequestsCount: String
|
||||||
|
public let pendingNotificationsCount: String
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct NotificationsRequest: Identifiable, Decodable, Sendable {
|
||||||
|
public let id: String
|
||||||
|
public let account: Account
|
||||||
|
public let notificationsCount: String
|
||||||
|
}
|
|
@ -1,26 +1,54 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Models
|
||||||
|
|
||||||
public enum Notifications: Endpoint {
|
public enum Notifications: Endpoint {
|
||||||
case notifications(minId: String?,
|
case notifications(minId: String?,
|
||||||
maxId: String?,
|
maxId: String?,
|
||||||
types: [String]?,
|
types: [String]?,
|
||||||
limit: Int)
|
limit: Int)
|
||||||
|
case notificationsForAccount(accountId: String, maxId: String?)
|
||||||
case notification(id: String)
|
case notification(id: String)
|
||||||
|
case policy
|
||||||
|
case putPolicy(policy: Models.NotificationsPolicy)
|
||||||
|
case requests
|
||||||
|
case acceptRequest(id: String)
|
||||||
|
case dismissRequest(id: String)
|
||||||
case clear
|
case clear
|
||||||
|
|
||||||
public func path() -> String {
|
public func path() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .notifications:
|
case .notifications, .notificationsForAccount:
|
||||||
"notifications"
|
"notifications"
|
||||||
case let .notification(id):
|
case let .notification(id):
|
||||||
"notifications/\(id)"
|
"notifications/\(id)"
|
||||||
|
case .policy, .putPolicy:
|
||||||
|
"notifications/policy"
|
||||||
|
case .requests:
|
||||||
|
"notifications/requests"
|
||||||
|
case let .acceptRequest(id):
|
||||||
|
"notifications/requests/\(id)/accept"
|
||||||
|
case let .dismissRequest(id):
|
||||||
|
"notifications/requests/\(id)/dismiss"
|
||||||
case .clear:
|
case .clear:
|
||||||
"notifications/clear"
|
"notifications/clear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var jsonValue: (any Encodable)? {
|
||||||
|
switch self {
|
||||||
|
case let .putPolicy(policy):
|
||||||
|
return policy
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
|
case let .notificationsForAccount(accountId, maxId):
|
||||||
|
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: nil) ?? []
|
||||||
|
params.append(.init(name: "account_id", value: accountId))
|
||||||
|
return params
|
||||||
case let .notifications(mindId, maxId, types, limit):
|
case let .notifications(mindId, maxId, types, limit):
|
||||||
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: mindId) ?? []
|
var params = makePaginationParam(sinceId: nil, maxId: maxId, mindId: mindId) ?? []
|
||||||
params.append(.init(name: "limit", value: String(limit)))
|
params.append(.init(name: "limit", value: String(limit)))
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Models
|
||||||
|
import DesignSystem
|
||||||
|
import Env
|
||||||
|
|
||||||
|
struct NotificationsHeaderFilteredView: View {
|
||||||
|
@Environment(Theme.self) private var theme
|
||||||
|
@Environment(RouterPath.self) private var routerPath
|
||||||
|
|
||||||
|
let filteredNotifications: NotificationsPolicy.Summary
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if let count = Int(filteredNotifications.pendingNotificationsCount), count > 0 {
|
||||||
|
HStack {
|
||||||
|
Label("notifications.content-filter.requests.title", systemImage: "archivebox")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Spacer()
|
||||||
|
Text(filteredNotifications.pendingNotificationsCount)
|
||||||
|
.font(.footnote)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(theme.primaryBackgroundColor)
|
||||||
|
.padding(8)
|
||||||
|
.background(.secondary)
|
||||||
|
.clipShape(Circle())
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
routerPath.navigate(to: .notificationsRequests)
|
||||||
|
}
|
||||||
|
.listRowBackground(theme.secondaryBackgroundColor)
|
||||||
|
.listRowInsets(.init(top: 12,
|
||||||
|
leading: .layoutPadding,
|
||||||
|
bottom: 12,
|
||||||
|
trailing: .layoutPadding))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,18 +7,26 @@ import SwiftUI
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct NotificationsListView: View {
|
public struct NotificationsListView: View {
|
||||||
@Environment(\.scenePhase) private var scenePhase
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(StreamWatcher.self) private var watcher
|
@Environment(StreamWatcher.self) private var watcher
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
@Environment(CurrentAccount.self) private var account
|
@Environment(CurrentAccount.self) private var account
|
||||||
|
@Environment(CurrentInstance.self) private var currentInstance
|
||||||
|
|
||||||
@State private var viewModel = NotificationsViewModel()
|
@State private var viewModel = NotificationsViewModel()
|
||||||
|
@State private var isNotificationsPolicyPresented: Bool = false
|
||||||
@Binding var scrollToTopSignal: Int
|
@Binding var scrollToTopSignal: Int
|
||||||
|
|
||||||
let lockedType: Models.Notification.NotificationType?
|
let lockedType: Models.Notification.NotificationType?
|
||||||
|
let lockedAccountId: String?
|
||||||
|
|
||||||
public init(lockedType: Models.Notification.NotificationType?, scrollToTopSignal: Binding<Int>) {
|
public init(lockedType: Models.Notification.NotificationType? = nil,
|
||||||
|
lockedAccountId: String? = nil,
|
||||||
|
scrollToTopSignal: Binding<Int>) {
|
||||||
self.lockedType = lockedType
|
self.lockedType = lockedType
|
||||||
|
self.lockedAccountId = lockedAccountId
|
||||||
_scrollToTopSignal = scrollToTopSignal
|
_scrollToTopSignal = scrollToTopSignal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +35,9 @@ public struct NotificationsListView: View {
|
||||||
List {
|
List {
|
||||||
scrollToTopView
|
scrollToTopView
|
||||||
topPaddingView
|
topPaddingView
|
||||||
|
if lockedAccountId == nil, let summary = viewModel.policy?.summary {
|
||||||
|
NotificationsHeaderFilteredView(filteredNotifications: summary)
|
||||||
|
}
|
||||||
notificationsView
|
notificationsView
|
||||||
}
|
}
|
||||||
.id(account.account?.id)
|
.id(account.account?.id)
|
||||||
|
@ -58,7 +69,7 @@ public struct NotificationsListView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if lockedType == nil {
|
if lockedType == nil && lockedAccountId == nil {
|
||||||
ToolbarTitleMenu {
|
ToolbarTitleMenu {
|
||||||
Button {
|
Button {
|
||||||
viewModel.selectedType = nil
|
viewModel.selectedType = nil
|
||||||
|
@ -83,9 +94,20 @@ public struct NotificationsListView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if currentInstance.isNotificationsFilterSupported {
|
||||||
|
Divider()
|
||||||
|
Button {
|
||||||
|
isNotificationsPolicyPresented = true
|
||||||
|
} label: {
|
||||||
|
Label("notifications.content-filter.title", systemImage: "line.3.horizontal.decrease")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $isNotificationsPolicyPresented) {
|
||||||
|
NotificationsPolicyView()
|
||||||
|
}
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
|
@ -97,11 +119,14 @@ public struct NotificationsListView: View {
|
||||||
if let lockedType {
|
if let lockedType {
|
||||||
viewModel.isLockedType = true
|
viewModel.isLockedType = true
|
||||||
viewModel.selectedType = lockedType
|
viewModel.selectedType = lockedType
|
||||||
|
} else if let lockedAccountId {
|
||||||
|
viewModel.lockedAccountId = lockedAccountId
|
||||||
} else {
|
} else {
|
||||||
viewModel.loadSelectedType()
|
viewModel.loadSelectedType()
|
||||||
}
|
}
|
||||||
Task {
|
Task {
|
||||||
await viewModel.fetchNotifications()
|
await viewModel.fetchNotifications()
|
||||||
|
await viewModel.fetchPolicy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
|
@ -203,10 +228,8 @@ public struct NotificationsListView: View {
|
||||||
message: "notifications.error.message",
|
message: "notifications.error.message",
|
||||||
buttonTitle: "action.retry")
|
buttonTitle: "action.retry")
|
||||||
{
|
{
|
||||||
Task {
|
|
||||||
await viewModel.fetchNotifications()
|
await viewModel.fetchNotifications()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Network
|
||||||
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
struct NotificationsPolicyView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
@Environment(Client.self) private var client
|
||||||
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
|
@State private var policy: NotificationsPolicy?
|
||||||
|
@State private var isUpdating: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
Form {
|
||||||
|
Section("notifications.content-filter.title-inline") {
|
||||||
|
Toggle(isOn: .init(get: { policy?.filterNotFollowing == true },
|
||||||
|
set: { newValue in
|
||||||
|
policy?.filterNotFollowing = newValue
|
||||||
|
Task { await updatePolicy() }
|
||||||
|
}), label: {
|
||||||
|
Text("notifications.content-filter.peopleYouDontFollow")
|
||||||
|
})
|
||||||
|
Toggle(isOn: .init(get: { policy?.filterNotFollowers == true },
|
||||||
|
set: { newValue in
|
||||||
|
policy?.filterNotFollowers = newValue
|
||||||
|
Task { await updatePolicy() }
|
||||||
|
}), label: {
|
||||||
|
Text("notifications.content-filter.peopleNotFollowingYou")
|
||||||
|
})
|
||||||
|
Toggle(isOn: .init(get: { policy?.filterNewAccounts == true },
|
||||||
|
set: { newValue in
|
||||||
|
policy?.filterNewAccounts = newValue
|
||||||
|
Task { await updatePolicy() }
|
||||||
|
}), label: {
|
||||||
|
Text("notifications.content-filter.newAccounts")
|
||||||
|
})
|
||||||
|
Toggle(isOn: .init(get: { policy?.filterPrivateMentions == true },
|
||||||
|
set: { newValue in
|
||||||
|
policy?.filterPrivateMentions = newValue
|
||||||
|
Task { await updatePolicy() }
|
||||||
|
}), label: {
|
||||||
|
Text("notifications.content-filter.privateMentions")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.formStyle(.grouped)
|
||||||
|
.navigationTitle("notifications.content-filter.title")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.toolbar { CloseToolbarItem() }
|
||||||
|
.disabled(policy == nil || isUpdating)
|
||||||
|
.task {
|
||||||
|
await getPolicy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.presentationDetents([.medium])
|
||||||
|
.presentationBackground(.thinMaterial)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getPolicy() async {
|
||||||
|
defer {
|
||||||
|
isUpdating = false
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
isUpdating = true
|
||||||
|
policy = try await client.get(endpoint: Notifications.policy)
|
||||||
|
} catch {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePolicy() async {
|
||||||
|
if let policy {
|
||||||
|
defer {
|
||||||
|
isUpdating = false
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
isUpdating = true
|
||||||
|
self.policy = try await client.put(endpoint: Notifications.putPolicy(policy: policy))
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,6 +39,8 @@ import SwiftUI
|
||||||
private let filterKey = "notification-filter"
|
private let filterKey = "notification-filter"
|
||||||
var state: State = .loading
|
var state: State = .loading
|
||||||
var isLockedType: Bool = false
|
var isLockedType: Bool = false
|
||||||
|
var lockedAccountId: String? = nil
|
||||||
|
var policy: Models.NotificationsPolicy?
|
||||||
var selectedType: Models.Notification.NotificationType? {
|
var selectedType: Models.Notification.NotificationType? {
|
||||||
didSet {
|
didSet {
|
||||||
guard oldValue != selectedType,
|
guard oldValue != selectedType,
|
||||||
|
@ -82,11 +84,16 @@ import SwiftUI
|
||||||
var nextPageState: State.PagingState = .hasNextPage
|
var nextPageState: State.PagingState = .hasNextPage
|
||||||
if consolidatedNotifications.isEmpty {
|
if consolidatedNotifications.isEmpty {
|
||||||
state = .loading
|
state = .loading
|
||||||
let notifications: [Models.Notification] =
|
let notifications: [Models.Notification]
|
||||||
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
if let lockedAccountId {
|
||||||
|
notifications = try await client.get(endpoint: Notifications.notificationsForAccount(accountId: lockedAccountId,
|
||||||
|
maxId: nil))
|
||||||
|
} else {
|
||||||
|
notifications = try await client.get(endpoint: Notifications.notifications(minId: nil,
|
||||||
maxId: nil,
|
maxId: nil,
|
||||||
types: queryTypes,
|
types: queryTypes,
|
||||||
limit: Constants.notificationLimit))
|
limit: Constants.notificationLimit))
|
||||||
|
}
|
||||||
consolidatedNotifications = await notifications.consolidated(selectedType: selectedType)
|
consolidatedNotifications = await notifications.consolidated(selectedType: selectedType)
|
||||||
markAsRead()
|
markAsRead()
|
||||||
nextPageState = notifications.count < Constants.notificationLimit ? .none : .hasNextPage
|
nextPageState = notifications.count < Constants.notificationLimit ? .none : .hasNextPage
|
||||||
|
@ -119,7 +126,7 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchNewPages(minId: String, maxPages: Int) async -> [Models.Notification] {
|
private func fetchNewPages(minId: String, maxPages: Int) async -> [Models.Notification] {
|
||||||
guard let client else { return [] }
|
guard let client, lockedAccountId == nil else { return [] }
|
||||||
var pagesLoaded = 0
|
var pagesLoaded = 0
|
||||||
var allNotifications: [Models.Notification] = []
|
var allNotifications: [Models.Notification] = []
|
||||||
var latestMinId = minId
|
var latestMinId = minId
|
||||||
|
@ -146,11 +153,17 @@ import SwiftUI
|
||||||
func fetchNextPage() async throws {
|
func fetchNextPage() async throws {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
guard let lastId = consolidatedNotifications.last?.notificationIds.last else { return }
|
guard let lastId = consolidatedNotifications.last?.notificationIds.last else { return }
|
||||||
let newNotifications: [Models.Notification] =
|
let newNotifications: [Models.Notification]
|
||||||
|
if let lockedAccountId {
|
||||||
|
newNotifications =
|
||||||
|
try await client.get(endpoint: Notifications.notificationsForAccount(accountId: lockedAccountId, maxId: lastId))
|
||||||
|
} else {
|
||||||
|
newNotifications =
|
||||||
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
||||||
maxId: lastId,
|
maxId: lastId,
|
||||||
types: queryTypes,
|
types: queryTypes,
|
||||||
limit: Constants.notificationLimit))
|
limit: Constants.notificationLimit))
|
||||||
|
}
|
||||||
await consolidatedNotifications.append(contentsOf: newNotifications.consolidated(selectedType: selectedType))
|
await consolidatedNotifications.append(contentsOf: newNotifications.consolidated(selectedType: selectedType))
|
||||||
if consolidatedNotifications.contains(where: { $0.type == .follow_request }) {
|
if consolidatedNotifications.contains(where: { $0.type == .follow_request }) {
|
||||||
await currentAccount?.fetchFollowerRequests()
|
await currentAccount?.fetchFollowerRequests()
|
||||||
|
@ -168,12 +181,17 @@ import SwiftUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchPolicy() async {
|
||||||
|
policy = try? await client?.get(endpoint: Notifications.policy)
|
||||||
|
}
|
||||||
|
|
||||||
func handleEvent(event: any StreamEvent) {
|
func handleEvent(event: any StreamEvent) {
|
||||||
Task {
|
Task {
|
||||||
// Check if the event is a notification,
|
// Check if the event is a notification,
|
||||||
// if it is not already in the list,
|
// if it is not already in the list,
|
||||||
// and if it can be shown (no selected type or the same as the received notification type)
|
// and if it can be shown (no selected type or the same as the received notification type)
|
||||||
if let event = event as? StreamEventNotification,
|
if lockedAccountId == nil,
|
||||||
|
let event = event as? StreamEventNotification,
|
||||||
!consolidatedNotifications.flatMap(\.notificationIds).contains(event.notification.id),
|
!consolidatedNotifications.flatMap(\.notificationIds).contains(event.notification.id),
|
||||||
selectedType == nil || selectedType?.rawValue == event.notification.type
|
selectedType == nil || selectedType?.rawValue == event.notification.type
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Network
|
||||||
|
import Models
|
||||||
|
import DesignSystem
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public struct NotificationsRequestsListView: View {
|
||||||
|
@Environment(Client.self) private var client
|
||||||
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
|
enum ViewState {
|
||||||
|
case loading
|
||||||
|
case error
|
||||||
|
case requests(_ data: [NotificationsRequest])
|
||||||
|
}
|
||||||
|
@State private var viewState: ViewState = .loading
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
List {
|
||||||
|
switch viewState {
|
||||||
|
case .loading:
|
||||||
|
ProgressView()
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
.listSectionSeparator(.hidden)
|
||||||
|
case .error:
|
||||||
|
ErrorView(title: "notifications.error.title",
|
||||||
|
message: "notifications.error.message",
|
||||||
|
buttonTitle: "action.retry")
|
||||||
|
{
|
||||||
|
await fetchRequests()
|
||||||
|
}
|
||||||
|
#if !os(visionOS)
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
.listSectionSeparator(.hidden)
|
||||||
|
case let .requests(data):
|
||||||
|
ForEach(data) { request in
|
||||||
|
NotificationsRequestsRowView(request: request)
|
||||||
|
.swipeActions {
|
||||||
|
Button {
|
||||||
|
Task { await acceptRequest(request) }
|
||||||
|
} label: {
|
||||||
|
Label("account.follow-request.accept", systemImage: "checkmark")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Task { await dismissRequest(request) }
|
||||||
|
} label: {
|
||||||
|
Label("account.follow-request.reject", systemImage: "xmark")
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.plain)
|
||||||
|
#if !os(visionOS)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.background(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
.navigationTitle("notifications.content-filter.requests.title")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.task {
|
||||||
|
await fetchRequests()
|
||||||
|
}
|
||||||
|
.refreshable {
|
||||||
|
await fetchRequests()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchRequests() async {
|
||||||
|
do {
|
||||||
|
viewState = .requests(try await client.get(endpoint: Notifications.requests))
|
||||||
|
} catch {
|
||||||
|
viewState = .error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func acceptRequest(_ request: NotificationsRequest) async {
|
||||||
|
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id))
|
||||||
|
await fetchRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismissRequest(_ request: NotificationsRequest) async {
|
||||||
|
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id))
|
||||||
|
await fetchRequests()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import SwiftUI
|
||||||
|
import Models
|
||||||
|
import DesignSystem
|
||||||
|
import Env
|
||||||
|
import Network
|
||||||
|
|
||||||
|
struct NotificationsRequestsRowView: View {
|
||||||
|
@Environment(Theme.self) private var theme
|
||||||
|
@Environment(RouterPath.self) private var routerPath
|
||||||
|
@Environment(Client.self) private var client
|
||||||
|
|
||||||
|
let request: NotificationsRequest
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .center, spacing: 8) {
|
||||||
|
AvatarView(request.account.avatar, config: .embed)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
EmojiTextApp(request.account.cachedDisplayName, emojis: request.account.emojis)
|
||||||
|
.font(.scaledBody)
|
||||||
|
.foregroundStyle(theme.labelColor)
|
||||||
|
.lineLimit(1)
|
||||||
|
Text(request.account.acct)
|
||||||
|
.font(.scaledFootnote)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
Spacer()
|
||||||
|
Text(request.notificationsCount)
|
||||||
|
.font(.footnote)
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(theme.primaryBackgroundColor)
|
||||||
|
.padding(8)
|
||||||
|
.background(.secondary)
|
||||||
|
.clipShape(Circle())
|
||||||
|
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
|
||||||
|
}
|
||||||
|
.listRowInsets(.init(top: 12,
|
||||||
|
leading: .layoutPadding,
|
||||||
|
bottom: 12,
|
||||||
|
trailing: .layoutPadding))
|
||||||
|
#if os(visionOS)
|
||||||
|
.listRowBackground(RoundedRectangle(cornerRadius: 8)
|
||||||
|
.foregroundStyle(.background))
|
||||||
|
#else
|
||||||
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,9 +152,7 @@ public struct StatusDetailView: View {
|
||||||
message: "status.error.message",
|
message: "status.error.message",
|
||||||
buttonTitle: "action.retry")
|
buttonTitle: "action.retry")
|
||||||
{
|
{
|
||||||
Task {
|
_ = await viewModel.fetch()
|
||||||
await viewModel.fetch()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
|
|
@ -38,10 +38,8 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
||||||
message: "status.error.loading.message",
|
message: "status.error.loading.message",
|
||||||
buttonTitle: "action.retry")
|
buttonTitle: "action.retry")
|
||||||
{
|
{
|
||||||
Task {
|
|
||||||
await fetcher.fetchNewestStatuses(pullToRefresh: false)
|
await fetcher.fetchNewestStatuses(pullToRefresh: false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue