mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
Fix broken avatar aspect ratio in the share sheet and notifications
The media repo serves thumbnails with the scale method, so avatars keep their source aspect ratio. The system stretches whatever INImage we donate to fill a circle, distorting any non-square avatar in the share sheet's conversation suggestions (and notifications, which are donated the same image). Centre-crop donated avatars to a square before handing them to the system, in both the timeline's send message intent donation and the NSE's communication context.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
enum Avatars {
|
||||
enum Size {
|
||||
@@ -38,6 +39,28 @@ enum Avatars {
|
||||
}
|
||||
}
|
||||
|
||||
/// Media repo thumbnails are scaled, not cropped, so they keep the source's aspect
|
||||
/// ratio. System UI (share sheet suggestions, notifications) stretches non-square
|
||||
/// images to fill its circle, so centre-crop to a square before donating them.
|
||||
nonisolated static func squareAvatarImageData(from data: Data) -> Data? {
|
||||
guard let image = UIImage(data: data) else { return nil }
|
||||
|
||||
let side = min(image.size.width, image.size.height)
|
||||
guard side > 0 else { return nil }
|
||||
|
||||
if image.size.width == image.size.height {
|
||||
return data
|
||||
}
|
||||
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = image.scale
|
||||
|
||||
return UIGraphicsImageRenderer(size: CGSize(width: side, height: side), format: format)
|
||||
.pngData { _ in
|
||||
image.draw(at: CGPoint(x: (side - image.size.width) / 2, y: (side - image.size.height) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
static func generatePlaceholderAvatarImageData(name: String, id: String, size: CGSize) -> Data? {
|
||||
let image = PlaceholderAvatarImage(name: name, contentID: id)
|
||||
|
||||
@@ -650,8 +650,9 @@ class TimelineController: TimelineControllerProtocol {
|
||||
}
|
||||
|
||||
if let avatarURL, let mediaSource = try? MediaSourceProxy(url: avatarURL, mimeType: nil) {
|
||||
if case let .success(avatarData) = await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) {
|
||||
sendMessageIntent.setImage(INImage(imageData: avatarData), forParameterNamed: \.speakableGroupName)
|
||||
if case let .success(avatarData) = await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)),
|
||||
let squareAvatarData = Avatars.squareAvatarImageData(from: avatarData) {
|
||||
sendMessageIntent.setImage(INImage(imageData: squareAvatarData), forParameterNamed: \.speakableGroupName)
|
||||
} else {
|
||||
addPlacehoder()
|
||||
}
|
||||
|
||||
@@ -259,7 +259,9 @@ nonisolated struct NotificationContentBuilder {
|
||||
let mediaSource = icon.mediaSource {
|
||||
switch await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) {
|
||||
case .success(let data):
|
||||
fetchedImage = INImage(imageData: data)
|
||||
if let squareData = Avatars.squareAvatarImageData(from: data) {
|
||||
fetchedImage = INImage(imageData: squareData)
|
||||
}
|
||||
case .failure(let error):
|
||||
MXLog.error("Couldn't add sender icon: \(error)")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user