Updated the profile picture modal

Moved the ProfilePictureView into SessionUIKit
Fixed a couple of minor ProfilePictureView bugs
pull/859/head
Morgan Pretty 2 years ago
parent 2d792e4e3e
commit cf2e198a64

@ -381,9 +381,6 @@
C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; 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 */; }; 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 */; }; C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */; };
C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; }; C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; };
C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.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 */; }; FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E728264937000CE219 /* MediaDetailViewController.swift */; };
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */; }; FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5E9282A1BB2000CE219 /* ThreadTypingIndicator.swift */; };
FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5EB282B8F17000CE219 /* AttachmentError.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 */; }; FD17D79927F40AB800122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */; };
FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacy.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 */; }; 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; }; 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; }; 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; }; 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 = SessionUIKit/Components/PlaceholderIcon.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 = SessionUIKit/Components/ProfilePictureView.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; };
C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIViewController+Utilities.swift"; path = "SignalUtilitiesKit/Utilities/UIViewController+Utilities.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; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; FD17D79B27F40B2E00122BE0 /* SMKLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacy.swift; sourceTree = "<group>"; };
@ -2857,6 +2857,8 @@
C38EF3EE255B6DF6007E1867 /* GradientView.swift */, C38EF3EE255B6DF6007E1867 /* GradientView.swift */,
B86BD08323399ACF000F5AE3 /* Modal.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */,
FD52090628B49738006098F6 /* ConfirmationModal.swift */, FD52090628B49738006098F6 /* ConfirmationModal.swift */,
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */,
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */,
FD71165A28E6DDBC00B47552 /* StyledNavigationController.swift */, FD71165A28E6DDBC00B47552 /* StyledNavigationController.swift */,
); );
path = Components; path = Components;
@ -2867,7 +2869,7 @@
children = ( children = (
C33FD9B7255A54A300E217F9 /* Meta */, C33FD9B7255A54A300E217F9 /* Meta */,
C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */, C36096ED25AD20FD008B62B2 /* Media Viewing & Editing */,
C36096EF25AD2268008B62B2 /* Profile Pictures */, FD16AB5D2A1DD8E70083D849 /* Profile Pictures */,
C36096EE25AD21BC008B62B2 /* Screen Lock */, C36096EE25AD21BC008B62B2 /* Screen Lock */,
C3851CD225624B060061EEB0 /* Shared Views */, C3851CD225624B060061EEB0 /* Shared Views */,
C360970125AD22D3008B62B2 /* Shared View Controllers */, C360970125AD22D3008B62B2 /* Shared View Controllers */,
@ -3046,16 +3048,6 @@
path = "Screen Lock"; path = "Screen Lock";
sourceTree = "<group>"; 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 */ = { C360970125AD22D3008B62B2 /* Shared View Controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -3174,6 +3166,7 @@
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */, FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */, FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */,
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */, FD772899284AF1BD0018502F /* Sodium+Utilities.swift */,
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */,
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */, C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
C3ECBF7A257056B700EA7FCE /* Threading.swift */, C3ECBF7A257056B700EA7FCE /* Threading.swift */,
); );
@ -3558,6 +3551,13 @@
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FD16AB5D2A1DD8E70083D849 /* Profile Pictures */ = {
isa = PBXGroup;
children = (
);
path = "Profile Pictures";
sourceTree = "<group>";
};
FD17D79427F3E03300122BE0 /* Migrations */ = { FD17D79427F3E03300122BE0 /* Migrations */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -5171,6 +5171,7 @@
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */, C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */,
FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */, FD37E9F628A5F106003AE748 /* Configuration.swift in Sources */,
C331FFE02558FB0000070591 /* SearchBar.swift in Sources */, C331FFE02558FB0000070591 /* SearchBar.swift in Sources */,
FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */,
FD71162C28E1451400B47552 /* Position.swift in Sources */, FD71162C28E1451400B47552 /* Position.swift in Sources */,
FD52090328B4680F006098F6 /* RadioButton.swift in Sources */, FD52090328B4680F006098F6 /* RadioButton.swift in Sources */,
C331FFE82558FB0000070591 /* TextView.swift in Sources */, C331FFE82558FB0000070591 /* TextView.swift in Sources */,
@ -5181,6 +5182,7 @@
FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */, FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */,
C331FF9A2558FA6B00070591 /* Values.swift in Sources */, C331FF9A2558FA6B00070591 /* Values.swift in Sources */,
FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */, FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */,
FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */,
C331FFE42558FB0000070591 /* SessionButton.swift in Sources */, C331FFE42558FB0000070591 /* SessionButton.swift in Sources */,
C331FFE92558FB0000070591 /* Separator.swift in Sources */, C331FFE92558FB0000070591 /* Separator.swift in Sources */,
FD71163228E2C42A00B47552 /* IconSize.swift in Sources */, FD71163228E2C42A00B47552 /* IconSize.swift in Sources */,
@ -5199,7 +5201,6 @@
C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */, C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */,
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */, C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */,
C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */, C38EF3C5255B6DE7007E1867 /* OWSViewController+ImageEditor.swift in Sources */,
C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */,
C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */, C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */,
C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */, C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */,
FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */, FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */,
@ -5220,12 +5221,10 @@
C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */, C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */,
C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */, C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */,
C38EF388255B6DD2007E1867 /* AttachmentApprovalViewController.swift in Sources */, C38EF388255B6DD2007E1867 /* AttachmentApprovalViewController.swift in Sources */,
C38EF2A7255B6D93007E1867 /* ProfilePictureView.swift in Sources */,
C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */, C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */,
C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */, C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */,
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */, C38EF407255B6DF7007E1867 /* Toast.swift in Sources */,
C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */, C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */,
C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */,
C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */, C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */,
C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */, C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */,
C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */, C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */,
@ -5558,6 +5557,7 @@
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */,
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */,
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */, FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */, B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */, C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,

@ -5,6 +5,7 @@ import CallKit
import GRDB import GRDB
import WebRTC import WebRTC
import PromiseKit import PromiseKit
import SessionUIKit
import SignalUtilitiesKit import SignalUtilitiesKit
import SessionMessagingKit import SessionMessagingKit
@ -154,7 +155,7 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate {
self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact) self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact)
self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId) self.profilePicture = ProfileManager.profileAvatar(db, id: sessionId)
.map { UIImage(data: $0) } .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 WebRTCSession.current = self.webRTCSession
self.webRTCSession.delegate = self self.webRTCSession.delegate = self

@ -32,7 +32,7 @@ import SignalUtilitiesKit
let srcImage: UIImage let srcImage: UIImage
let successCompletion: ((UIImage) -> Void) let successCompletion: ((Data) -> Void)
var imageView: UIView! var imageView: UIView!
@ -79,7 +79,7 @@ import SignalUtilitiesKit
notImplemented() 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. // normalized() can be slightly expensive but in practice this is fine.
self.srcImage = srcImage.normalized() self.srcImage = srcImage.normalized()
self.successCompletion = successCompletion self.successCompletion = successCompletion
@ -487,10 +487,9 @@ import SignalUtilitiesKit
@objc func donePressed(sender: UIButton) { @objc func donePressed(sender: UIButton) {
let successCompletion = self.successCompletion let successCompletion = self.successCompletion
dismiss(animated: true, completion: { dismiss(animated: true, completion: {
guard let dstImage = self.generateDstImage() else { guard let dstImageData: Data = self.generateDstImageData() else { return }
return
} successCompletion(dstImageData)
successCompletion(dstImage)
}) })
} }
@ -517,4 +516,8 @@ import SignalUtilitiesKit
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
return scaledImage 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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Êtes-vous sûr de vouloir supprimer votre conversation avec %@ ?";
"delete_conversation_confirmation_alert_title" = "Supprimer conversation"; "delete_conversation_confirmation_alert_title" = "Supprimer conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "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_message" = "Are you sure you want to delete your conversation with %@?";
"delete_conversation_confirmation_alert_title" = "Delete Conversation"; "delete_conversation_confirmation_alert_title" = "Delete Conversation";
"update_profile_modal_title" = "Set Display Picture"; "update_profile_modal_title" = "Set Display Picture";
"update_profile_modal_upload" = "Upload"; "update_profile_modal_save" = "Save";
"update_profile_modal_remove" = "Remove"; "update_profile_modal_remove" = "Remove";

@ -52,7 +52,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
private var editedDisplayName: String? private var editedDisplayName: String?
private var editProfilePictureModal: ConfirmationModal? private var editProfilePictureModal: ConfirmationModal?
private var editProfilePictureModalInfo: ConfirmationModal.Info? private var editProfilePictureModalInfo: ConfirmationModal.Info?
private var editedProfilePicture: UIImage? private var editedProfilePictureData: Data?
private var editedProfilePictureFileName: String? private var editedProfilePictureFileName: String?
// MARK: - Initialization // MARK: - Initialization
@ -166,7 +166,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
self?.oldDisplayName = updatedNickname self?.oldDisplayName = updatedNickname
self?.updateProfile( self?.updateProfile(
name: updatedNickname, name: updatedNickname,
profilePicture: nil, profilePictureData: nil,
profilePictureFilePath: ProfileManager.profileAvatarFilepath(id: userSessionId), profilePictureFilePath: ProfileManager.profileAvatarFilepath(id: userSessionId),
isUpdatingDisplayName: true, isUpdatingDisplayName: true,
isUpdatingProfilePicture: false isUpdatingProfilePicture: false
@ -396,27 +396,27 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
private func updateProfilePicture() { private func updateProfilePicture() {
let existingDisplayName: String = self.oldDisplayName let existingDisplayName: String = self.oldDisplayName
let existingImage: UIImage? = ProfileManager let existingImageData: Data? = ProfileManager
.profileAvatar(id: self.userSessionId) .profileAvatar(id: self.userSessionId)
.map { UIImage(data: $0) }
let editProfilePictureModalInfo: ConfirmationModal.Info = ConfirmationModal.Info( let editProfilePictureModalInfo: ConfirmationModal.Info = ConfirmationModal.Info(
title: "update_profile_modal_title".localized(), title: "update_profile_modal_title".localized(),
body: .image( body: .image(
placeholder: UIImage(named: "profile_placeholder"), placeholderData: UIImage(named: "profile_placeholder")?.pngData(),
value: existingImage, valueData: existingImageData,
icon: .rightPlus,
style: .circular, style: .circular,
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() } onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
), ),
confirmTitle: "update_profile_modal_upload".localized(), confirmTitle: "update_profile_modal_save".localized(),
confirmEnabled: false, confirmEnabled: false,
cancelTitle: "update_profile_modal_remove".localized(), cancelTitle: "update_profile_modal_remove".localized(),
cancelEnabled: (existingImage != nil), cancelEnabled: (existingImageData != nil),
hasCloseButton: true, hasCloseButton: true,
dismissOnConfirm: false, dismissOnConfirm: false,
onConfirm: { [weak self] modal in onConfirm: { [weak self] modal in
self?.updateProfile( self?.updateProfile(
name: existingDisplayName, name: existingDisplayName,
profilePicture: self?.editedProfilePicture, profilePictureData: self?.editedProfilePictureData,
profilePictureFilePath: self?.editedProfilePictureFileName, profilePictureFilePath: self?.editedProfilePictureFileName,
isUpdatingDisplayName: false, isUpdatingDisplayName: false,
isUpdatingProfilePicture: true, isUpdatingProfilePicture: true,
@ -426,7 +426,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
onCancel: { [weak self] modal in onCancel: { [weak self] modal in
self?.updateProfile( self?.updateProfile(
name: existingDisplayName, name: existingDisplayName,
profilePicture: nil, profilePictureData: nil,
profilePictureFilePath: nil, profilePictureFilePath: nil,
isUpdatingDisplayName: false, isUpdatingDisplayName: false,
isUpdatingProfilePicture: true, isUpdatingProfilePicture: true,
@ -434,7 +434,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
) )
}, },
afterClosed: { [weak self] in afterClosed: { [weak self] in
self?.editedProfilePicture = nil self?.editedProfilePictureData = nil
self?.editedProfilePictureFileName = nil self?.editedProfilePictureFileName = nil
self?.editProfilePictureModal = nil self?.editProfilePictureModal = nil
self?.editProfilePictureModalInfo = nil self?.editProfilePictureModalInfo = nil
@ -447,18 +447,19 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
self.transitionToScreen(modal, transitionType: .present) 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 } guard let info: ConfirmationModal.Info = self.editProfilePictureModalInfo else { return }
self.editedProfilePicture = image self.editedProfilePictureData = imageData
self.editedProfilePictureFileName = filePath self.editedProfilePictureFileName = filePath
if let image: UIImage = image { if let imageData: Data = imageData {
self.editProfilePictureModal?.updateContent( self.editProfilePictureModal?.updateContent(
with: info.with( with: info.with(
body: .image( body: .image(
placeholder: UIImage(named: "profile_placeholder"), placeholderData: UIImage(named: "profile_placeholder")?.pngData(),
value: image, valueData: imageData,
icon: .rightPlus,
style: .circular, style: .circular,
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() } onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
), ),
@ -470,8 +471,9 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
self.editProfilePictureModal?.updateContent( self.editProfilePictureModal?.updateContent(
with: info.with( with: info.with(
body: .image( body: .image(
placeholder: UIImage(named: "profile_placeholder"), placeholderData: UIImage(named: "profile_placeholder")?.pngData(),
value: UIImage(contentsOfFile: filePath), valueData: FileManager.default.contents(atPath: filePath),
icon: .rightPlus,
style: .circular, style: .circular,
onClick: { [weak self] in self?.showPhotoLibraryForAvatar() } onClick: { [weak self] in self?.showPhotoLibraryForAvatar() }
), ),
@ -496,7 +498,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
private func updateProfile( private func updateProfile(
name: String, name: String,
profilePicture: UIImage?, profilePictureData: Data?,
profilePictureFilePath: String?, profilePictureFilePath: String?,
isUpdatingDisplayName: Bool, isUpdatingDisplayName: Bool,
isUpdatingProfilePicture: Bool, isUpdatingProfilePicture: Bool,
@ -506,7 +508,7 @@ class SettingsViewModel: SessionTableViewModel<SettingsViewModel.NavButton, Sett
ProfileManager.updateLocal( ProfileManager.updateLocal(
queue: DispatchQueue.global(qos: .default), queue: DispatchQueue.global(qos: .default),
profileName: name, profileName: name,
image: profilePicture, image: profilePictureData.map { UIImage(data: $0) },
imageFilePath: profilePictureFilePath, imageFilePath: profilePictureFilePath,
success: { db, updatedProfile in success: { db, updatedProfile in
if isUpdatingDisplayName { if isUpdatingDisplayName {
@ -642,9 +644,9 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
else { else {
let viewController: CropScaleImageViewController = CropScaleImageViewController( let viewController: CropScaleImageViewController = CropScaleImageViewController(
srcImage: rawAvatar, srcImage: rawAvatar,
successCompletion: { resultImage in successCompletion: { resultImageData in
self?.viewModel.updatedProfilePictureSelected( self?.viewModel.updatedProfilePictureSelected(
image: resultImage, imageData: resultImageData,
filePath: nil filePath: nil
) )
} }
@ -654,7 +656,7 @@ class ImagePickerHandler: NSObject, UIImagePickerControllerDelegate & UINavigati
} }
self?.viewModel.updatedProfilePictureSelected( self?.viewModel.updatedProfilePictureSelected(
image: nil, imageData: nil,
filePath: imageUrl.path 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 // FIXME: Refactor as part of the Groups Rebuild
public class ConfirmationModal: Modal { public class ConfirmationModal: Modal {
private static let imageSize: CGFloat = 80
private static let closeSize: CGFloat = 24 private static let closeSize: CGFloat = 24
private var internalOnConfirm: ((ConfirmationModal) -> ())? = nil private var internalOnConfirm: ((ConfirmationModal) -> ())? = nil
@ -37,22 +36,7 @@ public class ConfirmationModal: Modal {
return result return result
}() }()
private lazy var imageViewContainer: UIView = { private lazy var profileView: ProfilePictureView = ProfilePictureView(size: .hero)
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 confirmButton: UIButton = { private lazy var confirmButton: UIButton = {
let result: UIButton = Modal.createButton( let result: UIButton = Modal.createButton(
@ -73,7 +57,7 @@ public class ConfirmationModal: Modal {
}() }()
private lazy var contentStackView: UIStackView = { private lazy var contentStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, imageViewContainer ]) let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, profileView ])
result.axis = .vertical result.axis = .vertical
result.spacing = Values.smallSpacing result.spacing = Values.smallSpacing
result.isLayoutMarginsRelativeArrangement = true result.isLayoutMarginsRelativeArrangement = true
@ -141,11 +125,6 @@ public class ConfirmationModal: Modal {
contentView.addSubview(mainStackView) contentView.addSubview(mainStackView)
contentView.addSubview(closeButton) 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) mainStackView.pin(to: contentView)
closeButton.pin(.top, to: .top, of: contentView, withInset: 8) closeButton.pin(.top, to: .top, of: contentView, withInset: 8)
closeButton.pin(.right, to: .right, 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.attributedText = attributedText
explanationLabel.isHidden = false 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 mainStackView.spacing = 0
imageView.image = (value ?? placeholder) profileView.clipsToBounds = (style == .circular)
imageView.layer.cornerRadius = (style == .circular ? profileView.update(
(ConfirmationModal.imageSize / 2) : ProfilePictureView.Info(
0 imageData: (value ?? placeholder),
icon: icon
)
) )
imageViewContainer.isHidden = false profileView.isHidden = false
internalOnBodyTap = onClick internalOnBodyTap = onClick
} }
@ -406,8 +387,9 @@ public extension ConfirmationModal.Info {
// case input(placeholder: String, value: String?) // case input(placeholder: String, value: String?)
// case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)]) // case radio(explanation: NSAttributedString?, options: [(title: String, selected: Bool)])
case image( case image(
placeholder: UIImage?, placeholderData: Data?,
value: UIImage?, valueData: Data?,
icon: ProfilePictureView.ProfileIcon = .none,
style: ImageStyle, style: ImageStyle,
onClick: (() -> ()) onClick: (() -> ())
) )
@ -432,10 +414,11 @@ public extension ConfirmationModal.Info {
// lhsOptions.map { "\($0.0)-\($0.1)" } == rhsValue.map { "\($0.0)-\($0.1)" } // 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 ( return (
lhsPlaceholder == rhsPlaceholder && lhsPlaceholder == rhsPlaceholder &&
lhsValue == rhsValue && lhsValue == rhsValue &&
lhsIcon == rhsIcon &&
lhsStyle == rhsStyle lhsStyle == rhsStyle
) )
@ -449,9 +432,10 @@ public extension ConfirmationModal.Info {
case .text(let text): text.hash(into: &hasher) case .text(let text): text.hash(into: &hasher)
case .attributedText(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) placeholder.hash(into: &hasher)
value.hash(into: &hasher) value.hash(into: &hasher)
icon.hash(into: &hasher)
style.hash(into: &hasher) style.hash(into: &hasher)
} }
} }

@ -2,14 +2,23 @@
import UIKit import UIKit
import CryptoSwift import CryptoSwift
import SessionUIKit import SessionUtilitiesKit
public class PlaceholderIcon { 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 private let seed: Int
// Colour palette // Colour palette
private var colors: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color } private var colors: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color }
// MARK: - Initialization
init(seed: Int, colors: [UIColor]? = nil) { init(seed: Int, colors: [UIColor]? = nil) {
self.seed = seed self.seed = seed
if let colors = colors { self.colors = colors } 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() } if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { hash = seed.sha512() }
guard let number = Int(hash.substring(to: 12), radix: 16) else { 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) self.init(seed: 0, colors: colors)
return return
} }
@ -29,7 +38,53 @@ public class PlaceholderIcon {
self.init(seed: number, colors: colors) 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 color: UIColor = self.colors[seed % self.colors.count]
let base: CALayer = getTextLayer(with: diameter, color: color, text: text) let base: CALayer = getTextLayer(with: diameter, color: color, text: text)
base.masksToBounds = true base.masksToBounds = true

@ -3,10 +3,36 @@
import UIKit import UIKit
import GRDB import GRDB
import YYImage import YYImage
import SessionUIKit
import SessionMessagingKit
public final class ProfilePictureView: UIView { 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 { public enum Size {
case navigation case navigation
case message case message
@ -21,7 +47,7 @@ public final class ProfilePictureView: UIView {
} }
} }
var imageSize: CGFloat { public var imageSize: CGFloat {
switch self { switch self {
case .navigation, .message: return 26 case .navigation, .message: return 26
case .list: return 46 case .list: return 46
@ -29,7 +55,7 @@ public final class ProfilePictureView: UIView {
} }
} }
var multiImageSize: CGFloat { public var multiImageSize: CGFloat {
switch self { switch self {
case .navigation, .message: return 18 // Shouldn't be used case .navigation, .message: return 18 // Shouldn't be used
case .list: return 32 case .list: return 32
@ -44,32 +70,31 @@ public final class ProfilePictureView: UIView {
case .hero: return 24 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 none
case crown case crown
case rightPlus 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 { public var size: Size {
didSet { didSet {
widthConstraint.constant = (customWidth ?? size.viewSize) widthConstraint.constant = (customWidth ?? size.viewSize)
heightConstraint.constant = size.viewSize heightConstraint.constant = size.viewSize
profileIconTopConstraint.constant = size.iconVerticalInset
profileIconBottomConstraint.constant = -size.iconVerticalInset
profileIconBackgroundWidthConstraint.constant = size.iconSize profileIconBackgroundWidthConstraint.constant = size.iconSize
profileIconBackgroundHeightConstraint.constant = size.iconSize profileIconBackgroundHeightConstraint.constant = size.iconSize
additionalProfileIconTopConstraint.constant = size.iconVerticalInset
additionalProfileIconBottomConstraint.constant = -size.iconVerticalInset
additionalProfileIconBackgroundWidthConstraint.constant = size.iconSize additionalProfileIconBackgroundWidthConstraint.constant = size.iconSize
additionalProfileIconBackgroundHeightConstraint.constant = size.iconSize additionalProfileIconBackgroundHeightConstraint.constant = size.iconSize
@ -82,7 +107,18 @@ public final class ProfilePictureView: UIView {
self.widthConstraint.constant = (customWidth ?? self.size.viewSize) 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 // MARK: - Constraints
@ -108,6 +144,26 @@ public final class ProfilePictureView: UIView {
private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint! private var additionalProfileIconBackgroundRightAlignConstraint: NSLayoutConstraint!
private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint! private var additionalProfileIconBackgroundWidthConstraint: NSLayoutConstraint!
private var additionalProfileIconBackgroundHeightConstraint: 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 // MARK: - Components
@ -144,18 +200,7 @@ public final class ProfilePictureView: UIView {
result.clipsToBounds = true result.clipsToBounds = true
result.themeBackgroundColor = .primary result.themeBackgroundColor = .primary
result.themeBorderColor = .backgroundPrimary result.themeBorderColor = .backgroundPrimary
result.isHidden = true result.layer.borderWidth = 1
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.isHidden = true result.isHidden = true
return result 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)) super.init(frame: CGRect(x: 0, y: 0, width: size.viewSize, height: size.viewSize))
clipsToBounds = true
setUpViewHierarchy() setUpViewHierarchy()
} }
@ -251,23 +297,16 @@ public final class ProfilePictureView: UIView {
imageContainerView.addSubview(animatedImageView) imageContainerView.addSubview(animatedImageView)
additionalImageContainerView.addSubview(additionalImageView) additionalImageContainerView.addSubview(additionalImageView)
additionalImageContainerView.addSubview(additionalAnimatedImageView) additionalImageContainerView.addSubview(additionalAnimatedImageView)
additionalImageContainerView.addSubview(additionalProfilePlaceholderImageView)
imageView.pin(to: imageContainerView) // Activate the image edge constraints
animatedImageView.pin(to: imageContainerView) imageEdgeConstraints.forEach { $0.isActive = true }
additionalImageView.pin(to: additionalImageContainerView) additionalImageEdgeConstraints.forEach { $0.isActive = true }
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)
profileIconTopConstraint = profileIconImageView.pin( profileIconTopConstraint = profileIconImageView.pin(
.top, .top,
to: .top, to: .top,
of: profileIconBackgroundView, of: profileIconBackgroundView,
withInset: size.iconVerticalInset withInset: 0
) )
profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView) profileIconImageView.pin(.left, to: .left, of: profileIconBackgroundView)
profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView) profileIconImageView.pin(.right, to: .right, of: profileIconBackgroundView)
@ -275,7 +314,7 @@ public final class ProfilePictureView: UIView {
.bottom, .bottom,
to: .bottom, to: .bottom,
of: profileIconBackgroundView, of: profileIconBackgroundView,
withInset: -size.iconVerticalInset withInset: 0
) )
profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView) profileIconBackgroundLeftAlignConstraint = profileIconBackgroundView.pin(.leading, to: .leading, of: imageContainerView)
profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView) profileIconBackgroundRightAlignConstraint = profileIconBackgroundView.pin(.trailing, to: .trailing, of: imageContainerView)
@ -289,7 +328,7 @@ public final class ProfilePictureView: UIView {
.top, .top,
to: .top, to: .top,
of: additionalProfileIconBackgroundView, of: additionalProfileIconBackgroundView,
withInset: size.iconVerticalInset withInset: 0
) )
additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView) additionalProfileIconImageView.pin(.left, to: .left, of: additionalProfileIconBackgroundView)
additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView) additionalProfileIconImageView.pin(.right, to: .right, of: additionalProfileIconBackgroundView)
@ -297,7 +336,7 @@ public final class ProfilePictureView: UIView {
.bottom, .bottom,
to: .bottom, to: .bottom,
of: additionalProfileIconBackgroundView, of: additionalProfileIconBackgroundView,
withInset: -size.iconVerticalInset withInset: 0
) )
additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView) additionalProfileIconBackgroundLeftAlignConstraint = additionalProfileIconBackgroundView.pin(.leading, to: .leading, of: additionalImageContainerView)
additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView) additionalProfileIconBackgroundRightAlignConstraint = additionalProfileIconBackgroundView.pin(.trailing, to: .trailing, of: additionalImageContainerView)
@ -314,8 +353,10 @@ public final class ProfilePictureView: UIView {
icon: ProfileIcon, icon: ProfileIcon,
imageView: UIImageView, imageView: UIImageView,
backgroundView: UIView, backgroundView: UIView,
topConstraint: NSLayoutConstraint,
leftAlignConstraint: NSLayoutConstraint, leftAlignConstraint: NSLayoutConstraint,
rightAlignConstraint: NSLayoutConstraint rightAlignConstraint: NSLayoutConstraint,
bottomConstraint: NSLayoutConstraint
) { ) {
backgroundView.isHidden = (icon == .none) backgroundView.isHidden = (icon == .none)
leftAlignConstraint.isActive = ( leftAlignConstraint.isActive = (
@ -325,6 +366,8 @@ public final class ProfilePictureView: UIView {
rightAlignConstraint.isActive = ( rightAlignConstraint.isActive = (
icon == .rightPlus icon == .rightPlus
) )
topConstraint.constant = icon.iconVerticalInset(for: size)
bottomConstraint.constant = -icon.iconVerticalInset(for: size)
switch icon { switch icon {
case .none: imageView.image = nil case .none: imageView.image = nil
@ -345,201 +388,159 @@ public final class ProfilePictureView: UIView {
} }
case .rightPlus: case .rightPlus:
imageView.image = UIImage(systemName: "plus") imageView.image = UIImage(
systemName: "plus",
withConfiguration: UIImage.SymbolConfiguration(weight: .semibold)
)
imageView.themeTintColor = .black imageView.themeTintColor = .black
backgroundView.themeBackgroundColor = .primary backgroundView.themeBackgroundColorForced = .primary(.green)
} }
} }
// 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
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( public func update(
publicKey: String = "", _ info: Info,
profile: Profile? = nil, additionalInfo: Info? = nil
icon: ProfileIcon = .none,
additionalProfile: Profile? = nil,
additionalIcon: ProfileIcon = .none,
threadVariant: SessionThread.Variant,
openGroupProfilePictureData: Data? = nil,
useFallbackPicture: Bool = false,
showMultiAvatarForClosedGroup: Bool = false
) { ) {
AssertIsOnMainThread() prepareForReuse()
// Sort out the profile icon first // Sort out the icon first
updateIconView( updateIconView(
icon: icon, icon: info.icon,
imageView: profileIconImageView, imageView: profileIconImageView,
backgroundView: profileIconBackgroundView, backgroundView: profileIconBackgroundView,
topConstraint: profileIconTopConstraint,
leftAlignConstraint: profileIconBackgroundLeftAlignConstraint, leftAlignConstraint: profileIconBackgroundLeftAlignConstraint,
rightAlignConstraint: profileIconBackgroundRightAlignConstraint rightAlignConstraint: profileIconBackgroundRightAlignConstraint,
bottomConstraint: profileIconBottomConstraint
) )
guard !useFallbackPicture else { // Populate the main imageView
switch self.size { switch info.imageData?.guessedImageFormat {
case .navigation, .message: imageView.image = #imageLiteral(resourceName: "SessionWhite16") case .gif, .webp: animatedImageView.image = info.imageData.map { YYImage(data: $0) }
case .list: imageView.image = #imageLiteral(resourceName: "SessionWhite24") default:
case .hero: imageView.image = #imageLiteral(resourceName: "SessionWhite40") imageView.image = info.imageData
} .map {
guard info.renderingMode != .automatic else { return UIImage(data: $0) }
imageView.contentMode = .center
imageView.isHidden = false return UIImage(data: $0)?.withRenderingMode(info.renderingMode)
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
return
} }
guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return }
func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) { imageView.themeTintColor = info.themeTintColor
if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) { imageView.isHidden = (imageView.image == nil)
let format: ImageFormat = profileData.guessedImageFormat animatedImageView.themeTintColor = info.themeTintColor
animatedImageView.isHidden = (animatedImageView.image == nil)
let image: UIImage? = (format == .gif || format == .webp ? imageContainerView.themeBackgroundColor = info.backgroundColor
nil : imageContainerView.themeBackgroundColorForced = info.forcedBackgroundColor
UIImage(data: profileData) profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2)
) imageEdgeConstraints.enumerated().forEach { index, constraint in
let animatedImage: YYImage? = (format != .gif && format != .webp ? switch index % 4 {
nil : case 0: constraint.constant = info.inset.top
YYImage(data: profileData) case 1: constraint.constant = info.inset.left
) case 2: constraint.constant = -info.inset.bottom
case 3: constraint.constant = -info.inset.right
if image != nil || animatedImage != nil { default: break
return (image, animatedImage, true)
}
} }
return (
Identicon.generatePlaceholderIcon(
seed: publicKey,
text: (profile?.displayName(for: threadVariant))
.defaulting(to: publicKey),
size: size
),
nil,
false
)
} }
// Calulate the sizes (and set the additional image content) // Check if there is a second image (if not then set the size and finish)
let targetSize: CGFloat guard let additionalInfo: Info = additionalInfo else {
imageViewWidthConstraint.constant = size.imageSize
switch (threadVariant, showMultiAvatarForClosedGroup) { imageViewHeightConstraint.constant = size.imageSize
case (.closedGroup, true): imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.imageSize / 2) : 0)
targetSize = self.size.multiImageSize return
additionalImageContainerView.isHidden = false }
imageViewTopConstraint.isActive = true
imageViewLeadingConstraint.isActive = true // Sort out the additional icon first
imageViewCenterXConstraint.isActive = false updateIconView(
imageViewCenterYConstraint.isActive = false icon: additionalInfo.icon,
imageView: additionalProfileIconImageView,
// Sort out the additinoal profile icon if needed backgroundView: additionalProfileIconBackgroundView,
updateIconView( topConstraint: additionalProfileIconTopConstraint,
icon: additionalIcon, leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint,
imageView: additionalProfileIconImageView, rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint,
backgroundView: additionalProfileIconBackgroundView, bottomConstraint: additionalProfileIconBottomConstraint
leftAlignConstraint: additionalProfileIconBackgroundLeftAlignConstraint, )
rightAlignConstraint: additionalProfileIconBackgroundRightAlignConstraint
) // Set the additional image content and reposition the image views correctly
switch additionalInfo.imageData?.guessedImageFormat {
if let additionalProfile: Profile = additionalProfile { case .gif, .webp: additionalAnimatedImageView.image = additionalInfo.imageData.map { YYImage(data: $0) }
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
}
default: default:
targetSize = self.size.imageSize additionalImageView.image = additionalInfo.imageData
.map {
additionalImageContainerView.isHidden = true guard additionalInfo.renderingMode != .automatic else { return UIImage(data: $0) }
additionalProfileIconBackgroundView.isHidden = true
additionalImageView.image = nil return UIImage(data: $0)?.withRenderingMode(additionalInfo.renderingMode)
additionalImageView.isHidden = true }
additionalAnimatedImageView.image = nil
additionalAnimatedImageView.isHidden = true
additionalProfilePlaceholderImageView.isHidden = true
imageViewTopConstraint.isActive = false
imageViewLeadingConstraint.isActive = false
imageViewCenterXConstraint.isActive = true
imageViewCenterYConstraint.isActive = true
} }
// Set the image additionalImageView.themeTintColor = additionalInfo.themeTintColor
if let openGroupProfilePictureData: Data = openGroupProfilePictureData { additionalImageView.isHidden = (additionalImageView.image == nil)
let format: ImageFormat = openGroupProfilePictureData.guessedImageFormat additionalAnimatedImageView.themeTintColor = additionalInfo.themeTintColor
additionalAnimatedImageView.isHidden = (additionalAnimatedImageView.image == nil)
let image: UIImage? = (format == .gif || format == .webp ? additionalImageContainerView.isHidden = false
nil :
UIImage(data: openGroupProfilePictureData) switch (info.backgroundColor, info.forcedBackgroundColor) {
) case (_, .some(let color)): additionalImageContainerView.themeBackgroundColorForced = color
let animatedImage: YYImage? = (format != .gif && format != .webp ? case (.some(let color), _): additionalImageContainerView.themeBackgroundColor = color
nil : default: additionalImageContainerView.themeBackgroundColor = .primary
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( additionalImageEdgeConstraints.enumerated().forEach { index, constraint in
of: targetSize, switch index % 4 {
for: publicKey, case 0: constraint.constant = additionalInfo.inset.top
profile: profile case 1: constraint.constant = additionalInfo.inset.left
) case 2: constraint.constant = -additionalInfo.inset.bottom
imageView.image = image case 3: constraint.constant = -additionalInfo.inset.right
animatedImageView.image = animatedImage default: break
imageView.isHidden = (animatedImage != nil) }
animatedImageView.isHidden = (animatedImage == nil)
hasTappableProfilePicture = isTappable
} }
imageView.contentMode = .scaleAspectFill imageViewTopConstraint.isActive = true
animatedImageView.contentMode = .scaleAspectFill imageViewLeadingConstraint.isActive = true
imageContainerView.themeBackgroundColor = .backgroundSecondary imageViewCenterXConstraint.isActive = false
imageViewWidthConstraint.constant = targetSize imageViewCenterYConstraint.isActive = false
imageViewHeightConstraint.constant = targetSize
imageContainerView.layer.cornerRadius = (targetSize / 2) imageViewWidthConstraint.constant = size.multiImageSize
additionalImageViewWidthConstraint.constant = targetSize imageViewHeightConstraint.constant = size.multiImageSize
additionalImageViewHeightConstraint.constant = targetSize imageContainerView.layer.cornerRadius = (imageContainerView.clipsToBounds ? (size.multiImageSize / 2) : 0)
additionalImageContainerView.layer.cornerRadius = (targetSize / 2) additionalImageViewWidthConstraint.constant = size.multiImageSize
profileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) additionalImageViewHeightConstraint.constant = size.multiImageSize
additionalImageContainerView.layer.cornerRadius = (additionalImageContainerView.clipsToBounds ?
(size.multiImageSize / 2) :
0
)
additionalProfileIconBackgroundView.layer.cornerRadius = (size.iconSize / 2) 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…
Cancel
Save