mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			183 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			183 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import UIKit
 | |
| import GRDB
 | |
| import SessionUIKit
 | |
| import SessionMessagingKit
 | |
| 
 | |
| @objc(LKProfilePictureView)
 | |
| public final class ProfilePictureView: UIView {
 | |
|     private var hasTappableProfilePicture: Bool = false
 | |
|     @objc public var size: CGFloat = 0 // Not an implicitly unwrapped optional due to Obj-C limitations
 | |
|     @objc public var useFallbackPicture = false
 | |
|     @objc public var publicKey: String!
 | |
|     @objc public var additionalPublicKey: String?
 | |
|     @objc public var openGroupProfilePicture: UIImage?
 | |
|     // Constraints
 | |
|     private var imageViewWidthConstraint: NSLayoutConstraint!
 | |
|     private var imageViewHeightConstraint: NSLayoutConstraint!
 | |
|     private var additionalImageViewWidthConstraint: NSLayoutConstraint!
 | |
|     private var additionalImageViewHeightConstraint: NSLayoutConstraint!
 | |
|     
 | |
|     // MARK: Components
 | |
|     private lazy var imageView = getImageView()
 | |
|     private lazy var additionalImageView = getImageView()
 | |
|     
 | |
|     // MARK: Lifecycle
 | |
|     public override init(frame: CGRect) {
 | |
|         super.init(frame: frame)
 | |
|         setUpViewHierarchy()
 | |
|     }
 | |
|     
 | |
|     public required init?(coder: NSCoder) {
 | |
|         super.init(coder: coder)
 | |
|         setUpViewHierarchy()
 | |
|     }
 | |
|     
 | |
|     private func setUpViewHierarchy() {
 | |
|         // Set up image view
 | |
|         addSubview(imageView)
 | |
|         imageView.pin(.leading, to: .leading, of: self)
 | |
|         imageView.pin(.top, to: .top, of: self)
 | |
|         let imageViewSize = CGFloat(Values.mediumProfilePictureSize)
 | |
|         imageViewWidthConstraint = imageView.set(.width, to: imageViewSize)
 | |
|         imageViewHeightConstraint = imageView.set(.height, to: imageViewSize)
 | |
|         // Set up additional image view
 | |
|         addSubview(additionalImageView)
 | |
|         additionalImageView.pin(.trailing, to: .trailing, of: self)
 | |
|         additionalImageView.pin(.bottom, to: .bottom, of: self)
 | |
|         let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize)
 | |
|         additionalImageViewWidthConstraint = additionalImageView.set(.width, to: additionalImageViewSize)
 | |
|         additionalImageViewHeightConstraint = additionalImageView.set(.height, to: additionalImageViewSize)
 | |
|         additionalImageView.layer.cornerRadius = additionalImageViewSize / 2
 | |
|     }
 | |
|     
 | |
|     // MARK: Updating
 | |
|     @objc(updateForContact:)
 | |
|     public func update(for publicKey: String) { // TODO: Confirm this is still used
 | |
|         GRDBStorage.shared.read { db in update(db, publicKey: publicKey) }
 | |
|     }
 | |
|         
 | |
|     public func update(_ db: Database, publicKey: String) {
 | |
|         openGroupProfilePicture = nil
 | |
|         self.publicKey = publicKey
 | |
|         additionalPublicKey = nil
 | |
|         useFallbackPicture = false
 | |
|         update(db)
 | |
|     }
 | |
| 
 | |
