Fix crash when opening Compose screen before account/instance is loaded

Prevents when opening the Compose screen with poor network connectivity
This commit is contained in:
Shadowfacts 2020-09-21 18:04:08 -04:00
parent 9b85090884
commit 809584cc54
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 55 additions and 36 deletions

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import Pachyderm import Pachyderm
class MastodonController { class MastodonController: ObservableObject {
static private(set) var all = [LocalData.UserAccountInfo: MastodonController]() static private(set) var all = [LocalData.UserAccountInfo: MastodonController]()
@ -42,8 +42,8 @@ class MastodonController {
let client: Client! let client: Client!
var account: Account! @Published private(set) var account: Account!
var instance: Instance! @Published private(set) var instance: Instance!
var loggedIn: Bool { var loggedIn: Bool {
accountInfo != nil accountInfo != nil
@ -95,7 +95,9 @@ class MastodonController {
completion?(.failure(error)) completion?(.failure(error))
case let .success(account, _): case let .success(account, _):
self.account = account DispatchQueue.main.async {
self.account = account
}
self.persistentContainer.backgroundContext.perform { self.persistentContainer.backgroundContext.perform {
if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) { if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) {
accountMO.updateFrom(apiAccount: account, container: self.persistentContainer) accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
@ -118,13 +120,12 @@ class MastodonController {
let request = Client.getInstance() let request = Client.getInstance()
run(request) { (response) in run(request) { (response) in
guard case let .success(instance, _) = response else { fatalError() } guard case let .success(instance, _) = response else { fatalError() }
self.instance = instance DispatchQueue.main.async {
completion?(instance) self.instance = instance
completion?(instance)
}
} }
} }
} }
} }
// ObservableObject so that SwiftUI views can receive it through @EnvironmentObject
extension MastodonController: ObservableObject {}

View File

@ -91,7 +91,9 @@ struct ComposeAttachmentsList: View {
} }
private var canAddAttachment: Bool { private var canAddAttachment: Bool {
switch mastodonController.instance.instanceType { switch mastodonController.instance?.instanceType {
case nil:
return false
case .pleroma: case .pleroma:
return true return true
case .mastodon: case .mastodon:

View File

@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
struct ComposeAvatarImageView: View { struct ComposeAvatarImageView: View {
let url: URL let url: URL?
@State var request: ImageCache.Request? = nil @State var request: ImageCache.Request? = nil
@State var avatarImage: UIImage? = nil @State var avatarImage: UIImage? = nil
@ObservedObject var preferences = Preferences.shared @ObservedObject var preferences = Preferences.shared
@ -19,7 +19,9 @@ struct ComposeAvatarImageView: View {
.resizable() .resizable()
.frame(width: 50, height: 50) .frame(width: 50, height: 50)
.cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50) .cornerRadius(preferences.avatarStyle.cornerRadiusFraction * 50)
.onAppear(perform: self.loadImage) .conditionally(url != nil) {
$0.onAppear(perform: self.loadImage)
}
.onDisappear(perform: self.cancelRequest) .onDisappear(perform: self.cancelRequest)
} }
@ -27,24 +29,33 @@ struct ComposeAvatarImageView: View {
if let avatarImage = avatarImage { if let avatarImage = avatarImage {
return Image(uiImage: avatarImage) return Image(uiImage: avatarImage)
} else { } else {
let imageName: String return placeholderImage
switch preferences.avatarStyle {
case .circle:
imageName = "person.crop.circle"
case .roundRect:
imageName = "person.crop.square"
}
return Image(systemName: imageName)
} }
} }
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() { private func loadImage() {
guard let url = url else { return }
request = ImageCache.avatars.get(url) { (data) in request = ImageCache.avatars.get(url) { (data) in
DispatchQueue.main.async { if let data = data, let image = UIImage(data: data) {
self.request = nil DispatchQueue.main.async {
if let data = data, let image = UIImage(data: data) { self.request = nil
self.avatarImage = image self.avatarImage = image
} }
} else {
DispatchQueue.main.async {
self.request = nil
}
} }
} }
} }

View File

@ -12,24 +12,29 @@ import Pachyderm
struct ComposeCurrentAccount: View { struct ComposeCurrentAccount: View {
@EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var mastodonController: MastodonController
var account: Account { var account: Account? {
mastodonController.account! mastodonController.account
} }
var body: some View { var body: some View {
HStack(alignment: .top) { HStack(alignment: .top) {
ComposeAvatarImageView(url: account.avatar) ComposeAvatarImageView(url: account?.avatar)
.accessibility(label: Text("\(account.displayName) avatar")) .accessibility(label: Text(account != nil ? "\(account!.displayName) avatar" : "Avatar"))
VStack(alignment: .leading) { if let id = account?.id,
AccountDisplayNameLabel(account: mastodonController.persistentContainer.account(for: account.id)!, fontSize: 20) let account = mastodonController.persistentContainer.account(for: id) {
.lineLimit(1) VStack(alignment: .leading) {
AccountDisplayNameLabel(account: account, fontSize: 20)
Text(verbatim: "@\(account.acct)") .lineLimit(1)
.font(.system(size: 17, weight: .light))
.foregroundColor(.secondary) Text(verbatim: "@\(account.acct)")
.lineLimit(1) .font(.system(size: 17, weight: .light))
.foregroundColor(.secondary)
.lineLimit(1)
}
} }
Spacer()
} }
} }
} }

View File

@ -28,7 +28,7 @@ struct ComposeView: View {
} }
var charactersRemaining: Int { var charactersRemaining: Int {
let limit = mastodonController.instance.maxStatusCharacters ?? 500 let limit = mastodonController.instance?.maxStatusCharacters ?? 500
let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0 let cwCount = draft.contentWarningEnabled ? draft.contentWarning.count : 0
return limit - (cwCount + CharacterCounter.count(text: draft.text)) return limit - (cwCount + CharacterCounter.count(text: draft.text))
} }