Эх сурвалжийг харах

Merge pull request #1710 from nextcloud/fix/display-avatar

Fix user display
Marino Faggiana 3 жил өмнө
parent
commit
8c71cb2ec5

+ 2 - 0
Nextcloud.xcodeproj/project.pbxproj

@@ -17,6 +17,7 @@
 		370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370D26AE248A3D7A00121797 /* NCCellProtocol.swift */; };
 		371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */; };
 		3781B9B023DB2B7E006B4B1D /* AppDelegate+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3781B9AF23DB2B7E006B4B1D /* AppDelegate+Menu.swift */; };
+		D575039F27146F93008DC9DC /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extensions.swift */; };
 		F700222C1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; };
 		F700222D1EC479840080073F /* Custom.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F700222B1EC479840080073F /* Custom.xcassets */; };
 		F700510122DF63AC003A3356 /* NCShare.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F700510022DF63AC003A3356 /* NCShare.storyboard */; };
@@ -2040,6 +2041,7 @@
 				2C1D5D7523E2DE3300334ABB /* NCDatabase.swift in Sources */,
 				2C1D5D7623E2DE3300334ABB /* NCManageDatabase.swift in Sources */,
 				2C33C48223E2C475005F963B /* NotificationService.swift in Sources */,
+				D575039F27146F93008DC9DC /* String+Extensions.swift in Sources */,
 				F73D5E4A246DE09200DF6467 /* NCElementsJSON.swift in Sources */,
 				F79B646326CA661600838ACA /* UIControl+Extensions.swift in Sources */,
 				F798F0EC2588060A000DAFFD /* UIColor+Extensions.swift in Sources */,

+ 9 - 22
Share/NCShareExtension.swift

@@ -237,36 +237,23 @@ class NCShareExtension: UIViewController, NCListCellDelegate, NCEmptyDataSetDele
             }
             self.setNavigationBar(navigationTitle: navigationTitle)
         }
-        
+
         // PROFILE BUTTON
-                
-        var image: UIImage?
-        
-        if #available(iOS 13.0, *) {
-            let config = UIImage.SymbolConfiguration(pointSize: 30)
-            image = NCUtility.shared.loadImage(named: "person.crop.circle", symbolConfiguration: config)
-        } else {
-            image = NCUtility.shared.loadImage(named: "person.crop.circle", size: 30)
-        }
-        
-        let fileName = String(CCUtility.getUserUrlBase(activeAccount.user, urlBase: activeAccount.urlBase)) + "-" + activeAccount.user + "-original.png"
-        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
-        if let imageUser = UIImage(contentsOfFile: fileNameLocalPath) {
-            image = NCUtility.shared.createAvatar(image: imageUser, size: 30)
-        }
-        
+
+        let image = NCUtility.shared.loadUserImage(for: activeAccount.user, displayName: activeAccount.displayName, urlBase: activeAccount.urlBase)
+
         let profileButton = UIButton(type: .custom)
         profileButton.setImage(image, for: .normal)
