From 809584cc54fcdf68690523943688ac1b2a0ba35c Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 21 Sep 2020 18:04:08 -0400 Subject: [PATCH] Fix crash when opening Compose screen before account/instance is loaded Prevents when opening the Compose screen with poor network connectivity --- Tusker/Controllers/MastodonController.swift | 19 +++++----- .../Compose/ComposeAttachmentsList.swift | 4 +- .../Compose/ComposeAvatarImageView.swift | 37 ++++++++++++------- .../Compose/ComposeCurrentAccount.swift | 29 +++++++++------ Tusker/Screens/Compose/ComposeView.swift | 2 +- 5 files changed, 55 insertions(+), 36 deletions(-) diff --git a/Tusker/Controllers/MastodonController.swift b/Tusker/Controllers/MastodonController.swift index 5723f4df..308e3986 100644 --- a/Tusker/Controllers/MastodonController.swift +++ b/Tusker/Controllers/MastodonController.swift @@ -9,7 +9,7 @@ import Foundation import Pachyderm -class MastodonController { +class MastodonController: ObservableObject { static private(set) var all = [LocalData.UserAccountInfo: MastodonController]() @@ -42,8 +42,8 @@ class MastodonController { let client: Client! - var account: Account! - var instance: Instance! + @Published private(set) var account: Account! + @Published private(set) var instance: Instance! var loggedIn: Bool { accountInfo != nil @@ -95,7 +95,9 @@ class MastodonController { completion?(.failure(error)) case let .success(account, _): - self.account = account + DispatchQueue.main.async { + self.account = account + } self.persistentContainer.backgroundContext.perform { if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) { accountMO.updateFrom(apiAccount: account, container: self.persistentContainer) @@ -118,13 +120,12 @@ class MastodonController { let request = Client.getInstance() run(request) { (response) in guard case let .success(instance, _) = response else { fatalError() } - self.instance = instance - completion?(instance) + DispatchQueue.main.async { + self.instance = instance + completion?(instance) + } } } } } - -// ObservableObject so that SwiftUI views can receive it through @EnvironmentObject -extension MastodonController: ObservableObject {} diff --git a/Tusker/Screens/Compose/ComposeAttachmentsList.swift b/Tusker/Screens/Compose/ComposeAttachmentsList.swift index 584fc397..f63ae1eb 100644 --- a/Tusker/Screens/Compose/ComposeAttachmentsList.swift +++ b/Tusker/Screens/Compose/ComposeAttachmentsList.swift @@ -91,7 +91,9 @@ struct ComposeAttachmentsList: View { } private var canAddAttachment: Bool { - switch mastodonController.instance.instanceType { + switch mastodonController.instance?.instanceType { + case nil: + return false case .pleroma: return true case .mastodon: diff --git a/Tusker/Screens/Compose/ComposeAvatarImageView.swift b/Tusker/Screens/Compose/ComposeAvatarImageView.swift index f0d466d4..292c206b 100644 --- a/Tusker/Screens/Compose/ComposeAvatarImageView.swift +++ b/Tusker/Screens/Compose/ComposeAvatarImageView.swift @@ -9,7 +9,7 @@ import SwiftUI struct ComposeAvatarImageView: View { - let url: URL + let url: URL? @State var request: ImageCache.Request? = nil @State var avatarImage: UIImage? = nil @ObservedObject var preferences = Preferences.shared @@ -19,7 +19,9 @@ struct ComposeAvatarImageView: View { .resizable() .frame(width: 50, height: 50) .cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50) - .onAppear(perform: self.loadImage) + .conditionally(url != nil) { + $0.onAppear(perform: self.loadImage) + } .onDisappear(perform: self.cancelRequest) } @@ -27,24 +29,33 @@ struct ComposeAvatarImageView: View { if let avatarImage = avatarImage { return Image(uiImage: avatarImage) } else { - let imageName: String - switch preferences.avatarStyle { - case .circle: - imageName = "person.crop.circle" - case .roundRect: - imageName = "person.crop.square" - } - return Image(systemName: imageName) + return placeholderImage } } + private var placeholderImage: Image { + let imageName: String + switch preferences.avatarStyle { + case .circle: + imageName = "person.crop.circle" + case .roundRect: + imageName = "person.crop.square" + } + return Image(systemName: imageName) + } + private func loadImage() { + guard let url = url else { return } request = ImageCache.avatars.get(url) { (data) in - DispatchQueue.main.async { - self.request = nil - if let data = data, let image = UIImage(data: data) { + if let data = data, let image = UIImage(data: data) { + DispatchQueue.main.async { + self.request = nil self.avatarImage = image } + } else { + DispatchQueue.main.async { + self.request = nil + } } } } diff --git a/Tusker/Screens/Compose/ComposeCurrentAccount.swift b/Tusker/Screens/Compose/ComposeCurrentAccount.swift index 82da83de..1a723d40 100644 --- a/Tusker/Screens/Compose/ComposeCurrentAccount.swift +++ b/Tusker/Screens/Compose/ComposeCurrentAccount.swift @@ -12,24 +12,29 @@ import Pachyderm struct ComposeCurrentAccount: View { @EnvironmentObject var mastodonController: MastodonController - var account: Account { - mastodonController.account! + var account: Account? { + mastodonController.account } var body: some View { HStack(alignment: .top) { - ComposeAvatarImageView(url: account.avatar) - .accessibility(label: Text("\(account.displayName) avatar")) + ComposeAvatarImageView(url: account?.avatar) + .accessibility(label: Text(account != nil ? "\(account!.displayName) avatar" : "Avatar")) - VStack(alignment: .leading) { - AccountDisplayNameLabel(account: mastodonController.persistentContainer.account(for: account.id)!, fontSize: 20) - .lineLimit(1) - - Text(verbatim: "@\(account.acct)") - .font(.system(size: 17, weight: .light)) - .foregroundColor(.secondary) - .lineLimit(1) + if let id = account?.id, + let account = mastodonController.persistentContainer.account(for: id) { + VStack(alignment: .leading) { + AccountDisplayNameLabel(account: account, fontSize: 20) + .lineLimit(1) + + Text(verbatim: "@\(account.acct)") + .font(.system(size: 17, weight: .light)) + .foregroundColor(.secondary) + .lineLimit(1) + } } + + Spacer() } } } diff --git a/Tusker/Screens/Compose/ComposeView.swift b/Tusker/Screens/Compose/ComposeView.swift index e6ac0e7a..48e7c677 100644 --- a/Tusker/Screens/Compose/ComposeView.swift +++ b/Tusker/Screens/Compose/ComposeView.swift @@ -28,7 +28,7 @@ struct ComposeView: View { } var charactersRemaining: Int { - let limit = mastodonController.instance.maxStatusCharacters ?? 500 + let limit = mastodonController.instance?.maxStatusCharacters ?? 500 let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0 return limit - (cwCount + CharacterCounter.count(text: draft.text)) }