Compare commits

..

No commits in common. "5c479e3bf05544b653a6717205e6fe2c81cdc4f2" and "9d1c3f14109d4e0ad7400b496a5e82cee88e7bd0" have entirely different histories.

3 changed files with 58 additions and 95 deletions

View File

@ -112,12 +112,12 @@ public final class Account: AccountProtocol, Decodable {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow") return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unfollow")
} }
public static func block(_ accountID: String) -> Request<Relationship> { public static func block(_ account: Account) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/block") return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(account.id)/block")
} }
public static func unblock(_ accountID: String) -> Request<Relationship> { public static func unblock(_ account: Account) -> Request<Relationship> {
return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(accountID)/unblock") return Request<Relationship>(method: .post, path: "/api/v1/accounts/\(account.id)/unblock")
} }
public static func mute(_ account: Account, notifications: Bool? = nil) -> Request<Relationship> { public static func mute(_ account: Account, notifications: Bool? = nil) -> Request<Relationship> {

View File

@ -67,24 +67,15 @@ enum CompositionAttachmentData {
completion(.failure(.missingData)) completion(.failure(.missingData))
return return
} }
let utType: UTType
let image = CIImage(data: data)!
let needsColorSpaceConversion = image.colorSpace?.name != CGColorSpace.sRGB
// neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG let utType: UTType
// they also do a bad job converting wide color gamut images (they seem to just drop the profile, letting the wide-gamut values be reinterprete as sRGB) if dataUTI == "public.heic" {
// if that changes in the future, we'll need to pass the InstanceFeatures in here somehow and gate the conversion // neither Mastodon nor Pleroma handles HEIC well, so convert to JPEG
if needsColorSpaceConversion || dataUTI == "public.heic" { let image = CIImage(data: data)!
let context = CIContext() let context = CIContext()
let sRGB = CGColorSpace(name: CGColorSpace.sRGB)! let colorSpace = image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!
if dataUTI == "public.png" { data = context.jpegRepresentation(of: image, colorSpace: colorSpace, options: [:])!
data = context.pngRepresentation(of: image, format: .ARGB8, colorSpace: sRGB)! utType = .jpeg
utType = .png
} else {
data = context.jpegRepresentation(of: image, colorSpace: sRGB)!
utType = .jpeg
}
} else { } else {
utType = UTType(dataUTI)! utType = UTType(dataUTI)!
} }

View File