-            
+
         if serverUrl == NCUtilityFileSystem.shared.getHomeServer(account: activeAccount.account) {
 
             var title = "  "
-            if activeAccount?.alias == "" {
-                title = title + (activeAccount?.user ?? "")
+            if let userAlias = activeAccount?.alias, !userAlias.isEmpty {
+                title += userAlias
             } else {
-                title = title + (activeAccount?.alias ?? "")
+                title += activeAccount?.displayName ?? ""
             }
-                
+
             profileButton.setTitle(title, for: .normal)
             profileButton.setTitleColor(.systemBlue, for: .normal)
         }

+ 48 - 1
iOSClient/Brand/NCBrand.swift

@@ -107,6 +107,7 @@ class NCBrandColor: NSObject {
     @objc static let shared: NCBrandColor = {
         let instance = NCBrandColor()
         instance.createImagesThemingColor()
+        instance.createUserColors()
         return instance
     }()
     
@@ -151,6 +152,8 @@ class NCBrandColor: NSObject {
     @objc public let gray:                  UIColor = UIColor(red: 104.0/255.0, green: 104.0/255.0, blue: 104.0/255.0, alpha: 1.0)
     @objc public let lightGray:             UIColor = UIColor(red: 229.0/255.0, green: 229.0/229.0, blue: 104.0/255.0, alpha: 1.0)
     @objc public let yellowFavorite:        UIColor = UIColor(red: 248.0/255.0, green: 205.0/255.0, blue: 70.0/255.0, alpha: 1.0)
+    
+    public var userColors: [CGColor] = []
 
     @objc public var systemBackground: UIColor {
         get {
@@ -307,7 +310,11 @@ class NCBrandColor: NSObject {
         self.brandElement = self.customer
         self.brandText = self.customerText        
     }
-    
+
+    private func createUserColors() {
+        self.userColors = generateColors()
+    }
+
     public func createImagesThemingColor() {
         
         let gray: UIColor = UIColor(red: 162.0/255.0, green: 162.0/255.0, blue: 162.0/255.0, alpha: 0.5)
@@ -427,4 +434,44 @@ class NCBrandColor: NSObject {
             NCBrandColor.shared.brandElement = NCBrandColor.shared.brand
         }
     }
+
+    private func stepCalc(steps: Int, color1: CGColor, color2: CGColor) -> [CGFloat] {
+        var step = [CGFloat](repeating: 0, count: 3)
+        step[0] = (color2.components![0] - color1.components![0]) / CGFloat(steps)
+        step[1] = (color2.components![1] - color1.components![1]) / CGFloat(steps)
+        step[2] = (color2.components![2] - color1.components![2]) / CGFloat(steps)
+        return step
+    }
+
+    private func mixPalette(steps: Int, color1: CGColor, color2: CGColor) -> [CGColor] {
+        var palette = [color1]
+        let step = stepCalc(steps: steps, color1: color1, color2: color2)
+        
+        let c1Components = color1.components!
+        for i in 1 ..< steps {
+            let r = c1Components[0] + step[0] * CGFloat(i)
+            let g = c1Components[1] + step[1] * CGFloat(i)
+            let b = c1Components[2] + step[2] * CGFloat(i)
+            
+            palette.append(UIColor(red: r, green: g, blue: b, alpha: 1).cgColor)
+        }
+        return palette
+    }
+
+    /**
+     Generate colors from the official nextcloud color.
+     You can provide how many colors you want (multiplied by 3).
+     if `step` = 6,
+     3 colors \* 6 will result in 18 generated colors
+     */
+    func generateColors(steps: Int = 6) -> [CGColor] {
+        let red = UIColor(red: 182/255, green: 70/255, blue: 157/255, alpha: 1).cgColor
+        let yellow = UIColor(red: 221/255, green: 203/255, blue: 85/255, alpha: 1).cgColor
+        let blue = UIColor(red: 0/255, green: 130/255, blue: 201/255, alpha: 1).cgColor
+
+        let palette1 = mixPalette(steps: steps, color1: red, color2: yellow)
+        let palette2 = mixPalette(steps: steps, color1: yellow, color2: blue)
+        let palette3 = mixPalette(steps: steps, color1: blue, color2: red)
+        return palette1 + palette2 + palette3
+    }
 }

+ 28 - 0
iOSClient/Extensions/String+Extensions.swift

@@ -23,8 +23,17 @@
 
 import Foundation
 import UIKit
+import CommonCrypto
 
 extension String {
+    public var uppercaseInitials: String? {
+        let initials = self.components(separatedBy: .whitespaces)
+            .reduce("", {
+                guard $0.count < 2, let nextLetter = $1.first else { return $0 }
+                return $0 + nextLetter.uppercased()
+            })
+        return initials.isEmpty ? nil : initials
+    }
     
     func formatSecondsToString(_ seconds: TimeInterval) -> String {
         if seconds.isNaN {
@@ -36,4 +45,23 @@ extension String {
         return String(format: "%02d:%02d:%02d", hour, min, sec)
     }
     
+    func md5() -> String {
+        //https://stackoverflow.com/a/32166735/9506784
+
+        let length = Int(CC_MD5_DIGEST_LENGTH)
+        let messageData = self.data(using:.utf8)!
+        var digestData = Data(count: length)
+        
+        _ = digestData.withUnsafeMutableBytes { digestBytes -> UInt8 in
+            messageData.withUnsafeBytes { messageBytes -> UInt8 in
+                if let messageBytesBaseAddress = messageBytes.baseAddress, let digestBytesBlindMemory = digestBytes.bindMemory(to: UInt8.self).baseAddress {
+                    let messageLength = CC_LONG(messageData.count)
+                    CC_MD5(messageBytesBaseAddress, messageLength, digestBytesBlindMemory)
+                }
+                return 0
+            }
+        }
+        
+        return digestData.map { String(format: "%02hhx", $0) }.joined()
+    }
 }

+ 3 - 10
iOSClient/Main/Account Request/NCAccountRequest.swift

@@ -235,16 +235,9 @@ extension NCAccountRequest: UITableViewDataSource {
         } else {
         
             let account = accounts[indexPath.row]
-            
-            let fileName = String(CCUtility.getUserUrlBase(account.user, urlBase: account.urlBase)) + "-" + account.user + ".png"
-            let fileNamePath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
-            
-            if let image = UIImage(contentsOfFile: fileNamePath) {
-                avatarImage?.image = image
-            } else {
-                avatarImage?.image = NCUtility.shared.loadImage(named: "person.crop.circle")
-            }
-                    
+
+            avatarImage?.image = NCUtility.shared.loadUserImage(for: account.user, displayName: account.displayName, urlBase: account.urlBase)
+
             if account.alias != "" {
                 userLabel?.text = account.alias.uppercased()
             } else {

+ 10 - 29
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -591,40 +591,23 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
             // PROFILE BUTTON
             
             if layoutKey == NCGlobal.shared.layoutViewFiles {
-            
-                var image: UIImage?
-                
-                if #available(iOS 13.0, *) {
-                    let config = UIImage.SymbolConfiguration(pointSize: 30)
-                    image = NCUtility.shared.loadImage(named: "person.crop.circle", symbolConfiguration: config)
-                } else {
-                    image = NCUtility.shared.loadImage(named: "person.crop.circle", size: 30)
-                }
-                
-                let fileName = String(CCUtility.getUserUrlBase(appDelegate.user, urlBase: appDelegate.urlBase)) + "-" + self.appDelegate.user + "-original.png"
-                let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
-                if let imageUser = UIImage(contentsOfFile: fileNameLocalPath) {
-                    image = NCUtility.shared.createAvatar(image: imageUser, size: 30)
-                }
-                
+                let activeAccount = NCManageDatabase.shared.getActiveAccount()
+
+                let image = NCUtility.shared.loadUserImage(for: appDelegate.user, displayName: activeAccount?.displayName, urlBase: appDelegate.urlBase)
+
                 let button = UIButton(type: .custom)
                 button.setImage(image, for: .normal)
                 
                 if serverUrl == NCUtilityFileSystem.shared.getHomeServer(account: appDelegate.account) {
                  
-                    let activeAccount = NCManageDatabase.shared.getActiveAccount()
                     var titleButton = "  "
-                    
-                    if activeAccount?.alias == "" {
-                        titleButton = titleButton + (activeAccount?.user ?? "")
-                    } else {
-                        titleButton = titleButton + (activeAccount?.alias ?? "")
-                    }
-                    
+
                     if getNavigationTitle() == activeAccount?.alias {
                         titleButton = ""
+                    } else {
+                        titleButton += activeAccount?.displayName ?? ""
                     }
-                    
+
                     button.setTitle(titleButton, for: .normal)
                     button.setTitleColor(.systemBlue, for: .normal)
                 }
@@ -662,13 +645,11 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     }
     
     func getNavigationTitle() -> String {
-        
         let activeAccount = NCManageDatabase.shared.getActiveAccount()
-        if activeAccount?.alias == "" {
+        guard let userAlias = activeAccount?.alias, !userAlias.isEmpty else {
             return NCBrandOptions.shared.brand
-        } else {
-            return activeAccount?.alias ?? NCBrandOptions.shared.brand
         }
+        return userAlias
     }
     
     // MARK: - BackgroundImageColor Delegate

+ 3 - 7
iOSClient/Menu/NCLoginWeb+Menu.swift

@@ -37,13 +37,9 @@ extension NCLoginWeb {
         for account in accounts {
             
             let title = account.user + " " + (URL(string: account.urlBase)?.host ?? "")
-            let fileName = String(CCUtility.getUserUrlBase(account.user, urlBase: account.urlBase)) + "-" + account.user + ".png"
-            let fileNamePath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
 
-            if let image = UIImage(contentsOfFile: fileNamePath) {
-                avatar = image
-            }
-            
+            avatar = NCUtility.shared.loadUserImage(for: account.user, displayName: account.displayName, urlBase: account.urlBase)
+
             actions.append(
                 NCMenuAction(
                     title: title,
@@ -64,7 +60,7 @@ extension NCLoginWeb {
                 )
             )
         }
-        
+
         actions.append(
             NCMenuAction(
                 title: NSLocalizedString("_delete_active_account_", comment: ""),

+ 3 - 9
iOSClient/More/NCMore.swift

@@ -322,15 +322,9 @@ class NCMore: UIViewController, UITableViewDelegate, UITableViewDataSource {
             cell.icon.image = nil
             cell.status.text = ""
             cell.displayName.text = ""
-            
-            let fileName = String(CCUtility.getUserUrlBase(appDelegate.user, urlBase: appDelegate.urlBase)) + "-" + self.appDelegate.user + "-original.png"
-            let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
-            if let image = UIImage.init(contentsOfFile: fileNameLocalPath) {
-                cell.avatar?.image = NCUtility.shared.createAvatar(image: image, size: 50)
-            } else {
-                cell.avatar?.image = UIImage.init(named: "avatar")?.imageColor(NCBrandColor.shared.gray)
-            }
-           
+
+            cell.avatar.image = NCUtility.shared.loadUserImage(for: appDelegate.user, displayName: tabAccount?.displayName, urlBase: appDelegate.urlBase)
+
             if let account = tabAccount {
                 if account.alias == "" {
                     cell.displayName?.text = account.displayName

+ 32 - 1
iOSClient/NCGlobal.swift

@@ -29,6 +29,38 @@ class NCGlobal: NSObject {
         return instance
     }()
 
+    func usernameToColor(_ username: String) -> CGColor {
+        // Normalize hash
+        let lowerUsername = username.lowercased()
+        var hash: String
+
+        let regex = try! NSRegularExpression(pattern: "^([0-9a-f]{4}-?){8}$")
+        let matches = regex.matches(
+            in: username,
+            range: NSRange(username.startIndex..., in: username))
+
+        if (!matches.isEmpty) {
+            // Already a md5 hash?
+            // done, use as is.
+            hash = lowerUsername
+        } else {
+            hash = lowerUsername.md5()
+        }
+
+        hash = hash.replacingOccurrences(of: "[^0-9a-f]", with: "", options: .regularExpression)
+
+        // userColors has 18 colors by default
+        let userColorIx = NCGlobal.hashToInt(hash: hash, maximum: 18)
+        return NCBrandColor.shared.userColors[userColorIx]
+    }
+
+    // Convert a string to an integer evenly
+    // hash is hex string
+    static func hashToInt(hash: String, maximum: Int) -> Int {
+        let result = hash.compactMap(\.hexDigitValue)
+        return result.reduce(0, { $0 + $1 }) % maximum
+    }
+
     // Struct for Progress
     //
     struct progressType {
@@ -355,4 +387,3 @@ if let popoverController = alertController.popoverPresentationController {
 
 @discardableResult
 */
-

+ 3 - 3
iOSClient/Networking/NCService.swift

@@ -110,9 +110,9 @@ class NCService: NSObject {
                     let fileName = String(CCUtility.getUserUrlBase(user, urlBase: url)) + "-" + self.appDelegate.user + ".png"
                     let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
                     let etag = NCManageDatabase.shared.getTableAvatar(fileName: fileName)?.etag
-                    
-                    NCCommunication.shared.downloadAvatar(user: user, fileNameLocalPath: fileNameLocalPath, sizeImage: NCGlobal.shared.avatarSize, avatarSizeRounded: NCGlobal.shared.avatarSizeRounded, etag: etag) { (account, image, imageOriginal, etag, errorCode, errorMessage) in
-                        
+
+                    NCCommunication.shared.downloadAvatar(user: tableAccount.userId, fileNameLocalPath: fileNameLocalPath, sizeImage: NCGlobal.shared.avatarSize, avatarSizeRounded: NCGlobal.shared.avatarSizeRounded, etag: etag) { (account, image, imageOriginal, etag, errorCode, errorMessage) in
+
                         if let etag = etag, errorCode == 0, let imageOriginal = imageOriginal {
                             
                             do {

+ 53 - 1
iOSClient/Utility/NCUtility.swift

@@ -384,7 +384,9 @@ class NCUtility: NSObject {
                 
         return(onlineStatus, statusMessage, descriptionMessage)
     }
-    
+
+    // MARK: -
+
     func imageFromVideo(url: URL, at time: TimeInterval) -> UIImage? {
         
         let asset = AVURLAsset(url: url)
@@ -496,6 +498,30 @@ class NCUtility: NSObject {
         return  UIImage(named: "file")!.image(color: color, size: size)
     }
     
+    func loadUserImage(for user: String, displayName: String?, urlBase: String) -> UIImage {
+        var image: UIImage?
+        
+        let fileName = String(CCUtility.getUserUrlBase(user, urlBase: urlBase)) + "-" + user + "-original.png"
+        let fileNameLocalPath = String(CCUtility.getDirectoryUserData()) + "/" + fileName
+        if let imageUser = UIImage(contentsOfFile: fileNameLocalPath) {
+            image = NCUtility.shared.createAvatar(image: imageUser, size: 30)
+        } else if let displayName = displayName, !displayName.isEmpty {
+            image = createAvatar(displayName: displayName, size: 30)
+        } else {
+            // fallback to default icon
+        }
+        return image ?? getDefaultUserIcon()
+    }
+    
+    func getDefaultUserIcon() -> UIImage {
+        if #available(iOS 13.0, *) {
+            let config = UIImage.SymbolConfiguration(pointSize: 30)
+            return NCUtility.shared.loadImage(named: "person.crop.circle", symbolConfiguration: config)
+        } else {
+            return NCUtility.shared.loadImage(named: "person.crop.circle", size: 30)
+        }
+    }
+    
     @objc func createAvatar(image: UIImage, size: CGFloat) -> UIImage {
         
         var avatarImage = image
@@ -510,6 +536,32 @@ class NCUtility: NSObject {
         return avatarImage
     }
     
+    func createAvatar(displayName: String, size: CGFloat) -> UIImage? {
+        guard let initials = displayName.uppercaseInitials else {
+            return nil
+        }
+        let userColor = NCGlobal.shared.usernameToColor(displayName)
+        let rect = CGRect(x: 0, y: 0, width: size, height: size)
+        var avatarImage: UIImage?
+        
+        UIGraphicsBeginImageContextWithOptions(rect.size, false, 3.0)
+        let context = UIGraphicsGetCurrentContext()
+        UIBezierPath.init(roundedRect: rect, cornerRadius: rect.size.height).addClip()
+        context?.setFillColor(userColor)
+        context?.fill(rect)
+        let textStyle = NSMutableParagraphStyle()
+        textStyle.alignment = NSTextAlignment.center
+        let lineHeight = UIFont.systemFont(ofSize: UIFont.systemFontSize).pointSize
+        NSString(string: initials)
+            .draw(
+                in: CGRect(x: 0, y: (size - lineHeight) / 2, width: size, height: lineHeight),
+                withAttributes: [NSAttributedString.Key.paragraphStyle: textStyle])
+        avatarImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        return avatarImage
+    }
+
     // MARK: -
 
     @objc func startActivityIndicator(backgroundView: UIView?, blurEffect: Bool, bottom: CGFloat = 0, style: UIActivityIndicatorView.Style = .whiteLarge) {