|     public func update(_ db: Database, thread: SessionThread) {
 | |
|         openGroupProfilePicture = nil
 | |
|         
 | |
|         switch thread.variant {
 | |
|             case .contact: update(db, publicKey: thread.id)
 | |
|                 
 | |
|             case .closedGroup:
 | |
|                 let userPublicKey: String = getUserHexEncodedPublicKey(db)
 | |
|                 var randomUsers: [String] = (try? thread.closedGroup
 | |
|                     .fetchOne(db)?
 | |
|                     .members
 | |
|                     .fetchAll(db)
 | |
|                     .map { $0.profileId }
 | |
|                     .filter { $0 != userPublicKey }
 | |
|                     .sorted())   // Sort to provide a level of stability
 | |
|                     .defaulting(to: [])
 | |
|                 
 | |
|                 if randomUsers.count == 1 {
 | |
|                     // Ensure the current user is at the back visually
 | |
|                     randomUsers.insert(userPublicKey, at: 0)
 | |
|                 }
 | |
|                 
 | |
|                 publicKey = (randomUsers.first ?? "")
 | |
|                 additionalPublicKey = (randomUsers.count >= 2 ? randomUsers[1] : "")
 | |
|                 useFallbackPicture = false
 | |
|                 update(db)
 | |
|                 
 | |
|             case .openGroup:
 | |
|                 openGroupProfilePicture = (try? thread.openGroup
 | |
|                     .fetchOne(db)?
 | |
|                     .imageData)
 | |
|                     .map { UIImage(data: $0) }
 | |
|                 publicKey = ""
 | |
|                 useFallbackPicture = (openGroupProfilePicture == nil)
 | |
|                 hasTappableProfilePicture = (openGroupProfilePicture != nil)
 | |
|                 update(db)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc public func update() { // TODO: Confirm this is still used
 | |
|         GRDBStorage.shared.read { db in update(db) }
 | |
|     }
 | |
|     
 | |
|     public func update(_ db: Database) {
 | |
|         AssertIsOnMainThread()
 | |
|         func getProfilePicture(of size: CGFloat, for publicKey: String) -> UIImage? {
 | |
|             guard !publicKey.isEmpty else { return nil }
 | |
|             
 | |
|             if let profilePicture: UIImage = ProfileManager.profileAvatar(db, id: publicKey) {
 | |
|                 hasTappableProfilePicture = true
 | |
|                 return profilePicture
 | |
|             }
 | |
|             
 | |
|             hasTappableProfilePicture = false
 | |
|             // TODO: Pass in context?
 | |
|             let displayName: String = Profile.displayName(db, id: publicKey)
 | |
|             return Identicon.generatePlaceholderIcon(seed: publicKey, text: displayName, size: size)
 | |
|         }
 | |
|         
 | |
|         let size: CGFloat
 | |
|         if let additionalPublicKey = additionalPublicKey, !useFallbackPicture, openGroupProfilePicture == nil {
 | |
|             if self.size == 40 {
 | |
|                 size = 32
 | |
|             } else if self.size == Values.largeProfilePictureSize {
 | |
|                 size = 56
 | |
|             } else {
 | |
|                 size = Values.smallProfilePictureSize
 | |
|             }
 | |
|             
 | |
|             imageViewWidthConstraint.constant = size
 | |
|             imageViewHeightConstraint.constant = size
 | |
|             additionalImageViewWidthConstraint.constant = size
 | |
|             additionalImageViewHeightConstraint.constant = size
 | |
|             additionalImageView.isHidden = false
 | |
|             additionalImageView.image = getProfilePicture(of: size, for: additionalPublicKey)
 | |
|         }
 | |
|         else {
 | |
|             size = self.size
 | |
|             imageViewWidthConstraint.constant = size
 | |
|             imageViewHeightConstraint.constant = size
 | |
|             additionalImageView.isHidden = true
 | |
|             additionalImageView.image = nil
 | |
|         }
 | |
|         
 | |
|         guard publicKey != nil || openGroupProfilePicture != nil else { return }
 | |
|         
 | |
|         imageView.image = useFallbackPicture ? nil : (openGroupProfilePicture ?? getProfilePicture(of: size, for: publicKey))
 | |
|         imageView.backgroundColor = useFallbackPicture ? UIColor(rgbHex: 0x353535) : Colors.unimportant
 | |
|         imageView.layer.cornerRadius = size / 2
 | |
|         additionalImageView.layer.cornerRadius = size / 2
 | |
|         imageView.contentMode = useFallbackPicture ? .center : .scaleAspectFit
 | |
|         
 | |
|         if useFallbackPicture {
 | |
|             switch size {
 | |
|                 case Values.smallProfilePictureSize..<Values.mediumProfilePictureSize: imageView.image = #imageLiteral(resourceName: "SessionWhite16")
 | |
|                 case Values.mediumProfilePictureSize..<Values.largeProfilePictureSize: imageView.image = #imageLiteral(resourceName: "SessionWhite24")
 | |
|                 default: imageView.image = #imageLiteral(resourceName: "SessionWhite40")
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: Convenience
 | |
|     private func getImageView() -> UIImageView {
 | |
|         let result = UIImageView()
 | |
|         result.layer.masksToBounds = true
 | |
|         result.backgroundColor = Colors.unimportant
 | |
|         result.contentMode = .scaleAspectFit
 | |
|         return result
 | |
|     }
 | |
|     
 | |
|     @objc public func getProfilePicture() -> UIImage? {
 | |
|         return hasTappableProfilePicture ? imageView.image : nil
 | |
|     }
 | |
| }
 |