More Swift 6 concurrency

This commit is contained in:
Thomas Ricouard 2024-07-21 11:48:27 +02:00
parent 57452a676a
commit 8ce8eba157
17 changed files with 82 additions and 59 deletions

View file

@ -44,6 +44,7 @@ public struct ReportView: View {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
isSendingReport = true
let client = client
Task {
do {
let _: ReportSent =

View file

@ -140,13 +140,13 @@ struct AboutView: View {
private func fetchAccounts() async {
await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
let viewModel = try await fetchAccountViewModel(account: "dimillian@mastodon.social")
let viewModel = try await fetchAccountViewModel(client, account: "dimillian@mastodon.social")
await MainActor.run {
dimillianAccount = viewModel
}
}
group.addTask {
let viewModel = try await fetchAccountViewModel(account: "icecubesapp@mastodon.online")
let viewModel = try await fetchAccountViewModel(client, account: "icecubesapp@mastodon.online")
await MainActor.run {
iceCubesAccount = viewModel
}
@ -154,7 +154,7 @@ struct AboutView: View {
}
}
private func fetchAccountViewModel(account: String) async throws -> AccountsListRowViewModel {
private func fetchAccountViewModel(_ client: Client, account: String) async throws -> AccountsListRowViewModel {
let dimillianAccount: Account = try await client.get(endpoint: Accounts.lookup(name: account))
let rel: [Relationship] = try await client.get(endpoint: Accounts.relationships(ids: [dimillianAccount.id]))
return .init(account: dimillianAccount, relationShip: rel.first)

View file

@ -94,6 +94,7 @@ struct AddAccountView: View {
}
.onAppear {
isInstanceURLFieldFocused = true
let instanceName = instanceName
Task {
let instances = await instanceSocialClient.fetchInstances(keyword: instanceName)
withAnimation {
@ -104,6 +105,8 @@ struct AddAccountView: View {
}
.onChange(of: instanceName) {
searchingTask.cancel()
let instanceName = instanceName
let instanceSocialClient = instanceSocialClient
searchingTask = Task {
try? await Task.sleep(for: .seconds(0.1))
guard !Task.isCancelled else { return }
@ -129,7 +132,7 @@ struct AddAccountView: View {
let instance: Instance = try await instanceDetailClient.get(endpoint: Instances.instance)
withAnimation {
self.instance = instance
instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
self.instanceName = sanitizedName // clean up the text box, principally to chop off the username if present so it's clear that you might not wind up siging in as the thing in the box
}
instanceFetchError = nil
} else {

View file

@ -77,6 +77,7 @@ struct AddRemoteTimelineView: View {
.onAppear {
isInstanceURLFieldFocused = true
let client = InstanceSocialClient()
let instanceName = instanceName
Task {
instances = await client.fetchInstances(keyword: instanceName)
}

View file

@ -35,6 +35,7 @@ public struct AccountDetailContextMenu: View {
if viewModel.relationship?.blocking == true {
Button {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.unblock(id: account.id))
@ -53,6 +54,7 @@ public struct AccountDetailContextMenu: View {
if viewModel.relationship?.muting == true {
Button {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.unmute(id: account.id))
@ -65,6 +67,7 @@ public struct AccountDetailContextMenu: View {
Menu {
ForEach(Duration.mutingDurations(), id: \.rawValue) { duration in
Button(duration.description) {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.mute(id: account.id, json: MuteData(duration: duration.rawValue)))
@ -82,6 +85,7 @@ public struct AccountDetailContextMenu: View {
{
if relationship.notifying {
Button {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
@ -94,6 +98,7 @@ public struct AccountDetailContextMenu: View {
}
} else {
Button {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
@ -107,6 +112,7 @@ public struct AccountDetailContextMenu: View {
}
if relationship.showingReblogs {
Button {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,
@ -119,6 +125,7 @@ public struct AccountDetailContextMenu: View {
}
} else {
Button {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.follow(id: account.id,

View file

@ -364,6 +364,7 @@ public struct AccountDetailView: View {
.confirmationDialog("Block User", isPresented: $showBlockConfirmation) {
if let account = viewModel.account {
Button("account.action.block-user-\(account.username)", role: .destructive) {
let client = client
Task {
do {
viewModel.relationship = try await client.post(endpoint: Accounts.block(id: account.id))

View file

@ -118,7 +118,7 @@ struct EditFilterView: View {
.focused($focusedField, equals: .title)
.onSubmit {
Task {
await saveFilter()
await saveFilter(client)
}
}
}
@ -130,7 +130,7 @@ struct EditFilterView: View {
Section {
Button {
Task {
await saveFilter()
await saveFilter(client)
}
} label: {
if isSavingFilter {
@ -158,7 +158,7 @@ struct EditFilterView: View {
Spacer()
Button {
Task {
await deleteKeyword(keyword: keyword)
await deleteKeyword(client, keyword: keyword)
}
} label: {
Image(systemName: "trash")
@ -170,7 +170,7 @@ struct EditFilterView: View {
if let index = indexes.first {
let keyword = keywords[index]
Task {
await deleteKeyword(keyword: keyword)
await deleteKeyword(client, keyword: keyword)
}
}
}
@ -179,7 +179,7 @@ struct EditFilterView: View {
.focused($focusedField, equals: .newKeyword)
.onSubmit {
Task {
await addKeyword(name: newKeyword)
await addKeyword(client, name: newKeyword)
newKeyword = ""
focusedField = .newKeyword
}
@ -189,7 +189,7 @@ struct EditFilterView: View {
Button {
Task {
Task {
await addKeyword(name: newKeyword)
await addKeyword(client, name: newKeyword)
newKeyword = ""
}
}
@ -217,7 +217,7 @@ struct EditFilterView: View {
contexts.append(context)
}
Task {
await saveFilter()
await saveFilter(client)
}
})) {
Label(context.name, systemImage: context.iconName)
@ -242,7 +242,7 @@ struct EditFilterView: View {
}
.onChange(of: filterAction) {
Task {
await saveFilter()
await saveFilter(client)
}
}
.pickerStyle(.inline)
@ -256,11 +256,11 @@ struct EditFilterView: View {
Button {
Task {
if !newKeyword.isEmpty {
await addKeyword(name: newKeyword)
await addKeyword(client, name: newKeyword)
newKeyword = ""
focusedField = .newKeyword
}
await saveFilter()
await saveFilter(client)
dismiss()
}
} label: {
@ -273,7 +273,7 @@ struct EditFilterView: View {
.disabled(!canSave)
}
private func saveFilter() async {
private func saveFilter(_ client: Client) async {
do {
isSavingFilter = true
if let filter {
@ -288,7 +288,7 @@ struct EditFilterView: View {
isSavingFilter = false
}
private func addKeyword(name: String) async {
private func addKeyword(_ client: Client, name: String) async {
guard let filterId = filter?.id else { return }
isSavingFilter = true
do {
@ -302,7 +302,7 @@ struct EditFilterView: View {
isSavingFilter = false
}
private func deleteKeyword(keyword: ServerFilter.Keyword) async {
private func deleteKeyword(_ client: Client, keyword: ServerFilter.Keyword) async {
isSavingFilter = true
do {
let response = try await client.delete(endpoint: ServerFilters.removeKeyword(id: keyword.id),

View file

@ -50,7 +50,7 @@ public struct FiltersListView: View {
}
}
.onDelete { indexes in
deleteFilter(indexes: indexes)
deleteFilter(client, indexes: indexes)
}
}
}
@ -78,6 +78,7 @@ public struct FiltersListView: View {
.background(theme.secondaryBackgroundColor)
#endif
.task {
let client = client
do {
isLoading = true
filters = try await client.get(endpoint: ServerFilters.filters, forceVersion: .v2)
@ -89,7 +90,7 @@ public struct FiltersListView: View {
}
}
private func deleteFilter(indexes: IndexSet) {
private func deleteFilter(_ client: Client, indexes: IndexSet) {
if let index = indexes.first {
Task {
do {

View file

@ -108,6 +108,7 @@ struct ConversationMessageView: View {
Label("status.action.copy-text", systemImage: "doc.on.doc")
}
Button {
let client = client
Task {
do {
let status: Status
@ -125,7 +126,9 @@ struct ConversationMessageView: View {
Label(isLiked ? "status.action.unfavorite" : "status.action.favorite",
systemImage: isLiked ? "star.fill" : "star")
}
Button { Task {
Button {
let client = client
Task {
do {
let status: Status
if isBookmarked {
@ -144,6 +147,7 @@ struct ConversationMessageView: View {
Divider()
if message.account.id == currentAccount.account?.id {
Button("status.action.delete", role: .destructive) {
let client = client
Task {
_ = try await client.delete(endpoint: Statuses.status(id: message.id))
}

View file

@ -97,8 +97,8 @@ import OSLog
}
let rawEvent = try decoder.decode(RawStreamEvent.self, from: data)
logger.info("Stream update: \(rawEvent.event)")
if let event = rawEventToEvent(rawEvent: rawEvent) {
Task { @MainActor in
Task { @MainActor in
if let event = self.rawEventToEvent(rawEvent: rawEvent) {
self.events.append(event)
self.latestEvent = event
if let event = event as? StreamEventNotification, event.notification.status?.visibility != .direct {
@ -114,10 +114,13 @@ import OSLog
break
}
receiveMessage()
Task { @MainActor in
self.receiveMessage()
}
case .failure:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(retryDelay)) {
Task { @MainActor in
try? await Task.sleep(for: .seconds(self.retryDelay))
self.retryDelay += 30
self.stopWatching()
self.connect()

View file

@ -42,6 +42,7 @@ public struct ListCreateView: View {
CancelToolbarItem()
ToolbarItem {
Button {
let client = client
Task {
isSaving = true
let _: Models.List = try await client.post(endpoint: Lists.createList(title: title,

View file

@ -1,17 +1,17 @@
import Foundation
public struct RawStreamEvent: Decodable {
public struct RawStreamEvent: Decodable, Sendable{
public let event: String
public let stream: [String]
public let payload: String
}
public protocol StreamEvent: Identifiable {
public protocol StreamEvent: Identifiable, Sendable {
var date: Date { get }
var id: String { get }
}
public struct StreamEventUpdate: StreamEvent {
public struct StreamEventUpdate: StreamEvent, Sendable {
public let date = Date()
public var id: String { status.id }
public let status: Status
@ -20,7 +20,7 @@ public struct StreamEventUpdate: StreamEvent {
}
}
public struct StreamEventStatusUpdate: StreamEvent {
public struct StreamEventStatusUpdate: StreamEvent, Sendable {
public let date = Date()
public var id: String { status.id + (status.editedAt?.asDate.description ?? "") }
public let status: Status
@ -29,7 +29,7 @@ public struct StreamEventStatusUpdate: StreamEvent {
}
}
public struct StreamEventDelete: StreamEvent {
public struct StreamEventDelete: StreamEvent, Sendable {
public let date = Date()
public var id: String { status + date.description }
public let status: String
@ -38,7 +38,7 @@ public struct StreamEventDelete: StreamEvent {
}
}
public struct StreamEventNotification: StreamEvent {
public struct StreamEventNotification: StreamEvent, Sendable {
public let date = Date()
public var id: String { notification.id }
public let notification: Notification
@ -47,7 +47,7 @@ public struct StreamEventNotification: StreamEvent {
}
}
public struct StreamEventConversation: StreamEvent {
public struct StreamEventConversation: StreamEvent, Sendable {
public let date = Date()
public var id: String { conversation.id }
public let conversation: Conversation

View file

@ -65,7 +65,7 @@ public struct NotificationsListView: View {
Button {
viewModel.selectedType = nil
Task {
await viewModel.fetchNotifications()
await viewModel.fetchNotifications(viewModel.selectedType)
}
} label: {
Label("notifications.navigation-title", systemImage: "bell.fill")
@ -75,7 +75,7 @@ public struct NotificationsListView: View {
Button {
viewModel.selectedType = type
Task {
await viewModel.fetchNotifications()
await viewModel.fetchNotifications(viewModel.selectedType)
}
} label: {
Label {
@ -116,27 +116,27 @@ public struct NotificationsListView: View {
viewModel.loadSelectedType()
}
Task {
await viewModel.fetchNotifications()
await viewModel.fetchNotifications(viewModel.selectedType)
await viewModel.fetchPolicy()
}
}
.refreshable {
SoundEffectManager.shared.playSound(.pull)
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
await viewModel.fetchNotifications()
await viewModel.fetchNotifications(viewModel.selectedType)
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
SoundEffectManager.shared.playSound(.refresh)
}
.onChange(of: watcher.latestEvent?.id) {
if let latestEvent = watcher.latestEvent {
viewModel.handleEvent(event: latestEvent)
viewModel.handleEvent(selectedType: viewModel.selectedType, event: latestEvent)
}
}
.onChange(of: scenePhase) { _, newValue in
switch newValue {
case .active:
Task {
await viewModel.fetchNotifications()
await viewModel.fetchNotifications(viewModel.selectedType)
}
default:
break
@ -202,7 +202,7 @@ public struct NotificationsListView: View {
EmptyView()
case .hasNextPage:
NextPageView {
try await viewModel.fetchNextPage()
try await viewModel.fetchNextPage(viewModel.selectedType)
}
.listRowInsets(.init(top: .layoutPadding,
leading: .layoutPadding + 4,
@ -219,7 +219,7 @@ public struct NotificationsListView: View {
message: "notifications.error.message",
buttonTitle: "action.retry")
{
await viewModel.fetchNotifications()
await viewModel.fetchNotifications(viewModel.selectedType)
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)

View file

@ -20,28 +20,28 @@ struct NotificationsPolicyView: View {
Toggle(isOn: .init(get: { policy?.filterNotFollowing == true },
set: { newValue in
policy?.filterNotFollowing = newValue
Task { await updatePolicy() }
Task { await updatePolicy(client) }
}), label: {
Text("notifications.content-filter.peopleYouDontFollow")
})
Toggle(isOn: .init(get: { policy?.filterNotFollowers == true },
set: { newValue in
policy?.filterNotFollowers = newValue
Task { await updatePolicy() }
Task { await updatePolicy(client) }
}), label: {
Text("notifications.content-filter.peopleNotFollowingYou")
})
Toggle(isOn: .init(get: { policy?.filterNewAccounts == true },
set: { newValue in
policy?.filterNewAccounts = newValue
Task { await updatePolicy() }
Task { await updatePolicy(client) }
}), label: {
Text("notifications.content-filter.newAccounts")
})
Toggle(isOn: .init(get: { policy?.filterPrivateMentions == true },
set: { newValue in
policy?.filterPrivateMentions = newValue
Task { await updatePolicy() }
Task { await updatePolicy(client) }
}), label: {
Text("notifications.content-filter.privateMentions")
})
@ -57,14 +57,14 @@ struct NotificationsPolicyView: View {
.toolbar { CloseToolbarItem() }
.disabled(policy == nil || isUpdating)
.task {
await getPolicy()
await getPolicy(client)
}
}
.presentationDetents([.medium])
.presentationBackground(.thinMaterial)
}
private func getPolicy() async {
private func getPolicy(_ client: Client) async {
defer {
isUpdating = false
}
@ -76,7 +76,7 @@ struct NotificationsPolicyView: View {
}
}
private func updatePolicy() async {
private func updatePolicy(_ client: Client) async {
if let policy {
defer {
isUpdating = false

View file

@ -78,7 +78,7 @@ import SwiftUI
private var consolidatedNotifications: [ConsolidatedNotification] = []
func fetchNotifications() async {
func fetchNotifications(_ selectedType: Models.Notification.NotificationType?) async {
guard let client, let currentAccount else { return }
do {
var nextPageState: State.PagingState = .hasNextPage
@ -150,7 +150,7 @@ import SwiftUI
return allNotifications
}
func fetchNextPage() async throws {
func fetchNextPage(_ selectedType: Models.Notification.NotificationType?) async throws {
guard let client else { return }
guard let lastId = consolidatedNotifications.last?.notificationIds.last else { return }
let newNotifications: [Models.Notification]
@ -185,7 +185,7 @@ import SwiftUI
policy = try? await client?.get(endpoint: Notifications.policy)
}
func handleEvent(event: any StreamEvent) {
func handleEvent(selectedType: Models.Notification.NotificationType?, event: any StreamEvent) {
Task {
// Check if the event is a notification,
// if it is not already in the list,

View file

@ -32,7 +32,7 @@ public struct NotificationsRequestsListView: View {
message: "notifications.error.message",
buttonTitle: "action.retry")
{
await fetchRequests()
await fetchRequests(client)
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
@ -43,13 +43,13 @@ public struct NotificationsRequestsListView: View {
NotificationsRequestsRowView(request: request)
.swipeActions {
Button {
Task { await acceptRequest(request) }
Task { await acceptRequest(client, request) }
} label: {
Label("account.follow-request.accept", systemImage: "checkmark")
}
Button {
Task { await dismissRequest(request) }
Task { await dismissRequest(client, request) }
} label: {
Label("account.follow-request.reject", systemImage: "xmark")
}
@ -66,14 +66,14 @@ public struct NotificationsRequestsListView: View {
.navigationTitle("notifications.content-filter.requests.title")
.navigationBarTitleDisplayMode(.inline)
.task {
await fetchRequests()
await fetchRequests(client)
}
.refreshable {
await fetchRequests()
await fetchRequests(client)
}
}
private func fetchRequests() async {
private func fetchRequests(_ client: Client) async {
do {
viewState = try .requests(await client.get(endpoint: Notifications.requests))
} catch {
@ -81,13 +81,13 @@ public struct NotificationsRequestsListView: View {
}
}
private func acceptRequest(_ request: NotificationsRequest) async {
private func acceptRequest(_ client: Client, _ request: NotificationsRequest) async {
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id))
await fetchRequests()
await fetchRequests(client)
}
private func dismissRequest(_ request: NotificationsRequest) async {
private func dismissRequest(_ client: Client, _ request: NotificationsRequest) async {
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id))
await fetchRequests()
await fetchRequests(client)
}
}

View file

@ -199,6 +199,7 @@ public struct StatusRowView: View {
isPresented: $isBlockConfirmationPresented)
{
Button("account.action.block", role: .destructive) {
let client = client
Task {
do {
let operationAccount = viewModel.status.reblog?.account ?? viewModel.status.account