From ae84528dc32f7f875e668ba87172508c5bf04ad1 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 24 Sep 2018 14:37:23 -0400 Subject: [PATCH 1/6] Update avatar colors; add shaded conversation color constants, modify color picker to be color-name-based, not color-based, use shaded conversation colors, remove JSQ. --- Signal.xcodeproj/project.pbxproj | 48 ---- .../ColorPickerViewController.swift | 33 ++- .../ConversationView/Cells/OWSMessageCell.m | 9 +- .../OWSConversationSettingsViewController.m | 4 +- .../JSQMessagesViewController/JSQMVC-LICENSE | 20 -- .../JSQMVC-README.md | 104 --------- .../JSQMVC-SIGNAL.md | 7 - .../JSQMessageAvatarImageDataSource.h | 63 ------ .../JSQMessagesAvatarImage.h | 86 ------- .../JSQMessagesAvatarImage.m | 78 ------- .../JSQMessagesAvatarImageFactory.h | 99 -------- .../JSQMessagesAvatarImageFactory.m | 159 ------------- .../UIColor+JSQMessages.h | 56 ----- .../UIColor+JSQMessages.m | 90 -------- .../ViewModels/ContactShareViewModel.swift | 2 +- SignalMessaging/Views/ContactCellView.m | 5 +- SignalMessaging/categories/UIColor+OWS.h | 8 +- SignalMessaging/categories/UIColor+OWS.m | 38 +++- SignalMessaging/utils/ConversationStyle.swift | 3 +- SignalMessaging/utils/OWSAvatarBuilder.h | 25 +- SignalMessaging/utils/OWSAvatarBuilder.m | 213 +++++++++++++++--- .../utils/OWSContactAvatarBuilder.h | 2 +- .../utils/OWSContactAvatarBuilder.m | 48 ++-- SignalMessaging/utils/OWSGroupAvatarBuilder.m | 2 +- 24 files changed, 294 insertions(+), 908 deletions(-) delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-LICENSE delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-README.md delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-SIGNAL.md delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMessageAvatarImageDataSource.h delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.h delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.m delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.h delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.m delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.h delete mode 100644 SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 6b70da5b1..b19558679 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -42,16 +42,6 @@ 340FC8CD20518C77007AEB0F /* OWSBackupJob.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8CC20518C76007AEB0F /* OWSBackupJob.m */; }; 340FC8D0205BF2FA007AEB0F /* OWSBackupIO.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC8CE205BF2FA007AEB0F /* OWSBackupIO.m */; }; 341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */ = {isa = PBXBuildFile; fileRef = 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */; }; - 3421980F21061A0700C57195 /* UIColor+JSQMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 3421980521061A0600C57195 /* UIColor+JSQMessages.m */; }; - 3421981021061A0700C57195 /* JSQMessagesAvatarImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 3421980621061A0600C57195 /* JSQMessagesAvatarImage.m */; }; - 3421981121061A0700C57195 /* JSQMessagesAvatarImageFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 3421980721061A0600C57195 /* JSQMessagesAvatarImageFactory.h */; }; - 3421981221061A0700C57195 /* JSQMVC-LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 3421980821061A0600C57195 /* JSQMVC-LICENSE */; }; - 3421981321061A0700C57195 /* JSQMessagesAvatarImageFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 3421980921061A0700C57195 /* JSQMessagesAvatarImageFactory.m */; }; - 3421981421061A0700C57195 /* JSQMessagesAvatarImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 3421980A21061A0700C57195 /* JSQMessagesAvatarImage.h */; }; - 3421981521061A0700C57195 /* UIColor+JSQMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 3421980B21061A0700C57195 /* UIColor+JSQMessages.h */; }; - 3421981621061A0700C57195 /* JSQMVC-README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3421980C21061A0700C57195 /* JSQMVC-README.md */; }; - 3421981721061A0700C57195 /* JSQMVC-SIGNAL.md in Resources */ = {isa = PBXBuildFile; fileRef = 3421980D21061A0700C57195 /* JSQMVC-SIGNAL.md */; }; - 3421981821061A0700C57195 /* JSQMessageAvatarImageDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3421980E21061A0700C57195 /* JSQMessageAvatarImageDataSource.h */; }; 3421981C21061D2E00C57195 /* ByteParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3421981B21061D2E00C57195 /* ByteParserTest.swift */; }; 34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */; }; 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; }; @@ -662,16 +652,6 @@ 341458471FBE11C4005ABCF9 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = translations/fa.lproj/Localizable.strings; sourceTree = ""; }; 341F2C0D1F2B8AE700D07D6B /* DebugUIMisc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMisc.h; sourceTree = ""; }; 341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMisc.m; sourceTree = ""; }; - 3421980521061A0600C57195 /* UIColor+JSQMessages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIColor+JSQMessages.m"; sourceTree = ""; }; - 3421980621061A0600C57195 /* JSQMessagesAvatarImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImage.m; sourceTree = ""; }; - 3421980721061A0600C57195 /* JSQMessagesAvatarImageFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesAvatarImageFactory.h; sourceTree = ""; }; - 3421980821061A0600C57195 /* JSQMVC-LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "JSQMVC-LICENSE"; sourceTree = ""; }; - 3421980921061A0700C57195 /* JSQMessagesAvatarImageFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesAvatarImageFactory.m; sourceTree = ""; }; - 3421980A21061A0700C57195 /* JSQMessagesAvatarImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesAvatarImage.h; sourceTree = ""; }; - 3421980B21061A0700C57195 /* UIColor+JSQMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+JSQMessages.h"; sourceTree = ""; }; - 3421980C21061A0700C57195 /* JSQMVC-README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "JSQMVC-README.md"; sourceTree = ""; }; - 3421980D21061A0700C57195 /* JSQMVC-SIGNAL.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "JSQMVC-SIGNAL.md"; sourceTree = ""; }; - 3421980E21061A0700C57195 /* JSQMessageAvatarImageDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessageAvatarImageDataSource.h; sourceTree = ""; }; 3421981B21061D2E00C57195 /* ByteParserTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ByteParserTest.swift; sourceTree = ""; }; 34277A5C20751BDC006049F2 /* OWSQuotedMessageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQuotedMessageView.m; sourceTree = ""; }; 34277A5D20751BDC006049F2 /* OWSQuotedMessageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQuotedMessageView.h; sourceTree = ""; }; @@ -1470,28 +1450,10 @@ 34219803210619D300C57195 /* Libraries */ = { isa = PBXGroup; children = ( - 3421980421061A0600C57195 /* JSQMessagesViewController */, ); path = Libraries; sourceTree = ""; }; - 3421980421061A0600C57195 /* JSQMessagesViewController */ = { - isa = PBXGroup; - children = ( - 3421980521061A0600C57195 /* UIColor+JSQMessages.m */, - 3421980621061A0600C57195 /* JSQMessagesAvatarImage.m */, - 3421980721061A0600C57195 /* JSQMessagesAvatarImageFactory.h */, - 3421980821061A0600C57195 /* JSQMVC-LICENSE */, - 3421980921061A0700C57195 /* JSQMessagesAvatarImageFactory.m */, - 3421980A21061A0700C57195 /* JSQMessagesAvatarImage.h */, - 3421980B21061A0700C57195 /* UIColor+JSQMessages.h */, - 3421980C21061A0700C57195 /* JSQMVC-README.md */, - 3421980D21061A0700C57195 /* JSQMVC-SIGNAL.md */, - 3421980E21061A0700C57195 /* JSQMessageAvatarImageDataSource.h */, - ); - path = JSQMessagesViewController; - sourceTree = ""; - }; 34330A581E7875FB00DF2FB9 /* Fonts */ = { isa = PBXGroup; children = ( @@ -2527,7 +2489,6 @@ 346129711FD1D74C00532771 /* SignalKeyingStorage.h in Headers */, 34AC0A20211B39EA00997B47 /* ThreadViewHelper.h in Headers */, 34AC09DE211B39B100997B47 /* OWSNavigationController.h in Headers */, - 3421981121061A0700C57195 /* JSQMessagesAvatarImageFactory.h in Headers */, 34C82E5120F8E1F300E9688D /* Theme.h in Headers */, 34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */, 450998691FD8C10200D89EB3 /* AttachmentSharing.h in Headers */, @@ -2535,7 +2496,6 @@ 34AC09EC211B39B100997B47 /* OWSTableViewController.h in Headers */, 451F8A3C1FD71392005CB9DA /* UIUtil.h in Headers */, 346129D61FD20ADC00532771 /* UIViewController+OWS.h in Headers */, - 3421981421061A0700C57195 /* JSQMessagesAvatarImage.h in Headers */, 34612A061FD7238600532771 /* OWSContactsSyncing.h in Headers */, 34480B571FD0A7A400BC14EF /* OWSScrubbingLogFormatter.h in Headers */, 346129FC1FD5F31400532771 /* OWS101ExistingUsersBlockOnIdentityChange.h in Headers */, @@ -2543,11 +2503,9 @@ 451F8A491FD715CF005CB9DA /* OWSAvatarBuilder.h in Headers */, 346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */, 34B6D27420F664C900765BE2 /* OWSUnreadIndicator.h in Headers */, - 3421981821061A0700C57195 /* JSQMessageAvatarImageDataSource.h in Headers */, 34AC09E4211B39B100997B47 /* ScreenLockViewController.h in Headers */, 34AC09F1211B39B100997B47 /* LockInteractionController.h in Headers */, 346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */, - 3421981521061A0700C57195 /* UIColor+JSQMessages.h in Headers */, 342950892124CB0A0000B063 /* OWSSearchBar.h in Headers */, 346129FA1FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.h in Headers */, 346129E21FD5C0BE00532771 /* VersionMigrations.h in Headers */, @@ -2837,9 +2795,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3421981621061A0700C57195 /* JSQMVC-README.md in Resources */, - 3421981721061A0700C57195 /* JSQMVC-SIGNAL.md in Resources */, - 3421981221061A0700C57195 /* JSQMVC-LICENSE in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3181,7 +3136,6 @@ 34AC09EE211B39B100997B47 /* EditContactShareNameViewController.swift in Sources */, 346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */, 45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */, - 3421980F21061A0700C57195 /* UIColor+JSQMessages.m in Sources */, 4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */, 450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */, 347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */, @@ -3190,7 +3144,6 @@ 34074F61203D0CBE004596AE /* OWSSounds.m in Sources */, 346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */, 342950832124C9750000B063 /* OWSTextView.m in Sources */, - 3421981321061A0700C57195 /* JSQMessagesAvatarImageFactory.m in Sources */, 452EC6E1205FF5DC000E787C /* Bench.swift in Sources */, 342950882124CB0A0000B063 /* OWSSearchBar.m in Sources */, 342950822124C9750000B063 /* OWSTextField.m in Sources */, @@ -3259,7 +3212,6 @@ 34AC0A18211B39EA00997B47 /* TappableStackView.swift in Sources */, 34B6D27520F664C900765BE2 /* OWSUnreadIndicator.m in Sources */, 346129A61FD1F09100532771 /* OWSContactsManager.m in Sources */, - 3421981021061A0700C57195 /* JSQMessagesAvatarImage.m in Sources */, 4541B71D209D3B7A0008608F /* ContactShareViewModel.swift in Sources */, 4598198F204E2F28009414F2 /* OWS108CallLoggingPreference.m in Sources */, 34AC09F3211B39B100997B47 /* NewNonContactConversationViewController.m in Sources */, diff --git a/Signal/src/ViewControllers/ColorPickerViewController.swift b/Signal/src/ViewControllers/ColorPickerViewController.swift index 972a22991..dcb6f99a0 100644 --- a/Signal/src/ViewControllers/ColorPickerViewController.swift +++ b/Signal/src/ViewControllers/ColorPickerViewController.swift @@ -46,7 +46,7 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke private let pickerView: UIPickerView private let thread: TSThread - private let colors: [UIColor] + private let colorNames: [String] @objc public weak var delegate: ColorPickerDelegate? @@ -54,7 +54,7 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke required init(thread: TSThread) { self.thread = thread self.pickerView = UIPickerView() - self.colors = UIColor.ows_conversationColors + self.colorNames = UIColor.ows_conversationColorNames super.init(nibName: nil, bundle: nil) @@ -83,8 +83,7 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke super.viewDidLoad() if let colorName = thread.conversationColorName, - let currentColor = UIColor.ows_conversationColor(colorName: colorName), - let index = colors.index(of: currentColor) { + let index = colorNames.index(of: colorName) { pickerView.selectRow(index, inComponent: 0, animated: false) } } @@ -96,7 +95,7 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke } public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return self.colors.count + return self.colorNames.count } // MARK: UIPickerViewDelegate @@ -107,34 +106,32 @@ class ColorPickerViewController: UIViewController, UIPickerViewDelegate, UIPicke } public func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - guard let color = colors[safe: row] else { + guard let colorName = colorNames[safe: row] else { owsFailDebug("color was unexpectedly nil") return ColorView(color: .white) } - + guard let color = UIColor.ows_conversationColor(colorName: colorName, + isShaded: Theme.isDarkThemeEnabled) else { + owsFailDebug("unknown color name") + return ColorView(color: .white) + } return ColorView(color: color) } // MARK: Actions - var currentColor: UIColor { + var currentColorName: String { let index = pickerView.selectedRow(inComponent: 0) - guard let color = self.colors[safe: index] else { + guard let colorName = colorNames[safe: index] else { owsFailDebug("index was unexpectedly nil") - return UIColor.white + return UIColor.ows_defaultConversationColorName() } - - return color + return colorName } @objc public func didTapSave() { - guard let colorName = UIColor.ows_conversationColorName(color: self.currentColor) else { - owsFailDebug("colorName was unexpectedly nil") - self.delegate?.colorPickerDidCancel(self) - return - } - + let colorName = self.currentColorName self.delegate?.colorPicker(self, didPickColorName: colorName) } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index d7c924c66..219abf3d4 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -269,11 +269,10 @@ NS_ASSUME_NONNULL_BEGIN } TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction; - OWSAvatarBuilder *avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:incomingMessage.authorId - color:self.conversationStyle.primaryColor - diameter:self.avatarSize - contactsManager:contactsManager]; - self.avatarView.image = [avatarBuilder build]; + TSThread *authorThread = [TSContactThread getOrCreateThreadWithContactId:incomingMessage.authorId]; + UIImage *_Nullable authorAvatarImage = + [OWSAvatarBuilder buildImageForThread:authorThread diameter:self.avatarSize contactsManager:contactsManager]; + self.avatarView.image = authorAvatarImage; [self.contentView addSubview:self.avatarView]; [[NSNotificationCenter defaultCenter] addObserver:self diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index 72361f334..d601e1345 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -289,7 +289,9 @@ const CGFloat kIconViewLength = 24; [mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ NSString *colorName = self.thread.conversationColorName; - UIColor *currentColor = [UIColor ows_conversationColorForColorName:colorName]; + UIColor *currentColor = + [UIColor ows_conversationColorForColorName:colorName + isShaded:Theme.isDarkThemeEnabled]; NSString *title = NSLocalizedString(@"CONVERSATION_SETTINGS_CONVERSATION_COLOR", @"Label for table cell which leads to picking a new conversation color"); return [weakSelf disclosureCellWithName:title iconColor:currentColor]; diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-LICENSE b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-LICENSE deleted file mode 100644 index e7a4b01d5..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ - -MIT License -Copyright (c) 2013-present Jesse Squires - -http://www.jessesquires.com - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-README.md b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-README.md deleted file mode 100644 index 4d699c3ab..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-README.md +++ /dev/null @@ -1,104 +0,0 @@ -![JSQMessagesViewController banner](https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Assets/jsq_messages_banner.png) - -[![Build Status](https://secure.travis-ci.org/jessesquires/JSQMessagesViewController.svg)](https://travis-ci.org/jessesquires/JSQMessagesViewController) [![Version Status](https://img.shields.io/cocoapods/v/JSQMessagesViewController.svg)][podLink] [![license MIT](https://img.shields.io/cocoapods/l/JSQMessagesViewController.svg)][mitLink] [![codecov](https://codecov.io/gh/jessesquires/JSQMessagesViewController/branch/develop/graph/badge.svg)](https://codecov.io/gh/jessesquires/JSQMessagesViewController) [![Platform](https://img.shields.io/cocoapods/p/JSQMessagesViewController.svg)][docsLink] - -![Screenshot0][img0]    ![Screenshot1][img1]    - -![Screenshot2][img2]    ![Screenshot3][img3] - -> More screenshots available at [CocoaControls](https://www.cocoacontrols.com/controls/jsqmessagesviewcontroller) - -## Features - -See the [website](http://www.jessesquires.com/JSQMessagesViewController/) for the list of features. - -## Design Goals - -- Closely mimic the [iOS Messages](http://www.apple.com/ios/messages/) style and behavior -- [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) design -- Easy customization and extension for clients - -## Dependencies - -* [JSQSystemSoundPlayer][playerLink] - -## Requirements - -* iOS 7.0+ -* ARC - -## Installation - -### [CocoaPods](https://cocoapods.org/) (recommended) - -````ruby -# For latest release in cocoapods -pod 'JSQMessagesViewController' - -# Latest on develop -pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController.git', :branch => 'develop' - -# For version 5.3.2 -pod 'JSQMessagesViewController', :git => 'https://github.com/jessesquires/JSQMessagesViewController', :branch => 'version_5.3.2_patch' -```` - -## Getting Started - -See the [Getting Started](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/getting_started.md) guide! - -## Questions & Help - -* Review the [FAQ](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/faq.md). -* Search issues for previous and current [questions](https://github.com/jessesquires/JSQMessagesViewController/issues?utf8=✓&q=label%3A%22questions+%26+help%22+). *Do not open duplicates.* -* [StackOverflow](http://stackoverflow.com/questions/tagged/jsqmessagesviewcontroller) is often the most appropriate place for questions and help. We have our own tag, `jsqmessagesviewcontroller`. -* See the [Migration Guide](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/migration.md) for migrating between major versions of the library. -* **Only ask questions that are _specific_ to this library.** -* **Please avoid emailing questions.** I prefer to keep questions and their answers open-source. - -## Documentation - -Read the docs, [available here][docsLink] via [@CocoaDocs](https://twitter.com/CocoaDocs). - -## Core team - -- Jesse Squires ([**@jesse_squires**](https://twitter.com/jesse_squires)) -- Harlan Haskans ([**@harlanhaskins**](https://github.com/harlanhaskins)) -- Eli Burke ([**@eliburke**](https://github.com/eliburke)) - -## Contributing - -Please follow these sweet [contribution guidelines](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/.github/CONTRIBUTING.md). - -> **Interested in becoming a core contributor with push access? See our [onboarding guide](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/contributor_onboarding.md) for details.** - -## Donate - -Support the development of this **free** library! **[Donate](https://cash.me/$jsq)** via [Square Cash](https://cash.me/). - -## Credits - -* Created and maintained by [**@jesse_squires**](https://twitter.com/jesse_squires). -* Many thanks to [**the contributors**](https://github.com/jessesquires/JSQMessagesViewController/graphs/contributors) of this project. -* iOS assets extracted using [**@0xced**](https://github.com/0xced) / [iOS-Artwork-Extractor](https://github.com/0xced/iOS-Artwork-Extractor). - -## Apps using this library - -According to [CocoaPods stats](https://cocoapods.org/pods/JSQMessagesViewController), over **9,000 apps** are using `JSQMessagesViewController`. [Here are the ones](https://github.com/jessesquires/JSQMessagesViewController/blob/develop/Documentation/apps_using_this_library.md) that we know about. Please submit a [pull request](https://github.com/jessesquires/JSQMessagesViewController/compare) to add your app! :smile: - -## License - -`JSQMessagesViewController` is released under an [MIT License][mitLink]. See `LICENSE` for details. - ->**Copyright © 2013-present Jesse Squires.** - -*Please provide attribution, it is greatly appreciated.* - -[docsLink]:http://cocoadocs.org/docsets/JSQMessagesViewController/ -[podLink]:https://cocoapods.org/pods/JSQMessagesViewController -[mitLink]:http://opensource.org/licenses/MIT -[playerLink]:https://github.com/jessesquires/JSQSystemSoundPlayer - -[img0]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot0.png -[img1]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot1.png -[img2]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot2.png -[img3]:https://raw.githubusercontent.com/jessesquires/JSQMessagesViewController/develop/Screenshots/screenshot3.png diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-SIGNAL.md b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-SIGNAL.md deleted file mode 100644 index f9cf9824f..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMVC-SIGNAL.md +++ /dev/null @@ -1,7 +0,0 @@ -The files in this directory are drawn from JSQMessagesViewController, created by Jesse Squires. These files have been pulled into the Signal-iOS repository with his permission. - -https://github.com/jessesquires/JSQMessagesViewController - -These files are available under the MIT License. See JSQMVC-LICENSE. - - diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessageAvatarImageDataSource.h b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessageAvatarImageDataSource.h deleted file mode 100644 index fc31072b1..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessageAvatarImageDataSource.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import -#import - -/** - * The `JSQMessageAvatarImageDataSource` protocol defines the common interface through which - * a `JSQMessagesViewController` and `JSQMessagesCollectionView` interact with avatar image model objects. - * - * It declares the required and optional methods that a class must implement so that instances - * of that class can be display properly within a `JSQMessagesCollectionViewCell`. - * - * A concrete class that conforms to this protocol is provided in the library. See `JSQMessagesAvatarImage`. - * - * @see JSQMessagesAvatarImage. - */ -@protocol JSQMessageAvatarImageDataSource - -@required - -/** - * @return The avatar image for a regular display state. - * - * @discussion You may return `nil` from this method while the image is being downloaded. - */ -- (UIImage *)avatarImage; - -/** - * @return The avatar image for a highlighted display state. - * - * @discussion You may return `nil` from this method if this does not apply. - */ -- (UIImage *)avatarHighlightedImage; - -/** - * @return A placeholder avatar image to be displayed if avatarImage is not yet available, or `nil`. - * For example, if avatarImage needs to be downloaded, this placeholder image - * will be used until avatarImage is not `nil`. - * - * @discussion If you do not need support for a placeholder image, that is, your images - * are stored locally on the device, then you may simply return the same value as avatarImage here. - * - * @warning You must not return `nil` from this method. - */ -- (UIImage *)avatarPlaceholderImage; - -@end diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.h b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.h deleted file mode 100644 index 6d40fb4e8..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.h +++ /dev/null @@ -1,86 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import -#import - -#import "JSQMessageAvatarImageDataSource.h" - -/** - * A `JSQMessagesAvatarImage` model object represents an avatar image. - * This is a concrete class that implements the `JSQMessageAvatarImageDataSource` protocol. - * It contains a regular avatar image, a highlighted avatar image, and a placeholder avatar image. - * - * @see JSQMessagesAvatarImageFactory. - */ -@interface JSQMessagesAvatarImage : NSObject - -/** - * The avatar image for a regular display state. - */ -@property (nonatomic, strong) UIImage *avatarImage; - -/** - * The avatar image for a highlighted display state. - */ -@property (nonatomic, strong) UIImage *avatarHighlightedImage; - -/** - * Returns the placeholder image for an avatar to display if avatarImage is `nil`. - */ -@property (nonatomic, strong, readonly) UIImage *avatarPlaceholderImage; - -/** - * Initializes and returns an avatar image object having the specified image. - * - * @param image The image for this avatar image. This image will be used for the all of the following - * properties: avatarImage, avatarHighlightedImage, avatarPlaceholderImage; - * This value must not be `nil`. - * - * @return An initialized `JSQMessagesAvatarImage` object if successful, `nil` otherwise. - */ -+ (instancetype)avatarWithImage:(UIImage *)image; - -/** - * Initializes and returns an avatar image object having the specified placeholder image. - * - * @param placeholderImage The placeholder image for this avatar image. This value must not be `nil`. - * - * @return An initialized `JSQMessagesAvatarImage` object if successful, `nil` otherwise. - */ -+ (instancetype)avatarImageWithPlaceholder:(UIImage *)placeholderImage; - -/** - * Initializes and returns an avatar image object having the specified regular, highlighed, and placeholder images. - * - * @param avatarImage The avatar image for a regular display state. - * @param highlightedImage The avatar image for a highlighted display state. - * @param placeholderImage The placeholder image for this avatar image. This value must not be `nil`. - * - * @return An initialized `JSQMessagesAvatarImage` object if successful, `nil` otherwise. - */ -- (instancetype)initWithAvatarImage:(UIImage *)avatarImage - highlightedImage:(UIImage *)highlightedImage - placeholderImage:(UIImage *)placeholderImage NS_DESIGNATED_INITIALIZER; - -/** - * Not a valid initializer. - */ -- (id)init NS_UNAVAILABLE; - -@end diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.m b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.m deleted file mode 100644 index 20828611a..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImage.m +++ /dev/null @@ -1,78 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import "JSQMessagesAvatarImage.h" - -@implementation JSQMessagesAvatarImage - -#pragma mark - Initialization - -+ (instancetype)avatarWithImage:(UIImage *)image -{ - NSParameterAssert(image != nil); - - return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:image - highlightedImage:image - placeholderImage:image]; -} - -+ (instancetype)avatarImageWithPlaceholder:(UIImage *)placeholderImage -{ - return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:nil - highlightedImage:nil - placeholderImage:placeholderImage]; -} - -- (instancetype)initWithAvatarImage:(UIImage *)avatarImage - highlightedImage:(UIImage *)highlightedImage - placeholderImage:(UIImage *)placeholderImage -{ - NSParameterAssert(placeholderImage != nil); - - self = [super init]; - if (self) { - _avatarImage = avatarImage; - _avatarHighlightedImage = highlightedImage; - _avatarPlaceholderImage = placeholderImage; - } - return self; -} - -#pragma mark - NSObject - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: avatarImage=%@, avatarHighlightedImage=%@, avatarPlaceholderImage=%@>", - [self class], self.avatarImage, self.avatarHighlightedImage, self.avatarPlaceholderImage]; -} - -- (id)debugQuickLookObject -{ - return [[UIImageView alloc] initWithImage:self.avatarImage ?: self.avatarPlaceholderImage]; -} - -#pragma mark - NSCopying - -- (instancetype)copyWithZone:(NSZone *)zone -{ - return [[[self class] allocWithZone:zone] initWithAvatarImage:[UIImage imageWithCGImage:self.avatarImage.CGImage] - highlightedImage:[UIImage imageWithCGImage:self.avatarHighlightedImage.CGImage] - placeholderImage:[UIImage imageWithCGImage:self.avatarPlaceholderImage.CGImage]]; -} - -@end diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.h b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.h deleted file mode 100644 index 759cd5388..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.h +++ /dev/null @@ -1,99 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import -#import - -#import "JSQMessagesAvatarImage.h" - -/** - * `JSQMessagesAvatarImageFactory` is a factory that provides a means for creating and styling - * `JSQMessagesAvatarImage` objects to be displayed in a `JSQMessagesCollectionViewCell` of a `JSQMessagesCollectionView`. - */ -@interface JSQMessagesAvatarImageFactory : NSObject - -/** -* Creates and returns a `JSQMessagesAvatarImage` object with the specified placeholderImage that is -* cropped to a circle of the given diameter. -* -* @param placeholderImage An image object that represents a placeholder avatar image. This value must not be `nil`. -* @param diameter An integer value specifying the diameter size of the avatar in points. This value must be greater than `0`. -* -* @return An initialized `JSQMessagesAvatarImage` object if created successfully, `nil` otherwise. -*/ -+ (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage diameter:(NSUInteger)diameter; - -/** - * Creates and returns a `JSQMessagesAvatarImage` object with the specified image that is - * cropped to a circle of the given diameter and used for the `avatarImage` and `avatarPlaceholderImage` properties - * of the returned `JSQMessagesAvatarImage` object. This image is then copied and has a transparent black mask applied to it, - * which is used for the `avatarHighlightedImage` property of the returned `JSQMessagesAvatarImage` object. - * - * @param image An image object that represents an avatar image. This value must not be `nil`. - * @param diameter An integer value specifying the diameter size of the avatar in points. This value must be greater than `0`. - * - * @return An initialized `JSQMessagesAvatarImage` object if created successfully, `nil` otherwise. - */ -+ (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image diameter:(NSUInteger)diameter; - -/** - * Returns a copy of the specified image that is cropped to a circle with the given diameter. - * - * @param image The image to crop. This value must not be `nil`. - * @param diameter An integer value specifying the diameter size of the image in points. This value must be greater than `0`. - * - * @return A new image object if successful, `nil` otherwise. - */ -+ (UIImage *)circularAvatarImage:(UIImage *)image withDiameter:(NSUInteger)diameter; - -/** - * Returns a copy of the specified image that is cropped to a circle with the given diameter. - * Additionally, a transparent overlay is applied to the image to represent a pressed or highlighted state. - * - * @param image The image to crop. This value must not be `nil`. - * @param diameter An integer value specifying the diameter size of the image in points. This value must be greater than `0`. - * - * @return A new image object if successful, `nil` otherwise. - */ -+ (UIImage *)circularAvatarHighlightedImage:(UIImage *)image withDiameter:(NSUInteger)diameter; - -/** - * Creates and returns a `JSQMessagesAvatarImage` object with a circular shape that displays the specified userInitials - * with the given backgroundColor, textColor, font, and diameter. - * - * @param userInitials The user initials to display in the avatar image. This value must not be `nil`. - * @param backgroundColor The background color of the avatar. This value must not be `nil`. - * @param textColor The color of the text of the userInitials. This value must not be `nil`. - * @param font The font applied to userInitials. This value must not be `nil`. - * @param diameter The diameter of the avatar image. This value must be greater than `0`. - * - * @return An initialized `JSQMessagesAvatarImage` object if created successfully, `nil` otherwise. - * - * @discussion This method does not attempt to detect or correct incompatible parameters. - * That is to say, you are responsible for providing a font size and diameter that make sense. - * For example, a font size of `14.0f` and a diameter of `34.0f` will result in an avatar similar to Messages in iOS 7. - * However, a font size `30.0f` and diameter of `10.0f` will not produce a desirable image. - * Further, this method does not check the length of userInitials. It is recommended that you pass a string of length `2` or `3`. - */ -+ (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials - backgroundColor:(UIColor *)backgroundColor - textColor:(UIColor *)textColor - font:(UIFont *)font - diameter:(NSUInteger)diameter; - -@end diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.m b/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.m deleted file mode 100644 index 222c2c8f4..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/JSQMessagesAvatarImageFactory.m +++ /dev/null @@ -1,159 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import "JSQMessagesAvatarImageFactory.h" - -#import "UIColor+JSQMessages.h" - - -@implementation JSQMessagesAvatarImageFactory - -#pragma mark - Public - -+ (JSQMessagesAvatarImage *)avatarImageWithPlaceholder:(UIImage *)placeholderImage diameter:(NSUInteger)diameter -{ - UIImage *circlePlaceholderImage = [JSQMessagesAvatarImageFactory jsq_circularImage:placeholderImage - withDiameter:diameter - highlightedColor:nil]; - - return [JSQMessagesAvatarImage avatarImageWithPlaceholder:circlePlaceholderImage]; -} - -+ (JSQMessagesAvatarImage *)avatarImageWithImage:(UIImage *)image diameter:(NSUInteger)diameter -{ - UIImage *avatar = [JSQMessagesAvatarImageFactory circularAvatarImage:image withDiameter:diameter]; - UIImage *highlightedAvatar = [JSQMessagesAvatarImageFactory circularAvatarHighlightedImage:image withDiameter:diameter]; - - return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatar - highlightedImage:highlightedAvatar - placeholderImage:avatar]; -} - -+ (UIImage *)circularAvatarImage:(UIImage *)image withDiameter:(NSUInteger)diameter -{ - return [JSQMessagesAvatarImageFactory jsq_circularImage:image - withDiameter:diameter - highlightedColor:nil]; -} - -+ (UIImage *)circularAvatarHighlightedImage:(UIImage *)image withDiameter:(NSUInteger)diameter -{ - return [JSQMessagesAvatarImageFactory jsq_circularImage:image - withDiameter:diameter - highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; -} - -+ (JSQMessagesAvatarImage *)avatarImageWithUserInitials:(NSString *)userInitials - backgroundColor:(UIColor *)backgroundColor - textColor:(UIColor *)textColor - font:(UIFont *)font - diameter:(NSUInteger)diameter -{ - UIImage *avatarImage = [JSQMessagesAvatarImageFactory jsq_imageWitInitials:userInitials - backgroundColor:backgroundColor - textColor:textColor - font:font - diameter:diameter]; - - UIImage *avatarHighlightedImage = [JSQMessagesAvatarImageFactory jsq_circularImage:avatarImage - withDiameter:diameter - highlightedColor:[UIColor colorWithWhite:0.1f alpha:0.3f]]; - - return [[JSQMessagesAvatarImage alloc] initWithAvatarImage:avatarImage - highlightedImage:avatarHighlightedImage - placeholderImage:avatarImage]; -} - -#pragma mark - Private - -+ (UIImage *)jsq_imageWitInitials:(NSString *)initials - backgroundColor:(UIColor *)backgroundColor - textColor:(UIColor *)textColor - font:(UIFont *)font - diameter:(NSUInteger)diameter -{ - NSParameterAssert(initials != nil); - NSParameterAssert(backgroundColor != nil); - NSParameterAssert(textColor != nil); - NSParameterAssert(font != nil); - NSParameterAssert(diameter > 0); - - CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter); - - NSDictionary *attributes = @{ NSFontAttributeName : font, - NSForegroundColorAttributeName : textColor }; - - CGRect textFrame = [initials boundingRectWithSize:frame.size - options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) - attributes:attributes - context:nil]; - - CGPoint frameMidPoint = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame)); - CGPoint textFrameMidPoint = CGPointMake(CGRectGetMidX(textFrame), CGRectGetMidY(textFrame)); - - CGFloat dx = frameMidPoint.x - textFrameMidPoint.x; - CGFloat dy = frameMidPoint.y - textFrameMidPoint.y; - CGPoint drawPoint = CGPointMake(dx, dy); - UIImage *image = nil; - - UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale); - { - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGContextSetFillColorWithColor(context, backgroundColor.CGColor); - CGContextFillRect(context, frame); - [initials drawAtPoint:drawPoint withAttributes:attributes]; - - image = UIGraphicsGetImageFromCurrentImageContext(); - - } - UIGraphicsEndImageContext(); - - return [JSQMessagesAvatarImageFactory jsq_circularImage:image withDiameter:diameter highlightedColor:nil]; -} - -+ (UIImage *)jsq_circularImage:(UIImage *)image withDiameter:(NSUInteger)diameter highlightedColor:(UIColor *)highlightedColor -{ - NSParameterAssert(image != nil); - NSParameterAssert(diameter > 0); - - CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter); - UIImage *newImage = nil; - - UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale); - { - CGContextRef context = UIGraphicsGetCurrentContext(); - - UIBezierPath *imgPath = [UIBezierPath bezierPathWithOvalInRect:frame]; - [imgPath addClip]; - [image drawInRect:frame]; - - if (highlightedColor != nil) { - CGContextSetFillColorWithColor(context, highlightedColor.CGColor); - CGContextFillEllipseInRect(context, frame); - } - - newImage = UIGraphicsGetImageFromCurrentImageContext(); - - } - UIGraphicsEndImageContext(); - - return newImage; -} - -@end diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.h b/SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.h deleted file mode 100644 index 74d639331..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import - -@interface UIColor (JSQMessages) - -#pragma mark - Message bubble colors - -/** - * @return A color object containing HSB values similar to the iOS 7 messages app green bubble color. - */ -+ (UIColor *)jsq_messageBubbleGreenColor; - -/** - * @return A color object containing HSB values similar to the iOS 7 messages app blue bubble color. - */ -+ (UIColor *)jsq_messageBubbleBlueColor; - -/** - * @return A color object containing HSB values similar to the iOS 7 red color. - */ -+ (UIColor *)jsq_messageBubbleRedColor; - -/** - * @return A color object containing HSB values similar to the iOS 7 messages app light gray bubble color. - */ -+ (UIColor *)jsq_messageBubbleLightGrayColor; - -#pragma mark - Utilities - -/** - * Creates and returns a new color object whose brightness component is decreased by the given value, using the initial color values of the receiver. - * - * @param value A floating point value describing the amount by which to decrease the brightness of the receiver. - * - * @return A new color object whose brightness is decreased by the given values. The other color values remain the same as the receiver. - */ -- (UIColor *)jsq_colorByDarkeningColorWithValue:(CGFloat)value; - -@end diff --git a/SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.m b/SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.m deleted file mode 100644 index 9e254ac56..000000000 --- a/SignalMessaging/Libraries/JSQMessagesViewController/UIColor+JSQMessages.m +++ /dev/null @@ -1,90 +0,0 @@ -// -// Created by Jesse Squires -// http://www.jessesquires.com -// -// -// Documentation -// http://cocoadocs.org/docsets/JSQMessagesViewController -// -// -// GitHub -// https://github.com/jessesquires/JSQMessagesViewController -// -// -// License -// Copyright (c) 2014 Jesse Squires -// Released under an MIT license: http://opensource.org/licenses/MIT -// - -#import "UIColor+JSQMessages.h" - -@implementation UIColor (JSQMessages) - -#pragma mark - Message bubble colors - -+ (UIColor *)jsq_messageBubbleGreenColor -{ - return [UIColor colorWithHue:130.0f / 360.0f - saturation:0.68f - brightness:0.84f - alpha:1.0f]; -} - -+ (UIColor *)jsq_messageBubbleBlueColor -{ - return [UIColor colorWithHue:210.0f / 360.0f - saturation:0.94f - brightness:1.0f - alpha:1.0f]; -} - -+ (UIColor *)jsq_messageBubbleRedColor -{ - return [UIColor colorWithHue:0.0f / 360.0f - saturation:0.79f - brightness:1.0f - alpha:1.0f]; -} - -+ (UIColor *)jsq_messageBubbleLightGrayColor -{ - return [UIColor colorWithHue:240.0f / 360.0f - saturation:0.02f - brightness:0.92f - alpha:1.0f]; -} - -#pragma mark - Utilities - -- (UIColor *)jsq_colorByDarkeningColorWithValue:(CGFloat)value -{ - NSUInteger totalComponents = CGColorGetNumberOfComponents(self.CGColor); - BOOL isGreyscale = (totalComponents == 2) ? YES : NO; - - CGFloat *oldComponents = (CGFloat *)CGColorGetComponents(self.CGColor); - CGFloat newComponents[4]; - - if (isGreyscale) { - newComponents[0] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; - newComponents[1] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; - newComponents[2] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; - newComponents[3] = oldComponents[1]; - } - else { - newComponents[0] = oldComponents[0] - value < 0.0f ? 0.0f : oldComponents[0] - value; - newComponents[1] = oldComponents[1] - value < 0.0f ? 0.0f : oldComponents[1] - value; - newComponents[2] = oldComponents[2] - value < 0.0f ? 0.0f : oldComponents[2] - value; - newComponents[3] = oldComponents[3]; - } - - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGColorRef newColor = CGColorCreate(colorSpace, newComponents); - CGColorSpaceRelease(colorSpace); - - UIColor *retColor = [UIColor colorWithCGColor:newColor]; - CGColorRelease(newColor); - - return retColor; -} - -@end \ No newline at end of file diff --git a/SignalMessaging/ViewModels/ContactShareViewModel.swift b/SignalMessaging/ViewModels/ContactShareViewModel.swift index b4688bae7..e75f1720b 100644 --- a/SignalMessaging/ViewModels/ContactShareViewModel.swift +++ b/SignalMessaging/ViewModels/ContactShareViewModel.swift @@ -49,7 +49,7 @@ public class ContactShareViewModel: NSObject { } @objc - public func getAvatarImage(diameter: CGFloat, contactsManager: OWSContactsManager) -> UIImage { + public func getAvatarImage(diameter: CGFloat, contactsManager: OWSContactsManager) -> UIImage? { if let avatarImage = avatarImage { return avatarImage } diff --git a/SignalMessaging/Views/ContactCellView.m b/SignalMessaging/Views/ContactCellView.m index 8ee98f153..86e4fcc89 100644 --- a/SignalMessaging/Views/ContactCellView.m +++ b/SignalMessaging/Views/ContactCellView.m @@ -200,10 +200,9 @@ const CGFloat kContactCellAvatarTextMargin = 12; return [TSThread stableConversationColorNameForString:self.recipientId]; } }(); - UIColor *color = [UIColor ows_conversationColorForColorName:colorName]; - + self.avatarView.image = [[[OWSContactAvatarBuilder alloc] initWithSignalId:recipientId - color:color + colorName:colorName diameter:kContactCellAvatarSize contactsManager:contactsManager] build]; } diff --git a/SignalMessaging/categories/UIColor+OWS.h b/SignalMessaging/categories/UIColor+OWS.h index d3febf1f5..e1bc79f09 100644 --- a/SignalMessaging/categories/UIColor+OWS.h +++ b/SignalMessaging/categories/UIColor+OWS.h @@ -31,12 +31,10 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - ConversationColor + (nullable UIColor *)ows_conversationColorForColorName:(NSString *)colorName - NS_SWIFT_NAME(ows_conversationColor(colorName:)); -+ (nullable NSString *)ows_conversationColorNameForColor:(UIColor *)color - NS_SWIFT_NAME(ows_conversationColorName(color:)); + isShaded:(BOOL)isShaded + NS_SWIFT_NAME(ows_conversationColor(colorName:isShaded:)); @property (class, readonly, nonatomic) NSArray *ows_conversationColorNames; -@property (class, readonly, nonatomic) NSArray *ows_conversationColors; - (UIColor *)blendWithColor:(UIColor *)otherColor alpha:(CGFloat)alpha; @@ -73,6 +71,8 @@ NS_ASSUME_NONNULL_BEGIN @property (class, readonly, nonatomic) UIColor *ows_grey600Color; @property (class, readonly, nonatomic) UIColor *ows_darkSkyBlueColor; ++ (NSString *)ows_defaultConversationColorName; + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/categories/UIColor+OWS.m b/SignalMessaging/categories/UIColor+OWS.m index b5f067521..4cd87ce14 100644 --- a/SignalMessaging/categories/UIColor+OWS.m +++ b/SignalMessaging/categories/UIColor+OWS.m @@ -251,7 +251,7 @@ NS_ASSUME_NONNULL_BEGIN return [UIColor colorWithRed:32.f / 255.f green:144.f / 255.f blue:234.f / 255.f alpha:1.f]; } -+ (NSDictionary *)ows_conversationColorMap ++ (NSDictionary *)ows_conversationColorMapLight { static NSDictionary *colorMap; static dispatch_once_t onceToken; @@ -273,26 +273,46 @@ NS_ASSUME_NONNULL_BEGIN return colorMap; } -+ (NSArray *)ows_conversationColorNames ++ (NSDictionary *)ows_conversationColorMapDark { - return self.ows_conversationColorMap.allKeys; + static NSDictionary *colorMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + colorMap = @{ + @"red" : self.ows_red700Color, + @"pink" : self.ows_pink600Color, + @"purple" : self.ows_purple600Color, + @"indigo" : self.ows_indigo600Color, + @"blue" : self.ows_blue700Color, + @"cyan" : self.ows_cyan800Color, + @"teal" : self.ows_teal700Color, + @"green" : self.ows_green800Color, + @"deep_orange" : self.ows_deepOrange900Color, + @"grey" : self.ows_grey600Color + }; + }); + + return colorMap; } -+ (NSArray *)ows_conversationColors ++ (NSArray *)ows_conversationColorNames { - return self.ows_conversationColorMap.allValues; + OWSAssertDebug( + [self.ows_conversationColorMapLight.allKeys isEqualToArray:self.ows_conversationColorMapDark.allKeys]); + + return self.ows_conversationColorMapLight.allKeys; } -+ (nullable UIColor *)ows_conversationColorForColorName:(NSString *)colorName ++ (nullable UIColor *)ows_conversationColorForColorName:(NSString *)colorName isShaded:(BOOL)isShaded { OWSAssertDebug(colorName.length > 0); - return [self.ows_conversationColorMap objectForKey:colorName]; + return (isShaded ? self.ows_conversationColorMapDark : self.ows_conversationColorMapLight)[colorName]; } -+ (nullable NSString *)ows_conversationColorNameForColor:(UIColor *)color ++ (NSString *)ows_defaultConversationColorName { - return [self.ows_conversationColorMap allKeysForObject:color].firstObject; + return @"teal"; } @end diff --git a/SignalMessaging/utils/ConversationStyle.swift b/SignalMessaging/utils/ConversationStyle.swift index bfb9a308e..0de990efa 100644 --- a/SignalMessaging/utils/ConversationStyle.swift +++ b/SignalMessaging/utils/ConversationStyle.swift @@ -136,7 +136,8 @@ public class ConversationStyle: NSObject { return self.defaultBubbleColorIncoming } - guard let color = UIColor.ows_conversationColor(colorName: colorName) else { + guard let color = UIColor.ows_conversationColor(colorName: colorName, + isShaded: Theme.isDarkThemeEnabled) else { return self.defaultBubbleColorIncoming } diff --git a/SignalMessaging/utils/OWSAvatarBuilder.h b/SignalMessaging/utils/OWSAvatarBuilder.h index 1f5234432..ec3003730 100644 --- a/SignalMessaging/utils/OWSAvatarBuilder.h +++ b/SignalMessaging/utils/OWSAvatarBuilder.h @@ -1,24 +1,33 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN -@class TSThread; @class OWSContactsManager; +@class TSThread; @class UIImage; @interface OWSAvatarBuilder : NSObject -+ (UIImage *)buildImageForThread:(TSThread *)thread - diameter:(NSUInteger)diameter - contactsManager:(OWSContactsManager *)contactsManager NS_SWIFT_NAME(buildImage(thread:diameter:contactsManager:)); ++ (nullable UIImage *)buildImageForThread:(TSThread *)thread + diameter:(NSUInteger)diameter + contactsManager:(OWSContactsManager *)contactsManager + NS_SWIFT_NAME(buildImage(thread:diameter:contactsManager:)); -+ (UIImage *)buildRandomAvatarWithDiameter:(NSUInteger)diameter; ++ (nullable UIImage *)buildRandomAvatarWithDiameter:(NSUInteger)diameter; - (nullable UIImage *)buildSavedImage; -- (UIImage *)buildDefaultImage; -- (UIImage *)build; +- (nullable UIImage *)buildDefaultImage; +- (nullable UIImage *)build; + ++ (nullable UIImage *)avatarImageWithInitials:(NSString *)initials + backgroundColor:(UIColor *)backgroundColor + diameter:(NSUInteger)diameter; + ++ (nullable UIImage *)avatarImageWithIcon:(UIImage *)icon + backgroundColor:(UIColor *)backgroundColor + diameter:(NSUInteger)diameter; @end diff --git a/SignalMessaging/utils/OWSAvatarBuilder.m b/SignalMessaging/utils/OWSAvatarBuilder.m index e5c0e3060..c826eed61 100644 --- a/SignalMessaging/utils/OWSAvatarBuilder.m +++ b/SignalMessaging/utils/OWSAvatarBuilder.m @@ -3,20 +3,22 @@ // #import "OWSAvatarBuilder.h" -#import "JSQMessagesAvatarImageFactory.h" #import "OWSContactAvatarBuilder.h" #import "OWSGroupAvatarBuilder.h" #import "TSContactThread.h" #import "TSGroupThread.h" #import "UIColor+OWS.h" +#import "UIFont+OWS.h" NS_ASSUME_NONNULL_BEGIN +typedef void (^OWSAvatarDrawBlock)(CGContextRef context); + @implementation OWSAvatarBuilder -+ (UIImage *)buildImageForThread:(TSThread *)thread - diameter:(NSUInteger)diameter - contactsManager:(OWSContactsManager *)contactsManager ++ (nullable UIImage *)buildImageForThread:(TSThread *)thread + diameter:(NSUInteger)diameter + contactsManager:(OWSContactsManager *)contactsManager { OWSAssertDebug(thread); OWSAssertDebug(contactsManager); @@ -25,9 +27,8 @@ NS_ASSUME_NONNULL_BEGIN if ([thread isKindOfClass:[TSContactThread class]]) { TSContactThread *contactThread = (TSContactThread *)thread; NSString *colorName = thread.conversationColorName; - UIColor *color = [UIColor ows_conversationColorForColorName:colorName]; avatarBuilder = [[OWSContactAvatarBuilder alloc] initWithSignalId:contactThread.contactIdentifier - color:color + colorName:colorName diameter:diameter contactsManager:contactsManager]; } else if ([thread isKindOfClass:[TSGroupThread class]]) { @@ -38,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN return [avatarBuilder build]; } -+ (UIImage *)buildRandomAvatarWithDiameter:(NSUInteger)diameter ++ (nullable UIImage *)buildRandomAvatarWithDiameter:(NSUInteger)diameter { NSArray *eyes = @[ @":", @"=", @"8", @"B" ]; NSArray *mouths = @[ @"3", @")", @"(", @"|", @"\\", @"P", @"D", @"o" ]; @@ -50,34 +51,194 @@ NS_ASSUME_NONNULL_BEGIN NSString *randomEyebrow = eyebrows[arc4random_uniform((uint32_t)eyebrows.count)]; NSString *face = [NSString stringWithFormat:@"%@%@%@", randomEyebrow, randomEye, randomMouth]; - CGFloat fontSize = (CGFloat)(diameter / 2.4); + UIColor *backgroundColor = [UIColor colorWithRGBHex:0xaca6633]; + + return [self avatarImageWithDiameter:diameter + backgroundColor:backgroundColor + drawBlock:^(CGContextRef context) { + CGContextTranslateCTM(context, diameter / 2, diameter / 2); + CGContextRotateCTM(context, (CGFloat)M_PI_2); + CGContextTranslateCTM(context, -diameter / 2, -diameter / 2); + + [self drawInitialsInAvatar:face + textColor:self.avatarForegroundColor + font:self.avatarTextFont + diameter:diameter]; + }]; +} + ++ (UIColor *)avatarForegroundColor +{ + return (Theme.isDarkThemeEnabled ? UIColor.ows_gray05Color : UIColor.ows_whiteColor); +} + ++ (UIFont *)avatarTextFont +{ + return [UIFont ows_mediumFontWithSize:20.f]; +} + ++ (nullable UIImage *)avatarImageWithInitials:(NSString *)initials + backgroundColor:(UIColor *)backgroundColor + diameter:(NSUInteger)diameter +{ + return [self avatarImageWithInitials:initials + backgroundColor:backgroundColor + textColor:self.avatarForegroundColor + font:self.avatarTextFont + diameter:diameter]; +} + ++ (nullable UIImage *)avatarImageWithInitials:(NSString *)initials + backgroundColor:(UIColor *)backgroundColor + textColor:(UIColor *)textColor + font:(UIFont *)font + diameter:(NSUInteger)diameter +{ + OWSAssertDebug(initials); + OWSAssertDebug(textColor); + OWSAssertDebug(font); + + return [self avatarImageWithDiameter:diameter + backgroundColor:backgroundColor + drawBlock:^(CGContextRef context) { + [self drawInitialsInAvatar:initials textColor:textColor font:font diameter:diameter]; + }]; +} + ++ (nullable UIImage *)avatarImageWithIcon:(UIImage *)icon + backgroundColor:(UIColor *)backgroundColor + diameter:(NSUInteger)diameter +{ + return [self avatarImageWithIcon:icon + iconColor:self.avatarForegroundColor + backgroundColor:backgroundColor + diameter:diameter]; +} + ++ (nullable UIImage *)avatarImageWithIcon:(UIImage *)icon + iconColor:(UIColor *)iconColor + backgroundColor:(UIColor *)backgroundColor + diameter:(NSUInteger)diameter +{ + OWSAssertDebug(icon); + OWSAssertDebug(iconColor); + + return [self avatarImageWithDiameter:diameter + backgroundColor:backgroundColor + drawBlock:^(CGContextRef context) { + [self drawIconInAvatar:icon iconColor:iconColor diameter:diameter]; + }]; +} - UIImage *srcImage = - [[JSQMessagesAvatarImageFactory avatarImageWithUserInitials:face - backgroundColor:[UIColor colorWithRGBHex:0xaca6633] - textColor:[UIColor whiteColor] - font:[UIFont boldSystemFontOfSize:fontSize] - diameter:diameter] avatarImage]; ++ (nullable UIImage *)avatarImageWithDiameter:(NSUInteger)diameter + backgroundColor:(UIColor *)backgroundColor + drawBlock:(OWSAvatarDrawBlock)drawBlock +{ + OWSAssertDebug(drawBlock); + OWSAssertDebug(backgroundColor); + OWSAssertDebug(diameter > 0); - UIGraphicsBeginImageContext(srcImage.size); + CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter); - CGContextRef context = UIGraphicsGetCurrentContext(); + UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale); + CGContextRef _Nullable context = UIGraphicsGetCurrentContext(); + if (!context) { + return nil; + } - CGFloat width = srcImage.size.width; + CGContextSetFillColorWithColor(context, backgroundColor.CGColor); + CGContextFillRect(context, frame); - // Rotate - CGContextTranslateCTM(context, width / 2, width / 2); - CGContextRotateCTM(context, (CGFloat)M_PI_2); - CGContextTranslateCTM(context, -width / 2, -width / 2); + // Gradient + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); + CGFloat gradientLocations[] = { 0.0, 1.0 }; + CGGradientRef _Nullable gradient = CGGradientCreateWithColors(colorspace, + (__bridge CFArrayRef) @[ + (id)[UIColor colorWithWhite:0.f alpha:0.f].CGColor, + (id)[UIColor colorWithWhite:0.f alpha:0.15f].CGColor, + ], + gradientLocations); + if (!gradient) { + return nil; + } + CGPoint startPoint = CGPointMake(diameter * 0.5f, 0); + CGPoint endPoint = CGPointMake(diameter * 0.5f, diameter); + CGContextDrawLinearGradient(context, + gradient, + startPoint, + endPoint, + kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); + CFRelease(gradient); - [srcImage drawAtPoint:CGPointMake(0, 0)]; + CGContextSaveGState(context); + drawBlock(context); + CGContextRestoreGState(context); + + UIImage *_Nullable image = UIGraphicsGetImageFromCurrentImageContext(); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); + return image; } -- (UIImage *)build ++ (void)drawInitialsInAvatar:(NSString *)initials + textColor:(UIColor *)textColor + font:(UIFont *)font + diameter:(NSUInteger)diameter +{ + OWSAssertDebug(initials); + OWSAssertDebug(textColor); + OWSAssertDebug(font); + OWSAssertDebug(diameter > 0); + + CGRect frame = CGRectMake(0.0f, 0.0f, diameter, diameter); + + NSDictionary *textAttributes = @{ + NSFontAttributeName : font, + NSForegroundColorAttributeName : textColor, + }; + CGSize textSize = + [initials boundingRectWithSize:frame.size + options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) + attributes:textAttributes + context:nil] + .size; + // Ensure that the text fits within the avatar bounds, with a margin. + if (textSize.width > 0 && textSize.height > 0) { + CGFloat textDiameter = (CGFloat)sqrt(textSize.width * textSize.width + textSize.height * textSize.height); + // Leave a 10% margin. + CGFloat maxTextDiameter = diameter * 0.9f; + if (textDiameter > maxTextDiameter) { + font = [font fontWithSize:font.pointSize * maxTextDiameter / textDiameter]; + textAttributes = @{ + NSFontAttributeName : font, + NSForegroundColorAttributeName : textColor, + }; + textSize = + [initials boundingRectWithSize:frame.size + options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) + attributes:textAttributes + context:nil] + .size; + } + } + + CGPoint drawPoint = CGPointMake((diameter - textSize.width) * 0.5f, (diameter - textSize.height) * 0.5f); + + [initials drawAtPoint:drawPoint withAttributes:textAttributes]; +} + ++ (void)drawIconInAvatar:(UIImage *)icon iconColor:(UIColor *)iconColor diameter:(NSUInteger)diameter +{ + OWSAssertDebug(icon); + OWSAssertDebug(iconColor); + OWSAssertDebug(diameter > 0); + + CGPoint drawPoint = CGPointMake((diameter - icon.size.width) * 0.5f, (diameter - icon.size.height) * 0.5f); + [icon drawAtPoint:drawPoint]; +} + +- (nullable UIImage *)build { UIImage *_Nullable savedImage = [self buildSavedImage]; if (savedImage) { @@ -93,10 +254,10 @@ NS_ASSUME_NONNULL_BEGIN return nil; } -- (UIImage *)buildDefaultImage +- (nullable UIImage *)buildDefaultImage { OWSAbstractMethod(); - return [UIImage new]; + return nil; } @end diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.h b/SignalMessaging/utils/OWSContactAvatarBuilder.h index f3fef522e..12bf6b61c 100644 --- a/SignalMessaging/utils/OWSContactAvatarBuilder.h +++ b/SignalMessaging/utils/OWSContactAvatarBuilder.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithSignalId:(NSString *)signalId - color:(UIColor *)color + colorName:(NSString *)colorName diameter:(NSUInteger)diameter contactsManager:(OWSContactsManager *)contactsManager; diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.m b/SignalMessaging/utils/OWSContactAvatarBuilder.m index 04c75a4d8..0e70b527e 100644 --- a/SignalMessaging/utils/OWSContactAvatarBuilder.m +++ b/SignalMessaging/utils/OWSContactAvatarBuilder.m @@ -10,7 +10,6 @@ #import "UIColor+OWS.h" #import "UIFont+OWS.h" #import -#import "JSQMessagesAvatarImageFactory.h" NS_ASSUME_NONNULL_BEGIN @@ -19,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) NSString *signalId; @property (nonatomic, readonly) NSString *contactName; -@property (nonatomic, readonly) UIColor *color; +@property (nonatomic, readonly) NSString *colorName; @property (nonatomic, readonly) NSUInteger diameter; @end @@ -30,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithContactId:(NSString *)contactId name:(NSString *)name - color:(UIColor *)color + colorName:(NSString *)colorName diameter:(NSUInteger)diameter contactsManager:(OWSContactsManager *)contactsManager { @@ -39,9 +38,11 @@ NS_ASSUME_NONNULL_BEGIN return self; } + OWSAssertDebug(colorName.length > 0); + _signalId = contactId; _contactName = name; - _color = color; + _colorName = colorName; _diameter = diameter; _contactsManager = contactsManager; @@ -49,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN } - (instancetype)initWithSignalId:(NSString *)signalId - color:(UIColor *)color + colorName:(NSString *)colorName diameter:(NSUInteger)diameter contactsManager:(OWSContactsManager *)contactsManager { @@ -61,7 +62,11 @@ NS_ASSUME_NONNULL_BEGIN if (name.length == 0) { name = signalId; } - return [self initWithContactId:signalId name:name color:color diameter:diameter contactsManager:contactsManager]; + return [self initWithContactId:signalId + name:name + colorName:colorName + diameter:diameter + contactsManager:contactsManager]; } - (instancetype)initWithNonSignalName:(NSString *)nonSignalName @@ -71,11 +76,9 @@ NS_ASSUME_NONNULL_BEGIN { NSString *colorName = [TSThread stableConversationColorNameForString:colorSeed]; - UIColor *color = [UIColor ows_conversationColorForColorName:colorName]; - OWSAssertDebug(color); return [self initWithContactId:colorSeed name:nonSignalName - color:color + colorName:(NSString *)colorName diameter:diameter contactsManager:contactsManager]; } @@ -87,10 +90,15 @@ NS_ASSUME_NONNULL_BEGIN return [self.contactsManager imageForPhoneIdentifier:self.signalId]; } -- (UIImage *)buildDefaultImage +- (id)cacheKey +{ + return [NSString stringWithFormat:@"%@-%d", self.signalId, Theme.isDarkThemeEnabled]; +} + +- (nullable UIImage *)buildDefaultImage { UIImage *cachedAvatar = - [self.contactsManager.avatarCache imageForKey:self.signalId diameter:(CGFloat)self.diameter]; + [self.contactsManager.avatarCache imageForKey:self.cacheKey diameter:(CGFloat)self.diameter]; if (cachedAvatar) { return cachedAvatar; } @@ -121,19 +129,19 @@ NS_ASSUME_NONNULL_BEGIN [initials appendString:@"#"]; } - CGFloat fontSize = (CGFloat)self.diameter / 2.8f; - - UIImage *image = [[JSQMessagesAvatarImageFactory avatarImageWithUserInitials:initials - backgroundColor:self.color - textColor:[UIColor whiteColor] - font:[UIFont ows_boldFontWithSize:fontSize] - diameter:self.diameter] avatarImage]; + UIColor *color = [UIColor ows_conversationColorForColorName:self.colorName isShaded:Theme.isDarkThemeEnabled]; + OWSAssertDebug(color); - [self.contactsManager.avatarCache setImage:image forKey:self.signalId diameter:self.diameter]; + UIImage *_Nullable image = + [OWSAvatarBuilder avatarImageWithInitials:initials backgroundColor:color diameter:self.diameter]; + if (!image) { + return nil; + } + + [self.contactsManager.avatarCache setImage:image forKey:self.cacheKey diameter:self.diameter]; return image; } - @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/utils/OWSGroupAvatarBuilder.m b/SignalMessaging/utils/OWSGroupAvatarBuilder.m index 8a832a0d4..30fd02bdc 100644 --- a/SignalMessaging/utils/OWSGroupAvatarBuilder.m +++ b/SignalMessaging/utils/OWSGroupAvatarBuilder.m @@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN return self.thread.groupModel.groupImage; } -- (UIImage *)buildDefaultImage +- (nullable UIImage *)buildDefaultImage { return self.class.defaultGroupImage; } From 8910f1f65a56b0066e73bbb12361165ebc6a6d9b Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 24 Sep 2018 15:22:00 -0400 Subject: [PATCH 2/6] Enable conversation colors. --- .../ThreadSettings/OWSConversationSettingsViewController.m | 2 -- SignalServiceKit/src/TSConstants.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index d601e1345..bafb30ed5 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -285,7 +285,6 @@ const CGFloat kIconViewLength = 24; [weakSelf showMediaGallery]; }]]; -#ifdef CONVERSATION_COLORS_ENABLED [mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ NSString *colorName = self.thread.conversationColorName; @@ -299,7 +298,6 @@ const CGFloat kIconViewLength = 24; actionBlock:^{ [weakSelf showColorPicker]; }]]; -#endif if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing && !self.hasExistingContact) { diff --git a/SignalServiceKit/src/TSConstants.h b/SignalServiceKit/src/TSConstants.h index 375f09609..391c514be 100644 --- a/SignalServiceKit/src/TSConstants.h +++ b/SignalServiceKit/src/TSConstants.h @@ -17,8 +17,6 @@ typedef NS_ENUM(NSInteger, TSWhisperMessageType) { #define textSecureHTTPTimeOut 10 -//#define CONVERSATION_COLORS_ENABLED - #define kLegalTermsUrlString @"https://signal.org/legal/" #define SHOW_LEGAL_TERMS_LINK From 7b2dd19fb8c94e7309b3137d8fed55ba5513957c Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 25 Sep 2018 16:05:53 -0400 Subject: [PATCH 3/6] Respond to CR. --- .../ConversationView/Cells/OWSMessageCell.m | 4 ++-- .../ConversationView/ConversationViewItem.h | 3 +++ .../ConversationView/ConversationViewItem.m | 9 +++++++-- SignalMessaging/utils/OWSAvatarBuilder.m | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 219abf3d4..1407db90d 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -268,8 +268,8 @@ NS_ASSUME_NONNULL_BEGIN return NO; } - TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction; - TSThread *authorThread = [TSContactThread getOrCreateThreadWithContactId:incomingMessage.authorId]; + OWSAssertDebug(self.viewItem.incomingMessageAuthorThread); + TSThread *authorThread = self.viewItem.incomingMessageAuthorThread; UIImage *_Nullable authorAvatarImage = [OWSAvatarBuilder buildImageForThread:authorThread diameter:self.avatarSize contactsManager:contactsManager]; self.avatarView.image = authorAvatarImage; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index 794d250b6..efa514bb2 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -33,6 +33,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @class TSAttachmentPointer; @class TSAttachmentStream; @class TSInteraction; +@class TSThread; @class YapDatabaseReadTransaction; // This is a ViewModel for cells in the conversation view. @@ -45,6 +46,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @interface ConversationViewItem : NSObject @property (nonatomic, readonly) TSInteraction *interaction; +@property (nonatomic, nullable, readonly) TSThread *incomingMessageAuthorThread; + @property (nonatomic, readonly, nullable) OWSQuotedReplyModel *quotedReply; @property (nonatomic, readonly) BOOL isGroupThread; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index 3903de6bb..ffa56f9d2 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -63,12 +63,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) @property (nonatomic, nullable) DisplayableText *displayableBodyText; @property (nonatomic, nullable) DisplayableText *displayableQuotedText; @property (nonatomic, nullable) OWSQuotedReplyModel *quotedReply; -@property (nonatomic, readonly, nullable) NSString *quotedAttachmentMimetype; -@property (nonatomic, readonly, nullable) NSString *quotedRecipientId; @property (nonatomic, nullable) TSAttachmentStream *attachmentStream; @property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer; @property (nonatomic, nullable) ContactShareViewModel *contactShare; @property (nonatomic) CGSize mediaSize; +@property (nonatomic, nullable) TSThread *incomingMessageAuthorThread; @end @@ -94,6 +93,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) _interaction = interaction; _isGroupThread = isGroupThread; _conversationStyle = conversationStyle; + _incomingMessageAuthorThread = ([interaction isKindOfClass:[TSIncomingMessage class]] + ? [TSContactThread getOrCreateThreadWithContactId:((TSIncomingMessage *)interaction).authorId] + : nil); [self ensureViewState:transaction]; @@ -105,6 +107,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) OWSAssertDebug(interaction); _interaction = interaction; + _incomingMessageAuthorThread = ([interaction isKindOfClass:[TSIncomingMessage class]] + ? [TSContactThread getOrCreateThreadWithContactId:((TSIncomingMessage *)interaction).authorId] + : nil); self.hasViewState = NO; self.messageCellType = OWSMessageCellType_Unknown; diff --git a/SignalMessaging/utils/OWSAvatarBuilder.m b/SignalMessaging/utils/OWSAvatarBuilder.m index c826eed61..3d66671b4 100644 --- a/SignalMessaging/utils/OWSAvatarBuilder.m +++ b/SignalMessaging/utils/OWSAvatarBuilder.m @@ -221,6 +221,8 @@ typedef void (^OWSAvatarDrawBlock)(CGContextRef context); context:nil] .size; } + } else { + OWSFailDebug(@"Text has invalid bounds."); } CGPoint drawPoint = CGPointMake((diameter - textSize.width) * 0.5f, (diameter - textSize.height) * 0.5f); From 4186ce9a7211e77a2f6d2dcd164587be567f4e50 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 25 Sep 2018 17:05:26 -0400 Subject: [PATCH 4/6] Respond to CR. --- .../ViewControllers/ConversationView/ConversationViewItem.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index ffa56f9d2..d2596e379 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -94,7 +94,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) _isGroupThread = isGroupThread; _conversationStyle = conversationStyle; _incomingMessageAuthorThread = ([interaction isKindOfClass:[TSIncomingMessage class]] - ? [TSContactThread getOrCreateThreadWithContactId:((TSIncomingMessage *)interaction).authorId] + ? [TSContactThread getThreadWithContactId:((TSIncomingMessage *)interaction).authorId + transaction:transaction] : nil); [self ensureViewState:transaction]; @@ -108,7 +109,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) _interaction = interaction; _incomingMessageAuthorThread = ([interaction isKindOfClass:[TSIncomingMessage class]] - ? [TSContactThread getOrCreateThreadWithContactId:((TSIncomingMessage *)interaction).authorId] + ? [TSContactThread getThreadWithContactId:((TSIncomingMessage *)interaction).authorId + transaction:transaction] : nil); self.hasViewState = NO; From 72562920eda62c9e12ca88c607fd1ba1d92ec95b Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 26 Sep 2018 09:19:12 -0400 Subject: [PATCH 5/6] Fix author conversation colors. --- .../ConversationView/Cells/OWSMessageCell.m | 8 ++++-- .../ConversationView/ConversationViewItem.h | 6 ++-- .../ConversationView/ConversationViewItem.m | 28 +++++++++++++------ SignalServiceKit/src/Contacts/TSThread.h | 1 + .../src/Contacts/Threads/TSContactThread.h | 6 ++++ .../src/Contacts/Threads/TSContactThread.m | 13 +++++++++ 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 1407db90d..4296629bb 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -268,10 +268,12 @@ NS_ASSUME_NONNULL_BEGIN return NO; } - OWSAssertDebug(self.viewItem.incomingMessageAuthorThread); - TSThread *authorThread = self.viewItem.incomingMessageAuthorThread; + TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.viewItem.interaction; UIImage *_Nullable authorAvatarImage = - [OWSAvatarBuilder buildImageForThread:authorThread diameter:self.avatarSize contactsManager:contactsManager]; + [[[OWSContactAvatarBuilder alloc] initWithSignalId:incomingMessage.authorId + colorName:self.viewItem.authorConversationColorName + diameter:self.avatarSize + contactsManager:contactsManager] build]; self.avatarView.image = authorAvatarImage; [self.contentView addSubview:self.avatarView]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index efa514bb2..bc8bc9526 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -46,7 +46,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @interface ConversationViewItem : NSObject @property (nonatomic, readonly) TSInteraction *interaction; -@property (nonatomic, nullable, readonly) TSThread *incomingMessageAuthorThread; @property (nonatomic, readonly, nullable) OWSQuotedReplyModel *quotedReply; @@ -112,7 +111,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic, readonly, nullable) ContactShareViewModel *contactShare; -@property (nonatomic, nullable) NSString *systemMessageText; +@property (nonatomic, readonly, nullable) NSString *systemMessageText; + +// NOTE: This property is only set for incoming messages. +@property (nonatomic, readonly, nullable) NSString *authorConversationColorName; #pragma mark - MessageActions diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index d2596e379..4aed320f4 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -67,7 +67,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) @property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer; @property (nonatomic, nullable) ContactShareViewModel *contactShare; @property (nonatomic) CGSize mediaSize; +@property (nonatomic, nullable) NSString *systemMessageText; @property (nonatomic, nullable) TSThread *incomingMessageAuthorThread; +@property (nonatomic, nullable) NSString *authorConversationColorName; @end @@ -93,10 +95,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) _interaction = interaction; _isGroupThread = isGroupThread; _conversationStyle = conversationStyle; - _incomingMessageAuthorThread = ([interaction isKindOfClass:[TSIncomingMessage class]] - ? [TSContactThread getThreadWithContactId:((TSIncomingMessage *)interaction).authorId - transaction:transaction] - : nil); + + [self updateAuthorConversationColorNameWithTransaction:transaction]; [self ensureViewState:transaction]; @@ -108,10 +108,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) OWSAssertDebug(interaction); _interaction = interaction; - _incomingMessageAuthorThread = ([interaction isKindOfClass:[TSIncomingMessage class]] - ? [TSContactThread getThreadWithContactId:((TSIncomingMessage *)interaction).authorId - transaction:transaction] - : nil); self.hasViewState = NO; self.messageCellType = OWSMessageCellType_Unknown; @@ -123,11 +119,27 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.quotedReply = nil; self.systemMessageText = nil; + [self updateAuthorConversationColorNameWithTransaction:transaction]; + [self clearCachedLayoutState]; [self ensureViewState:transaction]; } +- (void)updateAuthorConversationColorNameWithTransaction:(YapDatabaseReadTransaction *)transaction +{ + OWSAssertDebug(transaction); + + if (self.interaction.interactionType != OWSInteractionType_IncomingMessage) { + _authorConversationColorName = nil; + return; + } + + TSIncomingMessage *incomingMessage = (TSIncomingMessage *)self.interaction; + _authorConversationColorName = + [TSContactThread conversationColorNameForRecipientId:incomingMessage.authorId transaction:transaction]; +} + - (BOOL)hasBodyText { return _displayableBodyText != nil; diff --git a/SignalServiceKit/src/Contacts/TSThread.h b/SignalServiceKit/src/Contacts/TSThread.h index 3adefacbf..0ebcb0723 100644 --- a/SignalServiceKit/src/Contacts/TSThread.h +++ b/SignalServiceKit/src/Contacts/TSThread.h @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)name; @property (readonly, nullable) NSString *conversationColorName; + - (void)updateConversationColorName:(NSString *)colorName transaction:(YapDatabaseReadWriteTransaction *)transaction; + (NSString *)stableConversationColorNameForString:(NSString *)colorSeed; diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.h b/SignalServiceKit/src/Contacts/Threads/TSContactThread.h index 23c8dbace..87001b9b3 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.h +++ b/SignalServiceKit/src/Contacts/Threads/TSContactThread.h @@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN + (NSString *)threadIdFromContactId:(NSString *)contactId; #endif +// This method can be used to get the conversation color for a given +// recipient without using a read/write transaction to create a +// contact thread. ++ (NSString *)conversationColorNameForRecipientId:(NSString *)recipientId + transaction:(YapDatabaseReadTransaction *)transaction; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m index a6dc7cb03..d8c47a66b 100644 --- a/SignalServiceKit/src/Contacts/Threads/TSContactThread.m +++ b/SignalServiceKit/src/Contacts/Threads/TSContactThread.m @@ -91,6 +91,19 @@ NS_ASSUME_NONNULL_BEGIN return [threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]; } ++ (NSString *)conversationColorNameForRecipientId:(NSString *)recipientId + transaction:(YapDatabaseReadTransaction *)transaction +{ + OWSAssertDebug(recipientId.length > 0); + + TSContactThread *_Nullable contactThread = + [TSContactThread getThreadWithContactId:recipientId transaction:transaction]; + if (contactThread) { + return contactThread.conversationColorName; + } + return [self stableConversationColorNameForString:recipientId]; +} + @end NS_ASSUME_NONNULL_END From 2f9eae5cafa93b1fac30ac590c9c719ab587f07f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 27 Sep 2018 08:50:52 -0400 Subject: [PATCH 6/6] Respond to CR. --- SignalMessaging/utils/OWSContactAvatarBuilder.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SignalMessaging/utils/OWSContactAvatarBuilder.m b/SignalMessaging/utils/OWSContactAvatarBuilder.m index 0e70b527e..a440fd12d 100644 --- a/SignalMessaging/utils/OWSContactAvatarBuilder.m +++ b/SignalMessaging/utils/OWSContactAvatarBuilder.m @@ -135,6 +135,7 @@ NS_ASSUME_NONNULL_BEGIN UIImage *_Nullable image = [OWSAvatarBuilder avatarImageWithInitials:initials backgroundColor:color diameter:self.diameter]; if (!image) { + OWSFailDebug(@"Could not generate avatar."); return nil; }