@ -42,6 +42,46 @@ extension MenuActionProvider {
guard let mastodonController = mastodonController, guard let mastodonController = mastodonController,
let account = mastodonController.persistentContainer.account(for: accountID) else { return [] } let account = mastodonController.persistentContainer.account(for: accountID) else { return [] }
guard let loggedInAccountID = mastodonController.accountInfo?.id else {
// user is logged out
return [
openInSafariAction(url: account.url),
createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in
guard let self = self else { return }
self.navigationDelegate?.showMoreOptions(forAccount: accountID, sourceView: sourceView)
})
]
}
let actionsSection: [UIMenuElement] = [
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in
guard let self = self else { return }
let draft = self.mastodonController!.createDraft(mentioningAcct: account.acct)
draft.visibility = .direct
self.navigationDelegate?.compose(editing: draft)
}),
UIDeferredMenuElement.uncached({ @MainActor [unowned self] elementHandler in
let relationship = Task {
await fetchRelationship(accountID: accountID, mastodonController: mastodonController)
}
// workaround for #198, may result in showing outdated relationship, so only do so where necessary
if ProcessInfo.processInfo.isiOSAppOnMac,
let mo = mastodonController.persistentContainer.relationship(forAccount: accountID),
let action = self.followAction(for: mo, mastodonController: mastodonController) {
elementHandler([action])
} else {
Task { @MainActor in
if let relationship = await relationship.value,
let action = self.followAction(for: relationship, mastodonController: mastodonController) {
elementHandler([action])
} else {
elementHandler([])
}
}
}
})
]
var shareSection = [ var shareSection = [
openInSafariAction(url: account.url), openInSafariAction(url: account.url),
createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in
@ -50,32 +90,11 @@ extension MenuActionProvider {
}) })
] ]
guard let loggedInAccountID = mastodonController.accountInfo?.id else {
// user is logged out
return shareSection
}
var actionsSection: [UIMenuElement] = [
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in
guard let self = self else { return }
let draft = self.mastodonController!.createDraft(mentioningAcct: account.acct)
draft.visibility = .direct
self.navigationDelegate?.compose(editing: draft)
})
]
var suppressSection: [UIMenuElement] = []
if accountID != loggedInAccountID {
actionsSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.followAction(for: $0, mastodonController: $1) }))
suppressSection.append(relationshipAction(accountID: accountID, mastodonController: mastodonController, builder: { [unowned self] in self.blockAction(for: $0, mastodonController: $1) }))
}
addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showProfileActivity(id: accountID, accountID: loggedInAccountID)) addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showProfileActivity(id: accountID, accountID: loggedInAccountID))
return [ return [
UIMenu(options: .displayInline, children: shareSection), UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: shareSection),
UIMenu(options: .displayInline, children: actionsSection), UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection),
UIMenu(options: .displayInline, children: suppressSection),
] ]
} }
@ -348,29 +367,12 @@ extension MenuActionProvider {
}) })
} }
private func relationshipAction(accountID: String, mastodonController: MastodonController, builder: @escaping @MainActor (RelationshipMO, MastodonController) -> UIMenuElement) -> UIDeferredMenuElement {
return UIDeferredMenuElement.uncached({ @MainActor elementHandler in
let relationship = Task {
await fetchRelationship(accountID: accountID, mastodonController: mastodonController)
}
// workaround for #198, may result in showing outdated relationship, so only do so where necessary
if ProcessInfo.processInfo.isiOSAppOnMac,
let mo = mastodonController.persistentContainer.relationship(forAccount: accountID) {
elementHandler([builder(mo, mastodonController)])
} else {
Task { @MainActor in
if let relationship = await relationship.value {
elementHandler([builder(relationship, mastodonController)])
} else {
elementHandler([])
}
}
}
})
}
@MainActor @MainActor
private func followAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement { private func followAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement? {
guard let ownAccount = mastodonController.account,
relationship.accountID != ownAccount.id else {
return nil
}
let accountID = relationship.accountID let accountID = relationship.accountID
let following = relationship.following let following = relationship.following
return createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus") { _ in return createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus") { _ in
@ -391,36 +393,6 @@ extension MenuActionProvider {
} }
} }
@MainActor
private func blockAction(for relationship: RelationshipMO, mastodonController: MastodonController) -> UIMenuElement {
let accountID = relationship.accountID
let blocking = relationship.blocking
let handler = { (_: UIAction) in
let request = (blocking ? Account.unblock : Account.block)(accountID)
mastodonController.run(request) { response in
switch response {
case .failure(let error):
if let toastable = self.toastableViewController {
let config = ToastConfiguration(from: error, with: "Error \(blocking ? "Unb" : "B")locking", in: toastable, retryAction: nil)
DispatchQueue.main.async {
toastable.showToast(configuration: config, animated: true)
}
}
case .success(let relationship, _):
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
}
}
}
if blocking {
return createAction(identifier: "block", title: "Unblock", systemImageName: "circle.slash", handler: handler)
} else {
return UIMenu(title: "Block", image: UIImage(systemName: "circle.slash"), children: [
UIAction(title: "Cancel", handler: { _ in }),
UIAction(title: "Block \(relationship.account!.displayOrUserName)", image: UIImage(systemName: "circle.slash"), attributes: .destructive, handler: handler),
])
}
}
} }
private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? { private func fetchRelationship(accountID: String, mastodonController: MastodonController) async -> RelationshipMO? {