mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-06-02 13:29:41 +00:00
Lint
This commit is contained in:
parent
ba4cc899f8
commit
c3edabb183
|
@ -8,10 +8,10 @@ import LinkPresentation
|
||||||
import Lists
|
import Lists
|
||||||
import MediaUI
|
import MediaUI
|
||||||
import Models
|
import Models
|
||||||
|
import Notifications
|
||||||
import StatusKit
|
import StatusKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Timeline
|
import Timeline
|
||||||
import Notifications
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
extension View {
|
extension View {
|
||||||
|
@ -67,7 +67,7 @@ extension View {
|
||||||
case .notificationsRequests:
|
case .notificationsRequests:
|
||||||
NotificationsRequestsListView()
|
NotificationsRequestsListView()
|
||||||
case let .notificationForAccount(accountId):
|
case let .notificationForAccount(accountId):
|
||||||
NotificationsListView(lockedType: nil ,
|
NotificationsListView(lockedType: nil,
|
||||||
lockedAccountId: accountId,
|
lockedAccountId: accountId,
|
||||||
scrollToTopSignal: .constant(0))
|
scrollToTopSignal: .constant(0))
|
||||||
case .blockedAccounts:
|
case .blockedAccounts:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import AppIntents
|
||||||
import Env
|
import Env
|
||||||
import MediaUI
|
import MediaUI
|
||||||
import StatusKit
|
import StatusKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppIntents
|
|
||||||
|
|
||||||
extension IceCubesApp {
|
extension IceCubesApp {
|
||||||
var appScene: some Scene {
|
var appScene: some Scene {
|
||||||
|
@ -125,20 +125,21 @@ extension IceCubesApp {
|
||||||
.defaultSize(width: 1200, height: 1000)
|
.defaultSize(width: 1200, height: 1000)
|
||||||
.windowResizability(.contentMinSize)
|
.windowResizability(.contentMinSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleIntent(_ intent: any AppIntent) {
|
private func handleIntent(_: any AppIntent) {
|
||||||
if let postIntent = appIntentService.handledIntent?.intent as? PostIntent {
|
if let postIntent = appIntentService.handledIntent?.intent as? PostIntent {
|
||||||
#if os(visionOS) || os(macOS)
|
#if os(visionOS) || os(macOS)
|
||||||
openWindow(value: WindowDestinationEditor.prefilledStatusEditor(text: postIntent.content ?? "",
|
openWindow(value: WindowDestinationEditor.prefilledStatusEditor(text: postIntent.content ?? "",
|
||||||
visibility: userPreferences.postVisibility))
|
visibility: userPreferences.postVisibility))
|
||||||
#else
|
#else
|
||||||
appRouterPath.presentedSheet = .prefilledStatusEditor(text: postIntent.content ?? "",
|
appRouterPath.presentedSheet = .prefilledStatusEditor(text: postIntent.content ?? "",
|
||||||
visibility: userPreferences.postVisibility)
|
visibility: userPreferences.postVisibility)
|
||||||
#endif
|
#endif
|
||||||
} else if let tabIntent = appIntentService.handledIntent?.intent as? TabIntent {
|
} else if let tabIntent = appIntentService.handledIntent?.intent as? TabIntent {
|
||||||
selectedTab = tabIntent.tab.toAppTab
|
selectedTab = tabIntent.tab.toAppTab
|
||||||
} else if let imageIntent = appIntentService.handledIntent?.intent as? PostImageIntent,
|
} else if let imageIntent = appIntentService.handledIntent?.intent as? PostImageIntent,
|
||||||
let urls = imageIntent.images?.compactMap({ $0.fileURL }) {
|
let urls = imageIntent.images?.compactMap({ $0.fileURL })
|
||||||
|
{
|
||||||
appRouterPath.presentedSheet = .imageURL(urls: urls,
|
appRouterPath.presentedSheet = .imageURL(urls: urls,
|
||||||
visibility: userPreferences.postVisibility)
|
visibility: userPreferences.postVisibility)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import Account
|
import Account
|
||||||
|
import AppIntents
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Explore
|
import Explore
|
||||||
import Foundation
|
import Foundation
|
||||||
import StatusKit
|
import StatusKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppIntents
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
enum Tab: Int, Identifiable, Hashable, CaseIterable, Codable {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import SwiftUI
|
|
||||||
import AppIntents
|
import AppIntents
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@Observable
|
@Observable
|
||||||
public class AppIntentService: @unchecked Sendable {
|
public class AppIntentService: @unchecked Sendable {
|
||||||
|
@ -7,19 +7,19 @@ public class AppIntentService: @unchecked Sendable {
|
||||||
static func == (lhs: AppIntentService.HandledIntent, rhs: AppIntentService.HandledIntent) -> Bool {
|
static func == (lhs: AppIntentService.HandledIntent, rhs: AppIntentService.HandledIntent) -> Bool {
|
||||||
lhs.id == rhs.id
|
lhs.id == rhs.id
|
||||||
}
|
}
|
||||||
|
|
||||||
let id: String
|
let id: String
|
||||||
let intent: any AppIntent
|
let intent: any AppIntent
|
||||||
|
|
||||||
init(intent: any AppIntent) {
|
init(intent: any AppIntent) {
|
||||||
self.id = UUID().uuidString
|
id = UUID().uuidString
|
||||||
self.intent = intent
|
self.intent = intent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let shared = AppIntentService()
|
public static let shared = AppIntentService()
|
||||||
|
|
||||||
var handledIntent: HandledIntent?
|
var handledIntent: HandledIntent?
|
||||||
|
|
||||||
private init() { }
|
private init() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,63 @@
|
||||||
import Foundation
|
|
||||||
import AppIntents
|
|
||||||
import AppAccount
|
import AppAccount
|
||||||
import Network
|
import AppIntents
|
||||||
import Env
|
import Env
|
||||||
|
import Foundation
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
|
|
||||||
enum PostVisibility: String, AppEnum {
|
enum PostVisibility: String, AppEnum {
|
||||||
case direct, priv, unlisted, pub
|
case direct, priv, unlisted, pub
|
||||||
|
|
||||||
public static var caseDisplayRepresentations: [PostVisibility : DisplayRepresentation] {
|
public static var caseDisplayRepresentations: [PostVisibility: DisplayRepresentation] {
|
||||||
[.direct: "Private",
|
[.direct: "Private",
|
||||||
.priv: "Followers Only",
|
.priv: "Followers Only",
|
||||||
.unlisted: "Quiet Public",
|
.unlisted: "Quiet Public",
|
||||||
.pub: "Public"]
|
.pub: "Public"]
|
||||||
}
|
}
|
||||||
|
|
||||||
static var typeDisplayName: LocalizedStringResource {
|
static var typeDisplayName: LocalizedStringResource { "Visibility" }
|
||||||
get { "Visibility" }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "Visibility"
|
public static let typeDisplayRepresentation: TypeDisplayRepresentation = "Visibility"
|
||||||
|
|
||||||
var toAppVisibility: Models.Visibility {
|
var toAppVisibility: Models.Visibility {
|
||||||
switch self {
|
switch self {
|
||||||
case .direct:
|
case .direct:
|
||||||
.direct
|
.direct
|
||||||
case .priv:
|
case .priv:
|
||||||
.priv
|
.priv
|
||||||
case .unlisted:
|
case .unlisted:
|
||||||
.unlisted
|
.unlisted
|
||||||
case .pub:
|
case .pub:
|
||||||
.pub
|
.pub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppAccountWrapper: Identifiable, AppEntity {
|
struct AppAccountWrapper: Identifiable, AppEntity {
|
||||||
var id: String { account.id }
|
var id: String { account.id }
|
||||||
|
|
||||||
let account: AppAccount
|
let account: AppAccount
|
||||||
|
|
||||||
static var defaultQuery = DefaultAppAccountQuery()
|
static var defaultQuery = DefaultAppAccountQuery()
|
||||||
|
|
||||||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "AppAccount"
|
static var typeDisplayRepresentation: TypeDisplayRepresentation = "AppAccount"
|
||||||
|
|
||||||
var displayRepresentation: DisplayRepresentation {
|
var displayRepresentation: DisplayRepresentation {
|
||||||
DisplayRepresentation(title: "\(account.accountName ?? account.server)")
|
DisplayRepresentation(title: "\(account.accountName ?? account.server)")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DefaultAppAccountQuery: EntityQuery {
|
struct DefaultAppAccountQuery: EntityQuery {
|
||||||
|
|
||||||
func entities(for identifiers: [AppAccountWrapper.ID]) async throws -> [AppAccountWrapper] {
|
func entities(for identifiers: [AppAccountWrapper.ID]) async throws -> [AppAccountWrapper] {
|
||||||
return await AppAccountsManager.shared.availableAccounts.filter { account in
|
return await AppAccountsManager.shared.availableAccounts.filter { account in
|
||||||
identifiers.contains { id in
|
identifiers.contains { id in
|
||||||
id == account.id
|
id == account.id
|
||||||
}
|
}
|
||||||
}.map{ AppAccountWrapper(account: $0 )}
|
}.map { AppAccountWrapper(account: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func suggestedEntities() async throws -> [AppAccountWrapper] {
|
func suggestedEntities() async throws -> [AppAccountWrapper] {
|
||||||
await AppAccountsManager.shared.availableAccounts.map{ .init(account: $0)}
|
await AppAccountsManager.shared.availableAccounts.map { .init(account: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultResult() async -> AppAccountWrapper? {
|
func defaultResult() async -> AppAccountWrapper? {
|
||||||
|
@ -72,21 +68,20 @@ struct DefaultAppAccountQuery: EntityQuery {
|
||||||
struct InlinePostIntent: AppIntent {
|
struct InlinePostIntent: AppIntent {
|
||||||
static let title: LocalizedStringResource = "Send text status to Mastodon"
|
static let title: LocalizedStringResource = "Send text status to Mastodon"
|
||||||
static var description: IntentDescription {
|
static var description: IntentDescription {
|
||||||
get {
|
"Send a text status to Mastodon using Ice Cubes"
|
||||||
"Send a text status to Mastodon using Ice Cubes"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let openAppWhenRun: Bool = false
|
static let openAppWhenRun: Bool = false
|
||||||
|
|
||||||
@Parameter(title: "Account", requestValueDialog: IntentDialog("Account"))
|
@Parameter(title: "Account", requestValueDialog: IntentDialog("Account"))
|
||||||
var account: AppAccountWrapper
|
var account: AppAccountWrapper
|
||||||
|
|
||||||
@Parameter(title: "Post visibility", requestValueDialog: IntentDialog("Visibility of your post"))
|
@Parameter(title: "Post visibility", requestValueDialog: IntentDialog("Visibility of your post"))
|
||||||
var visibility: PostVisibility
|
var visibility: PostVisibility
|
||||||
|
|
||||||
@Parameter(title: "Post content", requestValueDialog: IntentDialog("Content of the post to be sent to Mastodon"))
|
@Parameter(title: "Post content", requestValueDialog: IntentDialog("Content of the post to be sent to Mastodon"))
|
||||||
var content: String
|
var content: String
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
|
func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView {
|
||||||
let client = Client(server: account.account.server, version: .v1, oauthToken: account.account.oauthToken)
|
let client = Client(server: account.account.server, version: .v1, oauthToken: account.account.oauthToken)
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
import Foundation
|
|
||||||
import AppIntents
|
import AppIntents
|
||||||
|
import Foundation
|
||||||
|
|
||||||
struct PostImageIntent: AppIntent {
|
struct PostImageIntent: AppIntent {
|
||||||
static let title: LocalizedStringResource = "Post an image to Mastodon"
|
static let title: LocalizedStringResource = "Post an image to Mastodon"
|
||||||
static var description: IntentDescription {
|
static var description: IntentDescription {
|
||||||
get {
|
"Use Ice Cubes to post a status with an image to Mastodon"
|
||||||
"Use Ice Cubes to post a status with an image to Mastodon"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let openAppWhenRun: Bool = true
|
static let openAppWhenRun: Bool = true
|
||||||
|
|
||||||
@Parameter(title: "Image",
|
@Parameter(title: "Image",
|
||||||
description: "Image to post on Mastodon",
|
description: "Image to post on Mastodon",
|
||||||
supportedTypeIdentifiers: ["public.image"],
|
supportedTypeIdentifiers: ["public.image"],
|
||||||
inputConnectionBehavior: .connectToPreviousIntentResult)
|
inputConnectionBehavior: .connectToPreviousIntentResult)
|
||||||
var images: [IntentFile]?
|
var images: [IntentFile]?
|
||||||
|
|
||||||
func perform() async throws -> some IntentResult {
|
func perform() async throws -> some IntentResult {
|
||||||
AppIntentService.shared.handledIntent = .init(intent: self)
|
AppIntentService.shared.handledIntent = .init(intent: self)
|
||||||
return .result()
|
return .result()
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
import Foundation
|
|
||||||
import AppIntents
|
import AppIntents
|
||||||
|
import Foundation
|
||||||
|
|
||||||
struct PostIntent: AppIntent {
|
struct PostIntent: AppIntent {
|
||||||
static let title: LocalizedStringResource = "Post status to Mastodon"
|
static let title: LocalizedStringResource = "Post status to Mastodon"
|
||||||
static var description: IntentDescription {
|
static var description: IntentDescription {
|
||||||
get {
|
"Use Ice Cubes to post a status to Mastodon"
|
||||||
"Use Ice Cubes to post a status to Mastodon"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let openAppWhenRun: Bool = true
|
static let openAppWhenRun: Bool = true
|
||||||
|
|
||||||
@Parameter(title: "Post content", inputConnectionBehavior: .connectToPreviousIntentResult)
|
@Parameter(title: "Post content", inputConnectionBehavior: .connectToPreviousIntentResult)
|
||||||
var content: String?
|
var content: String?
|
||||||
|
|
||||||
func perform() async throws -> some IntentResult {
|
func perform() async throws -> some IntentResult {
|
||||||
AppIntentService.shared.handledIntent = .init(intent: self)
|
AppIntentService.shared.handledIntent = .init(intent: self)
|
||||||
return .result()
|
return .result()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Foundation
|
|
||||||
import AppIntents
|
import AppIntents
|
||||||
|
import Foundation
|
||||||
|
|
||||||
enum TabEnum: String, AppEnum, Sendable {
|
enum TabEnum: String, AppEnum, Sendable {
|
||||||
case timeline, notifications, mentions, explore, messages, settings
|
case timeline, notifications, mentions, explore, messages, settings
|
||||||
|
@ -12,13 +12,11 @@ enum TabEnum: String, AppEnum, Sendable {
|
||||||
case lists
|
case lists
|
||||||
case links
|
case links
|
||||||
|
|
||||||
static var typeDisplayName: LocalizedStringResource {
|
static var typeDisplayName: LocalizedStringResource { "Tab" }
|
||||||
get { "Tab" }
|
|
||||||
}
|
|
||||||
|
|
||||||
static let typeDisplayRepresentation: TypeDisplayRepresentation = "Tab"
|
static let typeDisplayRepresentation: TypeDisplayRepresentation = "Tab"
|
||||||
|
|
||||||
nonisolated static var caseDisplayRepresentations: [TabEnum : DisplayRepresentation] {
|
nonisolated static var caseDisplayRepresentations: [TabEnum: DisplayRepresentation] {
|
||||||
[.timeline: .init(title: "Home Timeline"),
|
[.timeline: .init(title: "Home Timeline"),
|
||||||
.trending: .init(title: "Trending Timeline"),
|
.trending: .init(title: "Trending Timeline"),
|
||||||
.federated: .init(title: "Federated Timeline"),
|
.federated: .init(title: "Federated Timeline"),
|
||||||
|
@ -34,44 +32,43 @@ enum TabEnum: String, AppEnum, Sendable {
|
||||||
.followedTags: .init(title: "Followed Tags"),
|
.followedTags: .init(title: "Followed Tags"),
|
||||||
.lists: .init(title: "Lists"),
|
.lists: .init(title: "Lists"),
|
||||||
.links: .init(title: "Trending Links"),
|
.links: .init(title: "Trending Links"),
|
||||||
.post: .init(title: "New post"),
|
.post: .init(title: "New post")]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var toAppTab: Tab {
|
var toAppTab: Tab {
|
||||||
switch self {
|
switch self {
|
||||||
case .timeline:
|
case .timeline:
|
||||||
.timeline
|
.timeline
|
||||||
case .notifications:
|
case .notifications:
|
||||||
.notifications
|
.notifications
|
||||||
case .mentions:
|
case .mentions:
|
||||||
.mentions
|
.mentions
|
||||||
case .explore:
|
case .explore:
|
||||||
.explore
|
.explore
|
||||||
case .messages:
|
case .messages:
|
||||||
.messages
|
.messages
|
||||||
case .settings:
|
case .settings:
|
||||||
.settings
|
.settings
|
||||||
case .trending:
|
case .trending:
|
||||||
.trending
|
.trending
|
||||||
case .federated:
|
case .federated:
|
||||||
.federated
|
.federated
|
||||||
case .local:
|
case .local:
|
||||||
.local
|
.local
|
||||||
case .profile:
|
case .profile:
|
||||||
.profile
|
.profile
|
||||||
case .bookmarks:
|
case .bookmarks:
|
||||||
.bookmarks
|
.bookmarks
|
||||||
case .favorites:
|
case .favorites:
|
||||||
.favorites
|
.favorites
|
||||||
case .post:
|
case .post:
|
||||||
.post
|
.post
|
||||||
case .followedTags:
|
case .followedTags:
|
||||||
.followedTags
|
.followedTags
|
||||||
case .lists:
|
case .lists:
|
||||||
.lists
|
.lists
|
||||||
case .links:
|
case .links:
|
||||||
.links
|
.links
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,15 +76,14 @@ enum TabEnum: String, AppEnum, Sendable {
|
||||||
struct TabIntent: AppIntent {
|
struct TabIntent: AppIntent {
|
||||||
static let title: LocalizedStringResource = "Open on a tab"
|
static let title: LocalizedStringResource = "Open on a tab"
|
||||||
static var description: IntentDescription {
|
static var description: IntentDescription {
|
||||||
get {
|
"Open the app on a specific tab"
|
||||||
"Open the app on a specific tab"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let openAppWhenRun: Bool = true
|
static let openAppWhenRun: Bool = true
|
||||||
|
|
||||||
@Parameter(title: "Selected tab")
|
@Parameter(title: "Selected tab")
|
||||||
var tab: TabEnum
|
var tab: TabEnum
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func perform() async throws -> some IntentResult {
|
func perform() async throws -> some IntentResult {
|
||||||
AppIntentService.shared.handledIntent = .init(intent: self)
|
AppIntentService.shared.handledIntent = .init(intent: self)
|
||||||
|
|
|
@ -326,20 +326,19 @@ public struct AccountDetailView: View {
|
||||||
|
|
||||||
if let account = viewModel.account {
|
if let account = viewModel.account {
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
routerPath.navigate(to: .blockedAccounts)
|
routerPath.navigate(to: .blockedAccounts)
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.blocked", systemImage: "person.crop.circle.badge.xmark")
|
Label("account.blocked", systemImage: "person.crop.circle.badge.xmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
routerPath.navigate(to: .mutedAccounts)
|
routerPath.navigate(to: .mutedAccounts)
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.muted", systemImage: "person.crop.circle.badge.moon")
|
Label("account.muted", systemImage: "person.crop.circle.badge.moon")
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|
|
@ -89,10 +89,10 @@ public enum AccountsListMode {
|
||||||
case let .accountsList(accounts):
|
case let .accountsList(accounts):
|
||||||
self.accounts = accounts
|
self.accounts = accounts
|
||||||
link = nil
|
link = nil
|
||||||
|
|
||||||
case .blocked:
|
case .blocked:
|
||||||
(accounts, link) = try await client.getWithLink(endpoint: Accounts.blockList)
|
(accounts, link) = try await client.getWithLink(endpoint: Accounts.blockList)
|
||||||
|
|
||||||
case .muted:
|
case .muted:
|
||||||
(accounts, link) = try await client.getWithLink(endpoint: Accounts.muteList)
|
(accounts, link) = try await client.getWithLink(endpoint: Accounts.muteList)
|
||||||
}
|
}
|
||||||
|
@ -125,14 +125,14 @@ public enum AccountsListMode {
|
||||||
case .accountsList:
|
case .accountsList:
|
||||||
newAccounts = []
|
newAccounts = []
|
||||||
link = nil
|
link = nil
|
||||||
|
|
||||||
case .blocked:
|
case .blocked:
|
||||||
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.blockList)
|
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.blockList)
|
||||||
|
|
||||||
case .muted:
|
case .muted:
|
||||||
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.muteList)
|
(newAccounts, link) = try await client.getWithLink(endpoint: Accounts.muteList)
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts.append(contentsOf: newAccounts)
|
accounts.append(contentsOf: newAccounts)
|
||||||
let newRelationships: [Relationship] =
|
let newRelationships: [Relationship] =
|
||||||
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map(\.id)))
|
try await client.get(endpoint: Accounts.relationships(ids: newAccounts.map(\.id)))
|
||||||
|
|
|
@ -10,7 +10,7 @@ public struct CloseToolbarItem: ToolbarContent {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
dismiss()
|
dismiss()
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "xmark.circle")
|
Image(systemName: "xmark.circle")
|
||||||
})
|
})
|
||||||
.keyboardShortcut(.cancelAction)
|
.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: (() async -> Void)
|
public let onButtonPress: () async -> Void
|
||||||
|
|
||||||
public init(title: LocalizedStringKey, message: LocalizedStringKey, buttonTitle: LocalizedStringKey, onButtonPress: @escaping (() async -> 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
|
||||||
|
|
|
@ -34,7 +34,7 @@ import Observation
|
||||||
public var isEditAltTextSupported: Bool {
|
public var isEditAltTextSupported: Bool {
|
||||||
version >= 4.1
|
version >= 4.1
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isNotificationsFilterSupported: Bool {
|
public var isNotificationsFilterSupported: Bool {
|
||||||
version >= 4.3
|
version >= 4.3
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
public var id: String {
|
public var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
|
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
|
||||||
.mentionStatusEditor, .quoteLinkStatusEditor, .prefilledStatusEditor, .imageURL:
|
.mentionStatusEditor, .quoteLinkStatusEditor, .prefilledStatusEditor, .imageURL:
|
||||||
"statusEditor"
|
"statusEditor"
|
||||||
case .listCreate:
|
case .listCreate:
|
||||||
"listCreate"
|
"listCreate"
|
||||||
|
@ -177,9 +177,10 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
}
|
}
|
||||||
return .handled
|
return .handled
|
||||||
} else if let client,
|
} else if let client,
|
||||||
client.isAuth,
|
client.isAuth,
|
||||||
client.hasConnection(with: url),
|
client.hasConnection(with: url),
|
||||||
let id = Int(url.lastPathComponent) {
|
let id = Int(url.lastPathComponent)
|
||||||
|
{
|
||||||
if url.absoluteString.contains(client.server) {
|
if url.absoluteString.contains(client.server) {
|
||||||
navigate(to: .statusDetail(id: String(id)))
|
navigate(to: .statusDetail(id: String(id)))
|
||||||
} else {
|
} else {
|
||||||
|
@ -189,11 +190,12 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
}
|
}
|
||||||
return urlHandler?(url) ?? .systemAction
|
return urlHandler?(url) ?? .systemAction
|
||||||
}
|
}
|
||||||
|
|
||||||
public func handleDeepLink(url: URL) -> OpenURLAction.Result {
|
public func handleDeepLink(url: URL) -> OpenURLAction.Result {
|
||||||
guard let client,
|
guard let client,
|
||||||
client.isAuth,
|
client.isAuth,
|
||||||
let id = Int(url.lastPathComponent) else {
|
let id = Int(url.lastPathComponent)
|
||||||
|
else {
|
||||||
return urlHandler?(url) ?? .systemAction
|
return urlHandler?(url) ?? .systemAction
|
||||||
}
|
}
|
||||||
// First check whether we already know that the client's server federates with the server this post is on
|
// First check whether we already know that the client's server federates with the server this post is on
|
||||||
|
@ -211,18 +213,18 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
handlerOrDefault(url: url)
|
handlerOrDefault(url: url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard client.hasConnection(with: url) else {
|
guard client.hasConnection(with: url) else {
|
||||||
handlerOrDefault(url: url)
|
handlerOrDefault(url: url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToStatus(url: url, id: id)
|
navigateToStatus(url: url, id: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return .handled
|
return .handled
|
||||||
}
|
}
|
||||||
|
|
||||||
private func navigateToStatus(url: URL, id: Int) {
|
private func navigateToStatus(url: URL, id: Int) {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
if url.absoluteString.contains(client.server) {
|
if url.absoluteString.contains(client.server) {
|
||||||
|
@ -231,7 +233,7 @@ public enum SheetDestination: Identifiable, Hashable {
|
||||||
navigate(to: .remoteStatusDetail(url: url))
|
navigate(to: .remoteStatusDetail(url: url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handlerOrDefault(url: URL) {
|
private func handlerOrDefault(url: URL) {
|
||||||
if let urlHandler {
|
if let urlHandler {
|
||||||
_ = urlHandler(url)
|
_ = urlHandler(url)
|
||||||
|
|
|
@ -6,7 +6,7 @@ public struct NotificationsPolicy: Codable, Sendable {
|
||||||
public var filterNewAccounts: Bool
|
public var filterNewAccounts: Bool
|
||||||
public var filterPrivateMentions: Bool
|
public var filterPrivateMentions: Bool
|
||||||
public let summary: Summary
|
public let summary: Summary
|
||||||
|
|
||||||
public struct Summary: Codable, Sendable {
|
public struct Summary: Codable, Sendable {
|
||||||
public let pendingRequestsCount: String
|
public let pendingRequestsCount: String
|
||||||
public let pendingNotificationsCount: String
|
public let pendingNotificationsCount: String
|
||||||
|
|
|
@ -33,7 +33,7 @@ public enum Notifications: Endpoint {
|
||||||
"notifications/clear"
|
"notifications/clear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var jsonValue: (any Encodable)? {
|
public var jsonValue: (any Encodable)? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .putPolicy(policy):
|
case let .putPolicy(policy):
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationsHeaderFilteredView: View {
|
struct NotificationsHeaderFilteredView: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
|
|
||||||
let filteredNotifications: NotificationsPolicy.Summary
|
let filteredNotifications: NotificationsPolicy.Summary
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let count = Int(filteredNotifications.pendingNotificationsCount), count > 0 {
|
if let count = Int(filteredNotifications.pendingNotificationsCount), count > 0 {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
|
@ -7,14 +7,14 @@ 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
|
@Environment(CurrentInstance.self) private var currentInstance
|
||||||
|
|
||||||
@State private var viewModel = NotificationsViewModel()
|
@State private var viewModel = NotificationsViewModel()
|
||||||
@State private var isNotificationsPolicyPresented: Bool = false
|
@State private var isNotificationsPolicyPresented: Bool = false
|
||||||
@Binding var scrollToTopSignal: Int
|
@Binding var scrollToTopSignal: Int
|
||||||
|
@ -24,7 +24,8 @@ public struct NotificationsListView: View {
|
||||||
|
|
||||||
public init(lockedType: Models.Notification.NotificationType? = nil,
|
public init(lockedType: Models.Notification.NotificationType? = nil,
|
||||||
lockedAccountId: String? = nil,
|
lockedAccountId: String? = nil,
|
||||||
scrollToTopSignal: Binding<Int>) {
|
scrollToTopSignal: Binding<Int>)
|
||||||
|
{
|
||||||
self.lockedType = lockedType
|
self.lockedType = lockedType
|
||||||
self.lockedAccountId = lockedAccountId
|
self.lockedAccountId = lockedAccountId
|
||||||
_scrollToTopSignal = scrollToTopSignal
|
_scrollToTopSignal = scrollToTopSignal
|
||||||
|
@ -113,7 +114,7 @@ public struct NotificationsListView: View {
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(theme.primaryBackgroundColor)
|
.background(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.client = client
|
viewModel.client = client
|
||||||
viewModel.currentAccount = account
|
viewModel.currentAccount = account
|
||||||
if let lockedType {
|
if let lockedType {
|
||||||
|
|
|
@ -1,50 +1,50 @@
|
||||||
import SwiftUI
|
|
||||||
import Network
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Models
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct NotificationsPolicyView: View {
|
struct NotificationsPolicyView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
@State private var policy: NotificationsPolicy?
|
@State private var policy: NotificationsPolicy?
|
||||||
@State private var isUpdating: Bool = false
|
@State private var isUpdating: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
Section("notifications.content-filter.title-inline") {
|
Section("notifications.content-filter.title-inline") {
|
||||||
Toggle(isOn: .init(get: { policy?.filterNotFollowing == true },
|
Toggle(isOn: .init(get: { policy?.filterNotFollowing == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterNotFollowing = newValue
|
policy?.filterNotFollowing = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.peopleYouDontFollow")
|
Text("notifications.content-filter.peopleYouDontFollow")
|
||||||
})
|
})
|
||||||
Toggle(isOn: .init(get: { policy?.filterNotFollowers == true },
|
Toggle(isOn: .init(get: { policy?.filterNotFollowers == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterNotFollowers = newValue
|
policy?.filterNotFollowers = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.peopleNotFollowingYou")
|
Text("notifications.content-filter.peopleNotFollowingYou")
|
||||||
})
|
})
|
||||||
Toggle(isOn: .init(get: { policy?.filterNewAccounts == true },
|
Toggle(isOn: .init(get: { policy?.filterNewAccounts == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterNewAccounts = newValue
|
policy?.filterNewAccounts = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.newAccounts")
|
Text("notifications.content-filter.newAccounts")
|
||||||
})
|
})
|
||||||
Toggle(isOn: .init(get: { policy?.filterPrivateMentions == true },
|
Toggle(isOn: .init(get: { policy?.filterPrivateMentions == true },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
policy?.filterPrivateMentions = newValue
|
policy?.filterPrivateMentions = newValue
|
||||||
Task { await updatePolicy() }
|
Task { await updatePolicy() }
|
||||||
}), label: {
|
}), label: {
|
||||||
Text("notifications.content-filter.privateMentions")
|
Text("notifications.content-filter.privateMentions")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
|
@ -63,7 +63,7 @@ struct NotificationsPolicyView: View {
|
||||||
.presentationDetents([.medium])
|
.presentationDetents([.medium])
|
||||||
.presentationBackground(.thinMaterial)
|
.presentationBackground(.thinMaterial)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPolicy() async {
|
private func getPolicy() async {
|
||||||
defer {
|
defer {
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
|
@ -75,7 +75,7 @@ struct NotificationsPolicyView: View {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePolicy() async {
|
private func updatePolicy() async {
|
||||||
if let policy {
|
if let policy {
|
||||||
defer {
|
defer {
|
||||||
|
@ -84,7 +84,7 @@ struct NotificationsPolicyView: View {
|
||||||
do {
|
do {
|
||||||
isUpdating = true
|
isUpdating = true
|
||||||
self.policy = try await client.put(endpoint: Notifications.putPolicy(policy: policy))
|
self.policy = try await client.put(endpoint: Notifications.putPolicy(policy: policy))
|
||||||
} catch { }
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ 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 lockedAccountId: String?
|
||||||
var policy: Models.NotificationsPolicy?
|
var policy: Models.NotificationsPolicy?
|
||||||
var selectedType: Models.Notification.NotificationType? {
|
var selectedType: Models.Notification.NotificationType? {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -156,7 +156,7 @@ import SwiftUI
|
||||||
let newNotifications: [Models.Notification]
|
let newNotifications: [Models.Notification]
|
||||||
if let lockedAccountId {
|
if let lockedAccountId {
|
||||||
newNotifications =
|
newNotifications =
|
||||||
try await client.get(endpoint: Notifications.notificationsForAccount(accountId: lockedAccountId, maxId: lastId))
|
try await client.get(endpoint: Notifications.notificationsForAccount(accountId: lockedAccountId, maxId: lastId))
|
||||||
} else {
|
} else {
|
||||||
newNotifications =
|
newNotifications =
|
||||||
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
try await client.get(endpoint: Notifications.notifications(minId: nil,
|
||||||
|
@ -180,7 +180,7 @@ import SwiftUI
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPolicy() async {
|
func fetchPolicy() async {
|
||||||
policy = try? await client?.get(endpoint: Notifications.policy)
|
policy = try? await client?.get(endpoint: Notifications.policy)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import SwiftUI
|
|
||||||
import Network
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
|
import Models
|
||||||
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct NotificationsRequestsListView: View {
|
public struct NotificationsRequestsListView: View {
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
enum ViewState {
|
enum ViewState {
|
||||||
case loading
|
case loading
|
||||||
case error
|
case error
|
||||||
case requests(_ data: [NotificationsRequest])
|
case requests(_ data: [NotificationsRequest])
|
||||||
}
|
}
|
||||||
|
|
||||||
@State private var viewState: ViewState = .loading
|
@State private var viewState: ViewState = .loading
|
||||||
|
|
||||||
public init() { }
|
public init() {}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
List {
|
List {
|
||||||
switch viewState {
|
switch viewState {
|
||||||
case .loading:
|
case .loading:
|
||||||
ProgressView()
|
ProgressView()
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.listSectionSeparator(.hidden)
|
.listSectionSeparator(.hidden)
|
||||||
case .error:
|
case .error:
|
||||||
ErrorView(title: "notifications.error.title",
|
ErrorView(title: "notifications.error.title",
|
||||||
|
@ -33,10 +34,10 @@ public struct NotificationsRequestsListView: View {
|
||||||
{
|
{
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.listSectionSeparator(.hidden)
|
.listSectionSeparator(.hidden)
|
||||||
case let .requests(data):
|
case let .requests(data):
|
||||||
ForEach(data) { request in
|
ForEach(data) { request in
|
||||||
NotificationsRequestsRowView(request: request)
|
NotificationsRequestsRowView(request: request)
|
||||||
|
@ -46,7 +47,7 @@ public struct NotificationsRequestsListView: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label("account.follow-request.accept", systemImage: "checkmark")
|
Label("account.follow-request.accept", systemImage: "checkmark")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Task { await dismissRequest(request) }
|
Task { await dismissRequest(request) }
|
||||||
} label: {
|
} label: {
|
||||||
|
@ -59,32 +60,32 @@ public struct NotificationsRequestsListView: View {
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(theme.primaryBackgroundColor)
|
.background(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.navigationTitle("notifications.content-filter.requests.title")
|
.navigationTitle("notifications.content-filter.requests.title")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.task {
|
.task {
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchRequests() async {
|
private func fetchRequests() async {
|
||||||
do {
|
do {
|
||||||
viewState = .requests(try await client.get(endpoint: Notifications.requests))
|
viewState = try .requests(await client.get(endpoint: Notifications.requests))
|
||||||
} catch {
|
} catch {
|
||||||
viewState = .error
|
viewState = .error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func acceptRequest(_ request: NotificationsRequest) async {
|
private func acceptRequest(_ request: NotificationsRequest) async {
|
||||||
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id))
|
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id))
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissRequest(_ request: NotificationsRequest) async {
|
private func dismissRequest(_ request: NotificationsRequest) async {
|
||||||
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id))
|
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id))
|
||||||
await fetchRequests()
|
await fetchRequests()
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import SwiftUI
|
|
||||||
import Models
|
|
||||||
import DesignSystem
|
import DesignSystem
|
||||||
import Env
|
import Env
|
||||||
|
import Models
|
||||||
import Network
|
import Network
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationsRequestsRowView: View {
|
struct NotificationsRequestsRowView: View {
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
@Environment(RouterPath.self) private var routerPath
|
@Environment(RouterPath.self) private var routerPath
|
||||||
@Environment(Client.self) private var client
|
@Environment(Client.self) private var client
|
||||||
|
|
||||||
let request: NotificationsRequest
|
let request: NotificationsRequest
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 8) {
|
HStack(alignment: .center, spacing: 8) {
|
||||||
AvatarView(request.account.avatar, config: .embed)
|
AvatarView(request.account.avatar, config: .embed)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
EmojiTextApp(request.account.cachedDisplayName, emojis: request.account.emojis)
|
EmojiTextApp(request.account.cachedDisplayName, emojis: request.account.emojis)
|
||||||
.font(.scaledBody)
|
.font(.scaledBody)
|
||||||
|
@ -35,14 +35,14 @@ struct NotificationsRequestsRowView: View {
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.background(.secondary)
|
.background(.secondary)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
|
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
|
||||||
}
|
}
|
||||||
.listRowInsets(.init(top: 12,
|
.listRowInsets(.init(top: 12,
|
||||||
leading: .layoutPadding,
|
leading: .layoutPadding,
|
||||||
bottom: 12,
|
bottom: 12,
|
||||||
trailing: .layoutPadding))
|
trailing: .layoutPadding))
|
||||||
|
@ -50,7 +50,7 @@ struct NotificationsRequestsRowView: View {
|
||||||
.listRowBackground(RoundedRectangle(cornerRadius: 8)
|
.listRowBackground(RoundedRectangle(cornerRadius: 8)
|
||||||
.foregroundStyle(.background))
|
.foregroundStyle(.background))
|
||||||
#else
|
#else
|
||||||
.listRowBackground(theme.primaryBackgroundColor)
|
.listRowBackground(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ extension StatusEditor {
|
||||||
@MainActor
|
@MainActor
|
||||||
struct CustomEmojisView: View {
|
struct CustomEmojisView: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
var viewModel: ViewModel
|
var viewModel: ViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|
|
@ -16,30 +16,30 @@ public extension StatusEditor {
|
||||||
@Environment(AppAccountsManager.self) private var appAccounts
|
@Environment(AppAccountsManager.self) private var appAccounts
|
||||||
@Environment(CurrentAccount.self) private var currentAccount
|
@Environment(CurrentAccount.self) private var currentAccount
|
||||||
@Environment(Theme.self) private var theme
|
@Environment(Theme.self) private var theme
|
||||||
|
|
||||||
@State private var presentationDetent: PresentationDetent = .large
|
@State private var presentationDetent: PresentationDetent = .large
|
||||||
@State private var mainSEVM: ViewModel
|
@State private var mainSEVM: ViewModel
|
||||||
@State private var followUpSEVMs: [ViewModel] = []
|
@State private var followUpSEVMs: [ViewModel] = []
|
||||||
@State private var editingMediaContainer: MediaContainer?
|
@State private var editingMediaContainer: MediaContainer?
|
||||||
@State private var scrollID: UUID?
|
@State private var scrollID: UUID?
|
||||||
|
|
||||||
@FocusState private var editorFocusState: EditorFocusState?
|
@FocusState private var editorFocusState: EditorFocusState?
|
||||||
|
|
||||||
private var focusedSEVM: ViewModel {
|
private var focusedSEVM: ViewModel {
|
||||||
if case let .followUp(id) = editorFocusState,
|
if case let .followUp(id) = editorFocusState,
|
||||||
let sevm = followUpSEVMs.first(where: { $0.id == id })
|
let sevm = followUpSEVMs.first(where: { $0.id == id })
|
||||||
{ return sevm }
|
{ return sevm }
|
||||||
|
|
||||||
return mainSEVM
|
return mainSEVM
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(mode: ViewModel.Mode) {
|
public init(mode: ViewModel.Mode) {
|
||||||
_mainSEVM = State(initialValue: ViewModel(mode: mode))
|
_mainSEVM = State(initialValue: ViewModel(mode: mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
@Bindable var focusedSEVM = focusedSEVM
|
@Bindable var focusedSEVM = focusedSEVM
|
||||||
|
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
@ -53,10 +53,10 @@ public extension StatusEditor {
|
||||||
isMain: true
|
isMain: true
|
||||||
)
|
)
|
||||||
.id(mainSEVM.id)
|
.id(mainSEVM.id)
|
||||||
|
|
||||||
ForEach(followUpSEVMs) { sevm in
|
ForEach(followUpSEVMs) { sevm in
|
||||||
@Bindable var sevm: ViewModel = sevm
|
@Bindable var sevm: ViewModel = sevm
|
||||||
|
|
||||||
EditorView(
|
EditorView(
|
||||||
viewModel: sevm,
|
viewModel: sevm,
|
||||||
followUpSEVMs: $followUpSEVMs,
|
followUpSEVMs: $followUpSEVMs,
|
||||||
|
@ -73,74 +73,74 @@ public extension StatusEditor {
|
||||||
.scrollPosition(id: $scrollID, anchor: .top)
|
.scrollPosition(id: $scrollID, anchor: .top)
|
||||||
.animation(.bouncy(duration: 0.3), value: editorFocusState)
|
.animation(.bouncy(duration: 0.3), value: editorFocusState)
|
||||||
.animation(.bouncy(duration: 0.3), value: followUpSEVMs)
|
.animation(.bouncy(duration: 0.3), value: followUpSEVMs)
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
.background(theme.primaryBackgroundColor)
|
.background(theme.primaryBackgroundColor)
|
||||||
#endif
|
#endif
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
AutoCompleteView(viewModel: focusedSEVM)
|
AutoCompleteView(viewModel: focusedSEVM)
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
#if os(visionOS)
|
||||||
.ornament(attachmentAnchor: .scene(.leading)) {
|
.ornament(attachmentAnchor: .scene(.leading)) {
|
||||||
AccessoryView(focusedSEVM: focusedSEVM,
|
|
||||||
followUpSEVMs: $followUpSEVMs)
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.safeAreaInset(edge: .bottom) {
|
|
||||||
if presentationDetent == .large || presentationDetent == .medium {
|
|
||||||
AccessoryView(focusedSEVM: focusedSEVM,
|
AccessoryView(focusedSEVM: focusedSEVM,
|
||||||
followUpSEVMs: $followUpSEVMs)
|
followUpSEVMs: $followUpSEVMs)
|
||||||
}
|
}
|
||||||
}
|
#else
|
||||||
#endif
|
.safeAreaInset(edge: .bottom) {
|
||||||
.accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views
|
if presentationDetent == .large || presentationDetent == .medium {
|
||||||
.navigationTitle(focusedSEVM.mode.title)
|
AccessoryView(focusedSEVM: focusedSEVM,
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
followUpSEVMs: $followUpSEVMs)
|
||||||
.toolbar { ToolbarItems(mainSEVM: mainSEVM,
|
}
|
||||||
focusedSEVM: focusedSEVM,
|
}
|
||||||
followUpSEVMs: followUpSEVMs) }
|
#endif
|
||||||
.toolbarBackground(.visible, for: .navigationBar)
|
.accessibilitySortPriority(1) // Ensure that all elements inside the `ScrollView` occur earlier than the accessory views
|
||||||
.alert(
|
.navigationTitle(focusedSEVM.mode.title)
|
||||||
"status.error.posting.title",
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
isPresented: $focusedSEVM.showPostingErrorAlert,
|
.toolbar { ToolbarItems(mainSEVM: mainSEVM,
|
||||||
actions: {
|
focusedSEVM: focusedSEVM,
|
||||||
Button("OK") {}
|
followUpSEVMs: followUpSEVMs) }
|
||||||
}, message: {
|
.toolbarBackground(.visible, for: .navigationBar)
|
||||||
Text(mainSEVM.postingError ?? "")
|
.alert(
|
||||||
}
|
"status.error.posting.title",
|
||||||
)
|
isPresented: $focusedSEVM.showPostingErrorAlert,
|
||||||
.interactiveDismissDisabled(mainSEVM.shouldDisplayDismissWarning)
|
actions: {
|
||||||
.onChange(of: appAccounts.currentClient) { _, newValue in
|
Button("OK") {}
|
||||||
if mainSEVM.mode.isInShareExtension {
|
}, message: {
|
||||||
currentAccount.setClient(client: newValue)
|
Text(mainSEVM.postingError ?? "")
|
||||||
mainSEVM.client = newValue
|
}
|
||||||
for post in followUpSEVMs {
|
)
|
||||||
post.client = newValue
|
.interactiveDismissDisabled(mainSEVM.shouldDisplayDismissWarning)
|
||||||
}
|
.onChange(of: appAccounts.currentClient) { _, newValue in
|
||||||
}
|
if mainSEVM.mode.isInShareExtension {
|
||||||
}
|
currentAccount.setClient(client: newValue)
|
||||||
.onDrop(of: [.image, .video, .gif, .mpeg4Movie, .quickTimeMovie, .movie],
|
mainSEVM.client = newValue
|
||||||
delegate: focusedSEVM)
|
for post in followUpSEVMs {
|
||||||
.onChange(of: currentAccount.account?.id) {
|
post.client = newValue
|
||||||
mainSEVM.currentAccount = currentAccount.account
|
}
|
||||||
for p in followUpSEVMs {
|
}
|
||||||
p.currentAccount = mainSEVM.currentAccount
|
}
|
||||||
}
|
.onDrop(of: [.image, .video, .gif, .mpeg4Movie, .quickTimeMovie, .movie],
|
||||||
}
|
delegate: focusedSEVM)
|
||||||
.onChange(of: mainSEVM.visibility) {
|
.onChange(of: currentAccount.account?.id) {
|
||||||
for p in followUpSEVMs {
|
mainSEVM.currentAccount = currentAccount.account
|
||||||
p.visibility = mainSEVM.visibility
|
for p in followUpSEVMs {
|
||||||
}
|
p.currentAccount = mainSEVM.currentAccount
|
||||||
}
|
}
|
||||||
.onChange(of: followUpSEVMs.count) { oldValue, newValue in
|
}
|
||||||
if oldValue < newValue {
|
.onChange(of: mainSEVM.visibility) {
|
||||||
Task {
|
for p in followUpSEVMs {
|
||||||
try? await Task.sleep(for: .seconds(0.1))
|
p.visibility = mainSEVM.visibility
|
||||||
withAnimation(.bouncy(duration: 0.5)) {
|
}
|
||||||
scrollID = followUpSEVMs.last?.id
|
}
|
||||||
|
.onChange(of: followUpSEVMs.count) { oldValue, newValue in
|
||||||
|
if oldValue < newValue {
|
||||||
|
Task {
|
||||||
|
try? await Task.sleep(for: .seconds(0.1))
|
||||||
|
withAnimation(.bouncy(duration: 0.5)) {
|
||||||
|
scrollID = followUpSEVMs.last?.id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if mainSEVM.isPosting {
|
if mainSEVM.isPosting {
|
||||||
ProgressView(value: mainSEVM.postingProgress, total: 100.0)
|
ProgressView(value: mainSEVM.postingProgress, total: 100.0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,7 +313,7 @@ public extension StatusEditor {
|
||||||
processItemsProvider(items: items)
|
processItemsProvider(items: items)
|
||||||
case let .imageURL(urls, visibility):
|
case let .imageURL(urls, visibility):
|
||||||
Task {
|
Task {
|
||||||
for container in await Self.makeImageContainer(from: urls) {
|
for container in await Self.makeImageContainer(from: urls) {
|
||||||
prepareToPost(for: container)
|
prepareToPost(for: container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -746,16 +746,16 @@ public extension StatusEditor {
|
||||||
error: nil
|
error: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func makeImageContainer(from urls: [URL]) async -> [MediaContainer] {
|
private static func makeImageContainer(from urls: [URL]) async -> [MediaContainer] {
|
||||||
var containers: [MediaContainer] = []
|
var containers: [MediaContainer] = []
|
||||||
|
|
||||||
for url in urls {
|
for url in urls {
|
||||||
let compressor = Compressor()
|
let compressor = Compressor()
|
||||||
|
|
||||||
if let compressedData = await compressor.compressImageFrom(url: url),
|
if let compressedData = await compressor.compressImageFrom(url: url),
|
||||||
let image = UIImage(data: compressedData) {
|
let image = UIImage(data: compressedData)
|
||||||
|
{
|
||||||
containers.append(MediaContainer(
|
containers.append(MediaContainer(
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
image: image,
|
image: image,
|
||||||
|
@ -766,7 +766,7 @@ public extension StatusEditor {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return containers
|
return containers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue