Signal-iOS/SignalMessaging/Views/AvatarImageView.swift

212 lines
6.1 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import UIKit
@objc
open class AvatarImageView: UIImageView {
public init() {
super.init(frame: .zero)
self.configureView()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.configureView()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.configureView()
}
public override init(image: UIImage?) {
super.init(image: image)
self.configureView()
}
func configureView() {
self.autoPinToSquareAspectRatio()
self.layer.minificationFilter = .trilinear
self.layer.magnificationFilter = .trilinear
self.layer.masksToBounds = true
self.contentMode = .scaleToFill
}
override public func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.size.width / 2
}
}
/// Avatar View which updates itself as necessary when the profile, contact, or group picture changes.
@objc
public class ConversationAvatarImageView: AvatarImageView {
var thread: TSThread
let diameter: UInt
// nil if group avatar
let recipientAddress: SignalServiceAddress?
// nil if contact avatar
let groupThreadId: String?
required public init(thread: TSThread, diameter: UInt) {
self.thread = thread
self.diameter = diameter
switch thread {
case let contactThread as TSContactThread:
self.recipientAddress = contactThread.contactAddress
self.groupThreadId = nil
case let groupThread as TSGroupThread:
self.recipientAddress = nil
self.groupThreadId = groupThread.uniqueId
default:
owsFailDebug("unexpected thread type: \(thread.uniqueId)")
self.recipientAddress = nil
self.groupThreadId = nil
}
super.init(frame: .zero)
if recipientAddress != nil {
NotificationCenter.default.addObserver(self, selector: #selector(handleOtherUsersProfileChanged(notification:)), name: .otherUsersProfileDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleSignalAccountsChanged(notification:)), name: .OWSContactsManagerSignalAccountsDidChange, object: nil)
}
if groupThreadId != nil {
NotificationCenter.default.addObserver(self, selector: #selector(handleGroupAvatarChanged(notification:)), name: .TSGroupThreadAvatarChanged, object: nil)
}
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .ThemeDidChange, object: nil)
// TODO group avatar changed
self.updateImageWithSneakyTransaction()
}
required public init?(coder aDecoder: NSCoder) {
notImplemented()
}
@objc func themeDidChange() {
updateImageWithSneakyTransaction()
}
@objc func handleSignalAccountsChanged(notification: Notification) {
Logger.debug("")
// PERF: It would be nice if we could do this only if *this* user's SignalAccount changed,
// but currently this is only a course grained notification.
self.updateImageWithSneakyTransaction()
}
@objc func handleOtherUsersProfileChanged(notification: Notification) {
Logger.debug("")
guard let changedAddress = notification.userInfo?[kNSNotificationKey_ProfileAddress] as? SignalServiceAddress else {
owsFailDebug("changedAddress was unexpectedly nil")
return
}
guard let recipientAddress = self.recipientAddress else {
// shouldn't call this for group threads
owsFailDebug("recipientAddress was unexpectedly nil")
return
}
guard recipientAddress == changedAddress else {
// not this avatar
return
}
self.updateImageWithSneakyTransaction()
}
@objc func handleGroupAvatarChanged(notification: Notification) {
Logger.debug("")
guard let changedGroupThreadId = notification.userInfo?[TSGroupThread_NotificationKey_UniqueId] as? String else {
owsFailDebug("groupThreadId was unexpectedly nil")
return
}
guard let groupThreadId = self.groupThreadId else {
// shouldn't call this for contact threads
owsFailDebug("groupThreadId was unexpectedly nil")
return
}
guard groupThreadId == changedGroupThreadId else {
// not this avatar
return
}
guard let latestThread = (databaseStorage.read { transaction in
TSThread.anyFetch(uniqueId: self.thread.uniqueId, transaction: transaction)
}) else {
owsFailDebug("Missing thread.")
return
}
self.thread = latestThread
self.updateImageWithSneakyTransaction()
}
public func updateImageWithSneakyTransaction() {
databaseStorage.read { transaction in
self.updateImage(transaction: transaction)
}
}
public func updateImage(transaction: SDSAnyReadTransaction) {
Logger.debug("updateImage")
self.image = OWSAvatarBuilder.buildImage(thread: thread,
diameter: diameter,
transaction: transaction)
}
}
@objc
public class AvatarImageButton: UIButton {
// MARK: - Button Overrides
override public func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = frame.size.width / 2
}
override public func setImage(_ image: UIImage?, for state: UIControl.State) {
ensureViewConfigured()
super.setImage(image, for: state)
}
// MARK: Private
var hasBeenConfigured = false
func ensureViewConfigured() {
guard !hasBeenConfigured else {
return
}
hasBeenConfigured = true
autoPinToSquareAspectRatio()
layer.minificationFilter = .trilinear
layer.magnificationFilter = .trilinear
layer.masksToBounds = true
contentMode = .scaleToFill
}
}