Updated the profile picture modal
Moved the ProfilePictureView into SessionUIKit Fixed a couple of minor ProfilePictureView bugs
This commit is contained in:
parent
2d792e4e3e
commit
cf2e198a64
|
@ -381,9 +381,6 @@
|
|||
C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF240255B6D67007E1867 /* UIView+OWS.swift */; };
|
||||
C38EF24E255B6D67007E1867 /* Collection+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF241255B6D67007E1867 /* Collection+OWS.swift */; };
|
||||
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */; };
|
||||
C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; };
|
||||
C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; };
|
||||
C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; };
|
||||
C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; };
|
||||
C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; };
|
||||
|
@ -547,6 +544,9 @@
|
|||
FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; };
|
||||
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */; };
|
||||
FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5EB282B8F17000CE219 /* AttachmentError.swift */; };
|
||||
FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; };
|
||||
FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; };
|
||||
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */; };
|
||||
FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; };
|
||||
FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */; };
|
||||
FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; };
|
||||
|
@ -1489,9 +1489,8 @@
|
|||
C38EF240255B6D67007E1867 /* UIView+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIView+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIView+OWS.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF241255B6D67007E1867 /* Collection+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Collection+OWS.swift"; path = "SignalUtilitiesKit/Utilities/Collection+OWS.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF281255B6D84007E1867 /* OWSAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSAudioSession.swift; path = SessionMessagingKit/Utilities/OWSAudioSession.swift; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Identicon+ObjC.swift"; path = "SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderIcon.swift; path = "SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = "SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlaceholderIcon.swift; path = SessionUIKit/Components/PlaceholderIcon.swift; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfilePictureView.swift; path = SessionUIKit/Components/ProfilePictureView.swift; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIViewController+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIViewController+Utilities.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScreenLock.swift; path = "SignalUtilitiesKit/Screen Lock/ScreenLock.swift"; sourceTree = SOURCE_ROOT; };
|
||||
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; };
|
||||
|
@ -1673,6 +1672,7 @@
|
|||
FD09C5E728264937000CE219 /* MediaDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaDetailViewController.swift; sourceTree = "<group>"; };
|
||||
FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadTypingIndicator.swift; sourceTree = "<group>"; };
|
||||
FD09C5EB282B8F17000CE219 /* AttachmentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentError.swift; sourceTree = "<group>"; };
|
||||
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilePictureView+Convenience.swift"; sourceTree = "<group>"; };
|
||||
FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
|
||||
FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
|
||||
FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacy.swift; sourceTree = "<group>"; };
|
||||
|
@ -2857,6 +2857,8 @@
|
|||
C38EF3EE255B6DF6007E1867 /* GradientView.swift */,
|
||||
B86BD08323399ACF000F5AE3 /* Modal.swift */,
|
||||
FD52090628B49738006098F6 /* ConfirmationModal.swift */,
|
||||
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */,
|
||||
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */,
|
||||
FD71165A28E6DDBC00B47552 /* StyledNavigationController.swift */,
|
||||
);
|
||||
path = Components;
|
||||
|
@ -2867,7 +2869,7 @@
|
|||
children = (
|
||||
C33FD9B7255A54A300E217F9 /* Meta */,
|
||||
C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */,
|
||||
C36096EF25AD2268008B62B2 /* Profile Pictures */,
|
||||
FD16AB5D2A1DD8E70083D849 /* Profile Pictures */,
|
||||
C36096EE25AD21BC008B62B2 /* Screen Lock */,
|
||||
C3851CD225624B060061EEB0 /* Shared Views */,
|
||||
C360970125AD22D3008B62B2 /* Shared View Controllers */,
|
||||
|
@ -3046,16 +3048,6 @@
|
|||
path = "Screen Lock";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C36096EF25AD2268008B62B2 /* Profile Pictures */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */,
|
||||
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */,
|
||||
C38EF2A2255B6D93007E1867 /* Identicon+ObjC.swift */,
|
||||
);
|
||||
path = "Profile Pictures";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C360970125AD22D3008B62B2 /* Shared View Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3174,6 +3166,7 @@
|
|||
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
|
||||
FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */,
|
||||
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */,
|
||||
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */,
|
||||
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
|
||||
C3ECBF7A257056B700EA7FCE /* Threading.swift */,
|
||||
);
|
||||
|
@ -3558,6 +3551,13 @@
|
|||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD16AB5D2A1DD8E70083D849 /* Profile Pictures */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = "Profile Pictures";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD17D79427F3E03300122BE0 /* Migrations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -5171,6 +5171,7 @@
|
|||
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */,
|
||||
FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */,
|
||||
C331FFE02558FB0000070591 /* SearchBar.swift in Sources */,
|
||||
FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */,
|
||||
FD71162C28E1451400B47552 /* Position.swift in Sources */,
|
||||
FD52090328B4680F006098F6 /* RadioButton.swift in Sources */,
|
||||
C331FFE82558FB0000070591 /* TextView.swift in Sources */,
|
||||
|
@ -5181,6 +5182,7 @@
|
|||
FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */,
|
||||
C331FF9A2558FA6B00070591 /* Values.swift in Sources */,
|
||||
FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */,
|
||||
FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */,
|
||||
C331FFE42558FB0000070591 /* SessionButton.swift in Sources */,
|
||||
C331FFE92558FB0000070591 /* Separator.swift in Sources */,
|
||||
FD71163228E2C42A00B47552 /* IconSize.swift in Sources */,
|
||||
|
@ -5199,7 +5201,6 @@
|
|||
C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */,
|
||||
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */,
|
||||
C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */,
|
||||
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */,
|
||||
C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */,
|
||||
C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */,
|
||||
FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */,
|
||||
|
@ -5220,12 +5221,10 @@
|
|||
C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */,
|
||||
C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */,
|
||||
C38EF388255B6DD2007E1867 /* AttachmentApprovalViewController.swift in Sources */,
|
||||
C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */,
|
||||
C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */,
|
||||
C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */,
|
||||
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */,
|
||||
C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */,
|
||||
C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */,
|
||||
C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */,
|
||||
C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */,
|
||||
C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */,
|
||||
|
@ -5558,6 +5557,7 @@
|
|||
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
|
||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */,
|
||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */,
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
|
||||
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
|
||||
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
|
||||
|
|
|
@ -5,6 +5,7 @@ import CallKit
|
|||
import GRDB
|
||||
import WebRTC
|
||||
import PromiseKit
|
||||
import SessionUIKit
|
||||
import SignalUtilitiesKit
|
||||
import SessionMessagingKit
|
||||
|
||||
|
@ -154,7 +155,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
|
|||
self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact)
|
||||
self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId)
|
||||
.map { UIImage(data: $0) }
|
||||
.defaulting(to: Identicon.generatePlaceholderIcon(seed: sessionId, text: self.contactName, size: 300))
|
||||
.defaulting(to: PlaceholderIcon.generate(seed: sessionId, text: self.contactName, size: 300))
|
||||
|
||||
WebRTCSession.current = self.webRTCSession
|
||||
self.webRTCSession.delegate = self
|
||||
|
|
|
@ -32,7 +32,7 @@ import SignalUtilitiesKit
|
|||
|
||||
let srcImage: UIImage
|
||||
|
||||
let successCompletion: ((UIImage) -> Void)
|
||||
let successCompletion: ((Data) -> Void)
|
||||
|
||||
var imageView: UIView!
|
||||
|
||||
|
@ -79,7 +79,7 @@ import SignalUtilitiesKit
|
|||
notImplemented()
|
||||
}
|
||||
|
||||
@objc required init(srcImage: UIImage, successCompletion : @escaping (UIImage) -> Void) {
|
||||
@objc required init(srcImage: UIImage, successCompletion : @escaping (Data) -> Void) {
|
||||
// normalized() can be slightly expensive but in practice this is fine.
|
||||
self.srcImage = srcImage.normalized()
|
||||
self.successCompletion = successCompletion
|
||||
|
@ -487,10 +487,9 @@ import SignalUtilitiesKit
|
|||
@objc func donePressed(sender: UIButton) {
|
||||
let successCompletion = self.successCompletion
|
||||
dismiss(animated: true, completion: {
|
||||
guard let dstImage = self.generateDstImage() else {
|
||||
return
|
||||
}
|
||||
successCompletion(dstImage)
|
||||
guard let dstImageData: Data = self.generateDstImageData() else { return }
|
||||
|
||||
successCompletion(dstImageData)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -517,4 +516,8 @@ import SignalUtilitiesKit
|
|||
UIGraphicsEndImageContext()
|
||||
return scaledImage
|
||||
}
|
||||
|
||||
func generateDstImageData() -> Data? {
|
||||
return generateDstImage().map { $0.pngData() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Êtes-vous sûr de vouloir supprimer votre conversation avec %@ ?";
|
||||
"delete_conversation_confirmation_alert_title" = "Supprimer conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -624,5 +624,5 @@
|
|||
"delete_conversation_confirmation_alert_message" = "Are you sure you want to delete your conversation with %@?";
|
||||
"delete_conversation_confirmation_alert_title" = "Delete Conversation";
|
||||
"update_profile_modal_title" = "Set Display Picture";
|
||||
"update_profile_modal_upload" = "Upload";
|
||||
"update_profile_modal_save" = "Save";
|
||||
"update_profile_modal_remove" = "Remove";
|
||||
|
|
|
@ -52,7 +52,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
private var editedDisplayName: String?
|
||||
private var editProfilePictureModal: ConfirmationModal?
|
||||
private var editProfilePictureModalInfo: ConfirmationModal.Info?
|
||||
private var editedProfilePicture: UIImage?
|
||||
private var editedProfilePictureData: Data?
|
||||
private var editedProfilePictureFileName: String?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
@ -166,7 +166,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
self?.oldDisplayName = updatedNickname
|
||||
self?.updateProfile(
|
||||
name: updatedNickname,
|
||||
profilePicture: nil,
|
||||
profilePictureData: nil,
|
||||
profilePictureFilePath: ProfileManager.profileAvatarFilepath(id: userSessionId),
|
||||
isUpdatingDisplayName: true,
|
||||
isUpdatingProfilePicture: false
|
||||
|
@ -396,27 +396,27 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
|
||||
private func updateProfilePicture() {
|
||||
let existingDisplayName: String = self.oldDisplayName
|
||||
let existingImage: UIImage? = ProfileManager
|
||||
let existingImageData: Data? = ProfileManager
|
||||
.profileAvatar(id: self.userSessionId)
|
||||
.map { UIImage(data: $0) }
|
||||
let editProfilePictureModalInfo: ConfirmationModal.Info = ConfirmationModal.Info(
|
||||
title: "update_profile_modal_title".localized(),
|
||||
body: .image(
|
||||
placeholder: UIImage(named: "profile_placeholder"),
|
||||
value: existingImage,
|
||||
placeholderData: UIImage(named: "profile_placeholder")?.pngData(),
|
||||
valueData: existingImageData,
|
||||
icon: .rightPlus,
|
||||
style: .circular,
|
||||
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
|
||||
),
|
||||
confirmTitle: "update_profile_modal_upload".localized(),
|
||||
confirmTitle: "update_profile_modal_save".localized(),
|
||||
confirmEnabled: false,
|
||||
cancelTitle: "update_profile_modal_remove".localized(),
|
||||
cancelEnabled: (existingImage != nil),
|
||||
cancelEnabled: (existingImageData != nil),
|
||||
hasCloseButton: true,
|
||||
dismissOnConfirm: false,
|
||||
onConfirm: { [weak self] modal in
|
||||
self?.updateProfile(
|
||||
name: existingDisplayName,
|
||||
profilePicture: self?.editedProfilePicture,
|
||||
profilePictureData: self?.editedProfilePictureData,
|
||||
profilePictureFilePath: self?.editedProfilePictureFileName,
|
||||
isUpdatingDisplayName: false,
|
||||
isUpdatingProfilePicture: true,
|
||||
|
@ -426,7 +426,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
onCancel: { [weak self] modal in
|
||||
self?.updateProfile(
|
||||
name: existingDisplayName,
|
||||
profilePicture: nil,
|
||||
profilePictureData: nil,
|
||||
profilePictureFilePath: nil,
|
||||
isUpdatingDisplayName: false,
|
||||
isUpdatingProfilePicture: true,
|
||||
|
@ -434,7 +434,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
)
|
||||
},
|
||||
afterClosed: { [weak self] in
|
||||
self?.editedProfilePicture = nil
|
||||
self?.editedProfilePictureData = nil
|
||||
self?.editedProfilePictureFileName = nil
|
||||
self?.editProfilePictureModal = nil
|
||||
self?.editProfilePictureModalInfo = nil
|
||||
|
@ -447,18 +447,19 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
self.transitionToScreen(modal, transitionType: .present)
|
||||
}
|
||||
|
||||
fileprivate func updatedProfilePictureSelected(image: UIImage?, filePath: String?) {
|
||||
fileprivate func updatedProfilePictureSelected(imageData: Data?, filePath: String?) {
|
||||
guard let info: ConfirmationModal.Info = self.editProfilePictureModalInfo else { return }
|
||||
|
||||
self.editedProfilePicture = image
|
||||
self.editedProfilePictureData = imageData
|
||||
self.editedProfilePictureFileName = filePath
|
||||
|
||||
if let image: UIImage = image {
|
||||
if let imageData: Data = imageData {
|
||||
self.editProfilePictureModal?.updateContent(
|
||||
with: info.with(
|
||||
body: .image(
|
||||
placeholder: UIImage(named: "profile_placeholder"),
|
||||
value: image,
|
||||
placeholderData: UIImage(named: "profile_placeholder")?.pngData(),
|
||||
valueData: imageData,
|
||||
icon: .rightPlus,
|
||||
style: .circular,
|
||||
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
|
||||
),
|
||||
|
@ -470,8 +471,9 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
self.editProfilePictureModal?.updateContent(
|
||||
with: info.with(
|
||||
body: .image(
|
||||
placeholder: UIImage(named: "profile_placeholder"),
|
||||
value: UIImage(contentsOfFile: filePath),
|
||||
placeholderData: UIImage(named: "profile_placeholder")?.pngData(),
|
||||
valueData: FileManager.default.contents(atPath: filePath),
|
||||
icon: .rightPlus,
|
||||
style: .circular,
|
||||
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
|
||||
),
|
||||
|
@ -496,7 +498,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
|
||||
private func updateProfile(
|
||||
name: String,
|
||||
profilePicture: UIImage?,
|
||||
profilePictureData: Data?,
|
||||
profilePictureFilePath: String?,
|
||||
isUpdatingDisplayName: Bool,
|
||||
isUpdatingProfilePicture: Bool,
|
||||
|
@ -506,7 +508,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
|
|||
ProfileManager.updateLocal(
|
||||
queue: DispatchQueue.global(qos: .default),
|
||||
profileName: name,
|
||||
image: profilePicture,
|
||||
image: profilePictureData.map { UIImage(data: $0) },
|
||||
imageFilePath: profilePictureFilePath,
|
||||
success: { db, updatedProfile in
|
||||
if isUpdatingDisplayName {
|
||||
|
@ -642,9 +644,9 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
|
|||
else {
|
||||
let viewController: CropScaleImageViewController = CropScaleImageViewController(
|
||||
srcImage: rawAvatar,
|
||||
successCompletion: { resultImage in
|
||||
successCompletion: { resultImageData in
|
||||
self?.viewModel.updatedProfilePictureSelected(
|
||||
image: resultImage,
|
||||
imageData: resultImageData,
|
||||
filePath: nil
|
||||
)
|
||||
}
|
||||
|
@ -654,7 +656,7 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
|
|||
}
|
||||
|
||||
self?.viewModel.updatedProfilePictureSelected(
|
||||
image: nil,
|
||||
imageData: nil,
|
||||
filePath: imageUrl.path
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUIKit
|
||||
|
||||
public extension ProfilePictureView {
|
||||
// FIXME: Remove this in the UserConfig branch
|
||||
func update(
|
||||
publicKey: String = "",
|
||||
profile: Profile? = nil,
|
||||
icon: ProfileIcon = .none,
|
||||
additionalProfile: Profile? = nil,
|
||||
additionalIcon: ProfileIcon = .none,
|
||||
threadVariant: SessionThread.Variant,
|
||||
openGroupProfilePictureData: Data? = nil,
|
||||
useFallbackPicture: Bool = false,
|
||||
showMultiAvatarForClosedGroup: Bool = false
|
||||
) {
|
||||
guard !useFallbackPicture else {
|
||||
let placeholderImage: UIImage = {
|
||||
switch self.size {
|
||||
case .navigation, .message: return #imageLiteral(resourceName: "SessionWhite16")
|
||||
case .list: return #imageLiteral(resourceName: "SessionWhite24")
|
||||
case .hero: return #imageLiteral(resourceName: "SessionWhite40")
|
||||
}
|
||||
}()
|
||||
|
||||
return update(
|
||||
Info(
|
||||
imageData: placeholderImage.pngData(),
|
||||
inset: UIEdgeInsets(
|
||||
top: 12,
|
||||
left: 12,
|
||||
bottom: 12,
|
||||
right: 12
|
||||
),
|
||||
forcedBackgroundColor: .theme(.classicDark, color: .borderSeparator)
|
||||
)
|
||||
)
|
||||
}
|
||||
guard openGroupProfilePictureData == nil else {
|
||||
return update(Info(imageData: openGroupProfilePictureData))
|
||||
}
|
||||
|
||||
switch (threadVariant, showMultiAvatarForClosedGroup) {
|
||||
case (.closedGroup, true):
|
||||
update(
|
||||
Info(
|
||||
imageData: (
|
||||
profile.map { ProfileManager.profileAvatar(profile: $0) } ??
|
||||
PlaceholderIcon.generate(
|
||||
seed: publicKey,
|
||||
text: (profile?.displayName(for: threadVariant))
|
||||
.defaulting(to: publicKey),
|
||||
size: (additionalProfile != nil ?
|
||||
self.size.multiImageSize :
|
||||
self.size.viewSize
|
||||
)
|
||||
).pngData()
|
||||
)
|
||||
),
|
||||
additionalInfo: additionalProfile
|
||||
.map { otherProfile in
|
||||
Info(
|
||||
imageData: (
|
||||
ProfileManager.profileAvatar(profile: otherProfile) ??
|
||||
PlaceholderIcon.generate(
|
||||
seed: otherProfile.id,
|
||||
text: otherProfile.displayName(for: threadVariant),
|
||||
size: self.size.multiImageSize
|
||||
).pngData()
|
||||
)
|
||||
)
|
||||
}
|
||||
.defaulting(
|
||||
to: Info(
|
||||
imageData: UIImage(systemName: "person.fill")?.pngData(),
|
||||
renderingMode: .alwaysTemplate,
|
||||
themeTintColor: .white,
|
||||
inset: UIEdgeInsets(
|
||||
top: 3,
|
||||
left: 0,
|
||||
bottom: -5,
|
||||
right: 0
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
default:
|
||||
update(
|
||||
Info(
|
||||
imageData: (
|
||||
profile.map { ProfileManager.profileAvatar(profile: $0) } ??
|
||||
PlaceholderIcon.generate(
|
||||
seed: publicKey,
|
||||
text: (profile?.displayName(for: threadVariant))
|
||||
.defaulting(to: publicKey),
|
||||
size: (additionalProfile != nil ?
|
||||
self.size.multiImageSize :
|
||||
self.size.viewSize
|
||||
)
|
||||
).pngData()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func update(
|
||||
publicKey: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
customImageData: Data?,
|
||||
profile: Profile?,
|
||||
additionalProfile: Profile?
|
||||
) {
|
||||
// If we are given 'customImageData' then only use that
|
||||
guard customImageData == nil else { return update(Info(imageData: customImageData)) }
|
||||
|
||||
// Otherwise there are conversation-type-specific behaviours
|
||||
switch threadVariant {
|
||||
case .openGroup:
|
||||
let placeholderImage: UIImage = {
|
||||
switch self.size {
|
||||
case .navigation, .message: return #imageLiteral(resourceName: "SessionWhite16")
|
||||
case .list: return #imageLiteral(resourceName: "SessionWhite24")
|
||||
case .hero: return #imageLiteral(resourceName: "SessionWhite40")
|
||||
}
|
||||
}()
|
||||
|
||||
update(
|
||||
Info(
|
||||
imageData: placeholderImage.pngData(),
|
||||
inset: UIEdgeInsets(
|
||||
top: 12,
|
||||
left: 12,
|
||||
bottom: 12,
|
||||
right: 12
|
||||
),
|
||||
forcedBackgroundColor: .theme(.classicDark, color: .borderSeparator)
|
||||
)
|
||||
)
|
||||
|
||||
case .closedGroup: //.legacyGroup, .group:
|
||||
guard !publicKey.isEmpty else { return }
|
||||
// TODO: Test that this doesn't call 'PlaceholderIcon.generate' when the original value exists
|
||||
update(
|
||||
Info(
|
||||
imageData: (
|
||||
profile.map { ProfileManager.profileAvatar(profile: $0) } ??
|
||||
PlaceholderIcon.generate(
|
||||
seed: publicKey,
|
||||
text: (profile?.displayName(for: threadVariant))
|
||||
.defaulting(to: publicKey),
|
||||
size: (additionalProfile != nil ?
|
||||
self.size.multiImageSize :
|
||||
self.size.viewSize
|
||||
)
|
||||
).pngData()
|
||||
)
|
||||
),
|
||||
additionalInfo: additionalProfile
|
||||
.map { otherProfile in
|
||||
Info(
|
||||
imageData: (
|
||||
ProfileManager.profileAvatar(profile: otherProfile) ??
|
||||
PlaceholderIcon.generate(
|
||||
seed: otherProfile.id,
|
||||
text: otherProfile.displayName(for: threadVariant),
|
||||
size: self.size.multiImageSize
|
||||
).pngData()
|
||||
)
|
||||
)
|
||||
}
|
||||
.defaulting(
|
||||
to: Info(
|
||||
imageData: UIImage(systemName: "person.fill")?.pngData(),
|
||||
renderingMode: .alwaysTemplate,
|
||||
themeTintColor: .white,
|
||||
inset: UIEdgeInsets(
|
||||
top: 3,
|
||||
left: 0,
|
||||
bottom: -5,
|
||||
right: 0
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
case .contact:
|
||||
guard !publicKey.isEmpty else { return }
|
||||
|
||||
update(
|
||||
Info(
|
||||
imageData: (
|
||||
profile.map { ProfileManager.profileAvatar(profile: $0) } ??
|
||||
PlaceholderIcon.generate(
|
||||
seed: publicKey,
|
||||
text: (profile?.displayName(for: threadVariant))
|
||||
.defaulting(to: publicKey),
|
||||
size: (additionalProfile != nil ?
|
||||
self.size.multiImageSize :
|
||||
self.size.viewSize
|
||||
)
|
||||
).pngData()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import SessionUtilitiesKit
|
|||
|
||||
// FIXME: Refactor as part of the Groups Rebuild
|
||||
public class ConfirmationModal: Modal {
|
||||
private static let imageSize: CGFloat = 80
|
||||
private static let closeSize: CGFloat = 24
|
||||
|
||||
private var internalOnConfirm: ((ConfirmationModal) -> ())? = nil
|
||||
|
@ -37,22 +36,7 @@ public class ConfirmationModal: Modal {
|
|||
return result
|
||||
}()
|
||||
|
||||
private lazy var imageViewContainer: UIView = {
|
||||
let result: UIView = UIView()
|
||||
result.isHidden = true
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var imageView: UIImageView = {
|
||||
let result: UIImageView = UIImageView()
|
||||
result.clipsToBounds = true
|
||||
result.contentMode = .scaleAspectFill
|
||||
result.set(.width, to: ConfirmationModal.imageSize)
|
||||
result.set(.height, to: ConfirmationModal.imageSize)
|
||||
|
||||
return result
|
||||
}()
|
||||
private lazy var profileView: ProfilePictureView = ProfilePictureView(size: .hero)
|
||||
|
||||
private lazy var confirmButton: UIButton = {
|
||||
let result: UIButton = Modal.createButton(
|
||||
|
@ -73,7 +57,7 @@ public class ConfirmationModal: Modal {
|
|||
}()
|
||||
|
||||
private lazy var contentStackView: UIStackView = {
|
||||
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, imageViewContainer ])
|
||||
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, profileView ])
|
||||
result.axis = .vertical
|
||||
result.spacing = Values.smallSpacing
|
||||
result.isLayoutMarginsRelativeArrangement = true
|
||||
|
@ -141,11 +125,6 @@ public class ConfirmationModal: Modal {
|
|||
contentView.addSubview(mainStackView)
|
||||
contentView.addSubview(closeButton)
|
||||
|
||||
imageViewContainer.addSubview(imageView)
|
||||
imageView.center(.horizontal, in: imageViewContainer)
|
||||
imageView.pin(.top, to: .top, of: imageViewContainer, withInset: 15)
|
||||
imageView.pin(.bottom, to: .bottom, of: imageViewContainer, withInset: -15)
|
||||
|
||||
mainStackView.pin(to: contentView)
|
||||
closeButton.pin(.top, to: .top, of: contentView, withInset: 8)
|
||||
closeButton.pin(.right, to: .right, of: contentView, withInset: -8)
|
||||
|
@ -185,14 +164,16 @@ public class ConfirmationModal: Modal {
|
|||
explanationLabel.attributedText = attributedText
|
||||
explanationLabel.isHidden = false
|
||||
|
||||
case .image(let placeholder, let value, let style, let onClick):
|
||||
case .image(let placeholder, let value, let icon, let style, let onClick):
|
||||
mainStackView.spacing = 0
|
||||
imageView.image = (value ?? placeholder)
|
||||
imageView.layer.cornerRadius = (style == .circular ?
|
||||
(ConfirmationModal.imageSize / 2) :
|
||||
0
|
||||
profileView.clipsToBounds = (style == .circular)
|
||||
profileView.update(
|
||||
ProfilePictureView.Info(
|
||||
imageData: (value ?? placeholder),
|
||||
icon: icon
|
||||
)
|
||||
)
|
||||
imageViewContainer.isHidden = false
|
||||
profileView.isHidden = false
|
||||
internalOnBodyTap = onClick
|
||||
}
|
||||
|
||||
|
@ -406,8 +387,9 @@ public extension ConfirmationModal.Info {
|
|||
// case input(placeholder: String, value: String?)
|
||||
// case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)])
|
||||
case image(
|
||||
placeholder: UIImage?,
|
||||
value: UIImage?,
|
||||
placeholderData: Data?,
|
||||
valueData: Data?,
|
||||
icon: ProfilePictureView.ProfileIcon = .none,
|
||||
style: ImageStyle,
|
||||
onClick: (() -> ())
|
||||
)
|
||||
|
@ -432,10 +414,11 @@ public extension ConfirmationModal.Info {
|
|||
// lhsOptions.map { "\($0.0)-\($0.1)" } == rhsValue.map { "\($0.0)-\($0.1)" }
|
||||
// )
|
||||
|
||||
case (.image(let lhsPlaceholder, let lhsValue, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsStyle, _)):
|
||||
case (.image(let lhsPlaceholder, let lhsValue, let lhsIcon, let lhsStyle, _), .image(let rhsPlaceholder, let rhsValue, let rhsIcon, let rhsStyle, _)):
|
||||
return (
|
||||
lhsPlaceholder == rhsPlaceholder &&
|
||||
lhsValue == rhsValue &&
|
||||
lhsIcon == rhsIcon &&
|
||||
lhsStyle == rhsStyle
|
||||
)
|
||||
|
||||
|
@ -449,9 +432,10 @@ public extension ConfirmationModal.Info {
|
|||
case .text(let text): text.hash(into: &hasher)
|
||||
case .attributedText(let text): text.hash(into: &hasher)
|
||||
|
||||
case .image(let placeholder, let value, let style, _):
|
||||
case .image(let placeholder, let value, let icon, let style, _):
|
||||
placeholder.hash(into: &hasher)
|
||||
value.hash(into: &hasher)
|
||||
icon.hash(into: &hasher)
|
||||
style.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,23 @@
|
|||
|
||||
import UIKit
|
||||
import CryptoSwift
|
||||
import SessionUIKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public class PlaceholderIcon {
|
||||
private static let placeholderCache: Atomic<NSCache<NSString, UIImage>> = {
|
||||
let result = NSCache<NSString, UIImage>()
|
||||
result.countLimit = 50
|
||||
|
||||
return Atomic(result)
|
||||
}()
|
||||
|
||||
private let seed: Int
|
||||
|
||||
// Colour palette
|
||||
private var colors: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color }
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
init(seed: Int, colors: [UIColor]? = nil) {
|
||||
self.seed = seed
|
||||
if let colors = colors { self.colors = colors }
|
||||
|
@ -21,7 +30,7 @@ public class PlaceholderIcon {
|
|||
if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { hash = seed.sha512() }
|
||||
|
||||
guard let number = Int(hash.substring(to: 12), radix: 16) else {
|
||||
owsFailDebug("Failed to generate number from seed string: \(seed).")
|
||||
SNLog("Failed to generate number from seed string: \(seed).")
|
||||
self.init(seed: 0, colors: colors)
|
||||
return
|
||||
}
|
||||
|
@ -29,7 +38,53 @@ public class PlaceholderIcon {
|
|||
self.init(seed: number, colors: colors)
|
||||
}
|
||||
|
||||
public func generateLayer(with diameter: CGFloat, text: String) -> CALayer {
|
||||
// MARK: - Convenience
|
||||
|
||||
public static func generate(seed: String, text: String, size: CGFloat) -> UIImage {
|
||||
let icon = PlaceholderIcon(seed: seed)
|
||||
|
||||
var content: String = (text.hasSuffix("\(String(seed.suffix(4))))") ?
|
||||
(text.split(separator: "(")
|
||||
.first
|
||||
.map { String($0) })
|
||||
.defaulting(to: text) :
|
||||
text
|
||||
)
|
||||
|
||||
if content.count > 2 && SessionId.Prefix(from: content) != nil {
|
||||
content.removeFirst(2)
|
||||
}
|
||||
|
||||
let initials: String = content
|
||||
.split(separator: " ")
|
||||
.compactMap { word in word.first.map { String($0) } }
|
||||
.joined()
|
||||
let cacheKey: String = "\(content)-\(Int(floor(size)))"
|
||||
|
||||
if let cachedIcon: UIImage = placeholderCache.wrappedValue.object(forKey: cacheKey as NSString) {
|
||||
return cachedIcon
|
||||
}
|
||||
|
||||
let layer = icon.generateLayer(
|
||||
with: size,
|
||||
text: (initials.count >= 2 ?
|
||||
initials.substring(to: 2).uppercased() :
|
||||
content.substring(to: 2).uppercased()
|
||||
)
|
||||
)
|
||||
|
||||
let rect = CGRect(origin: CGPoint.zero, size: layer.frame.size)
|
||||
let renderer = UIGraphicsImageRenderer(size: rect.size)
|
||||
let result = renderer.image { layer.render(in: $0.cgContext) }
|
||||
|
||||
placeholderCache.mutate { $0.setObject(result, forKey: cacheKey as NSString) }
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
private func generateLayer(with diameter: CGFloat, text: String) -> CALayer {
|
||||
let color: UIColor = self.colors[seed % self.colors.count]
|
||||
let base: CALayer = getTextLayer(with: diameter, color: color, text: text)
|
||||
base.masksToBounds = true
|
|
@ -3,10 +3,36 @@
|
|||
import UIKit
|
||||
import GRDB
|
||||
import YYImage
|
||||
import SessionUIKit
|
||||
import SessionMessagingKit
|
||||
|
||||
public final class ProfilePictureView: UIView {
|
||||
public struct Info {
|
||||
let imageData: Data?
|
||||
let renderingMode: UIImage.RenderingMode
|
||||
let themeTintColor: ThemeValue?
|
||||
let inset: UIEdgeInsets
|
||||
let icon: ProfileIcon
|
||||
let backgroundColor: ThemeValue?
|
||||
let forcedBackgroundColor: ForcedThemeValue?
|
||||
|
||||
public init(
|
||||
imageData: Data?,
|
||||
renderingMode: UIImage.RenderingMode = .automatic,
|
||||
themeTintColor: ThemeValue? = nil,
|
||||
inset: UIEdgeInsets = .zero,
|
||||
icon: ProfileIcon = .none,
|
||||
backgroundColor: ThemeValue? = nil,
|
||||
forcedBackgroundColor: ForcedThemeValue? = nil
|
||||
) {
|
||||
self.imageData = imageData
|
||||
self.renderingMode = renderingMode
|
||||
self.themeTintColor = themeTintColor
|
||||
self.inset = inset
|
||||
self.icon = icon
|
||||
self.backgroundColor = backgroundColor
|
||||
self.forcedBackgroundColor = forcedBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
public enum Size {
|
||||
case navigation
|
||||
case message
|
||||
|
@ -21,7 +47,7 @@ public final class ProfilePictureView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
var imageSize: CGFloat {
|
||||
public var imageSize: CGFloat {
|
||||
switch self {
|
||||
case .navigation, .message: return 26
|
||||
case .list: return 46
|
||||
|
@ -29,7 +55,7 @@ public final class ProfilePictureView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
var multiImageSize: CGFloat {
|
||||
public var multiImageSize: CGFloat {
|
||||
switch self {
|
||||
case .navigation, .message: return 18 // Shouldn't be used
|
||||
case .list: return 32
|
||||
|
@ -44,32 +70,31 @@ public final class ProfilePictureView: UIView {
|
|||
case .hero: return 24
|
||||
}
|
||||
}
|
||||
|
||||
var iconVerticalInset: CGFloat {
|
||||
switch self {
|
||||
case .navigation, .message: return 1
|
||||
case .list: return 3
|
||||
case .hero: return 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProfileIcon {
|
||||
public enum ProfileIcon: Equatable, Hashable {
|
||||
case none
|
||||
case crown
|
||||
case rightPlus
|
||||
|
||||
func iconVerticalInset(for size: Size) -> CGFloat {
|
||||
switch (self, size) {
|
||||
case (.crown, .navigation), (.crown, .message): return 1
|
||||
case (.crown, .list): return 3
|
||||
case (.crown, .hero): return 5
|
||||
|
||||
case (.rightPlus, _): return 3
|
||||
default: return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var size: Size {
|
||||
didSet {
|
||||
widthConstraint.constant = (customWidth ?? size.viewSize)
|
||||
heightConstraint.constant = size.viewSize
|
||||
profileIconTopConstraint.constant = size.iconVerticalInset
|
||||
profileIconBottomConstraint.constant = -size.iconVerticalInset
|
||||
profileIconBackgroundWidthConstraint.constant = size.iconSize
|
||||
profileIconBackgroundHeightConstraint.constant = size.iconSize
|
||||
additionalProfileIconTopConstraint.constant = size.iconVerticalInset
|
||||
additionalProfileIconBottomConstraint.constant = -size.iconVerticalInset
|
||||
additionalProfileIconBackgroundWidthConstraint.constant = size.iconSize
|
||||
additionalProfileIconBackgroundHeightConstraint.constant = size.iconSize
|
||||
|
||||
|
@ -82,7 +107,18 @@ public final class ProfilePictureView: UIView {
|
|||
self.widthConstraint.constant = (customWidth ?? self.size.viewSize)
|
||||
}
|
||||
}
|
||||
private var hasTappableProfilePicture: Bool = false
|
||||
override public var clipsToBounds: Bool {
|
||||
didSet {
|
||||
imageContainerView.clipsToBounds = clipsToBounds
|
||||
additionalImageContainerView.clipsToBounds = clipsToBounds
|
||||
|
||||
imageContainerView.layer.cornerRadius = (clipsToBounds ?
|
||||
(additionalImageContainerView.isHidden ? (size.imageSize / 2) : (size.multiImageSize / 2)) :
|
||||
0
|
||||
)
|
||||
imageContainerView.layer.cornerRadius = (clipsToBounds ? (size.multiImageSize / 2) : 0)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Constraints
|
||||
|
||||
|
@ -108,6 +144,26 @@ public final class ProfilePictureView: UIView {
|
|||
private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint!
|
||||
private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint!
|
||||
private var additionalProfileIconBackgroundHeightConstraint: NSLayoutConstraint!
|
||||
private lazy var imageEdgeConstraints: [NSLayoutConstraint] = [ // MUST be in 'top, left, bottom, right' order
|
||||
imageView.pin(.top, to: .top, of: imageContainerView, withInset: 0),
|
||||
imageView.pin(.left, to: .left, of: imageContainerView, withInset: 0),
|
||||
imageView.pin(.bottom, to: .bottom, of: imageContainerView, withInset: 0),
|
||||
imageView.pin(.right, to: .right, of: imageContainerView, withInset: 0),
|
||||
animatedImageView.pin(.top, to: .top, of: imageContainerView, withInset: 0),
|
||||
animatedImageView.pin(.left, to: .left, of: imageContainerView, withInset: 0),
|
||||
animatedImageView.pin(.bottom, to: .bottom, of: imageContainerView, withInset: 0),
|
||||
animatedImageView.pin(.right, to: .right, of: imageContainerView, withInset: 0)
|
||||
]
|
||||
private lazy var additionalImageEdgeConstraints: [NSLayoutConstraint] = [ // MUST be in 'top, left, bottom, right' order
|
||||
additionalImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 0),
|
||||
additionalImageView.pin(.left, to: .left, of: additionalImageContainerView, withInset: 0),
|
||||
additionalImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 0),
|
||||
additionalImageView.pin(.right, to: .right, of: additionalImageContainerView, withInset: 0),
|
||||
additionalAnimatedImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 0),
|
||||
additionalAnimatedImageView.pin(.left, to: .left, of: additionalImageContainerView, withInset: 0),
|
||||
additionalAnimatedImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 0),
|
||||
additionalAnimatedImageView.pin(.right, to: .right, of: additionalImageContainerView, withInset: 0)
|
||||
]
|
||||
|
||||
// MARK: - Components
|
||||
|
||||
|
@ -144,18 +200,7 @@ public final class ProfilePictureView: UIView {
|
|||
result.clipsToBounds = true
|
||||
result.themeBackgroundColor = .primary
|
||||
result.themeBorderColor = .backgroundPrimary
|
||||
result.isHidden = true
|
||||
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var additionalProfilePlaceholderImageView: UIImageView = {
|
||||
let result: UIImageView = UIImageView(
|
||||
image: UIImage(systemName: "person.fill")?.withRenderingMode(.alwaysTemplate)
|
||||
)
|
||||
result.translatesAutoresizingMaskIntoConstraints = false
|
||||
result.contentMode = .scaleAspectFill
|
||||
result.themeTintColor = .textPrimary
|
||||
result.layer.borderWidth = 1
|
||||
result.isHidden = true
|
||||
|
||||
return result
|
||||
|
@ -215,6 +260,7 @@ public final class ProfilePictureView: UIView {
|
|||
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize))
|
||||
|
||||
clipsToBounds = true
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
|
@ -251,23 +297,16 @@ public final class ProfilePictureView: UIView {
|
|||
imageContainerView.addSubview(animatedImageView)
|
||||
additionalImageContainerView.addSubview(additionalImageView)
|
||||
additionalImageContainerView.addSubview(additionalAnimatedImageView)
|
||||
additionalImageContainerView.addSubview(additionalProfilePlaceholderImageView)
|
||||
|
||||
imageView.pin(to: imageContainerView)
|
||||
animatedImageView.pin(to: imageContainerView)
|
||||
additionalImageView.pin(to: additionalImageContainerView)
|
||||
additionalAnimatedImageView.pin(to: additionalImageContainerView)
|
||||
|
||||
additionalProfilePlaceholderImageView.pin(.top, to: .top, of: additionalImageContainerView, withInset: 3)
|
||||
additionalProfilePlaceholderImageView.pin(.left, to: .left, of: additionalImageContainerView)
|
||||
additionalProfilePlaceholderImageView.pin(.right, to: .right, of: additionalImageContainerView)
|
||||
additionalProfilePlaceholderImageView.pin(.bottom, to: .bottom, of: additionalImageContainerView, withInset: 5)
|
||||
// Activate the image edge constraints
|
||||
imageEdgeConstraints.forEach { $0.isActive = true }
|
||||
additionalImageEdgeConstraints.forEach { $0.isActive = true }
|
||||
|
||||
profileIconTopConstraint = profileIconImageView.pin(
|
||||
.top,
|
||||
to: .top,
|
||||
of: profileIconBackgroundView,
|
||||
withInset: size.iconVerticalInset
|
||||
withInset: 0
|
||||
)
|
||||
profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView)
|
||||
profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView)
|
||||
|
@ -275,7 +314,7 @@ public final class ProfilePictureView: UIView {
|
|||
.bottom,
|
||||
to: .bottom,
|
||||
of: profileIconBackgroundView,
|
||||
withInset: -size.iconVerticalInset
|
||||
withInset: 0
|
||||
)
|
||||
profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView)
|
||||
profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView)
|
||||
|
@ -289,7 +328,7 @@ public final class ProfilePictureView: UIView {
|
|||
.top,
|
||||
to: .top,
|
||||
of: additionalProfileIconBackgroundView,
|
||||
withInset: size.iconVerticalInset
|
||||
withInset: 0
|
||||
)
|
||||
additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView)
|
||||
additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView)
|
||||
|
@ -297,7 +336,7 @@ public final class ProfilePictureView: UIView {
|
|||
.bottom,
|
||||
to: .bottom,
|
||||
of: additionalProfileIconBackgroundView,
|
||||
withInset: -size.iconVerticalInset
|
||||
withInset: 0
|
||||
)
|
||||
additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView)
|
||||
additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView)
|
||||
|
@ -314,8 +353,10 @@ public final class ProfilePictureView: UIView {
|
|||
icon: ProfileIcon,
|
||||
imageView: UIImageView,
|
||||
backgroundView: UIView,
|
||||
topConstraint: NSLayoutConstraint,
|
||||
leftAlignConstraint: NSLayoutConstraint,
|
||||
rightAlignConstraint: NSLayoutConstraint
|
||||
rightAlignConstraint: NSLayoutConstraint,
|
||||
bottomConstraint: NSLayoutConstraint
|
||||
) {
|
||||
backgroundView.isHidden = (icon == .none)
|
||||
leftAlignConstraint.isActive = (
|
||||
|
@ -325,6 +366,8 @@ public final class ProfilePictureView: UIView {
|
|||
rightAlignConstraint.isActive = (
|
||||
icon == .rightPlus
|
||||
)
|
||||
topConstraint.constant = icon.iconVerticalInset(for: size)
|
||||
bottomConstraint.constant = -icon.iconVerticalInset(for: size)
|
||||
|
||||
switch icon {
|
||||
case .none: imageView.image = nil
|
||||
|
@ -345,201 +388,159 @@ public final class ProfilePictureView: UIView {
|
|||
}
|
||||
|
||||
case .rightPlus:
|
||||
imageView.image = UIImage(systemName: "plus")
|
||||
imageView.image = UIImage(
|
||||
systemName: "plus",
|
||||
withConfiguration: UIImage.SymbolConfiguration(weight: .semibold)
|
||||
)
|
||||
imageView.themeTintColor = .black
|
||||
backgroundView.themeBackgroundColor = .primary
|
||||
backgroundView.themeBackgroundColorForced = .primary(.green)
|
||||
}
|
||||
}
|
||||
|
||||
public func update(
|
||||
publicKey: String = "",
|
||||
profile: Profile? = nil,
|
||||
icon: ProfileIcon = .none,
|
||||
additionalProfile: Profile? = nil,
|
||||
additionalIcon: ProfileIcon = .none,
|
||||
threadVariant: SessionThread.Variant,
|
||||
openGroupProfilePictureData: Data? = nil,
|
||||
useFallbackPicture: Bool = false,
|
||||
showMultiAvatarForClosedGroup: Bool = false
|
||||
) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// MARK: - Content
|
||||
|
||||
private func prepareForReuse() {
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.isHidden = true
|
||||
animatedImageView.contentMode = .scaleAspectFill
|
||||
animatedImageView.isHidden = true
|
||||
imageContainerView.clipsToBounds = clipsToBounds
|
||||
imageContainerView.themeBackgroundColor = .backgroundSecondary
|
||||
additionalImageContainerView.isHidden = true
|
||||
animatedImageView.image = nil
|
||||
additionalImageView.image = nil
|
||||
additionalAnimatedImageView.image = nil
|
||||
additionalImageView.isHidden = true
|
||||
additionalAnimatedImageView.isHidden = true
|
||||
additionalImageContainerView.clipsToBounds = clipsToBounds
|
||||
|
||||
// Sort out the profile icon first
|
||||
imageViewTopConstraint.isActive = false
|
||||
imageViewLeadingConstraint.isActive = false
|
||||
imageViewCenterXConstraint.isActive = true
|
||||
imageViewCenterYConstraint.isActive = true
|
||||
profileIconBackgroundView.isHidden = true
|
||||
profileIconBackgroundLeftAlignConstraint.isActive = false
|
||||
profileIconBackgroundRightAlignConstraint.isActive = false
|
||||
additionalProfileIconBackgroundView.isHidden = true
|
||||
additionalProfileIconBackgroundLeftAlignConstraint.isActive = false
|
||||
additionalProfileIconBackgroundRightAlignConstraint.isActive = false
|
||||
imageEdgeConstraints.forEach { $0.constant = 0 }
|
||||
additionalImageEdgeConstraints.forEach { $0.constant = 0 }
|
||||
}
|
||||
|
||||
public func update(
|
||||
_ info: Info,
|
||||
additionalInfo: Info? = nil
|
||||
) {
|
||||
prepareForReuse()
|
||||
|
||||
// Sort out the icon first
|
||||
updateIconView(
|
||||
icon: icon,
|
||||
icon: info.icon,
|
||||
imageView: profileIconImageView,
|
||||
backgroundView: profileIconBackgroundView,
|
||||
topConstraint: profileIconTopConstraint,
|
||||
leftAlignConstraint: profileIconBackgroundLeftAlignConstraint,
|
||||
rightAlignConstraint: profileIconBackgroundRightAlignConstraint
|
||||
rightAlignConstraint: profileIconBackgroundRightAlignConstraint,
|
||||
bottomConstraint: profileIconBottomConstraint
|
||||
)
|
||||
|
||||
guard !useFallbackPicture else {
|
||||
switch self.size {
|
||||
case .navigation, .message: imageView.image = #imageLiteral(resourceName: "SessionWhite16")
|
||||
case .list: imageView.image = #imageLiteral(resourceName: "SessionWhite24")
|
||||
case .hero: imageView.image = #imageLiteral(resourceName: "SessionWhite40")
|
||||
// Populate the main imageView
|
||||
switch info.imageData?.guessedImageFormat {
|
||||
case .gif, .webp: animatedImageView.image = info.imageData.map { YYImage(data: $0) }
|
||||
default:
|
||||
imageView.image = info.imageData
|
||||
.map {
|
||||
guard info.renderingMode != .automatic else { return UIImage(data: $0) }
|
||||
|
||||
return UIImage(data: $0)?.withRenderingMode(info.renderingMode)
|
||||
}
|
||||
}
|
||||
|
||||
imageView.themeTintColor = info.themeTintColor
|
||||
imageView.isHidden = (imageView.image == nil)
|
||||
animatedImageView.themeTintColor = info.themeTintColor
|
||||
animatedImageView.isHidden = (animatedImageView.image == nil)
|
||||
imageContainerView.themeBackgroundColor = info.backgroundColor
|
||||
imageContainerView.themeBackgroundColorForced = info.forcedBackgroundColor
|
||||
profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
|
||||
imageEdgeConstraints.enumerated().forEach { index, constraint in
|
||||
switch index % 4 {
|
||||
case 0: constraint.constant = info.inset.top
|
||||
case 1: constraint.constant = info.inset.left
|
||||
case 2: constraint.constant = -info.inset.bottom
|
||||
case 3: constraint.constant = -info.inset.right
|
||||
default: break
|
||||
}
|
||||
|
||||
imageView.contentMode = .center
|
||||
imageView.isHidden = false
|
||||
animatedImageView.isHidden = true
|
||||
imageContainerView.themeBackgroundColorForced = .theme(.classicDark, color: .borderSeparator)
|
||||
imageContainerView.layer.cornerRadius = (self.size.imageSize / 2)
|
||||
imageViewWidthConstraint.constant = self.size.imageSize
|
||||
imageViewHeightConstraint.constant = self.size.imageSize
|
||||
profileIconBackgroundWidthConstraint.constant = self.size.iconSize
|
||||
profileIconBackgroundHeightConstraint.constant = self.size.iconSize
|
||||
profileIconBackgroundView.layer.cornerRadius = (self.size.iconSize / 2)
|
||||
additionalProfileIconBackgroundWidthConstraint.constant = self.size.iconSize
|
||||
additionalProfileIconBackgroundHeightConstraint.constant = self.size.iconSize
|
||||
additionalProfileIconBackgroundView.layer.cornerRadius = (self.size.iconSize / 2)
|
||||
additionalImageContainerView.isHidden = true
|
||||
animatedImageView.image = nil
|
||||
additionalImageView.image = nil
|
||||
additionalAnimatedImageView.image = nil
|
||||
additionalImageView.isHidden = true
|
||||
additionalAnimatedImageView.isHidden = true
|
||||
additionalProfilePlaceholderImageView.isHidden = true
|
||||
}
|
||||
|
||||
// Check if there is a second image (if not then set the size and finish)
|
||||
guard let additionalInfo: Info = additionalInfo else {
|
||||
imageViewWidthConstraint.constant = size.imageSize
|
||||
imageViewHeightConstraint.constant = size.imageSize
|
||||
imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.imageSize / 2) : 0)
|
||||
return
|
||||
}
|
||||
guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return }
|
||||
|
||||
func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) {
|
||||
if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) {
|
||||
let format: ImageFormat = profileData.guessedImageFormat
|
||||
|
||||
let image: UIImage? = (format == .gif || format == .webp ?
|
||||
nil :
|
||||
UIImage(data: profileData)
|
||||
)
|
||||
let animatedImage: YYImage? = (format != .gif && format != .webp ?
|
||||
nil :
|
||||
YYImage(data: profileData)
|
||||
)
|
||||
|
||||
if image != nil || animatedImage != nil {
|
||||
return (image, animatedImage, true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
Identicon.generatePlaceholderIcon(
|
||||
seed: publicKey,
|
||||
text: (profile?.displayName(for: threadVariant))
|
||||
.defaulting(to: publicKey),
|
||||
size: size
|
||||
),
|
||||
nil,
|
||||
false
|
||||
)
|
||||
}
|
||||
// Sort out the additional icon first
|
||||
updateIconView(
|
||||
icon: additionalInfo.icon,
|
||||
imageView: additionalProfileIconImageView,
|
||||
backgroundView: additionalProfileIconBackgroundView,
|
||||
topConstraint: additionalProfileIconTopConstraint,
|
||||
leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint,
|
||||
rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint,
|
||||
bottomConstraint: additionalProfileIconBottomConstraint
|
||||
)
|
||||
|
||||
// Calulate the sizes (and set the additional image content)
|
||||
let targetSize: CGFloat
|
||||
|
||||
switch (threadVariant, showMultiAvatarForClosedGroup) {
|
||||
case (.closedGroup, true):
|
||||
targetSize = self.size.multiImageSize
|
||||
additionalImageContainerView.isHidden = false
|
||||
imageViewTopConstraint.isActive = true
|
||||
imageViewLeadingConstraint.isActive = true
|
||||
imageViewCenterXConstraint.isActive = false
|
||||
imageViewCenterYConstraint.isActive = false
|
||||
|
||||
// Sort out the additinoal profile icon if needed
|
||||
updateIconView(
|
||||
icon: additionalIcon,
|
||||
imageView: additionalProfileIconImageView,
|
||||
backgroundView: additionalProfileIconBackgroundView,
|
||||
leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint,
|
||||
rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint
|
||||
)
|
||||
|
||||
if let additionalProfile: Profile = additionalProfile {
|
||||
let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture(
|
||||
of: self.size.multiImageSize,
|
||||
for: additionalProfile.id,
|
||||
profile: additionalProfile
|
||||
)
|
||||
|
||||
// Set the images and show the appropriate imageView (non-animated should be
|
||||
// visible if there is no image)
|
||||
additionalImageView.image = image
|
||||
additionalAnimatedImageView.image = animatedImage
|
||||
additionalImageView.isHidden = (animatedImage != nil)
|
||||
additionalAnimatedImageView.isHidden = (animatedImage == nil)
|
||||
additionalProfilePlaceholderImageView.isHidden = true
|
||||
}
|
||||
else {
|
||||
additionalImageView.isHidden = true
|
||||
additionalAnimatedImageView.isHidden = true
|
||||
additionalProfilePlaceholderImageView.isHidden = false
|
||||
}
|
||||
|
||||
// Set the additional image content and reposition the image views correctly
|
||||
switch additionalInfo.imageData?.guessedImageFormat {
|
||||
case .gif, .webp: additionalAnimatedImageView.image = additionalInfo.imageData.map { YYImage(data: $0) }
|
||||
default:
|
||||
targetSize = self.size.imageSize
|
||||
|
||||
additionalImageContainerView.isHidden = true
|
||||
additionalProfileIconBackgroundView.isHidden = true
|
||||
additionalImageView.image = nil
|
||||
additionalImageView.isHidden = true
|
||||
additionalAnimatedImageView.image = nil
|
||||
additionalAnimatedImageView.isHidden = true
|
||||
additionalProfilePlaceholderImageView.isHidden = true
|
||||
imageViewTopConstraint.isActive = false
|
||||
imageViewLeadingConstraint.isActive = false
|
||||
imageViewCenterXConstraint.isActive = true
|
||||
imageViewCenterYConstraint.isActive = true
|
||||
additionalImageView.image = additionalInfo.imageData
|
||||
.map {
|
||||
guard additionalInfo.renderingMode != .automatic else { return UIImage(data: $0) }
|
||||
|
||||
return UIImage(data: $0)?.withRenderingMode(additionalInfo.renderingMode)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the image
|
||||
if let openGroupProfilePictureData: Data = openGroupProfilePictureData {
|
||||
let format: ImageFormat = openGroupProfilePictureData.guessedImageFormat
|
||||
|
||||
let image: UIImage? = (format == .gif || format == .webp ?
|
||||
nil :
|
||||
UIImage(data: openGroupProfilePictureData)
|
||||
)
|
||||
let animatedImage: YYImage? = (format != .gif && format != .webp ?
|
||||
nil :
|
||||
YYImage(data: openGroupProfilePictureData)
|
||||
)
|
||||
|
||||
imageView.image = image
|
||||
animatedImageView.image = animatedImage
|
||||
imageView.isHidden = (animatedImage != nil)
|
||||
animatedImageView.isHidden = (animatedImage == nil)
|
||||
hasTappableProfilePicture = true
|
||||
}
|
||||
else {
|
||||
let (image, animatedImage, isTappable): (UIImage?, YYImage?, Bool) = getProfilePicture(
|
||||
of: targetSize,
|
||||
for: publicKey,
|
||||
profile: profile
|
||||
)
|
||||
imageView.image = image
|
||||
animatedImageView.image = animatedImage
|
||||
imageView.isHidden = (animatedImage != nil)
|
||||
animatedImageView.isHidden = (animatedImage == nil)
|
||||
hasTappableProfilePicture = isTappable
|
||||
additionalImageView.themeTintColor = additionalInfo.themeTintColor
|
||||
additionalImageView.isHidden = (additionalImageView.image == nil)
|
||||
additionalAnimatedImageView.themeTintColor = additionalInfo.themeTintColor
|
||||
additionalAnimatedImageView.isHidden = (additionalAnimatedImageView.image == nil)
|
||||
additionalImageContainerView.isHidden = false
|
||||
|
||||
switch (info.backgroundColor, info.forcedBackgroundColor) {
|
||||
case (_, .some(let color)): additionalImageContainerView.themeBackgroundColorForced = color
|
||||
case (.some(let color), _): additionalImageContainerView.themeBackgroundColor = color
|
||||
default: additionalImageContainerView.themeBackgroundColor = .primary
|
||||
}
|
||||
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
animatedImageView.contentMode = .scaleAspectFill
|
||||
imageContainerView.themeBackgroundColor = .backgroundSecondary
|
||||
imageViewWidthConstraint.constant = targetSize
|
||||
imageViewHeightConstraint.constant = targetSize
|
||||
imageContainerView.layer.cornerRadius = (targetSize / 2)
|
||||
additionalImageViewWidthConstraint.constant = targetSize
|
||||
additionalImageViewHeightConstraint.constant = targetSize
|
||||
additionalImageContainerView.layer.cornerRadius = (targetSize / 2)
|
||||
profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
|
||||
additionalImageEdgeConstraints.enumerated().forEach { index, constraint in
|
||||
switch index % 4 {
|
||||
case 0: constraint.constant = additionalInfo.inset.top
|
||||
case 1: constraint.constant = additionalInfo.inset.left
|
||||
case 2: constraint.constant = -additionalInfo.inset.bottom
|
||||
case 3: constraint.constant = -additionalInfo.inset.right
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
imageViewTopConstraint.isActive = true
|
||||
imageViewLeadingConstraint.isActive = true
|
||||
imageViewCenterXConstraint.isActive = false
|
||||
imageViewCenterYConstraint.isActive = false
|
||||
|
||||
imageViewWidthConstraint.constant = size.multiImageSize
|
||||
imageViewHeightConstraint.constant = size.multiImageSize
|
||||
imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.multiImageSize / 2) : 0)
|
||||
additionalImageViewWidthConstraint.constant = size.multiImageSize
|
||||
additionalImageViewHeightConstraint.constant = size.multiImageSize
|
||||
additionalImageContainerView.layer.cornerRadius = (additionalImageContainerView.clipsToBounds ?
|
||||
(size.multiImageSize / 2) :
|
||||
0
|
||||
)
|
||||
additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
@objc public func getProfilePicture() -> UIImage? {
|
||||
return (hasTappableProfilePicture ? imageView.image : nil)
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import SessionUIKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@objc(LKIdenticon)
|
||||
public final class Identicon: NSObject {
|
||||
private static let placeholderCache: Atomic<NSCache<NSString, UIImage>> = {
|
||||
let result = NSCache<NSString, UIImage>()
|
||||
result.countLimit = 50
|
||||
|
||||
return Atomic(result)
|
||||
}()
|
||||
|
||||
@objc public static func generatePlaceholderIcon(seed: String, text: String, size: CGFloat) -> UIImage {
|
||||
let icon = PlaceholderIcon(seed: seed)
|
||||
|
||||
var content: String = (text.hasSuffix("\(String(seed.suffix(4))))") ?
|
||||
(text.split(separator: "(")
|
||||
.first
|
||||
.map { String($0) })
|
||||
.defaulting(to: text) :
|
||||
text
|
||||
)
|
||||
|
||||
if content.count > 2 && SessionId.Prefix(from: content) != nil {
|
||||
content.removeFirst(2)
|
||||
}
|
||||
|
||||
let initials: String = content
|
||||
.split(separator: " ")
|
||||
.compactMap { word in word.first.map { String($0) } }
|
||||
.joined()
|
||||
let cacheKey: String = "\(content)-\(Int(floor(size)))"
|
||||
|
||||
if let cachedIcon: UIImage = placeholderCache.wrappedValue.object(forKey: cacheKey as NSString) {
|
||||
return cachedIcon
|
||||
}
|
||||
|
||||
let layer = icon.generateLayer(
|
||||
with: size,
|
||||
text: (initials.count >= 2 ?
|
||||
initials.substring(to: 2).uppercased() :
|
||||
content.substring(to: 2).uppercased()
|
||||
)
|
||||
)
|
||||
|
||||
let rect = CGRect(origin: CGPoint.zero, size: layer.frame.size)
|
||||
let renderer = UIGraphicsImageRenderer(size: rect.size)
|
||||
let result = renderer.image { layer.render(in: $0.cgContext) }
|
||||
|
||||
placeholderCache.mutate { $0.setObject(result, forKey: cacheKey as NSString) }
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue